How blur (focus lost) the image after insertion with execCommand in IE10? - javascript

I have a content editable DIV such as:
<div contenteditable='true' id='myRichTextBox'></div>
In addition, I have a button that inserts an image into the mentioned DIV. Once the image inserted it has focused with resizable handlers.
How I can lose it's focus and bring back the focus to the content editable DIV ?
<button type='button' onclick='insertImage()'>Insert an image</button>
Javascript code:
function insertImage()
{
document.execCommand('insertImage',false,'myImage.png');
}
Thanks for any help

You can work round this in several ways, but a simple one would be to use document.execCommand("InsertHTML") in most browsers, falling back to pasteHTML() in IE. However, this will not work in IE 11 because it does not support document.selection or the InsertHTML command.
function insertImageWithInsertHtml(imgSrc) {
var imgHtml = "<img src='" + imgSrc + "'>";
var sel;
if (document.queryCommandSupported("InsertHTML")) {
document.execCommand("InsertHTML", false, imgHtml);
} else if ( (sel = document.selection) && sel.type != "Control") {
var range = sel.createRange();
range.pasteHTML(imgHtml);
range.collapse(false);
range.select();
}
}
Another way which would work in all browsers except IE <= 8 (for which you can use the same fallback as above) would be to insert the image manually using the insertNode() method of a Range obtained from the selection. This is the most future-proof and standards-compliant method so is what I'd recommend:
function insertImageWithInsertNode(imgSrc) {
var sel;
if (window.getSelection) {
var sel = window.getSelection();
if (sel.rangeCount > 0) {
var range = sel.getRangeAt(0).cloneRange();
range.deleteContents();
var img = document.createElement("img");
img.src = imgSrc;
range.insertNode(img);
// Place the caret immediately after the image
range.setStartAfter(img);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
} else if ( (sel = document.selection) && sel.type != "Control") {
var range = sel.createRange();
range.pasteHTML("<img src='" + imgSrc + "'>");
range.collapse(false);
range.select();
}
}
Finally, here's a live demo showing all three techniques:
http://jsfiddle.net/timdown/9ScLA/3/

I used the following solution and worked!
function insertImage(imgSrc)
{
var ua = navigator.userAgent.toLowerCase();
var tr;
if (ua.indexOf("msie") > 0)
tr = document.selection.createRange();
document.execCommand("insertImage", false, imgSrc);
if(ua.indexOf("firefox") > 0)
document.execCommand("enableObjectResizing", false, false);
if (ua.indexOf("msie") > 0)
{
tr.collapse(false);
tr.select();
}
this.textbox.focus();
}

Related

Insert text at current cursor position on dropdown list changed inside iframe

I am using a text editor provided by Microsoft ajax-toolkit.
It renders iframe on browser. I have added a dropdown in that editor and I want that when user changes the drop-down index the value should be added in the editor current cursor position.
I got a code on SO which gives me the current selected text inside editor is as follows
function getIframeSelectionText(iframe) {
var win = iframe.contentWindow;
var doc = iframe.contentDocument || win.document;
if (win.getSelection) {
return win.getSelection().toString();
} else if (doc.selection && doc.selection.createRange) {
return doc.selection.createRange().text;
}
}
But I want to add some text at the current position. The html is rendering as below
<td class="ajax__htmleditor_editor_editpanel"><div id="Editor1_ctl02" style="height:100%;width:100%;">
<iframe id="Editor1_ctl02_ctl00" name="Editor1_ctl02_ctl00" marginheight="0" marginwidth="0" frameborder="0" style="height:100%;width:100%;display:none;border-width:0px;">
</iframe><textarea id="Editor1_ctl02_ctl01" class="ajax__htmleditor_htmlpanel_default" style="height:100%;width:100%;display:none;"></textarea><iframe id="Editor1_ctl02_ctl02" name="Editor1_ctl02_ctl02" marginheight="0" marginwidth="0" frameborder="0" style="height:100%;width:100%;display:none;border-width:0px;">
</iframe>
</div></td>
I am trying as follow
$("#imgDropdown").change(function () {
//var iframeBody = $(window.Editor1_ctl02_ctl00.document.getElementsByTagName("body")[0]);
var iframe = document.getElementById("Editor1_ctl02_ctl00");
$("#Editor1_ctl02_ctl00").find("body").insertAtCaret("value");
//alert(getIframeSelectionText(iframe));
});
the function for inserting text is not working with iframe is as follow
$.fn.extend({
insertAtCaret: function (myValue) {
if (document.selection) {
this.focus();
sel = document.selection.createRange();
sel.text = myValue;
this.focus();
}
else if (this.selectionStart || this.selectionStart == '0') {
var startPos = this.selectionStart;
var endPos = this.selectionEnd;
var scrollTop = this.scrollTop;
this.value = this.value.substring(0, startPos) + myValue + this.value.substring(endPos, this.value.length);
this.focus();
this.selectionStart = startPos + myValue.length;
this.selectionEnd = startPos + myValue.length;
this.scrollTop = scrollTop;
} else {
this.value += myValue;
this.focus();
}
}
})
Easy, you just have to use.
$("#Editor1_ctl02_ctl00").contents().find('textarea').insertAtCaret('value');
Updated
Sorry, I thought the insertAtCaret function is working for you, you just needed to work inside iFrame. You can use this version of insertAtCaret:
jQuery.fn.extend({
insertAtCaret: function (html) {
var winObject = function (el){
var doc = el.ownerDocument;
return doc.defaultView || doc.parentWindow
};
return this.each(function (i) {
var sel, range, w = this;
w = winObject(w);
if (w.getSelection) {
// IE9 and non-IE
sel = w.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
range.deleteContents();
// Range.createContextualFragment() would be useful here but is
// only relatively recently standardized and is not supported in
// some browsers (IE9, for one)
var el = w.document.createElement("div");
el.innerHTML = html;
var frag = w.document.createDocumentFragment(), node, lastNode;
while ((node = el.firstChild)) {
lastNode = frag.appendChild(node);
}
range.insertNode(frag);
// Preserve the selection
if (lastNode) {
range = range.cloneRange();
range.setStartAfter(lastNode);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
}
} else if (w.document.selection && w.document.selection.type != "Control") {
// IE < 9
w.document.selection.createRange().pasteHTML(html);
}
}
)
}
});
and call it like:
$("#Editor1_ctl02_ctl00").contents().find('body').insertAtCaret($val);
Function adapted from here
Happy coding!
There seem to be a few issues here.
The Microsoft ajax-toolkit editor creates an iframe where the designMode property is turned on, and that's why it's editable, it has no value, and textNodes are added straight to the body, which makes it a little more difficult.
When you're selecting something from a dropdown, the focus is on the dropdown, and there is no caret position, as the focus is shifted away from the iFrame.
I'm assuming that the dropdown is in the top menubar for the editor or anywhere else that is outside the iFrame.
Also, the Microsoft ajax-toolkit editor has a recommended update, the HTMLEditorExtender.
The code you have to capture the caret position seems to be for a regular input / textarea, and you'd have to adapt that code to work with any Node inside an iframe that is in designMode, with it's own window and document etc.
Given the above considerations, this is what I came up with to do this
var frameID = 'Editor1_ctl02_ctl00',
selectID = 'imgDropdown',
iframe = document.getElementById(frameID),
iWin = iframe.contentWindow ? iframe.contentWindow : window.frames[frameID];
$(iWin).on('blur', function() {
$(iframe).data('range', getRange(iWin));
});
$('#' + selectID).on('change', function() {
var range = $(iframe).data('range');
addText(iWin, range, this.value);
});
function getRange(win) {
var sel, range, html;
if (win.getSelection) {
sel = win.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
range.deleteContents();
}
} else if (win.document.selection && win.document.selection.createRange) {
range = win.document.selection.createRange();
}
return range;
}
function addText(win, range, text) {
if (win.getSelection) {
range.insertNode(win.document.createTextNode(text));
} else if (win.document.selection && win.document.selection.createRange) {
range.text = text;
}
}
FIDDLE

