detect underline and strike through - javascript

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().

Related

How can set new content for selected item with javascript?

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

Get HTML of selection in a specific div

I have found a code snippet (can't remember where), and it's working fine - almost :-)
The problem is, that it copies the selection no matter where the selection is made on the entire website, and it must only copy the selection if it is in a specific div - but how is that done?
function getHTMLOfSelection () {
var range;
if (document.selection && document.selection.createRange) {
range = document.selection.createRange();
return range.htmlText;
}
else if (window.getSelection) {
var selection = window.getSelection();
if (selection.rangeCount > 0) {
range = selection.getRangeAt(0);
var clonedSelection = range.cloneContents();
var div = document.createElement('div');
div.appendChild(clonedSelection);
return div.innerHTML;
} else {
return '';
}
} else {
return '';
}
}
$(document).ready(function() {
$("#test").click(function() {
var kopitekst = document.getElementById("replytekst");
var kopitjek=getHTMLOfSelection(kopitekst);
if (kopitjek=='')
{
alert("Please select some content");
}
else
{
alert(kopitjek);
}
});
});
I have made a Jsfiddle
This is my first post here. Hopefully I done it right :-)
That's because it checks the entire document with:
if (document.selection && document.selection.createRange) {
range = document.selection.createRange();
return range.htmlText;
}
Not a specific section. If you want to check specific sections for selected text, you need to identify that you are searching for them in the search selection, something that nails your range down to a particular div:
range = $('#replytekst');
Specify a particular DOM element instead of using document object.
var oDiv = document.getElementById( 'selDiv' );
then use
if ( oDiv.selection && oDiv.selection.createRange ) {
range = oDiv.selection.createRange();
return range.htmlText;
}
You need to check if the section contains the selection. This is separate from getting the selection. There is a method for doing this in this answer: How to know if selected text is inside a specific div
I've updated your fiddle
Basically you need to check the id of the parent/ascendant of the selected text node.
selection.baseNode.parentElement.id or selection.baseNode.parentElement.parentElement.id will give you that.
Edit: I've thought of another, somewhat hack-y, way of doing it.
If
kopitekst.innerHTML.indexOf(kopitjek) !== -1
gives true, you've selected the right text.
DEMO1
DEMO2
(these work in Chrome and Firefox, but you might want to restructure the getHTMLOfSelection function a little)
If it possible for you I recommend to use rangy framework. Then your code might look like this:
// get the selection
var sel = rangy.getSelection();
var ranges = sel.getAllRanges();
if (!sel.toString() || !sel.toString().length)
return;
// create range for element, where selection is allowed
var cutRange = rangy.createRange();
cutRange.selectNode(document.getElementById("replytekst"));
// make an array of intersections of current selection ranges and the cutRange
var goodRanges = [];
$.each(ranges, function(j, tr) {
var rr = cutRange.intersection(tr);
if (rr)
goodRanges.push(rr);
});
sel.setRanges(goodRanges);
// do what you want with corrected selection
alert(sel.toString());
// release
sel.detach();
In this code if text was selected in your specific div then it will be kept, if there was selection where other elements take part too, these selection ranges will be cut off.

How to find if a HTMLElement is enclosed in Selected text

