Welcome, guest Sign In

Video: Satyen Desai — A Widget Walkthrough

Video published 2009-10-29.

Transcript:

Satyen Desai: Welcome to the Widget Walkthrough talk. My name is Satyen Desai, I'm a developer on the YUI team, and today I'm going to be taking you on a walk through the YUI 3 widget class. The talk is geared towards people who want to develop their own widgets in YUI 3. For the first half of the talk I'll be covering some of the design principles behind the YUI 3 widget class, and then we'll move on from there to look at the code behind a concrete spinner implementation, and see how that puts the principles we discussed in the first section into practice. With that, let's jump straight in.

When we set out to create the widget infrastructure for YUI 3, the first question that comes to mind is: what were the set of goals we were trying to address? What was it that we were trying to do? The first goal which comes to mind is a fairly obvious one. We wanted to make sure that all our widgets in YUI 3 shared a consistent base API so that no matter which widget you looked at, they had a consistent set of generic properties and methods upon which they were based.

The second thing we wanted to look at was formalizing development or design patterns which we'd encountered developing YUI 2 widgets over a four year time span. We wanted to make sure we formalized them and make sure that widgets build in YUI 3 followed that pattern, again, so that as you move from one widget to the next, they'd be doing the same type of things at the same point in the widget lifecycle.

The third area we wanted to hit was markup and [CSS] class names. We wanted to make sure all our widgets share a consistent markup structure philosophy, especially in cases where widgets were doing similar things. I think, more importantly, we wanted to make sure they shared a consistent class name structure, so the classes which were applied to those markup elements were consistent across widgets.

The final thing we wanted to address, highlighted by those two bullet points, was the whole kitchen sink concept. We wanted to make sure that we don't ship monolithic widgets with the entire feature set collected over a series of enhancements over a long period of time. We wanted to make sure all of that doesn't get bundled into a single class that gets shipped as a widget. We wanted to deliver lighter core widgets and then allow the user, or potentially other component developers, to mix and match features into that core widget set.

Let's spend some time digging into each of those development points, starting with the API. In YUI 3, the widget class extends base. I'm not sure how many of you are familiar with base, but base is our generic state management object in YUI 3. What it brings to the table is attribute support which widgets will use to maintain their state model. It brings to the picture event target, which allows widget to publish custom events. And it brings onto the table plugin host, which drives that mix and match feature set functionality we looked at on the previous slide. By virtue of extending base, widget has all that functionality available to it, and additionally it defines the generic set of attributes I was talking about — the generic set of methods — which we thought would be applicable to all widgets. As a custom widget developer, all you need to do is extend widget class and then start customizing behavior specific to your widget.

The common API that I mentioned: what's in it? What does the widget base class contain? It contains a generic set of attributes which we think will be applicable across all widgets. Things like a reference to the outer containing element for that widget, or the bounding box, attributes for height and width management, attributes for state management such as focus, disabled, visible. All those attributes are implemented and bundled at the base widget class level, but can be customized for your particular implementation.

Moving on to methods. The methods that the base widget class establishes are obviously the get and set method which are used for attribute management, the on and after methods which are used for event subscription — so users of your widget can use the on and after method to subscribe to custom events published by your widget — and a series of what I'd call sugar methods. These are methods that sit on top of the attributes which define the widget, and just provide a more user friendly interface for end users of your widget to interact with.

We've done attributes and methods, and the final component of the API is events. By virtue of storing its state in attributes, widget already provides a low level event interface for not only users of your application to hook into, but also for your internal development to key off of. We'll dig into some of those concepts a little later. Apart from the attribute change events like the height change and width change events shown there, widget also publishes events for each component of its lifecycle, and we'll look at that in a little more detail as we move along.

That was the common API or common base class concept we wanted to establish widget. Over the next couple of slides I'm going to talk about common designer development patterns we wanted to introduce for widget development.

The first thing we wanted to look at, and which came up early during widget development, was the idea of separating the state of a widget from the code, or the set of methods, which are responsible for the visual representation of that state. Not unlike the MVC type patterns you see out there. We wanted to apply that to the widget infrastructure, and we thought it provided certain benefits. Just the idea of coding your widget so that all the app methods which deal with widget state – and when I say app methods, I was looking for the right word there. What I'm trying to say is any methods which define the logic for your widget, which have nothing to do with how it's represented on the page, but just basic logic methods. If widgets were coded so that those set of methods were separated from the visual representation, the first benefit it can provide is the ability for re-use.

