Selecting parts of div text via the code - javascript

Is it possible to select specific text inside a div using the code. I have a div of text, and I need to iterate through each word selecting it individually, then deslecting and onto the next word.
I'm not talking about simulating a select by changing the background css of the words requiring highlighting, but actually selecting it so the outcome is the same as if the user used the mouse to select it.
I know it's possible inside a text area input, but is it possible on a Div?
------------------------UPDATE------------------------------------------
Ok this is where I'm at with it after having another look at it today. I can select all the text in a span, but not specifically a range of words within that span. The closest I have come ( code shown below... ) is selecting the range manually, then removing the selection, then reapplying it.
<div contentEditable = 'true' id="theDiv">
A selection of words but only from here to here to be selected
</div>
<script type="text/javascript">
setInterval( function() {
var currSelection = window.getSelection();
var storedSelections = [];
if ( currSelection ) {
for (var i = 0; i < currSelection.rangeCount; i++) {
storedSelections.push (currSelection.getRangeAt (i));
}
currSelection.removeAllRanges ();
}
if ( storedSelections.length != 0 ) {
currSelection.addRange( storedSelections[0] )
}
}, 1000 );
</script>
The stored selection range object has a startOffset and endOffset property. My question is how do I set this alongside the initial selection via the code ( not via a mouse select ) ?

Please read this article, it's difficult to summarise, so better read it:
http://www.quirksmode.org/dom/range_intro.html
This answer here can be useful too:
Can you set and/or change the user’s text selection in JavaScript?

Turns out it's fairly straightforward...
<div id = "theDiv">adsf asdf asdf asdf</div>
<script type="text/javascript">
var theDiv = document.getElementById('theDiv')
theDivFirstChild = theDiv.firstChild;
var range = document.createRange();
range.setStart( theDivFirstChild, 2 );
range.setEnd( theDivFirstChild, 8);
window.getSelection().addRange(range);
</script>

Related

JavaScript selected text entirely within node

I'm looking into text selection and ranges in JavaScript.
I need to get any nodes that surround the selected text exactly, for example:
<div>this is <span>some simple</span> text</div>
When the user selects the words 'some simple' i need to know that it sits entirely within the node .
Yet if they select just 'some' then this is not entirely within the node as the word 'simple' is NOT selected.
The end requirement is to be able to amend the class on the node only if the whole text within the node is selected.
jquery is also viable. thanks
To add some more context to this, when a user selects some text we add some sytling to it, let's say 'bold'. the user can edit the text in the parent div as often as they wish so each edit could add a new span enclosing the selected text. We could end up with something like this:
<div><span class="text-bold">Hi</span>, <span class="text-red">this <span class="text-italic">is</span></span> a sample text item</div>
So the spans can come and go dependant on what the user wants.
You are looking to get the DOM each time. So you can set id to your HTML Element Objects and get the value from them. For example:
<span id="f_span">some simple</span>
<script>
var x = document.getElementById("f_span");
</script>
Then you can check if x value equals with the value that user had selected.
You can use setInterval to get the selected text every x second. With the joined function you can get the selected text.
For the selection of user :
function getSelected() {
if(window.getSelection) { return window.getSelection(); }
else if(document.getSelection) { return document.getSelection(); }
else {
var selection = document.selection && document.selection.createRange();
if(selection.text) { return selection.text; }
return false;
}
return false;
}
It return an object that give you the offset of the selection. If result.anchorOffset = 0 and result.focusOffset = result.anchorNode.length (if it start at the begining of the node and it have the length of the whole node), then the user selected all your node.
Thanks for your replies, it allowed me to cobble together my solution:
function applyTextFormatClass(className) {
var selection = getSelected();
var parent = selection.getRangeAt(0).commonAncestorContainer; //see comment and link below
if (parent.nodeType !== 1) {
parent = parent.parentNode; //we want the parent node
}
var tagText = parent.innerText;
var selectText = selection.toString();
if (tagText.length !== selectText.length) {
addNodeAroundSelectedText(selection, className); //create new node
} else {
addClass(parent, className); //add class to existing node
}
}
commonAncestorContainer: https://developer.mozilla.org/en-US/docs/Web/API/Range/commonAncestorContainer

How to highlight an editable word in dynamically generated text?

