Search a large set of dom elements for string - javascript

I have a question on the best way of searching a large set of DOM elements for a string. The situation is that there is a JSON object with say products & serial numbers. Using this JSON object a list gets build consisting of divs, which can be scrolled through. My initial approach was to store the JSON object in localStorage and search localStorage however on ipad the JSON object seems to be stored incorrectly in localstorage even using JSON.stringify to set it and JSON.parse to retrieve it.
I am running a phonegap app on ipad and would like the user to be able to search the collection of items to find any items matching a certain amount of characters on the serial number.
The divs look similar to this:
<div data-id="XX">
Some Serial Number
</div>
There is an input field that triggers on textChange and searches for the string in the set of divs.
I have tried various ways of searching for the string however on ipad most of them feel sluggish.
All the elements start as visible and I want to hide the ones that are not applicable to the search.
This is what I have tried so far:
using contains:
var txt = $('#searchField').val();
$('.product:not(:contains("'+txt+'"))').hide();
which takes around 400-500ms to process per request
using a selector based on data-serial-number (i added the serialnumber as a data element as well):
$(".product:not([data-serial-number*='" + txt + "'])").hide()
which takes around 400ms per request.
I have also tried using a css class to hide elements instead of using .hide() however this made not much of a noticable difference.
In a tutorial I found an extension to the default selectors which seems to be the fastest approach so far:
$.expr[":"].containsNoCase = function(el, i, m) {
var search = m[3];
if (!search) return false;
return eval("/" + search + "/i").test($(el).text());
};
$('.product:containsNoCase(\'' + txt + '\')').hide();
I guess my question is are there any other methods of trying to achieve this search that might be faster? I have tried using .find() but found it to be sluggish as well.
Thanks in advance for looking at this :)

I would do it like this...
var timeout;
$('#searchField').keyup(function() {
var filter = $(this).val();
if(timeout) {
clearTimeout(timeout);
timeout = null;
}
timeout = setTimeout(function(){
if(filter.length>0) {
$('.product').show();
$('.product:not(:contains("'+filter+'"))').hide();
$('.product:contains("'+filter+'"))').show();
} else { $('.product').show();}
}, 500)
});
This way, it only responds after stopping typing for 50ms....then it searches.
DEMO HERE

Related

How do I extract array data from the object <div class="gwt-Label">Some Data</div>?