Let's take an example. Say you have a spinner widget, which is what we'll be looking at a little later. Logically speaking, all it does is allow you to select one value from a given range between a min and a max. That same model, and that same implementation, could potentially be used in something like the slider, for example, which has a completely different visual representation for that selector UI interaction.

Basic Spinner Widget implementation in YUI 3

Another thing we're looking at along those lines is the idea of a parent-child relationship, or a list list-item relationship. If we could have the model for those types of widgets, separate from their visual representation, we could use the concept of adding children, removing children, navigating around children, having events bubble up from children to parents. We could have that model re-used across widgets which vary in visual representation. For example, the tab view could use that model along with a menu, along with a tree, essentially. All of them could share that same set of code while having separate visual representation. What would bind the two things would be the powerful YUI 3 event infrastructure [video]; we'd be using that power to bind these two aspects of the widget together.

It's worth clarifying one thing, I think: initially during widget development, we were actually considering breaking these out into separate classes. So, as a widget developer, you'd have to implement a model class, or a render class, or a view class, along with the glue which bound the two together. We backed away from that idea, just in terms of the engineering overhead and potential constraints with JavaScript engines, and combined those pieces of functionality into one class. But the patterns people follow were suggesting they make sure that methods for each of these various responsibilities stay separate. We'll look at concrete details of that as we look at the spinner example.

That was one area of design we wanted to look at: the state/UI separation. The other area was defining a concrete lifecycle for the widget. We want to make sure, again, that any widget you looked at, if it did a certain set of operations in a given lifecycle moment, those operations, or the things it did, would be consistent when moving from one widget to the next. The three lifecycle moments which widget defines are the init moment, the render moment, and the destroy moment. The init lifecycle phase and the destroy lifecycle phase actually come from base. Base defines these, and it defines what needs to be done during these two phases. The render phase is introduced by widget's aspect of a visualization for a given state. So widget adds the render moment.

Digging into those phases in a little more detail, the init phase is all about creating the state for your widget; establishing the initial state. As part of the widget hierarchy, when you initialize a widget, what it'll do during the init phase — which is called by the constructor — is loop around each of the classes in the hierarchy. It'll set up the attributes — which you define for the widget, and we'll dig into that ATTR rest property a little later – and invoke the initializer if it's defined for that class in the hierarchy, and which can perform any additional initialization steps.

Going back to the slide about state versus UI separation, an additional benefit this provides is that if we split up the state aspect and the view aspect after initialization, you have a fully functional widget which you can call methods against, set and get attributes against, and have the model be updated accurately without anything being reflected in the DOM. So for larger scale applications, like Yahoo! Mail for example, you could create instances of widget not have them rendered to the page, set them up in the state you require them in, and render them when they're ready to be displayed. Once any widget is through the init phase, it stays in a consistent known state and can be programmatically worked against.

On the flip side of things, the destroy moment is responsible for clean up. I'm sure that's something you're all familiar with. The idea here is just to provide consistent hooks so that any widget class implementation provides a destructor if it has clean up to do during the destroy phase. Traditionally, stuff going on in the destructor is things like detaching event handlers, cleaning up any references to heavier objects which the widget may be holding on to. Memory can be freed up the next time garbage collection is invoked.

Those are the two state related moments: init and destroy. Init for creating the state, destroy for getting rid of it. In the middle is the render phase. As I mentioned, this is introduced by widget. As part of the render phase definition, the base widget class implements a render method. Traditionally, the render method is not something extended or customized widgets need to implement. Widget implements it, and it implements so that it calls these three abstract methods: render UI, bind UI, and sync UI. As a widget developer, your task is to implement each of these three methods for your customization.

The role of each of these we'll go over in a little detail, but at a high level, the idea of the render UI is the step at which the component or the widget lays down its visualization. That's where it creates DOM elements which the widget will use for its view. The bind UI step is where the widget sets up that event bridge we looked at. It's where the widget sets up events which will translate changes in the model or state over to the view. And vice versa, where it'll pick up DOM elements from the view and use them to set the state on the widget. The sync UI step is a phase where the widget can take its given state at any moment in time, and update the view to reflect it. It's a way to sync the view for a given widget state. So those are three phases: init, render and destroy, which should be consistent across all widgets in the YUI 3 library.

