Wednesday, September 9, 2009

Browser engine performance related to JavaScript

Question: Which browser engine perform better when it comes to JavaScript?

Browsers tested:


  • Firefox version 3.5.2
  • IE8.0.6001.18702
  • Google Chrome 1.0.154

Results:

When it came to rendering a complex page with lots of jQuery client-side activity, they all performed well, but Google Chrome is definitely faster than Firefox as well as IE8. However, in one of the screens I loaded multiple grids (jqGrid) that each calls the server via Ajax asynchronously. The interesting result here is that IE8 outperforms Firefox and Chrome by an order of magnitude. It loaded the 5 grids visibly about twice as fast as both Firefox and Chrome, the last two performing visibly the same. Without getting in to too much technical jargon, IE8 obviously performs some kind of parallel call whereas the other two rendering engines perform the different Ajax calls in sequence. When it comes to design and development work requiring WYSIWYG results, Firefox is king with the Firebug, YSlow, CacheViewer, and other plug-ins. I can recommend Chrome when client-side performance is crucial to users, but Firefox is hard to beat when it comes to plug-in support, security, adherence to HTML and CSS standards compliance.

To put it into perspective here are some stats on the content rendered:

3429 DOM elements on the page!


doc (1) 63.5K








js (2) 1532.3K








css (2) 234.4K








cssimage (25) 38.1K








image (22) 33.1K


jQuery AutoComplete in ASP.NET MVC Framework

Goal:

jQuery AutoComplete in ASP.NET MVC Framework with callback to customize result


Platform/Environment:

  • Asp.Net Mvc version 1
  • jQuery 1.3.2
  • Autocomplete - jQuery plugin 1.0.2

Quick Solution:


Here I am simply returning a list of account names:

$('#Name').autocomplete(
$('#ajaxListMatchingAccountNamesUrl').val(), { delay: 10, minChars: 3, matchSubset: 1, matchContains: 1, cacheLength: 10, autoFill: true, mustMatch: false, selectFirst: true, max: 15 }
);

ajaxListMatchingAccountNamesUrl - this is a hidden input control in my master page that contains the url that I set as a hidden input field so as to allow the Mvc framework to set it dynamically and accessed client-side.

Here is the C# controller action:

[AcceptVerbs(HttpVerbs.Get)]
public string GetMatchingAccounts(string q)
{
if (String.IsNullOrEmpty(q))
return null;

var sb = new StringBuilder();
foreach (AccountNameIdDTO dto in _accountRepository.SearchForAccountsStartingWithNameSuffixKey(q))
{
sb.AppendLine(dto.Name);
}
return sb.ToString();
}

Complex Solution:

A situation where my account names may be the same for different acounts. In this scenario I let the server-side action append the unique ID to the name, surrounded by brackets: My Account Name (23). This way when the user selects the account, I store the name in the textbox and the ID is parsed out and stored in a protected/readonly textbox.

var $accountMaintenanceWrapper = $('div#AccountMaintenanceWrapper');
$accountMaintenanceWrapper.find("#MasterAccount").autocomplete(
$('#ajaxListMatchingAccountNamesWithIdSuffixUrl').val(),
{
delay:10,
minChars:3,
matchSubset:1,
matchContains:1,
cacheLength:10,
autoFill:true,
mustMatch: false,
selectFirst : true,
max: 15,
formatResult: function(row, i, total) //this is the actual value that will be placed inside the textbox
{
//Parse out the ID suffix in brackets and place only the account name itself into the textbox for the name
$accountMaintenanceWrapper.find('#MasterAccountId').val(i.substring(i.indexOf('(')+1,i.indexOf(')')));
return i.substring(0,i.indexOf('('));
}
}
);

$accountMaintenanceWrapper.find("#MasterAccount").result(SetupCallBackForMasterAccount).next().click(function() {
$(this).prev().search();
});

//When the auto complete textbox is empty, delete the key code from the text
$accountMaintenanceWrapper.find("#MasterAccount").bind('keyup',function() {
if ($(this).val() == '')
{
$accountMaintenanceWrapper.find('#MasterAccountId').val('')
}
});


//This function parses the matched auto complete value's suffix key (in between parenthesis)
function SetupCallBackForMasterAccount(event, data, formatted) {
$('#MasterAccountId').val( !data ? " " : formatted.substring(formatted.indexOf('(')+1,formatted.indexOf(')')));
}

Here is the C# code controller action:

[AcceptVerbs(HttpVerbs.Get)]
public string GetMatchingAgentsWithIdSuffix(string q)
{
if (String.IsNullOrEmpty(q))
return null;

var sb = new StringBuilder();
foreach (AccountNameIdDTO dto in _accountRepository.SearchForAccountsStartingWithNameSuffixKey(q))
{
if ((Repository.Get(dto.Id) as Agency) != null)
sb.AppendLine(dto.Name + "(" + dto.Id + ")");
}
return sb.ToString();
}


Couple of pointers:

  • $accountMaintenanceWrapper - I declare this variable as I am referencing this container several times and to speed performance let jQuery find it and set the reference only once.
  • MasterAccountId - this is the readonly textbox that will contain the value of the unique ID
  • MasterAccount - this is the textbox where the user enters the name of the account
  • minChars:3 - Starts the search after/on three characters
  • selectFirst : true - if the user does not click on the name and simply tabs out will select the first one

Prevent repeated clicks on submit button

Scenario: The user wants to save something and hits the Save button (type=submit). The event performs an Ajax call to send the request to the server. The user, being unaware or not sure if their request is being processed or if they clicked the Save button properly, click it again, and again, causing several Ajax requests, which could be a real problem, if the update is to, for example, transfer funds to your ex-girlfriend's account

The solution: Prevent the user from being able to click the Save button or to when they do to ignore it.

$(document).ready(function() {
$("form").submit(function(e) {
if ($('form').valid()) {
$(':submit', this).each(function() {
if ($(this).attr('value') == 'Save') {
$(this).attr("disabled", "disabled").val("Saving....");
}
});
}
});
});


Couple of pointers:
  • The button being pressed is of type submit
  • The attr of the button gets disabled so the user cannot hit it again and again, and since the form is submitted there is no need for enabling it again.
  • In this example I have also overridden the value of the button to indicate that the form is being saved
  • In this example all submit buttons are impacted ($(':submit', this))
If you want to target only a specific button:

$(document).ready(function() {
$("#myButton").submit(function(e) {
$('#myButton', this).attr("disabled", "disabled").val("Processing....");
});
});



Oops, this does not work!!!!!!!!!!!!!!!!

The reason being is that the submit action is attached to the form not the "myButton". In order to perform the above and find out which button was pressed and take appropriate action do:

$("form").submit(function(e) {
if (e.originalEvent.explicitOriginalTarget.id == "myButton") {
//Do something
}

});

Intercept a form submit with jQuery and prevent or allow submission

Goal: Intercept a form submit, find out what submit button was clicked/invoked and decide to prevent the submission or continue to submit.

Solution:
$(document).ready(function() {
$("form").submit(function(e) {
if (e.originalEvent.explicitOriginalTarget.id == "myButton") {
if (some status is true continue to submit the form)
return true;
//If the status above is false continue to prompt the user if they want to submit or not
var ok = confirm('Do you really want to save your data?');
if (ok) {
return true;
}
else {
//Prevent the submit event and remain on the screen
e.preventDefault();
return false;
}
}
});

return;
});