Caret disappears in Firefox when saving its position with Rangy - javascript

This happens only in Firefox.
Important: I am saving the caret's position with rangy.saveSelection():
when click the content editable div
on keyup
when adding an external html element (as a node) to the content editable div
I need the position saved constantly through multiple means to be able to insert html elements on click (I have some tags).
When I click in the contentEditable div and the div is empty (first focus, let's say), I cannot see the caret unless I start typing. If the caret is at the end, I cannot see it either.
Another weird behaviour is that I cannot use the arrows to navigate between the text in the contentEditable div.
If I remove the functions which (constantly) saves the caret's position (on input, click etc.) the caret returns to normal (the caret is visible).
The problem appears when I start saving the position of the caret. Clearly I should be doing some sort of reset or a clear.. but from what I understand, those seem counterproductive (as from my understanding they destroy the saved caret location).
The content editable div
<div class="input__boolean input__boolean--no-focus">
<div
#keydown.enter.prevent
#blur="addPlaceholder"
#keyup="saveCursorLocation($event); fixDelete(); clearHtmlElem($event);"
#input="updateBooleanInput($event); clearHtmlElem($event);"
#paste="pasted"
v-on:click="clearPlaceholder(); saveCursorLocation($event);"
class="input__boolean-content"
ref="divInput"
contenteditable="true">Cuvinte cheie, cautare booleana..</div>
</div>
My methods/functions
inputLength($event){
this.input_length = $event.target.innerText.length;
if(this.input_length == 0)
this.typed = false;
},
addPlaceholder(){
if(this.input_length == 0 && this.typed == false){
this.$refs.divInput.innerHTML = 'Cuvinte cheie, cautare booleana..'
}
},
clearPlaceholder(){
if(this.input_length == 0 && this.typed == false){
this.$refs.divInput.innerHTML = '';
}
},
updateBooleanInput($event){
this.typed = true;
this.inputLength($event);
},
saveCursorLocation($event){
if($event.which != 8){
if(this.saved_sel)
rangy.removeMarkers(this.saved_sel)
this.saved_sel = rangy.saveSelection();
}
// if(this.input_length == 0 && this.typed == false){
// var div = this.$refs.divInput;
// var sel = rangy.getSelection();
// sel.collapse(div, 0);
// }
},
insertNode: function(node){
var selection = rangy.getSelection();
var range = selection.getRangeAt(0);
range.insertNode(node);
range.setStartAfter(node);
range.setEndAfter(node);
selection.removeAllRanges();
selection.addRange(range);
},
addBooleanTag($event){
// return this.$refs.ChatInput.insertEmoji($event.img);
this.$refs.divInput.focus();
console.log(this.input_length);
if(this.typed == false & this.input_length == 0){
this.$refs.divInput.innerHTML = ''
var space = '';
this.typed = true
this.saveCursorLocation($event);
}
rangy.restoreSelection(this.saved_sel);
var node = document.createElement('img');
node.src = $event.img;
node.className = "boolean-button--img boolean-button--no-margin";
node.addEventListener('click', (event) => {
// event.currentTarget.node.setAttribute('contenteditable','false');
this.$refs.divInput.removeChild(node);
})
this.insertNode(node);
this.saveCursorLocation($event);
},
clearHtmlElem($event){
var i = 0;
var temp = $event.target.querySelectorAll("span, br");
if(temp.length > 0){
for(i = 0; i < temp.length; i++){
if(!temp[i].classList.contains('rangySelectionBoundary')){
if (temp[i].tagName == "br"){
temp[i].parentNode.removeChild(temp[i]);
} else {
temp[i].outerHTML = temp[i].innerHTML;
}
}
}
}
},
pasted($event){
$event.preventDefault();
var text = $event.clipboardData.getData('text/plain');
this.insert(document.createTextNode(text));
this.inputLength($event);
this.typed == true;
},
insert(node){
this.$refs.divInput.focus();
this.insertNode(node);
this.saveCursorLocation($event);
},
As you can see in the saveCursorLocation(), I was trying to solve the scenario in which you click in the contentEditable div and there's no caret - which is confusing for the user.
// if(this.input_length == 0 && this.typed == false){
// var div = this.$refs.divInput;
// var sel = rangy.getSelection();
// sel.collapse(div, 0);
// }
It was a dead end - most likely because of my poor understanding of Rangy and how should I use those functions.
Expected behaviour vs actual results on Firefox
When I click on the contentEditable div I expect the caret to appear (while in the background to save my position). When typing, I expect the caret to appear after the last typed character while also on keyup to save my caret's position. Also I expect to be able to navigate the text via left/right arrows and see the caret when doing so.
All of these are generated by
v-on:click="..... saveCursorLocation($event);"
and
#keyup="saveCursorLocation($event);....."
If anybody believes that it would be helpful, I can record the content editable div and its behaviour in Firefox.
EDIT: I managed to isolate the problem and reproduce it into a JSFiddle - https://jsfiddle.net/Darkkz/6Landbj5/13.
What to look for?
Open the fiddle link in Firefox, then press one of the blue buttons (SI, SAU, NU) and then look at the input, the caret is not displayed.
Click the input, the caret is not displayed
While typing in the input,the caret is not displayed. Although, if you click in a word/in between content, the caret will be displayed

Apparently rangy's Selection Save and Restore module can't be used to keep track of the current selection while the user interacts with a contenteditable, like you want.
I digged into it a bit, and the problem is that rangy inserts hidden <span>s as markers, and updates the selection to be after the marker, instead of keeping it inside the #text node the user's editing:
<div contenteditable>
#text [This is something I typed <!-- selection is moved from here -->]
<span class="rangySelectionBoundary"/>
<!-- to here -->
</div>
Firefox has trouble displaying the caret in this scenario (I haven't found a bug about this specific issue, but here's a similar one where the caret is not displayed when the selection is between two <span>s).
Commenting this code out seems to fix the issue with the disappearing caret. It's unclear to me why that code is needed -- it was added before 1.0 in a large commit with its message saying: "Fixes for save/restore problems with control ranges and multiple range selections. Added demos for save/restore and CSS class applier modules." -- so I'm not comfortable to suggest fixing this in rangy (and since it's unmaintained for a few years, I don't have much hope in getting its author's input on this).
So I tried to figure out why you needed this in the first place, to suggest other solutions not involving rangy.saveSelection (for example, rangy's Text Range module provides getSelection().saveCharacterRanges(containerNode) that works without modifying the DOM.
It appears that you have a <div contenteditable> and some "buttons" (<span>s), clicking on which would insert some HTML at the caret position. The problem you were trying to solve was that when the "buttons" were clicked, the selection moved from the contenteditable into the button, and you were unable to detect the insert position.
Instead of storing and restoring the selection, you can instead make the buttons user-select: none - this will keep the caret in the contenteditable.
To test this, I commented out all references to rangy.saveSelection and rangy.restoreSelection and changed the this.$refs.divInput.focus(); call in the "button"'s onclick handler to run only when the contenteditable wasn't already focused by wrapping it in an if (!this.$refs.divInput.contains(document.activeElement)). See how this works in this updated fiddle:
https://jsfiddle.net/fjxsgvm2/

Related

Replace text in textarea on his exact position [duplicate]

I'm trying to use Javascript to replace the selected text in an arbitrary selected TEXTAREA node in Chrome (! not a content editable div !) The code fragment I see repeated in lots of places to replace selected text basically does this:
var sel = window.getSelection();
var range = sel.getRangeAt(0);
range.insertNode( document.createTextNode("test "));
However, this does not work for input fields such as TEXTAREA or INPUT TYPE=TEXT. The text is inserted BEFORE the TEXTAREA instead of inside it.
There is an alternative method to modify the selection text inside a text area using textarea.selectionStart and textarea.selectionEnd. However, these require figuring out which textarea element is actually active/selected. Chrome/Webkit document.activeElement seems to be broken and has been broken for a long time. I can't figure out any workaround to find the "currently selected textarea". See the bug here...
http://code.google.com/p/chromium/issues/detail?id=14436
You can see a micro-demo of the problem I'm trying to solve here.
http://dj1.willowmail.com/~jeske/_drop/insertIssue/1.html
http://ajaxandxml.blogspot.com/2007/11/emulating-activeelement-property-with.html
Any thoughts on this?
Given a webpage with an arbitrary bit of text selected in an arbitrary TEXTAREA node, without knowing ahead of time what textarea the focus is in, how do I find the active textarea and replace the selected text with some other text?
(( FYI: I'm using this code in a Chrome extension. An in-page javascript content script is extending the page javascript, so I have no idea what the page structure is ahead of time. It needs to work for any webpage. ))
I think the problem you may be having is that the active element changes as a result of clicking the button before your code runs. If you instead use the mousedown event and prevent the default button action, it works fine in Chrome:
http://jsfiddle.net/b3Fk5/2/
It appears that as of 8/23/2012, Chrome does not properly support activeElement, as it is often set to "body" when it shouldn't be.
There may also be some challenges because in my chrome extension, right-clicking to get a context menu might be altering the activeElement.
The solution was to provide a focus handler to create a more reliable activeElement in Chrome, and then use direct interaction with the TEXTAREA to handle the selection replacement.
var dActiveElement = null;
function _dom_trackActiveElement(evt) {
if (evt && evt.target) {
dActiveElement = evt.target;
console.log("focus on: " + dActiveElement.nodeName +
" id: " + dActiveElement.id);
} else {
console.log("focus else..");
}
}
if (document.addEventListener) {
document.addEventListener("focus",_dom_trackActiveElement,true);
}
function insertTextAtCursor(text) {
console.log("insertTextAtCursor : " + text);
if (dActiveElement.nodeName.toUpperCase() == "TEXTAREA") {
console.log("selection in textarea! id: " + dActiveElement.id);
var ta = dActiveElement;
var saveSelectionStart = ta.selectionStart;
var newvalue = ta.value.slice(0,ta.selectionStart) +
text + ta.value.slice(ta.selectionEnd,ta.length);
console.log("output : " + newvalue + ", len : " + newvalue.length);
var newSelectionEnd = ta.selectionStart + text.length;
ta.value = newvalue;
ta.selectionStart = ta.selectionEnd = (newSelectionEnd);
}
}

IE 8: move caret to the end of a text input field AND make the end visible

I'm trying to set the position of cursor/caret in a text box such that it is at the end of input control's text content. There are plenty examples of doing this. The tricky part is if the text extends beyond the width of the textbox, I'd like the text to 'scroll' into view so the end of the text and the caret are visible. This is exactly what is being asked in this question:
move caret to the end of a text input field AND make the end visible
There's a solution listed there that does work with Chrome and possibly later versions of IE (9 or later), but with IE 8 the solution doesn't work; there's no document.createEvent. I tried to modify the code so that I use createEventObject & fireEvent:
window.setTimeout(function() {
e = document.createEventObject("KeyboardEvent");
e.keyCode = 35;
//textfield.fireEvent('onkeypress', e);
textBox.fireEvent('onkeydown', e);
textBox.fireEvent('onkeyup', e);
textBox.blur();
textBox.focus();
}, 2000);
but this doesn't give the desired effect of horizontally scrolling the end of the text into view. Is there any way to do this with IE 8?
Thanks!
Notre
EDIT: In the end, I used this function which seems to work on all major browsers based on my testing.
scrollCaretIntoView: function(textField) {
if (textfield.selectionStart || textfield.selectionStart === 0) {
//For all browsers, except IE 8 and earlier
textfield.selectionStart = textfield.value.length;
textfield.selectionEnd = textfield.value.length;
textfield.blur(); // Webkit wake-up hack
textfield.focus();
} else if (document.selection) {
//IE8 and earlier specific code
textfield.focus();
var range = document.selection.createRange();
range.moveEnd('character', 0); //move 0 characters from current position
range.select();
}
}
I guess I've found solution for IE8 (I tested it in IE8 ONLY). See the fiddle
Here is a JS code:
var rng = text.createTextRange();
rng.moveEnd( 'textedit' );
rng.moveStart( 'textedit' );
rng.select();
rng.scrollIntoView();

JavaScript programatically hover mouse over element

I'm writing a vb.net program to automate and manage an online game. I'm using the Awesomium webcontrols to display and manipulate the pages of the game.
There is a point where I need to grab the data that's not shown in the source until the user hovers over a certain element, how can I use javascript (Not jquery please) to hover over it programatically until the data I need becomes available and then grabbed?
I apologise if this has been asked before (Which it has but from the perspective of someone who owns the web page) but I have been searching for hours for a solution and cant find anything.
What I've tried to use but failed is:
function findBpDate(){
document.getElementById('tileDetails').children[1].children[0].children[1].children[0].fireEvent('onmouseover');
return document.getElementsByClassName('text elementText')[0].textContent;
}
This returns "undefined" when it calls back to my application, I'm certain I'm pointing to the right DOM elements though.
This is what I want the javascript to "hover" on:
<span class="a arrow disabled">Send troops</span>
Once this element has been "hovered" on, this elements text changes to the text I need to grab:
<div class="text elementText">Beginners protection until 20/07/13 07:51 am.</div>
I've shown above what the element looks like when the mouse "hovers" on the element I need it to, however this changes a lot depending on which element the user hovers over while playing the game, from what i gather it's where the source keeps the text for each tooltip in the game.
So I need a function that will hover over a certain element and then while it's hovering, grab the text from the tooltip text/"text elementText" element.
Try WebView.InjectMouseMove(x, y).
Something like
public Point GetElementPosition(dynamic element)
{
dynamic rect = element.getBoundingClientRect();
using (rect)
{
return new Point(rect.left, rect.top);
}
}
dynamic element = webView.ExecuteJavascriptWithResult("document.getElementById('id')");
Point pos = GetElementPosition(element);
webView.InjectMouseMove(pos.X, pos.Y);
this is 10x easier with js/dom. http://jsfiddle.net/pA2Vd/
Do this...assuming you can get reference to elements somehow using by Id would have been lot easier.
var elm = document.getElementsByClassName('a arrow disabled')[0];
var txt = document.getElementsByClassName('text elementText')[0];
var evt = new Event('mouseover');
elm.dispatchEvent(evt);
var status = txt.innerText;
(helpfuL stuff down) otherwise you need to capture event, detect who fired it, check if that has this class and tag name. Lot of processing.
var txt,spn,status='';
document.getElementByTagName('span').forEach(function(d){
if (d.tagName=="div" && d.className == 'text elementText'){
var txt = d;
}
}
window.onmouseover = function(e) {
var elm = e.target;
if (elm.tagName=="SPAN" && elm.className == 'a arrow disabled') {
status=txt.innerText;
}
}

Issue with IE when Inserting text into text area at cursor position

I am having a scenario where I need to put cursor on text area and then click on tree view node on the same page to have selected node's text into my textarea where I placed cursor just before clicking on tree node.
I got many answers on Stack overflow including below,
Inserting text in textarea at cursor position if cursor placed else text should append at last in IE
Inserting text after cursor position in text areа
Insert text into textarea with jQuery
How to insert text at the current caret position in a textarea
Inserting text at cursor in a textarea, with Javascript
How do I insert some text where the cursor is?
FF and Chrome works fine with above solutions but IE 8 or lower version fails (didn’t check with IE9) if focus is moved to some other control.
There is below or similar implementation for IE in almost all posts:
(function ($) {
$.fn.getCursorPosition = function () {
var el = $(this).get(0);
var pos = 0;
if ('selectionStart' in el) {
pos = el.selectionStart;
} else if ('selection' in document) {
el.focus();
var Sel = document.selection.createRange();
var SelLength = document.selection.createRange().text.length;
Sel.moveStart('character', -el.value.length);
pos = Sel.text.length - SelLength
}
return pos;
}
})(jQuery);
Note : We also can use if(el.selectionStart || el.selectionStart == '0') instead of if ('selectionStart' in el) and if(document.selection) instead of if ('selection' in document)
But this will fail when focus is moved to some other control first and then executing it. In my case focus will be moved to tree nodes when user will traverse through nodes.
Is there any solution for this scenario?
I am thinking to write onkeyup and onclick on text area and save its cursor position into hidden field so when focus is moved to some other control, i will have hidden field to get cursor position of text area. I will post that here later, meanwhile if anyone has some good idea then please share.
Thank you in advance
As i mentioned above i have below code to make it working in IE as well,
(Also thought about having only onblur instead of these 2 events but it didn’t work as focus already lost when execution comes into my code to set hidden variable)
Below implementation It is working fine in my case.
if ($("#myTextArea").get(0).selectionStart == undefined) {
$("#myTextArea").click(function (e) {
$("#hdnTextAreaPosition").val($("#myTextArea").getCursorPosition());
});
$("#myTextArea").keyup(function (e) {
$("#hdnTextAreaPosition").val($("#myTextArea").getCursorPosition());
});
}
Above events (keyup and click) are in global script and will be attached only in case of selectStart is undefined
function getTextAreaCursorPosition() {
if ($("#myTextArea").get(0).selectionStart == undefined) {
return $("#hdnTextAreaPosition").val();
}
else {
return $("#myTextArea").getCursorPosition();
}
}
function insertToMyTextArea(textToInsert) {
$("#myTextArea").focus();
var cursorPosition = getTextAreaCursorPosition();
var myContent = $("#myTextArea").val();
$("#myTextArea").val(myContent.substring(0, cursorPosition) + textToInsert + myContent.substring(cursorPosition));
}
insertToMyTextArea is the main function i am calling on click of tree node.
Please share your views if any alternative solution is available instead of having events.
I would suggest using my jQuery plug-in for this in conjunction with some extra stuff to save the textarea's selection or cursor position before the focus is lost.
I've covered this exact case in a previous answer:
https://stackoverflow.com/a/5890708/96100

Tooltip triggered by text selection

I am looking to create a tooltip that is triggered by the selection of text (left-click dragging over text). Preferably by creating a JQuery plugin.
My ultimate goal is when a user selects/highlights a sentence, phrase, paragraph, it will trigger a tooltip. The tooltip will contain social sharing buttons that will allow a user to post the selection to their personal profile status.
So if you like a particular quote, you can select it, click share to twitter, it will call the twitter api to post the selection (if over the 140 characters it will add an ellipsis) with a shortened url back to the page of the selection.
Obviously this is going to take a bit of development, but as a front-end designer I just need to get the ball rolling. Thank you for any help you can provide.
An example of the functionality I desire is similar to how the apture extension functions:
http://www.apture.com/extension/
Here's a little demo: http://jsfiddle.net/sje397/fNt22/
It just monitors the selected text on a timer and tracks the mouse position, then creates a hovering div with a 'share' button at the mouse position whenever the selected text is not empty.
var mousePos;
$(document).mousemove(function (e) {
mousePos = {left: e.pageX + 20, top: e.pageY + 20};
});
var selectedText = '';
function getSelectedText(){
if(window.getSelection){
return window.getSelection().toString();
}
else if(document.getSelection){
return document.getSelection();
}
else if(document.selection){
return document.selection.createRange().text;
}
}
function checkSelectionChanged() {
var current = getSelectedText();
if(current != selectedText) {
selectedText = current;
if(selectedText != '') {
$('#quote #text').text(selectedText);
$('#quote').offset(mousePos);
$('#quote').show();
} else {
$('#quote').hide();
}
}
}
setInterval(checkSelectionChanged, 1000);
I already wrote this plugin :)
http://www.latentmotion.com/search-and-share
You're welcome to adapt upon it all you like, as long as you give credit.
I've also written a somewhat slicker one, but never fully vetted it (it's kinda in alpha):
http://seox.org/pro-beta.html

Categories