We discussed common API, we discussed design and development patterns. The next area I wanted to touch on was consistent mark up and class names. Default widget class, the default markup pattern, is a dual box pattern which I've found is useful in a wide variety of widgets. The idea behind this model is that the outer bounds of the widget are defined by the bounding box you see in there. The role of the bounding box is to control functional aspects of the widget's layouts; things like its width and height, and how it impacts content around it, such as whether the widget is a displayed lock type widget or is more of an inline block type widget. Those aspects of the widget are controlled by the bounding box. The bounding box traditionally will not have visual aspects applied to it; you wouldn't apply a border or padding to the bounding box. This allows us to deal with height and width properties without worrying about box bottle variance across browsers.

Where the widgets visual look and feel come into play is the content box, which is nested inside the bounding box. The idea is that the content box, for example for the spinner, would contain the input element plus the button elements required for the spinner. It could have borders defined, padding defined, background color defined. So that's the separation of roles in terms of the bounding box and the content box. We've found this model to be useful in a variety of widgets: block level widgets such as an overlay, data tables, inline widgets or inline block widgets such as buttons, for example.

An additional benefit of providing the dual structure is that the bounding box acts as a holder for decorator elements. So if you want to do things like rounded corners, or shimming, or shadow implementations on browsers which don't support them natively through CSS for example, the bounding box acts as a container where you can add elements for those decoration items. They can sit under or over the content boxes required for their presentation. That's a dual box structure.

In addition to the structure, we also wanted to make sure that the class names we generate, either as markers – for example, class names we generate mark elements inside the widget such as the content box or the bounding box — were consistently named. In this case, what that means is that they're prefixed by an application ID, and then the name of the widget is used as a prefix combination before they add any additional values which define things like, for example, the state they're representing. All the class names generated are in their own space defined by the application prefix, plus the name of the widget.

The final design area I wanted to cover was the area of the kitchen sink: the light base implementation with a mix and match feature set. I think this is something you've seen mentioned. It was mentioned in the YUI 3 talk I gave, and Eric mentioned it this morning. The idea here is really to be able to ship a light core widget class for your particular widget, but then allow the end user to mix and match end features as required by them on particular instances of your widget.

Here's an example. As a component developer, I could write a light overlay implementation which doesn't have animation or IO built into it; it's not built into that same one monolithic class. End users can create instances of overlay on their page which doesn't have that functionality, but if there's one of ten overlay instances they want on the page which requires animation, they can pull down the animation plugin, and apply it to the overlay instance using the plug interface. That single instance will have animation capabilities, while the other nine on their page will stay basic overlays.

In a similar vein on the component developer side of things, if I'm a component developer and I want to create a tooltip class, I don't necessarily need to extend something like overlay, which has a large part of the features I need but has a bunch of features extra which I don't really need. For example, overlay has a header body footer region which it supports, and which I don't necessarily need for tooltip. It has advanced positioning support, which I don't necessarily need for tool tip. I can choose to build my tooltip, which just extends widget and pulls in just the extensions I need for the functionality I need. Widget position would give me basic X Y support, widget stack would give me stacking or shimming support, and I don't need the header body footer support or the advanced positioning support which overlay has. So you get the mix and match flexibility both as an end user on a per instance basis, or as a component developer at the class level, through extensions.

That's about all I have to say in terms of the general design approach for widget, the set of patterns we were looking to follow. Using that discussion, I wanted to look at actual code for a spinner implementation as an example. We can dive a little deeper into each of those concepts we were talking about.

This spinner example is actually live on the developer site, so that's the example I'll be walking through — hopefully in a little more detail than the example — when we look at the code snippets. The spinner is a good example because when we're talking about the model versus view concept, the spinner really only has one part of its model as its actual value which needs to be reflected in the DOM. We'll follow that value attribute throughout the set of methods we looked at, and see how it's handled.

As a widget developer, the first thing you need to do is extend the widget class. In this case, spinner extends widget, and that's fairly straightforward. Then in sticking with the design patterns and API we discussed, spinner then adds the customization aspects it needs. What drives most widget customizations is the set of attributes for that spinner; they define the model for the spinner, how that spinner is going to hold its state. Following that definition, optionally we specify any additional stuff we want to set up in the state through the initializer. It's rare that people actually need that. The bulk of the work in terms of setting up the state for widget occurs through the attribute definition, and we'll look at code snippets which show all of that. The initializer generally stays empty or undefined in most widget customizations.

