Building a Rating Widget with Ext Core 3.0 Final and Google CDN
Wednesday, June 10th, 2009We are very proud to announce the final release of Ext Core under the MIT license. Your feedback was invaluable. Thank you for all the bugs reported and test cases created. For those of you who are new to Ext Core, we suggest you read the previous blog post about the all the features and examples that we released as part of the beta. You can find a list of changes and fixes we made for the final here.
For this post we will leverage the power of Ext by creating and dissecting a useful star rating example. We hope to share some of the general best practices behind creating unobtrusive, reusable code with Ext Core to liven up your pages.
Making a Splash
Including Ext Core on your site is easier than ever. We are honored to share with the community that Ext Core is now available via the Google AJAX Library API. Many thanks to Ben Lisbakken at Google for working with us to make this a reality.//any of these will work ;) <script type="text/javascript" src=".... http://ajax.googleapis.com/ajax/libs/ext-core/3.0/ext-core.js http://ajax.googleapis.com/ajax/libs/ext-core/3/ext-core.js http://ajax.googleapis.com/ajax/libs/ext-core/3.0/ext-core-debug.js http://ajax.googleapis.com/ajax/libs/ext-core/3/ext-core-debug.js
google.load('ext-core', '3'); google.load('ext-core', '3', {uncompressed : true});
Getting Started
Ext Core is a perfect fit for adding behavior to existing HTML. When designing a widget, having a markup structure that provides graceful degradation is an added plus. For this example, we will be using radio buttons. We can "group" the elements to specify which radio buttons are part of the control. It could look something like the following:<div id="rating1"> <input type="radio" name="rating1" value="1" title="Very poor"> <input type="radio" name="rating1" value="2" title="Not that bad"> <input type="radio" name="rating1" value="3" title="Average"> <input type="radio" name="rating1" value="4" title="Good"> <input type="radio" name="rating1" value="5" title="Perfect"> </div>
The API
One of the most important aspects of building reusable code is providing your developers a powerful API. Our aim here is to allow developers to progressively enhance and convert the markup into a star rating with a simple API. In this case we will need the element that wraps around the radio controls, and some optional configuration to customize the behavior of the widget. Following the Ext tradition we will provide these configuration options in the form of an object literal. A possible API for our widget could look like this://Keep it simple new Ext.ux.Rating('rating1', { showTitles: true });
Ext.util.Observable
So now that we know how we want to use our component, lets go ahead and actually look at some details on how to write it! In our previous post we mentioned that Ext Core allows you to write neatly structured object-oriented code. Whenever you want to create a piece of functionality, you should try to bundle it into a separate class. In most cases you will need to be able to listen for events on instances of your class. Ext provides a power class, the Ext.util.Observable class, to springboard your development. This is the same class that almost all classes in Ext JS extend from! Our basic shell for our rating plugin could look something like this:Ext.ns('Ext.ux'); Ext.ux.Rating = Ext.extend(Ext.util.Observable, { // Configuration default options showTitles: true, // Our class constructor constructor : function(element, config) { Ext.apply(this, config); Ext.ux.Rating.superclass.constructor.call(this); this.addEvents( 'change'); this.el = Ext.get(element); this.init() } });
Reaching the stars
It is time to think about the things we need to get our widget working. First we want to replace the radio buttons with our stars, we will need to store the values and titles for each star, we want to create a hidden input to put the current value in and finally we need to set up event listeners to listen for mouse hovers and clicks.init : function() { var me = this; // Some arrays we are going to store data in this.values = []; this.titles = []; this.stars = []; // We create a container to put all our stars into this.container = this.el.createChild({ cls: 'ux-rating-container ux-rating-clearfix' }); // We use DomQuery to select the radio buttons // Then we can loop over the CompositeElement using each this.radioBoxes = this.el.select('input[type=radio]'); this.radioBoxes.each(this.initStar, this); // We use DomHelper to create our hidden input this.input = this.el.createChild({ tag: 'input', type: 'hidden', name: this.name, value: this.values[this.defaultSelected] }); // Lets remove all the radio buttons from the DOM this.radioBoxes.remove(); if(this.disabled) { this.disable(); } else { // Enable will set up our event listeners this.enable(); } }
Creating Stars - using DomHelper and accessing the DOM from Ext Element
initStar : function(item, all, i) { // We use the name and disabled attributes of the first radio button if(i == 0) { this.name = item.dom.name; this.disabled = item.dom.disabled; } // Saving the value and title for this star this.values[i] = item.dom.value; this.titles[i] = item.dom.title; // Now actually create the star! var star = this.container.createChild({ cls: 'ux-rating-star' }); // Save the reference to this star so we can easily access it later this.stars.push(star.dom); },
Enable and Select Stars - listening for events, using the target of an event and firing custom events
enable : function() { // ... some code missing here ... // We will be using the technique of event delegation by listening // for bubbled up events on the container this.container.on({ click: this.onStarClick, mouseover: this.onStarOver, mouseout: this.onStarOut, scope: this, delegate: 'div.ux-rating-star' }); }, onStarClick : function(ev, t) { if(!this.disabled) { this.select(this.stars.indexOf(t)); } }, select : function(index) { // ... some code missing here ... else if(index !== this.selected) { // Update some properties this.selected = index; this.value = this.values[index]; this.title = this.titles[index]; // Set the value of our hidden input so the rating can be submitted this.input.dom.value = this.value; // the fillTo() method will fill the stars up until the selected one this.fillTo(index, false); // Lets also not forget to fire our custom event! this.fireEvent('change', this, this.values[index], this.stars[index]); }
Filler Up - dom manipulation (adding classes)
fillTo : function(index) { var cls = 'ux-rating-star-on'; // We add a css class to each star up until the selected one Ext.each(this.stars.slice(0, index+1), function() { Ext.fly(this).addClass(cls); }); // And then remove the same class from all the stars after this one Ext.each(this.stars.slice(index), function() { Ext.fly(this).removeClass(cls); }); }
We won't discuss all the details since most of it is pretty straightforward, but the final product should give you a general idea of how to use the basic functionality available in Ext Core to tie together all the missing pieces.
Wrapping it up
In this example we used the following cross-browser compatible functionality available in Ext core:- Classical Inheritance Class System
- Observable Class
- DomQuery
- DOM manipulation and traversal
- Event handling
- Markup generation
Ext Core makes it fun to write code, and helps you create clean, well-structured classes using a set of cross-browser abstractions on the existing browser API's. For those of you who want to use it or are just interested in seeing the completed work, we have included a version of the widget in the Ext Core Final build. You can see the working widget embedded in the post below:
The example page illustrating this widget can be found here.

With our recent change to the GPL v3 some concerns have been brought up by the Ext Community. We are hoping to address some of those concerns via community discussion of two new 