Home  >  

Creating your own Side bar "widget" using SpryDOMUtils and SpryEffects! Part 2

| | Comments (1)
AddThis Social Bookmark Button

In part 1 of this article I showed you how to a build your own widget using SpryDOMUtils and SpryEffects. In this article we are going to evolve the widget, making it more powerful, unobstructive and easier to use. We will replace the Spry Effects and make it in to a effect cluster. I will also give you some tips and show how to tackle some potential issues.

Note: All source code is available for download at the end of this article

Part 4: Evolve the Widget, first make it work then make it better!

Now that we have successfully created our own SideBar widget, it's time to make it more powerful and customizable. First of all it's time to strip our skeleton and remove all unused attributes. Remove all id's expect the id='example1' of the container.

<div id="example1" class="SprySideBar">
<div class="SprySideBarGroup">
<div class="SprySideBarContent">
<div class="SprySideBarMenu">
<h1>SprySideBar</h1>
<ul>
<li><a href="#">Link 1</a></li>
<li><a href="#">Link 2</a></li>
<li><a href="#">Link 3</a></li>
<li><a href="#">Link 4</a></li>
<li><a href="#">Link 5</a></li>
</ul>
8
</div>
</div>
</div>
<a href="javascript://" class="SprySideTab"><em></em></a>
</div>

