Welcome, guest Sign In

Video: Isaac Schlueter — Solving Problems with YUI 3

Video published 2009-10-29.

Transcript:

Isaac Schlueter: I'm Isaac Schlueter (@izs) from the YUI team. I'm here to talk to you today about solving real world problems in YUI 3 — in particular, a module that I'm working on for YUI 3 called AutoComplete, and just basically what goes into solving a real world problem in YUI 3.

When I say "real world", I mean something that's an actual use case for actual users on a real webpage that's actually going go out in production. This isn't about a hack or a proof of concept. Those things have their place, and they're great, but eventually you need to get something done. When I say "solve", I mean I should never have to deal with this problem again, because my code never has bugs. But apart from that, if anyone else uses YUI they should be able to just stick this AutoComplete thing in; if they have a similar use case they should be able to use this code without too much trouble. I don't want to have to keep solving this same problem in different ways over and over again. I also don't want to spend a whole lot of effort on this. I want to get this working, not have to do it over and over again, and not have people bugging me with new feature requests all the time. Make it easily extensible, and so on. Basically what that means in less tongue in check terms is that cost matters, and that's really the value proposition with YUI 3.

Here's the problem that we're trying to solve here. I think you've probably all seen an AutoComplete widget, and you probably know more or less what it does. You start typing something, some suggestions show up, and then you pick one and it either fills in the field or it takes you to another page or something. What you might not notice is that each one of these is actually really, really different. When you sit down to look at it, if you look at this right here you've got a search button right there in the widget, you've got this little remove thing. I doubt Google's going to be using YUI 3, but still. You also have cases like this where you've got a 'to' field on an email composition, and what it's actually completing is just this bit after the comma. There are a lot of these weird little things, but essentially what we've got is that you type something, some stuff shows up, and you pick of them.

Here's our shopping list of stuff that we need to wire up to make this work. We need a way to find out when the user types something, right? We need to respond to that interaction. We need a way to get the data, so we can know what things to show. Sometimes that's from a server, and saying "from a server" means any of a million different things. It could also be something that's just statically in the JavaScript. We also need a way to display the list of items such that we can solve a lot of the issues of overlapping other form elements and stuff. And we need a really simple way to wire everything together so that it's not hard to plug it onto some new form element. Really, I want to worry about as little of this stuff as possible. I just want to make it work.

Doing things for the first time, creating a whole new way of doing things, is really, really hard to get right. If you've already got some tools and you can just kind of plug those together, in general, that's going to be a whole lot easier. You'll spend much less effort for a much better result. Again, this is definitely a case of good laziness.

I don't know if I mentioned the pages that this AutoComplete module is going to be going on. It's pretty popular — maybe you've seen it before? It gets something in the order of a couple of hundred million people looking at it every so often. Also, this is going to be going on the header that shows up in all the Yahoo! properties. Combine all those together, and pretty much everybody is going to see this almost every day, so it's got to be very, very small. At this scale, I mean every byte matters. An extra couple of bytes on this thing and you're talking about an extra couple of gigabytes per day that Yahoo! has to send over the wire to its users. By re-using as much of the off the shelf components from YUI as we can, we're going to keep the added overhead really small. If it's something that's already in the page, and we just re-leverage that, all the better.

We've also got to make this accessible, because that's something Yahoo! cares about. A lot of times people think of accessibility as about being keyboard navigable, or being accessible to screen readers, but accessibility's not just about devices and blind people. It's also about working in any language, or in any internationalization, and being able to be accessed by the people who are going to be using it. With Yahoo!, that means pretty much everybody.

Those examples. Again, if you look closely, you'll see that sometimes it's a list of selections, sometimes it's a list of all sorts of other things, sometimes it's pictures and a title, and sometimes… If you've ever seen the rich search assist case, you start typing Kanye West and it shows a picture of Kanye West and news articles, and "Imma let you finish", and so on. This thing has to accommodate everything that is going to be done with an AutoComplete.

