96
jQuery UI Widget Factory

jQuery UI Widget Factory

Embed Size (px)

Citation preview

Page 1: jQuery UI Widget Factory

jQuery UI

Widget Factory

Page 2: jQuery UI Widget Factory

Scott GonzálezjQuery UI development leadhttp://nemikor.com@scott_gonzalez

$(λ);

Page 3: jQuery UI Widget Factory

The widget factory- What is it?- Why do we need it?- How do we use it?

$.widget();

Page 4: jQuery UI Widget Factory

Why we needed the widget factory

Page 5: jQuery UI Widget Factory

jQuery revolutionizedthe way we work with the DOM

$('p').show();

Page 6: jQuery UI Widget Factory

jQuery excels at simple tasks

$('p').fadeOut();

Page 7: jQuery UI Widget Factory

$('#notification').text('Your file has been uploaded').show().fadeOut(2000);

Page 8: jQuery UI Widget Factory

$('a.user').click(function() {var url = this.href;$('#user-info').load(url);return false;

});

Page 9: jQuery UI Widget Factory

What about stateful widgets?

$('div').dialog();

Page 10: jQuery UI Widget Factory

$.ajax({url: '/foo.html',beforeSend: function() { … },success: function() { … },error: function() { … }

});

Page 11: jQuery UI Widget Factory

$(elem).dialog({modal: true,open: function() { … },beforeClose: function() { … },close: function() { … }

});

Page 12: jQuery UI Widget Factory

How do we handle initializationand function calls?

$(elem).dialog('open');

Page 13: jQuery UI Widget Factory

The widget factory:A solution to state management

Page 14: jQuery UI Widget Factory

Defning an API

Page 15: jQuery UI Widget Factory

$(elem).dialog({

draggable: false,modal: true

}).dialog('open');

Page 16: jQuery UI Widget Factory

$(elem).createDialog({

draggable: false,modal: true

}).openDialog();

Page 17: jQuery UI Widget Factory

$(elem).dialog({

draggable: false,modal: true

}).dialogOpen();

Page 18: jQuery UI Widget Factory

$(elem).dialog({

draggable: false,modal: true

}).open();

Page 19: jQuery UI Widget Factory

var dialog = new $.dialog(elem, {draggable: false,modal: true

});dialog.open();

Page 20: jQuery UI Widget Factory

Any other models?

Page 21: jQuery UI Widget Factory

jQuery UI actually implementsmany of these models.

$(elem).data('dialog');

Page 22: jQuery UI Widget Factory

$(elem).dialog(options);

$(elem).dialog('open');

Page 23: jQuery UI Widget Factory

$(elem).dialog(options);

$(elem).data('dialog').open();

Page 24: jQuery UI Widget Factory

var dialog = new $.ui.dialog(elem, options);dialog._init();

dialog.open();

Page 25: jQuery UI Widget Factory

How does it work?

$.ui.dialog.prototype

Page 26: jQuery UI Widget Factory

$.ui.dialog.prototype.draggable =function(toggle) {

return this.option('draggable', toggle);

};$(elem).dialog('draggable', true);

Page 27: jQuery UI Widget Factory

Using the widget factory

Page 28: jQuery UI Widget Factory

The bare minimum

Page 29: jQuery UI Widget Factory

$.widget('ui.progressbar', {_init: function() {

this.element.text(this.options.value + '%');

}});$.ui.progressbar.defaults = { value: 0 };

Page 30: jQuery UI Widget Factory

$(elem).progressbar();

$(elem).progressbar({ value: 25 });

<div class=“{progressbar:{value:10}}”>$('div').progressbar();

Page 31: jQuery UI Widget Factory

Working with options

Page 32: jQuery UI Widget Factory

$(elem).progressbar();

$(elem).progressbar('option', 'value');

$(elem).progressbar('option', 'value', 5);

Page 33: jQuery UI Widget Factory

_setData: function(key, value) {this.options[key] = value;if (key == 'value') {

this.element.text(value + '%');}

}

Page 34: jQuery UI Widget Factory