Intro
I am creating a content editor in which I want to add the functionality to choose a word which you would like to be highlighted while typing your content.
At this moment I achieved to search any word chosen in the #dynamicWord and then typed in #contentAreaContainer and give it a red border by adding em around the keyword and style the em trough CSS:
Part of the Code:
<div class="word">
Dynamic word to highlight: <input name="dynamic_word" id="dynamicWord" value="Enter word..">
</div>
<div id="contentAreaContainer" oninput="highlighter()">
<textarea id="contentArea"></textarea>
</div>
function highlighter()
{
var contentAreaContainer = document.getElementById('contentAreaContainer');
var dynamicWord = document.getElementById('dynamicWord').value;
wrapWord(contentAreaContainer, dynamicWord);
};
wrapWord() does:
function wrapWord(el, word)
{
var expr = new RegExp(word, "i");
var nodes = [].slice.call(el.childNodes, 0);
for (var i = 0; i < nodes.length; i++)
{
var node = nodes[i];
if (node.nodeType == 3) // textNode
{
var matches = node.nodeValue.match(expr);
if (matches)
{
var parts = node.nodeValue.split(expr);
for (var n = 0; n < parts.length; n++)
{
if (n)
{
var em = el.insertBefore(document.createElement("em"), node);
em.appendChild(document.createTextNode(matches[n - 1]));
}
if (parts[n])
{
el.insertBefore(document.createTextNode(parts[n]), node);
}
}
el.removeChild(node);
}
}
else
{
wrapWord(node, word);
}
}
}
em{border: 1px solid red;}
The problem:
Now at this moment every time on input in #contentAreaContainer the keyword chosen is highlighted a short period in the #contentAreaContainer, because highlighter() is triggered on input. But it should stay highlighted after finding it instead of only oninput.
I need oninput to search for the #dynamicWord value with wrapWord() while some one is typing;
Any time the #dynamicWord value was found it should permanently get an em
So how can I sort of 'save' the found keywords and permanently give them the element until the dynamic keyword gets edited?
Check the DEMO version
Solved:
Using setTimeout() instead of oninput I managed to make the highlight look constant. The change:
function highlighter()
{
var contentAreaContainer = document.getElementById('contentAreaContainer');
var mainKeyword = document.getElementById('main_keyword').value;
wrapWord(contentAreaContainer, mainKeyword);
repeater = setTimeout(highlighter, 0.1);
}
highlighter();
I removed oninput="highlighter()" from #contentAreaContainer.
You are trying to highlight words in a textarea. As far as I know a textarea does not support html elements inside. If you do it would simply display them as text.
Therefore you need to use an editable div. This is a normal div but if you add the attribute:
contentEditable="true"
the div acts like a textarea with the only difference it now process html elements. I also needed to change the onchange event into the onkeyup event. The editable div does not support onchange events so the highlight would not be triggered. The HTML for this div looks like:
<div contentEditable="true" id="contentArea">Test text with a word in it</div>
Here is the working code in a fiddle: http://jsfiddle.net/Q6bGJ/ When you enter a new character in the textarea your keyword gets highlighted.
However there is still a problem left. You surround the keyword with an em element. This results in surrounding it on every keystroke. Now you end up width many em's around the keyword. How to solve this, I leave up to you as a challenge.

Javascript: How do I expand a user selection based on html tags?

Le Code:
http://jsfiddle.net/frf7w/12/
So right now, the current method will take the selected text exactly as... selected, and add tags so that when it is displayed, the page doesn't blow up.
But what I want to do:
Is to, when a user selects a portion of a page, if there are un-matched tags within the selection, the selection will either jump forward or backward (depending on what unmatched tag is in the selection) to the tag(s) that make the selection valid html.
The reason why I want to do this, is because I want a user to be able te select text on a page, and be able to edit that text in a WYSIWYG editor (I can currently do this with the linked code), and then put what they've edited back into the page (currently can't do this, because the method I use adds tags).
The coverAll method in this SO answer has exactly what you want Use javascript to extend a DOM Range to cover partially selected nodes. For some reason extending Selection prototype does not work for me on my chrome, so I extracted the code and substituted this with window.getSelection(). Final code looks like this:
function coverAll() {
var ranges = [];
for(var i=0; i<window.getSelection().rangeCount; i++) {
var range = window.getSelection().getRangeAt(i);
while(range.startContainer.nodeType == 3
|| range.startContainer.childNodes.length == 1)
range.setStartBefore(range.startContainer);
while(range.endContainer.nodeType == 3
|| range.endContainer.childNodes.length == 1)
range.setEndAfter(range.endContainer);
ranges.push(range);
}
window.getSelection().removeAllRanges();
for(var i=0; i<ranges.length; i++) {
window.getSelection().addRange(ranges[i]);
}
return;
}
You can change the boundaries of the selection by adding a range:
var sel = window.getSelection(),
range = sel.getRangeAt(0);
var startEl = sel.anchorNode;
if (startEl != range.commonAncestorContainer) {
while (startEl.parentNode != range.commonAncestorContainer) {
startEl = startEl.parentNode;
}
}
var endEl = sel.focusNode;
if (endEl != range.commonAncestorContainer) {
while (endEl.parentNode != range.commonAncestorContainer) {
endEl = endEl.parentNode;
}
}
range.setStartBefore(startEl);
range.setEndAfter(endEl);
sel.addRange(range);
The above example will give you a selection that is expanded to cover the entire of the tree between the start and end nodes, inclusive (thanks to commonAncestorContainer()).
This treats text nodes as equal to dom elements, but this shouldn't be a problem for you.
Demo: http://jsfiddle.net/Nq6hr/2/
You should work with the nodes given by the selection. It seems extentNode and anchorNode represents the end and the beginning of nodes of the selection both can help you having the "full" selection. https://developer.mozilla.org/fr/DOM/Selection
For the inline editing you should give a try to contentEditable attribute. You can surround the elements of your selection with a span containing this attribute https://developer.mozilla.org/en/DOM/element.contentEditable