The next three steps are obviously required for any custom widget implementation. As I mentioned, they define the markup which will represent the widget, the set of events which bind the state and view sites of the widget, and then a method to sync up the view based on the given state. Finally, the destructor for clean up. We'll dig into each of these implementations for spinner, and see how they pan out.

We'll start off with the constructor. That's fairly straight forward; there's nothing really widget-based in there. All it's setting up is a constructor function which will define spinner instances. The only thing it's doing is changing up to the parent class, which in this case is widget, to kick off the lifecycle we looked at. Simply calling the super class constructor will kick off the init phase for the widget, which will set up attributes and call initializers as required. Then the simple extend call to have spinner extend the widget class, and define whatever methods it needs to add to the prototype. That's basic constructor definition, plus the extend call. There's nothing really widget based in there. The one interesting thing to point out is the fact that the signature for all components which are derived from base – so it would apply to widgets too – is consistent across the YUI 3 library. Users of your widget will already be familiar with that signature as an attribute name value bag which they can pass into the constructor of any of your widgets.

The first widget-related aspect which comes into play is this idea of a name. We needed a way to identify a specific type, or class, of widget, and the static NAME property is what we came up with to handle that. If we could have inferred it from the actual constructor function that would have been ideal, but in the absence of that, as a widget developer what you need to do is tell us the string which uniquely defines your widget. By convention, we've been using the class name in CamelCase to set up that value. Where it ends up getting used is in generating the unique class name prefixes I discussed earlier, and also as a prefix for any custom events fired by the widget. For example, if you have a widget which bubbles events up to the Y instance, or bubbles events up to another bubble target, people listening at that bubble target can distinguish the render event from a spinner, compared to the render event from a menu or a tab view, for example, and act on it accordingly. So that's the NAME property. We discussed constructor and we discussed the static name definition.

The attributes definition is where the majority of the model work comes into play. For the spinner implementation, the value is at the heart of everything. That's the actual value which spinner is displaying, or storing, at any particular point in time. The min and the max define the range for the spinner. Let's say for this example that there's a minor step and major step attribute which defines how much the value should jump based on certain input. When you set up the attribute configs for your particular class, the configuration bag for any attribute has a default value which can be provided for the attribute. This is the value the component developer thinks is the suitable default value. Additionally, as you can see in the value attribute, you can also define helpers around that attribute such as validators, setters, and getters. We'll get into a little bit more detail there, but the idea is that all logic surrounding the management of this attribute is centralized in the attribute configuration.

Another interesting point to note is that as a design pattern, we make sure that we don't implement the validation logic inline in this static configuration. Delegating it to a prototype method allows people to extend our spinner implementation and not have to deal with this static bag of properties which we've set up. I'm going to look at that in a little detail also. So that's the set of attributes that spinner defines.

In addition to defining its own attributes, spinner can also override attribute configuration of classes it's extending. In this case, the strings attribute is actually an attribute set up at the widget level, and is used for localized string support on the widget. In this case, all spinner wants to do is override the default value, which for widget is an empty bucket, and add the set of strings it's going to be using for its default locale for the spinner UI. It could, if it wanted to, override the validator, the getter, the setter, or any other part of the attribute configuration. So that's the attribute bag for the widget, and that's really what defines the state the widget will be holding onto as part of its operation.

While we're discussing attributes, it's worth pointing out a hook we're looking at for widgets to support progressive enhancement. Stepping back a few slides, we looked at the idea of setting up a default value for an attribute in this attributes hash we looked at earlier. That's the value which the component developer thinks is the ideal default value for this component. Additionally, the user can pass in an initial value for the component as that config parameter we saw in the constructor. The value for the attribute falls back to the default if a constructor value isn't provided, and otherwise it uses the constructor value.

The HTML parser allows the developer to define another source for that value. So if the user doesn't want to set the value for the spinner programmatically — for example, in the constructor when they create a new spinner — they can point the spinner to markup which already exists on the page for a progressively enhanced solution. Spinner will use the HTML parser definition to get the value for the value attribute from an input box which may already exist in the markup on the page. The idea is that the markup on the page is just another form of initialization data for the widget to populate its model, or its attribute set.