Again, thankfully most of the hard stuff is already done — we're just going to plug some pieces together. The other advantage is that by doing things with the grain, we're going to be able to contribute it back to the library. That obviously matters a lot to me because I like getting a pay check, but even if you're solving your own problems for your own website you can contribute this stuff back to the library. The message of that is that you can solve your problems like this too. That's something that I'm really excited about as far as big announcements today, and at this conference. I don't know if you saw yesterday when Eric talked about the YUI gallery. Dav Glass is going to be talking about it later on today, and giving you steps on how you can get your code into the gallery. Getting feedback from people using your stuff is really, really addictive. Definitely watch for these features and pay attention to it, because you can solve your problems and then also maybe put your stuff out there and solve somebody else's problems at the same time. It's a good thing.

Anyway, down to business. A lot of you have probably heard about most of these things if you're at this conference. There are a couple of other people talking about YUI, I believe. If not, I definitely recommend that you pay attention to some of their other talks today, and maybe go to YUI Theater and watch them, because most of these people who talk about it are way smarter than me. Just in case, let me go over some of the pieces that are going to be relevant in this case. The first thing is node. Node is like… I can't put this strongly enough, node is like candy for the DOM. It does everything you think it should, and it does it in predictable and easy ways. If Internet Explorer and Netscape had invented node instead of the DOM they did invent, we wouldn't need YUI, at least not for this. This is what I think of when I think of the browser DOM. He's generally friendly, tries to do the right thing. It's a little bit slow, and has kind of a stuttering problem, and he's got no pants which is kind of embarrassing for everyone.

[laughter]

And if you're in the unlucky majority that gets to support MSIE, you get this guy. I'm sure you've probably all written this garbage before. We've got f is document.getElementsByClassName, so this gets items with a class name of foo. We're going to have to iterate through those, and on each one we want to add an event listener. It's pretty straight forward. And this is if you're lucky, because this doesn't work in IE. There's no getElementsByClassName, there's no addEventListener — you're going to have to fork, and you're going to have to get elements by tag name star, and then filter, and then… Oh, my God. By contrast, the node API gets in there, it gets done what you need it to get done, and it just gets out of the way and you really don't have to worry about it.

This is the corollary to that blob of ugly garbage. Let me get all the things matching a .foo selector, on click fire the function. It works just like you think it should. You can read more about node at developer.yahoo.com/yui/3/node. That's going to be the first item in our shopping list — we're going to plug a node element. When you say you want an AutoComplete on this form element, we're going to wrap that in a node, and that will give us the API to respond to user events.

The second thing we need is a way to get data from a server or from anywhere. Again, I said getting data from a server is like any of a million different things — it could be comma delim, it could be XML, it could be JSON, it could be JSON in a different format. It could be a lot of different things, and I really don't want to have to worry about that. I want a single, consistent API. Data is really, really weird. Again, it's strange, and I don't want to have to deal with it. I just want to get a nice consistent structure.

If you've ever used the Data Source utility in YUI 2, it's good, it's full of features. In fact, it's so full of features it's weighing in at about 7k plus. The thing is, it supports JSON, it supports XML, it supports comma delimited, it supports having a function that returns data. That's all great, but you almost never need all of that. So one of the real value propositions with YUI 3 is that you can just take — OK, I only care about, let's say, JSON, so I only really need this part right here, the JSON schema and the base data source. We can get a really, really tiny slice of what we need, and they all expose the same API. Read more about data source at developer.yahoo.com/yui/3/datasource. You'll probably see a pattern here with these API links.

So that's going to be our generic way to get data from the server, and now we just need a way to display these things. Again, these are not really so similar. Sometimes we have a bit of text, sometimes we have a whole bunch of other stuff, sometimes there's buttons on the thing, sometimes it's delimited. Even though they're all kind of the same UI paradigm, we really need to have a lot of customizability in terms of how the data gets displayed.

Here's what they all have in common, if we can look at that. They all need a bounding box, a way to say 'this thing goes in this spot'. We need to be able to support a lot of different looks and feels. If it's going to be on Yahoo! Games versus Yahoo! search, they're going to need to look a little different to fit in with the site they're on. We need to be able to overlap anything else that might be on the page. Sometimes if it's up in the header it might drop down, and then there's a scroll bar or select element and it's going to go underneath that — I'm sure you've all seen that before, I see some people nodding. Also, we need to be able to support keyboard navigation, hopefully without me having to do any work.

Basically, a widget is a box. I think Satyen is talking about the widget API, or did he yesterday?

Audience member 1: He might be today. He talked about attribute yesterday.

