Advanced Techniques in JavaScript and jQuery

kevin-murray-v1Welcome to this review of the Pluralsight course Advanced Techniques in JavaScript and JQuery by Kevin Murray.

Kevin is a lifelong learner with over 30 years in the IT industry. He works on web and mobile applications as well as the databases and web services to support them. With a gift for learning new languages, he is able to rapidly apply his broad experience in new environments.

Kevin has also produced courses on Making Work from Home Work for YouManaging Developers and a Developer’s Guide to SQL Server CLR Integration

Introduction

Traditional JavaScript Functions

gobj is bad name

function1();
function2();
obj.function3();
gobj.function4();

Draw Version 1

HTML5 Canvas Draw Function

+ Contained in a global namespace
+ Doesn’t accept parameters
+ Verifies the canvas context is accessible

Extending Draw – Two Parameters

Add some parameters

+ Left Position
+ Right Position

Stub Functions

Stub Downsides:

+ Documentation needed
+ Multiple versions of legacy code not supported
+ Wrong result if calling wrong function

Default Parameters

Provide defaults within the function when parameters are missing

+ Useful when one or two parameters are optional
+ Optional parameters should be at the end of the parameters list
+ If more than one parameters is optional, certain assumptions must be made if only one is supplied

 

All Default Values

Provide defaults for remaining parameters

  • All parameters are optional
  • Order of parameters is still important
  • May have to pass “undefined” as a position holder

Changing to an Object Parameter

Use one object parameter instead of multiple native types

draw: function(options)
{

if (typeof options !== ‘object’)
options = {};

}

  • Objects are just named value pairs
  • Properties of an object are optional by nature
  • The order of object properties does not matter
  • Object parameters are passed by reference

Legacy Support with Object

Honor contract with legacy code

draw: function(options)
{

if (typeof left !== ‘undefined’)
if (typeof left === ‘object’)
options = left;
else
options = { left: left,
top: top,
width: width,
height: height,
stroke: stroke,
fill: fill };

}

Default Values in an Object

// Setting canvas width will clear its contents
if (options.clear)

drawOptions:
{ left: 10, top: 10,
width: 100, height: 100,
stroke: ‘black’, fill: ‘silver’,
clear: false
}

Function Summary

Object Parameters are clean and future proof your code

Event Handling

Shorthand Methods

Handling standard events

  • Usually use an anonymous function
  • For common events, shorthand method can be used

.blur()
.change()
.click()
.dblclick()
.focus(), focusin(0,.focusout()
.hover()
.keydown(),.keypress(), .keyup()
.load()
.mousedown(), .mouseenter(), .mouseleave(), .mousemove(), .mouseout(), .mouseover(), .mouseup()

etc.

Work Area

$(function()
{ $(‘.clickable’).click(function()
{ $(‘#Messages’).append(‘click occurred
‘);
});
});

Message Function

  • Centralize message handling
  • Use an object parameter
  • Explicitly set what “this” references

$(function()
{ var showEventMessage = function(options)
{ options = $.extend(
{ eventType: ‘CLICK’,
eventTarget: this,
suffix: ‘

}, options);
var message = options.eventType + ‘: ‘ +
(options.eventTarget.nodeName || ‘unknown’) +
options.suffix;
$(‘#Messages’).append(message);
};

$(‘.clickable’).click(function()
{ showEventMessage.call(this, { suffix: ‘!
‘ });
});
});

Making use of call, and its close relative apply, can help simplify numerous check within a function
that try to determine what this references. In our case, we can be sure that this will refer to

More Elements

  • Add clickable to more elements
  • Add more event handlers
  • Preventing default behaviour
  • Internet Explorer is different

Chasm between user and developer expectations

$(‘.clickable’)
.click(function()
{ showEventMessage.call(this);
}}
.dblclick(function()
{ showEventMessage.call(this, { eventType: ‘double click’}};
}}
.mousedown(function()
{ showEventMessage.call(this, { eventType: ‘mouse down’}};
return false;
});
}};

Anonymous function passed in is not executed right away. It is just attached as a mousedown event handler to
the element

