Developers Club geek daily blog

2 years, 4 months ago
Recently I tried to write some conditionally cross-browser bukmarklet with selections and navigation of medium complexity. It has decided to be limited to the latests version of Google Chrome, Firefox and Internet Explorer. Having started check in the last browser, with grief has found, what even in IE 11 still there is no support XPath.

Seemingly complete support is promised in Edge:? Microsoft Edge supports the XML Path Language Version 1.0 with no variations or extensions?. And already even, apparently, implementation is added to Internet Explorer Developer Channel (nobody checked?). But this still insufficient consolation.

Detection on the method described here) became the following step? everything remarkably works even at the paranoid sites like the Twitter (by the way, if you suddenly did not know, in Firefox still it is impossible to start bukmarklt in the Twitter or, for example, in Gitkhab, because of still not corrected bug). But method this very bulky. It well is suitable for development of the sites, but it burdens small user bukmarkleta excess asynchrony, complication of logic and extra time on loading of the file.

It was necessary to look for simpler replacements for some tools which were not enough for me XPath.

Thus I tried to abstain from some useful new methods, which still not krossbrauzerna (it seems Element.closest(), for which, however, is polifit).

By search of ready solutions of some problems I have come across quite big pieces of code with cycles which were difficult to be considered as compact replacement. Therefore I have created initially small set of small functions which it would be desirable to offer for discussion. The matter is that I am not the professional programmer, rather curious user, and there was no strong wish to invent the ugly bicycle. Therefore if to you any more compact and graceful polifila which can be used in small bukmarkleta are known, please, share. If there are ideas how to improve these functions, too will be grateful for councils.

So far their of everything six, for those specific opportunities XPath, which was not enough for me. Functions are not really convenient in use, for them it would be good to implement cheyning (possibility of call chains), but as far as I heard, to expand DOM it is unsafe therefore to add them in Element.prototype I was not solved.

1. Replacement for /following-sibling::subject[predicate]

Let's tell, we have tree of elements:

<div>
    ...
    <p class='foo' id='point-of-view'></p>
    <p class='bar'></p>
    ...
    <p class='target'></p>
    ...
</div>


And we need to reach from the first p to it is not known what next p with the necessary class. It is possible to organize cycle with checks of all neighbors. And it is possible to make everything in conditional one contact. We create function:

function findNextSibling(startNode, endSelector) {
	return Array.prototype.slice.call(document.querySelectorAll(endSelector))
			.filter(function(el){
				return startNode.parentNode === el.parentNode &&
					   startNode.compareDocumentPosition(el) &Node.DOCUMENT_POSITION_FOLLOWING;
			}).shift();
}


And then we can cause it here so:

var from = document.querySelector('#point-of-view');
var to   = findNextSibling(from, 'p.target')


Perhaps, it not the best solution from the point of view of high-speed performance and the consumed resources (creation and bypass of big temporary collections and arrays), but from the point of view of compactness and convenience, seems to me, tolerantly. Especially as bukmarkleta are often applied to small single actions for which the saving of time and resources is not so critical.

2. Replacement for /preceding-sibling::subject[predicate]

The same upside-down (also we will already return the last array cell, it the closest of previous):

<div>
    ...
    <p class='target'></p>
    <p class='bar'></p>
    ...
    <p class='foo' id='point-of-view'></p>
    ...
</div>


function findPrevSibling(startNode, endSelector) {
	return Array.prototype.slice.call(document.querySelectorAll(endSelector))
			.filter(function(el){
				return startNode.parentNode === el.parentNode &&
					   startNode.compareDocumentPosition(el) &Node.DOCUMENT_POSITION_PRECEDING;
			}).pop();
}


var from = document.querySelector('#point-of-view');
var to   = findPrevSibling(from, 'p.target')


3. Replacement for /following::subject[predicate]

This task seemingly more difficult previous (not so easily normal ways to receive collection of elements, the subsequent to this element as direct bypass DOM, irrespective of the hierarchy relations), but implementation on our method will be simpler, minus one condition.

<div>
    ...
    <p class='foo' id='point-of-view'></p>
    <p class='bar'></p>
    ...
</div>
<div>
    ...
     <div>
        <p class='target'></p>
    </div>
    ...
</div>


function findNext(startNode, endSelector) {
	return Array.prototype.slice.call(document.querySelectorAll(endSelector))
			.filter(function(el){
				return startNode.compareDocumentPosition(el) &Node.DOCUMENT_POSITION_FOLLOWING;
			}).shift();
}


var from = document.querySelector('#point-of-view');
var to   = findNext(from, 'p.target')


4. Replacement for /preceding::subject[predicate]

In the opposite direction, returning the last array cell of the previous elements:

<div>
    ...
     <div>
        <p class='target'></p>
    </div>
    ...
</div>
<div>
    ...
    <p class='bar'></p>
    <p class='foo' id='point-of-view'></p>
    ...
</div>


function findPrev(startNode, endSelector) {
	return Array.prototype.slice.call(document.querySelectorAll(endSelector))
			.filter(function(el){
				return startNode.compareDocumentPosition(el) &Node.DOCUMENT_POSITION_PRECEDING;
			}).pop();
}


var from = document.querySelector('#point-of-view');
var to   = findPrev(from, 'p.target')


5. Replacement for /ancestor-or-self::subject[predicate]

This axis is often used for finding of the necessary initiator of the event rising from below up and also for other adjustments (for example, it is necessary to reach certain element from value getSelection().focusNode, as this property often corresponds to text node). It would be possible to use mentioned polifily for Element.closest(), but for the sake of uniformity I have added function in style of the previous.

For both cases function will return the same element:

<a href='#target'><code><b id='point-of-view'>ссылка</b></code></a>


<a href='#target' id='point-of-view'>ссылка</a>


function findClosestAncestorOrSelf(startNode, endSelector) {
	return Array.prototype.slice.call(document.querySelectorAll(endSelector))
			.filter(function(el){
				return startNode.compareDocumentPosition(el) &Node.DOCUMENT_POSITION_CONTAINS ||
					   startNode === el;
			}).pop();
}


var from = document.querySelector('#point-of-view');
var to   = findClosestAncestorOrSelf(from, 'a')


6. Replacement for /descendant::subject[node-predicate]

It is the temporary simplified replacement to the future CSS4 selector :has(), which still is not supported to any of browsers, ug.

For example, it is necessary to select the link which contains element code, here such:

<div id='point-of-view'>
    ...
    <a href='#target'>просто ссылка</a>
    ...
    <a href='#target'><code>ссылка на объяснение свойства или метода</code></a>
    ...
<div>


Arguments will increase, but all the same anything difficult:

function findByDescendant(contextNode, subjectSelector, predicateSelector) {
	return Array.prototype.slice.call(contextNode.querySelectorAll(subjectSelector))
			.filter(function(el){
				return el.querySelector(predicateSelector);
			}).shift();
}


var scope  = document.querySelector('#point-of-view');
var target = findByDescendant(scope, 'a', 'code')


If to edit a little this method (to clean the final .shift()), they can receive arrays of the necessary elements, and if in quality contextNode to set document, selection will be made of all document.

That's all. Thanks for the spent time also forgive for possible errors.

This article is a translation of the original post at habrahabr.ru/post/262853/
If you have any questions regarding the material covered in the article above, please, contact the original author of the post.
If you have any complaints about this article or you want this article to be deleted, please, drop an email here: sysmagazine.com@gmail.com.

We believe that the knowledge, which is available at the most popular Russian IT blog habrahabr.ru, should be accessed by everyone, even though it is poorly translated.
Shared knowledge makes the world better.
Best wishes.

comments powered by Disqus