how to select a text range in CKEDITOR programatically?

Problem:
I have a CKEditor instance in my javascript:
var editor = CKEDITOR.instances["id_corpo"];
and I need to insert some text programatically, and select some text range afterwards.
I already did insert text through
editor.insertHtml('<h1 id="myheader">This is a foobar header</h1>');
But I need to select (highlight) the word "foobar", programatically through javascript, so that I can use selenium to work out some functional tests with my CKEditor plugins.
UPDATE 1:
I've also tried something like
var selection = editor.getSelection();
var childs = editor.document.getElementsByTag("p");
selection.selectElement(childs);
But doesn't work at all!
How can I do that?
I think that
selection.selectRange()
could do the job, but I'could not figure out how to use it.
There are no examples over there :(
Get current selection
var editor = CKEDITOR.instances["id_corpo"];
var sel = editor.getSelection();
Change the selection to the current element
var element = sel.getStartElement();
sel.selectElement(element);
Move the range to the text you would like to select
var findString = 'foobar';
var ranges = editor.getSelection().getRanges();
var startIndex = element.getHtml().indexOf(findString);
if (startIndex != -1) {
ranges[0].setStart(element.getFirst(), startIndex);
ranges[0].setEnd(element.getFirst(), startIndex + findString.length);
sel.selectRanges([ranges[0]]);
}
You can also do the following:
get the current selection
var selection = editor.getSelection();
var selectedElement = selection.getSelectedElement();
if nothing is selected then create a new paragraph element
if (!selectedElement)
selectedElement = new CKEDITOR.dom.element('p');
Insert your content into the element
selectedElement.setHtml(someHtml);
If needed, insert your element into the DOM (it will be inserted into the current position)
editor.insertElement(selectedElement);
and then just select it
selection.selectElement(selectedElement);
Check out the selectElement() method of CKEDITOR.dom.selection.
http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.dom.selection.html
insert text at cursor point in ck editor
function insertVar(myValue) {
CKEDITOR.instances['editor1'].fire( 'insertText',myValue);
}
this is working for me

Tag-like autocompletion and caret/cursor movement in contenteditable elements

I'm working on a jQuery plugin that will allow you to do #username style tags, like Facebook does in their status update input box.
My problem is, that even after hours of researching and experimenting, it seems REALLY hard to simply move the caret. I've managed to inject the <a> tag with someone's name, but placing the caret after it seems like rocket science, specially if it's supposed work in all browsers.
And I haven't even looked into replacing the typed #username text with the tag yet, rather than just injecting it as I'm doing right now... lol
There's a ton of questions about working with contenteditable here on Stack Overflow, and I think I've read all of them, but they don't really cover properly what I need. So any more information anyone can provide would be great :)
You could use my Rangy library, which attempts with some success to normalize browser range and selection implementations. If you've managed to insert the <a> as you say and you've got it in a variable called aElement, you can do the following:
var range = rangy.createRange();
range.setStartAfter(aElement);
range.collapse(true);
var sel = rangy.getSelection();
sel.removeAllRanges();
sel.addRange(range);
I got interested in this, so I've written the starting point for a full solution. The following uses my Rangy library with its selection save/restore module to save and restore the selection and normalize cross browser issues. It surrounds all matching text (#whatever in this case) with a link element and positions the selection where it had been previously. This is triggered after there has been no keyboard activity for one second. It should be quite reusable.
function createLink(matchedTextNode) {
var el = document.createElement("a");
el.style.backgroundColor = "yellow";
el.style.padding = "2px";
el.contentEditable = false;
var matchedName = matchedTextNode.data.slice(1); // Remove the leading #
el.href = "http://www.example.com/?name=" + matchedName;
matchedTextNode.data = matchedName;
el.appendChild(matchedTextNode);
return el;
}
function shouldLinkifyContents(el) {
return el.tagName != "A";
}
function surroundInElement(el, regex, surrounderCreateFunc, shouldSurroundFunc) {
var child = el.lastChild;
while (child) {
if (child.nodeType == 1 && shouldSurroundFunc(el)) {
surroundInElement(child, regex, surrounderCreateFunc, shouldSurroundFunc);
} else if (child.nodeType == 3) {
surroundMatchingText(child, regex, surrounderCreateFunc);
}
child = child.previousSibling;
}
}
function surroundMatchingText(textNode, regex, surrounderCreateFunc) {
var parent = textNode.parentNode;
var result, surroundingNode, matchedTextNode, matchLength, matchedText;
while ( textNode && (result = regex.exec(textNode.data)) ) {
matchedTextNode = textNode.splitText(result.index);
matchedText = result[0];
matchLength = matchedText.length;
textNode = (matchedTextNode.length > matchLength) ?
matchedTextNode.splitText(matchLength) : null;
surroundingNode = surrounderCreateFunc(matchedTextNode.cloneNode(true));
parent.insertBefore(surroundingNode, matchedTextNode);
parent.removeChild(matchedTextNode);
}
}
function updateLinks() {
var el = document.getElementById("editable");
var savedSelection = rangy.saveSelection();
surroundInElement(el, /#\w+/, createLink, shouldLinkifyContents);
rangy.restoreSelection(savedSelection);
}
var keyTimer = null, keyDelay = 1000;
function keyUpLinkifyHandler() {
if (keyTimer) {
window.clearTimeout(keyTimer);
}
keyTimer = window.setTimeout(function() {
updateLinks();
keyTimer = null;
}, keyDelay);
}
HTML:
<p contenteditable="true" id="editable" onkeyup="keyUpLinkifyHandler()">
Some editable content for #someone or other
</p>
As you say you can already insert an tag at the caret, I'm going to start from there. The first thing to do is to give your tag an id when you insert it. You should then have something like this:
<div contenteditable='true' id='status'>I went shopping with <a href='#' id='atagid'>Jane</a></div>
Here is a function that should place the cursor just after the tag.
function setCursorAfterA()
{
var atag = document.getElementById("atagid");
var parentdiv = document.getElementById("status");
var range,selection;
if(window.getSelection) //FF,Chrome,Opera,Safari,IE9+
{
parentdiv.appendChild(document.createTextNode(""));//FF wont allow cursor to be placed directly between <a> tag and the end of the div, so a space is added at the end (this can be trimmed later)
range = document.createRange();//create range object (like an invisible selection)
range.setEndAfter(atag);//set end of range selection to just after the <a> tag
range.setStartAfter(atag);//set start of range selection to just after the <a> tag
selection = window.getSelection();//get selection object (list of current selections/ranges)
selection.removeAllRanges();//remove any current selections (FF can have more than one)
parentdiv.focus();//Focuses contenteditable div (necessary for opera)
selection.addRange(range);//add our range object to the selection list (make our range visible)
}
else if(document.selection)//IE 8 and lower
{
range = document.body.createRange();//create a "Text Range" object (like an invisible selection)
range.moveToElementText(atag);//select the contents of the a tag (i.e. "Jane")
range.collapse(false);//collapse selection to end of range (between "e" and "</a>").
while(range.parentElement() == atag)//while ranges cursor is still inside <a> tag
{
range.move("character",1);//move cursor 1 character to the right
}
range.move("character",-1);//move cursor 1 character to the left
range.select()//move the actual cursor to the position of the ranges cursor
}
/*OPTIONAL:
atag.id = ""; //remove id from a tag
*/
}
EDIT:
Tested and fixed script. It definitely works in IE6, chrome 8, firefox 4, and opera 11. Don't have other browsers on hand to test, but it doesn't use any functions that have changed recently so it should work in anything that supports contenteditable.
This button is handy for testing:
<input type='button' onclick='setCursorAfterA()' value='Place Cursor After <a/> tag' >
Nico

Categories

Resources