Set textarea's caret position using x and y position

I want to implement inline edition, so if user clicks on a div with some text, the textarea will appear at the same position as clicked div's position and will get the text from the div. This is fine for me, but what I want to do next is to set textarea's caret position according to x and y position from div click event. Any ideas?
HTML:
<div id="content" style="display: block; z-index: 10">Some text</div>
<textarea id="editor" style="position: absolute; display: none; z-index: 11"></textarea>
JS:
$('#content').click(function(e) {
var content = $(this);
var editor = $('#editor');
var position = content.offset();
editor.css('left', position.left);
editor.css('top', position.top);
editor.val(content.text());
editor.show();
var mousePosition = { x: e.offsetX, y: e.offsetY };
// here I want to set the #editor caret position at the same place,
// where user clicks the #content (mousePosition variable)
});
Looks like you could do something like this:
createTextArea = function (e) {
var range = window.getSelection().getRangeAt(0),
start = range.startOffset,
target = e.target,
setPoint;
while (target.tagName.toLowerCase() !== 'div') {
target = target.parentElement;
if (!target) return;
}
range.setStart(target, 0);
setPoint = range.toString().length;
// place and show #editor
editor.focus();
editor.setSelectionRange(setPoint, setPoint);
return;
};
An example at jsFiddle. Notice that this works only in modern browsers. Older IE's don't have Input API, and the Selection/Range model is totally different.
I've found a solution:
JS:
function getCaretPosition(editableDiv) {
var caretPos = 0, containerEl = null, sel, range;
if (window.getSelection) {
sel = window.getSelection();
if (sel.rangeCount) {
range = sel.getRangeAt(0);
if (range.commonAncestorContainer.parentNode == editableDiv) {
caretPos = range.endOffset;
}
}
} else if (document.selection && document.selection.createRange) {
range = document.selection.createRange();
if (range.parentElement() == editableDiv) {
var tempEl = document.createElement("span");
editableDiv.insertBefore(tempEl, editableDiv.firstChild);
var tempRange = range.duplicate();
tempRange.moveToElementText(tempEl);
tempRange.setEndPoint("EndToEnd", range);
caretPos = tempRange.text.length;
}
}
return caretPos;
}
$.fn.selectRange = function (start, end) {
if (!end) end = start;
return this.each(function () {
if (this.setSelectionRange) {
this.focus();
this.setSelectionRange(start, end);
} else if (this.createTextRange) {
var range = this.createTextRange();
range.collapse(true);
range.moveEnd('character', end);
range.moveStart('character', start);
range.select();
}
});
};
Usage:
$('#content').click(function (e) {
var content = $(this);
var editor = $('#editor');
var caret = getCaretPosition(this);
var position = content.offset();
editor.css('left', position.left);
editor.css('top', position.top);
editor.val(content.text());
editor.show();
var mousePosition = { x: e.offsetX, y: e.offsetY };
// here I want to set the #editor caret position at the same place,
// where user clicks the #content (mousePosition variable)
editor.selectRange(caret, caret);
});

