How to make your own great jQuery UI widgets (making it our collapsible control)

March 11, 2012 2 comments

In this segment we’re going to take the skeletal control we made in the last post, and make it do something useful. We will turn it into a collapsible area, used by many websites. Basically, it will be like one of the panes on a jQueryUI Accordion control. Surprisingly, this isn’t already included in jQueryUI. No problem, we’ll just whip one up.

Step 1 – Know the requirements

Like any other code, it is important to know what you are going to build before building it. We have a good rough idea of what we want, but let’s get it in a list so we can make sure we’re getting done what we need.

  1. It should look like a single pain of the Accordion control
  2. Header with text on it
  3. Body that is hidden or shown
  4. Clicking the header toggles the hidden or closed state of the body
  5. The base html element should be a div containing the body, with the header text in the title.

Step 2 – Strip sample code from our control

Open up the two files from the last segment, collapsible.js and collapsibleTest.html. We’re going to make some changes to these to turn it into our control.

Let’s remove the sample code in collapsible.js we don’t need with these simple requirements.

  1. We don’t have any properties, so let’s remove clear: null in our options object, and the switch statement in _setOption. (You can also remove the comment about jQuery 1.9 support.)
  2. In _create we don’t need the this.element.fadeOut(); line.

The code should now look like the following (this is really the bare skeleton):

(function( $ ) {
  $.widget( "nctools.collapsible", {
 
    // These options will be used as defaults
    options: { 
    },
 
    // Set up the widget
    _create: function() {
    },
	
    // Initialize the widget
    _init: function() {
    },
 
    // Use the _setOption method to respond to changes to options
    _setOption: function( key, value ) {
      $.Widget.prototype._setOption.apply( this, arguments );
    },
	
    // Use the destroy method to clean up any modifications your widget has made to the DOM
    destroy: function() {
      $.Widget.prototype.destroy.call( this );
    },
	
    enable: function() {
    },
	
    disable: function() {
    }
	
  });
}( jQuery ) );

Step 3 – Add our features

We need to take our raw element and convert it into the set of elements we need to deliver user experience of our control. This is done when the control is first made in _create. In our case, we start with html like this:

<div title="This is the header">
     <p>Here's our hidden text.<p>
     <p>And maybe even another whole paragraph.</p>
</div>

and want to transform it to:

<div>
     <h3>This is the header</h3>
     <div>
          <p>Here's our hidden text.</p>
          <p>And maybe even another whole paragraph.</p>
     </div>
</div>

We do this with a few quick jQuery commands in our _create.

	_create: function() {
		// create the new html structure
		this.element.wrapAll('<div></div>');
		var header = $('<h3>'+this.element.attr('title')+'</h3>');
		this.element.before(header);
	},

Now that we have the structure in-place, let’s add in the action (open and close). Again, we would do this in _create. Generally, I have three sections in my _create function: set up the control layout, set starting state, and wire up the actions. We already have the first section, set up the control layout. Now we’re going to add the third section, wire up the actions. Adding the following code after the existing code in _create to wire it up.

	_create: function() {
		// create the new html structure
		this.element.wrapAll('<div></div>');
		var header = $('<h3>'+this.element.attr('title')+'</h3>');
		this.element.before(header);
		
		// wire up actions
		var self = this;
		header.click(function(){self._onHeaderClick();});
	},

	_onHeaderClick: function(){
		this.element.slideToggle();
	},

A few quick things to notice in this code. The first is the click handler is calling a private method, _onHeaderClick, of the control to handle the event. (The new method is listed in the code right after _create. Private methods start with the underscore (_). Public methods don’t have the underscore.) The second is var self=this;. Events are odd in javascript from my c# point of view. In a javascript event this refers to the object triggering the event, not the object handling the event, even when in that handling object’s code. So the way we are using “self” here gets around that. We then call out to the private method so we can get back into a normal “this” scenario as soon as possible.

