i am trying to wrap the selected string between two characters
for eg: selecting 'test' and clicking on change button will change the selected text to 'atestb'
the problem is that, i am able to replace the selected text, but window.getSelection().toString() is coming empty.
This is the function that im using
replaceSelectedText(startTag, endTag) {
let sel, range;
console.log(window.getSelection().toString())
if (window.getSelection) {
sel = window.getSelection();
if (sel.rangeCount) {
range = sel.getRangeAt(0);
range.deleteContents();
const selectedContent = sel.toString();
console.log("Selected Content ")
console.log(selectedContent)
let replaceDiv = startTag + selectedContent;
replaceDiv=endTag ? replaceDiv + endTag : replaceDiv;
range.insertNode(document.createTextNode(replaceDiv));
}
} else if ((document as any).selection && (document as any).selection.createRange) {
range = (document as any).selection.createRange();
range.text = startTag;
}
}
Link to Stackblitz
https://stackblitz.com/edit/angular-idyhj5?file=src%2Fapp%2Fapp.component.ts
You are deleting the value before retrieving it.
range.deleteContents();
const selectedContent = sel.toString();
If you flip those two lines and store the contents before deleting, it will work as you expect.
Related
I have a problem with my custom WYSIWYG editor.
This is how it works:
Select a text and click on a button. The text will be formatted. Unselect the same text and select it again. Now click again on the same button to remove the format.
This is how it does NOT work:
Select a text and click on a button. The text will be formatted. Now click again on the same button to remove the format.
I assume, that it probably doesn't work, because I am inserting an element inside the parent element. So at this moment, this element is not selected. With selectedText?.selectNode(node) I have tried to select the correct node but this doesn't change anything.
So how can I remove the format, when the text stays selected?
document.getElementById('bold').addEventListener('click', () => edit('STRONG'));
document.getElementById('italic').addEventListener('click', () => edit('EM'));
function edit(format) {
const parentElementOfSelectedText = document.getSelection().getRangeAt(0).commonAncestorContainer.parentElement;
// If element is already formatted, undo the format
if (parentElementOfSelectedText.tagName === format) {
let grandParentOfSelectedText = parentElementOfSelectedText.parentElement;
if (parentElementOfSelectedText.textContent) {
const selectedText = document.createTextNode(parentElementOfSelectedText.textContent);
grandParentOfSelectedText.insertBefore(selectedText, parentElementOfSelectedText);
grandParentOfSelectedText.removeChild(parentElementOfSelectedText);
grandParentOfSelectedText.normalize();
}
} else {
const selectedText = document.getSelection().getRangeAt(0);
const node = document.createElement(format);
const fragment = selectedText.extractContents();
if (fragment) {
node.appendChild(fragment);
}
selectedText.insertNode(node);
}
}
<button id="bold">B</button>
<button id="italic">I</button>
<p>Lorem ipsum</p>
I assume, that it probably doesn't work, because I am inserting an element inside the parent element. So at this moment, this element is not selected.
correct. but you can just re-select it in js too.
document.getElementById('bold').addEventListener('click', () => edit('STRONG'));
document.getElementById('italic').addEventListener('click', () => edit('EM'));
function edit(format) {
let parentElementOfSelectedText = document.getSelection().getRangeAt(0).commonAncestorContainer;
// If element is already formatted, undo the format
if (parentElementOfSelectedText.tagName === format || parentElementOfSelectedText.parentElement.tagName === format) {
if(parentElementOfSelectedText.tagName !== format) parentElementOfSelectedText = parentElementOfSelectedText.parentElement;
let grandParentOfSelectedText = parentElementOfSelectedText.parentElement;
if (parentElementOfSelectedText.textContent) {
const selectedText = document.createTextNode(parentElementOfSelectedText.textContent);
//work with range of old element because
//text nodes are pass by value
//and we cant create a range after its a text node
const range = document.createRange();
range.selectNode(parentElementOfSelectedText);
//this replaces some of your code but uses a range
range.deleteContents();
range.insertNode(selectedText);
grandParentOfSelectedText.normalize();
//select the range again :)
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
}
} else {
let selectedText = document.getSelection().getRangeAt(0);
const node = document.createElement(format);
const fragment = selectedText.extractContents();
if (fragment) {
node.appendChild(fragment);
}
selectedText.insertNode(node);
//make only the inside of the node a range
//so [...].commonAncestorContainer is "STRONG" or "EM"
//and gets recognized
const range = document.createRange();
range.selectNodeContents(node);
const selection = window.getSelection();
selection.removeAllRanges()
selection.addRange(range);
}
}
<body>
<button id="bold">B</button>
<button id="italic">I</button>
<p>Lorem ipsum</p>
</body>
edits: adding comments to code; debugging
This is my editor content:
<h1>Heading 1<h1>
<p>Paragraph</p>
<h2>Heading 2</h2>
Now if i select text in the editor, is there a chance to get a list of all the elements involved in this selection? For example if i select a portion of Heading 1 and Paragraph i would like to get an array (h1, p) or at least an object where i can see which elements are in the selection.
Ive already tried most of the functions described here http://docs.ckeditor.com/#!/api/CKEDITOR.dom.selection but most of the time i only get the first element of the selection.
i have adapted a function i had for the same
$("textarea").select(function() {
var textComponent = $(this)[0]; //element identifier
var selectedText;
// IE version
if (document.selection !== undefined)
{
textComponent.focus();
var sel = document.selection.createRange();
selectedText = sel.text;
}
// Mozilla version
else if (textComponent.selectionStart !== undefined)
{
var startPos = textComponent.selectionStart;
var endPos = textComponent.selectionEnd;
selectedText = textComponent.value.substring(startPos, endPos);
}
$("p").html("You selected: " + selectedText);
});
check here: https://jsfiddle.net/ees8bupq/1/
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.
I'm looking for function which allows me to build some element before or after selected text. Something similar like this one javascript replace selection all browsers but for adding some content before or after selection instead of replacing it, like after() and before() jQuery methods. Should I use some DOM selection method, if yes which one? Or does exist something easier to carry it out?
Here's a pair of functions to do this.
Live example: http://jsfiddle.net/hjfVw/
Code:
var insertHtmlBeforeSelection, insertHtmlAfterSelection;
(function() {
function createInserter(isBefore) {
return function(html) {
var sel, range, node;
if (window.getSelection) {
// IE9 and non-IE
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = window.getSelection().getRangeAt(0);
range.collapse(isBefore);
// Range.createContextualFragment() would be useful here but is
// non-standard and not supported in all browsers (IE9, for one)
var el = document.createElement("div");
el.innerHTML = html;
var frag = document.createDocumentFragment(), node, lastNode;
while ( (node = el.firstChild) ) {
lastNode = frag.appendChild(node);
}
range.insertNode(frag);
}
} else if (document.selection && document.selection.createRange) {
// IE < 9
range = document.selection.createRange();
range.collapse(isBefore);
range.pasteHTML(html);
}
}
}
insertHtmlBeforeSelection = createInserter(true);
insertHtmlAfterSelection = createInserter(false);
})();
In MSIE:
collapse the given range and the use pasteHTML to insert the element
Others:
Also collapse the given Range and insert the element via insertNode
Both collapse-methods accept an optional argument which defines to where you want to collapse to.
If you want to put the element at the end, collapse to the end, otherwise to the start.
function yourFunction() {
const sel = window.getSelection ? window.getSelection() : document.selection.createRange()
if (!sel) return false
if (sel.getRangeAt) {
const range = sel.getRangeAt(0)
const text = range.toString()
console.log(text)
range.deleteContents()
range.insertNode(document.createTextNode(`before text${text}after`))
} else {
sel.pasteHTML(`[s=спойлер]${sel.htmlText}after`)
}
}
I've created a simple tool so employees can personalize their company email signature. This tool creates some styled text with some bolded fonts and a bit of color, nothing fancy. If I then select the text and copy and paste it into my Gmail signature field all works well. It retains the formatting. However, I'd prefer to give the user the option of clicking a "Copy" button that copies the formatted content onto their clipboard.
I'm currently using ZeroClipboard to add the "copy to clipboard" functionality and it's working great. Thing is I don't know how to grab that formatted text. It just keeps copying the unformatted version. One thing I tried was adding a mouseDown listener to ZeroClipboard that selects the text like this:
function selectText() {
if (document.selection) {
var range = document.body.createTextRange();
range.moveToElementText(document.getElementById('clicktocopy'));
range.select();
}
else if (window.getSelection) {
var range = document.createRange();
range.selectNode(document.getElementById('clicktocopy'));
window.getSelection().addRange(range);
}
}
Then returns the selection like this:
function getText() {
if (window.getSelection) {
var range = window.getSelection();
return range.toString();
}
else {
if (document.selection.createRange) {
var range = document.selection.createRange();
return range.text;
}
}
}
However, this is only copying the unformatted text. Is it possible to copy the text formatted?
My formatted text is in a div with the id "results".
If you want an HTML string representing the current selection, the following function will do this (replacing your getText() function):
function getSelectionHtml() {
var html = "";
if (typeof window.getSelection != "undefined") {
var sel = window.getSelection();
if (sel.rangeCount) {
var container = document.createElement("div");
for (var i = 0, len = sel.rangeCount; i < len; ++i) {
container.appendChild(sel.getRangeAt(i).cloneContents());
}
html = container.innerHTML;
}
} else if (typeof document.selection != "undefined") {
if (document.selection.type == "Text") {
html = document.selection.createRange().htmlText;
}
}
return html;
}