How to insert text at the current caret position in a textarea

On a function call from an image, I am trying to insert the alt tag value from the image into the textarea at the position where the caret currently is.
This is the code that I currently have which inserts the alt tag value to the end of the text area.
$("#emoticons").children().children().click(function () {
var ch = $(this).attr("alt");
$("#txtPost").append(ch);
});
The 2 things I have been having a problem with is determining the position of the caret, and creating a new string with the value of the textarea before the carets positon + the code I'm inserting + the value of the textarea after the carets position.
i've currently got this extension in place:
$.fn.insertAtCaret = function(text) {
return this.each(function() {
if (document.selection && this.tagName == 'TEXTAREA') {
//IE textarea support
this.focus();
sel = document.selection.createRange();
sel.text = text;
this.focus();
} else if (this.selectionStart || this.selectionStart == '0') {
//MOZILLA/NETSCAPE support
startPos = this.selectionStart;
endPos = this.selectionEnd;
scrollTop = this.scrollTop;
this.value = this.value.substring(0, startPos) + text + this.value.substring(endPos, this.value.length);
this.focus();
this.selectionStart = startPos + text.length;
this.selectionEnd = startPos + text.length;
this.scrollTop = scrollTop;
} else {
// IE input[type=text] and other browsers
this.value += text;
this.focus();
this.value = this.value; // forces cursor to end
}
});
};
and you can use it like so:
$("#txtPost").insertAtCaret(ch);

How to detect whether user selected entire document or not in HTML and JavaScript?

