How can I get HTML-Code in a contentEditable-DIV - javascript

In a contentEditable-DIV I try to get the HTML-Code from strat-Position 0 to end-position where the user has clicked.
<div id="MyEditableId" contentEditable="true">
1. Some text 123. <span style="background-color: #0CF;">text 123</span> 456 <span style="background-color: #9F3;">2-> abc </span>
<br />
<p> E.g. here is clicked: "click" Text after click </p>
<p></p>
<br />
end of text.
</div>
Something as below code snippet, which delivers the text from 0 to end of clicked node. But I need also the HTML-Code in contentEditable-DIV.
$('#MyEditableId').on('mouseup', function(event) {
var MyEditable = document.getElementById('MyEditableId');
MyEditable.focus();
range = document.createRange();
// endOffset: It will be better the length of where actually was clicked, e.g. after 15-characters. But this.length will be also ok.
endOffset = $(this).length;
range.setStart(MyEditable.firstChild,0);
range.setEnd(event.target,endOffset);
var selection = window.getSelection();
selection.addRange(range);
// Below I get the selected text from 0 to end of clicked node. But I need the selected HTML-Code from 0 to end of clicked position.
alert( window.getSelection() );
});
I expect for the result something as follows:
1. Some text 123. <span style="background-color: #0CF;">text 123</span> 456 <span style="background-color: #9F3;">2-> abc </span>
<br />
<p> E.g. here is clicked: "click"
How can I get the HTML-Code instead of text in my contentEditable-DIV?
Thanks In Advance.

You can select the div and use its property innerHTML
http://jsfiddle.net/at917rss/
<div id="MyEditableId" contentEditable="true">
1. Some text 123. <span style="background-color: #0CF;">text 123</span> 456 <span style="background-color: #9F3;">2-> abc </span>
<br />
<p> E.g. here is clicked: "click" Text after click </p>
<p></p>
<br />
end of text.
</div>
$('#MyEditableId').on('mouseup', function(event) {
var MyEditable = document.getElementById('MyEditableId');
MyEditable.focus();
range = document.createRange();
// endOffset: It will be better the length of where actually was clicked, e.g. after 15-characters. But this.length will be also ok.
endOffset = $(this).length;
range.setStart(MyEditable.firstChild,0);
range.setEnd(event.target,endOffset);
var selection = window.getSelection();
selection.addRange(range);
// get html for your div
var myDiv = document.getElementById('MyEditableId');
alert(myDiv.innerHTML);
});

Just change the alert line in your code to below one works well..
alert($.trim($('<div>').append(range.cloneContents()).html()));

I was just going through the documentation for Range and selection. You could use the extractContents() or cloneContents() method supported by the Range object like so in your case Demo:
var fragment = window.getSelection().getRangeAt(0).extractContents();
This automatically gets the first range that the user has selected. Although users can select multiple ranges by holding down the "Ctrl" Key. This gives the exact match till the cursor position for the simpler cases.
There are some caveats to this though. Both the methods extractContents() or cloneContents() return a documentFragment and the documentation clearly states that:
Event Listeners added using DOM Events are not copied during cloning. HTML attribute events are duplicated as they are for the DOM Core cloneNode method. HTML id attributes are also cloned, which can lead to an invalid document through cloning.
In essence, document fragments can contain invalid HTML and therefore you can not use all the normal DOM like .html() in some cases.
I found a relevant SO post on getting the cursor position on a contentEditable element and came across a TextRange Object (supported in IE < 9) and it has an htmlText property which returns the HTML source of the selection as a 'valid' HTML fragment. So in that case you would do something like:
var fragment = document.selection.createTextRange().htmlText;
However since most of the modern browsers support Window.getSelection(), it's good practice that you use it and build upon the suitable methods you have at your disposal. Hope it gets you started in the right direction.
Sidenote - Also from the docs:
using a selection object as the argument to window.alert will call the object's toString method

Related