Adding methods

Page 35: jQuery UI Widget Factory

progress: function(value) {this._setData('value',

this._constrain(value));},_constrain: function(value) {

return Math.max(0,Math.min(value, 100));

}

Page 36: jQuery UI Widget Factory

progress: function(value) {if (this.options.disabled) {

return;}this._setData('value',

this._constrain(value));}

Page 37: jQuery UI Widget Factory

Creating callbacks

Page 38: jQuery UI Widget Factory

progress: function(value) {value = this._constrain(value);this._setData('value', value);this._trigger('change', null, {

value: value});

}

Page 39: jQuery UI Widget Factory

progress: function(value) {value = this._constrain(value);var data = { value: value };if (false !==

this._trigger('change', null, data)) {this._setData('value', value);

}}

Page 40: jQuery UI Widget Factory

progress: function(value) {value = this._constrain(value);var data = { value: value };if (false !==

this._trigger('change', null, data)) {this._setData('value', value);

}}

Page 41: jQuery UI Widget Factory

Cleaning up

Page 42: jQuery UI Widget Factory

destroy: function() {this.element.text('');$.widget.prototype.destroy.call(this);

}

Page 43: jQuery UI Widget Factory

Even more goodies

Page 44: jQuery UI Widget Factory

What's planned for the future?

Page 45: jQuery UI Widget Factory

$.widget('ui.progressbar', {options: { value: 0 },_init: function() {

this.element.text(this.options.value + '%');

}});

Page 46: jQuery UI Widget Factory

