Using Jquery: Comparing Two Arrays for ANY Match - javascript

I'm looking for a concise way to compare two arrays for any match.
I am using this comparison to apply a particular style to any table cell that has matching content. One array is a static list of content that should be contained in at least one table cell on the page. The other array is being generated by JQuery, and is the text of all table cells.
The reason why I have to compare content to apply style is that the HTML document will semantically change over time, is being generated by different versions of excel (pretty awful looking code), and this script needs to accommodate that. I know that the content I'm looking to apply the style to in this document will never change, so I need to detect all matches for this content to apply styles to them.
So, the code should be something like (in english):
for each table cell, compare cell text to contents of array. If there is any match, apply this css to the table cell.
This is what I have so far (and I know it's wrong):
$(document).ready(function(){
$("a.loader").click(function(event){
event.preventDefault();
var fileToLoad = $(this).attr("href");
var fileType = $(this).text();
var makes = new Array("ACURA","ALFA ROMEO","AMC","ASTON MARTIN","ASUNA","AUDI","BENTLEY","BMW","BRITISH LEYLAND","BUICK","CADILLAC","CHEVROLET","CHRYSLER","CITROEN","COLT","DACIA","DAEWOO","DELOREAN","DODGE","EAGLE","FERRARI","FIAT","FORD","GEO","GMC","HONDA","HUMMER","HYUNDAI","INFINITI","INNOCENTI","ISUZU","JAGUAR","JEEP","KIA","LADA","LAMBORGHINI","LANCIA","LAND ROVER","LEXUS","LINCOLN","LOTUS","M.G.B.","MASERATI","MAYBACH","MAZDA","MERCEDES BENZ","MERCURY","MG","MINI","MITSUBISHI","MORGAN","NISSAN (Datsun)","OLDSMOBILE","PASSPORT","PEUGEOT","PLYMOUTH","PONTIAC","PORSCHE","RANGE ROVER","RENAULT","ROLLS-ROYCE / BENTLEY","SAAB","SATURN","SCION","SHELBY","SKODA","SMART","SUBARU","SUZUKI","TOYOTA","TRIUMPH","VOLKSWAGEN","VOLVO","YUGO","Acura","Alfa Romeo","Amc","Aston Martin","Asuna","Audi","Bentley","Bmw","British Leyland","Buick","Cadillac","Chevrolet","Chrysler","Citroen","Colt","Dacia","Daewoo","Delorean","Dodge","Eagle","Ferrari","Fiat","Ford","Geo","Gmc","Honda","Hummer","Hyundai","Infiniti","Innocenti","Isuzu","Jaguar","Jeep","Kia","Lada","Lamborghini","Lancia","Land Rover","Lexus","Lincoln","Lotus","M.G.B.","Maserati","Maybach","Mazda","Mercedes Benz","Mercury","Mg","Mini","Mitsubishi","Morgan","Nissan (Datsun)","Oldsmobile","Passport","Peugeot","Plymouth","Pontiac","Porsche","Range Rover","Renault","Rolls-Royce / Bentley","Saab","Saturn","Scion","Shelby","Skoda","Smart","Subaru","Suzuki","Toyota","Triumph","Volkswagen","Volvo","Yugo");
$("div#carApp").html("<img src='images/loadingAnimation.gif' alt='LOADING...' />");
$("div#carApp").load(fileToLoad, function(){
$("#carApp style").children().remove();
$('#carApp td').removeAttr('style');
$('#carApp td').removeAttr('class');
$('#carApp table').removeAttr('class');
$('#carApp table').removeAttr('style');
$('#carApp table').removeAttr('width');
$('#carApp tr').removeAttr('style');
$('#carApp tr').removeAttr('class');
$('#carApp col').remove();
$('#carApp table').width('90%');
var content = $("#carApp table td");
jQuery.each(content, function() {
var textValue = $(this).text();
if (jQuery.inArray(textValue, makes)==true)
$(this).css("color","red");
});
});
});
});
Any ideas?

You're checking $.inArray(...) == true. inArray actually returns an integer with the index of the item in the array (otherwise -1.) So you want to check if it is greater than or equal to 0.
Here's how you can change your each loop.
$('#carApp td').each(function () {
var cell = $(this);
if ($.inArray(cell.text(), makes) >= 0) {
cell.addClass('selected-make');
}
});
I use a CSS class instead of the style attribute, because it's better practice to put styling in a CSS file rather than in your JavaScript code. Easier to update that way (especially when you want to apply the same style in multiple places in your code.)
Other points worth noting:
jQuery selections have the each(...) function as well. So you can do $(...).each(...) instead of jQuery.each($(...), ...)
jQuery and $ are the same object as long as you don't have other frameworks that redefine the $ variable. Therefore you can do $.inArray(...) instead of jQuery.inArray(...). It's a matter of taste, though.

Have you had a look at $.grep() ?
Finds the elements of an array which
satisfy a filter function. The
original array is not affected. The
filter function will be passed two
arguments: the current array item and
its index. The filter function must
return 'true' to include the item in
the result array.

An optimization would be to make makes a hash (aka dictionary):
var makes = { "ACURA": 1,"ALFA ROMEO": 1,"AMC": 1, ...};
Then you don't have to iterate makes every time with inArray.
...
var textValue = $(this).text();
if (makes[textValue] == 1)
$(this).css("color","red");
}
...

