Okay, so I am able to edit the content of my DIV just fine. However, there are a couple things I'm trying to work around.
In my editable div, I have a div containing a site address (such as twitter.com), I don't want that to be deletable from the editable DIV; the reason it's in there is so a user could copy all of the text including the domain.
If you remove all of the text except the domain, then the caret doesn't show up - or it does for a second all the way at the right side of the DIV.
And if a user deletes the domain name, it reappears but the caret is now to the left of it which I also don't want.
Any solutions on making the caret always visible and keeping it to the right of the domain?
Oh, and for some reason I can't use onKeyDown on the editable DIV to activate the anylyeText function (at least not in JSfiddle) so I had to do a setTimeout loop.
Here is my fiddle: jsfiddle
CSS:
.url {
display: block;
border: 1px solid rgb(140,140,140);
padding: 5px;
box-sizing: border-box;
color: rgb(35,155,215);
cursor: text;
outline: none;
}
.url:focus {
border-color: rgb(35,155,215);
}
.noedit {
display: inline;
color: rgb(140,140,140);
}
HTML:
<div class="url" contenteditable="true" id="textbox"><div class="noedit" contenteditable="false">http://twitter.com/</div>username</div>
JS:
var analyzeText = function() {
var textbox = document.getElementById('textbox');
var textVal = textbox.innerHTML;
var urlString = '<div class="noedit" contenteditable="false">http://twitter.com/</div>';
if (textVal.length < urlString.length) {
textbox.innerHTML = urlString;
textbox.focus();
}
setTimeout(function() { analyzeText(); }, 100);
};
(function(){analyzeText();})();
Here's a method using Selection and Range.
MDN Selection
MDN Range
(If you check out MDN, you'll notice that IE9 support isn't there - prior to IE9, you had to use proprietary IE script. Google can help you out there!)
First thing I do is add these two variables:
var range = document.createRange();
var sel = window.getSelection();
Then, I modified your script like so:
if (textVal.length < urlString.length) {
textbox.innerHTML = urlString;
// We create a text node and append to the textbox to select it
textbox.appendChild(document.createTextNode(''));
// Text node is selected
range.setStart(textbox.childNodes[1], 0);
// Collapse our range to single point (we're not selecting a word, after all!)
range.collapse(true);
// Let's clear any selections
sel.removeAllRanges();
// Alright, time to position our cursor caret!
sel.addRange(range);
textbox.focus();
}
And that's it! Here's an updated Fiddle to demonstrate:
Edit - Updated to include logic to prevent user from inserting text to the left of the domain
http://jsfiddle.net/Qycw2/6/
Related
Let's say I have a text with mark, such as:
And I would like to disable selecting such that start or end of selection is not in the mark. Therefore, all of these selections should be possible:
On the other hand, these selections should be disabled:
So far, I tried only using some simple css,
mark {
-khtml-user-select: all;
-webkit-user-select: all;
-o-user-select: all;
user-select: all;
}
This is <mark>marked text</mark>. I want to disable selecting only part of <mark>marked text</mark>.
jsfiddle link
Is there any way to do this? Any answer would be appreciated!
I achieved what I wanted to do, so I share it here.
function checkSelection () {
var sel = window.getSelection();
var marks = document.getElementsByTagName('mark');
for(var i = 1; i < sel.rangeCount; i++) {
sel.removeRange(sel.getRangeAt(i));
}
var range = sel.getRangeAt(0);
var startnode = range.startContainer;
var endnode = range.endContainer;
for (var i = 0; i < marks.length; i++) {
if (marks[i].contains(startnode)) {
range.setStartBefore(startnode);
}
if (marks[i].contains(endnode)) {
range.setEndAfter(endnode);
}
}
}
document.addEventListener('mouseup', checkSelection)
document.addEventListener('touchend', checkSelection)
This is <mark>marked text</mark>. I want to disable selecting only part of <mark>marked text</mark>.
jsfiddle link
I don't think it would be possible to do with CSS alone, you're most likely going to need JavaScript to detect which elements the user began selecting on.
What I did was I used the containsNode method from the Selection API (a Working Draft, may not work with all browsers yet, and may be deprecated) to detect whether the selected range either contains the whole marked element or not at all. If the selection only contains part of a marked element, it would clear the selection using the removeAllRanges method.
function checkSelection () {
var sel = window.getSelection()
var marks = document.getElementsByTagName('mark')
for (var i = 0; i < marks.length; i++) {
if (sel.containsNode(marks[i], false) !== sel.containsNode(marks[i], true))
sel.removeAllRanges() // clear selection
}
}
document.addEventListener('mouseup', checkSelection)
document.addEventListener('touchend', checkSelection)
mark {
background: yellow;
}
This is <mark>marked text</mark>. I want to disable selecting only part of <mark>marked text</mark>.
Note that you would need to capture all methods of selection that you want, whether by mouse events and touch events (all that I included), keyboard caret selection, or programmatic selection.
This can be extended and tuned to your liking, but this is my best shot at replicating the behavior you want from your specs. The above snippet doesn't work correctly all the time if one of the selection's bounds begins or ends at the edge of a marked region, but the snippet should demonstrate the basic concept behind what should work, just with a little fine-tuning for edge cases.
user-select CSS property is not yet official and it is only supported by the latest browsers.
Anyways you can use user-select: none; and enclose all text that can not be selected inside a span tag
https://developer.mozilla.org/en-US/docs/Web/CSS/user-select
But you can try something like this
.unselectable {
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
<p>This text can be selected, But <span class="unselectable">this</span> can only be <span class="unselectable">partially se</span>lected</p>
https://developer.mozilla.org/en-US/docs/Web/CSS/user-select
I have this code
elementSpan = document.createElement('span').className = 'mainSpan';
elementSpan.innerHTML = '<span class="childSpan">A</sapn>';
range.insertNode(elementSpan);
range.setStartAfter(elementSpan); //the problem am having
Now I set the cursor to be after the elementSpan after inserting it into a contenteditable div but instead of the cursor to be after the elementSpan, it goes after the text A which is inside the <span class="childSpan"> but I want it to be after the elementSpan. Please anyone with a clue on how to do this?
Went through your question again, this is the closest I could get to work. This creates a dummy a with at the end and moves cursor past it. Both a and are important as without these some browsers (Safari, Chrome) force the text typed to go inside last element due to some optimisations. The code moves cursor to next element if there is one already, it can be removed by removing the if.
I am not an expert in range/selection APIs. Looking forward to see other interesting answers.
elementSpan = document.createElement('span');
elementSpan.className = 'mainSpan';
document.getElementById('ce').appendChild(elementSpan);
elementSpan.innerHTML = '<span id="childSpan">Another text</span>'; //the problem am having
setCursorAfterElement(document.getElementById('childSpan'));
function setCursorAfterElement(ele) {
if (!ele.nextElementSibling) {
var dummyElement = document.createElement('a')
dummyElement.appendChild(document.createTextNode('\u00A0'))
ele.parentNode.appendChild(dummyElement)
}
var nextElement = ele.nextElementSibling
nextElement.tabIndex=0
nextElement.focus()
var r = document.createRange();
r.setStart(nextElement.childNodes[0], 1);
r.setEnd(nextElement.childNodes[0], 1);
var s = window.getSelection();
s.removeAllRanges();
s.addRange(r);
//document.execCommand("delete", false, null);
}
#childSpan {
border: 1px solid red;
margin: 0px;
}
<div id="ce" contenteditable="true">
</div>
suppose to be we have a paragraph with this content " Hi , It's a new question in stackoverflow!"
and when we are selecting something in this paragraph , it's turn to be Red .for example we selected stackoverflow & then it turn to <span class="red">stackoverflow</span>.how can we do this with Javascript?
here is my codes :
var x = {};
x.getSelected = function() {
var t = '';
if (window.getSelection) {
t = window.getSelection();
} else if (document.getSelection) {
t = document.getSelection();
} else if (document.selection) {
t = document.selection.createRange().text;
}
return t;
}
$(document).ready(function() {
var selectedText;
$(document).bind("mouseup", function() {
selectedText = x.getSelected()
if (selectedText !=''){
alert(selectedText);
//Now I wanna set new content for selected item but not working
a=selectedText;
selectedText.html("<span class='red'>"+a+"</span>");
}
});
});
.red {
color : red;
}
<p>suppose to be we have a paragraph with this content " Hi , It's a new question in stackoverflow!" and when we are selecting something in this paragraph , it's turn to be Red .for example we selected stackoverflow & then it turn to .how can we do this with Javascript? </p>
...when we are selecting something in this paragraph , it's turn to be
Red...
You could have a stab at the styleWithCSS command of the editing API, execCommand that is.
However, before proceeding please note that:
This spec is incomplete and it is not expected that it will advance
beyond draft status. Authors should not use most of these features
directly, but instead use JavaScript editing libraries. The features
described in this document are not implemented consistently or fully
by user agents, and it is not expected that this will change in the
foreseeable future.... This spec is to meant to help implementations
in standardizing these existing features. It is predicted that in the
future both specs will be replaced by Content Editable Events and
Input Events....
Having clarified that, the following will work in most modern browsers viz. Edge, FireFox and Chrome that I could test in.
By default the foreColor command of execCommand wraps the selected text with a font tag, which is deprecated. So, you need to use the styleWithCSS command. Now this works with the editing API, which means that the element you are trying to work with, should have its contentEditable attribute set.
To work around this, you can temporarily set this attribute just before changing the color in the selected text fragment and then resetting the attribute once done.
Given your paragraph like this:
<p id="p">
Hi , It's a new question in StackOverflow!
</p>
When you select the word StackOverflow, the following code will result in this...
<p id="p">
Hi , It's a new question in <span style="color: rgb(255, 0, 0);">StackOverflow</span>!
</p>
... wrapping your selected text in a span with the style applied.
Fiddle: http://jsfiddle.net/abhitalks/j9w6dj7m/
Snippet:
p = document.getElementById('p');
p.addEventListener('mouseup', setColor);
function setColor() {
p.setAttribute('contentEditable', true);
document.execCommand('styleWithCSS', false, true);
document.execCommand('foreColor', false, "#f00");
p.setAttribute('contentEditable', false);
}
<p id="p" contentEditable="false">
Hi , It's a new question in stackoverflow!
</p>
Edit:
Now that you have added code (and what you have already tried) in your question, you could use the range selection to do what you are after.
Specifically, you will need to learn:
selection: https://developer.mozilla.org/en-US/docs/Web/API/Selection, this you have already done. Cheers!
range: https://developer.mozilla.org/en-US/docs/Web/API/Range/Range, because you will be dealing with ranges here
selection.getRangeAt(): https://developer.mozilla.org/en-US/docs/Web/API/Selection/getRangeAt, because you will need to extract the selected text as a range object
range.surroundContents(): https://developer.mozilla.org/en-US/docs/Web/API/range/surroundContents, because you will need to surround the selected text range with a span.
Putting it all together all you have to do is (explanation in code comments):
function setClass() {
var selection = x.getSelected(), range, // you have already done this
span = document.createElement("span"); // create a span element
span.classList.add('red'); // add the class to the span
if (selection != '') {
range = selection.getRangeAt(0); // get the range from selected text
range.surroundContents(span); // surround the range with span
}
}
Fiddle 2: http://jsfiddle.net/abhitalks/kn0u5frj/
Snippet 2:
var x = {},
p = document.getElementById('p');
p.addEventListener('mouseup', setClass);
function setClass() {
var selection = x.getSelected(), range,
span = document.createElement("span");
span.classList.add('red');
if (selection != '') {
range = selection.getRangeAt(0);
range.surroundContents(span);
}
}
x.getSelected = function() {
var t = '';
if (window.getSelection) {
t = window.getSelection();
} else if (document.getSelection) {
t = document.getSelection();
} else if (document.selection) {
t = document.selection.createRange().text;
}
return t;
}
.red { color: #f00; }
<p id="p">
Hi , It's a new question in stackoverflow!
</p>
You can use the getSelection() method
Below is the example:
Repeated Question:
How to get selected text in textarea?
You can use CSS with :: selection http://caniuse.com/#search=%3A%3Aselection
::selection {
background: #ffb7b7; /* WebKit/Blink Browsers */
}
::-moz-selection {
background: #ffb7b7; /* Gecko Browsers */
}
Or javascript with range
I need to select text from different paragraphs and make a span for showing this text. See this example:
<p> this is a text </p>
<p>hello ever one </p>
Now what I want is that if I select text from the web view in my iPhone app it highlights it in a different color. For this I am making a span and setting its style. It works fine for the same paragraph but not for different paragraphs. See this:
<p> this <span class="blue">is a </span> text </p>
Class blue declares its style and it works fine, but the following does not work:
<span class="blue">
<p> this is a text </p>
<p>hello ever </span> one </p>
For solving this problem I need two spans for both paragraphs. So how can I check where the new paragraph starts? The correct HTML code is:
<span class="blue">
<p> this is a text </p></span>
<p> <span class="blue"> hello ever </span> one </p>
I need to get this HTML string but I get the wrong one. I have written a JavaScript function that gets the selection and makes a span according to selection. But on selecting text from two paragraphs it does not work because it gives the wrong section of HTML code. See my JavaScript code:
function highlightsText()
{
var range = window.getSelection().getRangeAt(0);
var selectionContents = range.extractContents();
var div;
var newDate = new Date;
var randomnumber= newDate.getTime();
var imageTag = document.createElement("img");
imageTag.id=randomnumber;
imageTag.setAttribute("src","notes.png");
var linkTxt = document.createElement("a");
linkTxt.id=randomnumber;
linkTxt.setAttribute("href","highlight:"+randomnumber);
div = document.createElement("span");
div.style.backgroundColor = "yellow";
div.id=randomnumber;
linkTxt.appendChild(imageTag);
div.appendChild(selectionContents);
div.appendChild(linkTxt);
range.insertNode(div);
return document.body.innerHTML+"<noteseparator>"+randomnumber+"<noteseparator>"+range.toString();
}
Please provide a solution that can resolve this problem.
You could do something along the lines of:
Get highlighted section of text.
Insert span tag at the first point.
For every tag that you come accross within the highlighted text:
If it's an opening tag, check if it's corresponding closing tag is in the highlighted text.
If both opening and closing tags are within the text ignore them and move to the next point after the corresponding closing tag.
If only the opening tag or only the closing tag is present, then insert before the tag and after the tag.
Insert span closing tag at the end of the highlighted text.
Possible problem:
span is intented to group inline elements and not block elements so if your highlighted text includes block elements you could have problems. You could use div instead of span to solve this or you could add some checks to distinguish between inline and block tags.
To look at tag matching:
http://haacked.com/archive/2004/10/25/usingregularexpressionstomatchhtml.aspx
To find if the matching closing tag of an element is in the higlighted text (not tested):
function checkClosingTag(position)
{
//Find position of next opening or closing tag along the
//string of highlighted text.
//Return 0 if no more tags.
var nextTag = findNextTag(position);
if(nextTag == 0)
{
return 0;
}
if(!isOpeningTag(nextTag))
{
return nextTag;
}
var nextTagClose = checkClosingTag(nextTag);
if(nextTagClose == 0)
{
return 0;
}
return checkClosingTag(nextTagClose);
}
This looks like a fairly involved problem though - I don't have time to write the code for you but you should be able to work out a way of doing it from here.
some change in your code can work
see this line of codes
function highlightsText()
{
var range, sel;
if (window.getSelection)
{
sel = window.getSelection();
if (sel.getRangeAt) {
range = sel.getRangeAt(0);
}
document.designMode = "on";
if (range) {
sel.removeAllRanges();
sel.addRange(range);
}
if ( !document.execCommand("HiliteColor", false, "yellow") ) {
document.execCommand("BackColor", false, "yellow");
}
document.designMode = "off";
}
else if (document.selection && document.selection.createRange) {
range = document.selection.createRange();
range.execCommand("BackColor", false, "yellow");
}
var newDate = new Date;
var randomnumber= newDate.getTime();
var nodeList = document.querySelectorAll(".Apple-style-span");
for (var i = 0, length = nodeList.length; i < length; i++) {
nodeList[i].id = randomnumber;
}
var div = document.getElementById(randomnumber);
var imageTag = document.createElement("img");
imageTag.id=randomnumber;
imageTag.setAttribute("src","notes.png");
var linkTxt = document.createElement("a");
linkTxt.id=randomnumber;
linkTxt.setAttribute("href","highlight:"+randomnumber);
div.appendChild(linkTxt);
range.insertNode(div);
return document.body.innerHTML+"<noteseparator>"+randomnumber+"<noteseparator>"+range.toString();
}
You need make some adjustments in this code.
Since your goal (based on what you stated in your question) is to highlight selected text with a different color, here is a solution to that goal.
The HTML5BoilerPlate project includes styles to control the selection color (line 52 in the style.css file)
Here's the CSS for it:
/* Remove text-shadow in selection highlight: h5bp.com/i
*
* These selection declarations have to be separate
*
* Also: hot pink! (or customize the background color to match your design)
*/
::-moz-selection { background: #fe57a1; color: #fff; text-shadow: none; }
::selection { background: #fe57a1; color: #fff; text-shadow: none; }
I'm running an experiment to see if I can return that absolute start and end points of a highlighted block of test within a contentEditable (not actually important to the test) div. I'm not building a rich text editor or anything I just want to know how it's done! So all I want to return upon right click (not important, I'm just messing with that too) are two numbers, the absolute distance from the start of the wrapper div to the start of the selection and the absolute distance from the start of the wrapper div to the end of the selection.
I thought Mootools would make this easy but I could only get their implementation to work with forms (i.e. textarea, input etc). So I had a quick bash using Google, and it all worked fine when no tags were involved, e.g. He|llo Wor|ld (where the pipe, |, represents the highlighted range) would return [2, 9] which is correct. However, the moment I add tags to the div to allow colours / formatting these numbers do not make any sense as the range only gives position relative to text nodes and not an absolute value. Any ideas how to get this? I can only imagine it involves some form of horrendous DOM manipulation.
JS:
window.addEvent('domready', function()
{
document.body.addEvent('contextmenu',
function(e)
{
e.stop();
}
);
if(!window.SelectionHandler)
{
SelectionHandler = {};
}
SelectionHandler.Selector = {};
SelectionHandler.Selector.getSelected = function()
{
var userSelection = '';
if(window.getSelection)
{
userSelection = window.getSelection();
}
else if(document.getSelection)
{
userSelection = document.getSelection();
}
else if(document.selection)
{
userSelection = document.selection.createRange();
}
return userSelection;
}
SelectionHandler.Selector.getText = function(userSelection)
{
var selectedText = userSelection;
if(userSelection.text)
{
selectedText = userSelection.text;
}
return selectedText;
}
SelectionHandler.Selector.getRange = function(userSelection)
{
if(userSelection.getRangeAt && typeof(userSelection.getRangeAt) != 'undefined')
{
var selectedRange = userSelection.getRangeAt(0);
}
else
{
var selectedRange = document.createRange();
selectedRange.setStart(userSelection.anchorNode, userSelection.anchorOffset);
selectedRange.setEnd(userSelection.focusNode, userSelection.focusOffset);
}
return selectedRange;
}
$('mydiv').addEvent('mousedown',
function(event)
{
if(event.rightClick)
{
var userSelection = SelectionHandler.Selector.getSelected();
var selectedText = SelectionHandler.Selector.getText(userSelection);
var selectedRange = SelectionHandler.Selector.getRange(userSelection);
// New ranges to add identifiable nodes (must be in that order!?)
var endRange = document.createRange();
endRange.setStart(selectedRange.endContainer, selectedRange.endOffset);
endRange.insertNode(document.createTextNode('!~'));
var startRange = document.createRange();
startRange.setStart(selectedRange.startContainer, selectedRange.startOffset);
startRange.insertNode(document.createTextNode('~!'));
// Find the position of our new identifiable nodes (and account for their removal)
var div_content = $('mydiv').get('html');
var start = div_content.indexOf('~!');
var end = div_content.indexOf('!~') - 2;
// Remove our identifiable nodes (DOESN'T WORK)
//startRange.deleteContents();
//endRange.deleteContents();
// This does work, but obviously loses the selection
div_content = div_content.replace('~!', '').replace('!~', '');
$('mydiv').set('html', div_content);
console.log(start + ' vs ' + end);
}
}
);
}
);
HTML:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Edit Range Test</title>
</head>
<script type="text/javascript" src="mootools.js"></script>
<script type="text/javascript" src="edit_range.js"></script>
<style>
#mydiv {
width: 400px;
height: 400px;
border: 1px solid #a2a2a2;
padding: 5px;
}
</style>
<body>
<h1>Edit Range Test</h1>
<div id="mydiv" contentEditable="true"><span style="color: red;">Hello</span> World! <span style="color: red;">Hello</span> World! </div>
</body>
</html>
So when I now select He|llo Wor|ld (where the pipe, |, again represents the highlighted range) it would return [2, 4] when I want [28, 42].
EDIT: I've updated the code to clarify what I am trying to do. It does most of what I wanted to test, but loses the selection and is very scruffy!
Thanks in advance!
First, the information you get from Range objects are about as useful as you can get: for each of the start and end boundaries of the Range you get a DOM node and an offset within that node (a character offset inside a text or comment node or a child node offset otherwise), which completely describes the boundary. What you mean by "absolute start and end points" I imagine is two character offsets within the whole editable element, but that is a slippery concept: it seems simple, but is tricky to pin down. For example, how many characters does a paragraph break count for? A <br>? An <a> element with display: block? Elements such as <script> elements that contain text but are not visible to the user? Elements hidden via display: none? Elements outside the normal document flow, such as those positioned via position: absolute? Multiple consecutive whitespace characters that are rendered as a single visible character by the browser?
Having said all that, I have recently had a go at writing code to do this, and yes, it does involve DOM manipulation (although not that horrendous). It's extremely rudimentary and doesn't deal satisfactorily with any of the above issues, partly because I knocked it up quite quickly for a question on SO, and partly because in general I don't think it's possible to deal nicely in general with all those issues. The following answer provides functions for saving and restoring the selection as character indices within an editable element: replace innerHTML in contenteditable div