My code is here on codepen
I am trying to count the characters and wrap those that exceed a certain limit in a <span> which I've styled as red.
The code mostly works with the exception of the function that moves the caret to the end, on Firefox the ranges I create there lose all their whitespaces so that 'abcd abcd' becomes 'abcdabcd'
Here is the code for the function:
function placeCaretAtEnd(el) {
el.focus();
if (typeof window.getSelection != "undefined"
&& typeof document.createRange != "undefined") {
// Modern browsers
var range = document.createRange();
range.selectNodeContents(el);
range.collapse(false);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
} else if (typeof document.body.createTextRange != "undefined") {
// IE
var textRange = document.body.createTextRange();
textRange.moveToElementText(el);
textRange.collapse(false);
textRange.select();
}
}
Has anybody faced this before and found a solution ?
Firefox is ignoring white spaces in HTML. You have to modify it to replace space characters with html escape characters. In place of the code
inputText.substring(0,maxCharacter)
You should do
inputText.substring(0,maxCharacter).replace(/ /g, ' ')
Similarly in place of code
inputText.substring(maxCharacter)
You should do
inputText.substring(maxCharacter).replace(/ /g, ' ')
That will fix it. Hopefully it will work in all other browsers too.
Changed pen: http://codepen.io/anon/pen/GgXBjp
Related
I am writing a messenger and my aim is to add an opportunity to insert code and highlight it as it is done on Stack Overflow. I am using prettify library by Google.
Here is html part:
<div id="test" style="margin-left: auto; margin-right: auto; overflow-y: scroll;" contenteditable="true"
placeholder="Type your message" name="message" ng-model="message">
</div>
and js function:
$scope.pasteHtmlAtCaret = function () {
document.getElementById('test').focus();
var sel, range;
if (window.getSelection) {
// IE9 and non-IE
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
// Range.createContextualFragment() would be useful here but is
// only relatively recently standardized and is not supported in
// some browsers (IE9, for one)
var el = document.createElement("div");
//var el = document.getElementById('test');
/*var text = sel.toString().replace(/</g, "<");
text = text.replace(/>/g, ">");
text = text.replace(/&/g, "&");*/
el.innerHTML = "<pre class=prettyprint linenums><code>" + sel.toString() + '</code></pre></br>';
range.deleteContents();
var frag = document.createDocumentFragment(), node, lastNode;
while ((node = el.firstChild)) {
lastNode = frag.appendChild(node);
}
var firstNode = frag.firstChild;
range.insertNode(frag);
prettyPrint();
// Preserve the selection
if (lastNode) {
range = range.cloneRange();
range.setStartAfter(lastNode);
range.setStartBefore(firstNode);
sel.removeAllRanges();
sel.addRange(range);
}
}
} else if ((sel = document.selection) && sel.type != "Control") {
var originalRange = sel.createRange();
originalRange.collapse(true);
sel.createRange().pasteHTML(html);
range = sel.createRange();
range.setEndPoint("StartToStart", originalRange);
range.select();
}
}
As you can see, I have already tried to place < (in comments) and other symbols instead of <, >, & symbols. However, they get displayed as they are in js code, not like symbols. Could you tell me, please, how can I insert prettified html code inside my div block (or, possibly, another container).
Possibly, you could also tell me, how to make prettify show line numbers, because they are not shown right now (they are taken as another class somehow)
Afaict, you're creating a <pre class=prettyprint>, but the prettify library never actually gets called on it.
I think you can just use PR.prettyPrintOne and pass in your HTML source code. You're correct that you should escape &, <, and > first since it takes HTML, not plain source code.
PR.prettyPrintOne is available when you load <script src=".../prettify.js"></script> but not when you load <script src=".../run_prettify.js"></script>.
/*var text = sel.toString().replace(/</g, "<");
text = text.replace(/>/g, ">");
text = text.replace(/&/g, "&");*/
There's a bug here. You should do the .replace(/&/g, "&") first because
"<".replace(/</g, "<").replace(/&/g, "&") === "<"
whereas
"<".replace(/&/g, "&").replace(/</g, "<") === "<"
Replace order matters when some of the substitution strings also match patterns :)
function htmlEscape(s) {
return (String(s).replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">"));
}
I have a content editable DIV containing a P tag with text in it and am using placeCaretAtEnd to position the caret at the end of the content inside the P tag. This seems to work well for every browser with the exception of Firefox (v32.0.3). On that, the caret just seemingly disappears.
From what I can tell, the placeCaretAtEnd JS function is supposedly fully compatible with Firefox. Any idea why this isn't working in this instance?
Working example at: http://jsfiddle.net/0ktff3zj/1/
<input id=button type=button value="Caret to end">
<div contenteditable=true>
<p id="paragraph">Sample text. Sample text.</p>
</div>
<script>
$('#button').bind('click', function() {
placeCaretAtEnd(document.getElementById('paragraph'))
});
function placeCaretAtEnd(el) {
el.focus();
if (typeof window.getSelection != "undefined"
&& typeof document.createRange != "undefined") {
var range = document.createRange();
range.selectNodeContents(el);
range.collapse(false);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
} else if (typeof document.body.createTextRange != "undefined") {
var textRange = document.body.createTextRange();
textRange.moveToElementText(el);
textRange.collapse(false);
textRange.select();
}
}
</script>
I worked around this issue by placing the cursor in the contenteditable div and then using the placeCaratAtEnd function to move it.
First I gave the div an id so there would be no mix ups.
$('#button').bind('click', function() {
$('div#id').focus();
placeCaretAtEnd(document.getElementById('paragraph'))
});
I searched forever and while I know this is a bit on the hackish side, it is a solution.
I know how to set an <a /> tag with the href attribute in a contenteditable like this:
execCommand("CreateLink", false, "#jumpmark");
which will result in
selection
However I cannot figure out how to set an anchor name instead of the href.
This is my desired result:
<a name="jumpmark">selection</a>
Can anyone help me?
Side notes: I am using jQuery and Rangy as libraries, however I would prefer a solution that works directly with execCommand.
Update: Here's a jsfiddle: http://jsfiddle.net/fjYHr/ Select some text and click the button. All I want is that with the button click a link is inserted with a name attribute set instead of the href.
You could use something like the following, which is adapted from the pasteHtmlAtCaret() function from this answer of mine:
Demo: http://jsfiddle.net/F8Zny/
Code:
function surroundSelectedText(element) {
var sel, range;
if (window.getSelection) {
// IE9 and non-IE
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
element.appendChild( document.createTextNode(range.toString()) );
range.deleteContents();
range.insertNode(element);
// Preserve the selection
range = range.cloneRange();
range.setStartAfter(element);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
} else if (document.selection && document.selection.type != "Control") {
// IE < 9
var selRange = document.selection.createRange();
element.appendChild( document.createTextNode(selRange.text) );
selRange.pasteHTML(element.outerHTML);
}
}
If you must use document.execCommand() then you could use the InsertHTML command in non-IE browsers. However, IE does not support it.
document.execCommand("InsertHTML", false, '<a name="jumpmark">selection</a>');
I see you're using Rangy, but I don't how to use it at all. Before I realized what Rangy was, I looked up how to get the current selection. I found a function that gets it and replaces it with a passed in value. I ended up modfiying it, but here it is:
http://jsfiddle.net/fjYHr/1/
$(document).ready(function () {
$("#setlink").click(function () {
replaceSelectedText("jumplink");
});
});
function replaceSelectedText(nameValue) {
var sel, sel2, range;
if (window.getSelection) {
sel = window.getSelection();
sel2 = ""+sel; // Copy selection value
if (sel.rangeCount) {
range = sel.getRangeAt(0);
range.deleteContents();
var newA = document.createElement("a");
newA.name = nameValue;
newA.innerHTML = sel2;
range.insertNode(newA);
}
} else if (document.selection && document.selection.createRange) {
// Not sure what to do here
range = document.selection.createRange();
var newA = "<a name='" + nameValue.replace(/'/g, "") + "'>" + range.text + "</a>";
range.text = newA;
}
}
Notice how I store the original current selection, then replace it with an <a> element that gets its name set with the passed-in value.
As for the document.selection part (which seems to be used by IE < 9), I'm not 100% sure that the code I provided will work (actually allow HTML in the selection, and not escaping it). But it's my attempt :)
As you've seen execCommand is rather limited in the attributes you can set, as such you cannot set the name attribute using it - only the href.
As you have jQuery set as a tag, you can use that as an alternative:
var $a = $('<a></a>').attr('name', 'jumpmark').appendTo('body');
Update
I need to work on the current selection. Specifically I don't have a jQuery object that I can append to, meaning I don't have a DOM node that I can work on
In this case use a plugin such as Rangy to get the selection which you can then amend with jQuery as required.
Is there a simple js function I can use to replace the current document's selection with some html of mine?
For instance say the document contains a <p>AHAHAHA</p> somewhere and user selects the 1st "ha" text chunk.
Now I want to replace this with something like: <span><font color="red">hoho</font></span>
When I google for *javascript replace selection * I can't get a simple straightforward answer!
Yes. The following will do it in all major browsers, with an option to select the inserted content afterwards as requested in the comments (although this part is not implemented for IE <= 8):
Live demo: http://jsfiddle.net/bXsWQ/147/
Code:
function replaceSelection(html, selectInserted) {
var sel, range, fragment;
if (typeof window.getSelection != "undefined") {
// IE 9 and other non-IE browsers
sel = window.getSelection();
// Test that the Selection object contains at least one Range
if (sel.getRangeAt && sel.rangeCount) {
// Get the first Range (only Firefox supports more than one)
range = window.getSelection().getRangeAt(0);
range.deleteContents();
// Create a DocumentFragment to insert and populate it with HTML
// Need to test for the existence of range.createContextualFragment
// because it's non-standard and IE 9 does not support it
if (range.createContextualFragment) {
fragment = range.createContextualFragment(html);
} else {
// In IE 9 we need to use innerHTML of a temporary element
var div = document.createElement("div"), child;
div.innerHTML = html;
fragment = document.createDocumentFragment();
while ( (child = div.firstChild) ) {
fragment.appendChild(child);
}
}
var firstInsertedNode = fragment.firstChild;
var lastInsertedNode = fragment.lastChild;
range.insertNode(fragment);
if (selectInserted) {
if (firstInsertedNode) {
range.setStartBefore(firstInsertedNode);
range.setEndAfter(lastInsertedNode);
}
sel.removeAllRanges();
sel.addRange(range);
}
}
} else if (document.selection && document.selection.type != "Control") {
// IE 8 and below
range = document.selection.createRange();
range.pasteHTML(html);
}
}
Example:
replaceSelection('<span><font color="red">hoho</font></span>', true);
You can use the Rangy library
http://code.google.com/p/rangy/
You can then do
var sel = rangy.getSelection();
var range = sel.getRangeAt(0);
range.deleteContents();
var node = range.createContextualFragment('<span><font color="red">hoho</font></span>');
range.insertNode(node);
How can I (using jquery or other) insert html at the cursor/caret position of my contenteditable div:
<div contenteditable="true">Hello world</div>
For example, if the cursor/caret was between "hello" and "world" and the user then clicked a button, eg "insert image", then using javascript, something like <img src=etc etc> would be inserted between "hello" and "world". I hope I've made this clear =S
Example code would be greatly appreciated, thanks a lot!
The following function will insert a DOM node (element or text node) at the cursor position in all the mainstream desktop browsers:
function insertNodeAtCursor(node) {
var sel, range, html;
if (window.getSelection) {
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
sel.getRangeAt(0).insertNode(node);
}
} else if (document.selection && document.selection.createRange) {
range = document.selection.createRange();
html = (node.nodeType == 3) ? node.data : node.outerHTML;
range.pasteHTML(html);
}
}
If you would rather insert an HTML string:
function insertHtmlAtCursor(html) {
var sel, range, node;
if (window.getSelection) {
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = window.getSelection().getRangeAt(0);
node = range.createContextualFragment(html);
range.insertNode(node);
}
} else if (document.selection && document.selection.createRange) {
document.selection.createRange().pasteHTML(html);
}
}
I've adapted this from my answer to a similar question: How to find cursor position in a contenteditable DIV?
With contenteditable you should use execCommand.
Try document.execCommand('insertImage', false, 'image.jpg') or document.execCommand('insertHTML', false, '<img src="image.jpg" alt="" />'). The second doesn't work in older IE.
in this code i have just replace html code with (") to (')
use this syntax:
$("div.second").html("your html code and replace with (")to(') ");
I would recommend the use of the jquery plugin a-tools
This plugin has seven functions:
* getSelection – return start, end position, length of the selected text and the selected text. return start=end=caret position if text is not selected;
* replaceSelection – replace selected text with a given string;
* setSelection – select text in a given range (startPosition and endPosition);
* countCharacters – count amount of all characters;
* insertAtCaretPos – insert text at current caret position;
* setCaretPos – set cursor at caret position (1 = beginning, -1 = end);
* setMaxLength – set maximum length of input field. Also provides callback function if limit is reached. Note: The function has to have a number as input. Positive value for setting of limit and negative number for removing of limit.
The one that you need is insertAtCaretPos:
$("#textarea").insertAtCaretPos("<img src=etc etc>");
There might be a draw-back: this plugins only works with textarea en input:text elements, so there may be conflicts with contenteditable.