Rangy range contained within a jquery selector - javascript

I'm working on a JavaScript wrapper around the Rangy JavaScript plugin. What I'm trying to do: given a jQuery selector and a range, detect if the range is contained within the selector. This is for a space where a user will read a document and be able to make comments about particular sections. So I have a div with id="viewer" that contains the document, and I have an area of buttons that do things after a user selects some text. Here is the (broken) function:
function selectedRangeInRegion(selector) {
var selectionArea = $(selector);
var range = rangy.getSelection().getRangeAt(0);
var inArea = (selectionArea.has(range.startContainer).length > 0);
if (inArea) {
return range;
} else {
return null;
}
}
It appears that selectionArea.has(range.startContainer) returns an array of size 0. I have tried wrapping like: $(range.startContainer). Any tips?
I developed a solution for this problem. This assumes you have a div selector and that your content does not have any divs:
function containsLegalRange(selector, range) {
var foundContainingNode = false;
var container = range.commonAncestorContainer
var nearestDiv = $(container).closest("div");
if (nearestDiv.attr("id") == selector) {
return true
}
else {
return false
}
}

That's not how has() works: the parameter you pass to it is either a selector string or a DOM element, whereas range.startContainer is a DOM node that may in practice be a text node or an element.
I don't think there will be a way that's as easy as you're hoping. The following is as simple as I can think of off the top of my head.
jsFiddle: http://jsfiddle.net/TRVCm/
Code:
function containsRange(selector, range, allowPartiallySelected) {
var foundContainingNode = false;
$(selector).each(function() {
if (range.containsNode(this, allowPartiallySelected)) {
foundContainingNode = true;
return false;
}
});
return foundContainingNode;
}

.has() can be weird sometimes and produce .length == 0 when it is not supposed to. Try this way instead:
function selectedRangeInRegion(selector) {
var range = rangy.getSelection().getRangeAt(0);
var selectionArea = selector + ':has(\'' + range.startContainer + '\')';
var inArea = $(selectionArea).length > 0);
if (inArea) {
return range;
}
else {
return null;
}
}

Related

How to check if html if any html element contains text from some Object

I have some object with label and value. Those elements holds some data.
For now i want to loop on html elements and check, what element match this elements. Object is called ui.
Right now i want to select all elements that contains text from any of object elements.
Those elements have class .img.
Below simple javascript:
$("#project").autocomplete({
source: Items,
appendTo: ".autocomplete",
minLength: 2,
response: function (event, ui) {
if (ui.content.length == 0) {
return false;
}
var content = ui.content[0].label;
$.each($('.img'), function () {
if ($(this).is(':contains(' + content + ')')) {
return;
} else {
$(this).fadeOut(100);
}
});
}
});
This code acts as autocomplete, and i want to hide all emelents that don't match data from ui object.
This code works for me almost fine, but there is one thing that break things:
var content = ui.content[0].label;
This selects only first item from object, and i'm looking for a way, how to check all items from obejct to check if text contains data, not only from first obejct element [0].
Thanks for advice.
I create a jsfiddle for you, If I understand the problem
var matchLabelString=_.reduce(ui,function(accumulateString,current){
if(accumulateString == ""){
return current.label;
}
return accumulateString + "|"+current.label;
},"")
var regex = new RegExp("^"+matchLabelString);
$.each($('.img'), function () {
if (regex.test($(this).text())) {
return;
} else {
$(this).fadeOut(100);
}
});
https://jsfiddle.net/3o1oy5y2/
EDIT: I see your code, and I think that my code work anyway. Try it, as you can see I have used underscore library to create a string seperatad by | from array
You can add a loop in your $each callback :
$.each($('.img'), function() {
for (var i = 0; i < ui.content.length; i++) {
var content = ui.content[i].label;
...
}
});
EDIT: Change let to var

Find maximum z index on a page or in a container [duplicate]

