Ordering of jQuery binding to elements? - javascript

I'm having a bit of a difficulty using jQuery to bind a function to an element. Basically I have a set of divs that I load using JSON. Each of the JSON items have an associated "action" that defines what function to call if that div is clicked.
As I iterate through each of the JSON items, I do (editied for clarity):
for (var i = 0; i < JSONOBJECT.length; i++) {
var divbox = $('<div id="' + i + '"><img /></div>');
var action = JSONOBJECT[i]["action"];
$(divbox).click(function () {
eval(action); // bind function
});
$('.navigationarea').append(divbox); // Append to area
}
So for example, JSONOBJECT[0]["action"] can contain a "doThis()", while JSONOBJECT[1]["action"] can contain a "doThis2()".
The problem is that action always ends up being the action associated with the last item in the JSON object. I know this is an issue related to localizing the variable (or making a copy?) but I'm missing it.
Can I get some help?
Thanks in advance.

Your problem is that the action variable is shared by all of the anonymous methods.
In your specific case, the best solution is to write divbox.click(new Function(action)).
This will also be faster because the code will only be parsed once, not once per click.
In general, the solution is to put the code containing the anonymous function into a separate function, and pass the variable to the separate function as a parameter.
EDIT: Your code can be rewritten like this:
for (var i = 0; i < JSONOBJECT.length; i++) {
$('<div id="' + i + '"><img /></div>')
.click(new Function(JSONOBJECT[i].action))
.appendTo($('.navigationarea'));
}

Related

Create a table with several <tr> with different classes

I put them in context:
I need to create a table that has several , whose class I want to be different. It should be clarified that I am using javascript for this and I use the append function for this.
This is the code I currently have :
function my_function() {
$(document).ready(function() {
var number = 0;
var num = ++number;
$("#my_table> tbody").append("<tr class='tr" + num + "'><td class='center'>" + "HELLO WORLD" + "</td></tr>");
}
})
}
When I run it the result is 1, but when I call the function again it is still 1 instead of increasing the value back to 2.
The function is being called frequently so it should increase the value again but it doesn't. Now, I need you to increase the value multiple times without only doing it once. So that the second has class tr2. Thank you if you answer or give a solution.
You're declaring the variable inside the function so it will be reassigned var number = 0; every time you call it, declare it outside of it and it should work as you expect it to do.

Adding Objects from Another File to an Array in a Separate File

I am trying to find a better solution for adding objects to an array. The box objects are from a separate file and are pushed to the array one line at a time in a different file, as such:
function loadCols(){
collisionPoints.push(box1);
collisionPoints.push(box2);
collisionPoints.push(box3);
collisionPoints.push(box4);
collisionPoints.push(box5);
collisionPoints.push(box6);
collisionPoints.push(box7);
collisionPoints.push(box8);
collisionPoints.push(box9);
collisionPoints.push(box10);
};
I have tried using a for loop and concatenating the string "box" + i but this didn't work.
I also tried adding them to an array in the file where the objects are created but I was not able to find a way of passing the array to the main file. Although this works I'm hoping there is a cleaner solution. Any help would be appreciated, cheers.
You can get a variable from it's string name, by using the window object.
function loadCols(){
for (var i=1; i<=numberOfBoxVars; i++) {
collisionPoints.push(window["box" + i]);
}
}
Alternatively, if your variable are defined within a closure and your loadCols function is defined within the same closure, you can use the "this" keyword in place of the window object.
(function() {
var box1 = "1";
var box2 = "2";
...
function loadCols(){
for (var i=1; i<=numberOfBoxVars; i++) {
collisionPoints.push(this["box" + i]);
}
}
});
If I understand you correctly you are looking for a way to use dynamic variables in a for-loop. If box1 and so on are global variables you can get them dynamically by accessing them as property of window:
window['box'+i]
See here: Use dynamic variable names in JavaScript
If you send all the objects in a JSON array you could just do this:
var array = JSON.parse(boxesarray);
for(var i = 0;i< array.length; i++) {
collisionPoints.push(array[i]);
}
But it would require you sending all the boxes in an array, if this is not possible please post code as to why it isn't and i will adapt my anwser.

Is there a way cleanly use hidden classes in javascript when you dont know what the properties will be?

