Javascript Management
Best Practices
Best Practices
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<link rel="stylesheet" href="css/style.css">
<script src="modernizr.2.0.6.min.js"></script>
</head>
<body>
<header role="banner"> </header>
<section id="main" role="main"> </div>
<footer role="content info"></footer>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.6/jquery.js"></script>
<script>
window.jQuery ||
document.write('<script src="jquery.1.6.min.js">\x3C/script>')</script>
<script src="script.js"></script>
</body>
</html>
var myName = 'Morten',
yourName = 'ANUGs';
function switchNames(name1, name2) {
var temp = name1;
name1 = name2;
name2 = temp;
}
switchNames(myName, yourName);
// myName => Morten
// yourName => ANUGs
// temp, name1, name2 => undefined
function switchNamesAgain() {
temp = myName;
myName = yourName;
yourName = temp;
}
switchNamesAgain();
// myName => ANUGs
// yourName => Morten
// temp => Morten
// this => DOMWindow
undefined = true;
notThere === undefined // => false
// IIFE - Immediately-Invoked Function Expression
(function () {
// All vars and functions are in this scope only
// Still maintains access to all globals
})();
// Importing globals
(function ( $, undefined ) {
// The native undefined is triggered
// $ is properly assigned
})( jQuery );
// this => DOMWindow
// Ensure namespace
var WEBAPP = WEBAPP || {};
(function ( $, undefined ) {
WEBAPP.Properties = {
selectedCssClass : 'sel',
validationErrorCssClass : 'error'
}
WEBAPP.Handlers = {
init : function(){
$( '#siteSearch' ).bind( 'keyup', WEBAPP.Navigation.search )
}
}
WEBAPP.Navigation = {
search : function( e ){
var $input = $( this ),
searchTerm = $input.val();
WEBAPP.Utils.evalKey( e ) === 'RETURN' && location.hash = searchTerm;
}
}
WEBAPP.Utils = {
evalKey: function( e ) {
var KEY = [];
KEY[ 13 ] = 'RETURN';
return KEY[ e.keyCode ? e.keyCode : e.which ];
}
}
$( WEBAPP.Handlers.init ); //=> $( document ).ready();
})( jQuery || ender );
// Ensure namespace
var WEBAPP = WEBAPP || {};
WEBAPP.Module = (function ( $, undefined ) {
// Privates
var privateVar = 5,
privateMethod = function () {
return 'Private Test';
};
return {
// Publics
publicVar: 10,
publicMethod: function () {
return ' Followed By Public Test ';
},
// With acces to private members
getData: function () {
return privateMethod() + this.publicMethod() + privateVar;
}
}
})( jQuery || ender );
WEBAPP.Module.getData(); //=> 'Private Test Followed By Public Test 5'
// Augmentation
WEBAPP.Module = (function ( M, $, undefined ) {
M.anotherMethod = function () {
return 'Augmented method';
};
return M;
})( WEBAPP.Module, jQuery || ender );
WEBAPP.Module.anotherMethod(); //=> 'Augmented method'
// Loose Augmentation
WEBAPP.Module = (function ( M, $, undefined ) {
M.anotherMethod = function () {
return 'Loosely augmented method';
};
return M;
})( WEBAPP.Module || {}, jQuery || ender );
WEBAPP.Module.anotherMethod() //=> 'Loosely augmented method'
var WEBAPP = WEBAPP || {}; // Ensure namespace
WEBAPP.Module = (function ( M, $, undefined ) { // Augmentation loads first
return $.extend( M, {
anotherMethod : function () {
return 'Loosely augmented method';
}
});
})( WEBAPP.Module || {}, jQuery || ender );
WEBAPP.Module = (function ( M, $, undefined ) { // Module afterwards
var privateVar = 5,
privateMethod = function () {
return 'Private Test';
};
return $.extend( M, {
publicVar : 10,
publicMethod : function () {
return ' Followed By Public Test ';
},
getData : function () {
return privateMethod() + this.publicMethod() + privateVar;
}
});
})( WEBAPP.Module || {}, jQuery || ender );
WEBAPP.Module.anotherMethod(); //=> Loosely augmented method
WEBAPP.Module.getData(); //=> Private Test Followed By Public Test 5
// Typical ASP.NET AJAX request
$.ajax({
data: JSON.stringify( { command : {} } ),
url: '/services/service.svc/methodName',
type: 'post',
contentType: 'application/json; charset=utf-8',
success : function( result ){
var res = JSON.parse( result );
res.d; //=> What we actually want
},
error : function( error ){
// Do something with error
}
});
// Get's tedious!
// AJAX setup with converter
$.ajaxSetup({
type : 'post',
contentType: 'application/json; charset=utf-8',
dataType: 'dotnetjson',
converters: { // New to 1.5
"json dotnetjson": function( response ) {
return response.hasOwnProperty( 'd' ) ? response.d : response;
}
},
error : function( error ){
// Do something with error
}
});
// AJAX prefilter - New to 1.5
$.ajaxPrefilter( 'dotnetjson', function( options, originalOptions ){
options.data = JSON.stringify( originalOptions.data );
options.url = '/services/service.svc/' + options.url;
});
// Typical ASP.NET AJAX request is now
$.when($.ajax({
data: { command : {} },
url: 'methodName'
}))
.done(function (result) {
result //=> Is exactly what we want it to be
};
// Animation speeds
$.fx.speeds; //=> { _default: 400, fast: 200, slow: 600 }
// Browser specfic animation speeds
WEBAPP.Properties = {
animationSpeeds: (function( is ) {
var factor = is.msie && is.version <= 7 ? 2 : 1; // Older IE browsers are sped up
return {
normal: 400 / factor,
fast: 200 / factor,
slow: 600 / factor
}
})( $.browser )
}
// Are merged into the FX object
$.extend( $.fx.speeds, WEBAPP.Properties.animationSpeeds );
// And default is set
$.fx.speeds._default = $.fx.speeds.normal;
// Now we can use
$( '.element' ).fadeIn(); //=> 400 ms ( 200 ms for older IEs )
$( '.element' ).fadeIn( 'fast' ); //=> 200 ms ( 100 ms for older IEs )
$.fn; //=> $.prototype
// Simple plugin
$.fn.myPlugin = function () {
this.addClass('myPluginClass');
}
$('#element').myPlugin();
$('#element').hasClass('myPluginClass')); //=> true
// Simple plugin supporting chaining
$.fn.myPlugin = function () {
this.addClass('myPluginClass');
return this;
}
$('#element')
.myPlugin()
.hasClass('myPluginClass')); //=> true
// And even simpler
$.fn.myPlugin = function () {
return this.addClass('myPluginClass');
}
$('#element')
.myPlugin()
.hasClass('myPluginClass')); //=> true
(function ( $, undefined ) {
WEBAPP.Handlers = {
init : function(){
$( '#main' ) // Keep main wrapper and chain
.find( 'table' )
.zebraTable()
.end()
.find( '.teasers' )
.equalizeHeights();
.end();
}
}
WEBAPP.Utils = {
setZebraTable: function( tables ) {
tables.each(function(){
$( this ).find( 'tr:nth-child(2n)' ).addClass( WEBAPP.Properties.rowClass );
});
}
}
$.fn.extend({
zebraTable : function() {
WEBAPP.Utils.setZebraTable( this );
return this;
},
equalizeHeights : function() { },
reverse : [].reverse //<= Lets steal it from native Array
});
$( WEBAPP.Handlers.init );
})( jQuery || ender );
(function ( $, undefined ) {
WEBAPP.Handlers = {
init : function(){
$.subscribe( '/navigation/addToBasket/success', WEBAPP.Navigation.updateMiniBasket );
$.subscribe( '/navigation/addToBasket/success', WEBAPP.Navigation.updateOrderCount );
$.subscribe( '/navigation/specialOrder', WEBAPP.Navigation.notifySpecial );
$( '.addToBasket' ).click( WEBAPP.Navigation.addToBasket );
}
}
WEBAPP.Navigation = {
addToBasket : function () {
$.when($.ajax({
url: 'addToBasket',
data: { command : {} }
}))
.done( function( data ) {
$.publish('/navigation/addToBasket/success', [ data.lineItems ]);
data.specialOrder && $.publish('/navigation/specialOrder', []);
});
}
}
$( WEBAPP.Handlers.init );
})( jQuery || ender );
// Datagrid with 500 rows
$( '#dataGrid a.edit' ).click( WEBAPP.Navigation.editRow );
// Assigning 500 handlers in a jiffy is BAD
// So don't assign 500 handlers in a jiffy
// Much better
$( '#dataGrid' ).click( function( e ){
var $target = $( e.target );
$target.is( 'a.edit' ) && WEBAPP.Navigation.editRow( e, $target );
});
// 1 binding catching events when they bubble up
// Makes it work on a dynamic DOM too
// Delegate is an abstraction of the same
$( '#dataGrid' ).delegate( 'a.edit', 'click', WEBAPP.Navigation.editRow );
// But it can be too late to cancel the bubbling
e.stopPropagation(); //=> Has already bubbled all the way up!
// Intercept bubbling on a child element
$( '#dataGrid' ).find( 'tbody' ).delegate( 'a.edit', 'click', function( e ){
specialCase && e.stopPropagation();
});
// Datagrid with 500 rows
var $grid = $( '#dataGrid' );
// Highlight all edit buttons
$grid.find( 'a.edit' ).addClass( 'highLight' );
// Accesses layout properties for each element and results in 500 reflows
// Instead we create a Document Fragment
var $gridFragment = $grid.detach(); //=> 1 reflow
// And do it off-the-DOM
$gridFragment.find( 'a.edit' ).addClass( 'highLight' ); //=> 0 reflows
// Then append the Document Fragment again
$( 'body' ).append( $gridFragment ); //=> 1 reflow
// Thus saving 498 reflows. Yay!
// FAST: ID & element selectors
$( '#element, form, input' );
// Backed by .getElementById() and .getElementsByTagName()
// SLOWER: Class selectors
$( '.element' );
// Backed by .getElementsByClassName() except for IE5-8
// SLOWEST: Pseudo & attribute selectors
$( ':visible, :hidden' );
$( '[ attribute=value ]' );
// Backed only in latest browsers by .querySelector() and .querySelectorAll()
// Its good practice to create a context and keep it
$( '#main' )
.find( '.element' )
.show();
.end()
.find( '.anotherElement' )
.hide()
.end();
// Spamming a service every time input changes
$( input.amount ).change( function(){
$.when($.ajax({
url: 'updateBasket',
data: { command : {} }
}))
.done( function( data ) {
$.publish('/navigation/updateBasket/success', []);
});
});
// Instead we set the call up as a subscription
$.subscribe( '/navigation/updateBasket', function(){
$.when($.ajax({
url: 'updateBasket',
data: { command : {} }
}))
.done( function( data ) {
$.publish('/navigation/updateBasket/success', []);
});
});
// And publish that event after a short idle
$( input.amount ).change( function(){
$.doTimeout( timerId, 500, function(){
$.publish('/navigation/updateBasket', []);
}, true);
});
/// <reference path="jquery-1.6.2-vsdoc.js" />
$; //=> We're on a roll
(function ( $ ) {
$; //=> Intellisense lost in closure :|
})( jQuery );