How to determine the order occurrence of the currently selected text? - javascript

I want to know which occurrence of the currently selected text is selected, in my content div (.entry-content).
My current fiddle built from a previous answer is doing it right... sometimes!
http://jsfiddle.net/FTWLJ/10/
If you select the word Maecenas (first word), it gives the correct occurrence when you pick the first one. Try with the first one of the second paragraph and it gives the wrong occurrence. It should be 3.
It seems to work sometimes but not reliably, it perhaps is the use of elementFromPoint( x, y ).
It seems that when you select full words, it doesn't work. When you select half a word and half another word, it works.
$('.entry-content').on('click',function(e){
//get the x and y coords
var offset = $(this).offset(),
x = e.clientX - offset.left,
y = e.clientY - offset.top,
text;
//get the highlighted text
if(window.getSelection){
text = window.getSelection().toString();
}else if (document.selection && document.selection.type != 'Control'){
//for IE prior to 9
text = document.selection.createRange().text;
}
//check if the text is empty or just spaces
if($.trim(text).length){
//wrap each word similar to the highlighted text with <span>
var regex = new RegExp(text,'g')
$('.entry-content').html($('.entry-content').html().replace(regex, '<span class="highlight">'+text+'</span>'));
//get the new span index situating on coords
var el = document.elementFromPoint(x, y),
occurrence = $(el).index()+1,
exist = $('span').length;
alert(exist+' exist | occurrence: '+occurrence);
//remove the <span> wrapper
$('span').contents().unwrap();
}
});
Has anybody a reliable solution for this?

This returns the element's index in relation to its parent:
occurrence = $(el).index()+1
What you want instead is its index in relation to all of the spans:
occurrence = $('span').index(el)+1
Fiddle

Related

Get position of cursor when highlight the text

I need to create a Chrome Extension which people can use to look up words. And I have found that I use double-click method like when I want to find meaning of a word I just double click and then the notification will appear. (The image below is my Chrome Extension.)
.
However, I find it is difficult for user to find meaning of pharsal
verb, collocation and sentence.
So come up with an idea that I can get the position of cursor when
user select text and to the last letter of this text I get the
position.
In addition, I also have found many other Chrome extensions support
this feature and they are so great!. One of them is "Edge Translate". When I higlight a text and then I release the mouse it appears a little popup ball then I click to this ball it appears panel. This is their extension: https://chrome.google.com/webstore/detail/edge-translate/bocbaocobfecmglnmeaeppambideimao?hl=en
To get the text selected you could use this function, then remove the dots, and other characters you are not interested in and split the selected text by spaces to get the last word. (also you don't need the cursor coordinates for it, so you could remove this piece. of the code)
let hasSelection = false;
function showCoordsForMouseUp(event) {
const selection = window.getSelection().toString()
hasSelection = selection.length;
if (hasSelection) {
const x = event.clientX;
const y = event.clientY;
const coords = `X coords: ${x} , Y coords: ${y}`;
console.log(`${coords} - ${selection}`)
} else {
console.log('No text selected')
}
}
document.onmouseup = showCoordsForMouseUp;
Thanks for your contribution, I have found the solution with onMouseUp Event.
document.onmouseup = function F(e) {
x = e.pageX;
y = e.pageY;
if (window.getSelection().toString() != "") {
console.log("X: " + x + "Y: " + y);
}
}
<p>
Flower, flowers.
</p>

How to know if Ckeditor anchor is encapsulated within span?

This is specifically for the developers of CKEditor.
The goal is to know when the cursor is either inside or outside a span of custom atttributes.
When using the Ckeditor, if inserted a custom span like below from a plug-in, occassionally when you stop typing, un focus the textarea and replace your cusor to the end of the line - as though you are continuing typing where you had left off. The text can either be inside the span or outside with no encapsulation.
Example before unfocus:
<span style="color:blue;line-height:12px;font-size:10px;font-family:arial;" class="master-span">
<span style="color:red;line-height:20px;font-size:18px;font-family:museo;" class="child-span">Text is here</span>
</span>
Re-Focus and positioning cusor on screen at the end of "here" and start typing.
<span style="color:blue;line-height:12px;font-size:10px;font-family:arial;" class="master-span">
<span style="color:red;line-height:20px;font-size:18px;font-family:museo;" class="child-span">Text is here</span> Text is now outside!!!
</span>
What I'm asking is that where would I target my troubleshoot to know when the cursor/anchor is within the span or outside of it i.e. Is there a calculation being made in the code to determine this?
The reason for this enquiry is that the span formmating is extremely important and we can't use a master span in the surrounding textarea as this can be altered by the user. We find also, that using bulletpoints are troublesome due to the browsers inability to set line-height so ensuring text is always within the span would be a major help.
P.S I've downloaded the source code to see where I could find it.
Many Thanks for any help.
Upon clicking of the text area of any of my CKeditor instances I looked for the parent of the cursor to see if it was an LI node. This indicating that the cursor had landed outside of my span within a bullet point. I proceeded to find the most outside Text Node I could find as my assumption is that if the cursor was outside the span thus the user wanted to continue typing from the last character.
In my setup, within a span there can be a number of multiple sub spans with no limitation of number. So I had to find the most outside span that had a text node at the end (or close to the end).
I did by getting the Node List and reversing the order to find the first text node within the reverse order. I created a function that could loop the number of span and break upon finding the first text node.
Once I had my text node I then set up ranges and calculated the length of the text in order to place it at the end. Below is my final solution which works on latest Chrome/FF/IE.
$("#preview-text-3827").on("click", function(){
var CKEDITOR = window.parent.CKEDITOR;
var ck_instance_name = false;
for ( var ck_instance in CKEDITOR.instances ){
if (CKEDITOR.instances[ck_instance].focusManager.hasFocus){
ck_instance_name = ck_instance;
break;
}
}
var editor = CKEDITOR.instances[ck_instance_name];
var selection = editor.getSelection();
var parent_attrs = "";
if (selection.getType() == CKEDITOR.SELECTION_TEXT) {
parent_attrs = selection.getStartElement();
if(parent_attrs.getName() == "li"){
var nodeList = parent_attrs.getChildren();
console.log( "Now Reverse" );
for ( var i = nodeList.count() - 1; i > -1; --i ) {
//console.log( nodeList.getItem( i).nodeName );
var el = nodeList.getItem( i);
if(el.$.nodeName == "#text"){
// This is the last text but it's inside the li which is WRONG
}
if(el.$.nodeName == "SPAN"){
// This should be the last span
// Jump into this span and find the children.
var result = retractLiPosition(el);
if(result.text == true){
var inner = result.result;
var text = inner.$.textContent || inner.$.innerText;
var length = text.length;
var range = editor.createRange();
range.setStart( inner , length);
range.setEnd( inner, length);
editor.getSelection().selectRanges( [ range ] );
break;
}
}
}
}
}
});
function retractLiPosition(element){
var returning = new Object();
returning.result = element;
returning.text = false;
var result = element;
var nodeList = element.getChildren();
for ( var i = nodeList.count() - 1; i > -1; --i ) {
var el = nodeList.getItem( i);
if(el.$.nodeName == "#text"){
returning.result = el;
returning.text = true;
break;
}
if(el.$.nodeName == "SPAN"){
// This should be the last span
// Jump into this span and find the children.
returning = retractLiPosition(el);
}
}
return returning;
}
});