How do I extract array data from the object <div class="gwt-Label">Some Data</div>?
Since #BrockAdams provided an excellent answer and solution for the general problem of data extraction from a DIV object, described by class name only (no id) and since the web page is made of 100+ DIV objects, described by the same class name, mainly "gwt-Label"
How do I extract the text from a (dynamic) div, by class name, using a userscript?
I am looking for a solution to limit the output to the console from 100+ lines to just few by modifying the code by #BrockAdams below
waitForKeyElements (".gwt-Label", printNodeText);
function printNodeText (jNode) {
console.log("gwt-Label value: ", jNode.text().trim());
}
Since the output I read in the console is 100+ lines long, but all I need is just a few selected lines by array index.
Do you know how to manipulate jNode to save output to an array first and have only the selected array elements to be reread and send to the console?
I would prefer pseudocode like this:
jNode.text().trim()[0]
jNode.text().trim()[5]
Run as a script in Greasemonkey or Tampermonkey.
And what's more, I need to loop the script over a numerical query string setting dynamic #match URL in the script.
Okay, if you have lots of class gwt-Label elements and, assuming that they are AJAX'd in in separate batches, you can put them into an array with code like this:
var valArry = [];
var ajxFinshTmr = 0;
waitForKeyElements (".gwt-Label", storeValue);
function storeValue (jNode) {
valArry.push (jNode.text ().trim () );
if (ajxFinshTmr) clearTimeout (ajxFinshTmr);
//-- Let all initial AJAX finish, so we know array is complete.
ajxFinshTmr = setTimeout (printFinalArray, 200);
}
function printFinalArray () {
console.log ("The final values are: ", valArry);
}
Note that there are almost certainly more robust/efficient/sensible alternatives, but we need to see more of the true page, and the true goal, to engineer those.
ETA: I see you've just linked to the site. Now describe in detail what you want to do. The possibilities can be quite messy.
it took me days to understand the problem
// ==UserScript==
// #name Important test1
// #namespace bbb
// #include http://srv1.yogh.io/#mine:height:0
// #version 1
// ==/UserScript==
console.log("sdfasg");
window.setTimeout(function() {
var HTMLCollection = document.getElementsByClassName("gwt-Label");
console.log(HTMLCollection);
var elements = HTMLCollection.length;
console.log(elements);
element = HTMLCollection[6];
console.log(element);
text = element.innerHTML;
console.log(text);
textclass= text.innerHTML;
// console.log(textclass);
console.log("15minutes");
}, 15000);
var HTMLCollection = document.getElementsByClassName();
console.log(HTMLCollection);
#BrockAdams was very helpful.
Since the above site loaded at random time duration so GM script one day worked fine, generating HTMLCollection in full but another time generated it empty, making innerHTML to generate undefined value.
Great success came with "353 moreā€¦ ]" HTMLCollection link to Open In Variables View, generating exactly what I tried to accomplish via XPath, generating indexed (numered) list of all DIV objects in my HTMLCollection, to let me select DIV object of interest by known number.
I am still looking for alike solution provided by DOM Parsing and Serialization
[https://w3c.github.io/DOM-Parsing/#][1]
to work for me to append natural number, index to every DOM object of HTML document to let me use it in parallel with XPath generated by Firebug
example
html/body/div[3]/div[3]/div/div[6]/div[2]/div/div/div
followed by
html1/body2/div[3]5or-higher/div[3]/div/div[6]/div[2]/div/div/div
just serializing HTML DOM object, appending unique index to each object to let me call it via HTMLCollection[index] or better HTMLDOMCollection[index]
I am sure such approach has been known and adopted by HTML DOM serializer parser but I don't know how to access it.
thank you all

update html according to json

I have a JSON script which contain live matches. These changes every 5 minutes. The changes could for instance be the keys live_in or score. Beside this matches are also deleted and added to the JSON. I want to keep my html output updated at all time how can i do this the best possible way? So far i've set the updating speed to 5 seconds for testing purposes. I've tried so far to set the divs id to equal to the match_id and thereby update by
$('div#match-date-id-' + match['match_id']).html('test');
However does not seem to update. How can i do this the best possible way? i've created a plnkr which enable you to download it with a json snippet, which can be edited in order to check.
plnkr.co/edit/eQCShhW01OG5jU4VLx04?p=preview
My bad earlier on :-) Now I've done more thorough testing and updated the plunker code. What I found in the test: the filter was trying to use an undefined value (match.id), also the filter was trying to compare values from different datatypes (String vs. Integer); so it wasn't working as intended:
Original:
match = match.filter(
function(match) {
return match.id > lastId;
});
Corrected code:
match = match.filter(
function(match) {
return parseInt(match.match_id) > lastId;
});
Above match_id represents String datatype in your JSON file, so I had to convert it to Integer before comparison. More info about JSON data-types. Following the older filter functionality, no match passed the comparison test.
Also for testing purposes I commented out the following line:
if (match.match_id > lastLoadedMatch) {
//lastLoadedMatch = match.match_id
}
because in the second round of updates with the same testing data, no data passes that condition.
Also, I think you need to convert match_id to Integer as well; like following:
var matchId = parseInt(match.match_id)
if ( matchId > lastLoadedMatch) {
lastLoadedMatch = match.match_id
}
I hope you find it useful :-) + note I added a timestamp for those updates; so it's clearly visible that the updates take place now every 5 seconds.

Filter html table containing large data (More than 12K row)

