Better way to create a jQuery collection - javascript

I wrote a plugin like so to grab a subset of a collection:
jQuery.range = function(start, end, includingTheLast) {
var ret = $([]), i = 0;
while (!this.eq(i).is(start) && i < this.length)
i++;
for (; i < this.length && !this.eq(i).is(end); i++) {
ret = ret.add(this[i]); // we can do better than this
}
if (includingTheLast) ret = ret.add(this[i]); // we can do better than this
return this.pushStack(ret, 'range');
}
It's used like this:
$('a').range(':eq(2)', '#stop')...
Looking at ret = ret.add(this[i]) seems to be very slow, is this a smart way to do it? Should I build an array then turn it into a jQuery object? Is this micro-optimizing?

The jQuery constructor also accepts an array of DOM elements and wraps them in a jQuery object. So, if you are opposed to using .add, you could push them all to an array (as dom elements) and then wrap the whole thing at once.
I have not run a perf test to see what would be faster.
reference: http://api.jquery.com/jQuery/

Related

Get all files for all .uploadedFiles

Im looking for a javascript/jquery (doesn't matter which way) to collect all the files i've uploaded.
I have the following code, where .afbeelding is the class for a couple of file input fields
var geuploadeAfbeeldingen = $('.afbeeldingen').files;
for (var i = 0; i < geuploadeAfbeeldingen.length; i++) {
}
This somehow doesnt seem to work. When i try document.getElementsByClassName it also doesn't work. The funny thing however is, that document.getElementById seem to work on one input field
Any ideas?
This should do what you want
var files = [],
geuploadeAfbeeldingen = $('.afbeeldingen').each(function(){
for (var i = 0; i < this.files.length; i++){
files.push(this.files[i]);
}
});
You end up with an array (files) that holds each file you have selected through the input elements..
Demo at http://jsfiddle.net/gaby/GJW7Y/1/
If you only want the filenames then change
files.push(this.files[i]);
with
files.push(this.files[i].name);
Try this way :
var geuploadeAfbeeldingen = $('.afbeeldingen');
for (var i = 0; i < geuploadeAfbeeldingen.length; i++) {
alert(geuploadeAfbeeldingen[i].files[0].name);
}
This may help you.
Edit :
$('.afbeeldingen').files is not work and document.getElementById().files is worked because first one return JQuery object( array of objects) and second one return DOM object.The jQuery object (created by the $ method) is a wrapper around a DOM element or a set of DOM elements. The normal properties and methods are not available with JQuery object.
You need to loop through each input element and return the files property.
Something like this is probably the shortest way, using map to iterate through an array:
var geuploadeAfbeeldingen = $('.afbeeldingen').map(function(k, v) { return v.files[0]; }).get();

Javascript efficient search array for value with jQuery

There's a gap in my JavaScript knowledge here. I want to search an array of objects values for a particular value and return it.
For the year I have been writing JavaScript, I have been implementing it like this:
var itemClicked = (function(){
var retval;
//Note self.inventory.itemsArray is an array of JS objects
$(self.inventory.itemsArray).each(function(i){
if(parseInt(this.id) === parseInt(idOfItem)){
retval = this;
return false;
}
});
return retval;
})();
It works, but I'm sure as anything there is a more elegant way. Tell me please!
EDIT - Solution
Thanks to #gdoron with his answer below.
var myVar = $(self.owner.itemsArray).filter(function(){
return parseInt(this.id) == parseInt(recItemID);
}).get(0);
Note: .get(0) was added at the end because myVar is wrapped as a jQuery object.
The native jQuery function for this is filter:
$(data).filter(function(){
return this.id == "foo";
});
It's shorter than code you have and more important a lot more readable.
About efficiency, it will iterate all the elements in the set to find as much as possible matches, but I hardly believe it will be the bottle neck of your application, don't focus on micro-optimisations.
I suggest you read Eric Lipper blog about Which is faster.
You can also use grep as suggested by #Mattias Buelens:
$.grep(data, function(ele){
retun ele.id == "foo";
});
Just another option using jQuery's $.grep( ) function
var arr = $.grep( self.inventory.itemsArray, function ( n ) {
return n.id == idOfItem;
});
The above returns an array of matching array elements. If you just want the first it is easy enough to return arr[0] if it exists.
Although I'm unsure what the function is actually supposed to do (due to the external contexts' variables), the following should be more efficient cycle-wise
var itemClicked = (function(){
var i, array = self.inventory.itemsArray, length = array.length;
for( i=0; i < length; i++) {
if(parseInt(array[i].id) === parseInt(idOfItem)){
return array[i];
}
}
return undefined;
})();
It's an array of Javascript objects
Then do not use jQuery at all. At least, use $.each instead of building a wrapper object around the array. Still, a simple for-loop is much shorter and more performant:
var itemClicked = (function(idnum) {
var arr = self.inventory.itemsArray;
for (var i=0, l=arr.length; i<l; i++)
if (parseInt(arr[i].id, 10) === idnum)
return arr[i];
})( parseInt(idOfItem, 10) );
You might as well think of storing the id properties as numbers right away, so you don't need to convert it each time.