How Do I Add Ellipses to Text

I am trying to create a function that will take an element's text, cut off any characters beyond 80, and add an ellipses if necessary. Here's my code so far:
var maxLength = 80;
function shorten(element) {
var text = $('#' + element).text();
var ret = text;
if (text.length > maxLength) {
text = text.substr(0,maxLength-3) + "...";
}
$('#' + element).text(text);
}
shorten('slide1');
So, the function should take the element, remove extra text off the end, add an ellipses, and then replace the old text in the element with the new string I've just created.
When I run the function, I don't get any errors, but it doesn't actually cut off the text as it should. Why is this?
var text = "Some Text Goes Here. La La La La!";
var textLength = 10; // Number of characters to cut off after
function shorten(text, textLength){
if(text.length > textLength){
text = text.substring(0, textLength) + '…';
}
return text;
}
var shortText = shorten(text, textLength);
Also, using the HTML character for ellipsis is better than using three periods.
I've added a Codepen showing the code working. Additionally, I added a function spaceShorten that will split your text at the last occurrence of a space that is less than the length provided, so you don't split the text mid word.
http://codepen.io/supah_frank/pen/EaYzNz

Position changes every time with window.getSelection()

Can anyone help me? I got these codes here https://stackoverflow.com/a/17836828/2338164
$(document).on("mouseup",".wrap",function(){
var highlight = window.getSelection();
if(highlight.toString().length>=1){
var spn = '<span class="highlight">' + highlight + '</span>';
var text = $(this).text();
var range = highlight.getRangeAt(0);
var startText = text.substring(0, range.startOffset);
var endText = text.substring(range.endOffset, text.length);
$('#q3txt').append(range.startOffset+"<br>");
$(this).html(startText + spn + endText);
}
});
I tried to use it and it's working fine, until you highlight again...
Here's a link http://jsfiddle.net/AN76g/.
What im trying to do is... user will highlight a block then wrap it in span, but if the user made a mistake and tries to highlight again, the span is removed and will try to wrap the new highlighted text. But either the position changes or parts of the text are being appended.
See this update: jsfiddle.
First, on the mousedown, you can unwrap the span as so:
$(document).on("mousedown",".wrap",function(){
$('.highlight').contents().unwrap();
});
Secondly, the problem with using the range.startOffset and range.endOffset is that you the start is relative to the containing element which could be the highlight span which causes you to replace the incorrect text on subsequent selections. Instead, replace the selection with the span as so:
$(document).on("mouseup",".wrap",function(){
var highlight = window.getSelection();
if(highlight.toString().length>=1){
var range = highlight.getRangeAt(0);
var selectionContents = range.extractContents();
var spn = document.createElement("span");
spn.className='highlight';
spn.appendChild(selectionContents);
range.insertNode(spn);
highlight.removeAllRanges();
}
});
Information from MDN Range.startOffset, specifically:
the startContainer is a Node of type Text, Comment, or CDATASection, then the offset is the number of characters from the start of the startContainer to the boundary point of the Range. For other Node types, the startOffset is the number of child nodes between the start of the startContainer and the boundary point of the Range.
Also, this answer.

