Optimizing JS Array Search - javascript

I am working on a Browser-based media player which is written almost entirely in HTML 5 and JavaScript. The backend is written in PHP but it has one function which is to fill the playlist on the initial load. And the rest is all JS. There is a search bar that refines the playlist. I want it to refine as the person is typing, like most media players do. The only problem with this is that it is very slow and laggy as there are about 1000 songs in the whole program and there is likely to be more as time goes on.
The original playlist load is an ajax call to a PHP page that returns the results as JSON. Each item has 4 attirbutes:
artist
album
file
url
I then loop through each object and add it to an array called playlist. At the end of the looping a copy of playlist is created, backup. This is so that I can refine the playlist variable when people refine their search, but still repopulated it from backup without making another server request.
The method refine() is called when the user types a key into the searchbox. It flushes playlist and searches through each property (not including url) of each object in the backup array for a match in the string. If there is a match in any of the properties, it appends the information to a table that displays the playlist, and adds it to the object to playlist for access by the actual player.
Code for the refine() method:
function refine() {
$('#loadinggif').show();
$('#library').html("<table id='libtable'><tr><th>Artist</th><th>Album</th><th>File</th><th> </th></tr></table>");
playlist = [];
for (var j = 0; j < backup.length; j++) {
var sfile = new String(backup[j].file);
var salbum = new String(backup[j].album);
var sartist = new String(backup[j].artist);
if (sfile.toLowerCase().search($('#search').val().toLowerCase()) !== -1 || salbum.toLowerCase().search($('#search').val().toLowerCase()) !== -1 || sartist.toLowerCase().search($('#search').val().toLowerCase()) !== -1) {
playlist.push(backup[j]);
num = playlist.length-1;
$("<tr></tr>").html("<td>" + num + "</td><td>" + sartist + "</td><td>" + salbum + "</td><td>" + sfile + "</td><td><a href='#' onclick='setplay(" + num +");'>Play</a></td>").appendTo('#libtable');
}
}
$('#loadinggif').hide();
}
As I said before, for the first couple of letters typed, this is very slow and laggy. I am looking for ways to refine this to make it much faster and more smooth.

$('#search') isn't cheap. Move it outside the loop.
Move append() outside the loop. Just accumulate the markup in a string and append it once after the loop.
This should be much faster
function refine() {
$('#loadinggif').show();
$('#library').html("<table id='libtable'><tr><th>Artist</th><th>Album</th><th>File</th><th> </th></tr></table>");
playlist = [];
var srchText = $('#search').val().toLowerCase();
var markup = ["<tbody>"];
for (var j = 0; j < backup.length; j++) {
var sfile = backup[j].file.toLowerCase();
var salbum = backup[j].album.toLowerCase();
var sartist = backup[j].artist.toLowerCase();
if (sfile.search(srchText) !== -1 || salbum.search(srchText) !== -1 || sartist.search(srchText) !== -1) {
playlist.push(backup[j]);
num = playlist.length-1;
markup.push("<tr><td>" + num + "</td><td>" + sartist + "</td><td>" + salbum + "</td><td>" + sfile + "</td><td><a href='#' onclick='setplay(" + num +");'>Play</a></td></tr>");
}
}
markup.push("</tbody>");
$("#libtable").append(markup.join(''));
$('#loadinggif').hide();
}

I would suggest building your playlist information a little differently. You could probably get a pretty decent performance gain just by splitting up your playlist info by first letter.
albums = {
'a': [list of albums starting with a],
'b': ...
}
And doing the same for files and artists, of course.

One thing you could do is to take advantage of jQuery's ability to cache document fragments (the example is from a talk John Resig gave but you could apply it to your code):
// SLOW AS IT IS NOT CACHED. BECAUSE OF FOO
$("ul").append("<li><a>" + foo + "</a></li>");
// FAST. DOCUMENT FRAGMENT IS CACHED
$("<li><a></a></li>")
.find("a").text(foo).end()
.appendTo("ul");
This would be applicable to your line above:
$("<tr></tr>").html("<td>" + num + "</td><td>" + sartist + "</td><td>" + salbum + "</td><td>" + sfile + "</td><td><a href='#' onclick='setplay(" + num +");'>Play</a></td>").appendTo('#libtable');

Related

JavaScript - ] is removed from the first element after the deletion of another element from an Array

