I’ve worked on quite many widget projects, and very often they have certain patterns that keep appearing.
I’m calling these “widget design patterns” – let’s take a look at a few.
The view
This is probably the most common one. Almost all widgets have more than one “view” – a state which displays specific things, such as a form or a list.
The pattern usually shows as a bunch of divs whose visibilty is toggled with JavaScript.
<div id="home"> Welcome to my widget <button onclick="showConfig()">Click for config</button> </div> <div id="config"> This widget has no configuration options </div> <script type="text/javascript"> function showConfig() { document.getElementById('home').style.display = 'none'; document.getElementById('config').style.display = 'block'; } </script> |
My widgets usually have manual togglling in case of just a few views, or some helper functions if more.
This pattern could be easily abstracted to a class, which would handle keeping track of views and toggling them so that only one is visible at a time, and could perhaps even contain some method for adding event listeners that get alerted when specific views are shown or hidden.
The data view
This is usually a two-part pattern. You have a data source, which is a JavaScript object that can be read or that can push values to the data view.
For example, if you have a list of items, you might have a data view which renders itself as an unordered list, and gets its data from ItemStore. This could either work so, that when ItemStore’s values change, it fires an event and all data views that have subscribed to the event update themselves, or it could work by calling update on the data view, which then polls the ItemStore for data.
This pattern could be abstracted by providing a set of methods for firing events and subscribing to them, and/or fetching data from the data source object. Also, since this would usually involve markup of some kind, having a simple way to define a certain set of HTML to be generated would be useful. Something similar to Dojo’s DTL or Dijit templates?
The form
This is another pretty common pattern. Handling a form purely in JavaScript.
Many widgets have at least a configuration view that contains some values that you can then change. Others, like my bus time table widget, depend completely on form interaction.
Usually, this pattern is indicated by the presence of forms in the HTML markup and the subsequent JavaScript code required to parse them. Typically, you’d attach an event on the submit button to call a function which then checks the values in the form, and toggles CSS styles on erroneous inputs or changes views.
This could also be an antipattern: since Opera supports Web Forms 2.0, you could let it handle most of the things for you – for example, you can indicate a required element with the required attribute, and you can add things like regexes and min and max values to some field types. No JavaScript is required for validating the form, unless you require very specialized rules.
The form usually also functions in a similar way as the data view, by displaying the values previously inputted in the fields. Thus, it too would benefit from having a common datasource and event dispatching model.
The storage
Like the form and the view, this pattern appears in almost all widgets. Most widgets need to store some kind of data, be it in the internal opera widget preference store, or by pushing data to a server.
Depending on the widget, there may be a simple snippet of code which stores data, and another which retrieves it, in a simple string format. Others may serialize data into JSON and store that. The common parameter is that all widgets would benefit from a common storage interface, which could be easily swapped.
In its simplest form:
var Storage = { get: function(name) { }, set: function(name, value) { } }; |
The above is a very simple storage abstraction. You could easily have the get and set methods return data from json, strings, anything really. You could add events for fetching asynchronously, even if it didn’t really use that, since then it would be easier to swap it for XHR-based storage mechanisms such as the Opera XML store.
The sync
This one is a relatively new one. Not many widgets yet utilize the Opera XML store, which can be used to save data to your my.opera account. This pattern is more of a best practice type of thing, based on what I’ve found to work quite well.
A widget which needs to sync values into the Opera XML Store (or other similar mechanisms), will need to log in, check for data, push new data etc.
First, you log in. If it doesn’t fail, check for existing data. This is so that you can easily abort the syncing process and deal with the data in the store, in case it’s newer than the data on the widget. After the existing data (if any) has been checked and dealed with, the script can send in the new data which should replace the old data.
This pattern can be easily achieved for example with my Opera XML Store JS classes, which let you deal with JS objects and methods, rather than having to think of what goes on behind the scenes (converting data into xml, sending it, parsing the responses etc.)
During the whole process, it’s important to keep the user notified of the progress.
In closing
These five often appear in non-widget single page ajax applications too. As far as I know, there aren’t many libraries around that would provide functionality for these patterns out of the box.
Ext JS and Dojo probably have the most, as they both have templating, events and data source abstraction. JavaScriptMVC can probably also help with views. However, at least Ext can be a bit heavy weight, so it may not be a good idea to utilize too much of it or risk having the widget very slow on mobile devices.
d4n3, who’s another Opera widget developer, created a library he calls “Widgets on Wheels” to assist with some common tasks in widget development. To check out his library, grab his cookbook widget. Personally I found it a bit messy, but he says he plans on improving it.
Feel free to point out any patterns you’ve noticed, or any helpful libraries