Isaac: Oh, OK. I think he's right over there talking about it actually, isn't he? Alright. [laughs] Well, you're missing it, but catch it on YUI Theater. It's a good talk.

It's a really nice API, basically. You can put stuff in the box, you can move it around, and it all just works. There are some pretty simple plugins for widget that will let you add keyboard navigation and so on — basically everything is just tidied up for you and you get a really consistent API for it. Again, go to developer.yahoo.com/yui/3/widget to find out more about this. The thing about putting stuff in a box is that it makes things a lot easier to manage — that's why moving companies do it. We can fix the position of the box, we can skin it and decorate it, and we can change the contents fairly easily. We can overlap anything, it does the shimming and all that messiness for you, and there are really easy ways to extend it for keyboard and focus events to handle that.

So that's going to be the way that we display the list of items, but now what we need is a simple thing to wire it all together, a way to say 'attach this to this element', and then it becomes an AutoComplete. Plugins in YUI 3 let you add almost any functionality you can imagine to almost any object in the library. There's this base class, and anything that extends from base you can just plug stuff into and it becomes a plugin host. Writing a plugin is really easy. You set up functions that get called when it gets plugged in, and functions that get called when it gets unplugged. If you want to make something an AutoComplete and then take that functionality away, it can all be cleaned up and it goes right back to being a regular DOM node. Just like Voltron — it's exactly the same thing.

Basically, like I said, if you want to take an existing API and add some additional functionality you can use a plugin to do that — it's the decorator pattern, if you're familiar with that lingo. Creating a plugin's pretty easy. You create a function that gets called when it gets plugged, another one that gets called if it gets unplugged, and here's what it looks like when you're done with it. I have my DOM node, Y.one, pulling in a single node. Then I want to plug this Y.plugins.foo, which I created. That creates a namespace .foo, and that's where all the extra functionality hangs off of, so you don't really have to worry about things clobbering one another if two plugins define the same function. So it's not exactly like Voltron, but it's close. Learn more about this on developer.yahoo.com/yui/3/plugin.

OK, so that's going to be the simple way that we wire things together, and it looks like our shopping list is pretty much done here. I want to talk for a minute about a couple of things that are more stylistic points about building things with the grain in YUI that will make your code a little bit more re-usable. In particular, loose coupling. You're going to see this all over the YUI library. Basically, loose coupling means things connect in a consistent way but they're not tightly attached to one another, and they don't strictly depend on one another, like these train pieces.

I'm going to go ahead and mix metaphors and talk about ducks, because I found this really nice picture of a mallard here. Basically, a lot of times when you hear people talk about loose coupling, or about type inference and whatnot, they talk about ducks. One way we could look at a duck is to say it's a water bird, which is a bird, which is a vertebrate, which is an animal, which is an organism, which is a thing. That's one way to do it, and we can be really sure that everything that's a thing follows this consistent set of properties, and everything that's a vertebrate has these consistent properties, and so on. Another way to look at it, though, which is a little less of a pain to deal with, is to say that a duck is something that swims, flies, quacks, sits on eggs, and mixes metaphors. Then basically anything that can do those things can fill the role of a duck.

What this means for us is that in loose coupling our pieces should be able to be used either on their own or in other contexts. If we have an AutoComplete plugin that does part of the job, it should be able to take a different kind of display and be able to work with that as long as it exposes the same API. Pieces should work just fine with any other piece that has the same API. In general, methods and the observer pattern is going to be a lot more powerful here because if there's something that the AutoComplete plugin does that maybe your particular widget doesn't care about, you just don't listen to that particular event. If I have to depend on you exposing a function that I'm going to call, and we have to register with one another, that's a little bit more painful. We'll try to minimize that and use event driven coding as much as possible. If you caught Luke's talk yesterday on Events Evolved, you'll see that's very, very powerful.

I've talked a little bit here about the things that we're going to be using, but how do we actually make this a component that can be used in multiple different contexts? The answer to that is YUI.add. Basically, YUI.add lets you create a thing that can then be attached to YUI instances in other contexts. Here I'm defining my module. I create a function and that's where the magic happens. Give it a version number and a list of things that it depends upon, and when you do YUI.use it's going to automatically pull in all the dependencies and attach it to that YUI instance. What you're really doing is registering a module, you're not instantiating it. What that means is that you should really call add on the YUI global, not on the instance. This is a point that I've seen people get confused about and ask questions about in the IRC rooms, but it's really straightforward when you think about it. You're registering something in a list of available modules, you're not instantiating it and creating one. You're going to do that later, when you do use. This adds your module to the list of things that can be called with use. Later, we're going to do this.

