Let's say I have a text with mark, such as:
And I would like to disable selecting such that start or end of selection is not in the mark. Therefore, all of these selections should be possible:
On the other hand, these selections should be disabled:
So far, I tried only using some simple css,
mark {
-khtml-user-select: all;
-webkit-user-select: all;
-o-user-select: all;
user-select: all;
}
This is <mark>marked text</mark>. I want to disable selecting only part of <mark>marked text</mark>.
jsfiddle link
Is there any way to do this? Any answer would be appreciated!
I achieved what I wanted to do, so I share it here.
function checkSelection () {
var sel = window.getSelection();
var marks = document.getElementsByTagName('mark');
for(var i = 1; i < sel.rangeCount; i++) {
sel.removeRange(sel.getRangeAt(i));
}
var range = sel.getRangeAt(0);
var startnode = range.startContainer;
var endnode = range.endContainer;
for (var i = 0; i < marks.length; i++) {
if (marks[i].contains(startnode)) {
range.setStartBefore(startnode);
}
if (marks[i].contains(endnode)) {
range.setEndAfter(endnode);
}
}
}
document.addEventListener('mouseup', checkSelection)
document.addEventListener('touchend', checkSelection)
This is <mark>marked text</mark>. I want to disable selecting only part of <mark>marked text</mark>.
jsfiddle link
I don't think it would be possible to do with CSS alone, you're most likely going to need JavaScript to detect which elements the user began selecting on.
What I did was I used the containsNode method from the Selection API (a Working Draft, may not work with all browsers yet, and may be deprecated) to detect whether the selected range either contains the whole marked element or not at all. If the selection only contains part of a marked element, it would clear the selection using the removeAllRanges method.
function checkSelection () {
var sel = window.getSelection()
var marks = document.getElementsByTagName('mark')
for (var i = 0; i < marks.length; i++) {
if (sel.containsNode(marks[i], false) !== sel.containsNode(marks[i], true))
sel.removeAllRanges() // clear selection
}
}
document.addEventListener('mouseup', checkSelection)
document.addEventListener('touchend', checkSelection)
mark {
background: yellow;
}
This is <mark>marked text</mark>. I want to disable selecting only part of <mark>marked text</mark>.
Note that you would need to capture all methods of selection that you want, whether by mouse events and touch events (all that I included), keyboard caret selection, or programmatic selection.
This can be extended and tuned to your liking, but this is my best shot at replicating the behavior you want from your specs. The above snippet doesn't work correctly all the time if one of the selection's bounds begins or ends at the edge of a marked region, but the snippet should demonstrate the basic concept behind what should work, just with a little fine-tuning for edge cases.
user-select CSS property is not yet official and it is only supported by the latest browsers.
Anyways you can use user-select: none; and enclose all text that can not be selected inside a span tag
https://developer.mozilla.org/en-US/docs/Web/CSS/user-select
But you can try something like this
.unselectable {
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
<p>This text can be selected, But <span class="unselectable">this</span> can only be <span class="unselectable">partially se</span>lected</p>
https://developer.mozilla.org/en-US/docs/Web/CSS/user-select
Related
suppose to be we have a paragraph with this content " Hi , It's a new question in stackoverflow!"
and when we are selecting something in this paragraph , it's turn to be Red .for example we selected stackoverflow & then it turn to <span class="red">stackoverflow</span>.how can we do this with Javascript?
here is my codes :
var x = {};
x.getSelected = function() {
var t = '';
if (window.getSelection) {
t = window.getSelection();
} else if (document.getSelection) {
t = document.getSelection();
} else if (document.selection) {
t = document.selection.createRange().text;
}
return t;
}
$(document).ready(function() {
var selectedText;
$(document).bind("mouseup", function() {
selectedText = x.getSelected()
if (selectedText !=''){
alert(selectedText);
//Now I wanna set new content for selected item but not working
a=selectedText;
selectedText.html("<span class='red'>"+a+"</span>");
}
});
});
.red {
color : red;
}
<p>suppose to be we have a paragraph with this content " Hi , It's a new question in stackoverflow!" and when we are selecting something in this paragraph , it's turn to be Red .for example we selected stackoverflow & then it turn to .how can we do this with Javascript? </p>
...when we are selecting something in this paragraph , it's turn to be
Red...
You could have a stab at the styleWithCSS command of the editing API, execCommand that is.
However, before proceeding please note that:
This spec is incomplete and it is not expected that it will advance
beyond draft status. Authors should not use most of these features
directly, but instead use JavaScript editing libraries. The features
described in this document are not implemented consistently or fully
by user agents, and it is not expected that this will change in the
foreseeable future.... This spec is to meant to help implementations
in standardizing these existing features. It is predicted that in the
future both specs will be replaced by Content Editable Events and
Input Events....
Having clarified that, the following will work in most modern browsers viz. Edge, FireFox and Chrome that I could test in.
By default the foreColor command of execCommand wraps the selected text with a font tag, which is deprecated. So, you need to use the styleWithCSS command. Now this works with the editing API, which means that the element you are trying to work with, should have its contentEditable attribute set.
To work around this, you can temporarily set this attribute just before changing the color in the selected text fragment and then resetting the attribute once done.
Given your paragraph like this:
<p id="p">
Hi , It's a new question in StackOverflow!
</p>
When you select the word StackOverflow, the following code will result in this...
<p id="p">
Hi , It's a new question in <span style="color: rgb(255, 0, 0);">StackOverflow</span>!
</p>
... wrapping your selected text in a span with the style applied.
Fiddle: http://jsfiddle.net/abhitalks/j9w6dj7m/
Snippet:
p = document.getElementById('p');
p.addEventListener('mouseup', setColor);
function setColor() {
p.setAttribute('contentEditable', true);
document.execCommand('styleWithCSS', false, true);
document.execCommand('foreColor', false, "#f00");
p.setAttribute('contentEditable', false);
}
<p id="p" contentEditable="false">
Hi , It's a new question in stackoverflow!
</p>
Edit:
Now that you have added code (and what you have already tried) in your question, you could use the range selection to do what you are after.
Specifically, you will need to learn:
selection: https://developer.mozilla.org/en-US/docs/Web/API/Selection, this you have already done. Cheers!
range: https://developer.mozilla.org/en-US/docs/Web/API/Range/Range, because you will be dealing with ranges here
selection.getRangeAt(): https://developer.mozilla.org/en-US/docs/Web/API/Selection/getRangeAt, because you will need to extract the selected text as a range object
range.surroundContents(): https://developer.mozilla.org/en-US/docs/Web/API/range/surroundContents, because you will need to surround the selected text range with a span.
Putting it all together all you have to do is (explanation in code comments):
function setClass() {
var selection = x.getSelected(), range, // you have already done this
span = document.createElement("span"); // create a span element
span.classList.add('red'); // add the class to the span
if (selection != '') {
range = selection.getRangeAt(0); // get the range from selected text
range.surroundContents(span); // surround the range with span
}
}
Fiddle 2: http://jsfiddle.net/abhitalks/kn0u5frj/
Snippet 2:
var x = {},
p = document.getElementById('p');
p.addEventListener('mouseup', setClass);
function setClass() {
var selection = x.getSelected(), range,
span = document.createElement("span");
span.classList.add('red');
if (selection != '') {
range = selection.getRangeAt(0);
range.surroundContents(span);
}
}
x.getSelected = function() {
var t = '';
if (window.getSelection) {
t = window.getSelection();
} else if (document.getSelection) {
t = document.getSelection();
} else if (document.selection) {
t = document.selection.createRange().text;
}
return t;
}
.red { color: #f00; }
<p id="p">
Hi , It's a new question in stackoverflow!
</p>
You can use the getSelection() method
Below is the example:
Repeated Question:
How to get selected text in textarea?
You can use CSS with :: selection http://caniuse.com/#search=%3A%3Aselection
::selection {
background: #ffb7b7; /* WebKit/Blink Browsers */
}
::-moz-selection {
background: #ffb7b7; /* Gecko Browsers */
}
Or javascript with range
For example, if I want a grabbing icon for my cursor, in CSS I would use this:
div {
cursor: -moz-grabbing;
cursor: -webkit-grabbing;
cursor: grabbing;
}
But let's say, I want to implement this via JavaScript but still being able to cover all three, how do I do this? Do I just assign them in three lines -- does JavaScript fallback to the previous assignment?
document.getElementById('theDiv').style.cursor = '-webkit-grabbing';
document.getElementById('theDiv').style.cursor = '-moz-grabbing';
document.getElementById('theDiv').style.cursor = 'grabbing';
1) You can add a class for that purpose which assigns all the properties.
2) If you try it your way then, Javascript will reassign the property 3 times and end up with the last one executed as the active one, So
document.getElementById('theDiv').style.cursor = '-webkit-grabbing';
document.getElementById('theDiv').style.cursor = '-moz-grabbing';
document.getElementById('theDiv').style.cursor = 'grabbing';
will not work.
3) Adding a class would do it. for example:
css:-
.myClass {
cursor: -moz-grabbing;
cursor: -webkit-grabbing;
cursor: grabbing;
}
and
js:-
document.getElementById('theDiv').className += 'myClass';
No, Javascript will reassign the property 3 times and end up with the last one executed as the active one.
What you want is to detect which browser you are using with javascript and then apply the appropriate one.
In the end of the day your JS is executed on the user's specific machine with the specific browser so by detecting the browser and applying the appropriate style for that browser you solve your problem.
Pesudo Code:
if(isMozilla) {
document.getElementById('theDiv').style.cursor = '-moz-grabbing';
}
else if(isChrome OR isSafari) {
document.getElementById('theDiv').style.cursor = '-webkit-grabbing';
}
else {
document.getElementById('theDiv').style.cursor = 'grabbing';
}
Probably good to know in this regard: If you are trying to set an invalid value, the browser will ignore it. So something like this works:
function setStyle(element, property, value) {
var prefix = ['', '-webkit-', '-moz-'];
var i = 0;
var style = element.style;
do {
style[property] = prefix[i] + value;
i += 1;
}
while(style[property] !== prefix[i] + value && i < prefix.length);
}
Is there any way to detect with JavaScript/jQuery if an element is strike-through and underlined.
So let's say we have:
<u><s>text here.</s>other text here.</u>
Is it possible to detect if the text within <s> is also underlined?
*Ideally, it would not be allowed to look if <u> has any <s> children.
I've been toying around with it and it seems peculiar that both styles use the same CSS property, which in turn makes me wonder how it even works in the first place.
To make my problem clearer:
I'm playing around with a self-made wysiwyg editor, for usability reasons I'm trying to implement a listener on the text which alters (lights up) editing buttons. e.g. when a part of text is bold the "B" button changes to an active state. I'm currently handling this by getting the element at the cursor and checking if the element is bold or inherits it.
The problem with underline and striketrough is that they are neither overwriting the text-decoration attribute of each other, and are not visible in css
when I put the cursor on a underlined text-fragment, the text-decoration property only shows as underline, while the text is both underline and line-through. In such situations I cannot know what the exact relation is between the <u> element and the <s> element; the <s> element could be 100 parents back as far as I could know.
A lot of text, but I hope it kinda clears up my situation.
Here is the robust way of doing it. #Cheery answer works well but it fails if italic or underline or any other font-style provided through CSS. Credit is given to Tim Down for his numerous answers for these kind of questions.
function checkState(element, check) {
var doc = document;
var text = doc.getElementById(element);
if (doc.body.createTextRange) { // ms
var range = doc.body.createTextRange();
range.moveToElementText(text);
range.select();
} else if (window.getSelection) { // moz, opera, webkit
var selection = window.getSelection();
var range = doc.createRange();
range.selectNodeContents(text);
selection.removeAllRanges();
selection.addRange(range);
}
var range, checked = false;
if (window.getSelection) {
var sel = window.getSelection();
if (sel && sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
document.designMode = "on";
sel.removeAllRanges();
sel.addRange(range);
}
}
if (document.queryCommandState) {
checked = document.queryCommandState(check);
}
if (document.designMode == "on") {
document.designMode = "off";
}
if (window.getSelection) {
if (window.getSelection().empty) { // Chrome
window.getSelection().empty();
} else if (window.getSelection().removeAllRanges) { // Firefox
window.getSelection().removeAllRanges();
}
} else if (document.selection) { // IE?
document.selection.empty();
}
return checked;
}
alert(checkState('c', 'underline')); // italic, bold etc..
var str = '<u><s>text here.</s>other text here.</u>';
var el = $('<div>').html(str);
alert($('u s', el).length);
what if the combination is or even something like
so what, check inverse too..
var str = '<s><div><u></u></div><p><u></u></p></s>';
var el = $('<div>').html(str);
alert($('u s', el).length || $('s u', el).length);
if the initial string is not a valid html then you do not know how some browsers will behave at its output.
ps: made some simple example, by click..
$(function(){
$('.wrapper').on('click', '*', function() {
var styles = ['line-through', 'underline'], counter = [0, 0], tags = ['S', 'U'];
$(this).parentsUntil('.wrapper').andSelf().each(function() {
var current = $(this).css('text-decoration'), $tag = $(this)[0];
$.each(styles, function(index, style) {
if (current.indexOf(style) > -1 || $tag.tagName == tags[index]) counter[index] += 1;
});
});
var results = [];
if (counter[0] > 0) results.push('striked');
if (counter[1] > 0) results.push('underlined');
alert(results.join(' and '));
return false;
});
});
.strike {
text-decoration: line-through;
}
.underline {
text-decoration: underline;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<div class='wrapper'>
<div class='strike'>
striked <u>underlined</u>
</div>
<div class='underline'>
underlined <s>striked</s>
</div>
</div>
A somewhat horrible approach, but the only way I could see that doesn't involve explicitly checking nesting, or relying on default CSS surviving any theming (<s> won't always, necessarily, have text-decoration: line-through;, similarly <u> won't always, necessarily, have text-decoration: underline;):
// the text-decoration styles you want to find:
var styles = ['line-through', 'underline'];
// finding all elements within the <body>, and
// filtering them:
var underlinedAndLineThrough = $('body *').filter(function() {
// caching because of re-use:
var self = $(this),
decor = self.css('text-decoration');
// if the 'text-decoration' style is found in the array of styles we're
// looking for:
if (styles.indexOf(decor) > -1) {
// we add that style as a class-name to the current element, and all
// descendants:
self.find('*').add(self).addClass(decor);
// we return the current element (to keep it in the collection):
return self;
}
// filtering again:
}).filter(function(){
// we keep the current element of the collection if it has all the css styles
// we're looking for:
return $(this).is('.' + styles.join('.'));
});
console.log(underlinedAndLineThrough);
JS Fiddle demo.
References:
JavaScript:
Array.prototype.indexOf().
Array.prototype.join().
jQuery:
add().
addClass().
filter().
find().
is().
Okay, so I am able to edit the content of my DIV just fine. However, there are a couple things I'm trying to work around.
In my editable div, I have a div containing a site address (such as twitter.com), I don't want that to be deletable from the editable DIV; the reason it's in there is so a user could copy all of the text including the domain.
If you remove all of the text except the domain, then the caret doesn't show up - or it does for a second all the way at the right side of the DIV.
And if a user deletes the domain name, it reappears but the caret is now to the left of it which I also don't want.
Any solutions on making the caret always visible and keeping it to the right of the domain?
Oh, and for some reason I can't use onKeyDown on the editable DIV to activate the anylyeText function (at least not in JSfiddle) so I had to do a setTimeout loop.
Here is my fiddle: jsfiddle
CSS:
.url {
display: block;
border: 1px solid rgb(140,140,140);
padding: 5px;
box-sizing: border-box;
color: rgb(35,155,215);
cursor: text;
outline: none;
}
.url:focus {
border-color: rgb(35,155,215);
}
.noedit {
display: inline;
color: rgb(140,140,140);
}
HTML:
<div class="url" contenteditable="true" id="textbox"><div class="noedit" contenteditable="false">http://twitter.com/</div>username</div>
JS:
var analyzeText = function() {
var textbox = document.getElementById('textbox');
var textVal = textbox.innerHTML;
var urlString = '<div class="noedit" contenteditable="false">http://twitter.com/</div>';
if (textVal.length < urlString.length) {
textbox.innerHTML = urlString;
textbox.focus();
}
setTimeout(function() { analyzeText(); }, 100);
};
(function(){analyzeText();})();
Here's a method using Selection and Range.
MDN Selection
MDN Range
(If you check out MDN, you'll notice that IE9 support isn't there - prior to IE9, you had to use proprietary IE script. Google can help you out there!)
First thing I do is add these two variables:
var range = document.createRange();
var sel = window.getSelection();
Then, I modified your script like so:
if (textVal.length < urlString.length) {
textbox.innerHTML = urlString;
// We create a text node and append to the textbox to select it
textbox.appendChild(document.createTextNode(''));
// Text node is selected
range.setStart(textbox.childNodes[1], 0);
// Collapse our range to single point (we're not selecting a word, after all!)
range.collapse(true);
// Let's clear any selections
sel.removeAllRanges();
// Alright, time to position our cursor caret!
sel.addRange(range);
textbox.focus();
}
And that's it! Here's an updated Fiddle to demonstrate:
Edit - Updated to include logic to prevent user from inserting text to the left of the domain
http://jsfiddle.net/Qycw2/6/
How to highlight/select the contents of a DIV tag when the user clicks on the DIV...the idea is that all of the text is highlighted/selected so the user doesn't need to manually highlight the text with the mouse and potentially miss a bit of the text?
For example, say we've got a DIV as below:
<div id="selectable">http://example.com/page.htm</div>
...and when the user clicks on any of that URL the whole URL text is highlighted so they can easily drag the selected text around in the browser, or copy the complete URL with a right click.
Thanks!
function selectText(containerid) {
if (document.selection) { // IE
var range = document.body.createTextRange();
range.moveToElementText(document.getElementById(containerid));
range.select();
} else if (window.getSelection) {
var range = document.createRange();
range.selectNode(document.getElementById(containerid));
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
}
}
<div id="selectable" onclick="selectText('selectable')">http://example.com/page.htm</div>
Now you have to pass the ID as an argument, which in this case is "selectable", but it's more global, allowing you to use it anywhere multiple times without using, as chiborg mentioned, jQuery.
UPDATE 2017:
To select the node's contents call:
window.getSelection().selectAllChildren(
document.getElementById(id)
);
This works on all modern browsers including IE9+ (in standards mode).
Runnable Example:
function select(id) {
window.getSelection()
.selectAllChildren(
document.getElementById("target-div")
);
}
#outer-div { padding: 1rem; background-color: #fff0f0; }
#target-div { padding: 1rem; background-color: #f0fff0; }
button { margin: 1rem; }
<div id="outer-div">
<div id="target-div">
Some content for the
<br>Target DIV
</div>
</div>
<button onclick="select(id);">Click to SELECT Contents of #target-div</button>
The original answer below is obsolete since window.getSelection().addRange(range); has been deprecated
Original Answer:
All of the examples above use:
var range = document.createRange();
range.selectNode( ... );
but the problem with that is that it selects the Node itself including the DIV tag etc.
To select the Node's text as per the OP question you need to call instead:
range.selectNodeContents( ... )
So the full snippet would be:
function selectText( containerid ) {
var node = document.getElementById( containerid );
if ( document.selection ) {
var range = document.body.createTextRange();
range.moveToElementText( node );
range.select();
} else if ( window.getSelection ) {
var range = document.createRange();
range.selectNodeContents( node );
window.getSelection().removeAllRanges();
window.getSelection().addRange( range );
}
}
There is pure CSS4 solution:
.selectable{
-webkit-touch-callout: all; /* iOS Safari */
-webkit-user-select: all; /* Safari */
-khtml-user-select: all; /* Konqueror HTML */
-moz-user-select: all; /* Firefox */
-ms-user-select: all; /* Internet Explorer/Edge */
user-select: all; /* Chrome and Opera */
}
user-select is a CSS Module Level 4 specification, that is currently a draft and non-standard CSS property, but browsers support it well — see #search=user-select.
.selectable{
-webkit-touch-callout: all; /* iOS Safari */
-webkit-user-select: all; /* Safari */
-khtml-user-select: all; /* Konqueror HTML */
-moz-user-select: all; /* Firefox */
-ms-user-select: all; /* Internet Explorer/Edge */
user-select: all; /* Chrome and Opera */
}
<div class="selectable">
click and all this will be selected
</div>
Read more on user-select here on MDN and play with it here in w3scools
The answer of Neuroxik was really helpful. I had only a trouble with Chrome, because when I clicked on an external div, It did not work. I could solve it removing the old ranges before add the new range:
function selectText(containerid) {
if (document.selection) {
var range = document.body.createTextRange();
range.moveToElementText(document.getElementById(containerid));
range.select();
} else if (window.getSelection()) {
var range = document.createRange();
range.selectNode(document.getElementById(containerid));
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
}
}
<div id="selectable" onclick="selectText('selectable')">http://example.com/page.htm</div>
For content editable stuff (not regular inputs, you need to use selectNodeContents (rather than just selectNode).
NOTE: All the references to "document.selection" and "createTextRange()" are for IE 8 and lower... You'll not likely need to support that monster if you're attempting to do tricky stuff like this.
function selectElemText(elem) {
//Create a range (a range is a like the selection but invisible)
var range = document.createRange();
// Select the entire contents of the element
range.selectNodeContents(elem);
// Don't select, just positioning caret:
// In front
// range.collapse();
// Behind:
// range.collapse(false);
// Get the selection object
var selection = window.getSelection();
// Remove any current selections
selection.removeAllRanges();
// Make the range you have just created the visible selection
selection.addRange(range);
}
Using a text area field, you could use this: (Via Google)
<form name="select_all">
<textarea name="text_area" rows="10" cols="80"
onClick="javascript:this.form.text_area.focus();this.form.text_area.select();">
Text Goes Here
</textarea>
</form>
This is how I see most websites do it. They just style it with CSS so it doesn't look like a textarea.
This snippet provides the functionality you require. What you need to do is add an event to that div that which activates fnSelect in it. A quick hack that you totally shouldn't do and possibly might not work, would look like this:
document.getElementById("selectable").onclick(function(){
fnSelect("selectable");
});
Obviously assuming that the linked to snippet had been included.
I found it useful to wrap this function as a jQuery plugin:
$.fn.selectText = function () {
return $(this).each(function (index, el) {
if (document.selection) {
var range = document.body.createTextRange();
range.moveToElementText(el);
range.select();
} else if (window.getSelection) {
var range = document.createRange();
range.selectNode(el);
window.getSelection().addRange(range);
}
});
}
So, it becomes a reusable solution. Then you can do this:
<div onclick="$(this).selectText()">http://example.com/page.htm</div>
And it will selected test in the div.
How about this simple solution? :)
<input style="background-color:white; border:1px white solid;" onclick="this.select();" id="selectable" value="http://example.com/page.htm">
Sure it is not div-construction, like you mentioned, but still it is worked for me.
Niko Lay:
How about this simple solution? :)
`<input style="background-color:white; border:1px white solid;" onclick="this.select();" id="selectable" value="http://example.com/page.htm">`
.....
Code before:
<textarea rows="20" class="codearea" style="padding:5px;" readonly="readonly">
Code after:
<textarea rows="20" class="codearea" style="padding:5px;" readonly="readonly" onclick="this.select();" id="selectable">
Just this part onclick="this.select();" id="selectable" in my code worked fine.
Selects all in my code box with one mouse click.
Thanks for help Niko Lay!
Easily achieved with the css property user-select set to all. Like this:
div.anyClass {
user-select: all;
}
try this, altn adding to oncontextmenu will gice quick access to extentions allowing input this way.
<div onclick='window.getSelection().selectAllChildren(this)' id="selectable">http://example.com/page.htm</div>
A solution, working also in case your element is part of an iframe
element.ownerDocument?.getSelection()?.selectAllChildren(element);
$.fn.selectText = function () {
return $(this).each(function (index, el) {
if (document.selection) {
var range = document.body.createTextRange();
range.moveToElementText(el);
range.select();
} else if (window.getSelection) {
var range = document.createRange();
range.selectNode(el);
window.getSelection().addRange(range);
}
});
}
Above answer is not working in Chrome because addRange remove previous added range. I didnt find any solution for this beside fake selection with css.
export const selectText = (containerId) => {
let selection = window.getSelection()
let myElement = document.getElementById(containerId)
if (selection.rangeCount > 0) {
selection.removeAllRanges()
}
let range = document.createRange()
range.selectNode(myElement)
selection.addRange(range)
}**The most simplest answer works in all browsers**