Hi I have an Array which I would like to remove an element from.
After the deletion of the element from the array, it is saved to localStorage.
However, after loaded from storage it shows that the first element is missing ].
This is the code which I am using to perform the deletion:
var dataSet = (localStorage.getItem('expenditureListFromStorage'));
console.log(dataSet);
//var dataSet = "[" + dataSet + "]";
var dataSet = "[" + dataSet + "]";
console.log("Before parsing" + dataSet);
console.log(dataSet);
var dataSet = JSON.parse(dataSet);
//dataSet.push(dataSet.splice(dataSet.indexOf(selectedExpenditure), 1)[0]);
dataSet.unshift(dataSet.splice(dataSet.indexOf(selectedExpenditure), 1)[0]);
//dataSet.unshift(selectedExpenditure);
console.log("Before Deletion" + dataSet);
dataSet.shift();
//dataSet.pop(dataSet.indexOf(selectedExpenditure), 1);
//dataSet.splice(dataSet.indexOf(selectedExpenditure), 1);
console.log("After Deletion" + dataSet);
localStorage.setItem('expenditureListFromStorage', dataSet);
This is the code which I am using when saving the first element to the array:
if (localStorage.getItem('expenditureListFromStorage') === null)
{
var newExpenditure = [];
newExpenditure.push([tempExpenditureName, tempExpenditureCategory, tempExpenditureDescription, tempExpenditureOccurrence, tempExpenditurePrice, tempExpenditureDate]);
newExpenditure = "[" + newExpenditure + "]";
localStorage.setItem('expenditureListFromStorage', newExpenditure);
console.log(localStorage.getItem('expenditureListFromStorage'));
}
This is the code I am using to add subsequent elements (this piece of code is placed just after the code which saves the first element of an array):
else
{
var newExpenditure = [];
newExpenditure.push([tempExpenditureName, tempExpenditureCategory, tempExpenditureDescription, tempExpenditureOccurrence, tempExpenditurePrice, tempExpenditureDate]);
newExpenditure = "[" + newExpenditure + "]";
console.log("New Expenditure from Form = " + newExpenditure);
var expenditureArrayList = (localStorage.getItem('expenditureListFromStorage'));
console.log("expenditureArrayList from Storage before parsing" + expenditureArrayList);
expenditureArrayList = expenditureArrayList + "," + newExpenditure;
console.log("expenditureArrayList from Storage after parsing" + expenditureArrayList);
localStorage.setItem('expenditureListFromStorage', (expenditureArrayList));
console.log(expenditureArrayList);
}
If I do not save these elements this way, I cannot retrieve the array again after loading from localStorage.
I am trying to work with an array in this format
[["Array1Value1", "Array1Value2", "Array1Value3"],["Array2Value1","Array2Value2","Array2Value3"]]
This array would then be retrieved from localStorage and displayed as an HTML table using the dataTables plugin. However the problem is that when I am deleting an element and saving the array without this array, something is breaking the array and its first element.
Could someone help me out please?

output of api callback to show data of the first object

