I have a div set to contentEditable
and styled with \"white-space:pre
\" so it keeps things like linebreaks. In Safari, FF and IE, the div pretty mu
Unfortunately you do still have to handle this for the pre
case individually per-browser (I don't condone browser detection in many cases, use feature detection...but in this case it's necessary), but luckily you can take care of them all pretty concisely, like this:
var ce = $("<pre />").html($("#edit").html());
if($.browser.webkit)
ce.find("div").replaceWith(function() { return "\n" + this.innerHTML; });
if($.browser.msie)
ce.find("p").replaceWith(function() { return this.innerHTML + "<br>"; });
if($.browser.mozilla || $.browser.opera ||$.browser.msie )
ce.find("br").replaceWith("\n");
var textWithWhiteSpaceIntact = ce.text();
You can test it out here. IE in particular is a hassle because of the way is does
and new lines in text conversion, that's why it gets the <br>
treatment above to make it consistent, so it needs 2 passes to be handled correctly.
In the above #edit
is the ID of the contentEditable
component, so just change that out, or make this a function, for example:
function getContentEditableText(id) {
var ce = $("<pre />").html($("#" + id).html());
if ($.browser.webkit)
ce.find("div").replaceWith(function() { return "\n" + this.innerHTML; });
if ($.browser.msie)
ce.find("p").replaceWith(function() { return this.innerHTML + "<br>"; });
if ($.browser.mozilla || $.browser.opera || $.browser.msie)
ce.find("br").replaceWith("\n");
return ce.text();
}
You can test that here. Or, since this is built on jQuery methods anyway, make it a plugin, like this:
$.fn.getPreText = function () {
var ce = $("<pre />").html(this.html());
if ($.browser.webkit)
ce.find("div").replaceWith(function() { return "\n" + this.innerHTML; });
if ($.browser.msie)
ce.find("p").replaceWith(function() { return this.innerHTML + "<br>"; });
if ($.browser.mozilla || $.browser.opera || $.browser.msie)
ce.find("br").replaceWith("\n");
return ce.text();
};
Then you can just call it with $("#edit").getPreText()
, you can test that version here.
see this fiddle
Or this post
How to parse editable DIV's text with browser compatibility
created after lot of effort...........
here's a solution (using underscore and jquery) that seems to work in iOS Safari (iOS 7 and 8), Safari 8, Chrome 43, and Firefox 36 in OS X, and IE6-11 on Windows:
_.reduce($editable.contents(), function(text, node) {
return text + (node.nodeValue || '\n' +
(_.isString(node.textContent) ? node.textContent : node.innerHTML));
}, '')
see test page here: http://brokendisk.com/code/contenteditable.html
although I think the real answer is that if you're not interested in the markup provided by the browser, you shouldn't be using the contenteditable
attribute - a textarea would be the proper tool for the job.
I discovered this today in Firefox:
I pass a contenteditable div who's white-space is set to "pre" to this function, and it works sharply.
I added a line to show how many nodes there are, and a button that puts the output into another PRE, just to prove that the linebreaks are intact.
It basically says this:
For each child node of the DIV,
if it contains the 'data' property,
add the data value to the output
otherwise
add an LF (or a CRLF for Windows)
}
and return the result.
There is an issue, tho. When you hit enter at the end of any line of the original text, instead of putting a LF in, it puts a "Â" in. You can hit enter again and it puts a LF in there, but not the first time. And you have to delete the "Â" (it looks like a space). Go figure - I guess that's a bug.
This doesn't occur in IE8. (change textContent to innerText) There is a different bug there, tho. When you hit enter, it splits the node into 2 nodes, as it does in Firefox, but the "data" property of each one of those nodes then becomes "undefined".
I'm sure there's much more going on here than meets the eye, so any input on the matter will be enlightening.
<!DOCTYPE html>
<html>
<HEAD>
<SCRIPT type="text/javascript">
function htmlToText(elem) {
var outText="";
for(var x=0; x<elem.childNodes.length; x++){
if(elem.childNodes[x].data){
outText+=elem.childNodes[x].data;
}else{
outText+="\n";
}
}
alert(elem.childNodes.length + " Nodes: \r\n\r\n" + outText);
return(outText);
}
</SCRIPT>
</HEAD>
<body>
<div style="white-space:pre;" contenteditable=true id=test>Text in a pre element
is displayed in a fixed-width
font, and it preserves
both spaces and
line breaks
</DIV>
<INPUT type=button value="submit" onclick="document.getElementById('test2').textContent=htmlToText(document.getElementById('test'))">
<PRE id=test2>
</PRE>
</body>
</html>
this.editableVal = function(cont, opts)
{
if (!cont) return '';
var el = cont.firstChild;
var v = '';
var contTag = new RegExp('^(DIV|P|LI|OL|TR|TD|BLOCKQUOTE)$');
while (el) {
switch (el.nodeType) {
case 3:
var str = el.data.replace(/^\n|\n$/g, ' ').replace(/[\n\xa0]/g, ' ').replace(/[ ]+/g, ' ');
v += str;
break;
case 1:
var str = this.editableVal(el);
if (el.tagName && el.tagName.match(contTag) && str) {
if (str.substr(-1) != '\n') {
str += '\n';
}
var prev = el.previousSibling;
while (prev && prev.nodeType == 3 && PHP.trim(prev.nodeValue) == '') {
prev = prev.previousSibling;
}
if (prev && !(prev.tagName && (prev.tagName.match(contTag) || prev.tagName == 'BR'))) {
str = '\n' + str;
}
}else if (el.tagName == 'BR') {
str += '\n';
}
v += str;
break;
}
el = el.nextSibling;
}
return v;
}
I forgot about this question until now, when Nico slapped a bounty on it.
I solved the problem by writing the function I needed myself, cribbing a function from the existing jQuery codebase and modifying it to work as I needed.
I've tested this function with Safari (WebKit), IE, Firefox and Opera. I didn't bother checking any other browsers since the whole contentEditable thing is non-standard. It is also possible that an update to any browser could break this function if they change how they implement contentEditable. So programmer beware.
function extractTextWithWhitespace(elems)
{
var lineBreakNodeName = "BR"; // Use <br> as a default
if ($.browser.webkit)
{
lineBreakNodeName = "DIV";
}
else if ($.browser.msie)
{
lineBreakNodeName = "P";
}
else if ($.browser.mozilla)
{
lineBreakNodeName = "BR";
}
else if ($.browser.opera)
{
lineBreakNodeName = "P";
}
var extractedText = extractTextWithWhitespaceWorker(elems, lineBreakNodeName);
return extractedText;
}
// Cribbed from jQuery 1.4.2 (getText) and modified to retain whitespace
function extractTextWithWhitespaceWorker(elems, lineBreakNodeName)
{
var ret = "";
var elem;
for (var i = 0; elems[i]; i++)
{
elem = elems[i];
if (elem.nodeType === 3 // text node
|| elem.nodeType === 4) // CDATA node
{
ret += elem.nodeValue;
}
if (elem.nodeName === lineBreakNodeName)
{
ret += "\n";
}
if (elem.nodeType !== 8) // comment node
{
ret += extractTextWithWhitespace(elem.childNodes, lineBreakNodeName);
}
}
return ret;
}