Remember great widgets let their developer go back to the original state of the html if they want to, so we need to clean up after ourselves, just like mom always said. The method for this is destroy, so let’s get our clean up code in there.

	destroy: function() {
		var wrapper = this.element.parent();
		wrapper.before(this.element);
		wrapper.remove();
		
		$.Widget.prototype.destroy.call( this );
	},

Step 4 – Let’s test it

That’s it, we now have a working collapsible! What? You don’t believe me? Check it out, refresh the collapsibleTest.html page. It should look like this.

Now, try clicking “This is the header”, and see the other stuff disappear? Cool.

Feeling underwhelmed by the result? Hmm… I kind of am too. While this is a good functional control there are a couple things, that feel like they need improving:

  1. Should it start out open or closed?
  2. Can we make it clearer the header is clickable?
  3. How would the user know the two are related, or what clicking the header would do?
  4. Why doesn’t it look cool like an accordion pain does?

These are great questions, and deserve addressing. Some are bugs if you look at our original requirements (e.g. “Why doesn’t it look cool like an accordion pain does?” is a bug because of the requirement “It should look like a single pain of the Accordion control”). Some are feature improvements (e.g. “Should it start out open or closed?”).

The first one, “Should it start out open or closed?”, we’ll cover here for a quick taste of what’s ahead in the 4th segment. The other three questions have to do with “Doing it with Class / Theming” – which is next, so we’ll address those issues there.

Step 5 – Adding an property for open or closed

Figuring out how the developer would probably want to use your control is a key aspect to delivering a great control. Asking questions like would they want it to start open or closed is a great start. But go on to ask yourself which is the more common, but might they want to be able to control that? We developers are a picky opinionated group, so frequently, we like to be able to control things. We’ll talk a lot more about this in the 4th segment (Considering our Audience – Developers – Easy to use, Customizable, Extendable) of this series.

For now, let’s just add one quick option, “startOpen” and default it to false. To do this, we need to make changes to the code in a couple of places. The first is right up at the top of the file in the options object. Let’s add the property and default value, false, startOpen: false. Next, in the _create method we want to set the control’s initial collapsed state based on our new option. Check out the code below.

(function( $ ) {
  $.widget( "nctools.collapsible", {
 
    // These options will be used as defaults
    options: { 
		startOpen: false
    },
 
	_create: function() {
		// create the new html structure
		this.element.wrapAll('<div></div>');
		var header = $('<h3>'+this.element.attr('title')+'</h3>');
		this.element.before(header);

		// set initial state
		if (this.options.startOpen) {
			this.element.slideDown();
		} else {
			this.element.slideUp();
		}
		
		// wire up actions
		var self = this;
		header.click(function(){self._onHeaderClick();});
	},

	_onHeaderClick: function(){
		this.element.slideToggle();
	},

    // Initialize the widget
    _init: function() {
    },
 
    // Use the _setOption method to respond to changes to options
    _setOption: function( key, value ) {
      $.Widget.prototype._setOption.apply( this, arguments );
    },
	
    // Use the destroy method to clean up any modifications your widget has made to the DOM
    destroy: function() {
      $.Widget.prototype.destroy.call( this );
    },
	
    enable: function() {
    },
	
    disable: function() {
		var wrapper = this.element.parent();
		wrapper.before(this.element);
		wrapper.remove();
		
		$.Widget.prototype.destroy.call( this );
    }
	
  });
}( jQuery ) );

Another quick refresh of the collapsibleTest.html page, and we can see it is now defaulting to closed. However, to know if it is really working, we should set the option to true, and verify setting it false also works. So let’s quickly modify the script section of our html test page, collapsibleTest.html.