Is it possible to find out if an HTMLElement is totally enclosed within the selection?
I have a scenario where user selects some text in a HTML editor and applies some custom style from a list. Now I need to change the class attribute of each span element that is enclosed in that selection and surrounding the selection with a new span with the selected style.
Am able to find out if a particular span element is in selection by using DOM Range's compareBoundaryPoints method in firefox and safari but it will not work for IE.
Is there any way to find out if an element is totally enclosed with in the selected range for IE?
Thanks
Kapil
As #standardModel says, Rangy gives you full* DOM Range support in IE and has a helpful getNodes() method that you could use:
var sel = rangy.getSelection();
if (sel.rangeCount) {
var range = sel.getRangeAt(0);
var spans = range.getNodes([1], function(node) {
return node.nodeName.toLowerCase() == "span" && range.containsNode(node);
});
// Do stuff with spans here
}
If you'd rather not use something as bulky as Rangy, the following function will tell you if an element is completely selected:
function isSelected(el) {
if (window.getSelection) {
var sel = window.getSelection();
var elRange = document.createRange();
elRange.selectNodeContents(el);
for (var i = 0, range; i < sel.rangeCount; ++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.type == "Text") {
var textRange = document.selection.createRange();
var elTextRange = textRange.duplicate();
elTextRange.moveToElementText(el);
return textRange.inRange(elTextRange);
}
return false;
}
jsFiddle example: http://jsfiddle.net/54eGr/1/
(*) Apart from handling Range updates under DOM mutation
You may want to take a look at Rangy. This makes xbrowser Ranges and Selections a lot easier.

Javascript/Jquery - How do I get the element that the user has selected with their cursor?

If the user highlights the text within an <h1> with their cursor, how do I get that <h1> object? Or if they selected text within an <li>, how do i get that <li>?
You can get the selection on Document as,
dd = window.getSelection();
desiredElement = dd.focusNode.parentNode; // h1 or li or other
desiredTag = desiredElement.tagName; // its tagname
Happy Coding.
You need to deal with window.getSelection().
See
See Here
Here
and Here
$('h1').click(function(){
alert(this); // `this` is the <h1> object clicked.
});
is there some tricky part I missed in your question?
You can get the parent element of a selection in all modern mainstream browsers as follows. Bear in mind that Firefox allows multiple selections by default these days; this code will use only the first.
See also my answer here: How can I get the DOM element which contains the current selection?
function getSelectionContainerElement() {
var sel, el;
if (window.getSelection) {
sel = window.getSelection();
if (sel.getRangeAt) {
if (sel.rangeCount) {
el = sel.getRangeAt(0).commonAncestorContainer;
return (el.nodeType == 3) ? el.parentNode : el;
}
} else {
// This happens in old versions of Safari. A workaround
// exists, if you need it
}
} else if (document.selection && document.selection.createRange) {
return document.selection.createRange().parentElement();
}
}

Change CSS of selected text using Javascript

I'm trying to make a JavaScript bookmarklet that will act as a highlighter, changing the background of selected text on a webpage to yellow when the bookmarklet is pressed.
I'm using the following code to get the selected text, and it works fine, returning the correct string
function getSelText() {
var SelText = '';
if (window.getSelection) {
SelText = window.getSelection();
} else if (document.getSelection) {
SelText = document.getSelection();
} else if (document.selection) {
SelText = document.selection.createRange().text;
}
return SelText;
}
However, when I created a similar function to change the CSS of the selected text using jQuery, it isn't working:
function highlightSelText() {
var SelText;
if (window.getSelection) {
SelText = window.getSelection();
} else if (document.getSelection) {
SelText = document.getSelection();
} else if (document.selection) {
SelText = document.selection.createRange().text;
}
$(SelText).css({'background-color' : 'yellow', 'font-weight' : 'bolder'});
}
Any ideas?
The easiest way to do this is to use execCommand(), which has a command to change the background colour in all modern browsers.
The following should do what you want on any selection, including ones spanning multiple elements. In non-IE browsers it turns on designMode, applies a background colour and then switches designMode off again.
UPDATE
Fixed in IE 9.
function makeEditableAndHighlight(colour) {
var range, sel = window.getSelection();
if (sel.rangeCount && sel.getRangeAt) {
range = sel.getRangeAt(0);
}
document.designMode = "on";
if (range) {
sel.removeAllRanges();
sel.addRange(range);
}
// Use HiliteColor since some browsers apply BackColor to the whole block
if (!document.execCommand("HiliteColor", false, colour)) {
document.execCommand("BackColor", false, colour);
}
document.designMode = "off";
}
function highlight(colour) {
var range, sel;
if (window.getSelection) {
// IE9 and non-IE
try {
if (!document.execCommand("BackColor", false, colour)) {
makeEditableAndHighlight(colour);
}
} catch (ex) {
makeEditableAndHighlight(colour)
}
} else if (document.selection && document.selection.createRange) {
// IE <= 8 case
range = document.selection.createRange();
range.execCommand("BackColor", false, colour);
}
}
Here is a crude example of how it could work. As Zack points out you'll need to be aware of cases where the selection spans multiple elements. This isn't intended to be used as-is, just something to help get ideas flowing. Tested in Chrome.
var selection = window.getSelection();
var text = selection.toString();
var parent = $(selection.focusNode.parentElement);
var oldHtml = parent.html();
var newHtml = oldHtml.replace(text, "<span class='highlight'>"+text+"</span>");
parent.html( newHtml );
To make the highlight stick permanently, I believe you are going to have to wrap the selection in a new DOM element (span should do), to which you can then attach style properties. I don't know if jQuery can do that for you. Keep in mind that selections can span element boundaries, so in the general case you're going to have to inject a whole bunch of new elements
Have a look at a little example i made at http://www.jsfiddle.net/hbwEE/3/
It does not take into account selections that span multiple elements..
(IE will do but will mess the html a bit ..)
In Firefox, you can use the ::-moz-selection psuedo-class.
In Webkit, you can use the ::selection pseudo-class.
I like Tim's answer, it's clean and fast. But it also shuts down the doors to doing any interactions with the highlights.
Inserting inline elements directly around the texts is a bad choice, as they broke the text flow and mess things up in complex situations,
So I suggest a dirty hack that
calculates the absolute layout of each line of selected text (no matter where they are),
then insert colored, semi-transparent inline-block elements in the end of the document body.
This chrome extension is an example of how this can be done.
It uses API from this library to get the absolute layout of each selected line.

Categories