IE and Firefox handle events differently

Propagation

+ Events bubble up the DOM until handled

Ways to cancel event processing:

+ Return false from event handler (any version)
+ Call preventDefault() on the event (IE9+ when native)
+ Call stopPropagation() on the event (jQuery)
+ Call stopImmediatePropagation() on the event (jQuery)

jQuery provides methods to determine if the state of the event has been changed
+ isPropagationStopped()
+ isImmediatePropagationStopped()
+ isDefaultPrevented()

Some browsers may not support returning false from event handlers.
W3C spec doesn’t require it

$(‘.clickable’)
.click(function(event)
{ showEventMessage.call(this);
}}
.dblclick(function(event)
{ showEventMessage.call(this, { eventType: ‘double click’}};
}}
.mousedown(function(event)
{ showEventMessage.call(this, { eventType: ‘mouse down’}};
event.stopPropagation(); //or stopImmediatePropagation
event.preventDefault(); //call method on the event object
})
.mousedown(function(event)
{ showEventMessage.call(this, { eventType: event.type,
suffix: ‘#2
‘ });
});
}};

For stopPropagation this outputs:

mousedown: H5
mousedown: H5#2

For stopImmediatePropagation it outputs:

mousedown: H5

To manage the event without returning false, use stopPropagation()
it will NOT be passed up the DOM tree. Mouse event is only processed one time.

No Shorthand

Event Handlers Without Shorthand

.bind()
.unbind()
.live()
.die()
.delegate()
.undelegate()
.on() //supercedes bind, live and delegate
.off() //supercedes unbind, die, and undelegate
.one() //attach event handler that will only be processed one time

Summary

  • Shorthand methods ultimately reference the .on() method
  • Event bubble up the DOM from the innermost elements
  • .bind(), .live(), and .on() are other ways to attach events
  • .on is the preferred attachment method

Advanced Event Handling

Event Handler Methods

  • Change shorthand methods to use .on() method
  • Consolidate event handler functions
  • Pay attention when using .off()

Named Functions

  • Use a named function as the event handler
  • Easy to remove
  • Can be centralized in a library
  • Updating one handler function has global impact

Namespace

Event namespace doesn’t interfere with processing of system-generated events
Its part of how jQuery manages events