<html>
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
		<title>How to make your own great jQuery UI widgets</title>
	    <script type="text/javascript" src="http://ajax.microsoft.com/ajax/jquery/jquery-1.4.2.js"></script>
		<script type="text/javascript" src="http://ajax.microsoft.com/ajax/jquery.ui/1.8.5/jquery-ui.js"></script>
		<script type="text/javascript" src="segment2d.js"></script>
		<script type="text/javascript">
			$(function(){
				$("#sample").collapsible({startOpen: true;});
			});
		</script>
	</head>
	<body>
		<p>Before the control.</p>
		<div id="sample" title="This is the header">
			<p>Here's our hidden text.</p>
			<p>And maybe another whole paragraph.</p>
		</div>
		<p>After the control.</p>
	</body>
</html>

Refresh the browser to make sure it is open now. Change the value to false, refresh the browser and make sure starts closed again.

Conclusion

Congratulations! We’ve done a lot here. We created a control that provides use with a collapsible div. We even added a custom option to it. That’s a great start, but there’s still a lot more before it is a “Great” custom jQueryUI widget. We need to make it look cool, consider other requirements a developer (and ultimately their audience – the end user) might have for it, and consider the developers who aren’t “front-end” developers and might not want to mess with javascript too much.

Next: Do it with Class / Theming

Note: It is really important to test when you are developing a great control, especially one designed for reuse. Testing verifies things work as you intended in “all” cases. For example, you could even test what happens here if the option were set to something other than true or false. While, you might not want to test or handle that case, you certainly should verify things work as described.

If anyone knows a great resource for writing unit test for javascript code, I’d love the link in the comments below!!

Categories: Code, How To, jQuery Tags: , ,

How to make your own great jQuery UI widgets (Basics of making a control)

March 11, 2012 2 comments

A lot of people think writing custom jQuery UI widgets is hard. It’s not hard. It’s EASY! Start by reminding yourself of this, “It’s only code, and I know how to code.”

Step 1: Skeletal Code & Quick Explaination of a “widget” object

Here’s the skeletal code for a sample, do-nothing, custom jQuery UI widget. Below we’ll quickly walk through the code & the widget object enough to move on to our next step.

Create a javascript file called collapsible.js (since that’s the control we’re going to turn this into). Put this code in it, and save it.

(function( $ ) {
  $.widget( "nctools.collapsible", {
 
    // These options will be used as defaults
    options: { 
      clear: null
    },
 
    // Set up the widget
    _create: function() {
    },
	
    // Initialize the widget
    _init: function() {
    },
 
    // Use the _setOption method to respond to changes to options
    _setOption: function( key, value ) {
      switch( key ) {
        case "clear":
          // handle changes to clear option
          break;
      }
 
      // In jQuery UI 1.8, you have to manually invoke the _setOption method from the base widget
      $.Widget.prototype._setOption.apply( this, arguments );
      // In jQuery UI 1.9 and above, you use the _super method instead
      //this._super( "_setOption", key, value );
    },
	
    // Use the destroy method to clean up any modifications your widget has made to the DOM
    destroy: function() {
      // In jQuery UI 1.8, you must invoke the destroy method from the base widget
      $.Widget.prototype.destroy.call( this );
      // In jQuery UI 1.9 and above, you would define _destroy instead of destroy and not call the base method
    },
	
    enable: function() {
    },
	
    disable: function() {
    }
	
  });
}( jQuery ) );

Let’s walk through this code a bit.

