Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at CFinNC 2009 (Raleigh, North Carolina) with:

An Example Of Overloaded Functions With Very Different Sub-Function Implementations

Posted by Ben Nadel

A few days ago, I blogged about overloading Javascript functions with a sub-function approach. In that post, I demonstrated that branching logic could be factored-out into the core function in such a way that the individual sub-functions could execute with a very narrow set of concerns. Such a separation might seem overkill when the difference in arguments is not terribly influential. But, sometimes, a difference in invocation arguments can lead to a completely different execution. I wanted to take a quick moment to consider such a scenario in order to look at why separating branching logic is important.

I recently demonstrated how to use jQuery's animate() method to power easing-based iteration. Now, I want to take that concept and extend it to use argument-based branching logic in which the "duration" argument of the ease() method becomes optional. In this overloaded method signature, the duration-based easing will use the animate() implementation (as it did before) while the non-duration-based iteration will use a standard FOR-loop.

  • <!DOCTYPE html>
  • <html>
  • <head>
  • <title>Overloading The Ease Method Based On Arguments</title>
  • <script type="text/javascript" src="./jquery-1.4.2.js"></script>
  • <script type="text/javascript">
  •  
  • // I am the easing iteration funciton. This is built on top
  • // of the core animate function so that it can leverage the
  • // built-in timer optimization.
  • jQuery.ease = function( start, end, duration, easing, callback ){
  •  
  • // Check to see how many arguments we have. If we only
  • // have four, then we are using standard iteration. If
  • // we have five, we are using duration-based iteration.
  • if (arguments.length == 4){
  •  
  • // Standard iteration.
  • return(
  • jQuery.ease.iterate.apply( this, arguments )
  • );
  •  
  • } else {
  •  
  • // Duration-based iteration.
  • return(
  • jQuery.ease.iterateWithDuration.apply( this, arguments )
  • );
  •  
  • }
  • };
  •  
  •  
  • // I am the standard iteration method for easing.
  • // Essentially, I am just a FOR-LOOP with a given
  • // callback to execute per iteration.
  • jQuery.ease.iterate = function(
  • start,
  • end,
  • easing,
  • callback
  • ){
  •  
  • // Keep track of the iterations.
  • var stepIndex = 0;
  •  
  • // Use the difference between the start and end values
  • // in order to mimic duration. We are going to assume
  • // that the the FOR-Loop we are using has a +=1 nature.
  • var duration = Math.max(
  • Math.abs( end - start ),
  • 1
  • );
  •  
  • // Keep looping until the iteration index equals the
  • // end index.
  • for (var i = start ; i != end ; true ){
  •  
  • // Execute the callback for this iteration.
  • callback(
  • i,
  • stepIndex++,
  • start,
  • end
  • );
  •  
  • // Update the index value for our easing.
  • i = jQuery.easing[ easing ](
  • (stepIndex / duration),
  • stepIndex,
  • start,
  • (end - start),
  • duration
  • );
  •  
  • }
  •  
  • // Execute the callback once for end value (which would
  • // not have executed in the FOR-LOOP).
  • callback( i, stepIndex, start, end );
  • };
  •  
  •  
  • // I am the duration-based iteration method for easing.
  • // I use jQuery's animate() method to power the easing
  • // over the given time frame.
  • jQuery.ease.iterateWithDuration = function(
  • start,
  • end,
  • duration,
  • easing,
  • callback
  • ){
  •  
  • // Create a jQuery collection containing the one element
  • // that we will be animating internally.
  • var easer = $( "<div>" );
  •  
  • // Keep track of the iterations.
  • var stepIndex = 0;
  •  
  • // Set the start index of the easer.
  • easer.css( "easingIndex", start );
  •  
  • // Animate the easing index to the final value. For each
  • // step of the animation, we are going to pass the
  • // current step value off to the callback.
  • easer.animate(
  • {
  • easingIndex: end
  • },
  • {
  • easing: easing,
  • duration: duration,
  • step: function( index ){
  • // Invoke the callback for each step.
  • callback(
  • index,
  • stepIndex++,
  • start,
  • end
  • );
  • }
  • }
  • );
  • };
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // When the DOM is ready, init the scripts.
  • $(function(){
  •  
  • // Get a reference to the body tag.
  • var body = $( "body" );
  •  
  • // Get a reference to both lists.
  • var durationList = $( "#durationList" );
  • var iterateList = $( "#iterateList" );
  •  
  •  
  • // Iterate withOUT druation.
  • //
  • // NOTE: We are putting the non-duration iteration first
  • // because it is processor intensive and will mess up
  • // the animation-based duration (making it less smooth).
  • $.ease(
  • 1,
  • 100,
  • "swing",
  • function( i ){
  • // Create the list element for this iteration.
  • var listItem = $(
  • "<li />",
  • {
  • text: i,
  • css: {
  • backgroundColor: "#CC0000",
  • color: "#FFFFFF",
  • width: ((i * 10) + "px")
  • }
  • }
  • );
  •  
  • // Add the list item to the list.
  • iterateList.append( listItem );
  • }
  • );
  •  
  •  
  • // Iterate with druation.
  • $.ease(
  • 1,
  • 1000,
  • 1300,
  • "swing",
  • function( i ){
  • // Create the list element for this iteration.
  • var listItem = $(
  • "<li />",
  • {
  • text: i,
  • css: {
  • backgroundColor: "#CC0000",
  • color: "#FFFFFF",
  • width: (i + "px")
  • }
  • }
  • );
  •  
  • // Add the list item to the list.
  • durationList.append( listItem );
  • }
  • );
  •  
  • });
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • Overloading The Ease Method Based On Arguments
  • </h1>
  •  
  •  
  • <h2>
  • Easing With Duration (Animation)
  • </h2>
  •  
  • <ol id="durationList">
  • <!-- To be populated via easing. -->
  • </ol>
  •  
  •  
  • <h2>
  • Easing Without Duration
  • </h2>
  •  
  • <ol id="iterateList">
  • <!-- To be populated via easing. -->
  • </ol>
  •  
  • </body>
  • </html>