$.widget('ui.complexDialog',$.ui.simpleDialog, {_init: function() {

// …}

});

Page 47: jQuery UI Widget Factory

Go build some widgetsGive us some feedback

Page 48: jQuery UI Widget Factory

http://nemikor.com@scott_gonzalezhttp://speakerrate.com/scott.gonzalez

$(λ);

Page 49: jQuery UI Widget Factory

1

jQuery UI

Widget Factory

Page 50: jQuery UI Widget Factory

2

Scott GonzálezjQuery UI development leadhttp://nemikor.com@scott_gonzalez

$(λ);

Hi. My name is Scott González and I'm the development lead of jQuery UI.

I'm going to be talking about jQuery UI's widget factory and showing off some of its lesser-known features.

I'll also be showing how to use the widget factory to build your own plugins.

Page 51: jQuery UI Widget Factory

3

The widget factory- What is it?- Why do we need it?- How do we use it?

$.widget();

Many of you are probably unaware that the widget factory even exists.

So I'll be explaining why we needed it for jQuery UI and how we use it to solve some of the problems we faced.

Page 52: jQuery UI Widget Factory

4

Why we needed the widget factory

We'll start with some background info about why jQuery didn't solve our problems out o the box.

Page 53: jQuery UI Widget Factory

5

jQuery revolutionizedthe way we work with the DOM

$('p').show();

jQuery revolutionized the way we work with the DOM.

The "fi nd elements, do something" philosophy, along with chaining, has changed the way we approach tasks and made our lives easier.

Page 54: jQuery UI Widget Factory

6

jQuery excels at simple tasks

$('p').fadeOut();

A large part of jQuery's success comes from the fact that jQuery is based on, and excels at, performing simple tasks.

I've heard people describe jQuery by saying, "The way you do something in jQuery is to think about the word that describes what you want to do; and then you write it and it just works."

Page 55: jQuery UI Widget Factory

7

$('#notification').text('Your file has been uploaded').show().fadeOut(2000);

Usually we want to do something more complex.

Luckily, with jQuery, performing a complex task is nothing more than chaining together several simple tasks.

:: describe code functionality ::

Page 56: jQuery UI Widget Factory

8

$('a.user').click(function() {var url = this.href;$('#user-info').load(url);return false;

});

Even ajax and progressive enhancement are dead simple with jQuery, as we can see in this example.

:: describe code functionality ::

Page 57: jQuery UI Widget Factory

9

What about stateful widgets?

$('div').dialog();

However, all core methods are essentially stateless.

So what happens when you build a plugin that needs to manage state?

Page 58: jQuery UI Widget Factory

10

$.ajax({url: '/foo.html',beforeSend: function() { … },success: function() { … },error: function() { … }

});

The closest example to a stateful widget in jQuery core is probably the ajax method.

The XMLHttpRequest that it generates has state and jQuery.ajax allows you to bind callbacks to events that occur based on state changes to the request.

:: describe code functionality ::

If we follow this pattern to build a stateful widget, everything seems fi ne at fi rst glance.

Page 59: jQuery UI Widget Factory

11

$(elem).dialog({modal: true,open: function() { … },beforeClose: function() { … },close: function() { … }

});

:: describe code functionality ::

However, we'll quickly notice that something's missing: jQuery does not provide methods for modifying the options or state after the call to jQuery.ajax has been made.

This makes sense when dealing with XHR requests, but would be unacceptable for UI widgets.

In this example, we would have no way to programatically open or close the dialog.

Page 60: jQuery UI Widget Factory

12

How do we handle initializationand function calls?

$(elem).dialog('open');

So we're left with the question “how do we handle initialization, function calls and state management?”

Page 61: jQuery UI Widget Factory

13

The widget factory:A solution to state management

We knew that whatever solution we came up with would be able to be applied to all of our plugins.

So we built the widget factory to handle all of the common functionality based around state management.

Page 62: jQuery UI Widget Factory

14

Defning an API

As I just mentioned, the overarching question behind this whole process was “how do we handle initialization and function calls?”

So, the fi rst step in building the widget factory was deciding what the exposed API should look like.

Page 63: jQuery UI Widget Factory

15

$(elem).dialog({

draggable: false,modal: true

}).dialog('open');

Most of you are probably aware of the jQuery UI concept.

You call a plugin with no parameters or a set of options to initialize the plugin.

After initialization, you can call the plugin and pass a method name as a string to execute the method on the plugin instance.

Show of hands: how many people like the jQuery UI model for initialization and function calls?

What else can we do?

Page 64: jQuery UI Widget Factory

16

$(elem).createDialog({

draggable: false,modal: true

}).openDialog();

We can expose many functions in the jQuery.fn namespace.

Unfortunately, this pollutes jQuery and creates multi-word function names.

Page 65: jQuery UI Widget Factory

17

$(elem).dialog({

draggable: false,modal: true

}).dialogOpen();

We can do a little better than this by using a common prefi x for all functions, but it's essentially the same as the previous model.

Page 66: jQuery UI Widget Factory

18

$(elem).dialog({

draggable: false,modal: true

}).open();

Another option is to return the plugin instance, either on initialization or on subsequent calls.

However, this breaks chaining and only works with one element at a time.

A similar implementation has been suggested where the plugin instance would actually be a modifi ed version of a jQuery instance.

This solves the problem of only working with one element, but can be confusing to have different methods available based on context and it increases the chances of name collisions.

Page 67: jQuery UI Widget Factory

19

var dialog = new $.dialog(elem, {draggable: false,modal: true

});dialog.open();

We could also create a purely object oriented implementation that doesn't go through jQuery at all.

While this may not seem very jQuery-like, it may actually be the most convenient model when building complex pages or applications because it's the most direct.

Page 68: jQuery UI Widget Factory

20

Any other models?

Does anyone have any other methods they've used or ideas they've been thinking over for dealing with stateful widgets?

Let's do another show of hands for some of the more common approaches:

- pass method name as string- return plugin instance- direct instantiation with OOP

Page 69: jQuery UI Widget Factory

21

jQuery UI actually implementsmany of these models.

$(elem).data('dialog');

jQuery UI actually implements three of the models, with some minor caveats.

Page 70: jQuery UI Widget Factory

22

$(elem).dialog(options);

$(elem).dialog('open');

We've got the model that we've been publicly supporting for years.

We expose a single jQuery method that is used for both initialization and method calls.

Page 71: jQuery UI Widget Factory

23

$(elem).dialog(options);

$(elem).data('dialog').open();

We also support getting the plugin instance after initialization.

The caveat here is that we never return the plugin instance through the plugin method, you have to get it yourself using the data method.

Page 72: jQuery UI Widget Factory

24

var dialog = new $.ui.dialog(elem, options);dialog._init();

dialog.open();

Finally, we support pure OO use of our widgets.

The caveat here is that you have to call the init method yourself after calling the constructor.

Page 73: jQuery UI Widget Factory

25

How does it work?

$.ui.dialog.prototype

So, how does this all work?

All jQuery UI widgets have a constructor and a prototype.

When you initialize a plugin, it constructs a new plugin instance and stores it on the element using jQuery.data().

Then subsequent plugin calls are delegated out to the plugin instance.

Page 74: jQuery UI Widget Factory

26

$.ui.dialog.prototype.draggable =function(toggle) {

return this.option('draggable', toggle);

};$(elem).dialog('draggable', true);

This means that it's extremely easy to add or modify the functionality of a widget.

:: describe code functionality ::

Because the widget factory just delegates all plugin calls to the plugin instance, new methods are automatically available through the exposed jQuery method.

Page 75: jQuery UI Widget Factory

27

Using the widget factory

At this point we know the main problem that we need to solve for creating stateful widgets.

We've also seen how the widget factory addresses these issues.

So let's take a look at how to use it to build our own plugins.

We'll walk through some example code with an extremely simple progress bar plugin.

Page 76: jQuery UI Widget Factory

28

The bare minimum

We'll start with the bare minimum code needed to make a working plugin.

Page 77: jQuery UI Widget Factory

29

$.widget('ui.progressbar', {_init: function() {

this.element.text(this.options.value + '%');

}});$.ui.progressbar.defaults = { value: 0 };

To create the plugin, we call jQuery.widget and pass two parameters: the name of the plugin, including the namespace, and the prototype.

The widget factory creates a constructor, based on the plugin name, and calls the _init method whenever a new plugin instance is created.

You can also set default values for all of the plugin's options as shown here.

The jQuery.ui.progressbar namespace is created for us by the widget factory (this is actually the constructor that it generates).

Page 78: jQuery UI Widget Factory

30

$(elem).progressbar();

$(elem).progressbar({ value: 25 });

<div class=“{progressbar:{value:10}}”>$('div').progressbar();

At this point we can initialize our plugin, with or without options.

There are a few different ways to set the options for a plugin during initialization. The options are determined by looking at the default values, then any metadata provided and fi nally the options hash passed in.

The values are combined using a deep extend and use of metadata requires the metadata plugin.

The merging happens before the _init method is called, so you don't even need to think about this when building a plugin.

It's a good idea to make all options truly optional by providing the most common settings as defaults.

Page 79: jQuery UI Widget Factory

31

Working with options

The options used to confi gure the plugin are essentially the heart of our plugin's state.

So now that we have a working widget, we'll want to support changing the options after initialization.

Page 80: jQuery UI Widget Factory

32

$(elem).progressbar();

$(elem).progressbar('option', 'value');

$(elem).progressbar('option', 'value', 5);

We can see here that there's an option method that allows you to get and set individual options. This is defi ned in the base widget prototype which our progress bar plugin extends.

The option method also accepts a hash of key/value pairs, just like the css and attr methods.

Page 81: jQuery UI Widget Factory

33

_setData: function(key, value) {this.options[key] = value;if (key == 'value') {

this.element.text(value + '%');}

}

To add custom functionality, we can implement a _setData method, which takes a key and value.

Here we're reacting to the user setting the “value” option by updating the text of our progress bar.

We also update the options hash to make sure that it always represent the current state of the plugin. This makes it easy for users to query the plugin for its current state.

Page 82: jQuery UI Widget Factory

34

Adding methods

Now that we're able to control the plugin's state through it's options, we'll probably want to add some methods to control its behavior.

Page 83: jQuery UI Widget Factory

35

progress: function(value) {this._setData('value',

this._constrain(value));},_constrain: function(value) {

return Math.max(0,Math.min(value, 100));

}

We can add methods to our plugin just by defi ning them in the prototype that we pass to jQuery.widget.

Any functions that start with an underscore cannot be called through the plugin's exposed method. However, if the user accesses the plugin instance directly they can still execute these functions.

:: describe code functionality ::

Page 84: jQuery UI Widget Factory

36

progress: function(value) {if (this.options.disabled) {

return;}this._setData('value',

this._constrain(value));}

The base widget prototype also adds a disabled option, along with enable and disable methods.

The enable and disable methods are just convenience methods that delegate out to the _setData method. The default implementation will also add or remove a disabled class, in this case ui-progressbar-disabled.

Page 85: jQuery UI Widget Factory

37

Creating callbacks

At this point we have a lot of functionality built into our plugin with just a little bit of code, but we still need to provide a way for users to customize the behavior of our plugin.

Adding callbacks to your plugins is one of the easiest ways to make your plugins extensible.

Page 86: jQuery UI Widget Factory

38

progress: function(value) {value = this._constrain(value);this._setData('value', value);this._trigger('change', null, {

value: value});

}

To execute a callback, you can call the _trigger method on your plugin. This will execute a callback as well as trigger an event.

The callback can be specified in the options and events can be bound on the element or any parent since the event bubbles. The name of the event will be the name of the callback prepended with the name of the plugin, to prevent name collisions with other events.

:: explain parameters for _trigger ::

Page 87: jQuery UI Widget Factory

39

progress: function(value) {value = this._constrain(value);var data = { value: value };if (false !==

this._trigger('change', null, data)) {this._setData('value', value);

}}

In many cases it will make sense to allow the user to prevent an action from occurring, e.g., preventing a dialog from closing. To accommodate this, the _trigger method will return false if the user returns false or calls event.preventDefault() in an event handler or the callback.

It wouldn't really make sense to prevent changing the progress of progress bar, but we've implemented it here just for reference.

Page 88: jQuery UI Widget Factory

40

progress: function(value) {value = this._constrain(value);var data = { value: value };if (false !==

this._trigger('change', null, data)) {this._setData('value', value);

}}

In many cases it will make sense to allow the user to prevent an action from occurring, e.g., preventing a dialog from closing. To accommodate this, the _trigger method will return false if the user returns false or calls event.preventDefault() in an event handler or the callback.

It wouldn't really make sense to prevent changing the progress of progress bar, but we've implemented it here just for reference.

Page 89: jQuery UI Widget Factory

41

Cleaning up

In many cases it will make sense to allow a user to apply and then later unapply a widget.

So we need a way to undo whatever our plugin has done.

Page 90: jQuery UI Widget Factory

42

destroy: function() {this.element.text('');$.widget.prototype.destroy.call(this);

}

This is exactly what the destroy method is for.

This is useful for reverting changes such as unbinding event handlers or removing classes.

The destroy method is automatically called if the element is removed from the DOM, so this acts as a form of garbage collection as well.

Page 91: jQuery UI Widget Factory

43

Even more goodies

There are a few more features that the widget factory provides and some details that are worth learning, but are a bit outside the scope of this talk.

Page 92: jQuery UI Widget Factory

44

What's planned for the future?

While the widget factory has been around for a while, it's still evolving. We're working on making it even easier to use and adding some more functionality.

Page 93: jQuery UI Widget Factory

45

$.widget('ui.progressbar', {options: { value: 0 },_init: function() {

this.element.text(this.options.value + '%');

}});

For example, we're moving the default options into the prototype so you don't need to do additional setup after your call to the widget factory.

Page 94: jQuery UI Widget Factory

46

$.widget('ui.complexDialog',$.ui.simpleDialog, {_init: function() {

// …}

});

We're also adding in inheritance, so extending an existing widget is as simple as passing that widget in your call to the widget factory.

Page 95: jQuery UI Widget Factory

47

Go build some widgetsGive us some feedback

Page 96: jQuery UI Widget Factory

48

http://nemikor.com@scott_gonzalezhttp://speakerrate.com/scott.gonzalez

$(λ);