Do Firefox and Chrome treat differently selections?

I‘m trying to get the caret on an element with contenteditable="true", with a handler. In Firefox all works as expected and if I put the handler for an onclick event it works fine on Chrome as well. The problem arises when the handler is for an onfocus event of the contenteditable, which is what I want to do.
This is the code I'm using only to try to figure out why is this happening.
var editor = document.querySelector("#editor [contenteditable]");
var blackboard = document.querySelector("textarea");
editor.addEventListener('focus', () => {
blackboard.value = getSelection().focusNode.data;
console.log(getSelection(), getSelection().focusNode);
})
<div id="maq">
<textarea rows="44" cols="50">
</textarea>
</div>
<div id="editor">
<div contenteditable="true">
<p>11111</p>
<p>2222</p>
<p>3333<br></p>
</div>
</div>
getSelection seems to return the expected object. If I inspect it in the browser console its focusNode is a nodeText in contenteditable; but if I try actually to use the focusNode it returns the object I was before going to the contenteditable. For example, if I click in some text in the navigation bar and then click in contenteditable, it returns the nodeText in the navigation bar.
Any idea why is this?
Thank you.
getSelection() doesn't return a DomNode but a Selection object which, based on your results, I suspect is a live value, just like how node.childNodes is a NodeList which gets automatically updated if you add or remove children.
So when the selection changes you magically get the new selection.

innerText on content editable doubling some line breaks

I am trying to read multi line user input on a content editable div, and I don't get the right number of line breaks when I read the input with contentEditableDiv.innerText.
I tried textContent, but it doesn't return any line break, while innerText returns too many sometimes. innerHTML doesn't seem appropriate since I don't want any HTML code, just text.
If my div contains:
a
b
It returns "a↵b" (97 10 98 in the example)
But if my <div> contains:
a
b
innerText returns a↵↵↵b (one too many ↵, 97 10 10 10 98 in the example)
var input = document.getElementById("input");
var button = document.getElementById("button");
var result = document.getElementById("result");
button.addEventListener("click", (event) => {
var charCodes = "";
for (var i = 0; i < input.innerText.length; ++i) {
charCodes += input.innerText.charCodeAt(i) + " ";
}
result.innerText = charCodes;
});
<div id="input" contenteditable="true" spellcheck="true" style="border:1px #000 solid"></div>
<button id="button">check</button>
<div id="result"></div>
The standard is a bit vague:
UAs should offer a way for the user to request an explicit line break at the caret position without breaking the paragraph, e.g. as the default action of a keydown event whose identifier is the "Enter" key and that has a shift modifier set. Line separators are typically found within a poem verse or an address. To insert a line break, the user agent must insert a br element.
If the caret is positioned somewhere where phrasing content is not allowed (e.g. in an empty ol element), then the user agent must not insert the br element directly at the caret position. In such cases the behavior is UA-dependent, but user agents must not, in response to a request to insert a line separator, generate a DOM that is less conformant than the DOM prior to the request.
To conform this definition, it is safe to wrap the <br> element into <div>, which Chrome does, but it is UA dependent, so you should not rely on this. The side effect on this behavior and the cause of your problem is that both div and br elements produces line break in the innerText property.
The innerHTML of a↵↵b looks like this in Chrome:
a
<div>
<br>
<div>
<br>
<div>
b
</div>
</div>
</div>
But if you paste it (instead of typing char by char) it looks like this
<div>a</div>
<div>
<br>
</div>
<div>
<br>
</div>
<div>b</div>
To reduce the line breaks, you need to further process the innerHTML and treat
<div><br></div> after every input change as a single line-break and then read the innerText (with caution on places where phrasing content like br is not allowed, but in real life browsers can handle them well).

Can jQuery or Javascript change elements within textareas?