jQuery appending an array of elements

For the purpose of this question lets say we need to append() 1000 objects to the body element.
You could go about it like this:
for(x = 0; x < 1000; x++) {
var element = $('<div>'+x+'</div>');
$('body').append(element);
}
This works, however it seems inefficient to me as AFAIK this will cause 1000 document reflows. A better solution would be:
var elements = [];
for(x = 0; x < 1000; x++) {
var element = $('<div>'+x+'</div>');
elements.push(element);
}
$('body').append(elements);
However this is not an ideal world and this throws an error Could not convert JavaScript argument arg 0 [nsIDOMDocumentFragment.appendChild]. I understand that append() can't handle arrays.
How would I using jQuery (I know about the DocumentFragment node, but assume I need to use other jQuery functions on the element such as .css()) add a bunch of objects to the DOM at once to improve performance?
You could use an empty jQuery object instead of an array:
var elements = $();
for(x = 0; x < 1000; x++) {
elements = elements.add('<div>'+x+'</div>');
// or
// var element = $('<div>'+x+'</div>');
// elements = elements.add(element);
}
$('body').append(elements);
This might be useful if you want to do stuff with newly generated element inside the loop. But note that this will create a huge internal stack of elements (inside the jQuery object).
It seems though that your code works perfectly fine with jQuery 1.8.
You could just call
$('body').append(elements.join(''));
Or you can just create a large string in the first place.
var elements = '';
for(x = 0; x < 1000; x++) {
elements = elements + '<div>'+x+'</div>';
}
$(document.body).append(elements);
Like you mentioned, probably the most "correct" way is the usage of a DocFrag. This could look like
var elements = document.createDocumentFragment(),
newDiv;
for(x = 0; x < 1000; x++) {
newDiv = document.createElement('div');
newDiv.textContent = x;
elements.append( newDiv );
}
$(document.body).append(elements);
.textContent is not supported by IE<9 and would need an conditional check to use .innerText or .text instead.
Upgrade to jQuery 1.8, this works as intended:
​$('body')​.append([
'<b>1</b>',
'<i>2</i>'
])​;​
Since $.fn.append takes a variable number of elements we can use apply to pass the array as arguments to it:
el.append.apply(el, myArray);
This works if you have an array of jQuery objects. According to the spec though you can append an array of elements if you have the DOM elements. If you have an array of html strings you can just .join('') them and append them all at once.
A slight change to your second approach:
var elements = [],
newDiv;
for (x = 0; x < 1000; x++) {
newDiv = $('<div/>').text(x);
elements.push(newDiv);
}
$('body').append(elements);
$.append() certainly can append an array: http://api.jquery.com/append/
.append(content) | content: One or more additional DOM elements, arrays of elements, HTML strings, or jQuery objects to insert at the end of each element in the set of matched elements.
Sometimes, jQuery isn't the best solution. If you have a lot of elements to append to the DOM, documentFragment is a viable solution:
var fragment = document.createDocumentFragment();
for(var i = 0; i < 1000; i++) {
fragment.appendChild(document.createElement('div'));
}
document.getElementsByTagName('body')[0].appendChild(fragment);
If you're going for raw performance then I would suggest pure JS, though some would argue that your development performance is more important than your site's/program performance.
Check this link for benchmarks and a showcase of different DOM insertion techniques.
edit:
As a curiosity, documentFragment proves to be one of the slowest methods.
I would use native Javascript, normally much faster:
var el = document.getElementById('the_container_id');
var aux;
for(x = 0; x < 1000; x++) {
aux = document.createElement('div');
aux.innerHTML = x;
el.appendChild(aux);
}
EDIT:
There you go a jsfiddle with different options implemented. The #jackwander's solution is, clearly, the most effective one.
I know, the question is old, but maybe it helps others.
Or simple use ECMA6 spread operator:
$('body').append(...elements);

Custom for-loop helper for EmberJS/HandlebarsJS

