Paragraph breaks in 2 when adding link element - javascript

I have this wicked problem only in Firefox: when I add a link element to a contentEditable paragraph sometimes it breaks the paragraph in 2 or 3. This doesn't show any error and sometimes takes few seconds. Here's the code:
function changeSelectedText(type,text) {
var selObj = window.getSelection();
var selRange = selObj.getRangeAt(0);
var newElement = document.createElement(type);
if(type == "a") {
newElement.setAttribute('href', text.toLowerCase());
newElement.setAttribute('target', "_blank");
} else if(type == "span"){
newElement.setAttribute('class', "big");
}
var documentFragment = selRange.extractContents();
newElement.appendChild(documentFragment);
selRange.insertNode(newElement);
var range = document.createRange();
range.selectNodeContents(newElement);
selObj.removeAllRanges();
selObj.addRange(range);
}
Adding span works perfect but with links it sometimes causes this strange behavior. Any idea why?
Here's the link to jsfiddle like Mike suggested:
jsfiddle link

Found it :) The problem was that the button that I used for adding the link (the #link div at jsfiddle) sometimes got selected (only FF). I thought that the mousedown function should prevent this from happening but it didn't. So, what I did is I've added js to prevent selecting that button:
<div id="#link" onselectstart="return false;" ondragstart="return false;">Add Link</div>
If anybody has some better solution let me know ;)

Related

Caret disappears in Firefox when saving its position with Rangy

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/

Inline CKEditor adds <br> to empty div on startup

CKEditor in Inline mode adds a <br> in the source of the document to an empty div when it is initialized. When you check the source in CKEditor it shows completely empty. I guess this is done to stop collapsing the div or whatever element it is editing on, but for me this is causing issues since I target the empty div with CSS to display a placeholder.
I have searched about everywhere on how to disable this and have seen some issues with FireFox many years ago, but that seems to be unrelated.
<div id="editarea" placeholder="Title"></div>
CKEDITOR.inline('editarea, {});
<style>
div:empty:after {
content: attr(placeholder);
}
</style>
When you look in the Developer Tools the source of the document looks like:
<div id="editarea" placeholder="Title">
<br>
</div>
Adding the following to the config does not seem to be doing anything:
config.fillEmptyBlocks = false;
Can help someone else, the solution I found, pass by the editor event listener:
// Ckeditor 4
editor.on('key', function (evt) {
var selection = editor.getSelection();
var element = selection.getStartElement();
var html = element.getHtml();
var id = element.getId();
if (evt.data.keyCode !== 13
&& id === 'editarea')) {
if (html.match(/( )?<br>$/)) {
element.setHtml('');
}
}
})
That <br> is a "filler" and is always placed at input (editor initialization) into empty block elements by function createBogusAndFillerRules to give an height to the element so the user can click on it to edit it.
Actually there is not a CKeditor configuration to avoid this behaviour but, using jQuery, we can remove the <br> on instanceReady.ckeditor event with:
if($(this).html().trim() === '<br>'){
$(this).html('');
}
Please try this code it is working for me:
For removing br on load
$('your_id').ckeditor();
CKEDITOR.on('instanceReady', function() {
$('div.application').find('br').remove(); // remove br tag added on empty divs
});
for removing &nbsp use this in destroy function:
for(CKname in CKEDITOR.instances) // delete multiple instances of ckeditor
{
CKEDITOR.instances[CKname].destroy(function(){
$("your_id").html(function (i, html) {
return html.replace(/ /g, ''); // remove
});
})
}

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

Javascript text render error on update (webkit only)