Related

Accessing a Dynamically Named array in jQuery/javascript

I wish to name an array according to the table row containing the button that was clicked.
I get the table row thus:
var rowNum = $(this).parent().parent().index();
Now, I wish to name the array and access it.
var arrayName = 'arrTR' + rowNum;
window[arrayName] = new Array();
window[arrayName]["First"] = "Bob";
window[arrayName]["Last"] = "Roberts";
window[arrayName]["email"] = "me#there.com";
//The array should be accessible as arrTR__
alert(arrTR1["Last"]);
The alert does not work, so I am doing something wrong.
How should I refactor the code to allow me to update and access the array?
jsFiddle
What you're doing with the dynamically named variables is essentially creating an array of those variables (one for each rowNum), but giving each of those array elements its own individual named variable.
There is a much better way to do this. Instead of generating a series of dynamically named variables, make a single array or an object. Then add an element or property for each of the dynamically named variables you were going to generate.
Your test code could look like this:
var arrTR = [];
var rowNum = 1;
arrTR[rowNum] = {
First: 'Bob',
Last: 'Roberts',
email: 'me#there.com'
};
alert( arrTR[1].Last );
Alternatively, you can do something with $.data as mentioned in Johan's answer. But if you do use plain JavaScript code, use a single array as described here instead of multiple dynamically named variables.
There are several reasons to do it this way. It's cleaner and easier to understand the code, it may be faster when there are large numbers of entries, and you don't have to pollute the global namespace at all. You can define the var arrTR = []; in any scope that's visible to the other code that uses it.
Arrays and objects are made for keeping track of lists of things, so use them.
There is nothing wrong with your code, and the only place it has error is the alert since it is not defined on the first click button
see this fiddle with a little update
if(rowNum === 1)
alert(arrTR1["Last"]);
else if(rowNum === 2)
alert(arrTR2["Last"]);
fiddle
How about something like this?
$('.getinfo').click(function() {
var result = $('table tr:gt(0)').map(function(k, v){
return {
firstName: $(v).find('.fname').val(),
lastName: $(v).find('.lname').val(),
email: $(v).find('.email').val(),
}
}).get();
//update to show how you use the jQuery cache:
//1. set the value (using the body tag in this example):
$('body').data({ result: result });
//2. fetch it somewhere else:
var res = $('body').data('result');
});
Not sure how you want to handle the first row. I skip in in this case. You can access each row by result[index].
As you might have noticed, this saves all rows for each click. If you want to use the clicked row only, use the this pointer.
http://jsfiddle.net/nwW4h/4/

Select tags that starts with "x-" in jQuery