Back to what we're here for. Here's a pretty picture of the AutoComplete architecture. If I was more of a keynote whiz it would be moving around and stuff. As you can see, it's completely straightforward — I've got the node plugin which is going to plug into that node wrapper, it's going to talk to a data source, it's going to have the renderer widget, and everything's going to work with absolutely no problems whatsoever. Except that in some languages, when a user presses a key, that doesn't necessarily mean that a character has been entered. In particular, in Korean, Chinese, and Japanese, there are characters that you enter with multiple keystrokes. So keydown and keypress are going to be… We're going to be preemptively searching for things when the user hasn't even entered a new character yet. You're probably thinking that we could just wait for onChange, but onChange is way too late for our purposes because onChange doesn't fire until I tab out of the field. If you think about it, if I type something and then they tab away, and now the AutoComplete, that's not going to fly.

Our DOM events here: we've got keydown, keyup, keypress, and then a whole while later we've got change happening. There's something in there, in the middle, that's kind of missing. Thankfully, YUI gives us a tool to be able to fill in these missing pieces in the DOM, and that's custom events. If you're familiar with using custom events in YUI 2, you probably remember that they're a different API. You have to create your own custom events, and then they can return false, but they don't really have a cancel event, you can't set up default functions for them, and in general the arguments you get are kind of arbitrary.

In YUI 3 there is a slight difference between using a regular DOM event and a custom event. Let me show you that difference. This is attached to a click event, which is just a regular vanilla DOM event. If you notice, there's a stopPropagation, preventDefault, all the stuff you're used to seeing. Let's say we created a custom DOM event called MyEvent. Let me show you how this changes. That's it. It's just like using a regular DOM event. Basically, making a new node event is like writing your own DOM. You can proxy to DOM events, you can gather them up together. If you look in the gallery there is a Konami event which listens to keystrokes for the Konami code. It's basically like adding things to the DOM that should have been there in the first place. There's already some functionality if you take a look at the event focusin and focusout, or mouseeventer and mouseleave, they're sort of abstracting across browsers to give you a consistent DOM API. This is how they do it. Just try not to get carried away with it.

This is kind of what it looks like when you want to create a new DOM event. You create an event descriptor and then you register it in a couple of places, and that's really the whole thing. This is ridiculously easy to do. Obviously with removing the function body there's some complexity in there, but it's not anywhere near as complex as you probably think. Here are some signs that you should probably add a new node event. If it's a generic need related to a particular element, it's a good idea. Maybe the DOM has something like what you want, but not quite, and you need to just get something in there in between a couple of existing events. Basically if it fills a hole, that's why you should do it. In general terms, please don't go around creating events that already exist just because you don't like them. That's silly.

Let's add in the added piece here, the custom event that we're going to create custom node event. I've called it valueChange because it happens when the value changes. Basically, what this piece is going to do is we're going to listen to the key events keydown and keypress, change, onPaste, etcetera. We're going to use Y.later to defer the value check because another thing that happens is, if you've ever created a listener on a keydown or keypress event and the user's typing really fast, if you're trying to make Ajax calls or even update the DOM it gets really laggy really, really fast, because people type pretty fast. If you hold the character down, suddenly your memory and your CPU use spike up to 100 per cent, and nobody likes that — they say your page is broken. We're going to use Y.later to defer that check, so if there's a lot of keystrokes in a row, it's not actually going to fire until the last one is completed. Then we're going to see if the value is changed by tracking it each time in an internal variable, and if the value's different from last time then we fire this valueChange event and our listeners all get notified.

When you plug the node plugin itself into a node you can give it a handful of settings. We need to be able to configure where my data is coming from, how many characters it should be before I start searching, and whether we want to do local caching and filtration and so on. I've got parity [?], it's not rocket surgery, but you've got to pass in some settings so it knows how to work. It's going to assign a listener to the valueChange event, and then it's going to fire an ac:query custom event when the value has changed. You've probably noticed there's a namespace here, AC. I'm just using the AC namespace for everything AutoComplete related. The plugin's namespace is going to be myNode.ac.blah, whatever the plugin adds. All the events are going to be prefixed like this, so you can listen to events based on the chunk of the application that's firing it. The reason why we didn't do that with the node event is because we wanted that to look just like a normal, regular DOM event, and regular DOM events are not namespaced. That's another reason you shouldn't go around creating them unless there's a real definite need for it, because you could very easily get into namespace collisions.

