Tuesday, March 29, 2011

Refresh Javascript

Here are my favourite links to refresh or introduce Javascript to new comers.

http://www.jslint.com/lint.html

http://vimeo.com/18511621

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)

http://stackoverflow.com/questions/3722820/examples-of-practical-javascript-object-oriented-design-patterns

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
};
}



Monday, March 28, 2011

Page Load VS Init

 

Most web developer already inherits the wrong culture of population of data.
After they graduate from college, without knowing and really understand of page life cycle and state management, They are prune to inherits the bad habits of the culture.

Here are some of the culture which you may still see it in your current project to populate an item.


<asp:DropdownList id="lstStates" runat="server" DataTextField="StateName" DataValueField="StateCode" />

protected override void OnLoad(EventArgs args)
{
if(!this.IsPostback)
{
this.lstStates.DataSource = QueryDatabase();
this.lstStates.DataBind();
}
base.OnLoad(e);
}





As is the nature of databound controls in ASP.NET, the state dropdown will be using ViewState to remember it's databound list of list items.



At the time of this ranting, there are a total of States or countries. Not only does the dropdown list contain a ListItem for each and every state, but each and every one of those states and their state codes are being serialized into the encoded ViewState.



That's a lot of data to be stuffing down the pipe every time the page loads, especially over a dialup connection. I often wonder what it would be like if I explained to my grandmother the reason why her internet is so slow is because her computer is telling the server what all the States / Countries are. Too bad... those extra states and countries dropdown list are really wearing down your bandwidth.



Like the problem with static data, the general solution to this problem is to just disable ViewState on the control. Unfortunately, that is not always going to work. Whether it does depends on the nature of the control you are binding, and what features of it you are dependant on.



In this example, if we simply added EnableViewState="false" to the dropdown, and removed the if(!this.IsPostback) condition, we would successfully remove the state data from ViewState, but we would immediately run into a troubling problem.



The dropdown will no longer restore it's selected item on postbacks.



WAIT!!! This is another source of confusion with ViewState.


The reason the dropdown fails to remember it's selected item on postbacks is NOT because you have disabled ViewState on it.





Postback controls such as dropdownlist and textbox restore their posted state (the selected item of a dropdown ist 'posted') even when ViewState is disabled, because even with ViewState disabled the control is still able to post its value.





It forgets it's selected value because you are rebinding it in OnLoad, which is after the dropdown has already loaded it's posted value. When you databind it again, the first thing it does is throw that into the bit bucket (you know, digital trash). That means if a user selects California from the list, then click on a submit button, the dropdown will stubbornly return the default item (the first item if you don't specify it otherwise).





Thankfully, there is an easy solution: Move the DataBind into OnInit:




<asp:DropdownList id="lstStates" runat="server"   DataTextField="StateName" DataValueField="StateCode" EnableViewState="false" />

protected override void OnInit(EventArgs args)
{
this.lstStates.DataSource = QueryDatabase();
this.lstStates.DataBind();
base.OnInit(e);
}





Yes this will call QueryDatabase everytime you postback but you still can be solved it using simple caching instead of sacrificed the traffic performance in View state.

MVP or MVC

 

Recently, I am trying a very cool new architecture called WebForm MVP which comes as a mediator between Webform and MVC.

This framework is very brilliant and explain the best practice for using Webform.

For example the reason of using Page Init instead of Page load in regards of the viewstate.
This will enforce the best practice for the web developer to build there web application which now fully support for unit testing.

You can check this at http://webformsmvp.com/
and here is the contrib project which support common use presenter and Ioc Container such as Ninject and structure map.
http://webformsmvpcontrib.codeplex.com/

I believe this project will soon be adopted by VS next release.

Here is some brilliant article supported for this.
Viewstate - http://weblogs.asp.net/infinitiesloop/archive/2006/08/03/Truly-Understanding-Viewstate.aspx