How can I select nodes that begin with a "x-" tag name, here is an hierarchy DOM tree example:
<div>
<x-tab>
<div></div>
<div>
<x-map></x-map>
</div>
</x-tab>
</div>
<x-footer></x-footer>
jQuery does not allow me to query $('x-*'), is there any way that I could achieve this?
The below is just working fine. Though I am not sure about performance as I am using regex.
$('body *').filter(function(){
return /^x-/i.test(this.nodeName);
}).each(function(){
console.log(this.nodeName);
});
Working fiddle
PS: In above sample, I am considering body tag as parent element.
UPDATE :
After checking Mohamed Meligy's post, It seems regex is faster than string manipulation in this condition. and It could become more faster (or same) if we use find. Something like this:
$('body').find('*').filter(function(){
return /^x-/i.test(this.nodeName);
}).each(function(){
console.log(this.nodeName);
});
jsperf test
UPDATE 2:
If you want to search in document then you can do the below which is fastest:
$(Array.prototype.slice.call(document.all)).filter(function () {
return /^x-/i.test(this.nodeName);
}).each(function(){
console.log(this.nodeName);
});
jsperf test
There is no native way to do this, it has worst performance, so, just do it yourself.
Example:
var results = $("div").find("*").filter(function(){
return /^x\-/i.test(this.nodeName);
});
Full example:
http://jsfiddle.net/6b8YY/3/
Notes: (Updated, see comments)
If you are wondering why I use this way for checking tag name, see:
JavaScript: case-insensitive search
and see comments as well.
Also, if you are wondering about the find method instead of adding to selector, since selectors are matched from right not from left, it may be better to separate the selector. I could also do this:
$("*", $("div")). Preferably though instead of just div add an ID or something to it so that parent match is quick.
In the comments you'll find a proof that it's not faster. This applies to very simple documents though I believe, where the cost of creating a jQuery object is higher than the cost of searching all DOM elements. In realistic page sizes though this will not be the case.
Update:
I also really like Teifi's answer. You can do it in one place and then reuse it everywhere. For example, let me mix my way with his:
// In some shared libraries location:
$.extend($.expr[':'], {
x : function(e) {
return /^x\-/i.test(this.nodeName);
}
});
// Then you can use it like:
$(function(){
// One way
var results = $("div").find(":x");
// But even nicer, you can mix with other selectors
// Say you want to get <a> tags directly inside x-* tags inside <section>
var anchors = $("section :x > a");
// Another example to show the power, say using a class name with it:
var highlightedResults = $(":x.highlight");
// Note I made the CSS class right most to be matched first for speed
});
It's the same performance hit, but more convenient API.
It might not be efficient, but consider it as a last option if you do not get any answer.
Try adding a custom attribute to these tags. What i mean is when you add a tag for eg. <x-tag>, add a custom attribute with it and assign it the same value as the tag, so the html looks like <x-tag CustAttr="x-tag">.
Now to get tags starting with x-, you can use the following jQuery code:
$("[CustAttr^=x-]")
and you will get all the tags that start with x-
custom jquery selector
jQuery(function($) {
$.extend($.expr[':'], {
X : function(e) {
return /^x-/i.test(e.tagName);
}
});
});
than, use $(":X") or $("*:X") to select your nodes.
Although this does not answer the question directly it could provide a solution, by "defining" the tags in the selector you can get all of that type?
$('x-tab, x-map, x-footer')
Workaround: if you want this thing more than once, it might be a lot more efficient to add a class based on the tag - which you only do once at the beginning, and then you filter for the tag the trivial way.
What I mean is,
function addTagMarks() {
// call when the document is ready, or when you have new tags
var prefix = "tag--"; // choose a prefix that avoids collision
var newbies = $("*").not("[class^='"+prefix+"']"); // skip what's done already
newbies.each(function() {
var tagName = $(this).prop("tagName").toLowerCase();
$(this).addClass(prefix + tagName);
});
}
After this, you can do a $("[class^='tag--x-']") or the same thing with querySelectorAll and it will be reasonably fast.
See if this works!
function getXNodes() {
var regex = /x-/, i = 0, totalnodes = [];
while (i !== document.all.length) {
if (regex.test(document.all[i].nodeName)) {
totalnodes.push(document.all[i]);
}
i++;
}
return totalnodes;
}
Demo Fiddle
var i=0;
for(i=0; i< document.all.length; i++){
if(document.all[i].nodeName.toLowerCase().indexOf('x-') !== -1){
$(document.all[i].nodeName.toLowerCase()).addClass('test');
}
}
Try this
var test = $('[x-]');
if(test)
alert('eureka!');
Basically jQuery selector works like CSS selector.
Read jQuery selector API here.