We've looked at constructor, we've looked at the attribute set up – let's move on to the lifecycle methods. The initializer, as I've mentioned, in most widgets shouldn't have to do too much work. Most of the definition occurs in the attribute set, and that's something the widget infrastructure takes care of creating for you. In this case, all spinner is doing is setting up a couple of additional custom events which it's going to use as part of the state set up. So by the time the attributes are configured, and the initializer is run, the spinner widget is ready to programmatically use it. It's not rendered on the page, but you can develop against it.

On the flip side, we've got the destructor for the spinner implementation. All it really does is clean up any event handles it's holding onto. So any event listeners it's subscribed internally, it cleans those up on destruction and then frees up any of the heavier object references it may be holding onto so that they can be garbage collected if no one else is using them.

That's the initialization and destruction phase in the attribute set up, in terms of the state. The interesting part for widgets is the render portion: how this widget is actually visually represented on the page, and that's where the renderer comes in. As we saw earlier, the base widget class defines this render method, so for most common applications each custom widget doesn't need to redefine render as long as this pattern works for it, which we believe it should for most widgets. All the spinner needs to do is implement the renderUI, which is an abstract method on the base widget class, and implement the bind UI and sync UI to do the tasks we've mentioned there.

The only responsibility the spinner's render UI implementation has is to lay down the DOM for the spinner. This is where we first start touching the DOM. Throughout the initialization phase, there's nothing actively changing the DOM in any way. The render UI is the first place where we start laying down new DOM elements as part of this widget rendering. A little later on we'll look into one of these methods. There's nothing really uniquely widget about them; all they do is create elements and append them to other elements on the DOM, but there are a couple of widget helper methods that help in that process and which we'll look at in a bit of detail. But anyway, render UI is pretty straightforward. It renders the input box for the spinner, and renders the buttons.

The sync UI step for spinner is, again, fairly straightforward. The idea of this phase, or this step, is to take the existing state of the spinner – in this case, the only important part of the state which we're looking to reflect in the DOM is the actual value stored for the spinner state. The only thing this method then needs to do is obtain that value from the model using the attribute get method, and set it on the UI. It's syncing up the UI state with whatever it's got stored in its attribute model. We'll dig into that function a little later too.

Finally, the glue which holds both pieces together: the bind UI implementation. This is where the spinner defines what needs to happen when anything in the state changes, what actions I need to perform. In this case, spinner is saying: whenever the value, which I'm storing in my attribute model, changes, this is the method I want to invoke. Again, following the attribute pattern, all of these methods sit on the prototype, so that logic can easily be extended and re-used if required. That part of bind UI is handling the state to view synchronization, the left side to right side in that diagram, if you remember it.

Bind UI also sets up the opposite bridge. It sets up DOM listeners which say: if someone interacts with the input field in my DOM, this is the method I should invoke and will be responsible for updating my model or my state. It'll update the value attribute which I'm storing. Similarly, it sets up a key press listener on the bounding box to say: if anyone presses the page up, page down, or arrow keys within my bounding box, this is the method I want to call. Finally, as a result of this, we'll end up updating the attribute value.

I'll just touch on bind really quickly. I'm not sure if you're familiar with this, but in the YUI 3 world, we're trying to move to a pattern where we use bind for binding an event listener type application to a specific context, instead of using the older event subscription signature, which took the method plus the context it needed to be bound to. That's just something to keep in mind.

Before we jump into those attribute support methods, which we touched on — the UI set value, the after value change listener – I think it's worth spending a little time on why we chose attributes to drive the models for our components, as opposed to, say, regular JavaScript properties. I think the value they provide is in two key areas. The first is the fact that they centralize management for that particular attribute, so no matter where in your widget — or application for that matter — someone sets the value for the spinner, it always goes through the same validation routine, it always goes through the same setter routine which is responsible for normalizing value. So it could take string input, for example, and convert it into an integer. And only if that's all valid does the state actually get updated.

If you had that implementation with regular JS properties then you'd need to make sure that everywhere anyone is touching that JS property, they're performing the required validation and they're performing the required setter clean up. That quickly leads to a fragile system, especially over the course of years and multiple developers. You can see bugs creeping in there. It's the same on the getter side of things. Whenever anyone retrieves this value, if there's normalization to be done on the outbound side, it's localized to this getter implementation and people who request the value don't need to worry about normalizing it, or calling other methods to normalize it. That's one advantage of the value: the centralized port around the particular attribute value.