I m working on small project that contains list of Website(Max added 12000 name),
I m asking user to select their interested for that i created piece of code like on js fiddle: fiddle
$(document).ready(function () {
$("#title").keyup(function () {
if ($(this).val() != "") {
$("#doc_list_content tbody>tr").hide();
$("#doc_list_content td:contains-ci('" + $(this).val() + "')").parent("tr").show();
} else {
$("#doc_list_content tbody>tr").show();
}
});
});
$.extend($.expr[":"], {
"contains-ci": function (elem, i, match, array) {
return (elem.textContent || elem.innerText || $(elem).text() || "").toLowerCase().indexOf((match[3] || "").toLowerCase()) >= 0;
}
});
Code is working fine with small amount of data, but when it is for 12000 row it cause browser not responding error.
I tried searching and played lot with my code. i want same type of functionality (on js fiddle) with js or php(littlebit knowledge of it)
My questions are:
1> Is it feasible to use this type of sorting?
2> any js/jquery solution?
3> I m aware of little bit of php as server side scripting, can it help to solve issue.
Thanks.
1 don't use jquery for that... it's slow. use pure javascript
2 create an array containing the table
var tbl=[
[a1,b1,c1,d1],
[a2,b2,c2,d2],
]
3 use the while-- loop as it's the fastest
var l=tbl.length
while(l--){
}
4 use very minimal shorthand or bitwise checks as they are faster.
var l=tbl.length
while(l--){
!tbl[l][0]||(SOMETOGGLEFUNCTION(l))//l is the index of the real table
}
this is the only way i know to check fast 12k records.
if it were me I would basically use some sort of 'paging' to limit the users 'view' of the data to smaller chunks of data, but perhaps still allow for some sort of 'search' feature that searches / filters through ALL the data (not just the visible chunk) - through an array I guess.
in a past project I found that for speed / performance it was best to incrementally add objects to the DOM when the user had first scrolled down to the 'bottom' of the current chunk of data, then I would add a new chunk and remove the previous chunk... sort of how a user turns pages of a book depending on where they are in the book as opposed to laying all the individual pages of a book out in front of them on the floor -- maybe not the best analogy but hopefully you follow my thinking. good luck.

Efficiently replacing strings within an HTML block in Javascript