I'm trying to toggle text on click. When 'Pause' is clicked change the text to 'Play'.
For some reason the text is updating but not rendering correctly. It's as if that part of the DOM is being updated but not refreshed. For some reason this is only happening in the webkit browsers (Safari 5, Chrome 11). Firefox 4 is rendering it the way it should.
Here's a video of the problem: http://www.youtube.com/watch?v=tIRKx25NmYo
I'm using Cmd+A in the video to select the text, which appears to refresh the text and get it to display properly.
Here's the code:
<span class="playercontrols" id="playpause" onclick="toggle(this.id);" style="cursor:pointer;">Pause</span>
<script type="text/javascript">
function toggle(sender){
var t = document.getElementById(sender);
var txt = t.innerHTML;
switch(txt)
{
case 'Pause':
txt = 'Play';
pause();
break;
case 'Play':
txt = 'Pause';
play();
break;
default:
txt = 'Pause';
}
t.innerHTML = txt;
}
</script>
EDIT: I commented out every other piece of javascript referenced and written on the page and the problem is still there. I have no idea what's wrong but it doesn't appear to be a collision or conflict.
SOLVED IT. (Not allowed to mark this answered because it's too recent.)
Edit: Answer now added below.
Thanks for all the comments. Hopefully this post will help others in the future with the same problem.
It seems that flashing the text (setting visibility to hidden and then back to its original value) fixes the issue..
So here is the work around.
t.innerHTML = txt;
/* add the following right after changing the text */
var visibility = t.style.visibility;
t.style.visibility = 'hidden';
setTimeout( function(){
t.style.visibility = visibility;
}, 1);
demo http://jsfiddle.net/gaby/SgwsZ/4/
The problem was with the styling on the "playercontrols" class.
.playercontrols {
position: relative;
top: 80px;
left: 50px;
}
I had it set to position relative which apparently sent webkit's update rendering out of whack.

How can I get the original text node of the event?

for example ,I have the following html
<HTML>
<HEAD>
<script type="text/javascript">
function trackElement(event){
event=event||window.event;
var target = event.explicitOriginalTarget||event.srcElement||document.activeElement;
var targetText = target.nodeValue||target.innerHTML;
alert(targetText);
}
</script>
</HEAD>
<BODY onclick="trackElement(event)">
<div>bbbbbb<div>cccccc</div>dddddddddd<div>eeeeeeeee</div></div>
</BODY>
</HTML>
When I clicked "bbbbbb",
On firefox ,I got "bbbbbb" alerted
which is exactly what I expected.
But on IE, I got
"bbbbbb<div>cccccc</div>dddddddddd<div>eeeeeeeee</div>"
When I clicked "dddddddddd",
On firefox ,I got "dddddddddd"
alerted which is exactly what I
expected.
But on IE, I got
"bbbbbb<div>cccccc</div>dddddddddd<div>eeeeeeeee</div>"
How can I get the same result with firefox on IE?
That's because the property "nodeValue" returns null for element nodes so you return innerHtml instead, which is gonna contain the whole html code inside an element. In Internet Explorer, your target is assigned by event.srcElement and hence is an element node, while it's a text node in FF. One way to solve the problem would be the following code :
function trackElement(event){
event=event||window.event;
var targetText;
var target = event.explicitOriginalTarget||event.srcElement||document.activeElement;
if(target.nodeType==3){
targetText = target.nodeValue
}else{
targetText = target.firstChild.nodeValue;
}
alert(targetText);
}
This way, you'd return the value if your assignment assigned a text node, and the text of the first child (which is what you're after) for an element node.
EDIT: Turns out I'm wrong. The problem is that event.srcElement returns the whole Element that's being clicked (event.explicitOriginalTarget being mozilla specific). From there, you need to retrieve the text. I see no easy way to do that if there are several texts in the element. If there is only one, it's a matter of iterating over the child nodes and displaying the text ones.
Please see comment about explicitoriginaltarget in crossbrowser-equivalent-of-explicitoriginaltarget-event-parameter.
In IE you get the Div element and the second one is in it, does this solution of separating the divs work for you? - the script is the same...
(This works both in IE and in FF)
<BODY onclick="trackElement(event)">
<div>bbbbbb</div>
<div>cccccc</div>
</BODY>
What about just substringing it?
<script type="text/javascript">
function trackElement(event){
event=event||window.event;
var target = event.explicitOriginalTarget||event.srcElement||document.activeElement;
var targetText = target.nodeValue||target.innerHTML.substr(0, target.innerHTML.indexOf("<"));
alert(targetText);
}
</script>
</HEAD>
<BODY onclick="trackElement(event)">
<div>bbbbbb<div>cccccc<p>Hellooo</p></div></div>
This seems to work, clicking ccccc returns "cccccc", and so on. Or did I completely miss the point?
EDIT: You also need to check if the element has any child elements before substringing it...
finally,I use offset to check which text node is clicked as below:
if ($.browser.mozilla) {
el = event.originalEvent.explicitOriginalTarget;
}else{
var parent =event.srcElement;
var left = event.pageX;
var top = event.pageY;
var offset=$(parent).offset();
for(var i=0;i<parent.childNodes.length;i++){
var n = parent.childNodes[i];
if ( n.nodeType == 1 ){
if($(n).offset().top>offset.top){
if($(n).offset().top>top){
el=parent.childNodes[i-1];
break;
}
}else if($(n).offset().top+$(n).height()>offset.top){
if($(n).offset().left>left){
el=parent.childNodes[i-1];
break;
}
}
}
el=n;
}
}

Categories