The other part of it – the more powerful part – is the event firing. Any time an attribute value changes and it's a valid value passed through the setter correctly, the attribute system fires an attribute change. What that does is allow you to reflect dependencies in your code accurately. The only thing the attribute needs to be concerned about is setting its value. If there are ten other pieces of code in your application which are interested in responding to that change to say there's a method which updates to a certain piece of UI, another method which updates a string somewhere to use that new value, the attribute sub-system doesn't need to worry about that. All it does is fire off the attribute change event — which in this case, for example, we listened to in order to update the UI.

In a classic JS property scenario, what you'd need to do is make sure that anyone setting the JS property knows which set of ten methods to call to reflect that updated value in a variety of places. That set of methods won't be constant, and could be external application methods too. The event system allows us to decouple those two things, listen to them both internally in terms of your widget code, and also allow application developers to listen externally to the lowest level of state change, which we think is a very powerful feature.

The last thing I wanted to mention in terms of having attributes and driving this state view separation is the potential for loopback scenarios to occur. For example, for spinner, we have an attribute change event listener which is saying: anytime the value changes, I want to go and update the value stored in my view, which in this case is an input field value. On the view side of things, we've got a change listener we set up to say: anytime anyone types into this box and blurs, to be specific about it, we want to fire an event which will go and update the value we've stored in our attribute so that both of them are in sync. Although this doesn't occur in the real spinner implementation, you could see where there's potential for loopback. For example, if I have a change listener on my input field, it picks up a change to the value, which set an attribute, which fires an event, which tries to update the input field again. If that particular interaction for spinner set the value and blurred it at the same time, that would kick back this loop, and it would be going around in circles. We'll look at a way where we avoid that loopback condition — again, by using the power of the event subsystem in YUI 3 to add additional data about the event based on the source which is making the value change.

Let's dig into each of those attribute methods. The validate value method, as we saw earlier on the prototype, acts as the validator for the value attribute. There is nothing really complex going on here, but I think the key thing to note is that again, anytime this value is set, it's always going to go through this validation code and state will not be changed if this test doesn't pass. Also, this set of logic is what I was referring to as app logic for the widget. It has no references to DOM elements; it doesn't try to look up the value from an input field anywhere and pull it out and validate against it. It's purely contained in the model, and it can be validated against the model. You can actually write tests that just test the model, without the DOM or event implications; you have a layer of tests which test your widget's app logic separately from a set of tests which test the UI interaction for it. So that's validator.

The other method we came across when we were looking at the earlier slides was the UI set value method. By convention, as I mentioned, we decided not to split out the model and the render into separate classes. To identify methods which do something with the UI, we're trying to stick with a 'render_ui' prefix. In this case, this method is invoked as part of the attribute change event for the value attribute, and its only responsibility is interacting with the DOM. This is where we actually take the value and start manipulating the DOM. In this case, it's setting the value attribute of the text field node.

Yes?

Audience member 1: So the validator causes [inaudible] the DOM upgraded to [inaudible]?

Satyen: You cannot. But the idea of encapsulating that logic in a prototype method means that you can invoke it from somewhere else. You can have a DOM listener, or your chain listener, for example, could invoke the same validator to make sure you have a valid value before passing it or using it somewhere else.

Audience member 1: OK. So if the DOM thinks there's an invalid value, the state doesn't update, but the DOM…

Satyen: The DOM is out of sync, but you could validate the fact the state hasn't updated and revert to the currently stored value, for example, if you wanted to. If you look at the example on the site it's actually doing that; I left it out of this for simplicity, but that's what it ends up doing, yes.

We looked at the set UI method, we looked at the validator. This is where that loopback control I was referring to kicks in. It's going back a couple of slides. The after value change method was the listener we set up to be fired whenever the value attribute's state changes. When that happens, this method will get invoked. We use a source attribute we set up on the event façade, which is provided by the YUI 3 event infrastructure, to determine what the source of that change was. In this case, if the source of that change came from the UI, we know the UI is already reflecting a 20 or a 21 or whatever that value was. We don't want it to send the change back to the UI in that case, but in all other cases we want to update the UIs, which is what that method call ends up doing.

On the flip side of that equation, on our DOM listener for the input change we make sure that when the value is set with a new value, it's identifying the source it came from, so that the after logic has the ability to filter out programmatically set values versus values set from the UI.