replace ids of html elements in jquery clone object

So I have some HTML that I would like to be cloned several times and appended to the document. It's just typical HTML form stuff. But since things like <label for="something"> works based on element IDs, it would be nice if each HTML element within the jquery clone()'d element could have a unique ID, apart from all their cloned counterparts. Just as a guess to how I could do this, I wonder if it would be possible to make the IDs within my initial element all contain some unique string. I could then somehow traverse the element and replace that string with a _1,_2,_3 etc.
I haven't gotten very far, but it seems like something like this should work.
$(document).ready(function(){
var toReplace = "containerUniqueCharacterString1234";
var copy = $('#containerUniqueCharacterString1234').clone(true);
copy[0].style.display = "block"; //so i can reference the first element like this
console.log(copy[0]); //[object HTMLDivElement]
$('body').append(copy);
$.each(copy, function(index, value){
var children = copy[index].childNodes;
console.log(children); //[object NodeList]
var len = children.length;
if (copy[index].childNodes.length > 0){
if (copy[index].childNodes.hasOwnProperty('id')){
//replace the toReplace string with an incremented number here(?)
//annnnd this is where i've gotten in too deep and made this overly complex
}
}
$('#otherResults').append(len);
});
});
http://jsbin.com/ecojub/1/edit
Or perhaps there's a much much simpler way to do this.
Thanks!
If you are copying HTML elements many times for display only, maybe you should consider using a templating engine rather than copying the DOM elements, which are expensive and less maintainable. underscore has a pretty easy to use function, http://documentcloud.github.com/underscore/#template

Is it possible to use a variable to highlight a row in jQuery?

I'm trying to highlight rows in a table using jQuery, but I'm wondering if it's possible to use a variable for the row I want highlighted. This is the code I have now, which is not working.
var rowNumber = 3 //I want to use a loop, but for testing purposes I have it set to 3
$('tr:eq(rowNumber)').addClass('highlight');
Sure, why not. You may pass a variable in :eq() selector:
$("tr:eq(" + rowNumber + ")").addClass("highlight");
or use eq() method instead:
$("tr").eq(rowNumber).addClass("highlight");
$('tr').eq(rowNumber).addClass('highlight');
Should work for you.
Let me first address isolated access (i.e. not taking into consideration optimisation for loops)
Best solution: Use .eq() (fast, nice and short)
You could try something like
$('tr').eq(rowNumber).addClass('highlight');
Explanation: .eq(index) Reduces the set of matched elements to the one at the specified index.
Source: http://api.jquery.com/eq/
Alternative solution: Use the ":eq(index)" selector (unnecessarily slower, more verbose and convoluted)
$("tr:eq("+rowNumber+")").addClass('highlight');
A third solution: (fast, but more verbose than the proposed solution)
$($('tr').get(rowNumber)).addClass('highlight');
How this one works: $('tr').get(rowNumber) gets the (rowNumber+1)th DOM element matching the query selector and then this is wrapped in jQuery goodness using the surrounding $( ).
More info at: http://api.jquery.com/get/
Feel free to experiment with the accompanying jsFiddle:
http://jsfiddle.net/FuLJE/
If you are particularly performance conscious and are indeed are going to iterate through an array you can do this instead:
var trs = $('tr').get(); //get without arguments return the entire array of matched DOM elements
var rowNumber, len = trs.length;
for(rowNumber = 0; rowNumber < len; rowNumber++) {
var $tr = $(trs[rowNumber]);
//various stuff here
$tr.addClass('highlight');
//more stuff here
}
Of course you could also use .each()
$("tr").each(function (rowNumber, tr) {
var $tr = $(tr);
//various stuff here
$tr.addClass('highlight');
//more stuff here
})
Documentation can be found here: http://api.jquery.com/each/
Just to point out the obvious: $("tr").addClass('highlight') would work if adding the highlight class to all tr was all that the OP wanted to do :-)

Speed up selectors and method