A small two hours ago I started: Nested HandlebarsJS #each helpers with EmberJS not working
Shortly after I figured an acceptable temporary solution myself, question is still unaswered. My problems didn't stop there though.
I am now trying to make a custom helper which will loop through an array of objects, but exclude the first index - pretty much: for(i = 1; i < length; i++) {}. I've read on websites you have to get the length of your context and pass it to options - considering your function looks like: forLoop(context, options).
However, context is a string rather than an actual object. When you do a .length, you will get the length of the string, rather than the size of the array. When I pass that to options, nothing happens - not too mention browser freezes.
I then first tried to do a getPath before passing it to options, this returns an empty string.
What am I supposed to do instead, I made the for-loop code before for just HandlebarsJS and that worked, but EmberJS doesn't seem to take it, why?
EDIT: I pretty much also followed: http://handlebarsjs.com/block_helpers.html -> Simple Iterators
I solved this myself after trying for a long time.
The HandlebarsJS method (as described on the site) is no longer valid for EmberJS, it's now as follows:
function forLoop(context, options) {
var object = Ember.getPath(options.contexts[0], context);
var startIndex = options.hash.start || 0;
for(i = startIndex; i < object.length; i++) {
options(object[i]);
}
}
Heck, you could even extend the for-loop to include an index-value!
function forLoop(context, options) {
var object = Ember.getPath(options.contexts[0], context);
var startIndex = options.hash.start || 0;
for(i = startIndex; i < object.length; i++) {
object[i].index = i;
options(object[i]);
}
}
This is a working for-loop with variable start index. You use it in your templates like so:
{{#for anArray start=1}}
<p>Item #{{unbound index}}</p>
{{/for}}
Here is how I did it (and it works !!!)
First,
i had in my model a 'preview' property/function, that just return the arrayController in an array :
objectToLoop = Ember.Object.extend({
...
arrayController: [],
preview: function() {
return this.get('arrayController').toArray();
}.property('arrayController.#each'),
...
});
Then, I add a new Handlebars helper :
Handlebars.registerHelper("for", function forLoop(arrayToLoop, options) {
var data = Ember.Handlebars.get(this, arrayToLoop, options.fn);
if (data.length == 0) {
return 'Chargement...';
}
filtered = data.slice(options.hash.start || 0, options.hash.end || data.length);
var ret = "";
for(var i=0; i< filtered.length; i++) {
ret = ret + options.fn(filtered[i]);
}
return ret;
});
And thanks to all this magic, I can then call it in my view :
<script type="text/x-handlebars">
<ul>
{{#bind objectToLoop.preview}}
{{#for this end=4}}
<li>{{{someProperty}}}</li>
{{/for}}
{{/bind}}
</ul>
</script>
And that's it.
I know it is not optimal, so whoever have an idea on how to improve it, PLEASE, make me know :)

Better way to see if an array contains an object?

I have an array of items (terms), which will be put as <option> tags in a <select>. If any of these items are in another array (termsAlreadyTaking), they should be removed first. Here is how I have done it:
// If the user has a term like "Fall 2010" already selected, we don't need that in the list of terms to add.
for (var i = 0; i < terms.length; i++)
{
for (var iAlreadyTaking = 0; iAlreadyTaking < termsAlreadyTaking.length; iAlreadyTaking++)
{
if (terms[i]['pk'] == termsAlreadyTaking[iAlreadyTaking]['pk'])
{
terms.splice(i, 1); // remove terms[i] without leaving a hole in the array
continue;
}
}
}
Is there a better way to do this? It feels a bit clumsy.
I'm using jQuery, if it makes a difference.
UPDATE Based on #Matthew Flaschen's answer:
// If the user has a term like "Fall 2010" already selected, we don't need that in the list of terms to add.
var options_for_selector = $.grep(all_possible_choices, function(elem)
{
var already_chosen = false;
$.each(response_chosen_items, function(index, chosen_elem)
{
if (chosen_elem['pk'] == elem['pk'])
{
already_chosen = true;
return;
}
});
return ! already_chosen;
});
The reason it gets a bit more verbose in the middle is that $.inArray() is returning false, because the duplicates I'm looking for don't strictly equal one another in the == sense. However, all their values are the same. Can I make this more concise?
var terms = $.grep(terms, function(el)
{
return $.inArray(el, termsAlreadyTaking) == -1;
});
This still has m * n performance (m and n are the lengths of the arrays), but it shouldn't be a big deal as long as they're relatively small. To get m + n, you could use a hashtable
Note that ECMAScript provides the similar Array.filter and Array.indexOf. However, they're not implemented in all browsers yet, so you would have to use the MDC implementations as a fallback. Since you're using jQuery, grep and inArray (which uses native indexOf when available) are easier.
EDIT:
You could do:
var response_chosen_pk = $.map(response_chosen_items, function(elem)
{
return elem.pk;
});
var options_for_selector = $.grep(all_possible_choices, function(elem)
{
return $.inArray(elem.pk, response_chosen_pk) == -1;
});
http://github.com/danstocker/jorder
Create a jOrder table on termsAlreadyTaking, and index it with pk.
var table = jOrder(termsAlreadyTaking)
.index('pk', ['pk']);
Then you can search a lot faster:
...
if ([] == table.where([{ pk: terms[i].pk }]))
{
...
}
...

Categories

Resources