We'll walk through a couple more of the DOM event listener side of the equation. Again, there's nothing specifically widget related here, apart from stressing the fact that this particular method doesn't have to worry about validating the value it's got. It doesn't have to worry about normalizing it, it doesn't have to worry about invoking other methods which might be waiting for that change. All it does is set the value in the attribute and the system takes care of the rest; all that code is cleanly decoupled. The thing you can concentrate on here, in this particular method, is translating the DOM event to the value which needs to be set in the module, and that's all it does. So in the long run, it leads to more maintainable code. You tend to have less monolithic methods which are doing state related stuff, plus UI related stuff, plus kicking off a bunch of other stuff. Every method is a smaller method which does specifically what it's supposed to.

Another example of that is this next slide. Again, nothing is really widget related here, apart from stressing the fact that we have another method which is setting the value. But again, that doesn't need to replicate validation code, setter code, or whatever else.

I promised earlier that we would look at one of the rendering methods which the render UI method ends up invoking. Again, here there is nothing clearly widget specific in terms of the implementation, it's pretty straight forward. The job of this method is to create the element which represent the spinner's buttons and add them to the DOM, so in this case it's getting a reference to the widget's content box and appending a button element which it creates. The two methods worth pointing out, though, are widgets' get string implementation — which gets you the localized version of the increment string which is used, in this case, for the title of the tooltip for the button – and also the standardized, or normalized, class name. The result of this call will be the prefixed version of the increment class, which it can use to mark the button element with.

Last couple of implementation slides, and I think we're done. This one is, again, touching on the standardized class name generation support. You can call widgets get class name method with the suffix part of your class name if you will, and it'll introduce the standardized prefix for that particular widget. It's the same for the value example. And if you want purely the prefix version, you can call get class name without any arguments, and it'll return a non-suffix version of it.

Finally, what this all means in terms of a CSS implementation. For the spinner, we use the marker class which is used to define visibility, or in this case hidden-ness, I guess. In terms of widget implementations, we're moving away from setting styles directly on elements and using marker classes instead to define state. In this case, the spinner's saying: because it's an inline or inline block element, when I'm hidden I want to use display none as opposed to, for example, moving off-screen somewhere, or using visibility hidden to control my visibility. So you have the ability to customize what the widget is doing purely through CSS, without changing any code.

In the same way, the bounding box marker, which is this one, is used to define the functional layout aspects of the widget. In this case, it's saying that my spinner widget is an inline block type of widget and doing a bunch of stuff for cross browser normalization. But the message there is it's an inline block widget, it's not a block widget or any other type of widget. Finally, the visual handling which I mentioned is applied to the content box. This is where we start applying things like a border, and padding, and a background color, for example, as well as the rest of these standardized class name markers.

I think that's about it in terms of implementation. To the end user, what that looks like is this. They can have, for example, a progressively enhanced solution where they have markup already on the page, and which we want to use to drive the initial value of the spinner, call the constructor pointing to that markup, and render it to get a spinner with that initial value. Alternatively, if they didn't have markup on the page, they could provide an initial value here if they wanted to, and specify where in the DOM they wanted the widget rendered to. So that's the final result. Let me see if I'm live here, and I'll show you what that looks like if I have it up and running. I'll see if I can get that over there for you.

That's the functional example we're looking at. You can see it has keyboard support. We have DOM event listeners firing here which are updating the values stored for the spinner. It has basic button support, and it has that min/max validation we were talking about. You can't see it, but even though I'm hitting the down key, the model integrity is maintained. That should be live, so if that's something you want to play around with…

[Audience member makes inaudible comment]

Satyen: This particular implementation probably doesn't behave well, but the idea here is that if I typed it in, my input change listener would pass through the validator implementation we were talking about. If it's invalid it could call sync UI, for example, to say use the currently stored state as opposed to updating it. If it's valid it can pass it through. I think the key idea there is just the fact that we're not replicating all this logic in multiple places.

I wanted to end with the plans for widget moving into 3.1. As Eric mentioned this morning, we're aiming to nail down GA quality for the widget in that timeframe. What we've got on the road map between now and then is more thorough i18n support. Right now... We looked at the get strings method, for example, and the widget has the ability to programmatically set string name value pairs for specific locales. It'll maintain them, do the look up chaining for example from enUS to en on the client side if it needs to. But that's a programmatic interface. What we want to do is provide declarative support for it so you can dump a JSON blob on the page, have it register as the set of strings for a calendar, or for a spinner, or anything else, and have that JSON be a combined JSON output. So you could provide localized strings for multiple widgets across multiple locales, dump them as one blob, have them register with some kind of i18n manager which the widget could then look up for string input, as opposed to programmatically having set them.