Before I dive in to the jQuery/Sizzle source I thought i'd ask here about ways to speed the below method up.
This is a standard "Check All" checkbox scenario. A header cell (<thead><tr><th>) has a checkbox that when checked checks all other checkboxes in it's table's tbody that are in the same column.
This works:
// We want to check/uncheck all columns in a table when the "select all"
// header checkbox is changed (even if the table/rows/checkboxes were
// added after page load).
(function () {
// use this dummy div so we can reattach our table later.
var dummy = $("<div style=display:none />");
// hook it all up!
body.delegate(".js_checkAll", "change", function () {
// cache selectors as much as possible...
var self = $(this),
// use closest() instead of parent() because
// the checkbox may be in a containing element(s)
cell = self.closest("th"),
// Use "cell" here to make the "closest()" call 1 iteration
// shorter. I wonder if "parent().parent()" would be faster
// (and still safe for use if "thead" is omitted)?
table = cell.closest("table"),
isChecked,
index;
// detaching speeds up the checkbox loop below.
// we have to insert the "dummy" div so we know
// where to put the table back after the loop.
table.before(dummy).detach();
index = cell.index();
isChecked = this.checked;
// I'm sure this chain is slow as molasses
table
// get only _this_ table's tbody
.children("tbody")
// get _this_ table's trs
.children()
// get _this_ table's checkboxes in the specified column
.children(":eq(" + index + ") :checkbox")
// finally...
.each(function () {
this.checked = isChecked;
});
// put the table back and detach the dummy for
// later use
dummy.before(table).detach();
});
} ());
However, for 250+ rows, it starts to become slow (at least on my machine). Users may need to have up to 500 rows of data so paging the data isn't the solution (items are already paged # 500/page).
Any ideas how to speed it up some more?
I wouldn't use all those calls to .children() like that. You'd be much better off just using .find() to find the checkboxes, and then check the parents:
table.find('input:checkbox').each(function(_, cb) {
var $cb = $(cb);
if ($cb.parent().index() === index) cb.checked = isChecked;
});
By just calling .find() like that with a tag name ('input'), Sizzle will just use the native getElementsByTagName (if not querySelectorAll) to get the inputs, then filter through those for the checkboxes. I really suspect that'd be faster.
If finding the parent's index gets expensive, you could always precompute that and store it in a .data() element on the parent (or right on the checkbox for that matter).
// I wonder if "parent().parent()" would be faster
// (and still safe for use if "thead" is omitted)?
No. If <thead> is omitted then in HTML a <tbody> element will be automatically added, because in HTML4 both the start-tag and the end-tag are ‘optional’. So in HTML, it would be parent().parent().parent(), but in XHTML-served-as-XML, which doesn't have the nonsense that is optional tags, it would be parent().parent().
It's probably best to stick with closest(). It's clearer, it's not particularly slow and you're only using it once, so it's not critical anyway.
index = cell.index();
Although, again, this is only once per table so not critical, there is a standard DOM property to get the index of a table cell directly, which will be faster than asking jQuery to search and count previous siblings: index= cell[0].cellIndex.
// we have to insert the "dummy" div so we know
// where to put the table back after the loop.
That's a bit ugly. Standard DOM has a better answer to this: remember the element's parentNode and nextSibling (which may be null if this is the last sibling) and when you're done you can parent.insertBefore(table, sibling).
.children("tbody")
.children()
.children(":eq(" + index + ") :checkbox")
.each(function () {
this.checked = isChecked;
});
You should consider using .children().eq(index) rather than hiding that away in a selector. Won't make a big difference, but it's a bit clearer.
In any case, you can save jQuery's selector engine a bunch of work by using some more standard DOM to traverse the table:
$.each(table[0].tBodies[0].rows, function() {
$(this.cells[index]).find('input[type=checkbox]').each(function() {
this.checked = isChecked;
});
});
Selector queries can be fast, when they're operating against the document and using only standard CSS selectors. In this case jQuery can pass the work onto the browser's fast document.querySelectorAll method. But scoped selectors (find and the second argument to $()) can't be optimised due to a disagreement between jQuery and Selectors-API over what they mean, and non-standard Sizzle selectors like :eq and :checkbox will just get rejected. So this:
$('#tableid>tbody>tr>td:nth-child('+(index+1)+') input[type=checkbox]')
could actually be faster, on modern browsers with querySelectorAll!

Categories