That's how it should look now, we don't have to make any changes to our CSS so we can leave that file alone. But the JavaScript will get a big face-lift. Instead of using 2 effects, and an Observer we are going to cluster it to one constructor. In this constructor we can also define custom attributes for our widget such as images for our status indicators. The best thing is to just start again, clear your old JavaScript including your effects (don't forget to make a backup of it).

Now we are going to create the effect name of the cluster, this will be used to call our menu. I'm using:

Spry.effects.SideBar = function(element, options){
 /* code goes here */
}
Spry.Effect.SideBar.prototype = new Spry.Effect.Cluster();
Spry.Effect.SideBar.prototype.constructor = Spry.Effect.SideBar;

You can also create effects with another function name, it doesn't have to be an object of Spry.Effects. This is what I will be using in this article, but I'll show an another way to create the widget:

function SideBar(element, options){
 /* code goes here */
}
SideBar.prototype = new Spry.Effect.Cluster();
Sidebar.prototype.constructor = SideBar();

That way will look rather nice as your own effect later on as the constructor will look like this:

var mySideBar = new SideBar("element", {options});

Anyways back to the cluster as you can see I already add our arguments (element, options) in the function. you only have to use one variable for options because you will receive it as an Object. Within our function we are going to build in a check if our widget is correctly called, Spry Effects already has pre-build functionality for this.

Spry.effects.SideBar = function(element, options){
 if (!this.notStaticAnimator)
 return Spry.Effect.Utils.showInitError('SideBar');
}
Spry.Effect.SideBar.prototype = new Spry.Effect.Cluster();
Spry.Effect.SideBar.prototype.constructor = Spry.Effect.SideBar;

Place the code in your function. In the showError('name_here') function you can put the name of your effect, this will help the users of your widget to identify the problem. The next addition to our script is a function that registers our function as a cluster to Spry: Spry.Effect.Cluster.call(this, options);.

Spry.effects.SideBar = function(element, options){
 if (!this.notStaticAnimator)
 return Spry.Effect.Utils.showInitError('SideBar');
 Spry.Effect.Cluster.call(this, options);
}
Spry.Effect.SideBar.prototype = new Spry.Effect.Cluster();
Spry.Effect.SideBar.prototype.constructor = Spry.Effect.SideBar;

The next part of our widget took me a little while to think of, i wanted to be able to access the elements within our container without having to add extra id's to the elements. I started started thinking of the technique we used with the sidebar status indicators (children, siblings and parents). I have created a "sub" function in our cluster effect, when you call the function getChilderen('root_element_here'), the child elements and their child's will be put in an Array and given to U. This technique is also used for nearly all Spry widgets.

Spry.effects.SideBar = function(element, options){
 if (!this.notStaticAnimator)
 return Spry.Effect.Utils.showInitError('SideBar');
 Spry.Effect.Cluster.call(this, options);
 var getChildren = function(element){
  var children = [];
  var child = element.firstChild;
  while (child)
  {
   if (child.nodeType == 1){
    children.push(child);
    if (child.hasChildNodes())
    {
     var kids = child.childNodes;
     for (var i = 0; i < kids.length; i++)
     {
      if(kids[i].nodeType == 1)
      children.push(kids[i]);
     };
    };
   }
   child = child.nextSibling;
  }
  return children;
 };
}
Spry.Effect.SideBar.prototype = new Spry.Effect.Cluster();
Spry.Effect.SideBar.prototype.constructor = Spry.Effect.SideBar;

I'll explain the function bit by bit. var children = []; is the Array where we will store our found children in. We need to create it outside the while loop else it will get cleared every time the while loop starts over again. The same concept counts for the var child = element.firstChild; this get the first child of our element. Than we are starting a while loop, this basically means while we have child element in our variable we will repeat the code that is within our curly brackets { }.

In our while loop we want to check if our found element is indeed a element and not a text node. Firefox likes to see text as child nodes, while Internet Explorer doesn't. If it passes the test, we are going to add the element in our array using .push(data);. Push adds the element at the end of our children Array.

After we have done that, we are going to check if our child has childnodes using another if statement and a JavaScript function called .hasChildNodes(). If it has found childNodes within the element it will continue to the next block of code. In this block of code I have add another loop, a for loop. But before I'm going to talk about the for loop, we have to create another variable, var kids = child.childNodes; this will store all childNodes in the variable kids. Now we have something to loop through and check for useful nodes.

for (var i = 0; i < kids.length; i++) a for loop loops through a block of code a specified number of times. In our case it will keep looping until the amount of data of the array is less than the amount of loops it has done after each loop i++ will add another +1 to our loops(1 2 3 4 5 6 etc). These loops will allow us to go through Arrays and our Array kids. Within the loop I have placed the same code as before, the same check if its an element node, if it is, add it to our children array.

At the bottom of our while loop you see child = child.nextSibling;. This is required for the loop, so it stops when there aren't any childNode left. When the loop is done we are return our array children.

Note: more information about while loops can be found [here] and about the for loop [here]. ^above link to http://w3schools.com/js/js_loop_for.asp and http://w3schools.com/js/ js_loop_while.asp

Now it time to use this function, but first we need to get our root element, I'm using Spry.Effect.getElement(element); this is a build in function of SpryEffects.js. It will check if your variable element is an id or already an element.

Spry.effects.SideBar = function(element, options){
 if (!this.notStaticAnimator)
 return Spry.Effect.Utils.showInitError('SideBar');
 Spry.Effect.Cluster.call(this, options);
 var getChildren = function(element){
  var children = [];
  var child = element.firstChild;
  while (child)
  {
   if (child.nodeType == 1){
    children.push(child);
    if (child.hasChildNodes())
    {
     var kids = child.childNodes;
     for (var i = 0; i < kids.length; i++)
     {
      if(kids[i].nodeType == 1)
      children.push(kids[i]);
     };
    };
   }
   child = child.nextSibling;
  }
  return children;
 };
 var element = Spry.Effect.getElement(element);
 var content = getChildren(element);
 var menu = getChildren(content[1]);
}
Spry.Effect.SideBar.prototype = new Spry.Effect.Cluster();
Spry.Effect.SideBar.prototype.constructor = Spry.Effect.SideBar;

To access the sidebar group, content and tab we use our root element to access them. var content = getChildren(element). Our variable will now contain the following items:

  • 0 DIV : SprySideBarGroup
  • 1 DIV : SprySideBarContent
  • 2 A : SprySideTab
  • 3 EM :

And to access the menu (content of our widget) div we want the children of SprySideBarContent that why I'm using var menu = getChildren(content[1]) to access it, the menu variable now contains the following items:

  • 0 DIV : SprySideBarMenu
  • 1 H1
  • 2 UL

Next are our widgets options, we want to start with some default options, and be able to customize them through our constructor like we did with our normal effects. Let's create default options for: from, size, duration, toggle, transition, open indicator and close indicator (so we can customize it through our constructor and don't have to change our code);