I implied it there, but the second part of the equation was support for web service interaction, so you could hit a web service with the locale you're looking for, get this standardized blob back down on your page, and have it update your localized strings for the widget. And then formatting support – for example, support for things like token in localized strings. Plus we already have infrastructure for date, currency, localization, that type of thing.

We're looking at simplifying the HTML parser implementation. Currently it keys off of the content box to look for existing markup on the page which might drive widget configuration, and we want to open that up so you could point to anywhere on the page. It might not necessarily be somewhere on the page which ends up being part of the final widget, but it's just a point at which the user can say: this is where my markup is coming from, and which defines the initial configuration for my widget. That should simplify the need for the user to know what the content box is and what the bounding box is.

We want to finalize the content box and bounding box structure. The main thing I'm thinking about there is whether the two box structure is overkill for any widgets or not. We've found that it's useful in a large majority of widgets, but I can see certain use cases where you just want the bounding box and there is no need for that dual box structure. That's something we're trying to evaluate.

Also as part of the 3.1 effort, there's a bunch of people, a lot of them in this room, who have started developing production quality widgets on top of the current widget infrastructure. We want to make sure we get their feedback and incorporate fixes for any pain points that they've hit, and make sure that we maintain anything they deemed as a value add. So that's the road map for widget from here to Q1 of 2010.

I think that's about it. I ran a little long, but we have some time for questions, if anyone has any. Go ahead.

[Audience member makes inaudible comment]

Satyen: Yes, we are. I'm not sure if Todd covered any of that in his accessibility talk, but we definitely have the concept of a focus manager which will handle keyboard interaction, for example, for widgets. Anything else we need accessibility related, just by having a generic widget class in place and a generic content structure and class name structure, we can help drive all of that through that common interface. But yes, there are plans for generic accessibility support.

Audience member 4: Do you think the HTML parser adds value [inaudible], is there a place [inaudible] code?

Satyen: Actually, the programmatic way overrides the markup, so if they're also specifying that value in their constructor, that's going to win. That's a good point actually. As part of the HTML parser clean up, we're also looking at providing pre- and post- HTML parser hooks and that could potentially be a way where you could make that decision if you wanted to.

Any other questions?

Audience member 5: With the CSS and the separate file, will that be included in the YUI…?

Satyen: Absolutely.

Audience member 5: The user doesn't…

Satyen: Right, the user doesn't have to specify a separate package. When you say use calendar, it'll pull down the JS plus the CSS for the calendar handle.

Audience member 6: You showed us the markup example of the content box. With YUI 2, the recommendation was always to use the module patterns, so you have [xx] modules, like header, body and footer. Is this still the recommended way, or is the new structure sort of…

Satyen: It's derived from that. That structure, if you look at the YUI 2 container implementations — at least the ones which have more visualization handling like panel, for example — that introduces this double box concept. The idea of a header body footer is not something we're enforcing for all widgets for obvious reasons, but going back to the extension slide, the way the overlay is built is combining the base widget class with this extension which adds header, body, footer support, so you could optionally choose to include it in whatever widget class you're packaging.

Any other questions?

[Audience member makes inaudible comment]

Satyen: I believe so. I have to admit, I'm not up to date on the HTML 5 specs, so I'm not sure which areas you're talking about.

[Audience member makes inaudible comment]

Satyen: Right. All that, as far as it relates to the base widget class, the set of markup it uses for the content box and the bounding box is all configurable. So when you create a custom widget, if you want to use parts of it which rely on the HTML 5 spec, you're free to do so.

Audience member 8: Just wanted some clarification: with the custom events on the widget, the name that you define colon name of the event, that's what's going to be passed around?

Satyen: Right. I'll try not to put my foot in my mouth in terms of events, but in terms of generic custom event infrastructure, all custom events essentially have prefix related support in them. The way that event is defined in the event sub-system is with the prefix. Now, when you're listening for it on the widget instance, so when you use the 'widget.on' or 'widget.after' methods, you don't need the prefix, that's automatically filled in for you. So you can just say: 'spinner.onrender do this'. But it's only when you're bubbling to other targets where you could have a mix of event sources, if you will, that prefix comes into play.

Copyright © 2010 Yahoo! Inc. All rights reserved. Copyright | Privacy Policy

Help us continue to improve the Yahoo! Developer Network: Send Your Suggestions