Here are my favourite links to refresh or introduce Javascript to new comers.
http://www.jslint.com/lint.html
http://developer.yahoo.com/yui/theater/ by Douglas Crockford
Javascript is a wild language which need to be tamed.
Here are some js pattern which I think best pattern to tackle down the scoping and closure issue which called (Module Pattern)
Recently I build a framework called SiteUI which follows this pattern and give you protection and flexibility.
SiteUI.BaseUI = function (options)
{
//default options
var defaultsOpt = {
//set it to false otherwise, when you disable from the ajax, it when postback it will enable all your control
//but if you have other reason you can do so.
disableAllButtonOnSubmit: false,
ajaxProcessMessage: "Processing..."
};
var options = $.extend(defaultsOpt, options);
//optional value - to disable all button on submit or not
var disableAllButtonOnSubmit = options.disableAllButtonOnSubmit;
var setDisableAllButtonOnSubmit = function (value)
{
disableAllButtonOnSubmit = value;
}
var ajaxProcessMessage = options.ajaxProcessMessage;
var setAjaxProcessMessage = function (value)
{
ajaxProcessMessage = value;
}
//Shopping Cart Control
var shoppingCartControl = null;
var ddlPostbackSelector = '.ddlPostback';
var txtPostbackSelector = '.txtPostback';
var btnPostbackSelector = '.btnPostback';
var chkPostbackSelector = '.chkPostback';
var loadingSelector = '.postbackLoading';
var loaderHtml = "<img src='/images/ajax-loader.gif' class='postbackLoading' style='width:15px;height:15px;position:absolute; z-index:9999;' />";
var isHideLoadingBeforePostbackTemporary = false;
//this to indication a form validator after call back - (CommonFormValidator.js option)
var formValidator_CallBack = function (source, isValid)
{
//alert('formValidator_CallBack : ' + isValid);
if (!isValid)
{
//not valid - hide loading temporary - because this will be called first before click happen
isHideLoadingBeforePostbackTemporary = true;
}
};
//Show loading
var showLoadingBeforePostback = function (source, addLoadingToParent)
{
//alert('showLoadingBeforePostback isHideLoadingBeforePostbackTemporary: ' + isHideLoadingBeforePostbackTemporary);
if (!isHideLoadingBeforePostbackTemporary)
{
//add loader html after
if (addLoadingToParent)
{
$(source).parent().after(loaderHtml);
showPageLoader(); //show page loader
}
else
{
$(source).after(loaderHtml);
showPageLoader(); //show page loader
}
}
else
{
//set it to false again
isHideLoadingBeforePostbackTemporary = false;
}
};
//Clear Loading Function
var clearLoading = function ()
{
$(loadingSelector).remove();
hidePageLoader();
};
//show Page loader - this using jquery.loader this will disable all the button automatically
var showPageLoader = function ()
{
$.loader({
className: "loaderBox",
content: ''
});
};
//hide page loader
var hidePageLoader = function ()
{
$.loader('close');
};
//Show Hide Loading On Content
var showHideLoadingOnContent = function (contentSelector, isShow)
{
if (isShow)
{
$(contentSelector).before(loaderHtml); //add loader after it fades
//show loading
showPageLoader(); //show page loader to hide
$(contentSelector).animate({ opacity: 0 }, 150);
//$(contentSelector).effect('fade',null,500);
}
else
{
//hide loading - remove all loading selector
$(loadingSelector).remove(); //remove loader first
hidePageLoader(); // hide page loader
//just in case if in the beginning it hides this
$(contentSelector).animate({ opacity: 1 }, 150);
//$(contentSelector).effect('fade',null,500);
}
};
//redirect content
var redirectContent = function (url, contentSelector)
{
if (contentSelector)
{ //only if they pass content selector we can redirect with show and hide loading
showHideLoadingOnContent(contentSelector, true);
}
window.location = url;
};
//update content
var updateContent = function (responseText, contentSelector, updateContent_Callback)
{
$(contentSelector).html(responseText);
if (updateContent_Callback)
updateContent_Callback(responseText);
//hide loading on content
showHideLoadingOnContent(contentSelector, false);
};
//Used to track last Request Ajax URL
var lastRequestAjaxUrl = "";
//load content
var loadContent = function (ajaxUrl, relatedData, contentSelector, updateContent_Callback)
{
showHideLoadingOnContent(contentSelector, true);
lastRequestAjaxUrl = ajaxUrl;
$.ajax({
type: "POST",
url: ajaxUrl,
data: relatedData,
complete: function (data, stat)
{
//only process last request ajax url
if (this.url == lastRequestAjaxUrl)
{
//$.log("Update this " + this.url + " VS " + lastRequestAjaxUrl);
if (stat == "success")
{
var isError = true;
updateContent(data.responseText, contentSelector, updateContent_Callback);
}
else
{
updateContent("<p>Error Loading Data : " + stat + ' : ' + data.responseText + "</p>", contentSelector, updateContent_Callback);
}
}
else
{
//$.log("Dispose this ajax " + this.url + " VS " + lastRequestAjaxUrl);
}
}
});
};
// *************** MICROSOFT AJAX INIT - HANDLE ALL AJAX CALL*******************
var initMicrosoftAjaxRequest = function ()
{
/// <reference name="MicrosoftAjax.js"/>
if (typeof (Sys) != 'undefined' && typeof (Sys.WebForms) != 'undefined')
{
Sys.Application.add_init(microsoftAJAX_App_Init);
}
};
var microsoftAJAX_App_Init = function ()
{
Sys.WebForms.PageRequestManager.getInstance().add_beginRequest(microsoftAJAX_BeginRequest);
Sys.WebForms.PageRequestManager.getInstance().add_endRequest(microsoftAJAX_EndRequest);
Sys.WebForms.PageRequestManager.getInstance().add_pageLoaded(microsoftAJAX_pageLoaded);
};
var postbackTriggerElement;
var microsoftAJAX_BeginRequest = function (sender, args)
{
if (disableAllButtonOnSubmit)
{
//disable all button
disableAllSubmitButtons(true);
}
postbackTriggerElement = args.get_postBackElement()
var ctrl = $(postbackTriggerElement);
//remove all validation error
removeAllValidationError();
if (ctrl.is("input:checkbox") || ctrl.is("input:radio"))
{
ctrl.hide(); //hide this check box
if (!ctrl.hasClass("noLoaderOnPostback"))
{
showLoadingBeforePostback(ctrl, true); //show loading on this control
}
}
else if (ctrl.is("input:submit"))
{ //you can change it to other
if (ctrl.hasClass("waitLabelOnPostback"))
{
if (ajaxProcessMessage != "")
{
ctrl.val(ajaxProcessMessage);
}
}
if (ctrl.hasClass("disableOnPostback"))
{
ctrl.attr('disabled', true); //disable but later on postback it will enable or disable based on the behaviour
}
if (!ctrl.hasClass("noLoaderOnPostback"))
{
showLoadingBeforePostback(ctrl);
}
}
else
{ //other items show loading
//show loading on this control
if (!ctrl.hasClass("noLoaderOnPostback"))
{
showLoadingBeforePostback(ctrl);
}
}
};
var microsoftAJAX_pageLoaded = function (sender, args)
{
if (!postbackTriggerElement)
{
return;
}
//get updated panels and referesh
var updatedPanels = args.get_panelsUpdated();
for (i = 0; i < updatedPanels.length; i++)
{
//apply filter textbox
setFilterTextbox(updatedPanels[i].id);
//set rating
setRating(updatedPanels[i].id);
//set date picker
setDatePicker(updatedPanels[i].id);
//set text area counter
setTextAreaCounter(updatedPanels[i].id);
//Call Microsoft AJAX_EndRequest_AdditionalFunciton() if available
if (typeof (microsoftAJAX_PageLoaded_AdditionalFunction) == 'function')
{
microsoftAJAX_PageLoaded_AdditionalFunction(updatedPanels[i].id);
}
}
};
//end request
var microsoftAJAX_EndRequest = function (sender, args)
{
if (args.get_error() != undefined)
{
var errorMessage = args.get_error().message;
args.set_errorHandled(true);
alert("Error in ajax call : " + errorMessage);
clearLoading(); //clear loading
return;
}
if (disableAllButtonOnSubmit)
{
//enable all button
disableAllSubmitButtons(false);
}
//Call Microsoft AJAX_EndRequest_AdditionalFunciton() if available
if (typeof (microsoftAJAX_EndRequest_AdditionalFunction) == 'function')
{
microsoftAJAX_EndRequest_AdditionalFunction();
}
//empty the postback element
postbackTriggerElement = null;
hidePageLoader(); //hide page loader
};
//this can be override by calling set ajax page loaded
var microsoftAJAX_PageLoaded_AdditionalFunction = function (updatedPanelId)
{
//baseUI. microsoft AJAX Page Loaded Additional funxtion
//you can write default function here
};
//this can be override by calling set ajax end request
var microsoftAJAX_EndRequest_AdditionalFunction = function ()
{
//baseUI. microsoft AJAX End Request Additional funxtion
//you can write default function here
};
//override ajax end request
var overrideMicrosoftAJAX_EndRequest_AdditionalFunction = function (func)
{
microsoftAJAX_EndRequest_AdditionalFunction = func;
};
//override ajax page loaded
var overrideMicrosoftAJAX_PageLoaded_AdditionalFunction = function (func)
{
microsoftAJAX_PageLoaded_AdditionalFunction = func;
};
//-----------------------------------------------
//use for postback
var setupPostbackControlEvents = function ()
{
//show loading before postback
$(ddlPostbackSelector).live("change", function (e)
{
//e.preventDefault();//cannot prevent default, it won't go to onclick
showLoadingBeforePostback(this);
});
//filter search keyword
$(txtPostbackSelector).live("change", function (e)
{
//e.preventDefault();//cannot prevent default, it won't go to onclick
showLoadingBeforePostback(this);
});
$(btnPostbackSelector).live("click", function (e)
{
//e.preventDefault(); //cannot prevent default, it won't go to onclick
showLoadingBeforePostback(this);
if ($(this).hasClass("clearValidation"))
{
//check if it has class clear validation
removeAllValidationError();
}
});
$(chkPostbackSelector + " input").live("change", function (e)
{
//e.preventDefault(); //cannot prevent default, it won't go to onclick
showLoadingBeforePostback(this, true);
removeAllValidationError(); //it will clear validation - usually this will change the view
});
};
//setup Dropdown Item
var setupDropdownItem = function ()
{
//find drop down item trigger
$('.dropdownItemTrigger').each(function ()
{
// Make the drop down cart appear/disappear.
var boxSelector = $(this).attr('rel');
// Set natural height and other properties of hidable object.
$(boxSelector).data('naturalHeight', $(boxSelector).height());
$(boxSelector).addClass('hidden');
$(boxSelector).css(
{
display: 'none',
bottom: 'auto'
});
// Create back reference.
$(boxSelector).data('trigger', $(this));
});
$('.dropdownItemTrigger').live("click", function (e)
{
e.preventDefault();
var $trigger = $(this);
// Don't do anything if the drop down item is already animating.
if ($trigger.hasClass('disabled')) return false;
// Find direction.
var direction = $trigger.hasClass('maximise') ? 1 : -1;
// Find effect type.
var classes = $trigger.attr('class'); //.match(/effect-/);
var effect = $trigger.attr('class').match(/\beffect-[a-z]{1,}/);
//alert(effect);
// Make the drop down cart appear/disappear.
var boxSelector = $trigger.attr('rel');
if (boxSelector == shoppingCartControl.shoppingCartControlSelector)
{
shoppingCartControl.checkToReload();
}
$(boxSelector).trigger('dropdowntoggle', [direction, effect]);
});
$('.dropdownItem').live('dropdowntoggle', function (e, direction, fxtype)
{
var $dropdownItem = $(this);
var $trigger = $dropdownItem.data('trigger');
// Ensure direction.
if (isNaN(direction)) direction = $dropdownItem.hasClass('shown') ? -1 : 1;
// Disable trigger.
$trigger.addClass('disabled');
// Set states. / cursor
$trigger.toggleClass('maximise', direction == -1);
$trigger.toggleClass('minimise', direction == 1);
$dropdownItem.toggleClass('hidden', direction == -1);
$dropdownItem.toggleClass('shown', direction == 1);
// Effect
//var currPosition = $dropdownItem.css('top').replace(/([a-z]+)/, '') * 1
var currPosition; // = direction == 1 ? $dropdownItem.height() * -1 : 0;
var displacement; // = $dropdownItem.height(); //$dropdownItem.data('naturalHeight');
var newPosition; // = currPosition + displacement * direction;
//alert(fxtype);
// Toggle drop down item + Effect
if (fxtype == 'effect-slide')
{
// Calculate new values.
currPosition = direction == 1 ? ($dropdownItem.height() * -1) : 0 - 50;
displacement = $dropdownItem.height(); //$dropdownItem.data('naturalHeight');
newPosition = currPosition + displacement * direction;
// Execute
$dropdownItem.css(
{
display: 'block',
top: currPosition
}).animate(
{
top: newPosition
}, 400, function ()
{
$trigger.toggleClass('active', direction == 1);
$trigger.removeClass('disabled');
});
}
else
{
// if (fxtype=='effect-fade') {
// Calculate new values.
var currOpacity = direction == 1 ? 0 : 1;
var newOpacity = direction == 1 ? 1 : 0;
//Execute
$dropdownItem.css(
{
display: 'block',
top: 0,
opacity: currOpacity
}).animate(
{
opacity: newOpacity
}, 400, function ()
{
$trigger.toggleClass('active', direction == 1);
if (direction == -1)
{
//hide the display
$dropdownItem.css({display: 'none'});
}
$trigger.removeClass('disabled');
});
}
});
};
/*this doesn't work for shopping cart in IE 6*/
var setupDropdownItemUsingJqueryUI = function ()
{
//Bind Drop down item - just require this button
$('.dropdownItemTriggerBlind').live("click", function (e)
{
e.preventDefault();
var $trigger = $(this);
// Make the drop down cart appear/disappear.
var boxSelector = $trigger.attr('rel');
//check if it is shopping cart control
if (boxSelector == shoppingCartControl.shoppingCartControlSelector)
{
shoppingCartControl.checkToReload();
}
//using jquery.ui toggle( effect, [options], [speed], [callback] )
$(boxSelector).toggle('blind', null, 300);
});
}
/*Setup Events - This will be call unsynchronously*/
var setupEvents = function ()
{ //setup postback control events
setupPostbackControlEvents();
//setup drop down
setupDropdownItem();
//for login trigger
$(".txtTriggerLogin").live("change", function (e)
{
$(".btnLogin").click();
});
$(".txtTriggerLogin").live("keypress", function (e)
{
//$.log('run from DISABLE DEFAULT BUtton');
return runFunctionOnEnter(e,
function ()
{
$(".btnLogin").click();
}
);
});
// Removes SEO text in navItems
$(".clearText").html("");
$(".clearText").removeClass("clearText");
// Clears input fields on Focus
// Title must match the value to clear.
$(".clearField").live("blur", function ()
{
if ($(this).val() == "")
{
$(this).val($(this).attr("title"));
};
});
$(".clearField").live("focus", function ()
{
if ($(this).val() == $(this).attr("title"))
{
$(this).val("");
};
});
//bind shopping cart
//cart = new SiteUI.ShoppingCart();
//cart.updateCartHover();
};
/*Constructor*/
var bindDefaultBehaviour = function ()
{
//init ajax request behaviour
initMicrosoftAjaxRequest();
//disable default button
disableDefaultButton();
//set droppy menu
$('#dropdownNavigation').droppy();
shoppingCartControl = new SiteUI.ShoppingCartControl();
//run this async
setTimeout(function ()
{
setupEvents();
//apply filter textbox
setFilterTextbox();
setRating();
setDatePicker();
setTextAreaCounter();
}, 0);
};
var init = function ()
{
}
//return public method
return {
bindDefaultBehaviour: bindDefaultBehaviour,
formValidator_CallBack: formValidator_CallBack,
redirectContent: redirectContent,
setupPostbackControlEvents: setupPostbackControlEvents,
loadContent: loadContent,
updateContent: updateContent,
clearLoading: clearLoading,
showLoadingBeforePostback: showLoadingBeforePostback,
showHideLoadingOnContent: showHideLoadingOnContent,
showPageLoader: showPageLoader,
hidePageLoader: hidePageLoader,
setAjaxProcessMessage: setAjaxProcessMessage,
setDisableAllButtonOnSubmit: setDisableAllButtonOnSubmit,
overrideMicrosoftAJAX_EndRequest_AdditionalFunction: overrideMicrosoftAJAX_EndRequest_AdditionalFunction,
overrideMicrosoftAJAX_PageLoaded_AdditionalFunction: overrideMicrosoftAJAX_PageLoaded_AdditionalFunction
};
}