I have a number of div elements with different z-index. And I want to find the highest z-index among these divs - how can I achieve it?
CSS:
#layer-1 { z-index: 1 }
#layer-2 { z-index: 2 }
#layer-3 { z-index: 3 }
#layer-4 { z-index: 4 }
HTML:
<div id="layer-1">layer-1</div>
<div id="layer-2">layer-2</div>
<div id="layer-3">layer-3</div>
<div id="layer-4">layer-4</div>
I don't think this line can find the highest z-index though.
var index_highest = parseInt($("div").css("zIndex"));
// returns 10000
Note that z-index only affects positioned elements. Therefore, any element with position: static will not have a z-index, even if you assign it a value. This is especially true in browsers like Google Chrome.
var index_highest = 0;
// more effective to have a class for the div you want to search and
// pass that to your selector
$("#layer-1,#layer-2,#layer-3,#layer-4").each(function() {
// always use a radix when using parseInt
var index_current = parseInt($(this).css("zIndex"), 10);
if(index_current > index_highest) {
index_highest = index_current;
}
});
JSFiddle demo
A general jQuery selector like that when used with an option that returns one value will merely return the first So your result is simply the z-index of the first div that jQuery grabs. To grab only the divs you want, use a class on them. If you want all divs, stick with div.
Here is a very concise method:
var getMaxZ = function(selector){
return Math.max.apply(null, $(selector).map(function(){
var z;
return isNaN(z = parseInt($(this).css("z-index"), 10)) ? 0 : z;
}));
};
Usage:
getMaxZ($("#layer-1,#layer-2,#layer-3,#layer-4"));
Or, as a jQuery extension:
jQuery.fn.extend({
getMaxZ : function(){
return Math.max.apply(null, jQuery(this).map(function(){
var z;
return isNaN(z = parseInt(jQuery(this).css("z-index"), 10)) ? 0 : z;
}));
}
});
Usage:
$("#layer-1,#layer-2,#layer-3,#layer-4").getMaxZ();
Besides #justkt's native solution above, there is a nice plugin to do what you want.
Take a look at TopZIndex.
$.topZIndex("div");
Try this :
var index_highest = 0;
$('div').each(function(){
var index_current = parseInt($(this).css("z-index"), 10);
if(index_current > index_highest) {
index_highest = index_current;
}
});
This would do it:
$(document).ready(function() {
var array = [];
$("div").each(function() {
array.push($(this).css("z-index"));
});
var index_highest = Math.max.apply(Math, array);
alert(index_highest);
});
Try this
This is taken directly from jquery-ui, it works really well:
(function ($) {
$.fn.zIndex = function (zIndex) {
if (zIndex !== undefined) {
return this.css("zIndex", zIndex);
}
if (this.length) {
var elem = $(this[ 0 ]), position, value;
while (elem.length && elem[ 0 ] !== document) {
// Ignore z-index if position is set to a value where z-index is ignored by the browser
// This makes behavior of this function consistent across browsers
// WebKit always returns auto if the element is positioned
position = elem.css("position");
if (position === "absolute" || position === "relative" || position === "fixed") {
// IE returns 0 when zIndex is not specified
// other browsers return a string
// we ignore the case of nested elements with an explicit value of 0
// <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
value = parseInt(elem.css("zIndex"), 10);
if (!isNaN(value) && value !== 0) {
return value;
}
}
elem = elem.parent();
}
}
return 0;
}
})(jQuery);
I don't know how efficient this is, but you can use $.map to get all the z-indices:
var $divs = $('div'),
mapper = function (elem) {
return parseFloat($(elem).css('zIndex'));
},
indices = $.map($divs, mapper);
The indices variable is now an array of all the z-indices for all the divs. All you'd have to do now is apply them to Math.max:
var highest = Math.max.apply(whatevs, indices);
Here how I got both lowest/highest z-indexes. If you only want to get the highest z-index and nothing more, then this function may not efficient, but if you want to get all z-indexes and the ids associated with it (i.e. for use with bring 'layer' to front/send to back, bring forward, send backward, etc), this is one way to do it. The function returns an array of objects containing ids and their z-indexes.
function getZindex (id) {
var _l = [];
$(id).each(function (e) {
// skip if z-index isn't set
if ( $(this).css('z-index') == 'auto' ) {
return true
}
_l.push({ id: $(this), zindex: $(this).css('z-index') });
});
_l.sort(function(a, b) { return a.zindex - b.zindex });
return _l;
}
// You'll need to add a class 'layer' to each of your layer
var _zindexes = getZindex('.layer');
var _length = _zindexes.length;
// Highest z-index is simply the last element in the array
var _highest = _zindexes[_length - 1].zindex
// Lowest z-index is simply the first element in the array
var _lowest = _zindex[0].zindex;
alert(_highest);
alert(_lowest);
Vanilla JS, not 100% cross-browser. Including as reference for future readers/alternative method.
function getHighIndex (selector) {
// No granularity by default; look at everything
if (!selector) { selector = '*' };
var elements = document.querySelectorAll(selector) ||
oXmlDom.documentElement.selectNodes(selector),
i = 0,
e, s,
max = elements.length,
found = [];
for (; i < max; i += 1) {
e = window.getComputedStyle(elements[i], null).zIndex || elements[i].currentStyle.zIndex;
s = window.getComputedStyle(elements[i], null).position || elements[i].currentStyle.position;
// Statically positioned elements are not affected by zIndex
if (e && s !== "static") {
found.push(parseInt(e, 10));
}
}
return found.length ? Math.max.apply(null, found) : 0;
}
Try my fiddle:
http://planitize.tumblr.com/post/23541747264/get-highest-z-index-with-descendants-included
This combines three advantages I haven't seen combined elsewhere:
Gets either the highest explicitly defined z-index (default) or the highest computed one.
Will look at all descendants of your selector, or all descendants of the document if none is supplied.
Will return either the value of the highest z, or the element that has the highest z.
One disadvantage: no cross-browser guarantees.
If you are doing what I think you're doing, there is no need. Just do this:
$('div[id^=layer-]').css('z-index', 0);
$(this).css('z-index', 1000);