JavaScript 'contenteditable' -- Getting/Setting Caret Position

I have read a few posts on positioning the caret, but none seem to answer my particular issue.
I have 2 divs (div1 and div2)
div1 = noneditable div
div2 = contenteditable div
both divs contain exact same contents
when user clicks on div1, it gets hidden, and div2 appears in exact location and user can edit
The problem: I want the caret to appear in exact location on div2 as div1
So, I need some way to READ the location where the user clicks on div1, and then when div2 appears place the cursor/caret in that same location, so a getCaretLocation(in_div_id) and setCaretLocation(in_div_id) set of functions.
Any way to do that?
Thanks -
Short answer : You can't
Long answer : The problem you'll face is that you'll be able to get (x,y) coordinates for the click event on div1, but any implementation of the caret position while require you knowing the position of the caret in the content (which is the number of characters preceding the caret).
To convert the (x,y) coordinates to a character position you actually need to know how many characters were before (ie. left on the current line and above, if the text is ltr).
If you use a fixed width font, you can simplify the problem : mapping an (x,y) coordinate to a (line, column) coordinate on a character grid.
However, you still face the problem of not knowing how the text is wrapped. For example :
------------------
|Lorem ipsum |
|dolor sit amet |
|consectetur |
|adipiscing elit |
------------------
If the user clicks on the d in dolor, you know that the character is the 1st on the 2nd line, but without knowing the wrapping algorithm there is no way you'll know that it is the 13th character in "Lorem ipsum dolor sit…". And there is no guarantee that such a wrapping algorithm is identical across browsers and platform.
Now, what I'm wondering is why would you use 2 different synced div in the first place ? Wouldn't it be easier to use only one div and set its content to editable when the user clicks (or hovers) ?
You could insert a tiny span-element at the caret, get its position, and remove it. For a cross-browser range and selection library, see rangy.
you can, basically you need set temporary content editable on your first div to catch caret pos
$('div1').hover(function()
{ $(this).attr('contenteditable','true');
},function()
{ $(this).removeAttr('contenteditable');
}).mouseup(function()
{ var t = $(this);
// get caret position and remove content editable
var caret = t.getCaret();
t.removeAttr('contenteditable');
// do your div switch stuff
...
// and apply saved caret position
$('div2').setCaret(caret);
});
now just need get/set caret method :)
edit > here is my own, (live demo)
getSelection:function($e)
{ if(undefined === window.getSelection) return false;
var range = window.getSelection().getRangeAt(0);
function getTreeOffset($root,$node)
{ if($node.parents($root).length === 0) return false; // is node child of root ?
var tree = [], treesize = 0;
while(1)
{ if($node.is($root)) break;
var index, $parent = $node.parent();
index = $parent.contents().index($node);
if(index !== -1) { tree[treesize++] = index; } $node = $parent;
}; return tree.reverse();
}
var start = getTreeOffset($e,$(range.startContainer));
var end = getTreeOffset($e,$(range.endContainer));
if(start & end === false) return false;
return {start:start,end:end,startOffset:range.startOffset,endOffset:range.endOffset};
}, setSelection:function($e,s,win)
{ $e.focus(); if(s === false) return; var sel = win.getSelection(); sel.removeAllRanges();
function getNode($e,s)
{ var node = $e;
for( var n=0;n<s.length;n++ )
{ var index = s[n]; if(index < 0) break;
node = node.contents(':eq('+index+')');
} return node.get(0);
}
var start = getNode($e,s.start), end = getNode($e,s.end), range = win.document.createRange();
range.setStart(start,s.startOffset); range.setEnd(end,s.endOffset); sel.addRange(range);
}
It sounds like you are trying to do an inline edit... have you looked at the jeditable plugin?
When you click on an element, a Selection object with zero length is created (get it from element.getSelection() , where element is the div in question). The focusOffset of that object will let you know that you clicked on, for example, the 74th character in that div (this is the thing that Adrien said was impossible in a different answer).

Categories