(function($){...}(jQuery));
This sets $ to be jQuery for us, so we can count on that to be true, even if it isn’t outside of our control. We do this so our users can also use libraries other than jQuery side-by-side withour stuff. We will not have to touch this code again as we work on our control.
$.widget("<namespace>.<control>",{...});
This is where we are defining our control. It is good to namespace your controls to help avoid control namespace collisions with other controls. <namespace> is really just a name to group together your controls. Most of the time your control’s users won’t need to use it, but, if they have conflicts, it will let them specify if they want to use your control or the other (worse) one. <control> is the name of your control. We’re going to use namespace nctools, because that’s what I call my toolset. The control we are building is a collapsible, so let’s call it collapsible.
options
This object is a list of your widget’s properties with their default values. Figuring out what options to offer your control’s user (the developer) is a aspect of making a great control. You want to provide default behavior that works for most cases so the developer doesn’t need to customize your control, but you can’t solve all cases. The options are a great way to provide your user the configuration points they need to handle their special situations. We’ll talk a lot more about these in the “Considering Our Audience – Developers” segment.
_create
This is called once in the lifetime of your control, when it is first created. For example, $("button").button(); creates a new button instance for each button element the selector found, and calls _create on each of those instances. Here is where we do the work of converting our targeted element (stored in this.element) into the great user-experience our control delivers. We’ll frequently come to this section of the code.
_init
Right after _create this method, _init gets called. However, it also is called as a “reset” method. We won’t be in this code much.
_setOption
This is the setter for your control’s properties. If things happen when one of your properties gets set, this is where you initiate that happening. For our control we won’t be here much, but this is frequently a common place for code in more complex controls. It really just depends on what your properties do.
_trigger
Providing events to your developer user is a great way to make your control more usable in complex scenarios. There’s no reason to change the default implementation of this in the base object (so I don’t show it in the skeletal code). However, we will use this a fair amount, but not until we get to the “Considering Our Audience – Developers” segment.
destroy
Just like the Burning Man principal, “pack it in, pack it out, leave no trace.” We want to clean up after outselves. This method let’s your developer set things back to how they were before creating your control to begin with.
enable
Makes your control “active”. This only applies to control that have interaction. Your control doesn’t need to have this method if it is just a way to pretty display, not has no interaction (say a pretty time presentor that makes an element with a time value in its html look like a digital clock display). We are interactive, so we’ll be back here. The complementary method to this is disable.
disable
This is the complimentary method to enable, it turns off interaction on your control.

Step 2: Viewing Our Progress

Now let’s create an html file so we can make sure the code works. Create an html file called collapsibleTest.html with the following code, and save it.’Both the html file and the js file should be in the same folder.

<html>
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
		<title>How to make your own great jQuery UI widgets</title>
	    <script type="text/javascript" src="http://ajax.microsoft.com/ajax/jquery/jquery-1.4.2.js"></script>
		<script type="text/javascript" src="http://ajax.microsoft.com/ajax/jquery.ui/1.8.5/jquery-ui.js"></script>
		<script type="text/javascript" src="collapsible.js"></script>
		<script type="text/javascript">
			$(function(){
				$("#sample").collapsible();
			});
		</script>
	</head>
	<body>
		<p>Before the control.</p>
		<div id="sample" title="This is the header">
		     <p>Here's our hidden text.<p>
		     <p>And maybe even another whole paragraph.</p>
		</div>
		<p>After the control.</p>
	</body>
</html>

Open collapsibleTest.html in your favorite (modern) browser (like say Chrome), and you should see something like this.

Conclusion

If you included this javascript in a page you’d have a new control, collapsible, you could use. Granted all it does is fadeOut the element it is attached to, but hey, we just made a jQuery UI widget!

Now let’s move on to the “custom” and “great” parts! Let’s make something we would use. We will drill into the individual aspects of the widget code more as we move forward.

Next Making it our collapsible control

The spark for this post comes from an article, David Peterson‘s Stateful jQuery plugins, that got me started with making some controls. However, I’ve found jQuery: Widget Factory to be more helpful for creating “properly” formed controls. Check them both out, if you want. They are good reads.

Categories: Code, How To, jQuery Tags: , ,

How to make your own great jQuery UI widget (Introduction)

March 11, 2012 Leave a comment