Javascript css selector for nested classes

I am creating a CSS selector for homework. I have managed to extract and get single selectors - e.g. #_id, but I cannot work out how to get a result for nested ones such as : div#_id._class [NOTE: I cannot use any libraries to do this or querySelectorAll]
The pseudo-code below is an example of what I currently have:
if (regex match for class) {
for (a in match for class) {
if (a.indexOf('.') > -1) {
var split_ = a.split(".");
var dot = split_[0];
var class_ = split_[1];
array_of_elements = document.getElementsByClassName(class_);
}
}
The problem is when the selector is nested I can't extract the whole thing using a similar method. E.g. look for an id, look for a class. Can anyone point me in the right direction?
else if (is id) {
split by ("#");
for (each result) {
if (has class ('.')) {
array_elements = document.getElementById(result_ID)
.getElementsByClassName(result_CLASS_NAME));
} else {
array_elements = (document.getElementsByTagName(result));
}
}
What you mentioned is actually called a sequence of simple selectors.
div#_id._class
It consitst of three simple selectors div, #_id, ._class
What you need to do is get elements by tag name, and then check for matches on all of the remaining simple selectors. I'll give you an idea here:
function qSelector(sequence) {
var tagName = getTag(sequence) || '*'; // 'div'
var ids = getIDs(sequence); // ['_id']
var classes = getClasses(sequence); // ['_class']
var els = document.getElementsByTagName(tagName);
return [].filter.call(els, function (el) {
for (id in ids) { if (el.id != id) return false; }
for (cls in classes) { if (el.className not contains cls) return false; }
return true;
});
}
This is more versatile than your approach and can be easily generalized to work with selectors containing spaces.
I'll leave the implementation of the get… helpers to you.

Broken HTML tags when using .innerHTML

As part of a larger script, I've been trying to make a page that would take a block of text from another function and "type" it out onto the screen:
function typeOut(page,nChar){
var txt = document.getElementById("text");
if (nChar<page.length){
txt.innerHTML = txt.innerHTML + page[nChar];
setTimeout(function () {typeOut(page,nChar+1);},20);
}
}
This basically works the way I want it to, but if the block of text I pass it has any html tags in it (like links), those show up as plain-text instead of being interpreted. Is there any way to get around that and force it to display the html elements correctly?
The problem is that you will create invalid HTML in the process, which the browser will try to correct. So apparently when you add < or >, it will automatically encode that character to not break the structure.
A proper solution would not work literally with every character of the text, but would process the HTML element by element. I.e. whenever you encounter an element in the source HTML, you would clone the element and add it to target element. Then you would process its text nodes character by character.
Here is a solution I hacked together (meaning, it can probably be improved a lot):
function typeOut(html, target) {
var d = document.createElement('div');
d.innerHTML = html;
var source = d.firstChild;
var i = 0;
(function process() {
if (source) {
if (source.nodeType === 3) { // process text node
if (i === 0) { // create new text node
target = target.appendChild(document.createTextNode(''));
target.nodeValue = source.nodeValue.charAt(i++);
// stop and continue to next node
} else if (i === source.nodeValue.length) {
if (source.nextSibling) {
source = source.nextSibling;
target = target.parentNode;
}
else {
source = source.parentNode.nextSibling;
target = target.parentNode.parentNode;
}
i = 0;
} else { // add to text node
target.nodeValue += source.nodeValue.charAt(i++);
}
} else if (source.nodeType === 1) { // clone element node
var clone = source.cloneNode();
clone.innerHTML = '';
target.appendChild(clone);
if (source.firstChild) {
source = source.firstChild;
target = clone;
} else {
source = source.nextSibling;
}
}
setTimeout(process, 20);
}
}());
}
DEMO
Your code should work. Example here : http://jsfiddle.net/hqKVe/2/
The issue is probably that the content of page[nChar] has HTML chars escaped.
The easiest solution is to use the html() function of jQuery (if you use jQuery). There a good example given by Canavar here : How to decode HTML entities using jQuery?
If you are not using jQuery, you have to unescape the string by yourself. In practice, just do the opposite of what is described here : Fastest method to escape HTML tags as HTML entities?

Add HTML element after text

I am looking for a way to add an HTML element using JavaScript. But the problem is that the new element might be in between some text. In all other cases I'm using the insertBefore() method.
I am using the following function to get the cursor position.
My initial approach was to split the target innerHTML and add the necessary tags but the cursor position provided does not take into account the character conversions such as space to . So if there are multiple continous spaces, the cursor position will not give the coreect position int he innerHTML.
function getCursorPos()
{
var cursorPos=-1;
if (window.getSelection)
{
var selObj = window.getSelection();
var selRange = selObj.getRangeAt(0);
cursorPos = findNode(selObj.anchorNode.parentNode.childNodes,
selObj.anchorNode) + selObj.anchorOffset;
/* FIXME the following works wrong in Opera when the document is longer than 32767 chars */
}
else if (document.selection)
{
var range = document.selection.createRange();
var bookmark = range.getBookmark();
/* FIXME the following works wrong when the document is longer than 65535 chars */
cursorPos = bookmark.charCodeAt(2) - 11; /* Undocumented function [3] */
}
return cursorPos;
}
function findNode(list, node)
{
for (var i = 0; i < list.length; i++)
{
if (list[i] == node)
{
return i;
}
}
return -1;
}
Is there any other method to do this?
The new element may be in the middle of the HTML ie, it may not be always at the end.
Thank You
You can do this:
var text = $("#container").html();//the target element has the id of container
Process text now, that is break up text the way you want or whatever you want to do with it and add html elements at the relevant position in this text using string addition.
Then do this ...
$("#container").html(text);
You can also take Keith's approach. It's important to realize what he said. Another way to look into it would be inserting a dom element (e.g., html tags) inside text may not be possible with insertAfter or insertBefore.
Here's part of the method I used. It works but I dont know if there are better ways.
function getCursorNode()
{
if (window.getSelection)
{
var selObj = window.getSelection();
return selObj.anchorNode;
}
}
function splitTextNode(pos)
{
selNode=getCursorNode();
if(selNode.nodeName=="#text")
{
var value=selNode.nodeValue;
if(value.length == pos)
{
return ({"node":selNode,"txt":value });
}
else if(pos==0)
{
return ({"node":selNode,"txt":""});
}
else
{
var splittxt1=value.slice(0,pos)
var tempsplit1=document.createTextNode(splittxt1);
var splittxt2=value.slice(pos)
var tempsplit2=document.createTextNode(splittxt2);
myInsertAfterMany([tempsplit1,tempsplit2],selNode);
child.parentNode.removeChild(selNode);
return ({"node":tempsplit1,"first":false,"txt":splittxt1});
}
}
}
But, it(getCursorNode) does not work in IE.
Now, I append the required node after "node" after checking for "first"

Categories