$(‘.clickable’)
.on(‘click demo dblclick.demo mousedown.demo’, function(event)
{ if(!event.isPropoagationStopped() &&
!event.isImmediatePropagationStopped() &&
!event.isDefaultPrevented())
{ …

Using a namespace makes it easy to target specific event handlers for removal

Delegation

+ Bind event handlers higher in the DOM
+ Efficiency
+ Centralized processing

$(‘li’).bind(‘click’, function(event)
{ //process click event
})

Custom Events

+ Create new events for DOM elements
+ Create events for regular object
+ Initiate a custom event with .trigger()

$(‘.clickable’)
.on(‘click’, function(event)
{ var $this = $(this),
clickCount = ($this.data(‘clickcount’) || 0) +1;

$this.data(‘clickcount’, clickCount);

showEventMessage.call(this,
{ eventType: ” + clickCount + ‘) ‘ + event.type });

if (clickCount === 3)
$this.trigger(‘click3’);
})
.on(‘click3’, function(event)
{ event.stopPropagation();
showEventMessage.call(this, {eventType: event.type});
$(this).addClass(‘highlight’); //highlighted in yellow
});

Can attach event handler to internal object stored in memory just as we can with objects in the DOM:

$(internalObject)
.on(‘recordsloaded’, function(event)
{ showEventMessage.call(this, {eventType: event.type});
$.each(internalObject.records, function()
{ $(‘#Messages’).append(‘ — ‘ +
this.description + ‘: ‘ + this.value + ‘
‘);
});
});

Event Parameters

$(notifyObject)
.on(‘recordsloaded’, function(event)
{ showEventMessage.call(this, {eventType: event.type});
$.each(internalObject.records, function()
{ $(‘#Messages’).append(‘ — ‘ +
this.description + ‘: ‘ + this.value + ‘
‘);
});
});

Using jQuery Deferred Objects

Kevin says most JavaScript developers have been conditioned to use callback functions and many aren’t aware of jQuery’s deferred objects.

jQuery Deferred Processing

This is an implementation of the JavaScript promise, and Kevin explains how these work in this clip.

A promise states that the result of an asynchronous process will be reported to the calling code at some point in the future.

Kevin says the Promise pattern is underused because many developers are unaware of it.

Traditional AJAX Processing

$.get(‘SomeFile.html’, function(result)
{ // This is the callback function
//Process the result and set some flag to indicate completion
});

Kevin briefly discusses how traditional AJAX processing gets problematic

Loading Dynamic Content – Layout

As an example, let’s see if we can deliver the following requirements:

  • Load content into three <div> elements – asynchronously
  • When all content is loaded, enable another element
  • Only load content when user clicks the “Load” button

We see the layout, and we’re using CSS to style the divs and position them side-by-side.

Kevin also shows us the HTML and the CSS that generates our prototype webpage. The only JavaScript so far is an empty document ready function.

Section 1 Content

First we use jQuery to listen for the click on the Load button, and run an anonymous function in response to the click.

$(function()
{
$(‘#Load’).click(function()
{
$(‘#Section1’).load(‘Content1.html’, function()
{   $(‘#Proceed’).removeAttr(‘disabled’);
}
});
});

Everything seems to work properly at first, but there are a couple of problems. Kevin says we can’t trust our eyes.

Loading All Content

Debugging timing problems can be very troublesome. Adding tracing logic could make everything work correctly, and it could fail to work again when the tracing code is removed.

Common Pattern

Using nested callbacks. Bleugh!:

$(function()
{
$(‘#Load’).click(function()
{
$(‘#Section1’).load(‘Content1.html’, function()
{
$(‘#Section2’).load(‘Content2.html’, function()
{
$(‘#Section3’).load(‘Content3.html’, function()
{               $(‘#Proceed’).removeAttr(‘disabled’);
});
});
});
});

This is the Christmas tree effect that was covered in Wee Higbee’s Reasoning About Asynchronous JavaScript course.

We can do better.

Deferred Object

Kevin says many people are left scratching their heads even after reading the documentation.

When all content is loaded then we want to enable the proceed button. So this suits the jQuery syntax:

$(‘#Load’).click(function()
{    $.when{

$.get(‘Content1.html’, function(result)
{     $(‘#Section1’).html(result);
},    ‘html’),
$.get(‘Content2.html’, function(result)
{     $(‘#Section2’).html(result);
},    ‘html’),
$.get(‘Content3.html’, function(result)
{     $(‘#Section3’).html(result);
},    ‘html’)
}
.then(function()
{       $(‘#Proceed’).removeAttr(‘disabled’);
});
});
});

Kevin runs through how this code works. Basically it implements the when-then statement above.

We see that this works as expected. The code is more extensible and maintainable then using callbacks.

When/Then pattern

  • Can include as many gets as desired
  • Then processing only occurs once all When methods complete
  • Extensible, but readability could be improved

LoadSection Function

Create a function to load content

  • Load specified URL into specified element
  • Return the results of a “get” method
  • Needs two parameters – for now

Negative Testing

What happens if we have

  • Check invalid URL
  • Check blank section element
  • Alternate syntax for When/Then

Proceed button isn’t enabled if invalid URL

$(function()
{ var loadSection = function(options)
{

}

$(‘#Load’).click(function()
{ $.when(
loadSection({ selector: ‘#Section1’, url: ‘Content11.html’ }),
loadSection({ selector: ‘#Section2’, url: ‘Content2.html’ }),
loadSection({ selector: ‘#Section3’, url: ‘Content3.html’ }),
)

.then(function()
{ $(‘#Proceed’).removeAttr(‘disabled’);
},function(result)
{ $(‘#Messages’).append(‘Failure!
‘)
.append(‘Result:’ + result.statusText + ‘
‘);
});
});
});

This above code doesn’t handle multiple errors. Try using the .fail method using chaining:


.then(function()
{ $(‘#Proceed’).removeAttr(‘disabled’); //success
})
.fail(function(result)
{ $(‘#Messages’).append(‘Failure!
‘)
.append(‘Result:’ + result.statusText + ‘
‘);
});
});
});

The when, then and fail methods all execute almost immediately.
They setup event handlers for deferred processing.

Deferred Methods

Some methods available on deferred objects

Return deferred object for a process flow

  • when
  • promise

(the promise object returns just a subset of the deferred object that lets us attach handlers like we’ve been doing, but doesn’t allow the state of the deferred object that lets us attach handlers like we’ve been doing, but doesn’t allow the state of the deferred object to be changed by us)

Attach handler functions to deferred object

  • then
  • done – This method attaches a success handler function. The function will only execute when all processing completes successfully.
  • fail – Attaches a failure handler function. Only called once, regardless of number of errors
  • progress – Attaches a progress handler function. Most commonly for updating progress bars
  • always – like finally after a try/catch

(these functions will be called when the event associated with a name occurs for that object.)

Change state of deferred object

  • resolve – change state to indicate successful completion
  • reject – change state to indicate failure

$(function()
{ var loadSection = function(options)
{

}

$(‘#Load’).click(function()
{ var myDefer = $.when(
loadSection({ selector: ‘#Section1’, url: ‘Content11.html’ }),
loadSection({ selector: ‘#Section2’, url: ‘Content2.html’ }),
loadSection({ selector: ‘#Section3’, url: ‘Content3.html’ }),
) //this returned object can have it’s state changed
.promise() //make sure nothing we do accidentally changes the state
.done(function()
{ myDefer.done(function()
{ $(‘#Messages’).append(’embedded done handler
‘);
});
})
.done(function()
{ $(‘#Proceed’).removeAttr(‘disabled’);
})
.fail(function(result)
{ $(‘#Messages’).append(‘Failure!
‘)
.append(‘Result:’ + result.statusText + ‘
‘);
})
.always(function()
{ $(‘#Proceed’).removeAttr(‘disabled’);
});
});
});

There are other AJAX methods within jQuery that also return a deferred object.

Dynamic Pages

The dynamically loaded pages had static content

Dynamic pages vs static pages

  • Scripts execute on loaded page
  • Loaded page also loads embedded elements
  • Loaded page may wait for user interaction

Our previous logic could fail because we only monitor the successful loading of content and not whether any embedded scripts are executed.

Content1a.html

$(function()
{ setTimeout(function()
{ $(‘#Content1’)
.append(”)
.append(‘more content loaded dynamically’);
}, 2000);
});

Creating a Deferred Object

A self-managed deferred object is useful when

  • Writing long running processes
  • Loading content that contains embedded content
  • Wrapping a process flow into a single process

$(function()
{ var loadSection = function(options)
{ var defer = $.Deferred(),
$div;

if (typeof options !== ‘object’)
return defer.reject({ statusText: ‘Expecting parameter object’ });
//This will trigger the fail method using the statusText object

options.selector = options.selector || ”;
options.url = options.url || ”;

if (options.url === ”)
return defer.reject({ statusText: ‘Missing URL’ });

$div = $(options.selector)
if ($div.length > 0)
{ $div.load(options.url, function()
{ defer.resolve();
});
}
else
//we didn’t find the selector element
defer.reject({ statusText: ‘Error in selector’});

return defer; //immediately return this.
//It will remain in a pending state until we either resolve or reject it
//may have succeeded or failed
}

Defer will get resolved even if the load method fails. jQuery calls a success callback upon completion of the load method regardless of whether it is truly successful or not.

jQuery defer method can only have one failure. If we need to know more, we have to do it on each deferred object we create.

Current state of affairs

  • Proceed button enabled before dynamic content is loaded
  • Meaningful error messages
  • Only one error message per process flow

Using Deferred Objects

$(function()
{ var showMessage = function(options)
{ if (typeof options !== ‘object’)
options = { message: ‘Parameter not an object’, error: true };

options.message = options.message || false;
options.error = options.error || false;

$(‘#Messages’)
.append(options.error ? ‘Error: ‘ : ”)
.append(options.message)
.append(‘
‘);
},
loadSection = function(options)
{ var defer = $.Deferred(),
$div, msg;

if (typeof options !=== ‘object’)
{ msg = ‘Expecting parameter object’;
showMessage({ message: msg, error: true });
return defer.reject({ statusText: msg });
}

options.selector = options.selector || ”;
options.url = options.url || ”;
options.dynamic = options.dynamic || false;

if (options.url === ”)
{ msg = ‘Missing URL’;
showMessage({ message: msg, error: truew });
return defer.reject({ statusText: msg });
}

$div = $(options.selector)
if ($div.length > 0)
{ $.get(options.url, function(){}, ‘html’)
.done(function(result)
{ $div.html(result);
if (!options.dynamic)
{ defer.resolve();
}
})
.fail(function(result)
{ msg = ‘Could not load URL:’ + options.url;
showMessage({ message: msg, error: true });
defer.reject(result);
});
if (options.dynamic)
$div.off(‘complete, failure’)
.on(‘complete’, function(event)
{ defer.resolve();
})
.on(‘failure’, function(event, result)
//event e.g. click, blur, mouseover
//additional info in result parameter
{ msg = ‘Dynamic content: ‘ + result.statusText;
showMessage({ message: msg, error: true });
defer.reject(result);
});
}
else
{ msg = ‘Error in selector’;
showMessage({ message: msg, error: true });
defer.reject({ statusText: msg });
}

return defer.promise();
}

$(‘#Load’).click(function()
{ $.when(
showMessage({ message: ‘Started processing’ }),
//additional dynamic property
loadSection({ selector: ‘#Section1’, url: ‘Content1b.html’,
dynamic: true }),
loadSection({ selector: ‘#Section2’, url: ‘Content2.html’ }),
dynamic: true }),
loadSection({ selector: ‘#Section3’, url: ‘Content3a.html’,
dynamic: true }),
showMessage({ message: ‘Done with processing’ })
)
.done(function()
{
})
.fail(function(result)
{ $(‘#Messages’).append(‘Failure!
‘)
.append(‘Result:’ + result.statusText + ‘
‘);
})
.always(function()
{ $(‘#Proceed’).removeAttr(‘disabled’);
});
});

We can get two error messages returned.
The second message is in response to our fail method that’s part of the when block.

Takeaway:
When we reject our deferred object, that immediately rejects the deferred object used by the when block.

That causes the failed process and also causes the always method to immediately process.

Once the state of a deferred object is established, it can’t be changed.

Our current logic

When processing these embedded functions, run the done function immediately upon success, or run the fail function immediately upon failure.

That’s exactly what is happening in this case. Since we’ve purposely introduced negative test into this process, we’re encountering something that may not become apparent for a very long time. Under normal circumstances, this situation may not present itself for months.

What we really want our logic to be

When processing the embedded functions, report errors to the message area and keep processing. When everything is processed, enable the Proceed button.

The key point of that logic is that we’re handling the errors directly in the loadSection function.

There is no need to ever reject the deferred object that we’re using.

Once we report the error message we can resolve the deferred.

The only thing we’re doing in the fail logic is duplicating the error message.

[Changes all reject methods to resolve methods]

Now the Proceed button doesn’t enable until after the dynamic content is loaded.
Even we an error is present.

We must consider WHEN, not IF we get an error. Logic must hold up in unexpected circumstances.

Using Deferred Objects

Kevin explains the pro’s and cons of deferred Objects:

  • Use a deferred object in a function we create
  • Notify when dynamic content is loaded
  • Resolve or Reject deferred object as appropriate
  • Combining deferred objects with custom events give maximum flexibility in process control
  • Functions that don’t return a deferred object are resolved immediately – when embedded in a WHEN block
  • An embedded deferred object that is rejected will cause the containing deferred object to be rejected, also
  • Negative testing is very important

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s