I need to highlight a selected text with JavaScript (no jQuery) and having control points or markers (left and right), I don't really know how to call them, similarly like on mobile phones so I can extend the selection anytime by dragging any of the control points.
Example: http://screencast.com/t/KJBdvreeVW
I've grabbed the selected text, demo: http://jsfiddle.net/henrichro/HJ482/
function getSelectionText() {
var text = "";
if (window.getSelection) {
text = window.getSelection().toString();
} else if (document.selection && document.selection.type != "Control") {
text = document.selection.createRange().text;
}
alert(text);
}
if (window.Event) document.captureEvents(Event.MOUSEUP);
document.onmouseup = getSelectionText;
Now I have this working code to get the text, but I would like to have markers around it, as written above :)
Update 10/28/2013:
After Dementic's directions (his answer below), I figured out the next working code: http://jsfiddle.net/henrichro/WFLU9/
The only problem persists when I select more than one line. On that scenario the markers are showing wrong.
found a solution here : How do I wrap a text selection from window.getSelection().getRangeAt(0) with an html tag?
which uses just a bit of Jquery, modified it so its pure js (just removed the selectors and the onclick), you can find a working example here:
http://jsfiddle.net/3tvSL/83/
this marks the selected text with yellow background, and will need a bit more work to have "Markers" on the sides.
as for an idea, you can use an absolute positioned div as the marker,
and while it is dragged, you can use something like:
function expand(range) {
if (range.collapsed) {
return;
}
while (range.toString()[0].match(/\w/)) {
range.setStart(range.startContainer, range.startOffset - 1);
}
while (range.toString()[range.toString().length - 1].match(/\w/)) {
range.setEnd(range.endContainer, range.endOffset + 1);
}
}
Taken from: Select whole word with getSelection
one more thing,
i have found a working js library to do exactly what you wish...
http://www.mediawiki.org/wiki/Extension:MashaJS
take a look :)
Related
I'd like to replicate the behavior of dragging selected text, to move it in/into a text input or text area, within a webpage.
I'd like to know if it's possible (using a mouse drag of a selection) to:
copy/move selected text from one text input to another
copy/move selected text within an input to another position
copy/move selected text from a header/paragraph (i.e. non-input) to an input/textarea.
populate the dragged text programmatically using javascript
(Bonus: copy/move selected text to external program)
e.g. The green arrow demonstrates the behavior working, however, I'd like to make it work within a webpage (red arrow).
All google suggestions I've found so far, relate to dragging and dropping HTML elements, not text.
I'm using chrome, but bonus points if it's a cross-browser solution. Don't mind if the solution is HTML, CSS or javascript.
Similar question, but for visual studio code - not a webpage.
** UPDATE **
I feel stupid (but I'll blame usability). If I select text, then ensure the mouse isn't moving and click, THEN drag the mouse, points 1-3 work as expected and drag the selected text. (my problem I suspect is that I was impatient, and subtlety moving the mouse before clicking to drag.)
#cjl750's answer still stands. I've changed the question to focus on point 4.
So, in my tests in Chrome, anything involving dropping into a text input is handled natively by the browser (and I would expect the same for other browsers). I am guessing whatever mouse events SO has connected to their search bar is interfering with drag and drop from there into another input, but any normal input I tested works fine.
That leaves you only needing to handle dragging into non-inputs.
There's two parts of that. First, you can get the current highlighted text with window.getSelection(). The tricky part is that you need to figure out what is under your cursor when you are done dragging that text somewhere. The solution on how to do that was inspired from Solution #2 on this answer.
We want to make use of document.caretPositionFromPoint(), but that's the new name for the method that is so far only supported in Firefox. So using the simple if/else statement from MDN we can cover everything but IE, which is what I'm doing in the snippet below. A more complete solution that includes IE support is found in the other link.
Basic strategy:
on mousedown, check for highlighted text
if we have some highlighted text, listen for the dragend event
assuming we stopped dragging on top of a text node, check cursor position within that node with document.caretPositionFromPoint() or document.caretRangeFromPoint()
using the caret position from #3 as well as the position of our highlighted text within its larger string, slice up our strings and output the result
(() => {
document.addEventListener('mouseup', checkForSelection);
let textBeingDragged;
let originalNode
function checkForSelection(event) {
const selection = window.getSelection();
const selectedText = selection.toString();
if (selectedText) {
originalNode = selection.anchorNode.parentNode;
textBeingDragged = selectedText;
document.addEventListener('dragend', handleDragEnd);
}
}
function handleDragEnd(event) {
const charRange = getCharPosition(event);
const elemDrugOver = charRange.endContainer;
if (elemDrugOver.nodeType === 3) {
const offset = charRange.startOffset;
const startText = elemDrugOver.wholeText.slice(0, offset);
const endText = elemDrugOver.wholeText.slice(offset);
elemDrugOver.textContent = `${startText}${textBeingDragged}${endText}`;
const origText = originalNode.textContent;
const indexOfSelection = origText.indexOf(textBeingDragged);
const origStartText = origText.slice(0, indexOfSelection);
const origEndText = origText.slice(offset + textBeingDragged.length);
originalNode.textContent = `${origStartText}${origEndText}`;
textBeingDragged = undefined;
originalNode = undefined;
}
document.removeEventListener('dragend', handleDragEnd);
}
function getCharPosition(event) {
if (document.caretPositionFromPoint) {
return document.caretPositionFromPoint(event.clientX, event.clientY);
} else if (document.caretRangeFromPoint) {
return document.caretRangeFromPoint(event.clientX, event.clientY);
}
return false;
}
})();
<h1>This is a (try to move me) header</h1>
<h2>This is another header</h2>
I'll leave it to you to add any enhancements you want, but this should get you 95% of the way there at least.
I am using a mouseup event to trigger a function which highlights text and surrounds the highlighted text with a span (function from stack overflow):
function highlightText(e) {
var t = window.getSelection().toString();
if (t) {
$("#mySpan").remove();
var range = window.getSelection().getRangeAt(0);
newNode = document.createElement("span");
newNode.id = 'mySpan';
range.surroundContents(newNode);
}
}
The main problem I am encountering is that as long as surroundContents is included, the text remains highlighted only about 20% of the highlight attempts (otherwise highlighting disappears immediately). I tried adding a setTimeout, not calling surroundContent for 1s. I also tried removing the remove() statement, but still no good.
Any ideas on why this is happening?
I was facing the same problem with Chromium on Android. In some specific cases, the call of range.surroundContents(newNode) would cause a very weird behaviour of page reload and so on. After checking the documentation of the function:
This method is nearly equivalent to
newNode.appendChild(range.extractContents());
range.insertNode(newNode). After surrounding, the boundary points of
the range include newNode.
So the obvious thing was to apply another way highlight the text. I found mark.js library which did exactly what I wanted without that annoying side effect. (Here's a JSFiddle sample that shows how it's used to highlight just selection). The difference is that library was not using range.surroundContents(newNode) nor newNode.appendChild but rather node.replaceChild.
Based on that, here's the solution to the problem I was having and I think it applies to your case as well.
function surroundRangeWithSpan(range) {
var span = document.createElement('span');
// The text is within the same node (no other html elements inside of it)
if (range.startContainer.isEqualNode(range.endContainer) && range.startContainer.childNodes.length == 0) {
// Here you customise your <span> element
customSurroundContents(range, span);
} else {
// Here you have to break the selection down
}
return span;
}
function customSurroundContents(range, span) {
var node = range.commonAncestorContainer;
var startNode = node.splitText(range.startOffset);
var ret = startNode.splitText(range.toString().length);
span.textContent = startNode.textContent;
startNode.parentNode.replaceChild(span, startNode);
}
And you pass window.getSelection().getRangeAt(0) to the function.
The likely cause of the failure is the selected text encompasses only the beginning or the ending of a non-text node, and not both of them.
So if were to run that code only selecting "This is Bo" in the following it will fail (and throw an exception) because it doesn't also capture the closing tag in the selection:
This is <em>bold</em>
So ending up with:
This is <em>bo
Reference: https://developer.mozilla.org/en-US/docs/Web/API/Range/surroundContents
I created a contenteditable div and added features for formatting.
One of the feature I added is creating a list from paragraph, I'll reproduce the steps below.
====
1) This is the paragraph I wanted part of it to be a list.
2) So I marked the text to make it a list
, I use plugin from https://github.com/kenshin54/popline/ (modified it myself).
And it uses
document.execCommand("InsertOrderedList", false);
for creating list
3) And then after I clicking the button, it made the whole paragraph to list. I just want the marked text to be (just for "Sed imperdiet....orci,")
The reason that I posting on Stackoverflow instead of Github repository issues (https://github.com/kenshin54/popline/) because I'm not sure that is an issue or not, I just want it to work like that.
Anyone has some suggestions?
Bun, your answer doesn't work for me, but this does:
var sel = rangy.getSelection();
var range = sel.rangeCount ? sel.getRangeAt(0) : null;
if (range) {
var el = document.createElement("li");
if (range.canSurroundContents(el)) {
range.surroundContents(el);
var ul = document.createElement("ul");
range.surroundContents(ul);
} else {
alert("Unable to surround range because range partially selects a non-text node..");
}
}
(taken mostly from the rangy core demo)
After long time of finding workarounds, kenshin (https://github.com/kenshin54) just saved my life.
He gave me this one http://jsfiddle.net/j83ueh6g
You can use document.execCommand('insertOrderedList', false); as the same way it works.
Consider the following problem:
Have a textarea like so:
<textarea id="body" name="body"></textarea>
Also have some simple JavaScript (jQuery) that inserts some new text into the textarea so a user can embed an image:
$('textarea').val($('textarea').val() + '[img]path to image file[/img]');
The trick is to automatically highlight the text in between the [img][/img] tags after that text is inserted so the user can just copy and paste their image URL in quickly, instead of manually selecting, then copy and pasting.
I've racked my brain and gone all over the internet trying to figure this out, and the best I could do was this popular StackOverflow question Selecting text in an element (akin to highlighting with your mouse) which only addresses the problem of selecting the text inside an ENTIRE element, which is not what is desired here. The problem is to select text that matches a certain string, in this case path to image file, so the user can just copy/paste. (not sure if this is the best way to do it, but that's what I thought of...).
Is this possible? I'm guessing we're going to need getSelection() and createRange() but other than that I have no idea where to go... any JavaScript wizards figured this one out already? I feel like this could be a popular question. Using jQuery is fine, as I'm already using it on the rest of the document.
You could use my jQuery plug-in. It works around browser differences in textarea selection manipulation and has some convenience methods:
https://code.google.com/p/rangyinputs/
For your example, the code would be
var $textarea = $("#body");
var text = "path to image file"
$textarea.replaceSelectedText(text, "select");
$textarea.surroundSelectedText("[img]", "[/img]");
Demo: http://jsfiddle.net/P8Jrh/1/
I actually figured this one out myself... I used the Rangy library https://code.google.com/p/rangy/ and code like this:
// Add text to the reply area at the very end, and move the cursor to the very end.
function insertText(textarea, text) {
textarea = $(textarea);
textarea.focus();
textarea.val(textarea.val() + text);
textarea.focus();
// Trigger the textarea's keyup to emulate typing.
textarea.trigger("keyup");
}
// Add text to the reply area, with the options of wrapping it around a selection and selecting a part of it when it's inserted.
function wrapText(textarea, tagStart, tagEnd, selectArgument, defaultArgumentValue) {
textarea = $(textarea);
// Save the scroll position of the textarea.
var scrollTop = textarea.scrollTop();
// Work out what text is currently selected.
var selectionInfo = textarea.getSelection();
if (textarea.val().substring(selectionInfo.start, selectionInfo.start + 1).match(/ /)) selectionInfo.start++;
if (textarea.val().substring(selectionInfo.end - 1, selectionInfo.end).match(/ /)) selectionInfo.end--;
var selection = textarea.val().substring(selectionInfo.start, selectionInfo.end);
// Work out the text to insert over the selection.
selection = selection ? selection : (defaultArgumentValue ? defaultArgumentValue : "");
var text = tagStart + selection + (typeof tagEnd != "undefined" ? tagEnd : tagStart);
// Replace the textarea's value.
textarea.val(textarea.val().substr(0, selectionInfo.start) + text + textarea.val().substr(selectionInfo.end));
// Scroll back down and refocus on the textarea.
textarea.focus();
// If a selectArgument was passed, work out where it is and select it. Otherwise, select the text that was selected
// before this function was called.
if (selectArgument) {
var newStart = selectionInfo.start + tagStart.indexOf(selectArgument);
var newEnd = newStart + selectArgument.length;
} else {
var newStart = selectionInfo.start + tagStart.length;
var newEnd = newStart + selection.length;
}
textarea.selectRange(newStart, newEnd);
// Trigger the textarea's keyup to emulate typing.
textarea.trigger("keyup");
}
var bbcode = {
bold: function(id) {wrapText($("textarea"), "[b]", "[/b]", "", "bolded text");},
};
Example usage:
bbcode.bold();
Full code (in a larger project I did): https://github.com/wnajar/textarea
I need to get the position of the selected text within a content non-editable div (not a textarea, not a rtf editor, just a simple div)
I want to do this in order to enable users to select pieces of an article and "highlight it", by wrapping it in a span with a different background and, of course, an article is build with divs and/or p-s etc, not textareas or rtfs
Any ideas?
P.s. You can also use jQuery :D
P.s.s. I need the position of the selection, not the selection itself. Aka: it start from index I to index J. I need this because the normal method of finding the text in the parent does not always return a unique result, which would suck :)
If you just want to change the background of the selected text, the easiest way to do this is by using document.execCommand(). See my answer here: Change CSS of selected text using Javascript
//Wrap selected text in span tags with the class 'hl'
//Take some action after (in this case, a simple alert)
$("p").live("mouseup",
function() {
selection = getSelectedText();
if(selection.length >= 3) {
$(this).html($(this).html().replace(selection, $('<\/span>').attr({'class':'hl'}).html(selection).parent().html()) );
alert(selection);
}
}
);
//Grab selected text
function getSelectedText(){
if(window.getSelection){
return window.getSelection().toString();
}
else if(document.getSelection){
return document.getSelection();
}
else if(document.selection){
return document.selection.createRange().text;
}
}
Code comes from here: http://esbueno.noahstokes.com/post/92274686/highlight-selected-text-with-jquery
You can check if text is selected by running :
window.getSelection and document.getSelection() and document.selection
(because browsers can check this i different ways)
and then search for div containing this text .
For getting the position of the selection, try these links:
http://bytes.com/topic/javascript/answers/153164-return-selectionstart-div
Set cursor position on contentEditable <div>
Well, even though you found a solution to the problem stated in your 2nd paragraph, i don't think the answer to your main question has been given. :)
The object Selection has a property named anchorOffset, giving exactly what you asked for (the position of the selected text within an element). The above link will tell you about which browsers support it, i'm afraid IE <9 might not.
function show_selected()
{
var sel = selection();
console.log(sel.anchorOffset + ':' + sel);
}
Now if you bind show_selected to, say, mouseup, you will see the offset and the selected text printed on the js console.
The fonction selection may be the following, supposed to be cross-browser:
function selection()
{
var sel;
if(window.getSelection){
sel = window.getSelection()
}
else if(document.getSelection){
sel = document.getSelection()
}
else if(document.selection){
sel = document.selection.createRange()
}
return sel
}