jQuery: Order Inside .contents() - javascript

If I do this:
function getAllTextNodes(root) {
root = $(root || "body");
return root.find("*:not(iframe)").contents().filter(function() {
return this.nodeType === 3 && //Node.TEXT_NODE = 3
$.trim(this.nodeValue) !== "";
});
}
getAllTextNodes($.parseHTML("<div><div>a<div>sub</div>b</div></div>"))
the result is an array with "a", "b" and "sub". So it seems that they traverse the structure and when they reach an element they work on that element entirely before they continue with the nested elements.
While this may make sense (or it doesn't matter in some cases) it causes some troubles on my end, because I need a logic that returns the elements in the exact same order they appear in the DOM-tree, i.e. I'd be happy to see "a", "sub" and "b" being returned.
Is that something that jQuery built on purpose? Can I change the order somehow? Or is this a bug?

Is that something that jQuery built on purpose? Or is this a bug?
I don't think it's done on purpose, but given that selector APIs and even most modification methods do have their results in DOM order, it might be considered a bug. From what you show, it looks like contents is implemented with a simple flatMap(el => el.childNodes).
Can I change the order somehow?
Yes, you can use jQuery.uniqueSort() on the jQuery object, which uses Node.compareDocumentPosition internally:
return $.uniqueSort(root.find("*:not(iframe)").contents().filter(function() {
return this.nodeType === 3 && $.trim(this.nodeValue) !== "";
}));
However, jQuery isn't great with text nodes anyway. It might be simpler to use a native DOM API here, such as NodeIterator:
const it = document.createNodeIterator(root[0], NodeFilter.SHOW_TEXT, node => node.data.trim() != ""),
res = [];
for (let node; node = it.nextNode(); )
res.push(node);
return res;

Related

Data structure to store unique values in javascript

What is the best data type to store unique values only?
an array can have duplicates
[one, one, two]
and an object (? maybe wrong terminology) have unnecessary values for my current case
{one: something, two: something, three: something}
Shortly, I need something like this:
{one, two, three}
I am not sure what it is called, or if it does exist in js. Needing some enlightment.
You mean a structure called Set, and in the current version of ECMAScript there's no such structure. It will be standarized in the next version, however it's available now in some browsers.
You can emulate set using object, but as you said that also involves unnecessary values. If you don't want to care about them, you can use a library that emulates Set, like http://collectionsjs.com/
The most common way to solve this is to use an array, and just check if it already has the value you want to insert, that way it contains only unique values.
if ( arr.indexOf(value) == -1 ) arr.push(value);
In addition to obvious ways you can always create you own data structure on top of array if you need some more advanced functionality. For example:
function UArray(val) {
this._values = [];
if (typeof val !== 'undefined') {
this.set(val);
}
}
UArray.prototype.set = function(values) {
this._values = this._values.concat(values).filter(function(el, i, arr) {
return arr.indexOf(el) === i;
});
};
UArray.prototype.get = function() {
return this._values;
}
var uarr = new UArray();
uarr.set(['one', 'one', 'two']);
alert( uarr.get() );
uarr.set('two');
uarr.set(['three', 'one']);
alert( uarr.get() );
Such a custom data structure could be extended with additional necessary methods, i.e.:
remove to remove specific item
find to find item's index or -1 if not found,
etc.

Cross-browser attribute selection in plain Javascript

When I don't have access to JQuery for whatever reason, I usually perform attribute selection manually using element.hasAttribute or element.getAttribute.
However, there seems to be some complication here because older browsers (IE <= 8) don't support hasAttribute. So if you want to check whether an element has a certain attribute, you need to use getAttribute and check the return value.
if ((element.hasAttribute && element.hasAttribute("foo"))
|| (element.getAttribute("foo") != null))
{
....
}
This makes me think you may as well just forget about using hasAttribute at all, and just always use getAttribute. The problem is that I can't find consistent documentation on the return value of getAttribute. In practice, it returns null on most browsers if the attribute doesn't exist - but it also might return empty string, because that is what it's supposed to do according to the DOM 3 specification.
Unfortunately, returning an empty string leaves us with no way to disambiguate between:
<div data-my-attribute = ""></div>
and
<div></div>
So, in practice - it seems the most portable thing to do is to first check if the browser supports hasAttribute, and if not, use getAttribute - since IE 6-8 implement getAttribute so that it returns null (instead of empty string) if the attribute doesn't exist.
Is this really the best way to go about doing this? Or is there a better way to write cross-browser attribute detection in plain Javascript?
The following works well in IE6-10 (tested it in IETester), Firefox, Chrome and Safari:
function hasAttrib(element, attributeName) {
return (typeof element.attributes[attributeName] != 'undefined');
}
Here are jsfiddle and its standalone result page (for testing in older browsers).
This will probably need some testing, but would not the length of the String describing the Element change if you tried to setAttribute an attribute it doesn't already have vs remain the same if you tried for one it does have?
var hasAttribute = (function () {
if (HTMLElement && HTMLElement.prototype
&& HTMLElement.prototype.hasAttribute)
return function (node, attrib) { // use if available
return node.hasAttribute(attrib);
};
return function (node, attrib) {
var d = document.createElement('div'), // node for innerHTML
e = node.cloneNode(false), // id attribute will be lost here
i;
if (attrib.toLowerCase() === 'id') return !!node.getAttribute('id');
d.appendChild(e);
i = d.innerHTML.length; // get original length
e.setAttribute(attrib, e.getAttribute(attrib)); // set attrib to test
return d.innerHTML.length === i; // see if length changed
};
}());

How i can simplify my if condition in jQuery

I'm looking for a way to do the following:
$("#a" || "#b").val() === ""
as opposed to:
$("#a").val() === "" || $("#b").val() === ""
Any ideas?
Thanks in advance!
For two elements, I believe your example is about as short as you can make it and its meaning is clear. However, if you wish to repeat such logic or evaluate more elements, you might be able to improve upon it by creating a simple function to evaluate if any items in a set match a condition.
Extending jQuery
$.fn.any = function (evaluator) {
var items = $(this);
for (var i = 0; i < items.length; i++) {
if (evaluator(items[i]) === true) {
return true;
}
}
return false;
};
Demo: http://jsfiddle.net/xE76y/1/
This is similar to the Any() method implemented in the .Net LINQ library* (and I'm sure is implemented in other libraries, especially those geared towards functional programming). In c#, you would call such a method:
enumerable.Any( o => o.Value == "" );
JavaScript's syntax (sadly) isn't as concise; you end up with something like:
array.any( function(o){ return o.value === ""; } );
So far, this hasn't saved you anything. However, if you want to iterate over a large number of elements, it becomes much more elegant.
// there could be zero inputs or 100 inputs; it doesn't matter
var result = $("input").any(function (o) {
return o.value === "";
});
Native Solution
Note that we aren't relying on jQuery in our any() method. You could also consider a native JavaScript solution such as the Array.some() method.
some() executes the callback function once for each element present in
the array until it finds one where callback returns a true value. If
such an element is found, some immediately returns true.
Demo: http://jsfiddle.net/xE76y/2/
var result = jQuery.makeArray($("input")).some(function (o) {
return o.value === "";
});
Since this is an array method, it only works on an array. This unfortunately means that document.getElementsByTagName("input").some(...) will not work since getElementsByTagName() returns a NodeList.
Of course, you could push whatever you wanted into an array and call some() on that array. The call to jQuery.makeArray() in the example is just for convenience.
Abstracting the Evaluation Functions
Demo: http://jsfiddle.net/xE76y/3/
Perhaps the evaluation functions (such as testing for an empty string) will be reused. These can be abstracted further.
// ideally, this should NOT be left in global scope
function isEmpty(input) {
return input.value === "";
}
// the check now fits nicely in one line.
if ($("input").any(isEmpty)) {
alert("At least one input is empty.");
}
The resulting method calls are quite clean: $("#a, #b").any(isEmpty) and $("input").any(isEmpty)
* Also worth noting that LINQ has been recreated for JavaScript.
Try like this instead:
if ($('#a,#b').is(':empty'))
{
alert("Either a or b is Empty!");
}
Try my demo
Edit:
If it is an input type like a textbox then it would be a little bit bulky but will achieve the same effect:
if ($.inArray("",[ $("#a").val(), $("#b").val() ])>=0)
{
alert("Either a or b is Empty!");
}
See another Demo
If you want to avoid duplication of the empty string "", you could do this:
if ($.inArray([ $("#a").val(), $("#b").val() ], ""))
Or if you only want to select once with jQuery:
if ($.inArray($("#a, #b").map(function() { return this.value; }), ""))
But I wouldn't use either of these myself. They are arguably both less efficient, more contrived, and certainly less readable than the "easy" way!
I'm not an expert in javaScript, but have you cross checked with :
http://api.jquery.com/multiple-selector/
jQuery selector regular expressions
Also, one way would be using the .each function as in
jQuery Multiple ID selectors

Is there a DOM API for querying comment nodes?

I have a document with a debugging comment in it that looks like this:
<!--SERVER_TRACE {...}-->
Is there a way to query the DOM to access this node? I am looking for a vanilla JavaScript solution, without the aid of any libraries.
My first thought was to depth first search the DOM and compare nodes found against the node type value for comments, Node.COMMENT_NODE. Is there an easier way?
There's the TreeWalker APIs:
var tw = document.createTreeWalker(document, NodeFilter.SHOW_COMMENT, null, null),
comment;
while (comment = tw.nextNode()) {
// ...
}
This isn't supported by IE8 and lower.
T.J. in the comments provided a link to the specs. I kind of always use just TreeWalkers, but in your case a NodeIterator is fine too.
The nodeType core property allows you to differentiate between types of nodes. In this particular case, 8 represents comments. As they have no selector, you'll need to loop through their parent to get them (which sucks, I know). The following code filters them out for you:
$("*").contents().filter(function(){
return this.nodeType == Node.COMMENT_NODE;
})
And my own jQuery-less version, because some people don't have it:
function getAllComments() {
var t = [],
recurse = function (elem) {
if (elem.nodeType == Node.COMMENT_NODE) {
t.push(elem);
};
if (elem.childNodes && elem.childNodes.length) {
for (var i = 0; i < elem.childNodes.length; i++) {
recurse(elem.childNodes[i]);
};
};
};
recurse(document.getElementsByTagName("html")[0]);
return t;
};
If you'd like to search on a specific node, re-bind the document.getElementsByTagName call to a variable of your choosing.
Edit: fiddle to demonstrate the use, done by Jason Sperske!

Array find, natively or through jQuery?

Would you do this sort of thing?
var getBoard1 = function(id) {
return $.grep(me.boards, function (board) {
return board.Id == id;
});
};
Or this sort of thing?
var getBoard2 = function(id) {
for (var i = 0; i < me.boards.length; i++) {
var board = me.boards[i];
if (board.Id == id)
return board;
}
return null;
};
And why, in the context of correctness, readability and performance would you prefer that way? If you would rather do it in a third way, please share.
This is what the grep function looks like (jQuery v1.8.2):
grep: function( elems, callback, inv ) {
var retVal,
ret = [],
i = 0,
length = elems.length;
inv = !!inv;
// Go through the array, only saving the items
// that pass the validator function
for ( ; i < length; i++ ) {
retVal = !!callback( elems[ i ], i );
if ( inv !== retVal ) {
ret.push( elems[ i ] );
}
}
return ret;
}
Essentially, you're doing the same so it wouldn't be much of a difference when it comes to performance. When I look at the jQuery code, they always return an array, where you return null. If you're using jQuery already, I would go for the jQuery version since it's better readable, otherwise go with native code.
* -- EDIT -- *
When looking at the code, this made me realize it does make a difference. Your code already returns (and finishes the loop) when it found the first item (expecting only one single result), where jQuery loops through all the items. So, if you expect only one result, your version would be faster.
jQuery provides convenience methods, in the background it will probably do something similar. If you are already using jQuery then you can take advantage of this, I would however not include jQuery just for one bit of code like this. It entirely depends on your situation.
As for performance, try it, see what your results are.
If you already have a dependency on jQuery then do it the first way because it's shorter and reads easier. In the very unlikely case that this function is your bottleneck and performance is not acceptable then you can start thinking about alternate implementations.
If you don't already depend on jQuery then the second version is preferable because the tradeoff (including jQuery vs writing a few more lines of code) is not worth it.
I would use the native Array.filter() which is probably the fastest if you don't care for Browsersupport (IE8- will die on this).
a.filter(function(e){return e.id == id});
This returns, same like jQuerys grep an array where you would have to fetch the first value.

Categories