I currently have a sketch for a truthtable generator. While it works fine, it is rather slow. Each combination of boolean values I have added to a <table> using jQuery. For each value, a <td> element is created by jQuery and then added to the <table>. Moreover, I'm using jQuery UI for a nice looking buttonset instead of radio buttons.
In my posted code extract, table is an array containing each boolean combination. Perhaps my code is a little inscrutable but what it basically comes down to is that with 4 boolean variables (16 possibilities), 96 <td> elements are created with classes added and data attributes set. In the second part, three groups of three radio buttons are created and converted into a jQuery UI buttonset.
Using a timer I figured out that it takes approximately 0.4 seconds before everything is filled up. Not that big of a deal, but it is certainly noticeable and does not have a positive effect on the user as each time he enters a different boolean formula it takes half a second to load.
$table = $('#table');
$.each(table, function(k, v) {
$tr = $('<tr>').addClass('res').data('number', k);
$.each(v[0], function(k2, v2) {
$td = $('<td>').text(v2).addClass(v2 ? 'green notresult' : 'red notresult');
for(var i = 0; i < 4; i++) {
$td.data(i, i === k2);
}
$tr.append($td);
});
$tr.append($('<td>').addClass('spacing'));
$table.append(
$tr.append(
$('<td>').text(v[1]).addClass(v[1] ? 'green result' : 'red result')
)
);
});
// ... here is some code that's not slowing down
$radiobuttonsdiv = $('#radiobuttonsdiv');
for(var i = 0; i < 4; i++) {
var $radiobase = $('<input>').attr('type', 'radio')
.attr('name', 'a'+i)
.click(handleChange);
// .label() is a custom function of mine which adds a label to a radio button
var $radioboth = $radiobase.clone().val('both').label();
var $radiotrue = $radiobase.clone().val('true').label();
var $radiofalse = $radiobase.clone().val('false').label();
var $td1 = $('<td>').addClass('varname').html(i);
var $td2 = $('<td>').attr('id', i);
$td2.append($radioboth, $radiotrue, $radiofalse).buttonset();
var $tr = $('<tr>').append($td1, $td2);
$radiobuttonsdiv.append($tr);
}
My questions are:
How could table-filling using jQuery be optimized? Or is a table perhaps not the best solution in this scenario?
Is it perhaps possible to suspend drawing, since that might be slowing everything down?
Try to avoid using .append in a loop, especially if you're adding a lot of elements. This is always a performance killer.
A better option is to build up a string with the markup and do a single (or as few as possible) .append when your loop is finished.
I see that you're using .data, which makes things a bit more complicated, but another option is to use the metadata plugin to attach structured markup to existing tags.
To defer rendering, you could try creating a new table without adding it to the DOM like:
var myDisconnectedTable = $('<table></table>')
Then adding your rows to that table:
myDisconnectedTable.append(...)
Then append your table to the div you want it in:
$('#idOfMyDiv').append(myDisconnectedTable)
I don't know that it will help, but it's worth a shot.
Related
Thanks for giving this a look. I'll start with a quick image. Clicking on any of the red-boxed search results seems to return the <div> for the item directly above it.
Here I clicked on 1613 CAROUSEL CIR, but the event returned the id/content for the item representing 1612..
Sometimes it's even weirder, for example, every item following 1420 might point back to 1420. So it's not always a conflict with a <div> and it's immediate neighbor, although that's usually the case.
I've been unable to find any definite pattern in this behavior. Sometimes it's just one or two items in the list; sometimes most of the list is affected, with "a good portion" of results pointing to one particular div.
There's only one true consistency--typically the first several items work as expected, and short lists will be 100% correct. But really long lists (50+) are not necessarily worse than semi-long lists (20+).. :/
The code building the search results iterates over JSON data retrieved by a JQuery $.ajax() call, and this is the relevant code building the visible search results:
if( result.d.length > 0 )
{
var i=0;
for(i; i<result.d.length; i++)
{
// ..there's a bunch of irrelevant code here to set the map bounds..
// ..then I build the HTML using JQuery like this
//
var date = new Date();
var divID = "searchItemDiv" + date.getTime().toString();
var $searchItemDiv = $( "<div id='" + divID + "' class='searchItemDiv'>"+result.d[i].Description+"</div>" );
$searchItemDiv.data('itemData', result.d[i]);
$searchItemDiv.bind('click', onSearchItemClick);
$( "#searchResults" ).append($searchItemDiv);
}
}
While I don't suspect the event handler is the issue, the relevant code there looks like this:
function onSearchItemClick(event)
{
if( event.target.id.toString() !== '' )
{
// I clicked 1613, but event returned DIV with text of "1612"??
//
var item = $('#'+event.target.id.toString()).data('itemData');
alert( event.target.id.toString()+"\n"+
$('#'+event.target.id.toString()).text() );
// ..more irrelevant stuff to show a popup of property data..
}
}
FireFox, Chrome, and IE all demonstrate the same behavior, so it's not browser-specific.
I'm relatively sure this is not the product of a race condition during the render phase, but I'm not comfortable-enough with JavaScript to know that for certain.
I'm pretty baffled by this. FWIW, I'm a former Flex & C# developer and relatively new to JavaScript/JQuery development, so there may be a gotcha related JavaScript contexts and/or JQuery that I'm stepping into.
I would say, instead of binding the click function within a for-loop, just select all of the searchItemDiv's after the for-loop binds the data to them, and register a click function on all of them at once. You don't need a separate line to define variable i, just do it in the for statement. I also wouldn't try to generate random IDs with new Dates, that just seems unnecessary. Registering all click functionality at once will also make your click handler much simpler:
if( result.d.length > 0 )
{
for(var i = 0; i<result.d.length; i++)
{
// ..there's a bunch of irrelevant code here to set the map bounds..
// ..then I build the HTML using JQuery like this
// select the i'th searchItemDiv
$searchItemDiv = $($('.searchItemDiv')[i])
// give it the data
$searchItemDiv.data('itemData', result.d[i]);
$( "#searchResults" ).append($searchItemDiv);
}
// then register all the click handlers at once, very simple
$('.searchItemDiv').bind('click', function() {
var item = $(this);
alert(item.text());
});
}
--EDIT--
also, do the searchItemDivs already exist or are you trying to create them?
if you're trying to create them, you might want this in the for-loop instead:
for(var i = 0; i<result.d.length; i++)
{
// ..there's a bunch of irrelevant code here to set the map bounds..
// ..then I build the HTML using JQuery like this
// create a searchItemDiv
$searchItemDiv = $('<div class="searchItemDiv"></div>')
// give it the data
$searchItemDiv.data('itemData', result.d[i]);
$( "#searchResults" ).append($searchItemDiv);
}
I'm guessing that is what you want to do.
I think your problem depends on your searchItemDiv id.
Using the date doesn't ensure ids are unique so when you retrieve the object by id it will return an element (probably the first) with the same id.
Make sure to assign unique id on your elements.
A common problem I have is the need to create multiple DOM nodes in a loop, and then activate those nodes in some way, either by applying a plugin, an event handler or similar. The activation step requires that the element actually exist first.
So you end up doing something like:
// Loop 1: Create the nodes
var HTML = '<tr id="UID">';
for(var k in Fields){ // Fields is an object!
HTML += '<td>';
HTML += '<input class="ActivateMe"/>';
HTML += '</td>';
}
var HTML += '</tr>';
$TableBody.children('tr').first().before(HTML);
// Loop 2: Activate the new nodes
$('#'+UID).children('td').children('.ActivateMe').each(function(index){
$(this).InitSomePlugin();
});
The code above is simplified for the question, but assume that each element inside a given cell can be different (maybe an input, may be a div), and might also require a different plugin (Maybe it's a color picker, maybe it's a combo box).
Is it possible to avoid looping over the data set twice and doing the insert and activate in one go? I think it may be possible by appending the nodes within the first loop, which would also allow activation in the first loop. But it is generally considered bad practice to use append in a loop rather than store all your HTML it in a var and append all the HTML at once. At the same time, looping over the same set of data two times seems inefficient too. What is the best way to handle this scenario with minimal performance impact?
Yes. Don't build a lengthy HTML string, but create the elements programmatically in the first loop so that you can direclty instantiate your plugin on them:
var $TableBody = …,
var $row = $('<tr>', {id:UID});
for(var k in Fields) { // sure that Fields is an object?
// For an array, use a normal for loop
var $cell = $('<td>');
var $input = $('<input class="ActivateMe"/>');
$input.InitSomePlugin();
$input.appendTo($cell);
$cell.appendTo($row);
}
$row.prependTo($TableBody);
You might need to do the appends before calling .InitSomePlugin(). You also might want to nest the calls and use chaining for shortening the code:
var $row = $('<tr>', {id:UID}).prependTo(…);
for(var k in Fields)
$('<input class="ActivateMe"/>')
.appendTo($('<td>').appendTo($row))
.InitSomePlugin();
$tableBody = $('table');
// Prepare an DOM object but don't append it to DOM yet.
$tr = $('<tr></tr>',{
id: "UID"
});
// Loop over any condition you would see fit.
for(var i = 0; i < 2; ++i) {
$td = $('<td></td>',{
// Prepare your input element with your event bound to it
"html": $('<input/>', {"class": "ActivateMe"}).InitSomePlugin();
});
// append your td element
$tr.append($td);
}
// Add your tr element to the beginning of your table with prepand method.
$tableBody.prepend($tr);
Since the activation step requires that the element already exists and (probably) is in the DOM, then you only have two choices here:
1) You can create each DOM element individually (with things like document.creatElement() or jQuery's $(html)) such that you have saved DOM object references that you can later use for intializing the plugin.
or
2) You can build up a string of HTML as you are doing. Insert that string, letting the browser create all the elements for you and then you will have to find the appropriate DOM elements in order to initialize the plugins.
Tests have shown that it is often the case that browsers will create lots of HTML objects faster when given a string of HTML rather than manually creating and inserting individual DOM objects so there is no particular issue with using the string of HTML.
There is no 100% right or wrong answer here. Performance is probably not the primary issue unless you have hundreds to thousands of these DOM elements. I tend to go with whichever path leads to the cleanest and simplest code.
In your case, you have to iterate over the Fields object so you can't avoid that. You have to find the first row of your table so you can't avoid that.
If you've decided that building the string of HTML is the most expedient approach to writing the code (which it probably is here), then you can't avoid refinding the objects you need to activate in the DOM.
You can be as efficient about things as possible.
Here's a little bit of streamlining that stays with the basic philosophy:
// create the new rows
var HTML = '<tr id="UID">';
for(var k in Fields) {
HTML += '<td>' + '<input class="ActivateMe"/>'+ '</td>';
}
HTML += '</tr>';
// create an insert new content, save reference to new content
var newObj = $(HTML);
$TableBody.prepend(newObj);
// now activate the plugin on the appropriate objects in the new content
newObj.find(".ActivateMe").InitSomePlugin();
Streamlining steps:
Create each cell in one statement rather than three
When you create the new row object, keep a reference to it so we don't have to find it again.
When you add the new content, use .prepend() to make it the first row rather than finding all the rows and selecting the first one
With you initalize the plugin, there's no need for a .each() loop if you're running the same jQuery method on every object. You can so it like this: newObj.find(".ActivateMe").InitSomePlugin(); without .each().
You could also create the DOM objects yourself and not use the HTML string and keep track of the objects that need to be activate as you go so they don't have to be found again:
// create the new rows
var row = $('<tr id="UID"></tr>'), input, item, activates = [];
for(var k in Fields) {
item = $('<td>');
input = $('<input class="ActivateMe"/>')
activates.push(input);
item.append(input);
row.append(item);
}
// insert row into table
$TableBody.prepend(row);
// now activate the plugin on the appropriate objects that are now inserted
$(activates).InitSomePlugin();
Purists might "like" the second option better than the first option because it's not using an HTML string, but unless you're doing this hundreds to thousands of times such that performance is paramount (in which case you'd have to test which method actually performs better and diagnose why), I can't honestly say that the second is better than the first. I like the coding simplicity of the first and finding a few class objects in a specific table just isn't an expensive operation.
Disclaimer: I am fully aware that the id attribute is for unique IDs. In my case, when the ajax request takes longer than usual it can glitch, causing two same chat messages. I am also aware that there are similar questions out there like this, but I have been unable to find one that solves my issue.
This is what I want to do:
Are there any duplicate IDs inside the div chat_log?
What are they?
Delete all the duplicates
Making sure that the original one is still there.
I've tried using the following code:
$('[id]').each(function () {
var ids = $('[id=' + this.id + ']');
if (ids.length > 1 && ids[0] == this) {
$(ids[1]).remove();
}
});
But I'm not sure how I can adapt that method to my situation, nor am I sure if it would be possible.
How can you ensure that something is unique? Let's say you have a bunch of vegetables (cucumbers, turnips, pizzas etc.) You want to ensure colour uniqueness, making sure that any colour only appears once. How'd you do it?
What I'd do is make a list. I'd go through every vegetable, and inspect its colour. If the colour is already on the list, we'll remove that vegetable from the bunch. Otherwise, we leave it as-is and add its colour to our list.
Once that logic is understood, all we need is to convert it to code! What a fantastically trivial thing to do (on paper, of course.)
//assumes $elem is the element you're deleting duplicates in
//create the ids list we'll check against
var ids = {};
//go over each element
var children = $elem.children();
for ( var i = 0, len = children.length; i < len; i++ ) {
var id = children[ i ].id;
//was this id previously seen?
if ( ids.hasOwnProperty(id) ) {
$( children[i] ).remove();
}
//a brand new id was discovered!
else {
ids[ id ] = true;
}
}
//done!
This is the very simple, plain logic version. You can make much fancier ways with some weird sizzle selectors, but this should get you started.
Demo (without jquery): http://tinkerbin.com/qGJpPsAQ
Your code should work but it only removes the second element that has the same ID, try this:
$('[id]').each(function() {
var $ids = $('[id=' + this.id + ']');
if ($ids.length > 1) {
$ids.not(':first').remove();
}
});
http://jsfiddle.net/3WUwZ/
Here's what I'm trying to accomplish. I have a grid with entries that a user can "batch update", that is a user can select a single or multiple entries from the grid, select values from a form above the grid that they want to apply to all entries, and then submit.
What I'd like to accomplish is a highlight() done on all the changes rows. The problem I'm having is I don't know how to run a highlight() on all of the changes values at once, so I've been doing it individually. I might just be running through the loop wrong. Here's what I'm doing to update the values.
// for every property we have in our batchUpdateValues
for (var propertyName in batchUpdateValues) {
// change the selected banners attributes to match
for (var i = 0 ; i < bannersToUpdate.length ; i++)
{
// if they've selected "Name", we only want to append, not replace"
var oldName = bannersToUpdate[i].get('bannerName');
if (propertyName == 'bannerName') {
bannersToUpdate[i].set(propertyName, oldName + ' ' + batchUpdateValues['bannerName']);
} else {
bannersToUpdate[i].set(propertyName, batchUpdateValues[propertyName]);
}
var changedRowId = this.getStore().indexOf(updatedBanners[i]);
var changedRow = this.getView().getRow(changedRowId);
Ext.get(changedRow).highlight();
}
}
What's happening now is if the user selects 4 "attributes" to update form the form the highlight gets run 4 times, so I'd need to put that code outside the loop. How would I grab all of the DOM attributes for each row and then run a highlight on them()?
My first suggestion is to make sure you understand the SelectionModel. It's described in the documentation. I'd link you but I'm not sure which version you're using. Actually, what version of Ext JS are you using? The answer to this question might be different in 3.x vs. 4.x.
I'd be happy to answer in more detail but I'll need you to clarify with an example. Could you build a small table with some dummy data and then explain when you're talking about rows/columns in the model?
I have some problems with finding and replacing words in PHP files, especially when there is tons of them. So I thought that I will try to use javascript / jQuery.
I'd like to create table witch word_to_replace#new_word to do so.
This is my code which doesn't work (and runs very long), filter doesn't seem to work,
any advices?
(function($){
var arr = [ 'photo-board.pl przyjazny portal fotograficzny# ','Upload images from your#Upload zdjęć z ',
'Total number of images# Całkowita liczba zdjęć'];
for(var i in arr)
{
var st_to_replace = arr[i].split('#')[0];
// alert(st_to_replace);
$('*').filter(function() {
return $(this).text() == st_to_replace;
}).html(arr[i].split('#')[1]);
}
}) (jQuery)
You're getting the text() of every page element (which will include the text of child elements) and replacing within it. That means, when you get the 'body' element you replace all the text, and then you get all the elements within body, and replace all the text, etc.
Something like this may work better:
(function($){
var arr = [ 'photo-board.pl przyjazny portal fotograficzny# ','Upload images from your#Upload zdjęć z ',
'Total number of images# Całkowita liczba zdjęć'];
var bodyText = $('body').html();
$.each(arr, function(i, v) {
var words = v.split('#');
var fromTxt = words[0], toTxt = words[1];
bodyText = bodyText.replace(fromTxt, toTxt);
});
$('body').html(bodyText);
})(jQuery);
Demo here.
It's worth noting though that since this destroys and recreates the entire body content, you'll loose event handlers and data set using .data(...).
One of the performance issues of your script is the immediate call to .html every iteration of the filter. This is causing the browser to repaint the element every iteration.
You might consider editing the html detached from the dom and then, after the for and the filter loops, replacing the html in the dom.