My first SO question! Here's what I am trying to do:
I'm rewriting a tool that generates some code a user can paste directly into Craigslist and other classified ad posting websites. I have created a list of websites (they populate from a database with PHP) the user can choose from with a radio button, and I want their choice to populate as bare text (not a link) between some <p></p> elements in a textarea. I'm using jQuery for this.
Textarea before the user chooses:
<p id="thing"></p>
Textarea after the user chooses:
<p id="thing">www.somewebsite.com</p>
HTML
<input type="radio" name="sitechoice" value="www.websiteone.com">www.websiteone.com<br />
<input type="radio" name="sitechoice" value="www.secondwebs.com">www.secondwebs.com
<textarea>
Some stuff already in here
Here is the website you chose:
<p id="thing"></p>
More stuff already here.
</textarea>
JS
$(document).ready(function () {
$("input").change(function () {
var website = $(this).val();
alert(website);
$("#thing2").html(website);
});
});
JS Fiddle (With comments)
If you see the JS Fiddle, you can see that I put another p element on the page outside the textarea, and it updates just fine, but the one inside the textarea does not. I have read many other like questions on SO and I'm starting to think that I can't change an element that's between textarea tags, I can only change the entire textarea itself. Please, lead me to enlightenment!
You actually can fairly easily manipulate the text contents of the textarea like it is part of the DOM, by transforming its contents into a jQuery object.
Here is a jsFiddle demonstrating this solution: http://jsfiddle.net/YxtH4/2/
The relevant code, inside the input change event:
// Your normal code
var website = $(this).val();
$("#thing2").html(website);
// This turns the textarea's val into a jQuery object ...
// And inserts it into an empty div that is created
var textareaHtml = $('<div>' + $("#textarea").val() + '</div>');
// Here you can do your normal selectors
textareaHtml.find("#thing").html(website);
// And this sets the textarea's content to the empty div's content
$("#textarea").val(textareaHtml.html());
The empty div wrapping your HTML is so that you can easily retrieve it as a string later using jQuery's .html() method, and so the parse does not fail if additional text is entered around the p element inside the textarea.
The real magic is $($("#textarea").val()), which takes your textarea's text and parses it into an HTML node contained in a jQuery object.
It can't do it the way that you are thinking (i.e., manipulate it as if it were a DOM element), but it is still accessible as the value of the textarea, so you can retrieve it like that, use basic string manipulation to alter it, and then set the updated string as the new value of the textarea again.
Something like this . . . first give the <textarea> an id value:
<textarea id="taTarget">
Some stuff already in here
Here is the website you chose:
<p id="thing"></p>
More stuff already here.
</textarea>
Then alter your script like this:
$(document).ready(function () {
$("input").change(function () {
var website = $(this).val();
var currentTAVal = $("#taTarget").val();
$("#taTarget").val(currentTAVal.replace(/(<p id="thing">)([^<]*)(<\/p>)/, "$1" + website + "$3"));
});
});
Unless you need the <p> element in there, you might consider using a more simple placeholder, since it won't actually act as an HTML element within the textarea. :)
EDIT : Fixed a typo in the .replace() regex.
I know that this answer is a little bit late, but here it goes =)
You can do exactly the way you want to do. But for that, you need to implement a small trick.
by having this HTML
<input type="radio" name="sitechoice" value="www.websiteone.com">www.websiteone.com
<br />
<input type="radio" name="sitechoice" value="www.secondwebs.com">www.secondwebs.com
<p id="thing2"></p>
<textarea id="textarea">
<p id="thing"></p>
</textarea>
you can edit textarea content, as a DOM by implementing something like the function changeInnerText
$(document).ready(function () {
$("input").change(function () {
var website = $(this).val(); // Gets value of input
changeInnerText(website);
//$("#thing").html(website); // Changes
//$("#thing2").html(website); // Does not change
});
var changeInnerText = function(text) {
var v = $("#textarea").val();
var span = $("<span>");
span.html(v);
var obj = span.find("#thing")[0];
$(obj).html(text);
console.log(obj);
console.log(span.html());
$("#textarea").val(span.html());
}
});
As you can see, I just get the information from the textarea, I create a temporary variable span to place textarea's content. and then manipulate it as DOM.
Instead of attempting to insert the text into the <p> element, insert the text into <textarea> element and include the <p> tag. Something like this should do the trick:
Change:
$("#thing").html(website);
to:
$("textarea").html('<p id="thing">'+website+'</p>');
And here is a fiddle: http://jsfiddle.net/nR94s/

