Javascript: Event when DOM manupulation is done - javascript

We have a table that is populated via an Ajax call. In our Watin tests, we want to check the contents of this table. We already managed to find out when the javascript populating the table has stopped running, so that part works fine.
However, in IE there seems to be a delay between the moment javascript populating the DOM is finished and the moment the DOM is complely updated for Watin to detect the changes.
Right now we have a Thread.Sleep(500) to make this work, but I don't like this. Is there an event or something to catch the moment the DOM is completely updated?
EDIT: Example code
var tbody = $("#myID tbody");
tbody.empty();
$.each(item.Producten, function (i, item) {
var row = "<tr><td>" + item.Property + "</td></tr>";
tbody.append(row);
});

I don't think that there is such an event. If it were, WatiN would have use it. But when WatiN waits for something, eg. when you use WaitUntil methods (sometimes you could use it indirectly, for example when reading Exists property), WatiN is just making the Thread.Sleep call in a loop (see TryFuncUntilTimeOut class).
Almost everything you need can be achieved by using WaitUntil methods, but sometimes it is really tricky. If I were you, I would try a little bit more with this approach (especially if you are new to WatiN), but if it takes too much times I would just use Thread.Sleep(500) and forget about it.
I would like to add, that if there is a possibility to add some javascript code for test purposes to the end of your example code, you could set some kind of flag, like loadingCompleted = 1, and use TryFuncUntilTimeOut (I can't remember if there is appropriate WaitUntil method) to wait until loadingCompleted is set to 1. You can of course use WatiN to read this variable - see Document.Eval method.

Did you try the Method .WaitUntilExist ?
EDIT: add sample code
In your JS you can add a class:
var tbody = $("#myID tbody");
tbody.empty();
$.each(item.Producten, function (i, item) {
var row = "<tr class='newRow'><td>" + item.Property + "</td></tr>";
tbody.append(row);
});
In WatiN side:
Table table = Browser.Table("myID");
table.OwnTableRow(Find.ByClass("newRow")).WaitUntilExists();
Remarks:
If your table is empty, you can use Find.Any in the constraints
If Find.ByClass doesn't work (I know it has some problems), use another specific constraint : Find.ByIndex, Find.ById, etc.

This is an answer along the same line as prostynick suggests.
Why not give the tbody an attribute like data-uniqueId and give that a different value everytime at the end of your javascript code. It might be as simple as being the current datetime, as long as it is different from previous values.
Now that you have this attribute and value you can use that in WatiN:
var currentUniqueId = browser.Table("myID").TableBody.GetAttributeValue("data-uniqueId");
.... do something to trigger the AJAX call
browser.Table("myID").TableBody.WaitUntil(!Find.By("data-uniqueId", currentUniqueId);
In the first line we retrieve the value for data-uniqueId. In the last line WatiN waits until there is no tbody with this attribute having that value (because the data-uniqueId value was changed by your javascript code). Now we can proceed.
HTH,
Jeroen

Related

Slickgrid - how to get the value of edited cells?

I'd like to make my code be able to update database table immediately when the cell is changed. However, I can get the value of the changed cell whereas not the data of those of next cell.
Methods I have tried already without success:
grid.onCellChange.subscribe(
function (e, args) {
//alert(data[args.row][grid.getColumns()[args.cell].field]);
//alert(grid.getColumns()[args.cell].name);
//alert(args);
<%
updateDatabase("UPDATE table1 "+
"SET "+
" col1="+data[args.row][grid.getColumns()[args.cell].field]+" "+
"WHERE "+
" col2="+???)")
%>
}
);
How to get the value of the next column that I can use in the where clause ?
Addressing your problem:
If you check out the official SlickGrid Editing demo, you can try the following to get a basic understanding how grid editing works. Open your JS debugger and subscribe to the grids onCellChange event using the following command:
grid.onCellChange.subscribe(
function (e,args) {
console.log('row: ' + args.row + ' cell: ' + args.cell)
});
The grid's onCellChange events args argument object contains two properties:
row
cell
Now edit any of the cells and leave the just edited cell. It's important since this event will only fire on blur event.
In the console log you will see for instance:
row: 6 cell: 1
Addressing your data object:
The only way I found how could you address your data source is in a hackish way only using eval(). For further information about object property accessing you can check this answer.
A bit from the Javascript jargon:
The actual eval() command to address your data object with the given fieldname or property name is:
eval('data[args.row]'+'.'+columns[args.cell].field)
Simplified it will evaluate the JavaScript code represented as a string, in this SlickGrid Editing example it will become as if you would write:
data[6].title
So the proof of concept code is:
grid.onCellChange.subscribe(
function (e,args) {
console.log(eval('data[args.row]'+'.'+columns[args.cell].field))
});
This will return you the value of the current edited cell. For instance changing args.cell+1 will return you the next cell value from the data source of your grid. Of course you should always check the columns.length property to keep the column index key inside of array bounds.
Regarding your idea about the updateDatabase call: why do you want to update the database after every time this event gets fired? I suggest you to take a look at the official Composite editor demo which has an edit form.
The other thing I would strongly advise is that you not write server side or code behind code directly into the javascript code block. For example, you could make it use an ajax post.
The reason I recommend this is because then you are going to have separated functions with separated responsibilities. Just by adhering to this principle in itself could lead you to have a cleaner code, also you only give one reason to change. Also it may help testing your functions much easier.

jQuery single line code optmization for getting and setting for the same input element

I'm using PHPStorm and have the following js code
$('#id'.val($('#id'.attr('default'));
The idea is to reset the value of a input field to it's default which is set in default attribute of the input element.
Now the IDE is suggesting me to avoid duplicate selectors.
Though it is working I'm interested in finding out what is the best way to optimize this code line?
Not exactly what you asked for but more future proof using data and not an attribute. You could even store complex data or other information in there as well like "originalvalue" or "lastchangedvalue" etc.
Storing in an attribute is fine however I prefer to store things like this in the data of the element like:
<myelementtag id="myid" data-defaultvalue="defaultvalue" />
You then access it like:
$('#myid').data("defaultvalue");
For example:
var myElement = $('#myid');
myElement.value = myElement.data('defaultvalue');
Want to reset the default?
var mynewdefault = "mynewvalue";
myElement.data('defaultvalue', mynewdefault);
Since you asked only one selector and one line code, please use like this as stated in jQuery documentation (middle section):
$("#id").val(function(index,value) {
return $(this).attr('default');
});
or if you want to avoid $(this):
$("#id").val(function (index, value) {
return this.getAttribute('default');
});
JSFiddle
And yes, as other members have pointed out, it would be better if you use data attribute (data-defaultValue) instead.
This is a more compact solution:
var id = $('#id');
id.val(id.attr('default'));
You really don't need to use $(...) every time.
Store your jQuery element in a variable:
var $id = $('#id');
$id.val($id.attr('default'));

live function with out arguments in jQuery

I wanna select some item by jQuery which has been added after loading page,so I wanna use live() function.I used it before for clicking like following code:
$("selector").live('click')
but now when I wanna use it in another function.
but It will not work with out argument,like it live()
for e.g followin code will alert test (work)
var pos_eq=Math.abs($('.myList').css("left").replace("px","")/$('.myList').children('li').eq(0).css('width').replace("px","")) + 1;
alert("test");
but this will not.
var pos_eq=Math.abs($('.myList').live().css("left").replace("px","")/$('.myList').live().children('li').eq(0).css('width').replace("px","")) + 1;
alert("test");
how can I solve it?
You want a function, not a variable. It looks like you are trying to keep pos_eq up to date after elements have been added to the page. Having a variable auto-update when the DOM changes in the way you are trying to do is not possible with JavaScript. What you can do is use a function instead of a variable. This way whenever the value is accessed you are getting the latest value because it is computed on demand:
function pos_eq() {
var list = $('.myList');
var left = parseInt(list.css("left"));
var width = parseInt(list.children('li').eq(0).css('width'));
return Math.abs(left / width) + 1;
}
I broke your code up into multiple statements to make it more readable. You would use this function the same as you used the variable, but instead add parens to the end to invoke the function:
alert(pos_eq);
alert(pos_eq());
To get a set of objects at the time you need them, just do $("selector"). That will do a query at that time and get the set of objects. There is no need to use .live() in order to query objects on the page. It does not matter whether the objects were part of the original page or were added dynamically later. When you do $("selector"), it will search the contents of the current page and get you the objects that are currently in the page that match the selector.
There is no way to do a live selector query and save it and have it automatically update in jQuery or any other library I know of. The way you solve that issue with a dynamic page is that you just do a new query when you need current results.
The description of live() is: Attach a handler to the event for all elements which match the current selector, now and in the future. It does not give you a live node list despite its name. jQuery does not have any method that returns a live node list(such as those returned by getElementsByTagName etc.) as far as I know.

jQuery 1.2.6 caching

I'm doing quite a bit of DOM manipulation in my app, adding new nodes, and I've found that the children() function can get out of sync. I've got a tbody element with two rows, I use the children() function on this to do some manipulation with these rows. I then add two more rows to the tbody, when I use the children function again to do more manipulation I only get back the original two rows, not these plus the two rows I've just added. I'm doing a new call to children every time, not relying on any variable to auto-update. Is there any way to clear jQuery's cache - I've noticed problems like this a few times with selectors and got around it by selecting further up the DOM tree then navigating back down (i.e. don't select the tbody with a jQuery CSS selector, select the table then do table.tBodies[0].rows), but that won't work in this case.
Thanks,
Phil
The children() function doesn't get out of sync. You have to execute that again if you changed the DOM. On modifying you can have a callback that remakes your array of matched elements. This is not a cache mechanism, is like JavaScript works: linear. Ajax calls are sometimes cached but doesn't seem the case here.
Recommendation
Always use the latest version of jQuery: 1.3.2 now.
Update. So this is your code:
function removeGroup() {
var groupNodes = [ $('#row1')[0], $('#row2') ]; // is this correct?
var parent = groupNodes[0].parentNode;
if ( $(parent).children().length > 2) {
parent.removeChild(groupNodes[0]); // why not use dirrectly $('#row1')[0].remove();?
parent.removeChild(groupNodes[1]); // same here
} else {
alert("Can't delete last group");
}
}
This code has nothing to do with caching and most of it doenst even pass through jQuery.
Better explain what you want to do and will do it.

jQuery pitfalls to avoid [closed]

As it currently stands, this question is not a good fit for our Q&A format. We expect answers to be supported by facts, references, or expertise, but this question will likely solicit debate, arguments, polling, or extended discussion. If you feel that this question can be improved and possibly reopened, visit the help center for guidance.
Closed 11 years ago.
I am starting a project with jQuery.
What pitfalls/errors/misconceptions/abuses/misuses did you have in your jQuery project?
Being unaware of the performance hit and overusing selectors instead of assigning them to local variables. For example:-
$('#button').click(function() {
$('#label').method();
$('#label').method2();
$('#label').css('background-color', 'red');
});
Rather than:-
$('#button').click(function() {
var $label = $('#label');
$label.method();
$label.method2();
$label.css('background-color', 'red');
});
Or even better with chaining:-
$('#button').click(function() {
$("#label").method().method2().css("background-color", "red");
});
I found this the enlightening moment when I realized how the call stacks work.
Edit: incorporated suggestions in comments.
Understand how to use context. Normally, a jQuery selector will search the whole doc:
// This will search whole doc for elements with class myClass
$('.myClass');
But you can speed things up by searching within a context:
var ct = $('#myContainer');
// This will search for elements with class myClass within the myContainer child elements
$('.myClass', ct);
Don't use bare class selectors, like this:
$('.button').click(function() { /* do something */ });
This will end up looking at every single element to see if it has a class of "button".
Instead, you can help it out, like:
$('span.button').click(function() { /* do something */ });
$('#userform .button').click(function() { /* do something */ });
I learned this last year from Rebecca Murphy's blog
Update - This answer was given over 2 years ago and is not correct for the current version of jQuery.
One of the comments includes a test to prove this.
There is also an updated version of the test that includes the version of jQuery at the time of this answer.
Try to split out anonymous functions so you can reuse them.
//Avoid
$('#div').click( function(){
//do something
});
//Do do
function divClickFn (){
//do something
}
$('#div').click( divClickFn );
Avoid abusing document ready.
Keep the document ready for initialize code only.
Always extract functions outside of the doc ready so they can be reused.
I have seen hundreds of lines of code inside the doc ready statement. Ugly, unreadable and impossible to maintain.
While using $.ajax function for Ajax requests to server, you should avoid using the complete event to process response data. It will fire whether the request was successful or not.
Rather than complete, use success.
See Ajax Events in the docs.
"Chaining" Animation-events with Callbacks.
Suppose you wanted to animate a paragraph vanishing upon clicking it. You also wanted to remove the element from the DOM afterwards. You may think you can simply chain the methods:
$("p").click(function(e) {
$(this).fadeOut("slow").remove();
});
In this example, .remove() will be called before .fadeOut() has completed, destroying your gradual-fading effect, and simply making the element vanish instantly. Instead, when you want to fire a command only upon finishing the previous, use the callback's:
$("p").click(function(e){
$(this).fadeOut("slow", function(){
$(this).remove();
});
});
The second parameter of .fadeOut() is an anonymous function that will run once the .fadeOut() animation has completed. This makes for a gradual fading, and a subsequent removal of the element.
If you bind() the same event multiple times it will fire multiple times . I usually always go unbind('click').bind('click') just to be safe
Don't abuse plug-ins.
Most of the times you'll only need the library and maybe the user interface. If you keep it simple your code will be maintainable in the long run. Not all plug-ins are supported and maintained, actually most are not. If you can mimic the functionality using core elements I strongly recommend it.
Plug-ins are easy to insert in your code, save you some time, but when you'll need an extra something, it is a bad idea to modify them, as you lose the possible updates. The time you save at the start you'll loose later on changing deprecated plug-ins.
Choose the plug-ins you use wisely.
Apart from library and user interface, I constantly use $.cookie , $.form, $.validate and thickbox. For the rest I mostly develop my own plug-ins.
Pitfall: Using loops instead of selectors.
If you find yourself reaching for the jQuery '.each' method to iterate over DOM elements, ask yourself if can use a selector to get the elements instead.
More information on jQuery selectors:
http://docs.jquery.com/Selectors
Pitfall: NOT using a tool like Firebug
Firebug was practically made for this kind of debugging. If you're going to be mucking about in the DOM with Javascript, you need a good tool like Firebug to give you visibility.
More information on Firebug:
http://getfirebug.com/
Other great ideas are in this episode of the Polymorphic Podcast:
(jQuery Secrets with Dave Ward)
http://polymorphicpodcast.com/shows/jquery/
Misunderstanding of using this identifier in the right context. For instance:
$( "#first_element").click( function( event)
{
$(this).method( ); //referring to first_element
$(".listOfElements").each( function()
{
$(this).someMethod( ); // here 'this' is not referring first_element anymore.
})
});
And here one of the samples how you can solve it:
$( "#first_element").click( function( event)
{
$(this).method( ); //referring to first_element
var $that = this;
$(".listOfElements").each( function()
{
$that.someMethod( ); // here 'that' is referring to first_element still.
})
});
Avoid searching through the entire DOM several times. This is something that really can delay your script.
Bad:
$(".aclass").this();
$(".aclass").that();
...
Good:
$(".aclass").this().that();
Bad:
$("#form .text").this();
$("#form .int").that();
$("#form .choice").method();
Good:
$("#form")
.find(".text").this().end()
.find(".int").that().end()
.find(".choice").method();
Always cache $(this) to a meaningful variable
especially in a .each()
Like this
$(selector).each(function () {
var eachOf_X_loop = $(this);
})
Similar to what Repo Man said, but not quite.
When developing ASP.NET winforms, I often do
$('<%= Label1.ClientID %>');
forgetting the # sign. The correct form is
$('#<%= Label1.ClientID %>');
Events
$("selector").html($("another-selector").html());
doesn't clone any of the events - you have to rebind them all.
As per JP's comment - clone() does rebind the events if you pass true.
Avoid multiple creation of the same jQuery objects
//Avoid
function someFunc(){
$(this).fadeIn();
$(this).fadeIn();
}
//Cache the obj
function someFunc(){
var $this = $(this).fadeIn();
$this.fadeIn();
}
I say this for JavaScript as well, but jQuery, JavaScript should NEVER replace CSS.
Also, make sure the site is usable for someone with JavaScript turned off (not as relevant today as back in the day, but always nice to have a fully usable site).
Making too many DOM manipulations. While the .html(), .append(), .prepend(), etc. methods are great, due to the way browsers render and re-render pages, using them too often will cause slowdowns. It's often better to create the html as a string, and to include it into the DOM once, rather than changing the DOM multiple times.
Instead of:
var $parent = $('#parent');
var iterations = 10;
for (var i = 0; i < iterations; i++){
var $div = $('<div class="foo-' + i + '" />');
$parent.append($div);
}
Try this:
var $parent = $('#parent');
var iterations = 10;
var html = '';
for (var i = 0; i < iterations; i++){
html += '<div class="foo-' + i + '"></div>';
}
$parent.append(html);
Or even this ($wrapper is a newly created element that hasn't been injected to the DOM yet. Appending nodes to this wrapper div does not cause slowdowns, and at the end we append $wrapper to $parent, using only one DOM manipulation):
var $parent = $('#parent');
var $wrapper = $('<div class="wrapper" />');
var iterations = 10;
for (var i = 0; i < iterations; i++){
var $div = $('<div class="foo-' + i + '" />');
$wrapper.append($div);
}
$parent.append($wrapper);
Using ClientID to get the "real" id of the control in ASP.NET projects.
jQuery('#<%=myLabel.ClientID%>');
Also, if you are using jQuery inside SharePoint you must call jQuery.noConflict().
Passing IDs instead of jQuery objects to functions:
myFunc = function(id) { // wrong!
var selector = $("#" + id);
selector.doStuff();
}
myFunc("someId");
Passing a wrapped set is far more flexible:
myFunc = function(elements) {
elements.doStuff();
}
myFunc($("#someId")); // or myFunc($(".someClass")); etc.
Excessive use of chaining.
See this:
this.buttonNext[n ? 'bind' : 'unbind'](this.options.buttonNextEvent, this.funcNext)[n ? 'removeClass' : 'addClass'](this.className('jcarousel-next-disabled')).attr('disabled', n ? false : true);
Explanation
Use strings accumulator-style
Using + operator a new string is created in memory and the concatenated value is assigned to it. Only after this the result is assigned to a variable.
To avoid the intermediate variable for concatenation result, you can directly assign the result using += operator.
Slow:
a += 'x' + 'y';
Faster:
a += 'x';
a += 'y';
Primitive operations can be faster than function calls
Consider using alternative primitive operation over function calls in performance critical loops and functions.
Slow:
var min = Math.min(a, b);
arr.push(val);
Faster:
var min = a < b ? a : b;
arr[arr.length] = val;
Read More at JavaScript Performance Best Practices
If you want users to see html entities in their browser, use 'html' instead of 'text' to inject a Unicode string, like:
$('p').html("Your Unicode string")
my two cents)
Usually, working with jquery means you don't have to worry about DOM elements actual all the time. You can write something like this - $('div.mine').addClass('someClass').bind('click', function(){alert('lalala')}) - and this code will execute without throwing any errors.
In some cases this is useful, in some cases - not at all, but it is a fact that jquery tends to be, well, empty-matches-friendly. Yet, replaceWith will throw an error if one tries to use it with an element which doesn't belong to the document. I find it rather counter-intuitive.
Another pitfall is, in my opinion, the order of nodes returned by prevAll() method - $('<div><span class="A"/><span class="B"/><span class="C"/><span class="D"/></div>').find('span:last-child').prevAll(). Not a big deal, actually, but we should keep in mind this fact.
If you plan to Ajax in lots of data, like say, 1500 rows of a table with 20 columns, then don't even think of using jQuery to insert that data into your HTML. Use plain JavaScript. jQuery will be too slow on slower machines.
Also, half the time jQuery will do things that will cause it to be slower, like trying to parse script tags in the incoming HTML, and deal with browser quirks. If you want fast insertion speed, stick with plain JavaScript.
Using jQuery in a small project that can be completed with just a couple of lines of ordinary JavaScript.
Not understanding event binding. JavaScript and jQuery work differently.
By popular demand, an example:
In jQuery:
$("#someLink").click(function(){//do something});
Without jQuery:
<a id="someLink" href="page.html" onClick="SomeClickFunction(this)">Link</a>
<script type="text/javascript">
SomeClickFunction(item){
//do something
}
</script>
Basically the hooks required for JavaScript are no longer necessary. I.e. use inline markup (onClick, etc) because you can simply use the ID's and classes that a developer would normally leverage for CSS purposes.

Categories

Resources