As you can see in the above code, I am using both forms of easing-based iteration to build an Ordered List (OL). Each list item in the ordered list is set to a width that is proportional to the index of the overall iteration. When we run this code, we get the following page output:

 
 
 
 
 
 
Duration And Non-Duration Based Easing Using jQuery's Animate() Method And FOR-Loops. 
 
 
 

The difference between these two approaches (duration and non-duration) is rather substantial; one executes over a given time period (duration) whereas the other executes immediately. This behavioral difference requires a completely different implementation behind the two different invocations. Because of the difference of implementation, it is critical that the branching logic be factored-out of the primary execution function. If it were not, the primary execution function - ease() - would become large and unmaintainable.

I'm sure it's easy to look at this example and argue that these two different implementations shouldn't be two branches of a single method - that they should actually be two entirely different methods (ie. ease() and easeOverDuration()). That might be true; but, this post isn't really about easing - it's a case-in-point of how factoring-out branching logic based on arguments creates clean, cohesive functions with narrow execution concerns.




Reader Comments

Why not wrap it:
Function = function(){
}
Function.prototype = {
implementations: []
, call: function () {
if (typeof this.implementations[arguments.length] == "function")
{
this.implementations[arguments.length].apply ( null, arguments );
}
}
}

And then it could be set up with:
easing = new Function();
easing.implementations = [ null, null, null, iterate, iterateWithDuration ];

and called with:
easing.call( "arg1", "arg2", "arg3" );
or
easing.call( "arg1", "arg2", "arg3", "arg4" );

Reply to this Comment

@Nelle,

You could do it that way; ultimately, we're both doing the same thing - factoring out the branching logic such that the "worker" functions can have a very narrow set of concerns. In either case, I think we're on the right track.

Reply to this Comment

Post A Comment

?
You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.