var doFrom = '0%';
var doTo = '100%';
var doDuration = 1000;
var doToggle = false; //toggle false is required to the correct effect direction
var doTransition = Spry.fifthTransition; //default
var isOpenIndc = '>>';
var isCloseIndc = '<<';

Now that we have our default values, we can check if we have options in our constructor that overwrites our default values. This can be done with a few simple If statements. First we need to check if there are any options send to us, if there are we can do checks if they will replace our default value.

if (options)
{
 if (options.duration != null) doDuration = Math.ceil(options.duration/2);
 if (options.toggle != null) doToggle = options.toggle;
 if (options.to != null) doTo = options.to;
 if (options.from != null) doFrom = options.from;
 if (options.transition != null) doTransition = options.transition;
 if (options.openIndc != null) isOpenIndc = options.openIndc;
 if (options.closeIndc != null) isCloseIndc = options.closeIndc;
}

So if(options) means if options has a value. Than we are going to check for each option if the option has a value in it. If it does, we are going to replace our default value with the options value. The Math.ceil makes sure that our duration is indeed in mili seconds. To make it easier for ourselves we are going to create an option variable for all our effects because they all require the same information.

var effectoptions = {duration:doDuration, toggle:doToggle, from:doFrom, to:doTo,
 transition:doTransition};

Now that we have all our data ready it's time apply the effects. We stored our effect elements in our variables, and we know which effects we are going to apply on the widget.

var slide = new Spry.Effect.Squish(content[1],effectoptions);
var fade = new Spry.Effect.Fade(menu[0],effectoptions);

Now we need to choose how we want to run our effects. We can choose from addParalellelEffect and addNextEffect. As we want them to be activated at the same time we are going to use AddParalellelEffect. You have to add this function for both effects:

this.addParallelEffect(slide);
this.addParallelEffect(fade);

Now the last part of our widget is the observer, it almost works the same as we did in our simple version. We are not attaching the observer to an defined effect, but to our widget.

this.addObserver({
 onPreEffect:
 function(effect){
  if (effect.direction == Spry.forwards)
  content[3].innerHTML = isCloseIndc;
  else
  content[3].innerHTML = isOpenIndc;
 }
});

As you can see the base is still the same as our simple version, I have add an object to the observer and put functions on our onPreEffect. If effect is going forwards, the innerHTML of content[3] (EM TAG) gets the value from the isCloseIndc variable that was set constructor and other way around for backwards. Our finished function should look like:

