I have an input that holds a date value like so
03/15/2012
I am trying to select only portions of the the value instead of the whole thing. For instance if I click the spot before 2 in 2012 the year 2012 will be selected not the whole date (same for is true for months and day).
This is the code I am working with now
html:
<input class = "date-container" />
javascript/jquery:
$('.date-container').on('select', function (e)
e.preventDefault()
this.onselectstart = function () { return false; };
})
$('.date-container').on('focus', function ()
{
if (document.selection) {
this.focus();
var Sel = document.selection.createRange();
Sel.moveStart('character', -this.value.length);
CaretPos = Sel.text.length;
}
// Firefox support
else if (this.selectionStart || this.selectionStart == '0')
switch (this.selectionStart) {
case 0:
case 1:
this.selectionStart = 0;
this.selectionEnd = 1;
break;
}
}
I have tried a couple things so far. The code above is attempting to prevent the normal select action then based on where the focus is, select a portion of the string(I only have the switch statement options for the month portion, but if it worked I would do the same for day and year). This may be the wrong way to go about it.
Things to note:
By select I mean highlight.
I do not want to use plugins.
This code will select the portion of the date that is clicked on:
$(".date-container").click(function() {
var val = $(this).val();
var sel = this.selectionStart;
var firstSep = val.indexOf("/"), secondSep;
if (firstSep != -1) {
secondSep = val.indexOf("/", firstSep + 1);
if (secondSep != -1) {
if (sel < firstSep) {
this.setSelectionRange(0, firstSep);
} else if (sel < secondSep) {
this.setSelectionRange(firstSep + 1, secondSep);
} else {
this.setSelectionRange(secondSep + 1, val.length);
}
}
}
});
You can see it work here: http://jsfiddle.net/jfriend00/QV4VT/
Related
An input field #chatInput needs to be be focused when clicking on a container element #text EXCEPT if text inside that element was (highlighted via either double click or mouse selection)
// what I got so far which is incomplete
$('#text').on('click', function (e) {
$('#chatInput').focus();
});
Fiddle: https://jsfiddle.net/xhykmtwy/4/
You may want to consider the solution below, which checks if some text is selected in the text element after the click event:
$('#text').click(function () {
var container = this;
setTimeout(function () {
var selectedElement = null;
var selectedText = null;
if (window.getSelection) {
// For modern browsers
var selection = window.getSelection();
if (selection) {
selectedText = selection.toString();
if (selection.anchorNode) {
selectedElement = selection.anchorNode.parentNode;
}
}
}
else if (document.selection && document.selection.type === "Text") {
// For IE < 9
var selection = document.selection;
selectedText = selection.createRange().text;
}
if (!(selectedText && selectedText.length > 0) || (selectedElement !== container && !$(container).has(selectedElement))) {
setTimeout(function () { $('#chatInput').focus(); }, 0);
}
}, 0);
});
According to my tests, it works in IE (including IE7), Firefox and Chrome. The only exception is the double-click in IE, which does not select the text. You can see the result in this jsfiddle.
The calls to setTimeout ensures that all the selection processing has been done, especially when clicking on the selected text to deselect it.
Credits:
I used the method proposed by Eineki in How can I get the element in which highlighted text is in? to check if the text element contains the selected text.
The code for processing the selection in IE < 9 was found in Tim Down's answer to the post Get the Highlighted/Selected text.
A bit longer than I initially thought a solution could be but here's what I got:
var mouseDownStart = 0,
lastKeyupTime = 0;
function processKeyDown() {
if (!mouseDownStart) {
mouseDownStart = Date.now();
}
}
function processKeyUp() {
var now = Date.now(),
isDoubleClick = lastKeyupTime && now - lastKeyupTime < 500;
isHighliting = now - mouseDownStart > 150
lastKeyupTime = now;
mouseDownStart = 0;
return {
isDoubleClick: isDoubleClick,
isHighliting: isHighliting
}
}
$('#text').on('mousedown', function (e) {
processKeyDown();
});
$('#text').on('mouseup', function (e) {
var data = processKeyUp();
if (data.isDoubleClick || data.isHighliting) return;
$('#chatInput').focus();
});
Updated fiddle: https://jsfiddle.net/xhykmtwy/1/
I'll be thankful for feedback on how i should build a logic of returning caret position for modified input.
Case: we have an input, processed by JS to be formatted like x999y999z9999, where x,y,z - are dividers we define on case by case basis. We process and modify it as intended, but i seem to become lost in logic for returning user's caret position in context of those x,y&z of variable length. I'm even kinda inclined to build a whole complex of if\else in response to those length fluctuations, but there probably is a simpler solution, which i'm missing.
Thanks in advance!
Code example: https://jsfiddle.net/zktva4kc/
function doGetCaretPosition (field) {
if (!!field){
var CaretPos = 0;
// IE Support
if (document.selection) {
field.focus ();
var Sel = document.selection.createRange ();
Sel.moveStart ('character', -field.value.length);
CaretPos = Sel.text.length;
}
// Firefox support
else if (field.selectionStart || field.selectionStart == '0')
CaretPos = field.selectionStart;
return (CaretPos);
}else{
console.log("No such field exist here for function initiation.");
}
}
function setCaretPosition(field, pos)
{
if (!!field){
if(field.setSelectionRange)
{
field.focus();
field.setSelectionRange(pos,pos);
}
else if (field.createTextRange) {
var range = field.createTextRange();
range.collapse(true);
range.moveEnd('character', pos);
range.moveStart('character', pos);
range.select();
}
}else{
console.log("No such field exist here for function initiation.");
}
}
function formatItDown(field, format) {
if (!!field){
field.oninput = function () {
var position=doGetCaretPosition(field);
var sInput=this.value;
var input = this.value;
input = input.replace(/[^\d]/gi, "");
var first = input.substr(0, 3);
var second = input.substr(3, 3);
var third = input.substr(6, 4);
if (input.length > 3) {
first = format[0] + first + format[1];
}
if (input.length > 6) {
second = second + format[2];
}
formatted = first + second + third;
//x012y456z8901
/* this here is the problem area when we use some complex formats
if ((formatted[3]!=sInput[3])&&(position>3)&&(position<6)){
position=position+1;
}else if ((formatted[7]!=sInput[7])&&(position>7)){
position=position+1;
}*/
this.value = formatted;
setCaretPosition(field, position);
}
}else{
console.log("No such field exist here for function initiation.");
}
}
formatItDown(document.getElementById('exampleInput'), ["--","==","__"]);
<input id='exampleInput'>
Why not quickly google for something well coded and bug free ?
Here is a good plugin I used once
https://github.com/acdvorak/jquery.caret
The Question:
When a certain textbox receives focus, set the caret to the end of the textbox. The solution needs to work with IE7, Opera, Chrome and Firefox.
To make things a bit easier, this behavior is only needed when the current value of the textbox is a fixed value. (Say 'INIT')
Incomplete Solutions:
One would expect this to be pretty simple but I couldn't find an answer on SO that works on all browsers. The following answers do NOT work:
$("#test").focus(function() {
// This works for Opera only
// Also tested with $(this).val($(this).val());
if (this.value == "INIT") {
this.value = "";
this.value = "INIT";
}
});
$("#test").focus(function() {
// This works for IE and for Opera
if (this.value == "INIT") {
setCaretPosition(this, 4);
}
});
I got the setCaretPosition function from SO questions and saw it on different blogs aswell:
function setCaretPosition(ctrl, pos) {
if (ctrl.setSelectionRange) {
//ctrl.focus(); // can't focus since we call this from focus()
// IE only
ctrl.setSelectionRange(pos, pos);
}
else if (ctrl.createTextRange) {
// Chrome, FF and Opera
var range = ctrl.createTextRange();
range.collapse(true);
range.moveEnd('character', pos); // Also tested with this
range.moveStart('character', pos); // and this line in comment
range.select();
}
}
Fiddle
I've setup a jsFiddle.
Try this:
$("input").on("focus", function() {
if (this.value === "INIT") {
var input = this;
setTimeout(function() {
if (input.createTextRange) {
var r = input.createTextRange();
r.collapse(true);
r.moveEnd("character", input.value.length);
r.moveStart("character", input.value.length);
r.select();
}
else {
input.selectionStart = input.selectionEnd = input.value.length;
}
}, 13);
}
});
http://jsfiddle.net/azBxU/4/
This should work:
$("#test").focus(function() {
var $this = $(this);
var value = $this.val();
if (value === "INIT") {
setTimeout(function() {
$this.val(value);
}, 0);
}
});
I found a jQuery plugin that's been working for me for a long time. Can't remember where though.
(function($)
{
jQuery.fn.putCursorAtEnd = function() {
return this.each(function() {
$(this).focus()
// If this function exists...
if (this.setSelectionRange) {
// ... then use it
// (Doesn't work in IE)
// Double the length because Opera is inconsistent about whether a carriage return is one character or two. Sigh.
var len = $(this).val().length * 2;
this.setSelectionRange(len, len);
} else {
// ... otherwise replace the contents with itself
// (Doesn't work in Google Chrome)
$(this).val($(this).val());
}
// Scroll to the bottom, in case we're in a tall textarea
// (Necessary for Firefox and Google Chrome)
this.scrollTop = 999999;
});
};
})(jQuery);
I am trying to retrieve/find the start point and end point of selection in textarea.
Here is my code which work fine in Mozilla and chrome, but it is not working in Internet Explorer 9:
<script type="txt/javascript">
function update(o) {
var t = o.value, s = getSelectionStart(o), e = getSelectionEnd(o);
alert("start: " + s + " End: " + e);
}
function getSelectionStart(o) {
if (o.createTextRange) {
var r = document.selection.createRange().duplicate()
rse = r.text.length;
r.moveEnd('character', o.value.length)
if (r.text == '')
return o.value.length
return o.value.lastIndexOf(r.text)
}
else
return o.selectionStart
}
function getSelectionEnd(o) {
if (o.createTextRange) {
var r = document.selection.createRang;
e().duplicate()
r.moveStart('character', -o.value.length)
return r.text.length
}
else
return o.selectionEnd
}
</script>
<textarea id ="text" rows=10 cols="50" onselect="update(this);"></textarea>
When I test this code in Mozilla and Chrome, it gives me correct answer, but when I run this code in Internet Explorer 9, it shows -1 for start and any value for end.
I want to just find out the start and end point/index of the selected text of the textarea. Actually, the above code works fine for a textbox in all browsers, but not with textarea.
How can I fix it?
Use the code below or check this fiddle
function getTextSelection(el) {
var start = 0, end = 0, normalizedValue, range,
textInputRange, len, endRange;
if (typeof el.selectionStart == "number" && typeof el.selectionEnd == "number") {
start = el.selectionStart;
end = el.selectionEnd;
} else {
range = document.selection.createRange();
if (range && range.parentElement() == el) {
len = el.value.length;
normalizedValue = el.value.replace(/\r\n/g, "\n");
// Create a working TextRange that lives only in the input
textInputRange = el.createTextRange();
textInputRange.moveToBookmark(range.getBookmark());
// Check if the start and end of the selection are at the very end
// of the input, since moveStart/moveEnd doesn't return what we want
// in those cases
endRange = el.createTextRange();
endRange.collapse(false);
if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
start = end = len;
} else {
start = -textInputRange.moveStart("character", -len);
start += normalizedValue.slice(0, start).split("\n").length - 1;
if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
end = len;
} else {
end = -textInputRange.moveEnd("character", -len);
end += normalizedValue.slice(0, end).split("\n").length - 1;
}
}
}
}
alert("start :" + start + " End :" + end);
}
While the original answer may have helped the OP in 2012, things have changed, and this has now become simpler, at least in modern browsers.
You can use the JavaScript selectionStart and selectionEnd attributes of the textarea.
Basic Usage
These are both standard attributes that will work in today's major browsers (Chrome, Safari, Firefox, Opera, Edge, and IE).
For example:
console.log(
document.getElementById("text").selectionStart,
document.getElementById("text").selectionEnd
)
will show both the start and end point of the selection in the textarea with the ID text.
Boundary Cases
If there is no item selected in the textarea, then both the start and end attributes will return the last position of the caret. If the textarea has not received focus yet, the attributes will both return a value of 0.
Changing the Selection
By setting these attributes to new values, you will adjust the active text selection. Thus, you can also use this to select text in a textarea.
Checking if a selection is in place
You can check if there is currently a selection by checking if the values are both different, i.e. if
document.getElementById("text").selectionStart != document.getElementById("text").selectionEnd
is true, then there is a text selection.
Demo
const textarea = document.getElementById("text");
const selStart = document.getElementById("selStart");
const selEnd = document.getElementById("selEnd");
// should maybe also look at other events, e.g. "keydown", "select", etc
textarea.addEventListener("mousemove", () => {
selStart.innerText = textarea.selectionStart;
selEnd.innerText = textarea.selectionEnd;
});
<textarea id="text">Some text here</textarea>
<p>Selection Start: <span id="selStart">0</span></p>
<p>Selection End: <span id="selEnd">0</span></p>
References
Live Demo (JSFiddle)
Documentation at MDN
Documentation at MSDN
When selecting a block of text (possibly spanning across many DOM nodes), is it possible to extract the selected text and nodes using Javascript?
Imagine this HTML code:
<h1>Hello World</h1><p>Hi <b>there!</b></p>
If the user initiated a mouseDown event starting at "World..." and then a mouseUp even right after "there!", I'm hoping it would return:
Text : { selectedText: "WorldHi there!" },
Nodes: [
{ node: "h1", offset: 6, length: 5 },
{ node: "p", offset: 0, length: 16 },
{ node: "p > b", offset: 0, length: 6 }
]
I've tried putting the HTML into a textarea but that will only get me the selectedText. I haven't tried the <canvas> element but that may be another option.
If not JavaScript, is there a way this is possible using a Firefox extension?
You are in for a bumpy ride, but this is quite possible. The main problem is that IE and W3C expose completely different interfaces to selections so if you want cross browser functionality then you basically have to write the whole thing twice. Also, some basic functionality is missing from both interfaces.
Mozilla developer connection has the story on W3C selections. Microsoft have their system documented on MSDN. I recommend starting at PPK's introduction to ranges.
Here are some basic functions that I believe work:
// selection objects will differ between browsers
function getSelection () {
return ( msie )
? document.selection
: ( window.getSelection || document.getSelection )();
}
// range objects will differ between browsers
function getRange () {
return ( msie )
? getSelection().createRange()
: getSelection().getRangeAt( 0 )
}
// abstract getting a parent container from a range
function parentContainer ( range ) {
return ( msie )
? range.parentElement()
: range.commonAncestorContainer;
}
My Rangy library will get your part of the way there by unifying the different APIs in IE < 9 and all other major browsers, and by providing a getNodes() function on its Range objects:
function getSelectedNodes() {
var selectedNodes = [];
var sel = rangy.getSelection();
for (var i = 0; i < sel.rangeCount; ++i) {
selectedNodes = selectedNodes.concat( sel.getRangeAt(i).getNodes() );
}
return selectedNodes;
}
Getting the selected text is pretty easy in all browsers. In Rangy it's just
var selectedText = rangy.getSelection().toString();
Without Rangy:
function getSelectedText() {
var sel, text = "";
if (window.getSelection) {
text = "" + window.getSelection();
} else if ( (sel = document.selection) && sel.type == "Text") {
text = sel.createRange().text;
}
return text;
}
As for the character offsets, you can do something like this for any node node in the selection. Note this does not necessarily represent the visible text in the document because it takes no account of collapsed spaces, text hidden via CSS, text positioned outside the normal document flow via CSS, line breaks implied by <br> and block elements, plus other subtleties.
var sel = rangy.getSelection();
var selRange = sel.getRangeAt(0);
var rangePrecedingNode = rangy.createRange();
rangePrecedingNode.setStart(selRange.startContainer, selRange.startOffset);
rangePrecedingNode.setEndBefore(node);
var startIndex = rangePrecedingNode.toString().length;
rangePrecedingNode.setEndAfter(node);
var endIndex = rangePrecedingNode.toString().length;
alert(startIndex + ", " + endIndex);
This returns the selected nodes as I understand it:
When I have
<p> ... </p><p> ... </p><p> ... </p><p> ... </p><p> ... </p>...
<p> ... </p><p> ... </p><p> ... </p><p> ... </p><p> ... </p>
a lot of nodes and I select only a few then I want only these nodes to be in the list.
function getSelectedNodes() {
// from https://developer.mozilla.org/en-US/docs/Web/API/Selection
var selection = window.getSelection();
if (selection.isCollapsed) {
return [];
};
var node1 = selection.anchorNode;
var node2 = selection.focusNode;
var selectionAncestor = get_common_ancestor(node1, node2);
if (selectionAncestor == null) {
return [];
}
return getNodesBetween(selectionAncestor, node1, node2);
}
function get_common_ancestor(a, b)
{
// from http://stackoverflow.com/questions/3960843/how-to-find-the-nearest-common-ancestors-of-two-or-more-nodes
$parentsa = $(a).parents();
$parentsb = $(b).parents();
var found = null;
$parentsa.each(function() {
var thisa = this;
$parentsb.each(function() {
if (thisa == this)
{
found = this;
return false;
}
});
if (found) return false;
});
return found;
}
function isDescendant(parent, child) {
// from http://stackoverflow.com/questions/2234979/how-to-check-in-javascript-if-one-element-is-a-child-of-another
var node = child;
while (node != null) {
if (node == parent) {
return true;
}
node = node.parentNode;
}
return false;
}
function getNodesBetween(rootNode, node1, node2) {
var resultNodes = [];
var isBetweenNodes = false;
for (var i = 0; i < rootNode.childNodes.length; i+= 1) {
if (isDescendant(rootNode.childNodes[i], node1) || isDescendant(rootNode.childNodes[i], node2)) {
if (resultNodes.length == 0) {
isBetweenNodes = true;
} else {
isBetweenNodes = false;
}
resultNodes.push(rootNode.childNodes[i]);
} else if (resultNodes.length == 0) {
} else if (isBetweenNodes) {
resultNodes.push(rootNode.childNodes[i]);
} else {
return resultNodes;
}
};
if (resultNodes.length == 0) {
return [rootNode];
} else if (isDescendant(resultNodes[resultNodes.length - 1], node1) || isDescendant(resultNodes[resultNodes.length - 1], node2)) {
return resultNodes;
} else {
// same child node for both should never happen
return [resultNodes[0]];
}
}
The code should be available at: https://github.com/niccokunzmann/spiele-mit-kindern/blob/gh-pages/javascripts/feedback.js
I posted this answer here because I would have liked to find it here.
There is a much shorter way if you just want the range.
function getRange(){
return (navigator.appName=="Microsoft Internet Explorer")
? document.selection.createRange().parentElement()
: (getSelection||document.getSelection)().getRangeAt(0).commonAncestorContainer
}
All standards compliant code that works in IE11+.
Text String
window.getSelection().getRangeAt(0).toString()
The start node (even if the text is selected backwards):
window.getSelection().anchorNode
The end node (even if the text is selected backwards):
window.getSelection().focusNode
Would you like to know more? Select some text and run the following JavaScript in the console:
console.log(window.getSelection());
console.log(window.getSelection().getRangeAt(0));