How to find the selection text beginning and end positions in Javascript?

I am trying to find the textual start and end of the selection. So, in the following text, if I selected "world! What a fine" from within "Hello, world! What a fine day it is!", I should get 7 as the start coordinate, and 24 as the end coordinate assuming a zero based index.
How is this achievable?
EDIT:
I am looking to find the selection of text that is not inside any <input> or <textarea> elements.
EDIT:
Decided the solution to use disabled <textarea>s
I use this:
/* Returns 3 strings, the part before the selection, the part after the selection and the selected part */
function getSelected()
{
var u = editor.val();
var start = editor.get(0).selectionStart;
var end = editor.get(0).selectionEnd;
return [u.substring(0, start), u.substring(end), u.substring(start, end)];
}
where editor is $("#editor") or whatever ID your textarea / input field may have.
Usage:
var select = getSelected()
editor.val(select[0] + '<h1>'+ select[2] + '</h1>' + select[1]);
Will wrap selected text in H1. If nothing is selected it will just add empty H1, but you can add checks and functionality to your liking.
** Not tested in all browsers, works in Chrome though **
This is possible but slightly complicated with contenteditable HTML content (as opposed to text within an <input> or <textarea> element). Here's a simple cross-browser implementation:
https://stackoverflow.com/a/4812022/96100
But I know a jQuery plugin that aims your problem and much more - https://github.com/localhost/jquery-fieldselection

Backspace doesn't delete inner html tags of a contenteditable DIV in Firefox

I have created a DIV with attribute contenteditable=true and appended children like span and a with attributes contenteditable=false.
Wanted to test if the entire node be deleted with a single backspace and to my surprise Firefox couldn't delete the elements.
Also, this works as expected in all major desktop browsers except Firefox.
Any clues on this or what could be the possible workaround?
Found the exact issue on bugzilla here.
Okay! found the solution... its rather simple than what you would think. I am actually inserting html for links, so using <a> here. The <a> tag has attribute set to contenteditable=false and its not getting deleted with a backspace. So I have created an inner <span> level with contenteditable=true for firefox and that did the trick.
<div contentEditable="true">
<a href="your/url/path" contentEditable="false">
<span contentEditable="true">link here</span>
</a>
</div>
This is required in Firefox only. Other browsers treat this as expected with the span having content contenteditable=false.
I have faces the same terrible bug and had no choice but to make an elaborate javascript-based solution that listens to keypress events and if backspace was pressed, and the caret was just at the start offset of a text node, and the node before it is an element node, then delete that whole element node.
// credit: https://stackoverflow.com/a/25397485/104380
var isFF = !!navigator.userAgent.match(/firefox/i)
var editableElm = document.querySelector('div')
// listen to any key press
if( isFF )
editableElm.addEventListener('keydown', onKeydown)
function onKeydown(e){
if( e.key == "Backspace" ){
var selection = document.getSelection();
// if caret is at the begining of the text node (0), remove previous element
if( selection && selection.anchorOffset == 0 )
selection.anchorNode.previousSibling.parentNode.removeChild(selection.anchorNode.previousSibling)
}
}
<div contenteditable='true'>
Try deleting theme <mark contenteditable='false'>marked</mark> words on <mark contenteditable='false'>Firefox</mark> ok?
</div>
Try adding an "onkeydown" function on the editable div to stop event propagation (as follows). Worked for me:
onKeyDown = (e) => { e.stopPropagation(); }

Categories

Resources