Spry.effects.SideBar = function(element, options){
 if (!this.notStaticAnimator)
 return Spry.Effect.Utils.showInitError('SideBar');
 Spry.Effect.Cluster.call(this, options);
 var getChildren = function(element){
  var children = [];
  var child = element.firstChild;
  while (child)
  {
   if (child.nodeType == 1){
    children.push(child);
    if (child.hasChildNodes())
    {
     var kids = child.childNodes;
     for (var i = 0; i < kids.length; i++)
     {
      if(kids[i].nodeType == 1)
      children.push(kids[i]);
     };
    };
   }
   child = child.nextSibling;
  }
  return children;
 };
 var element = Spry.Effect.getElement(element);
 var content = getChildren(element);
 var menu = getChildren(content[1]);
 var doFrom = '0%';
 var doTo = '100%';
 var doDuration = 1000;
 var doToggle = false;
 var doTransition = Spry.fifthTransition;
 var isOpenIndc = '>>';
 var isCloseIndc = '<<';
 if (options)
 {
  if (options.duration != null) doDuration = Math.ceil(options.duration/2);
  if (options.toggle != null) doToggle = options.toggle;
  if (options.to != null) doTo = options.to;
  if (options.from != null) doFrom = options.from;
  if (options.transition != null) doTransition = options.transition;
  if (options.openIndc != null) isOpenIndc = options.openIndc;
  if (options.closeIndc != null) isCloseIndc = options.closeIndc;
 }
 var effectoptions = {duration:doDuration, toggle:doToggle, from:doFrom, to:doTo,
  transition:doTransition};
 var slide = new Spry.Effect.Squish(content[1],effectoptions);
 var fade = new Spry.Effect.Fade(menu[0],effectoptions);
 this.addParallelEffect(slide);
 this.addParallelEffect(fade);
 this.addObserver({
  onPreEffect:
  function(effect){
   if (effect.direction == Spry.forwards)
   content[3].innerHTML = isCloseIndc;
   else
   content[3].innerHTML = isOpenIndc;
  }
 });
};
}
Spry.Effect.SideBar.prototype = new Spry.Effect.Cluster();
Spry.Effect.SideBar.prototype.constructor = Spry.Effect.SideBar;

And there you have it, your first widget! To activate it you can use:

var sidebar = new Spry.Effect.SideBar('example1',{toggle:true,closeIndc:'<<'});
Spry.Utils.addLoadListener(function(){
 Spry.Utils.addEventListener("sideBarTab", "click", function(){
  sidebar.start()
 }, false);
});

As you see I still have our load listener that will add our start(); function to the tab so it gets activated when the users presses on it. This technique still requires an id to attach the EventListener on. In the next part I'll show you how to remove this to, and integrate it in the widget.

Part 5: Tips and tricks.

Now that the widget is working and operating as it should there are always points of improvement and little changes little changes to optimize the widget. I'll be sharing a few points with you.

As you can see we are using an LoadListener to attach our .start() to our tab. It works as it should be, but we can make it work faster, better and smarter. We have two options to choose from. Changing the LoadListener to a DOMReady function or in our Advanced version attach function dynamic. The DOMReady function can also be used to improove the simple version of the widget so I'll start with that point first.

The biggest disadvantage of using an LoadListener is that the function only gets activated when the whole page is completly loaded, this includes the markup, images, scripts, etc. So if you have page with 50 pictures and use an LoadListener to attach the start function to the tab, your user can click the menu, but nothing happens because the browser is still to buzzy with loading the images.

A DOM ready function will fire when the markup is ready, so we don't have to wait for all 50 images to be loaded. I have included a custom DOMready script in the download. We can replace, this script still requires SpryDOMUtils.js to be included in the page because it uses functions from that file.

Spry.Utils.onDOMReady(function(){
 Spry.Utils.addEventListener("sideBarTab", "click", function(){
  sidebar.start()
 }, false);
});

That will be our new code. As I said before we have two options to consider, the second option was dynamic adding the start functionality within our widget. This can be done only extra lines of code. We already have access to the widget, so we don't need to know the varname to start the widget. We are going to store the access to our widget in a new variable that and attach the eventlistener to the tab:

var self = this;
Spry.Utils.addEventListener(content[2], "click", function(e){self.start();} ,false);

The var self = this; gives us access to our widget. With our getChildren function we got access to all childNodes of our widget. This was stored in the var content. As you can see this is a lot shorter, and compact that using an DOM ready or loadListener.

Full browser support is always a must, with this widget this is also the case. Internet Explorer, Firefox and Opera are looking and functioning the way they should be. But in Safari there is a little problem area with the background wrapper. Even if the bugs and errors are created by the browser, its still our duty to fix this. And we will, we can add an extra observer to our widget. In this observer we are adding a piece of code that will pass the width of the SideBarGroup to the SideBarContent.