This series walks you through learning how to make great jQuery UI widgets.  The great ones are ones you’ll find yourself, and others, using again and again.  We’ll take this one step at a time, from making a jQuery UI widget through enhancing it for our backend users.  The sample widget we’re going to build in this series is “collapsible”, a div that can be opened and closed.  Think of it as one of the items on the accordion control.

  1. Basics of making a control
  2. Making it our collapsible control
  3. Do it with Class / Theming
  4. Considering our audience – developers – Easy To Use / Customizable / Extendable
  5. Considering our audience – non-javascript developers – Ease of use on backend (MVC HtmlHelper extensions)
  6. A path forward – looking for more controls to make

This series uses jQuery 1.3.2, jQuery UI 1.8.18, and knowledge I’ve gathered from around the web (cited when possible) and from working with some great people over the years.

Next: Basics of making a control

Categories: Code, How To, jQuery Tags: ,

jQuery & ASP.NET

I never thought I would say this, but, really, why ASP.NET?

In my latest project we are using a SQL Server database, ASP.NET c# server code, and XHTML JavaScript with jQuery client side. As we focus more and more on improving the user experience and the performance of the site, the more we move away from .net controls, and more to client side JavaScript experience. The middle tier is getting thin, and the “need” for a powerful OO language like c# really seems to be diminishing.

The break through for me was really learning how much decent OO coding you can do with JavaScript. Between a co-worker’s help (Ian Garrison) and modeling after jQuery code, taking the OO approach with JavaScript really made doing the rich app in JavaScript work well. Moving towards a RESTful approach also helps deliver the experience we’re going for.

C# for the middle tier is working fine, but we are using a strong SOA with AJAX client side to avoid the expensive Page PostBack. It was shocking to find out how long it took just doing a simple postback that does nothing.

Going with the Ajax client caused us to need the soa (web services), which also helps us deliver another client feature, which is the web API. Needing to do have a back button experience with an AJAX site moved us towards a modified RESTful URL approach to our architecture. It is a lot of fun to see how all of these industry buzz words really do quite naturally come together.

For me the saddest part of it is how little knowing Microsoft dev tools matter. SQL Server is a great database for the leads we are dealing with. C# is working fine, but it really could be a bunch of other web server languages. Same thing for IIS, no real reason for it instead of Apache.

Let’s set up my Mac’s web server and see how life is with no Microsoft products in the dev process. I already miss Visual Studio. What’s a good Mac OS X web dev IDE?

Categories: Code

Spreading WordPress

July 14, 2009 Leave a comment

Sitting at Hartford Community Coffee with Jeff teaching the joys of WordPress.

Categories: Uncategorized

iPhone – spread the infection

July 13, 2009 Leave a comment

I’m sitting in the Apple store blogging on my iPhone while my buddy is getting his. Hee hee – I’ve infected him!

The guys at the St. Louis Galleria Apple store rock! They’re even right by a Starbucks.

Categories: iPhone, Personal Tags:

Blog and Twitter

Welcome, to my place to share about stuff.

Right, now I want to just get in the habit of writing each day.  I’m doing to start this as more a diary – jotting down thoughts more for myself than everyone else.  Overtime though, I hope to shape as something more enjoyable for others.

Last night I started my Twitter account.  Let’s see what all of the hype is actually about.  So far I’ve learned if I were a huge fan, I could follow some stars.  If I were a stalker, I could track someone.  Well, that doesn’t seem too promising.

I decided to tweak about how I was hoping to meet local iPhone developers after failing to find a meetup group on http://www.meetup.com.  (No iPhone developer or Cocoa or Objective-C groups.)  Hmmm…  If I did that – I wonder if anyone else did – quick keyword search – look someone else did just an hour before I did.  Interesting.  Let’s see if anything comes of that.

My iPhone project has totally slowed down.  After overcoming most of the technical obsticals of porting my friend’s Windows Mobile game over to the iPhone, I realized, it wasn’t actually a great mobile game.  It is hard to learn, not quick in / quick out.  Also, it wasn’t something calling to me to play.  I need to find a project I’ll enjoy more.  I should just hurry up and finish this port this week.

Categories: Uncategorized
Follow

Get every new post delivered to your Inbox.