Publishing Events In YUI3
One of the things I love most about YUI3 is the event utility. Firing and listening for custom events makes solving typically tedious problems easy, allows for true extensibility in the application and compartmentalizes functionality into logical buckets. Let’s examine the following issue and how we might use YUI3’s event structure to help solve it.
The sign in and sign out events are two of the most interesting events that could impact a web application on multiple levels. For the purpose of this example we will use the sign out event and follow how that might effect a web application with several widgets residing in it.
We’ll start off with a simple page that contains a cart widget that holds a number of items we have in our cart. When the user signs out we will want to empty the cart without refreshing the page, as the user may be in the middle of a search or some other important action that we don’t want to interrupt if possible. Since we use authentication and carts on many sites it makes sense to turn these two items into YUI3 widgets that will handle our events. YUI3 widgets are designed to handle common user interface patterns in applications. Here’s what a simple cart widget may look like:
YUI.add('cart', function(Y) {
function Cart( config ) {
Cart.superclass.constructor.apply( this, arguments );
}
Cart.NAME = "cart";
Y.extend( Cart, Widget, {
initializer : function() {
},
destructor : function() {
},
renderUI : function() {
this.countNode = this.get('srcNode').one('.cartCount');
},
bindUI : function() {
Y.on('auth:signOut', this._emptyCart, this);
},
syncUI : function() {
},
_emptyCart : function(e){
this.countNode.set('innerHTML', '0');
}
});
Y.Cart = Cart;
}, '1.0.0' ,{requires:[ 'widget' ], skinnable:false});
If you aren’t familar with YUI3 widgets let me briefly run down what’s happening here. The first line of this code registers our widget to the YUI instance in which it’s being used. A little further down we are extending the Widget class which provides us with some basic functionality. When the new method is called on our widget the initilazer function is immediately called. Likewise, destructor on destroy. When we call render on our widget three functions are called: renderUI, bindUI and syncUI. In our example renderUI sets this.countNode to the carts count span so that we may manipulate those contents later.
After renderUI sets the countNode we are now ready to bind our listener: Y.on('auth:signOut', this._emptyCart, this). This simple line tells our widget to listen for the signOut event and perform an action when it happens. In this case it calls the _emptyCart function which simply sets the innerHTML of our count span to 0.
Next we will create an authentication widget that would likely contain functions to handle signing in and out. For our example we’ll create a simple handler for when the sign out button is clicked.
YUI.add('auth', function(Y) {
function Auth( config ) {
Auth.superclass.constructor.apply( this, arguments );
}
Auth.NAME = "auth";
Y.extend( Auth, Y.Widget, {
initializer : function() {
////////////////////////////////////////////////////////
// Create custom events
////////////////////////////////////////////////////////
this.publish("signOut", {
broadcast: 1
});
},
destructor : function() {
},
renderUI : function() {
this.signOutNode = this.get('srcNode').one('#signOut');
},
bindUI : function() {
this.signOutNode.on('click', this._signOut, this);
},
syncUI : function() {
},
_signOut : function(e){
e.preventDefault();
this.signOutNode.set('innerHTML', 'sign in');
this.fire('signOut');
}
});
Y.Auth = Auth;
}, '1.0.0' ,{requires:[ 'widget', 'event-custom' ], skinnable:false});
The structure of this widget is similar to the cart widget. We are setting this.signOutNode to the sign out button and attaching a click event. The click event fires a custom event called signOut. Our initializer function is what I want to spend a little more time on.
this.publish("signOut", {
broadcast: 1
});
The first line of code creates a custom event called signOut passing in a paramater of broadcast:1. Broadcast will notify the YUI instance every time this event is fired. This allows us to use the line Y.on(‘auth:signOut’) in our cart widget.
Putting it all together our HTML document looks something like this:
<head>
<script src="http://yui.yahooapis.com/3.3.0/build/yui/yui-min.js"></script>
<script>
YUI({
modules: {
cart: {
fullpath: 'cart.js',
requires: ['widget', 'event-custom',]
},
auth: {
fullpath: 'auth.js',
requires: ['widget', 'event-custom']
}
}
}).use('node', 'cart', 'auth', function(Y) {
var myCart = new Y.Cart({
srcNode : Y.one('#cart')
});
myCart.render();
var myAuth = new Y.Auth({
srcNode : Y.one('#auth')
})
myAuth.render();
});
</script>
</head>
<body>
<div id="auth">
<button id="signOut">Sign Out</button>
</div>
<div id="cart">
<span class="cartCount">5</span>
</div>
In Conlusion
Could we have handled this more procedurally? Sure. This is a simple example that could have been handled many different ways. As you begin adding complexity in the form of additional widgets or porting of these widgets into other applications the power and grace of the custom events become apparent.
0. David added the following to the conversation
Luke pointed out that we could have, and probably should have, used publish(‘logout’, { broadcast: 1 }) instead of this.addTarget(Y) in the example above.