I am using Javascript(with Mootools) to dynamically build a large page using HTML "template" elements, copying the same template many times to populate the page. Within each template I use string keywords that need to be replaced to create the unique IDs. I'm having serious performance issues however in that it takes multiple seconds to perform all these replacements, especially in IE. The code looks like this:
var fieldTemplate = $$('.fieldTemplate')[0];
var fieldTr = fieldTemplate.clone(true, true);
fieldTr.removeClass('fieldTemplate');
replaceIdsHelper(fieldTr, ':FIELD_NODE_ID:', fieldNodeId);
parentTable.grab(fieldTr);
replaceIdsHelper() is the problem method according to IE9's profiler. I've tried two implementations of this method:
// Retrieve the entire HTML body of the element, replace the string and set the HTML back.
var html = rootElem.get('html').replace(new RegExp(replaceStr, 'g'), id);
rootElem.set('html', html);
and
// Load the child elements and replace just their IDs selectively
rootElem.getElements('*').each(function(elem) {
var elemId = elem.get('id');
if (elemId != null) elemId = elemId.replace(replaceStr, id);
elem.set('id', elemId)
});
However, both of these approaches are extremely slow given how many times this method gets called(about 200...). Everything else runs fine, it's only replacing these IDs which seems to be a major performance bottleneck. Does anyone know if there's a way to do this efficiently, or a reason it might be running so slow? The elements start hidden and aren't grabbed by the DOM until after they're created so there's no redrawing happening.
By the way, the reason I'm building the page this way is to keep the code clean, since we need to be able to create new elements dynamically after loading as well. Doing this from the server side would make things much more complicated.
I'm not 100% sure, but it sounds to me that the problem is with the indexing of the dom tree.
First of all, do you must use ids or can you manage with classes? since you say that the replacement of the id is the main issue.
Also, why do you clone part of the dom tree instead of just inserting a new html?
You can use the substitute method of String (when using MooTools), like so:
var template = '<div id="{ID}" class="{CLASSES}">{CONTENT}</div>';
template.substitute({ID: "id1", CLASSES: "c1 c2", CONTENT: "this is the content" });
you can read more about it here http://mootools.net/docs/core/Types/String#String:substitute
Then, just take that string and put it as html inside a container, let's say:
$("container_id").set("html", template);
I think that it might improve the efficiency since it does not clone and then index it again, but I can't be sure. give it a go and see what happens.
there are some things you can do to optimise it - and what #nizan tomer said is very good, the pseudo templating is a good pattern.
First of all.
var fieldTemplate = $$('.fieldTemplate')[0];
var fieldTr = fieldTemplate.clone(true, true);
you should do this as:
var templateHTML = somenode.getElement(".fieldTemplate").get("html"); // no need to clone it.
the template itself should/can be like suggested, eg:
<td id="{id}">{something}</td>
only read it once, no need to clone it for every item - instead, use the new Element constructor and just set the innerHTML - notice it lacks the <tr> </tr>.
if you have an object with data, eg:
var rows = [{
id: "row1",
something: "hello"
}, {
id: "row2",
something: "there"
}];
Array.each(function(obj, index) {
var newel = new Element("tr", {
html: templateHTML.substitute(obj)
});
// defer the inject so it's non-blocking of the UI thread:
newel.inject.delay(10, newel, parentTable);
// if you need to know when done, use a counter + index
// in a function and fire a ready.
});
alternatively, use document fragments:
Element.implement({
docFragment: function(){
return document.createDocumentFragment();
}
});
(function() {
var fragment = Element.docFragment();
Array.each(function(obj) {
fragment.appendChild(new Element("tr", {
html: templateHTML.substitute(obj)
}));
});
// inject all in one go, single dom access
parentTable.appendChild(fragment);
})();
I did a jsperf test on both of these methods:
http://jsperf.com/inject-vs-fragment-in-mootools
surprising win by chrome by a HUGE margin vs firefox and ie9. also surprising, in firefox individual injects are faster than fragments. perhaps the bottleneck is that it's TRs in a table, which has always been dodgy.
For templating: you can also look at using something like mustache or underscore.js templates.

regex to find match in patterned data range?

I have a table with 3 columns (contact person, sector, phone#) each sector cell would contain a lot of data numbers in range like these: (exact format without quote)
"1003, 1005-29/36/38/49, 4587-99, 3301/21, 50123, 9900-04/10-14/20/30/41-44"
Is there a way to add a filter (textbox) to the webpage for a quick look-up?
Example, if I type "9912" --> it will find the string: "9900-04/10-14/20/30/41-44" and highlight it.
note: I have no control over the table (there is no id/class for that column or entire table), searching the entire webpage will be ok, there is no duplicate info elsewhere.
Can someone point me to a good direction? jQuery?
jQuery will help you with the interaction for the textbox, but for processing the strings and extracting the data (i.e. which integers they match) you will need some heavy processing (using regular expressions, String.indexOf() and some loops). It's probably best to do all this processing on page load and cache the results somewhere, but depending on how many strings there are to process, this could lock up the user interface for a while, but assuming this isn't a problem then code a bit like this would do the job of highlighting the correct results
var dataCache = {};
$(selector to collect all your strings).each(function() {
var string = $(this).html();
var matches = yourParserWhichreturnsAnArrayOfIntegers(string);
for(var i = 0, il = matches.length;i<il;i++) {
if(dataCache[matches[i]]) {
dataCache[matches[i]].push(this);
} else {
dataCache[matches[i]] = [this];
}
}
});
$(yourtextbox).change(function() {
$(selector to collect all your strings).removeClass("highlighted");
var matches = dataCache[$(this).val()];
if (matches) {
for(var i=0,il=matches.length;i<il;i++){
$(matches[i]).addClass("highlighted");
}
}
});
If the table appears in the same location within the DOM everytime then you can still get at the data. If this is the case I think you will have to search in the expanded numbers. A regular expression for searching the compressed number format will probably be very complicated. Don't forget to keep a reference to the original data on the page so you can highlight it if a match is found.

Categories