this.addObserver({
 onPreEffect:
 function(effect){
  if (effect.direction == Spry.forwards)
  content[3].innerHTML = isCloseIndc;
  else
  content[3].innerHTML = isOpenIndc;
 },
 onStep:function(){
  content[0].style.width = content[1].style.width;
 }
});

Be careful when using the onStep observer the code you execute here should not be time consuming otherwise you'll affect the animation smoothness of the effect as it will be called each time the effect executes a new animation step.

Last but not least, we want the widget to degrade gracefully when JavaScript is off. In our case this is a simple fix, we have the display:none attribute in the .SprySideBarContent class that makes sure that the content is hidden when the page opens. If we remove this line we will have our content visible when JavaScript is disabled. Now we have to add one simple line to our script to hide the content when JavaScript is enabled.

content[1].style.display='none';

That will do the trick. When the script gets loaded in it will automatic change the display of SprySideBarContent to none.

This is how the advanced tweaked code should look like now, with all changes made:

Spry.Effect.SideBar = function (element, options)
{
 if (!this.notStaticAnimator)
 return Spry.Effect.Utils.showInitError('SideBar');
 Spry.Effect.Cluster.call(this, options);
 var getChildren = function(element){
  var children = [];
  var child = element.firstChild;
  while (child)
  {
   if (child.nodeType == 1){
    children.push(child);
    if (child.hasChildNodes())
    {
     var kids = child.childNodes;
     for (var i = 0; i < kids.length; i++)
     {
      if(kids[i].nodeType == 1)
      children.push(kids[i]);
     };
    };
   }
   child = child.nextSibling;
  }
  return children;
 }
 var element = Spry.Effect.getElement(element);
 var content = getChildren(element);
 var menu = getChildren(content[1]);
 content[1].style.display='none';
 var doFrom = '0%';
 var doTo = '100%';
 var doDuration = 1000;
 var doToggle = false; //toggle false is required to the correct effect direction
 var doTransition = Spry.fifthTransition; //default
 var isOpenIndc = '>>';
 var isCloseIndc = '<<';
 if (options)
 {
  if (options.duration != null) doDuration = Math.ceil(options.duration/2);
  if (options.toggle != null) doToggle = options.toggle;
  if (options.to != null) doTo = options.to;
  if (options.from != null) doFrom = options.from;
  if (options.transition != null) doTransition = options.transition;
  if (options.openIndc != null) isOpenIndc = options.openIndc;
  if (options.closeIndc != null) isCloseIndc = options.closeIndc;
 }
 var effectoptions = {duration:doDuration, toggle:doToggle, from:doFrom, to:doTo,
  transition:doTransition};
 var slide = new Spry.Effect.Squish(content[1],effectoptions);
 var fade = new Spry.Effect.Fade(menu[0],effectoptions);
 var self = this;
 Spry.Utils.addEventListener(content[2], "click", function(e){self.start();} ,false);
 this.addParallelEffect(slide);
 this.addParallelEffect(fade);
 this.addObserver({
  onPreEffect:
  function(effect){
   if (effect.direction == Spry.forwards)
   content[3].innerHTML = isCloseIndc;
   else
   content[3].innerHTML = isOpenIndc;
  },
  onStep:function(){
   content[0].style.width = content[1].style.width;
  }
 });
};
Spry.Effect.SideBar.prototype = new Spry.Effect.Cluster();
Spry.Effect.SideBar.prototype.constructor = Spry.Effect.SideBar;

Article Assets

Creating Your own Sidebar source code

Read more from Arnout Kazemier. Arnout Kazemier's Atom feed 3rdEden on Twitter

Comments

1 Comments

@mrita saini said:

my personal aliginda:)

Leave a comment


Tag Cloud

Question of the Week: Dream App

If you had an unlimited budget and unlimited resources what application would you build and why would you build it?

Answer

Latest Features

Recommended for You

@InsideRIA on Twitter

Archives

  • Or, visit our complete archive.  

About This Site

Welcome to the premiere community site for all things RIA sponsored by O'Reilly Media and Adobe Systems Incorporated.