When we add on a data source, this ac:query event, I'm going to hook some default behavior onto it and if none of the listeners cancel it then it's going to tell the data source to go fetch some data and return. Assuming that we do actually get data in response — it's possible that your server's down, so the user starts typing and we fire that query off and it never comes back — we're going to fire the ac:load event which says 'I've got some data and it's time to load it into the widget and display it'.

Here's our AutoComplete renderer widget that's going to extend the widget class. When the user clicks on one of these items, it's going to call whateverTheNodeIs.ac.set, and set the query value to that selection. Now, set is actually something gets added on by the base class, and that's going give you attribute control, setters and getters. And that set function is then how we're going to handle the limited cases, because if I know that I'm a delimited AutoComplete, when you call set I'm just going to set the part that's currently focused, not the whole thing. Widget's basically going to give it a bounding box, an easy way to manage the position, and a render function to make it visible.

Here's what this looks like now, again. I can see all the blog posts going out and the tweets and whatnot from people in the back row: 'much hyped AutoComplete module doesn't even work'. So I put this scary warning here. This is a slide deck, this is not an API Doc, and if I show you some code it's probably going to change. This is very early beta stuff. You can definitely be sure that, this being YUI, when it does actually get pushed out to beta and released, it's going to be documented up and down.

This is kind of what I've talked through already. We're going to use the ac-plugin, I'm going to plug that into a node with a data source, listen to the onLoad event, and then do something with the results. The thing is, that's kind of a lot of code. Probably a handful of people in this room are thinking 'oh great, I can customize every part of that', and the rest of you are probably thinking 'God, I have to do that every time?' It's really nice if you can roll a lot of this up together and take care of the common use cases without as much work. Basically, I do that by just setting defaults and making it really simple to instantiate.

YUI 3 introduces the concept of a roll-up. You can take a bunch of functionality that's a common use case and bundle it up into one thing. You just include that thing and plug it in and it just works. YUI 2 was a modular system, but basically each module in YUI 2 — I kind of talked about it with data source — is either all or nothing. If you want data source, you get all of data source, there's no way to pick and choose based on a feature set. In YUI 3, it's much more sub-modular and there's a much greater degree of granularity to the things that you're pulling in with the use statement. The downside of this is that it makes it harder if you do just want a common use case. Roll-ups give you an easy way to trade that customizability for the convenience of being able to plug into it really easily. Just take the bits you like, and leave the rest behind. Adding onto this picture here, let's just wrap this up in our AutoComplete roll-up.

I can't stress this enough. I'm going to go ahead and just tell you again: don't try to use this right now. Basically, this is a little bit of a prettier way to go about it. I'm just going to say, I wanted to use AutoComplete and I've given it a nice readable name, unlike ac-plugin which is a little more specific but also kind of esoteric. I'm just going to say: give me a new Y.AutoComplete, here's the node reference, the selector, here's the data source that you're going to be pulling data in from, and it'll just do the basic use case. If you want to customize the widget you can do that with this reference here, but most of the time you probably don't need to. It's just going be CSS to make it look like your page or whatever.

In the final tally, at least what it's looking like so far, there's really not very much new code. Most of this is just leveraging what's already in the YUI library. The biggest thing is the custom DOM event, because there's kind of a lot of functionality in there. The rest is just configuration and instantiation and hooking things together. Basically, the moral here is that good solutions really don't have to be very difficult to accomplish. You can do things that are fairly accessible with YUI — you just take the parts that are already done for you and hook them up together.

Chances are, this is going to be in the YUI Library in early 2010, in the YUI 3.1 release. But if you need it earlier than that you can fork YUI 3 from GitHub, or you can even follow my own repository which is where this code is being pushed out live. I know a handful of people are actually… I can see Julian there who's been on me to get this done so that he can use it, so it's definitely going to be getting a lot of play testing before it's actually included in the library.

That's it. Any questions?

[applause]

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

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