I'm developing a off-line web-page for iPhone Safari.
For highlighting the user's selection in web-page. I've implemented following code.
function highlight() {
if (window.getSelection) {
sel = window.getSelection();
if (sel.getRangeAt) {
range = sel.getRangeAt(0);
}
document.designMode = "on";
if (range) {
sel.removeAllRanges();
sel.addRange(range);
}
document.execCommand("HiliteColor", false, "yellow");
document.designMode = "off";
}
}
That's working perfectly for normal selection.
Edit :
How to detect has user selected entire document or not?
What to do?
The following is a function that will return a Boolean indicating whether all of the contents of the specified element are selected and works in all major browsers, including IE. You can use this with document.body to test whether the whole page is selected.
function areElementContentsSelected(el) {
var range, elRange;
if (window.getSelection && document.createRange) {
var sel = window.getSelection();
if (sel.rangeCount) {
elRange = document.createRange();
elRange.selectNodeContents(el);
for (var i = 0, len = sel.rangeCount; i < len; ++i) {
range = sel.getRangeAt(i);
if (range.compareBoundaryPoints(range.START_TO_START, elRange) <= 0 && range.compareBoundaryPoints(range.END_TO_END, elRange) >= 0) {
return true;
}
}
}
} else if (document.selection && document.selection.createRange && document.selection.type == "Text") {
range = document.selection.createRange();
elRange = range.duplicate();
elRange.moveToElementText(el);
return range.compareEndPoints("StartToStart", elRange) <= 0 && range.compareEndPoints("EndToEnd", elRange) >= 0;
}
return false;
}
alert( areElementContentsSelected(document.body) );
But I don't want to allow user to select entire document & highlight it.
Why not?
Interfering with users' typically-expected-browsing-experience is never a good thing and usually leads to frustrated users simply disabling JS or choosing to abandon your site altogether.
Please reconsider.
Could you use selectNodeContents(document body) (pseudocode) to create a range representing the whole page, and then compare that to the range that the user selected?

Mapping the event position to the text position in non text-fields

Is there a way to map an event such as a click-event on this element
<div>Just normal text</div>
to the position in the contained text ( "You just clicked the 6th character", when hitting the 'n' )?
I don't know any pretty way of achieving that but I got a solution that would work, although it's ugly.
You could wrap each letter of your div text in a lets say span element and add unique identifiers for each letter. Then you'd hook up event handlers for those span elements, not the whole div and based on the span id you could tell which character was that.
This whole thing can be done in JS but as I said that's not the ideal solution for sure.
Here's the example (I've added a test id to the div so I could find it easier).
var letters = $('#test').text();
var spans = '';
for (var i = 0; i < letters.length; i++) {
spans += '<span id="id' + i + '">' + letters[i] + '<span>';
}
$('#test').html(spans);
$('span[id^=id]').click(function() {
alert('Clicked char: ' + (Number($(this).attr('id').substring(2)) + 1));
return false;
});
You can also give it a try on my demo.
Not as such; I guess you would have to split each character into a <span> element, either on server side or using JQuery.
Here's a hacky way that could possibly be made to work: it involves temporarily making the document editable and examining the selection. It should work in Firefox 3+, IE 6+, recent Safari and Chrome.
As it stands, there are some problems I can see:
The results in IE are different to other browsers: IE counts all characters in the whole containing element up until the caret while other browsers give an offset within the containing text node, but you could work round this;
A border appears round the current element in some browsers
Doesn't work in Opera or Firefox 2, and leaves document editable
Possibility of other UI glitches: it's a nasty hack.
Code:
window.onload = function() {
var mouseDownEl;
document.onmousedown = function(evt) {
evt = evt || window.event;
var el = evt.target || evt.srcElement;
if (evt.srcElement || !("contentEditable" in el)) {
document.designMode = "on";
} else {
el.contentEditable = "true";
}
mouseDownEl = el;
};
document.onclick = function(evt) {
evt = evt || window.event;
var el = evt.target || evt.srcElement;
if (el == mouseDownEl) {
window.setTimeout(function() {
var caretPos, range;
if (typeof window.getSelection != "undefined") {
caretPos = window.getSelection().focusOffset;
} else if (document.selection && document.selection.createRange) {
range = document.body.createTextRange();
range.moveToElementText(el);
range.collapse();
range.setEndPoint("EndToEnd", document.selection.createRange());
caretPos = range.text.length;
}
if (el.contentEditable == "true") {
el.contentEditable = "false";
} else {
document.designMode = "off";
}
alert(caretPos);
}, 1);
} else {
if (mouseDownEl.contentEditable == "true") {
mouseDownEl.contentEditable = "false";
} else {
document.designMode = "off";
}
}
mouseDownEl = null;
};
};

Categories

Resources