So i have two very large multidimesional arrays(4000+). I get the first array as a response from the server where, i have to create dom nodes for each of these array elements. Once this process is finished i have to send another request where i will get another list of elements which will be a subset of the first list, based on the second list i have to modify some elements in the first list (and reflect these changes in the DOM as well). This process takes a very long time to finish, is there any way to accomplish this without two for loops? Or perhaps a faster comparison?
Scenario
The real world example would be as follows, consider a group of people
in a particular area (arr1). In DOM this would be represented as
CheckBox - Name Now consider a group of people who have been administered with a
particular vaccine (arr2), Now arr2 has the list of elements for which
the checkbox should be checked. The whole list(arr1's dom representation) has to be shown at all
costs.
Arrays are of the type
[ ["index", "name", "age"],............. ["4000-index", "4000-name", "4000-age"]]
Here is a pseudo code..
//First request, get the response (resp) and Create DOM elements accordingly
for(var i=0, iLen=resp.length; i<iLen; i++)
{
// Checkbox and <span id='entry-"+resp[i][0]+"'>resp[i][1]</span>
}
// At a later stage in the code...
//Request server for the second list get the response (resp)
arr2 = resp // Second Array
// Walk through the dom, get the list of span elements and store their ids in an array arr1
for(var i=0, iLen=arr1.length; i<iLen; i++)
{
for(var j=0, jLen= arr2.length; j<jLen; j++)
{
if(+arr2[j][0] === +arr1[i][0])
{
//checkbox attr = 'checked'
}
}
}
If you send in the second set of data that your receive as an object with the following structure, you could get some really good performance boost.
var subset = {
anID:{
bcg: true,
someOtherProp: false,
//and so on
}
}
Then all you need to modify your DOM elements -
for (var id in subset){
//do whatever needs to be done
}
Related
I am in a situation where I haven't found a selector or a selector function that quite does what I would like it to do.
Therefore I am trying to filter the list to contain only the items I would like it to.
I have a selector
var html = $(".foo .foobar")
This returns what I wanted it to.
Then I have a for loop that loops through those selected items and identifies the ones I want to keep in that list.
However, I need to keep the modified list the same type as a selector so that I can perform jquery actions to them later.
how do I create a copy of the 'html' variable (or a filtered original) but with only the desired rows that were found in the function (Keeping it still in a state as if it was a selector itself)?
Later I have an 'each' loop that begins like this:
html.each(function(i, el) {
$(this).replaceWith(tempArr[i]);
I am trying to achieve a result where 'html.each' has 'html' as the modified list previously selected.
Thanks.
// Update
var htmlTemp;
for (var primaryCounter = 0, secondryCounter = 0; primaryCounter < htmlTemp.length; primaryCounter++) {
if (firstFound) {
secondryCounter++;
if (secondryCounter % columnCount === 0) {
html.push(htmlTemp[primaryCounter]);
}
} else {
if (primaryCounter === currI) {
html.push(htmlTemp[primaryCounter]);
firstFound = true;
}
}
}
Above is the function including the logic that I wanted to use (Which isn't going to run). Is there a way with 'filter' possibly where I can call this function and instead of 'push()' just include at these indexes found? Thanks.
Assuming html as an array, you can use html.filter(callbackFunc) to get a new list every time.
Check this https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
I have the following scenario:
A JSON object array gets fetched with angular ajax and displayed as list with ng-repeat.
The list gets sorted by object property "index", which is set as default input value of the respective list item.
The user can change the input values.
On press of a button, the list gets redrawn, sorted by the updated input values.
Now there should be a counter, ++ whenever a list item changes position.
I so far managed to count all list items with updated values.
How do I register position changes for the other list items that change position?
For example: #3 changes position to #1, then #1 and #2 also change position.
Second, using Dragula, the list items are now draggable.
How can I resort the list after list items were dragged?
I also tired Angular Dragula without success.
here is my code on github.
Thank you for your help!
I had a similar issue. You need a way of getting the DOM indexing and updating the JavaScript object to match. I looped through the DOM elements, got their index values, found the corresponding item in the data object and then set the index value in the object to that of the DOM index value. Hope this example helps someone.
var updateIndexes = function() {
// this gets the index of any DOM element, like jQuery index()
function getIndex(el) {
for (var i = 0; el = el.previousElementSibling; i++);
return i;
}
// loop through the list DOM elements
for (var i = 0; i < dataArray.length; i++) {
var dataId = dataArray[i].id;
var domIndex = getIndex(document.getElementById(dataId));
dataArray[i].index= domIndex;
}
};
I am working on a JavaScript web app that takes input from a user (the name of a musical artist) and outputs a list of related artists and their most popular song, as determined by the Spotify API. I initially had a rudimentary version of this functioning, but it would just post a list of all the related artists with a list of those related artists' most popular songs immediately above it, and I want the entire object to print out (artist plus most popular song).
I have a series of objects representing the artists that I received through the spotify-web-api-js node module with Node.js and Browserify to make it function on the browser, uniquely identified by their Spotify ID. How can I loop through them if I don't know those IDs in advance before a user does a search so that I can properly push them into an object that I can then input to the DOM through jQuery's append? I've been trying to access them in various ways, but it doesn't seem to be working:
s.getArtistRelatedArtists(originalArtistId, function(err, data) {
for (i = 0; i < data.artists.length; i++)
{
console.log(data.artists[i].name);
relatedArtistsObject[data.artists[i].id] = {
name: data.artists[i].name,
songs: []
};
s.getArtistTopTracks(data.artists[i].id, "US", function (err, data2) {
if (relatedArtistsObject[data2.tracks[0].artists[0].id] !== undefined)
{
// console.log(data2.tracks[0].name); -- this outputs the song titles I want
relatedArtistsObject[data2.tracks[0].artists[0].id].songs.push(data2.tracks[0].name);
}
});
}
console.log(relatedArtistsObject);
// loop through this object and print it through the screen
for (k = 0; k < relatedArtistsObject.length; k++)
{
console.log(relatedArtistsObject.id.songs[0].name);
$('#related-artist').append(relatedArtistsObject[k]);
}
// $('#related-artist').append(relatedArtistsObject);
});
Here is a link to the full code (not functioning because it doesn't have Node.js/browserify enabled): https://jsfiddle.net/pmh04e99/
This answer is partially helpful, but doesn't apply here because the JSON output doesn't have a "name" field in the array I want to access. Instead, songs is an array of 1, and the content I want is within item 0. My console.log output of the relatedArtistsObject looks like this, for example:
How can I access these objects in the DOM through my code when I don't know the spotify IDs right now?
Note: I'm aware that i'm not error handling yet, but I want to be able to implement the main functionality first.
You can use for in or use Object.keys.
The former will iterate over each enumerable property name of the object, and setting that name into the specified variable. You can then use that name to access that object's property value.
The latter will return an array of the object's property names. You can then use a regular for loop to iterate over that array and use each of the values in the array as the id
For...In
for(id in relatedArtistsObject){
var artist = relatedArtistsObject[id];
console.log(artist.name,artist.songs);
}
Object.keys
var ids = Object.keys(relatedArtistsObject);
for(var i=0; i<ids.length; i++){
var artist = relatedArtistsObject[ids[i]];
console.log(artist.name,artist.songs);
}
For example, if you want to remove the completed todos from a todo list.
Selectively removing models from a Backbone collection and server seems like a common task. What are the common ways to do it, and what are the costs and benefits associated with each way?
Solution One
var toRemove = collection.filter(function(model) {
return condition(model);
});
_.invoke(toRemove, 'destroy');
This seems like the cleanest way. It's what Addy used in his book to delete completed todos (which is a big reason why I list this solution first). It's especially clean if you're reusing the filter function (like he is).
However, I think it's slower than Solution Two because it involves iteration over collection and toRemove, whereas Solution Two only involves iteration over collection. Still, they both have linear run times so it's not too big a deal.
Solution Two
for (var i = collection.models.length-1; i >= 0; i--) { // looping from back to front
var model = collection.models[i];
if (condition(model)) model.destroy();
}
I think this is relatively clean. And as mentioned above, this has the benefit of only having to loop through the collection, not the collection + a filtered version of the collection.
It's important that you loop from back to front. Consider what happens when you loop from front to back:
for (var i = 0; i < collection.models.length; i++) { // looping from front to back
var model = this.models[i];
if (condition(model)) {
model.destroy();
i--;
}
}
When you destroy a model, the models in collection.models basically get shifted up by one. This has two implications:
The length of the array decreases by one.
Say you delete element two. The next element will be four, not three. The index gets incremented to 3, and since the models get shifted up by one, three has an index of 2 and four has an index of 3.
Solutions:
Calculate collection.models.length after each iteration of the loop. Ie. for (var i = 0; i < **collection.models.length**; i++)
Decrement i after you destroy a model.
You could loop from front to back, but you would just have to address these things, which makes it a bit more complicated.
Solution Three
var toRemove = [];
collection.forEach(function(model) {
if (condition(model)) toRemove.push(model);
});
toRemove.forEach(function(model) {
model.destroy();
});
This is pretty similar to Solution One.
Differences:
We're using a forEach to construct toRemove instead of filter.
We're manually iterating through and calling destroy instead of using invoke.
Like Solution One, you have to iterate through collection and through toRemove, and so it presumably takes longer than Solution Two.
Note: we can't destroy the model in the first forEach loop. If we do, then it has the same problem as the front-to-back loop in Solution Two. To get around this limitation, we have to use the toRemove array.
Solution Four
This uses reset() + the reset event.
var toKeep = collection.filter(function(model) {
return condition(model);
});
collection.reset(toKeep);
and
collection.on('reset', function(after, before) {
// let after.length = k
// let before.length = n
after.sort(); // k*logk
for (var i = before.length-1; i >= 0; i--) { // n
var model = before[i];
if (!binarySearch(model, after)) model.remove(); // logk
}
// total runtime: n*logk + k*logk
});
This seems a bit excessive to me, but is an option.
I have an javascript array, which looks like:
var styles = new Array();
styles[0] = { ... Position: 0, ... };
styles[1] = { ... Position: 1, ... };
styles[2] = { ... Position: 2, ... };
...
styles[N] = { ... Position: N, ... };
I use this array to display a list, where each item is a div. The end result is this:
<div id="container">
<div>... item 1...</div>
<div>... item 2...</div>
<div>... item 3...</div>
</div>
Now the "container" div is also jquery sortable. That way I can drag/drop the items and change the position. Now whenever the user drags an item to a different position I update the positions back in the array by looping through the div items, which is pretty bad. It looks more or less like that:
var items = $("#container");
for (var i = 0; i < items.length; i++)
{
....
styles[i] = { ... Position: i, ... };
}
Is there a better way to achieve this?
Update 1:
I need to save the positions in the database, which is why I need change my array after the list has been changed. The list changes depending on other criterias. So I could have a list of 10 items or I could have a list of X items. It depends on which list the users selects. Now if the user changes one list and then wants to see a second list, then I need to make sure that the first list maintains the positions.
I think Drew Wills response is on the mark, and this might also help. A div is a JavaScript object, which means you can add properties to it. So you might be able to reduce the number of lines of JavaScript code and increase the code's expressiveness by sticking your "information object" right onto each div, meaning you'd no longer need your array.
Where does your array come from in the first place? I'm going to assume it is a JSON-ified version of some data you have on the server. So I'm assuming (guessing) that you have some kind of "each" or for loop that creates the divs from the array, perhaps like this:
for (var i = 0; i < styles.length; i++)
{
var newDiv = $("<div>" + style[i].item + "</div>";
$("#container").append(newDiv);
}
Assuming you have that, then you could modify it to this:
for (var i = 0; i < styles.length; i++)
{
var newDiv = $("<div>" + style[i].item + "</div>";
// KEY NEW LINE FOLLOWS:
newDiv.myCustomObject = styles[i];
$("#container").append(newDiv);
}
Now, as the divs get sorted all over the place, this "myCustomObject" goes along with them. You don't need the array anymore. If you need the "Position" property on "myCustomObject" to be updated, use the "index" property of the div, as Drew Wills said.
So, imagine that your "styles" object has a property in it called "Color". Imagine you want to show an alert on click that tells the color associated with the div they clicked on. You can now accomplish this without needing the "styles" array, because the divs now have everything you need. So you could do this:
$("#container div").click(function() {
alert("The color is " + this.myCustomObject.Color);
});
Later, when it comes time to post or send via ajax the positions, you could do this:
$("#container div").each(function(index) {
this.myCustomObject.Position = index;
}
NOTE: Also, some people would prefer to use the JQuery data() method to associate "myCustomObject" with the div. It achieves the same result and perhaps gets garbage collected more completely in some browsers.
Why is it you need to know the position?
You won't have to track the position at all if you can rely on jQuery's $.index() method.
When you need to know the position of an element, just use something like the following...
$("#container div").index(myDiv);
UPDATE 1:
I see -- the server needs to know the positions to store them in the DB. Nevertheless, I'm not crazy about the notion of having 2 representations of this data on the page: markup (divs), and JS array. I would look at changing my code to build the array (from the divs) at the last second... just before you send it to the server. That way you can loop over the divs in order and you won't have to maintain the order in parallel in a separate data structure. Keeping 2 structures in sync in messy and can be error-prone
Little disclaimer: I don't know everything about your page. I don't know anything about your situation that would make this suggestion unworkable, but I suppose there may be something out there.
Little tip: if there are data elements int he area that aren't represented in the markup somehow, consider just tagging the markup with arbitrary info.
link text