I am performing an api call back in a loop to get the latest status of a value.. The api call and loop is working but its not showing me the data of the first object returned.. instead it is showing me the data of the last object. The reason for the loop is because there could be multiple objects returned but i want the output to go through them in order from object 0 then object 1 and so on..
Here is a screenshot from firebug showing you two objects with there respective data points. As you can see object 0 is shown first then object 1.
Here is my code to output the data on the page. This code is within a if statement where status = "RESULT" - you can't tell from the below code but both of these objects match that criteria
for (var i = 0; i < response.endpoints.length; i++) {
var endpoint = response.endpoints[i];
//console.log(endpoint);
$pending0.html("<br><b>Server Name: </b>" + endpoint.serverName + "<br><b>Status: </b>" + endpoint.statusMessage + "<br><b>Progress: </b>" + endpoint.statusDetailsMessage);
My issue with the above code is with the order of the output. It shows data for the second object where as I need it to give me the output for the first object.
-- UPDATE --
seems to be working per Barmer's suggestion to add an IF within the loop
for (var i = 0; i < response.endpoints.length; i++) {
var endpoint = response.endpoints[i];
if (endpoint.statusMessage == "In progress") {
//console.log(endpoint);
$pending0.html("<br><b>Server Name: </b>" + endpoint.serverName + "<br><b>Status: </b>" + endpoint.statusMessage + "<br><b>Progress: </b>" + endpoint.statusDetailsMessage);
}
}
Test the status in the loop. Since you only want to show the first endpoint that's in progress, you should break out of the loop when you find it.
for (var i = 0; i < response.endpoints.length; i++) {
var endpoint = response.endpoints[i];
if (endpoint.statusMessage == "In progress") {
//console.log(endpoint);
$pending0.html("<br><b>Server Name: </b>" + endpoint.serverName + "<br><b>Status: </b>" + endpoint.statusMessage + "<br><b>Progress: </b>" + endpoint.statusDetailsMessage);
break;
}
}

Facebook work history fql

I am creating facebook application where in user will be able to fetch the company name of all the friends in friends list
FB.api('/me', function(response) {
var query = FB.Data.query('select name,work,work_history,pic_square
from user where uid IN (SELECT uid2 FROM friend WHERE uid1={0})', response.id)
query.wait(function(rows){
var data = "";
for (var i = 0; i < rows.length;i++) {
data += rows[i].name + " => " +'<img src="' +rows[i].pic_square + '" alt="image" />' + "===> ";
if(rows[i].work[i]||rows[i].work_history[i]) {
data+=rows[i].work_history[0].company_name+ "<br/>";
}
data += "<br />";
}
document.getElementById('location').innerHTML = data;
});
});
}
You should check out https://developers.facebook.com/blog/post/561/
FB.Data.query was deprecated a very long time ago. Here's an example of how to achieve the same goal: http://www.fbrell.com/saved/b9b65bf81dc84b58fc1e167167b13112 (Note it only uses work_history, take a look at work too.
A few things to consider:
When you have JS strings spreading on more rows, make sure you concatenate as in the example I included, you'll save yourself a lot of headaches.
Why check rows[i].work[i]? i contains the value of the index of the current friend. Work and work_history are arrays of my previous jobs, you should check the length of the array and make sure it's > 0.

using localStorage to keep a count

var nicWinsVsMac;
if (tempresult === win) {
wincount = JSON.parse(localStorage.getItem (playerName + 'wincount'));
wincount += 1;
localStorage.setItem(playerName + 'wincount', wincount);
winsvsopponent = 'WinsVs' + opponent;
winsvsopponent = JSON.parse(localStorage.getItem(playerName + 'WinsVs' + opponent));
winsvsopponent += 1;
console.log(winsvsopponent);
localStorage.setItem(playerName + 'WinsVs' + opponent, 'winsVs' + opponent);
console.log(localStorage.getItem(nicWinsVsMac));
}
playerName and opponent are parameters passed in. In this case, playerName = 'nic' and opponent = "Mac"
My browser is giving me "unexpected token w" on the line where i parse out the localStorage. I cannot figure out what is going on. Any help would be great. Thanks!
Instead of using a separate localStorage variable for each attribute of the player. Why not store all the players attributes in a single object and then save that to localStorage.
For example, you can do the following:
var player = new Object();
player.name = 'Mac';
player.winCount = 3;
player.winAgainst = new Array();
localStorage.setItem(player.name, JSON.stringify(player));
var player1 = JSON.parse(localStorage.getItem(player.name));
console.log(player1.name + " has " + player1.winCount + " wins.");
This allows you to save all the player's attributes to a single localStorage variable making it much easier to read and write from.
In regards to the error you are recieving, I believe the issue with your code is that you are not using JSON.stringify in the call to setItem.

Javascript Ajax help..multiple values [i]

What I'm trying to do is post an array of messages asynchronously using this code. I spent over an hour trying to make it only post the four items in the array, but it keeps posting gibberish in addition to the 4 items. Also, it doesn't redirect when done.
var a = document.body.innerHTML;
formx = a.match(/name="post_form_id" value="([\d\w]+)"/)[1];
dts = a.match(/name="fb_dtsg" value="([^"]+)"/)[1];
composerid = a.match(/name="xhpc_composerid" value="([^"]+)"/)[1];
var msg = ['my first update',
'posting again',
'and again',
'done'
];
target = a.match(/name="targetid" value="([^"]+)"/)[1];
for (var i in msg) {
pst = "post_form_id=" + formx +
"&fb_dtsg=" + dts +
"&xhpc_composerid=" + composerid +
"&xhpc_targetid=" + target +
"&xhpc_context=home&xhpc_fbx=1&xhpc_message_text=" + encodeURIComponent(msg[i]) +
"&xhpc_message=" + encodeURIComponent(msg[i]) +
"&UIPrivacyWidget[0]=40&privacy_data[value]=40&privacy_data[friends]=0&privacy_data[list_anon]=0&privacy_data[list_x_anon]=0&=Share&nctr[_mod]=pagelet_composer&lsd&post_form_id_source=AsyncRequest";
with(newx = new XMLHttpRequest())
open("POST", "/ajax/updatestatus.php?__a=1"),
setRequestHeader("Content-Type", "application/x-www-form-urlencoded"),
send(pst);
}
redirect('http://apple.com');
I haven't looked at the code in depth because the formating is all messed up, but I bet the problem is on that for-in loop. for-in in Javascript is not a for-each loop and shouldn't be used to iterate over arrays. Use a normal for loop instead
for(var i=0; i<msgs.length; i++){
BTW, your code is full of bad practices, the worse of which is the use of the evil with statement.

Categories

Resources