I have a (GIS) project which displays large amounts of customer data (Thousands of records) to clients. Where nescessary/possible/required, we use server side pagination/filtering/data manipulation but there are cases where it is most efficient to send the data in JSON format to the client and let their browser do the filtering.
The amount of data is large, so we format it to save on bandwidth and parsing time - instead of individual objects, we send a structure that includes the attribute names first and then the values in a single flat array. On the client, we rebuild this into more traditional json objects before other processing occurs. eg:
{attrNames:["foo","bar"],values:[1,2,3,4,...]) -> [{foo:1,bar:2},{foo:3,bar:4},...]
The code for doing this looks a little like this:
function toObjectArray(attrNames, values){
var ret = [];
var index = 0;
var numAttrNames = attrNames.length;
var numValues = values.length;
while(index < numValues){
var obj = {};
for(var a = 0; a < numAttrNames; a++){
obj[attrNames[a]] = values[index++];
}
ret.push(obj);
}
return ret;
}
Given that the attributes may change depending on the customer data, is there a way to do this translation that takes advantage of hidden classes in modern javascript engines like V8? I have done some micro benchmarks similar to our use case ( http://jsfiddle.net/N6CrK/1/ ) where working with json such that hidden classes are used is orders of magnitude faster than building the objects as above. I can get some of this boost using "eval" to create objects, but this feels ugly (This is demonstrated in the js fiddle). Is there a better way? Perhaps using some variant of Object.create, or something like it?
You mean something like this right?
function toHiddenObjectArray(attrNames, attrValues){
var numAttrNames = attrNames.length,
numValues = attrValues.length;
function Data( values ) {
for(var v = 0; v < numAttrNames; v++) {
this[attrNames[v]] = values[v];
}
}
var ret=[];
for( var i=0; i<numValues ; i+=numAttrNames ) {
ret.push( new Data( attrValues.slice(i,i+numAttrNames) ) );
}
return ret;
}
You can check our the fiddle here: http://jsfiddle.net/B2Bfs/ (With some comparison code). It should use the same "Hidden Class" (i.e. Data). Not sure how much quicker it is though!
But, if you really want to make your code none blocking, why not load the page, then request the data via AJAX, then run all you code when you get a response.
I can get some of this boost using "eval" to create objects, but this feels ugly
There's a less ugly way using the Function constructor. Also, further optimisations can be done by immediately assigning the values to the properties, instead of initialising them with null and then again iterating through the attrs array like the adHoc does it. You'd just pass each of the rows you get in the response (array? string? byte-whatever?) as a parameter to the factory.
Also I've moved the creation of the factory function out of the create function, so that only one function will be instantiated (and optimized after enough calls to it).
A decent amount of the time in your test loop is spent on the getTotal, so I've optimised this in a similar manner. Not using getTotalAdHoc in testing the optimised solution drastically reduces the measured time (you can test with getTotalOptimum as well).
var factory = new Function("arr", "return{"+attrs.map(function(n, i){
return n+":arr["+i+"]";
}).join(",")+"};");
var getSum = new Function("o","return "+attrs.map(function(n, i){
return "o."+n;
}).join("+")+";");
(updated jsfiddle)
I haven't yet tried moving the complete loop into the generated code, which could avoid a few function calls, but I don't think this is necessary.
For some reason I just recalled this question... and I just came up with a solution that is way dirtier than using eval but which causes a huge speed boost. The downside of it is that code will be similarly little maintainable as when using eval.
The basic idea is: When receiving the attribute names, generate the function code to parse the following data in JavaScript and add it in a <script> tag to the <head>.
Yeah, isn't that dirty? :-)
If performance is so critical for you, it will definitely help you... here's a modified version of your microbenchmak that proves it: http://jsfiddle.net/N6CrK/17/
Some remarks on the code...
The two functions createWithGeneratedFunction and getTotalWithGeneratedFunction are simply wrapper functions that can be used by productive code. All they do is make sure that the <script> with the generated functions is set up and then call it.
function createWithGeneratedFunction(numValues){
makeSureScriptsAreSetUp()
return createWithGeneratedFunctionAdded(numValues);
}
function getTotalWithGeneratedFunction(objs){
makeSureScriptsAreSetUp()
return getTotalWithGeneratedFunctionAdded(objs);
}
The actual workhorse is the makeSureScriptsAreSetUp with the functions it creates. I'll go through it line by line:
function makeSureScriptsAreSetUp() {
if(scriptIsSetUp)
return;
If the required <script> tag was already set up this function will directly return since there is nothing to do for it anymore.
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
var theFunctions = "";
This prepares the creation of the required functions. The theFunctions variable will be filled with the code that is going to be put into the <script> tag content.
theFunctions =
"function createWithGeneratedFunctionAdded(numValues) {" +
" var ret = [];" +
" var value = 0;" +
" for(var i = numValues; i-- > 0;) {" +
" ret.push({";
for(var attr in attrs) {
theFunctions +=
" " + attrs[attr] + ": value++,";
}
theFunctions +=
" });" +
" }" +
" return ret;" +
"}" +
"";
This completes the code for the parsing function. Obviously it just "parses" the numbers 0 to numValues in this microbenchmark. But replacing value++ with something like TheObjectThatTheClientSentMe.values[value++] should bring you very close to what you outlined in your question. (Obviously it would make quite a lot of sense to rename value to index then.)
theFunctions +=
"function getTotalWithGeneratedFunctionAdded(objs) {" +
" var ret = 0;" +
" for(var i = objs.length; i-- > 0;) {" +
" var obj = objs[i];" +
" ret += 0";
for(var attr in attrs) {
theFunctions +=
" + obj." + attrs[attr];
}
theFunctions +=
" ;" +
" }" +
" return ret;" +
"}";
This completes the code for the processing function. Since you seem to require several processing functions, especially this code could become somewhat ugly to write and maintain.
script.text = theFunctions;
head.appendChild(script);
scriptIsSetUp = true;
}
In the very end we simply set the <script> tag content to the code we just created. By then adding that tag to the <head>, Chrome's hidden class magic will occur and will make the code VERY fast.
Concerning extensibility: If you have to query different attribute/value sets from the server on the same page, you might want to give each parsing/processing method set unique names. For example, if you first receive attrs = ["foo","bar"] and next attrs = ["foo","bar","baz"] you could concat the underscore-joined attribute name array to the generated function names.
For example, instead of using createWithGeneratedFunctionAdded you could use createWithGeneratedFunctionAdded_foo_bar for the first attribute/value set and createWithGeneratedFunctionAdded_foo_bar_baz for the second attribute/value set. An attr parameter could then be added to the wrapper functions that will be used to generate the correct code line for an eval (yes, here the evil eval would return) to trigger the correct generated function. Obviously, the attr parameter would also be required for the makeSureScriptsAreSetUp function.

Javascript Remove Form Data

I have a form that I want to only submit post data for value which have changed.
So the way I have been doing this is like this:
function submit_form(){
var hd = [];
// hd is a big array that is defined here
// hd ['some id_number'] = 'some value'
// main function
for (var id_number in hd ){
var x=document.getElementById(id_number).selectedIndex;
var y=document.getElementById(id_number).options;
selector_text = y[x].text;
if (hd[id_number] == selector_text){
$(id_number).remove();
}
}
document.forms["my_form"].submit()
}
So the goal is that if the selector equals what is in the array, then don't POST the data.
To do this I have been doing the remove function. Everything up to the remove function works as expected. However when I look at the post data I still get the selected value for the id_numbers that mach the value in hd.
Is there a better way to remove to prevent it from going to the POST data? The id.parent.removeChild(id) method didn't work either.
The jQuery id selector should begin with a #, but yours appears not to:
$('#' + id_number).remove();
Your for-in loop should be a regular incremental for loop, which is the proper way to iterate an array in JavaScript. for-in loops are typically used for iterating object properties rather than array elements.
for (var i=0; i<hd.length; i++) {
// Access hd[i] in the loop
var x=document.getElementById(hd[i]).selectedIndex;
var y=document.getElementById(hd[i]).options;
selector_text = y[x].text;
if (hd[i] == selector_text){
$('#' + hd[i]).remove();
}
}
Since you aren't really using jQuery here except for that line, instead the plain JS version is:
var removeMe = document.getElementById(hd[i]);
removeMe.parentNode.removeChild(removeMe);

Hooking into the change() event on jQuery .toChecklist

I'm experiencing a problem when I attempt to use the .change() event on select lists, using the jQuery .toChecklist plugin.
My page contains a number of select lists, which are changed to CheckLists, using jQuery.
Consider the following Javascript snippet:
for (var i=0;i<5;i++)
{
var selectListId = 'selectList' + i;
// Assume this line represents the outputting on a
// standard select list
// Convert to Checklist
$("#" + selectListId).toChecklist();
$("#" + selectListId).change
(
function ()
{
alert("SelectListId: " + selectListId);
}
);
}
For each iteration of this loop, I output a multi-select list, convert it to Checklist, and then add the .change() handler.
However, when the page renders (visually, everything is fine), choosing an option from ANY of the lists gives the alert text of "SelectListId: selectList4" (ie. the last list id of the loop). Thus it appears that each invocation of .change() globally replaces the change handler.
Does anyone know how to change the code so that each checklist has its own change handler (so that the first change handler would output "SelectListId: selectList0", etc).
Thanks,
Try pulling the change function out of the loop. I also added a line that adds a class to each list. The new change function references the lists by the class and will know which is actively being changed via this.
for (var i = 0; i < 5; i++) {
var selectListId = 'selectList' + i;
$("#" + selectListId).toChecklist();
$("#" + selectListId).addClass('newChecklist');
}
$('.newChecklist').change(function() {
alert( $(this).attr('id') );
});
So, after a lot of head scratching, I've found a work-around for this issue.
While concatenating strings together in the anonymous function behaves in an unexpected manner, quoting the whole line of code and wrapping it in an eval statement produces the required results.
Thus, instead of writing, as above:
$("#" + selectListId).change
(
function ()
{
alert("SelectListId: " + selectListId);
}
)
You would need to write this instead:
eval('$("#' + selectListId + '").change(function (){alert("SelectListId: ' + selectListId + '");});');
This may not be the best approach, but it works, and for now that's good enough! :-)

Categories

Resources