This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 1 11 How it works… The previous code uses Ext.get(element), a shorthand for Ext.Element.get(element), to acquire a reference to a div element in the document. You can use this function to retrieve references that encapsulate DOM elements. There's more… The Ext.get(element) function uses simple caching to consistently return the same object. Note that Ext.get(element) does not retrieve Ext JS components. This is can be accomplished using Ext.getCmp(), explained in the next recipe. See also… ff The next recipe, Acquiring references to Ext JS components, explains how to obtain a reference to a previously created component. ff The Running high performance DOM queries recipe, which is covered later in this chapter, teaches you how to run queries against the DOM using Ext JS. Acquiring references to Ext JS components As Ext JS is all about working with components, it's essential to learn how to acquire a reference to any component in your code. For example, this recipe shows how easy it is to reference a ComboBox component. How to do it… You can reference a ComboBox component as shown in the following code: How it works... References to components are obtained using the Ext.getCmp(id) function, where id is the ID of the component. Keeping track of components is possible, thanks to the ComponentMgr class. It provides for easy registration, un-registration and retrieval, as well as notifications when components are added or removed. There's more... This method is particularly useful when explicit component references do not already exist in your code, for example when components are defined as part of the items collection of a container. (Think of a tab panel and its tabs, or a border layout and its contained panels.) This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 1 13 There are other DOM and component utilities provided by Ext JS: ff Ext.getBody() returns the body of the document as an Ext.Element ff Ext.getDoc() returns the current HTML document as an Ext.Element ff Ext.getDom(id) returns the DOM node for the supplied ID, DOM node, or element See also... ff The Retrieving DOM nodes and elements recipe, covered earlier in this chapter, explains how to get a handle on any DOM element. ff The next recipe, Running high-performance DOM queries, teaches you about running queries against the DOM. Running high-performance DOM queries Now you'll see how to run queries against the DOM using Ext JS—a must-have when you need to manipulate or perform actions on multiple, related DOM elements. The examples show how to reference all the div elements in a document, obtain all the elements with a CSS class name msg, and iterate over the options of a select element. How to do it... The following code snippets are examples of how to run high-performance queries against the DOM using Ext JS: ff When you need to retrieve the elements that match the div selector to find the div elements in the document, use the following code snippet: Ext.onReady(function() { // Get all the div elements. var nodes = Ext.query('div'); Ext.each(nodes, function(item, index, allItems) { document.write(index + ''); }); }); ff When you need to reference the elements with the class name msg, use the following code snippet: var msgLinks = Ext.query('.msg'); Ext.each(msgLinks, function(item,index) { // Do something with the element here. }); This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 DOM and Data Types, the Ext JS Way 14 ff When you want to iterate over the options of a select element, use the following code snippet: var select = Ext.get('countries-select'); Ext.each(select.options, function(item,index) { // Do something with the item here. }); How it works... The previous examples use Ext.query(path, [root]), a shorthand of Ext.DomQuery.select(path, [root]), to retrieve an array of DOM nodes that match a given selector. There's more... DomQuery provides high-performance selector/XPath processing by compiling queries into reusable functions. It works on HTML and XML documents, supports most of the CSS3 selector's specification as well as some custom selectors and basic XPath, and allows you to plug new pseudo classes and matchers. See also... ff The Retrieving DOM nodes and elements recipe, covered earlier in this chapter, shows how you can use Ext JS to get a handle on any DOM element. ff The Acquiring references to Ext JS components recipe, covered earlier in this chapter, explains how to acquire a reference to any component in your code. Encoding and decoding JSON Converting JavaScript and Ext JS objects to JSON, and converting JSON data back to JavaScript objects is easily achievable with Ext JS. For example, here's how to JSON-encode an array and how to rebuild the array from its JSON representation: JavaScript Object Notation (JSON) is a lightweight text format where an object is represented with an unordered set of name/value pairs and an array with an ordered collection of values. JSON is completely language independent, easy for humans to read and write, and easy for machines to parse and generate. These properties make JSON an ideal data-interchange format. Find out more about JSON at www.json.org. This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 1 15 How to do it… Let's encode an array of colors using the following steps: 1. Create an array called colorsArray: var colorsArray = new Array(); 2. Put some values in the array: colorsArray[0] = 'Blue'; colorsArray[1] = 'Red'; colorsArray[2] = 'White'; 3. Now, convert to JSON: var colorsJson = Ext.encode(colorsArray); The value of the colorsJson variable should be the string ["Blue","Red","White"] string. 4. Let's re-create the array based on its JSON string. Take the JSON representation of colorsArray: var colorsJson = '["Blue","Red","White"]'; 5. Parse the JSON and rebuild the array: var colorsArray = Ext.decode(colorsJson); After this, colorsArray contains the colors data: colorsArray[0] is 'Blue', colorsArray[1] is 'Red', and colorsArray[2] is 'White'. How it works… To obtain a JSON representation of an array, object, or other value, pass the value to Ext. util.JSON.encode(object). You can also use the shorthand, Ext.encode(object). You can parse a JSON string by using Ext.util.JSON.decode(json), or its shorthand Ext.decode(json). While decoding JSON involves simply calling the JavaScript eval(String) function, the encoding process is more complicated and requires different implementations depending on the data type being encoded. Internally, the encode(object) function calls specialized encoding functions for arrays, dates, Boolean values, strings, and other types. You can also set the Ext.USE_NATIVE_JSON property to true, which will cause calls to encode(object) and decode(json) to use the browser's native JSON handling features. This option is turned off by default. If you turn it on, beware that native JSON methods will not work on objects that have functions, and that property names should be quoted in order for the data to be correctly decoded. This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 DOM and Data Types, the Ext JS Way 16 There's more… JSON encoding and decoding is a pillar of modern web development, given the role of JSON—a language-independent, data-interchange format—in facilitating communications between the client-side and server-side of web applications. For instance, you can expect to find JSON manipulation when your application needs to send data to the server, as well as when the application needs to dynamically create objects from server-supplied data. See also… ff The next recipe, Encoding and decoding URL data, shows how to do two-way conversion between objects and URL data. Encoding and decoding URL data Two-way conversion between objects and URL data is a challenge that Ext JS can help with. Let's examine how a JavaScript object can be encoded for transmission through the URL query string, as well as how information contained in a URL can be used to build a JavaScript object. How to do it... The following steps will guide you through the process of encoding and decoding URL data: 1. Take a selectedColors object as the data to be passed in a URL: var selectedColors = {color1:'Blue', color2:'Red', color3:'White'}; 2. Convert the object to URL data like this: var encodedUrl = Ext.urlEncode(selectedColors); // encodedUrl is an encoded URL query string: //color1=Blue&color2=Red&color3=White. 3. Now, a URL can be built using the data just created. For example, http://MyGreatApp/SetSelectedColors?color1=Blue&color2=Red&colo r3=White. This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 1 17 4. You can easily create objects from the encoded URL. Assuming we obtained the data from the URL we used above (http://MyGreatApp/SetSelectedColors?color 1=Blue&color2=Red&color3=White), obtain the URL data like this: encodedUrl = location.search; 5. Re-create the selectedColors object as follows: var selectedColors = Ext.urlDecode(encodedUrl); // Now the value of selectedColors' color1 property is 'Blue', // color2's value is 'Red' and color3's value is 'White'. How it works... Ext.urlEncode(object) and Ext.urlDecode(string, overwrite) provide object serialization to URL data and URL data deserialization to objects respectively. Encoding is accomplished by creating the URL query string's key-value pairs based on each object property, or array value passed to the encoding function. Decoding is accomplished by creating an object with a property for each key-value pair that exists in the URL's query string. There's more... You can use this recipe when your application needs to send information to the server via AJAX or standard HTTP requests, as well as when you need to use the URL's query string to feed the application data that can later be converted to JavaScript objects. See also... ff The Encoding and decoding JSON recipe, covered earlier in this chapter, explains how to convert JavaScript objects to JSON and how to convert JSON to JavaScript objects. Determining the object type and converting empty references to a default value This recipe teaches you how to determine the types of different objects using the facilities of Ext JS, as well as a simple method that can be used to initialize empty references with a default value. This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 DOM and Data Types, the Ext JS Way 18 How to do it... You can determine the types of different objects in the following way: 1. Create some dummy data structures: var colorsArray = new Array(); colorsArray[0] = 'Blue'; colorsArray[1] = 'Red'; colorsArray[2] = 'White'; var colorsObject = { color1: 'Blue', color2: 'Red', color3: 'White' }; var aNumber = 1; var aString = '1'; var sample; var empty; 2. Check the types of our variables: var colorsArrayType = Ext.type(colorsArray); // colorsArrayType's value is "array". var isArray = Ext.isArray(colorsArray); // isArray is true var colorsObjectType = Ext.type(colorsObject); // colorsObjectType's value is "object". var isArray = Ext.isArray(colorsObject); // isArray is false var number = Ext.num(aNumber, 0); // number is 1. number = Ext.num(aString, 0); // Since aString is not numeric, the supplied // default value (0) will be returned. var defined = Ext.util.Format.undef(sample); // defined is an empty string sample = "sample is now defined"; defined = Ext.util.Format.undef(sample); // defined is now "sample is now defined". var notEmpty = Ext.value(empty, 'defaultValue', false); // notEmpty is 'defaultValue' This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 1 19 How it works... The Ext.type(object) function is capable of detecting elements, objects, text nodes, whitespaces, functions, arrays, regular expressions, numbers, and node lists. As its name indicates, Ext.isArray(object) simply checks whether the passed object is a JavaScript array. Ext.num(value, defaultValue), in turn, does a numeric type check on the passed value and returns the default value when the argument is not numeric. Ext.util.Format.undef(value) is a very useful function when you need to test for undefined values. It returns either the supplied argument or an empty string if the argument is undefined. Ext.value(value, defaultValue, allowBlank) also allows you to specify a default value when the value being tested is undefined. Finding objects in an array and removing array items The main task in this recipe is to find out whether an arbitrary object exists in an array. A way to remove objects from the array is also explored. How to do it... The following steps illustrate how you can perform object existence tests and object removal in an array: 1. Create a sample array as follows: var colorsArray = new Array(); colorsArray[0] = 'Blue'; colorsArray[1] = 'Red'; colorsArray[2] = 'White'; 2. Determine whether an object exists in an array by trying to find its position in the array: var position = colorsArray.indexOf('White'); // postition is 2, the index of 'White' in the array. position = colorsArray.indexOf('Brown'); // 'Brown' does not exist in the array, // position is -1. This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 DOM and Data Types, the Ext JS Way 20 3. Remove one of the objects from the array: colorsArray.remove('Blue'); position = colorsArray.indexOf('Blue'); // 'Blue' does not exist anymore, // position is -1. How it works... Ext JS augments the native Array class with Array.indexOf(object) and Array. remove(object). While indexOf(object) works by examining each array element until it finds one that matches the supplied argument, remove(object) uses the native Array. splice(index, howmany, element1,....., elementX) function to remove the supplied argument from the array. Manipulating strings à la Ext JS String manipulation has always been a challenging area in JavaScript. Here, you will learn how to escape special characters, trim, pad, format, truncate, and change the case of your strings with the help of the utilities of Ext JS. How to do it... You can manipulate strings as shown in the following steps: 1. Create your sample values as shown here: var needsEscape = "ExtJS's String Class will escape this"; var needsTrimming = " Needs trimming "; var cls = 'color-class' var color = 'Blue'; var sort = 'ASC'; var sample = "some text"; var longText = "This text should be truncated, it's really long."; var withScript = 'Some text This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 More Applications of Grid and List Views 232 The BufferView.js file can be obtained from the Ext JS 3.0 samples at http://extjs.com/deploy/dev/examples/grid/buffer.html. 2. Define the movies data store: var store = new Ext.data.JsonStore({ url: 'grid-buffer-view.php', root: 'movies', idProperty: 'id', totalProperty: 'count', fields: ['id', 'title', 'category', 'rating', 'actors', { name: 'length', type: 'int' }, { name: 'price', type: 'float' }, 'description'], remoteSort: true }); store.setDefaultSort('title', 'asc'); 3. Create a grid that will display the movies list and use a BufferView instance as its view: Ext.onReady(function() { var grid = new Ext.grid.GridPanel({ title: 'Movies', store: store, width:600, height: 300, loadMask: true, renderTo: Ext.getBody(), view: new Ext.ux.BufferView({ // Render rows as they come into viewable area. scrollDelay: false }), autoExpandColumn: 'title-col', columns: [ new Ext.grid.RowNumberer(), { header: "ID", width: 30, dataIndex: 'fid', sortable: true, hidden: true }, { id: 'title-col', header: "Title", width: 180, dataIndex: 'title', sortable: true }, { header: "Category", width: 65, dataIndex: 'category', sortable: true }, { header: "Rating", width: 65, This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 6 233 dataIndex: 'rating', sortable: true }, { header: "Length", width: 65, dataIndex: 'length', sortable: true, align: 'right', renderer: function(v) { return v + ' min'; } }, { header: "Price", width: 65, dataIndex: 'price', sortable: true, align: 'right', renderer: Ext.util.Format.usMoney } ], viewConfig: { forceFit: true } }); store.load(); }); // onReady How it works... Rows can be rendered as they come into view with the use of the BufferView instance as the grid panel's view: view: new Ext.ux.BufferView({ // Render rows as they come into viewable area. scrollDelay: false }), The BufferView class is a custom GridView that caches a number of the rows that have already been rendered. This cache can be changed with the cacheSize config option. See also... ff The next recipe, Using the lightweight ListView class, explains how to display the read-only tabular data with a little overhead This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 More Applications of Grid and List Views 234 Using the lightweight ListView class The new ListView class is a high-performance, lightweight implementation of a grid-like display. In this recipe, we use it to display a read-only list of movies as shown in the following screenshot: How to do it... 1. Create a data store for the movies list view: var store = new Ext.data.JsonStore({ url: 'grid-listview.php', root: 'movies', idProperty: 'id', totalProperty: 'count', fields: ['id', 'title', 'category', 'rating', { name: 'length', type: 'int' }, { name: 'price', type: 'float' }, 'description'] }); store.setDefaultSort('title', 'asc'); 2. Define the movies list view: Ext.onReady(function() { var moviesListView = new Ext.ListView({ store: store, multiSelect: false, emptyText: 'No images to display', This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 6 235 reserveScrollOffset: true, loadingText: 'Loading movies...', columns: [{ header: 'Title', width: .4, dataIndex: 'title' }, { header: 'Category', width: .15, dataIndex: 'category' }, { header: 'Rating', dataIndex: 'rating', width: .15, align: 'right' }, { header: 'Length', dataIndex: 'length', width: .15, align: 'right', tpl: '{length} min' }, { header: 'Price', dataIndex: 'price', width: .15, align: 'right', tpl: '{price:usMoney}' }] }); 3. Build a container for the list view: var moviesPanel = new Ext.Panel({ id: 'movies-panel', renderTo: Ext.getBody(), width: 525, height: 300, layout: 'fit', title: 'Movies List', items: moviesListView }); store.load(); }); // onReady This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 More Applications of Grid and List Views 236 How it works... The ListView class uses templates to render the data in any required format, such as in the Length and Price columns: { header: 'Length', dataIndex: 'length', width: .15, align: 'right', tpl: '{length} min' }, { header: 'Price', dataIndex: 'price', width: .15, align: 'right', tpl: '{price:usMoney}' } Although ListView has no horizontal scrolling, it provides selection, column resizing, sorting, and other features inherited from the DataView class. Column widths are specified by percentage, based on the container width and the number of columns. There's more... With the addition of the ListView class to Ext JS, you have another option for displaying tabular information—especially if you're implementing a read-only UI that does not require all of the features of the GridPanel component. See also... ff The previous recipe, Displaying large recordsets with a buffered grid, explains how to improve your UI's performance in the presence of large recordsets Editing rows with the RowEditor plugin The RowEditor plugin is another great addition to Ext, which allows you to rapidly edit full rows in a grid. The Customers grid in this recipe shows you how to use RowEditor. This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 6 237 How to do it... 1. Include the RowEditor.js file in your project: The RowEditor.js file can be obtained from the Ext JS 3.0 samples at http://extjs.com/deploy/dev/examples/ grid/row-editor.html. 2. Define the fields for the customers data store: var custFields = [{ name:'ID', type: 'int' },{ name: 'first_name', type: 'string' }, { name: 'last_name', type: 'string' }, { This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 More Applications of Grid and List Views 238 name:'phone', type:'string' }, { name: 'email', type: 'string' }] var Customer = Ext.data.Record.create(custFields); 3. Create a data store for the customers grid: var customersStore = new Ext.data.JsonStore({ url: 'grid-row-editor.php', root: 'customers', fields: custFields }); 4. Create a data store for the cities combo box: var citiesStore = new Ext.data.JsonStore({ url: 'grid-row-editor.php', baseParams: { xaction: 'cities' }, root: 'cities', fields: ['city_id', 'city'] }); 5. Define the RowEditor instance: var editor = new Ext.ux.RowEditor({ saveText: 'Update' }); 6. Build an EditorGridPanel with a toolbar containing the New and Delete customer buttons: Ext.onReady(function() { var grid = new Ext.grid.EditorGridPanel({ title: 'Customers', store: customersStore, plugins: [editor], sm: new Ext.grid.RowSelectionModel({ singleSelect: true }), tbar: [{ iconCls: 'icon-add', text: 'New', handler: function() { var cust = new Customer({ Id:'0', first_name: '[First name]', This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 6 239 last_name: '[Last Name]', phone:'[Phone Number]', email: 'user@domain.com' }); editor.stopEditing(); customersStore.insert(0, cust); grid.getView().refresh(); grid.getSelectionModel().selectRow(0); editor.startEditing(0); } },'-', { ref: '../removeBtn', iconCls: 'icon-del', text: 'Delete', disabled: true, handler: function() { editor.stopEditing(); var s = grid.getSelectionModel().getSelections(); for (var i = 0, r; r = s[i]; i++) { customersStore.remove(r); } } }], columns: [ { header: "ID", width: 30, dataIndex: 'ID', sortable: true, hidden: true }, { id: 'fname', header: "First Name", width: 180, dataIndex: 'first_name', sortable: true, editor: new Ext.form.TextField({ allowBlank: false }) }, { id: 'lname', header: "Last Name", width: 160, dataIndex: 'last_name', sortable: true, editor: new Ext.form.TextField({ This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 More Applications of Grid and List Views 240 allowBlank: false }) }, { header: "Phone", width: 135, dataIndex: 'phone', sortable: true, align: 'left', editor: new Ext.form.TextField({ allowBlank: false }) }, { id:'email', header: "Email", width: 95, dataIndex: 'email', sortable: true, align: 'left', editor: new Ext.form.TextField({ allowBlank: false, vtype: 'email' }) } ], autoExpandColumn:'email', renderTo: Ext.getBody(), width: 750, height: 500, loadMask: true }); grid.getSelectionModel().on('selectionchange', function(sm) { grid.removeBtn.setDisabled(sm.getCount() < 1); }); customersStore.load(); }); // onReady How it works... New records are created in the handler for the New button: tbar: [{ iconCls: 'icon-add', text: 'New', handler: function() { var cust = new Customer({ Id:'0', first_name: '[First name]', last_name: '[Last Name]', This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 6 241 phone:'[Phone Number]', email: 'user@domain.com' }); editor.stopEditing(); customersStore.insert(0, cust); grid.getView().refresh(); grid.getSelectionModel().selectRow(0); editor.startEditing(0); } } After a new Customer instance is added to the customers store, the grid's view is refreshed and the RowEditor component is put in edit mode so that the user can enter values for the newly created record. Deletions are implemented in the handler for the Delete button. Changes through the RowEditor components are interrupted with a call to stopEditing(), and any selected records in the grid are removed from the store: { ref: '../removeBtn', iconCls: 'icon-del', text: 'Delete', disabled: true, handler: function() { editor.stopEditing(); var s = grid.getSelectionModel().getSelections(); for (var i = 0, r; r = s[i]; i++) { customersStore.remove(r); } } Double-clicking on any row activates the RowEditor component for editing the selected record. See also... ff The Changing grid panel data using cell editors recipe from Chapter 5 explains how to use the different cell editors to allow for in-place editing of the record data This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 More Applications of Grid and List Views 242 Adding tool tips to grid cells This recipe teaches you how to add tool tips to the columns of the grid component. The grid created using this recipe will look like this: How to do it... 1. Start the QuickTips singleton: Ext.QuickTips.init(); 2. Create the fields for the customers grid: var custFields = [{ name:'ID', type: 'int' },{ name: 'first_name', type: 'string' }, { name: 'last_name', type: 'string' }, { name:'phone', type:'string' }, { This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 6 243 name: 'email', type: 'string' }] 3. Create a Customer record based on the already defined customer fields: var Customer = Ext.data.Record.create(custFields); 4. Create the data store for the customers grid: var customersStore = new Ext.data.JsonStore({ url: 'grid-cell-qtip.php', root: 'customers', fields: custFields }); 5. An XTemplate instance defines the format of the tool tips: var tpl = new Ext.XTemplate('

'; } This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 6 245 Using the PropertyGrid class The PropertyGrid class is a grid implementation similar to the property grids so commonly used in different IDEs. Each row in the property grid represents a property of some object. In this example, each row represents a property of a movie from a movies list: How to do it... 1. Create a data store for the property grid: var moviesStore = new Ext.data.JsonStore({ url: 'grid-property-grid.php', root: 'movies', totalProperty: 'count', baseParams: { action: 'movies' }, fields: [{ name: 'film_id' }, { name: 'title' }, { name: 'rating' }, { name: 'length', type: 'int' }, { name: 'price', type: 'float' }, { name: 'last_rented', type:'date'}] }); This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 More Applications of Grid and List Views 246 2. Define the property grid: Ext.onReady(function() { var movieGrid = new Ext.grid.PropertyGrid({ title: 'Properties Grid', autoHeight: true, width: 300, renderTo: Ext.getBody() }); 3. A handler for the store's load event will set the first record as the source for the property grid: moviesStore.on('load', function(store, records, options) { if (records && records.length > 0) { movieGrid.setSource(moviesStore.getAt(0).data); } }); moviesStore.load(); }); // onReady How it works... A call to setSource() specifies the record containing the data for the property grid. In this example, the first record retrieved from the server is used: moviesStore.on('load', function(store, records, options) { if (records && records.length > 0) { movieGrid.setSource(moviesStore.getAt(0).data); } }); Through its column model (PropertyColumnModel class), PropertyGrid has the ability to display editors for the date, string, number and boolean data types. Using drag-and-drop between two grids If you have worked with data mining and analysis interfaces, you have probably seen the application of drag-and-drop feature between two grids. This recipe explains how you can enable drag-and-drop between two grids in Ext JS. The example uses a Movies grid and a Selected Movies grid, as shown in the next screenshot: This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 6 247 If you drag the selected rows in the Movies grid and drop them in the Selected Movies grid, the grids' data stores will be updated to reflect the changes: This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 More Applications of Grid and List Views 248 How to do it... 1. Create fields for the two data stores used: var sharedFields = ['film_id', 'title', 'rating', { name: 'length', type: 'int' }, { name: 'price', type: 'float' } ] 2. Create a data store for the available movies grid: var moviesStore = new Ext.data.JsonStore({ url: 'grid-to-grid-drag-drop.php', root: 'movies', totalProperty: 'count', baseParams: { action: 'movies' }, fields: sharedFields }); 3. Create a data store for the selected movies grid: var selectedMoviesStore = new Ext.data.JsonStore({ root: 'movies', fields: sharedFields }); 4. Define the colums that both grids will display: var sharedColumns = { header: "ID", width: 30, dataIndex: 'film_id', sortable: true, hidden: true }, { id: 'title-col', header: "Title", width: 180, dataIndex: 'title', sortable: true }, { header: "Rating", width: 45, dataIndex: 'rating', sortable: true }, { header: "Length", width: 45, dataIndex: 'length', This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 6 249 sortable: true, align: 'right', renderer: function(v) { return v + ' min'; } }, { header: "Price", width: 45, dataIndex: 'price', sortable: true, align: 'right', renderer: Ext.util.Format.usMoney } ] 5. Create the available movies grid. Make sure drag-and-drop is enabled and a ddGroup is defined: Ext.onReady(function() { var moviesGrid = new Ext.grid.GridPanel({ title: 'Movies', enableDragDrop: true, ddGroup: 'selectedMoviesDDGroup', renderTo: 'movies-div', store: moviesStore, columns: sharedColumns, autoExpandColumn: 'title-col', width: 370, height: 300, loadMask: true, columnLines: true, viewConfig: { forceFit: true } }); 6. Create the selected movies grid. As with the first grid, make sure drag-and-drop is enabled and a ddGroup is defined: var selectedMoviesGrid = new Ext.grid.GridPanel({ title: 'Selected Movies', enableDragDrop: true, ddGroup: 'availableMoviesDDGroup', renderTo: 'selected-movies-div', store: selectedMoviesStore, columns: sharedColumns, This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 More Applications of Grid and List Views 250 autoExpandColumn: 'title-col', width: 370, height: 300, loadMask: true, columnLines: true, viewConfig: { forceFit: true } }); 7. Define a drop target for the available movies grid: var moviesGridDropTargetEl = moviesGrid.getView().el.dom.childNodes[0].childNodes[1]; var moviesGridDropTarget = new Ext.dd.DropTarget(moviesGridDropTargetEl, { ddGroup: 'availableMoviesDDGroup', copy: true, notifyDrop: function(ddSource, e, data) { function addRow(record, index, allItems) { var foundItem = moviesStore.find('title', record.data.name); if (foundItem == -1) { moviesStore.add(record); moviesStore.sort('title', 'ASC'); ddSource.grid.store.remove(record); } } Ext.each(ddSource.dragData.selections, addRow); return (true); } }); // This will make sure we only drop to the view container var selectedMoviesGridDropTargetEl = selectedMoviesGrid.getView().el.dom.childNodes[0].childNodes[1] 8. Define a drop target for the selected movies grid: var selectedMoviesGridDropTarget = new Ext.dd.DropTarget(selectedMoviesGridDropTargetEl, { ddGroup: 'selectedMoviesDDGroup', copy: false, notifyDrop: function(ddSource, e, data) { function addRow(record, index, allItems) { This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 6 251 var foundItem = selectedMoviesStore.find('title', record.data.name); if (foundItem == -1) { selectedMoviesStore.add(record); selectedMoviesStore.sort('title', 'ASC'); ddSource.grid.store.remove(record); } } Ext.each(ddSource.dragData.selections, addRow); return (true); } }); moviesStore.load(); }); // onReady How it works... Although the figures portray dragging rows from the available movies grid to the selected movies grid, it is also possible to move rows back to the available movies grid. The explanation for this is that both grids have drag-and-drop enabled. Each grid contains a drop target that allows for dropping rows coming from the other grid. Visually, it appears that the rows are moving from one grid to the other. In reality, records are being removed from the source store and added to the target store. These changes in the stores cause the grids to refresh. The notifyDrop() functions in each drop target call the addRow() function for each of the selected records on the source store: Ext.each(ddSource.dragData.selections, addRow); AddRow() adds the record to the target store and removes it from the source store: function addRow(record, index, allItems) { var foundItem = selectedMoviesStore.find('title', record.data.name); if (foundItem == -1) { selectedMoviesStore.add(record); selectedMoviesStore.sort('title', 'ASC'); ddSource.grid.store.remove(record); } } This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 7 Keeping Tabs on Your Trees In this chapter, you will learn the following recipes: ff Handling tab activation ff Loading tab data with Ajax ff Adding tabs dynamically ff Enhancing a TabPanel with plugins: The Close menu ff Enhancing a TabPanel with plugins: The TabScroller menu ff Populating nodes with server-side data, reordering nodes ff Tree and panel in a master-details relationship ff Multi-column TreePanel ff Drag-and-drop between tree panels ff Drag-and-drop from a tree to a panel Introduction This chapter explores how TabPanel widgets can be used to group components or information under the same container. It also explains how hierarchical views of information can be built using the Ext JS tree view widget. This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Keeping Tabs on your Trees 254 Handling tab activation This recipe explains how you can execute code when a TabPanel's tab is activated. In this recipe's sample tab panel, activating the second tab initiates a request, which will populate the tab with server-side data. You can see this in the following screenshot: How to do it... 1. Define the TabPanel and add a listener for the activate event: Ext.onReady(function() { var tabs = new Ext.TabPanel({ renderTo: document.body, activeTab: 0, width: 500, height: 250, plain: true, defaults: { autoScroll: true }, items: [{ title: 'First Tab', HTML: "" },{ title: 'Load on tab activation', bodyStyle: 'padding:5px;', listeners: { activate: activationHandler } }] }); 2. Create the handler function for the activate event: function activationHandler(tab) { tab.load({ url: 'tabs-ajax-load.php', params: 'xaction=load' }); } }); This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 7 255 How it works... The activate event indicates that a panel has been visually activated. The handler for this event is passed a reference to the activated panel. Panels do not directly support being activated, unless they are children of a TabPanel. However, there are some panel subclasses (such as the Window class) that support activation. See also... ff The next recipe, Loading tab data with Ajax, explains how to load content into a tab when the tab is first activated ff The Adding tabs dynamically recipe (covered later in this chapter) illustrates how the dynamic addition of tabs to a tab panel can be implemented Loading tab data with Ajax In this recipe, you will learn how to load static and dynamic content into a TabPanel's tab when the tab is first activated. A sample TabPanel's tabs will load data from an HTML file, as seen below: Another tab will receive its contents from a server page that makes a database call, as shown in the following screenshot: This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Keeping Tabs on your Trees 256 Getting ready... The sample data used in this recipe comes from the Sakila sample database. You can obtain the Sakila database, as well as the installation instructions, at http://dev.mysql.com/doc. How to do it... 1. Define the Tabpanel: Ext.onReady(function() { var tabs = new Ext.TabPanel({ renderTo: document.body, activeTab: 0, width: 500, height: 250, plain: true, defaults: { autoScroll: true }, 2. On the tabs, use the autoLoad config option to specify the page that will provide the tab's contents: items: [{ title: 'First Tab', HTML: "" }, { title: 'Ajax load from HTML file', bodyStyle: 'padding:5px;', autoLoad: { url: 'tabs-ajax-load-data.HTML' } }, { title: 'Ajax load from DB', bodyStyle:'padding:5px;', autoLoad: { url: 'tabs-ajax-load.php' } } ] }); }); This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 7 257 How it works... When the autoLoad option is present, the panel will try to load its contents immediately upon rendering. The URL specified in autoLoad becomes the default URL for the panel's body element, and it may be refreshed at any time. See also... ff The Handling tab activation recipe (seen previously in this chapter) shows how you can execute code when a TabPanel's tab is activated ff The next recipe, Adding tabs dynamically, teaches you how the dynamic addition of tabs to a TabPanel can be implemented Adding tabs dynamically A scenario found in many web applications is the dynamic addition of tabs to a TabPanel. This recipe teaches you a simple way of using a button that, when it is clicked on, executes code that creates a tab, as shown in the following screenshot: How to do it... 1. Define the TabPanel: Ext.onReady(function() { var tabs=new Ext.TabPanel({ renderTo: 'tabs', resizeTabs: true, minTabWidth: 115, tabWidth: 135, enableTabScroll: true, width: 500, height: 250, defaults: { autoScroll: true } }); This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Keeping Tabs on your Trees 258 2. Add a few tabs to start up with: // Create some tabs. var index=0; while (index < 3) { addTab(index); index++; } 3. Create a function that adds tabs dynamically: function addTab(index) { var tab=tabs.add({ title: 'New Tab ' + (index), iconCls: 'icon-tab', bodyStyle:'padding: 5px', HTML: 'Tab Body ' + (index) + '

', closable: true }); tab.show(); } 4. Define a button that will call the newly created function: var btn=new Ext.Button({ text: 'Add a tab', handler: addTab, iconCls: 'icon-new-tab' }) btn.render('button'); }); How it works... You can dynamically add tabs (panels) to a TabPanel by calling the TabPanel's add() method and passing the desired configuration for the tab. This is possible because the TabPanel uses a Ext.layout.CardLayout layout manager, which handles the sizing and positioning of child components. Other layout managers that support the dynamic addition of child components are Ext. layout.AnchorLayout, Ext.layout.FormLayout, and Ext.layout.TableLayout. See also... ff The previous recipe, Loading tab data with Ajax, explains how to load content into a tab when the tab is first activated This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 7 259 ff The Handling tab activation recipe (seen earlier in this chapter) explains how you can execute code when a TabPanel's tab is activated Enhancing a TabPanel with plugins: The Close menu This is another example of extending a component's features. Here, you'll use the TabCloseMenu plugin to add a Close menu to the tabs in a TabPanel. This menu allows the user to close either the selected tab, or all the tabs but the selected one, as seen in the following screenshot: Getting ready... In order to use the TabCloseMenu plugin, you need to locate and include the TabCloseMenu.js file. The TabCloseMenu.js file can be obtained from this book's sample code. How to do it... 1. Create the styles for the button and the tabs: 2. Include the TabCloseMenu.js plugin file: This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Keeping Tabs on your Trees 260 3. Define the TabPanel and assign an instance of the TabCloseMenu class to the plugin's config object: Ext.onReady(function() { var tabs = new Ext.TabPanel({ renderTo: 'tabs', resizeTabs: true, minTabWidth: 115, tabWidth: 135, enableTabScroll: true, width: 500, height: 250, defaults: { autoScroll: true }, plugins: new Ext.ux.TabCloseMenu() }); // Create some tabs. var index = 0; while (index < 3) { addTab(); } 4. Create a function that adds tabs on the fly: function addTab() { var tab=tabs.add({ title: 'New Tab '+(++index), iconCls: 'icon-tab', bodyStyle:'padding: 5px', HTML: 'Tab Body '+(index)+'

', closable: true }); tab.show(); } 5. Define a button that will call the newly created function: var btn=new Ext.Button({ text: 'Add a tab', handler: addTab, iconCls: 'icon-new-tab' }) btn.render('button'); }); How it works... The TabCloseMenu plugin adds a Close menu to the tabs with two actions. One action is to close the active panel and the other one is to close all the panels, except the active panel. The menu's code looks like this: This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 7 261 menu=new Ext.menu.Menu({ items: [{ id: tabs.id+'-close', text: 'Close', handler: function() { tabs.remove(ctxItem); } }, { id: tabs.id+'-close-others', text: 'Close All But This', handler: function() { tabs.items.each(function(item) { if (item.closable && item!=ctxItem) { tabs.remove(item); } }); } }] }); Observe how the act of closing a tab consists of instructing the container panel to remove the tab component from its items collection. You can customize this menu (text, icons, for example) by modifying the code in the TabCloseMenu.js file and also the styles defined for the plugin. See also... ff The next recipe, Enhancing a TabPanel with plugins: The TabScroller menu, explains how to create a menu that provides easy access to any of the tabs of a TabPanel Enhancing a TabPanel with plugins: The TabScroller menu The TabScrollerMenu plugin is another example of how to extend a component's features. Used on a TabPanel, this plugin builds a menu that provides easy access to any of the tabs without having to resort to the panel's default tab-scrolling behavior, as seen below: This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Keeping Tabs on your Trees 262 Getting ready... Obtain the TabScrollerMenu.js, tab-scroller-menu.gif, and tab-scroller-menu.css files from the Ext JS samples page at http://extjs.com/deploy/dev/examples/tabs/ tab-scroller-menu.html. How to do it... 1. Create the styles for the button and tabs: icon-new-tab { background:url(img/add2.png) 0 no-repeat !important; } icon-tab { background:url(img/star-yellow.png) 0 no-repeat !important; } 2. Include the styles used in the TabScrollerMenu plugin: 3. Include the TabScrollerMenu plugin file: 4. Create an instance of the TabScrollerMenu class that will be used by the TabPanel: Ext.onReady(function() { Ext.QuickTips.init(); var scrollerMenu = new Ext.ux.TabScrollerMenu({ maxText: 15, pageSize: 5, menuPrefixText:'Tabs' }); 5. Define the TabPanel and use the scrollerMenu plugin: var tabs = new Ext.TabPanel({ renderTo: 'tabs', resizeTabs: true, enableTabScroll: true, minTabWidth: 100, width: 500, This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 7 263 height: 250, defaults: { autoScroll: true }, plugins: [scrollerMenu] }); 6. Add enough tabs so that the TabScrollerMenu can be shown: // Create some tabs. var index = 0; while (index < 15) { var tab = tabs.add({ title: 'New Tab ' + (++index), iconCls: 'icon-tab', bodyStyle: 'padding: 5px', HTML: 'Tab Body ' + (index) + '

', closable: true }); tab.show(); } }); How it works... The TabScrollerMenu class is a plugin that inserts a context menu in the header area of the TabPanel instance. This menu contains an item for each tab of the TabPanel. Clicking on any of these items activates the corresponding tab. There's more... A disadvantage of tabbed interfaces presents itself when dealing with many tabs at once, particularly when the number of tabs exceeds the available area of the container. Using this plugin helps to deal with tab clutter by giving the user fast access to any tab, without having to resort to the default scrolling behavior of the TabPanel. See also... ff The Enhancing a TabPanel with plugins: The Close menu, explains how to enhance a TabPanel with a menu that allows the user to close either the selected tab, or all the tabs but the selected one This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Keeping Tabs on your Trees 264 Populating tree nodes with server-side data This recipe explains how you can use dynamic content to populate the nodes of a TreePanel. The TreePanel in this recipe belongs to a fictitious report development tool and it will display various properties of a reporting project, as shown in the following screenshot: How to do it... 1. Create styles for the tree-node icons: report { background:url(img/pie-chart.png) 0 no-repeat !important; } dataset { background:url(img/table.png) 0 no-repeat !important; } datasource { background:url(img/data.png) 0 no-repeat !important; } This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 7 265 2. Define the TreePanel: Ext.onReady(function() { var tree = new Ext.tree.TreePanel({ el: 'tree-reorder', frame:true, width: 250, height:400, useArrows: true, autoScroll: true, animate: true, enableDD: true, containerScroll: true, border: false, dataUrl: 'tree-ajax-load.php', 3. In the TreePanel, create the root node and finish the tree's definition: root: { nodeType: 'async', text: 'My Reporting Project', draggable: false, id: 'project' } }); 4. Render the tree: tree.render(); 5. Expand the root node and its child nodes. This causes the data to be retrieved from the server: tree.getRootNode().expand(true); }); How it works... The dataUrl config option is the URL from which a JSON string representing an array of node definition objects is requested. These node definition objects will become the child nodes of the tree. To simplify the example, dataUrl is specified directly on the TreePanel instead of explicitly creating a TreeLoader instance. Expanding the root node with a call to expand(true) causes the root's children nodes to expand as well. This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Keeping Tabs on your Trees 266 Tree and panel in a master-details relationship A master-details page interface can be built using a tree and a panel component. In this recipe, you'll use a panel to display additional details about the selected node on a TreePanel, as seen in the following screenshot: How to do it... 1. Create the styles for the tree node icons: report { background:url(img/pie-chart.png) 0 no-repeat !important; } dataset { background:url(img/table.png) 0 no-repeat !important; } datasource { background:url(img/data.png) 0 no-repeat !important; } This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 7 267 2. Create the initial text for the panel body: var pnlBody = 'Select an item to see its information'; 3. Create and compile a template to be used on the panel body: var tpl = new Ext.Template('

{description}

'); tpl.compile(); 4. Define the TreePanel: Ext.onReady(function() { var tree = new Ext.tree.TreePanel({ height: 350, region:'center', useArrows: true, autoScroll: true, animate: true, enableDD: true, containerScroll: true, border: false, dataUrl: 'tree-panel-master-details.php', root: { nodeType: 'async', text: 'My Reporting Project', draggable: false, id: 'project' } }); 5. Define the container for the TreePanel and details panel, and define the details panel in line: new Ext.Panel({ title: 'Project Explorer', renderTo: document.body, layout: 'border', width: 250, height: 500, items: [tree, { region: 'south', id: 'details-panel', autoScroll: true, split: true, height: 150, baseCls: 'x-box-mc', This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Keeping Tabs on your Trees 268 HTML: pnlBody, bodyStyle:'padding:5px' }] }); 6. Attach a handler to the tree's selection model so that you can update the details panel when the selected node changes: tree.getSelectionModel().on('selectionchange', function(tree, node) { var el = Ext.getCmp('details-panel').body; tpl.overwrite(el, node.attributes); }) 7. Expand the tree's root node and its children: tree.getRootNode().expand(true); }); How it works... All the action occurs when a tree node is selected, so a handler for the selectionchange event is used. Inside the handler, a call to the overwrite(…) method of the Ext.Template instance makes it possible to write the selected node's description to the body of the panel. Since all the selected node's attributes are passed in the call to Ext.Template overwrite(…), you can easily add more details to the panel's body by altering the declaration of the template. For example, you could add the node's name using the following code: var tpl=new Ext.Template('

Name: {name}{description} p>'); There's more... This solution can be used when the node's underlying data object has numerous properties that need to be clearly displayed. When the amount of detail is minimal, using a tool tip on the node might suffice. See also... ff The Drag-and-drop from a tree to a panel recipe (seen later in this chapter) teaches you how to drop tree nodes onto a panel and have the panel display details about the dropped node This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 7 269 The multi-column TreePanel This recipe describes how to add grid view-like features to a TreePanel using a plugin, as seen in the following screenshot: Getting ready... The column-tree.css, column-tree.js, and ColumnNodeUI.js files used in this recipe can be obtained from the Ext JS 3.0 samples page at http://extjs.com/deploy/ dev/examples/tree/column-tree.html. How to do it... 1. Add the styles needed for the multi-column tree: 2. Add the ColumnNodeUI class and ColumTree class definitions, contained in the ColumnNodeUI.js and column-tree.js files: 3. Create an instance of the ColumnTree class: Ext.onReady(function() { var tree = new Ext.tree.ColumnTree({ width: 620, height: 300, rootVisible: false, autoScroll: true, title: 'Product Backlog', This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Keeping Tabs on your Trees 270 renderTo: Ext.getBody(), 4. In the ColumnTree instance declaration, define the multiple columns of the tree: columns: [{ header: 'Item', width: 400, dataIndex: 'task' }, { header: 'Planned', width: 60, dataIndex: 'planned' }, { header: 'Actual', width: 60, dataIndex: 'actual' }, { header: 'Status', width: 80, dataIndex: 'status' }], 5. Define the loader for the multi-column tree and use an instance of the ColumnNodeUI class as the UI provider: loader: new Ext.tree.TreeLoader({ dataUrl: 'column-tree-data.txt', requestMethod:'GET', uiProviders: { 'col': Ext.tree.ColumnNodeUI } }), root: new Ext.tree.AsyncTreeNode({ text: 'Tasks' }) }); }); How it works... The ColumnTree class is a TreePanel extension that displays nodes in a multi-column fashion. The multi-column display is achieved by using the ColumnNodeUI class, an extension of TreeNodeUI, as the node's UI implementation. ColumnTree requires a definition of the tree columns through the columns config option. Observe how each column in the tree is tied to a property of the node's underlying data object by means of the dataIndex config option: This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 7 271 columns: [{ header: 'Item', width: 400, dataIndex: 'task' }, { header: 'Planned', width: 60, dataIndex: 'planned' }, { header: 'Actual', width: 60, dataIndex: 'actual' }, { header: 'Status', width: 80, dataIndex: 'status' }] There's more... This recipe can be used when the node's underlying data object has numerous properties that need to be clearly displayed. Be mindful that you need to allocate sufficient screen real estate for the additional tree columns. Drag-and-drop between tree panels Dragging and dropping nodes between two trees is an easy-to-accomplish task due to the native drag-and-drop support in the TreePanel. This recipe explains how it's done, as shown in the following screenshot: This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Keeping Tabs on your Trees 272 How to do it... 1. Create the styles for the tree-node icons: report { background:url(img/pie-chart.png) 0 no-repeat !important; } dataset { background:url(img/table.png) 0 no-repeat !important; } datasource { background:url(img/data.png) 0 no-repeat !important; } 2. Define the source tree. Be sure to enable the drag-and-drop feature: Ext.onReady(function() { var leftTree = new Ext.tree.TreePanel({ el: 'leftTree', frame:true, width: 250, height:400, useArrows: true, autoScroll: true, animate: true, enableDD: true, dropConfig: {appendOnly:true}, containerScroll: true, border: false, loader: { dataUrl: 'tree-drag-drop-trees.php', baseParams: { tree: 'leftTree' } }, root: { nodeType: 'async', text: 'My Reporting Project', draggable: false, id: 'project' } }); This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 7 273 3. Define the destination tree. Enable drag-and-drop on this one, too: var rightTree = new Ext.tree.TreePanel({ el: 'rightTree', frame: true, width: 250, height: 400, useArrows: true, autoScroll: true, animate: true, enableDD: true, dropConfig: {appendOnly:true}, containerScroll: true, border: false, loader:{ dataUrl: 'tree-drag-drop-trees.php', baseParams:{tree:'rightTree'} }, root: { text: 'Selected Items', draggable: false, id: 'selected-items' } }); 4. Render both trees and expand their root nodes: leftTree.render(); rightTree.render(); leftTree.getRootNode().expand(true); rightTree.getRootNode().expand(false); }); How it works... The TreePanel class has a native support for the drag-and-drop feature. Setting the enableDD config option to true is all that is needed to obtain the desired results. See also... ff The next recipe, Drag-and-drop from a tree to a panel, teaches you how to drop tree nodes onto a panel and program the panel to display details about the dropped node This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Keeping Tabs on your Trees 274 Drag-and-drop from a tree to a panel This is another take on the master-details interfaces with a tree and a panel component, but this time it has the ability to drop tree nodes onto the details panel, as shown below: After a node is dropped on the panel, the panel will display the underlying data object's description, as seen in the following screenshot: This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 7 275 How to do it... 1. Create the styles for the tree-node icons: report { background:url(img/pie-chart.png) 0 no-repeat !important; } dataset { background:url(img/table.png) 0 no-repeat !important; } datasource { background:url(img/data.png) 0 no-repeat !important; } 2. Create the initial text to show on the details panel: var pnlBody='Drop an item to see its description'; 3. Create and compile a template to be used on the panel body: var tpl=new Ext.Template('

{description}

'); tpl.compile(); 4. Define the TreePanel: Ext.onReady(function() { var tree = new Ext.tree.TreePanel({ height: 350, region:'center', useArrows: true, autoScroll: true, animate: true, enableDD: true, ddGroup: 'treeDDGroup', containerScroll: true, border: false, dataUrl: 'tree-panel-drag-drop.php', root: { nodeType: 'async', text: 'My Reporting Project', draggable: false, id: 'project' } }); This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Keeping Tabs on your Trees 276 5. Define a container for the TreePanel and the details panel: new Ext.Panel({ title: 'Project Explorer', renderTo: document.body, layout: 'border', width: 250, height: 500, 6. Define the details panel inline: items: [tree, new Ext.Panel({ region: 'south', id: 'details-panel', autoScroll: true, split: true, height: 150, baseCls:'x-box-mc', HTML: pnlBody, bodyStyle:'padding:5px' })] }); 7. Define the details panel's body as a drop target for items dragged from the tree: var detailsPanel = Ext.getCmp('details-panel'); var pnlDropTargetEl = detailsPanel.body; var pnlDropTarget = new Ext.dd.DropTarget(pnlDropTargetEl, { ddGroup: 'treeDDGroup', copy: false, notifyDrop: function(ddSource, e, data) { var el = detailsPanel.body; tpl.overwrite(el, data.node.attributes); return true; } }); 8. Retrieve the tree's nodes: tree.getRootNode().expand(true); }); How it works... As the TreePanel natively supports the drag-and-drop operations, all you need is to give the details panel the ability to catch the nodes that will be dropped onto its body. This is accomplished by creating an Ext.dd.DropTarget instance and adding it to the body of the details panel. This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 7 277 You'll need to specify that the DropTarget will interact only with objects originating from the tree by configuring it with the tree's ddGroup: ddGroup: 'treeDDGroup' The notifyDrop function is called when a node has been dropped. This is where you call the overwrite(…) method of the Ext.Template instance, which makes it possible to write the selected node's description to the body of the panel. Observe how notifyDrop returns true to signal to the drag source that the drop was successful. See also... ff The Drag-and-drop between tree panels recipe (seen earlier in this chapter) explains how nodes could be dragged and dropped between two tree panels ff The Tree and panel in a master-details relationship recipe (seen earlier in this chapter) illustrates how a Panel could be set up to display details about the selected node on a TreePanel This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 8 Making Progress with Menus and Toolbars This chapter will teach you the following recipes: ff Placing buttons in a toolbar ff Working with the new ButtonGroup component ff Placing menus in a toolbar ff Commonly used menu items ff Embedding a progress bar in a status bar ff Creating a custom look for the status bar items ff Using a progress bar to indicate that your application is busy ff Using a progress bar to report progress updates ff Changing the look of a progress bar Introduction In this chapter, you will learn how to use menus, toolbars, and progress bars. Along with an examination of the commonly used menu items, the recipes in this chapter will teach you how to work with the new ButtonGroup component as well as the different ways to set up toolbars and progress bars in your applications. This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Making Progress with Menus and Toolbars 280 Placing buttons in a toolbar You can embed different types of components in a toolbar. This recipe teaches you how to build a toolbar that contains image-only, text-only and image/text buttons, a toggle button, and a combo box. How to do it... 1. Create the styles for the toolbar items: #tbar { width:600px; } .icon-data { background:url(img/data.png) 0 no-repeat !important; } .icon-chart { background:url(img/pie-chart.png) 0 no-repeat !important; } .icon-table { background:url(img/table.png) 0 no-repeat !important; } 2. Define a data store for the combo box: Ext.onReady(function() { Ext.QuickTips.init(); var makesStore = new Ext.data.ArrayStore({ fields: ['make'], data: makes // from cars.js }); 3. Create a toolbar and define the buttons and combo box inline: var tb = new Ext.Toolbar({ renderTo: 'tbar', items: [{ iconCls: 'icon-data', This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 8 281 tooltip: 'Icon only button', handler:clickHandler }, '-', { text: 'Text Button' }, '-', { text: 'Image/Text Button', iconCls: 'icon-chart' }, '-', { text: 'Toggle Button', iconCls: 'icon-table', enableToggle: true, toggleHandler: toggleHandler, pressed: true }, '->', 'Make: ', { xtype: 'combo', store: makesStore, displayField: 'make', typeAhead: true, mode: 'local', triggerAction: 'all', emptyText: 'Select a make...', selectOnFocus: true, width: 135 }] }); 4. Finally, create handlers for the push button and the toggle button: function clickHandler(btn) { Ext.Msg.alert('clickHandler', 'button pressed'); } function toggleHandler(item, pressed) { Ext.Msg.alert('toggleHandler', 'toggle pressed'); } This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Making Progress with Menus and Toolbars 282 How it works... The buttons and the combo box are declared inline. While the standard button uses a click handler through the handler config option, the toggle button requires the toggleHandler config option. The button icons are set with the iconCls option, using the classes declared in the first step of the recipe. As an example, note the use of the Toolbar.Separator instances in this fragment: }, '-', { text: 'Text Button' }, '-', { text: 'Image/Text Button', iconCls: 'icon-chart' }, '-', { Using '-' to declare a Toolbar.Separator is equivalent to using xtype: 'tbseparator'. Similarly, using '->' to declare Toolbar.Fill is equivalent to using xtype:'tbfill'. See also... ff The next recipe, Working with the new ButtonGroup component, explains how to use the ButtonGroup class to organize a series of related buttons Working with the new ButtonGroup component A welcome addition to Ext JS is the ability to organize buttons in groups. Here's how to create a panel with a toolbar that contains two button groups: This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 8 283 How to do it... 1. Create the styles for the buttons: #tbar { width:600px; } .icon-data { background:url(img/data.png) 0 no-repeat !important; } .icon-chart { background:url(img/pie-chart.png) 0 no-repeat !important; } .icon-table { background:url(img/table.png) 0 no-repeat !important; } .icon-sort-asc { background:url(img/sort-asc.png) 0 no-repeat !important; } .icon-sort-desc { background:url(img/sort-desc.png) 0 no-repeat !important; } .icon-filter { background:url(img/funnel.png) 0 no-repeat !important; } 2. Define a panel that will host the toolbar: Ext.onReady(function() { var pnl = new Ext.Panel({ title: 'My Application', renderTo:'pnl-div', height: 300, width: 500, bodyStyle: 'padding:10px', autoScroll: true, 3. Define a toolbar inline and create two button groups: tbar: [{ xtype: 'buttongroup', title: 'Data Connections', columns: 1, defaults: { scale: 'small' This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Making Progress with Menus and Toolbars 284 }, items: [{ xtype:'button', text: 'Data Sources', iconCls:'icon-data' }, { xtype: 'button', text: 'Tables', iconCls: 'icon-table' }, { xtype: 'button', text: 'Reports', iconCls: 'icon-chart' }] }, { xtype: 'buttongroup', title: 'Sort & Filter', columns: 1, defaults: { scale: 'small' }, items: [{ xtype: 'button', text: 'Sort Ascending', iconCls: 'icon-sort-asc' }, { xtype: 'button', text: 'Sort Descending', iconCls: 'icon-sort-desc' }, { xtype: 'button', text: 'Filter', iconCls: 'icon-filter' }] }] How it works... Using a button group consists of adding a step to the process of adding buttons, or other items, to a toolbar. Instead of adding the items directly to the toolbar, you need to firstly define the group and then add the items to the group: tbar: [{ xtype: 'buttongroup', title: 'Data Connections', columns: 1, defaults: { scale: 'small' }, This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 8 285 items: [{ xtype:'button', text: 'Data Sources', iconCls:'icon-data' }, { xtype: 'button', text: 'Tables', iconCls: 'icon-table' }, { xtype: 'button', text: 'Reports', iconCls: 'icon-chart' }] } See also... ff The next recipe, Placing buttons in a toolbar, illustrates how you can embed different types of components in a toolbar Placing menus in a toolbar In this recipe, you will see how simple it is to use menus inside a toolbar. The panel's toolbar that we will build, contains a standard button and a split button, both with menus: How to do it... 1. Create the styles for the buttons: #tbar { width:600px; } .icon-data { background:url(img/data.png) 0 no-repeat !important; } This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Making Progress with Menus and Toolbars 286 .icon-chart { background:url(img/pie-chart.png) 0 no-repeat !important; } .icon-table { background:url(img/table.png) 0 no-repeat !important; } 2. Create a click handler for the menus: Ext.onReady(function() { Ext.QuickTips.init(); var clickHandler = function(action) { alert('Menu clicked: "' + action + '"'); }; 3. Create a window to host the toolbar: var wnd = new Ext.Window({ title: 'Toolbar with menus', closable: false, height: 300, width: 500, bodyStyle: 'padding:10px', autoScroll: true, 4. Define the window's toolbar inline, and add the buttons and their respective menus: tbar: [{ text: 'Button with menu', iconCls: 'icon-table', menu: [ { text: 'Menu 1', handler:clickHandler.createCallback('Menu 1'), iconCls: 'icon-data' }, { text: 'Menu 1', handler: clickHandler.createCallback('Menu 2'), iconCls: 'icon-data'}] }, '-', { xtype: 'splitbutton', text: 'Split button with menu', iconCls: 'icon-chart', handler: clickHandler.createCallback('Split button with menu'), menu: [ { text: 'Menu 3', handler: clickHandler.createCallback('Menu 3'), iconCls: 'icon-data' }, This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 8 287 { text: 'Menu 4', handler: clickHandler.createCallback('Menu 4'), iconCls: 'icon-data'}] }] }); 5. Finally, show the window: wnd.show(); How it works... This is a simple procedure. Note how the split button is declared with the xtype: 'splitbutton' config option. Also, observe how the createCallback() function is used to invoke the clickHandler() function with the correct arguments for each button. See also... ff The next recipe, Commonly used menu items, shows the different items that can be used in a menu Commonly used menu items To show you the different items that can be used in a menu, we will build a menu that contains radio items, a checkbox menu, a date menu, and a color menu. This is how the radio options and checkbox menu will look: This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Making Progress with Menus and Toolbars 288 The Pick a Date menu item will display a date picker, as shown in the next screenshot: The Pick a Color menu item displays a color picker, as seen here: How to do it... 1. Create a handler for the checkbox menu: Ext.onReady(function() { Ext.QuickTips.init(); var onCheckHandler = function(item, checked) { Ext.Msg.alert('Menu checked', item.text + ', checked: ' + (checked ? 'checked' : 'unchecked')); }; 2. Define a date menu: var dateMenu = new Ext.menu.DateMenu({ handler: function(dp, date) { Ext.Msg.alert('Date picker', date); } }); This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 8 289 3. Define a color menu: var colorMenu = new Ext.menu.ColorMenu({ handler: function(cm, color) { Ext.Msg.alert('Color picker', String.format('You picked {0}.', color)); } }); 4. Create a main menu. Now add the date and color menus, as well as a few inline menus: var menu = new Ext.menu.Menu({ id: 'mainMenu', items: [{ text: 'Radio Options', menu: { items: [ 'Choose a Theme', { text: 'Aero Glass', checked: true, group: 'theme', checkHandler: onCheckHandler }, { text: 'Vista Black', checked: false, group: 'theme', checkHandler: onCheckHandler }, { text: 'Gray Theme', checked: false, group: 'theme', checkHandler: onCheckHandler }, { text: 'Default Theme', checked: false, group: 'theme', checkHandler: onCheckHandler } ] } }, { text: 'Pick a Date', iconCls: 'calendar', menu: dateMenu }, { This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Making Progress with Menus and Toolbars 290 text: 'Pick a Color', menu: colorMenu }, { text: 'The last menu', checked: true, checkHandler: onCheckHandler }] }); 5. Create a toolbar and add the main menu: var tb = new Ext.Toolbar({ renderTo: 'tbar', items: [{ text: 'Menu Items', menu: menu }] }); How it works... After defining the date and color pickers, the main menu is built. This menu contains the pickers, as well as a few more items that are defined inline. To display checked items (see the checked: true config option) with a radio button instead of a checkbox, the menu items need to be defined using the group config option. This is how the theme selector menu is built: menu: { items: [ 'Choose a Theme', { text: 'Aero Glass', checked: true, group: 'theme', checkHandler: onCheckHandler }, { text: 'Vista Black', checked: false, group: 'theme', checkHandler: onCheckHandler This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 8 291 See also... ff The Placing buttons in a toolbar recipe (covered earlier in this chapter) illustrates how you can embed different types of components in a toolbar Embedding a progress bar in a status bar This recipe explains how to embed a progress bar in a panel's status bar, a scenario found in countless user interfaces: How to do it... 1. Create a click handler that will simulate a long-running activity and update the progress bar: Ext.onReady(function() { var loadFn = function(btn, statusBar) { btn = Ext.getCmp(btn); btn.disable(); Ext.fly('statusTxt').update('Saving...'); pBar.wait({ interval: 200, duration: 5000, increment: 15, fn: function() { btn.enable(); Ext.fly('statusTxt').update('Done'); } }); }; 2. Create an instance of the progress bar: var pBar = new Ext.ProgressBar({ id: 'pBar', width: 100 }); This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Making Progress with Menus and Toolbars 292 3. Create a host panel and embed the progress bar in the bbar of the panel. Also, add a button that will start the progress bar updates: var pnl = new Ext.Panel({ title: 'Status bar with progress bar', renderTo: 'pnl1', width: 400, height: 200, bodyStyle: 'padding:10px;', items: [{ xtype: 'button', id: 'btn', text: 'Save', width:'75', handler: loadFn.createCallback('btn', 'sBar') }], bbar: { id: 'sBar', items: [{ xtype: 'tbtext', text: '',id:'statusTxt' },'->', pBar] } }); How it works... The first step consists of creating loadFn, a function that simulates a long-running operation, so that we can see the progress bar animation when the button is clicked. The heart of loadFn is a call to ProgressBar.wait(…), which initiates the progress bar in an auto-update mode. And this is how the status bar is embedded in the bbar of the panel: bbar: { id: 'sBar', items: [{ xtype: 'tbtext', text: '',id:'statusTxt' },'->', pBar] Observe how the progress bar is sent to the rightmost location in the status bar with the help of a Toolbar.Fill instance, declared with '->'. See also... ff The next recipe, Creating a custom look for the status bar items, shows how you can easily change the look of a status bar's items using custom styles This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 8 293 Creating a custom look for the status bar items Customizing the look of toolbar items is relatively simple. In this recipe, you will learn how to create toolbar items with a sunken look that can be found in many desktop applications: How to do it... 1. Create the styles that will provide the custom look of the status bar text items: .custom-status-text-panel { border-top:1px solid #99BBE8; border-right:1px solid #fff; border-bottom:1px solid #fff; border-left:1px solid #99BBE8; padding:1px 2px 2px 1px; } 2. Create a host panel: Ext.onReady(function() { var pnl = new Ext.Panel({ title: 'Status bar with sunken text items', renderTo: 'pnl1', width: 400, height: 200, bodyStyle: 'padding:10px;', 3. Define the panel's bbar with the text items: bbar: { id: 'sBar', items: [ { id: 'cachedCount', xtype:'tbtext', text: 'Cached: 15' }, ' ', This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Making Progress with Menus and Toolbars 294 { id: 'uploadedCount', xtype: 'tbtext', text: 'Uploaded: 7' }, ' ', { id: 'invalidCount', xtype: 'tbtext', text: 'Invalid: 2' } ] }, 4. Now, add a handler for the afterrender event and use it to modify the styles of the text items: listeners: { 'afterrender': { fn: function() { Ext.fly(Ext.getCmp('cachedCount').getEl()).parent(). addClass('custom-status-text-panel'); Ext.fly(Ext.getCmp('uploadedCount').getEl()).parent(). addClass('custom-status-text-panel'); Ext.fly(Ext.getCmp('invalidCount').getEl()).parent(). addClass('custom-status-text-panel'); }, delay:500 } } How it works... The actual look of the items is defined by the style in the custom-status-text-panel CSS class. After the host panel and toolbar are created and rendered, the look of the items is changed by applying the style to each of the TD elements that contain the items. For example: Ext.fly(Ext.getCmp('uploadedCount').getEl()).parent(). addClass('custom-status-text-panel'); See also... ff The previous recipe, Embedding a progress bar in a status bar, explains how a progress bar can be embedded in a panel's status bar This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 8 295 Using a progress bar to indicate that your application is busy This recipe teaches you how to use a progress bar to indicate that your application is busy performing an operation. The next screenshot shows a progress bar built using this recipe: How to do it... 1. Define the progress bar: Ext.onReady(function() { Ext.QuickTips.init(); var pBar = new Ext.ProgressBar({ id: 'pBar', width: 300, renderTo: 'pBarDiv' }); 2. Add a handler for the update event and use it to update the wait message: pBar.on('update', function(val) { //Handle this event if you need to // execute code at each progress interval. Ext.fly('pBarText').dom.innerHTML += '.'; }); 3. Create a click handler for the button that will simulate a long-running activity: var btn = Ext.get('btn'); btn.on('click', function() { Ext.fly('pBarText').update('Please wait'); btn.dom.disabled = true; pBar.wait({ interval: 200, duration: 5000, increment: 15, fn: function() { btn.dom.disabled = false; This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Making Progress with Menus and Toolbars 296 Ext.fly('pBarText').update('Done'); } }); }); 4. Add the button to the page: How it works... After creating the progress bar, the handler for its update event is created. While I use this handler simply to update the text message, you can use it to execute some other code every time that a progress interval occurs. The click handler for the button calls the progress bar's wait(…) function, which causes the progress bar to auto-update at the configured interval and reset itself after the configured duration: pBar.wait({ interval: 200, duration: 5000, increment: 15, fn: function() { btn.dom.disabled = false; Ext.fly('pBarText').update('Done'); } }); There's more... The progress bar can also be configured to run indefinitely by not passing the duration config option. Clearing the progress bar in this scenario requires a call to the reset() function. See also... ff The next recipe, Using a progress bar to report progress updates, illustrates how a progress bar can be set up to notify the user that progress is being made in the execution of an operation ff The Changing the look of a progress bar recipe (covered later in this chapter) shows you how easy it is to change the look of the progress bar using custom styles This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 8 297 Using a progress bar to report progress updates As its name indicates, a progress bar can be used to notify the user that progress is being made at each step of a long-running operation. This recipe explains how it is accomplished by using a progress bar to indicate when each of the 10 steps of a fictitious long-running operation are executed: How to do it... 1. Create an object that will encapsulate the simulation of a long-running operation: var Loader = function() { } 2. Inside the Loader class, create a function that will update the progress bar status: var f = function(v, pbar, btn, count, cb) { return function() { if (v > count) { btn.dom.disabled = false; cb(); } else { pbar.updateProgress(v / count, 'Loading item ' + v + ' of ' + count + '...'); } }; }; 3. The start() method of the Loader class will trigger the simulation of a long- running operation: return { start: function(pbar, btn, count, cb) { btn.dom.disabled = true; var ms = 5000 / count; for (var i = 1; i < (count + 2); i++) { setTimeout(f(i, pbar, btn, count, cb), i * ms); This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Making Progress with Menus and Toolbars 298 } } } } (); 4. Now, it's time to define the progress bar: Ext.onReady(function() { var pBar = new Ext.ProgressBar({ id: 'pBar', width: 300, renderTo: 'pBarDiv', text: 'Loading...' }); 5. After the progress bar, a click handler for the button triggers the simulation: var btn = Ext.get('btn'); btn.on('click', function() { Loader.start(pBar, Ext.get('btn'), 10, function() { Ext.getCmp('pBar').reset(true); Ext.fly('pBarText').update('Finished').show(); }); }); }); 6. Add the button to the page: How it works... The Loader class simulates a long-running operation. Its start(…) function sets up a timer that periodically invokes the function f(), which, in turn, updates the progress bar via ProgressBar.updateProgress(…). In a real-world scenario, updateProgress(…) will be called upon the completion of each of the steps of a long-running operation. See also... ff The previous recipe, Using a progress bar to indicate that your application is busy, shows how the progress bar can help to inform users that the UI is busy performing an operation ff The next recipe, Changing the look of a progress bar, shows you how easy it is to change the look of the progress bar using custom styles This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 8 299 Changing the look of a progress bar In this recipe, you will learn how easy it is to change the look of the progress bar. Let's see how it is done. How to do it... 1. Create the styles for the custom look of the progress bar: .x-progress-wrap.custom { height:17px; border:1px solid #cccccc; overflow:hidden; padding:0 2px; } .ext-ie .x-progress-wrap.custom { height:19px; } .custom .x-progress-inner { height:17px; background: #fff; } .custom .x-progress-bar { height:15px; background:transparent url(img/pbar.gif) repeat-x 0 0; border-top:1px solid #BEBEBE; border-bottom:1px solid #EFEFEF; border-right:0; } 2. Define the progress bar and specify that it will use the custom CSS class: Ext.onReady(function() { var pBar = new Ext.ProgressBar({ id: 'pBar', This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Making Progress with Menus and Toolbars 300 width: 300, renderTo: 'pBarDiv', cls:'custom' }); 3. Now, create a handler for the progress bar's update event that will change the status message shown on the page: pBar.on('update', function(val) { //Handle this event if you need to // execute code at each progress interval. Ext.fly('pBarText').dom.innerHTML += '.'; }); 4. Create a click handler for the button, which simulates a long-running operation: var btn = Ext.get('btn'); btn.on('click', function() { Ext.fly('pBarText').update('Please wait'); btn.dom.disabled = true; pBar.wait({ interval: 200, duration: 5000, increment: 15, fn: function() { btn.dom.disabled = false; Ext.fly('pBarText').update('Done'); } }); }); }); 5. Finally, add the button to the page: This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 8 301 How it works... Needless to say, the look of the progress bar component is defined by a number of styles. After defining the custom styles in the first step, you just need to make the progress bar aware of these styles by using the cls config option: var pBar = new Ext.ProgressBar({ id: 'pBar', width: 300, renderTo: 'pBarDiv', cls:'custom' }); See also... ff The Using a progress bar to indicate that your application is busy recipe (covered earlier in this chapter) shows how the progress bar can help to inform users that the user interface is busy performing an operation ff The previous recipe, Using a progress bar to report progress updates, illustrates how a progress bar can be set up to notify the user at each step of a long-running operation This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 9 Well-charted Territory These are the recipes that you will learn in this chapter: ff Setting up a line chart to display local data ff Setting up a line chart to display data retrieved from the server ff Setting up a column chart to display local data ff Setting up a column chart to display data retrieved from the server ff Displaying local data with a pie chart ff Displaying remote data with a pie chart ff Using a chart component to display multiple data series ff Creating an auto-refreshing chart ff Configuring the Slider component to display a custom tip ff Enhancing the Slider component with custom tick marks Introduction This chapter covers the Chart and Slider components. It explores the different chart types, typical usage scenarios of charts when working with visual presentation of data, and the approaches to configuring and customizing the look of the slider widget. This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Well-charted Territory 304 Setting up a line chart to display local data This recipe explains how to create a line chart that displays data stored in a local array. The array will contain income by month data for a fictitious movie rental company. The chart's X-axis will represent the time-scale (months), whereas the income information will be reflected on the Y-axis. This can be seen in the following screenshot: How to do it... 1. Set the URL to load the chart from: Ext.chart.Chart.CHART_URL = '../ext3/charts.swf'; 2. Define the data store: Ext.onReady(function() { var rentalsStore = new Ext.data.JsonStore({ fields: ['month', 'payments'], data: [ { month: 'May 2005', payments: 4824.43 }, { month: 'June 2005', payments: 9631.88 }, { month: 'July 2005', payments: 28373.89 }, { month: 'August 2005', payments: 24072.13 }, { month: 'September 2005', payments: 33475.55 } ] }); This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 9 305 3. Create a panel that will contain your chart and define the chart within the panel: var pnl = new Ext.Panel({ title: 'Movie Rentals', renderTo: Ext.getBody(), width: 500, height: 300, layout: 'fit', items: { xtype: 'linechart', store: rentalsStore, xField: 'month', yField: 'payments', yAxis: new Ext.chart.NumericAxis({ displayName: 'Rentals', labelRenderer : Ext.util.Format. numberRenderer('0,0') }) } }); }); How it works... To specify a line chart, use xtype = 'linechart'; or use Ext.chart.LineChart if you want to create the chart explicitly. To display local data, simply use a local array as the data for the grid's data store. Notice the presence of an Ext.util.Format.numberRenderer instance to format the labels of the Y-axis. See also... ff The next recipe, Setting up a line chart to display data retrieved from the server, explains how to create and configure a line chart so that it displays the data that has been downloaded from the server This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Well-charted Territory 306 Setting up a line chart to display data retrieved from the server This recipe explains how to set up a line chart to display remote data. As with the previous recipe, the information displayed will be income-by-month figures for a fictitious movie rental company. In this case, a server page will retrieve this information from a database and make it available to the chart, as seen in the following screenshot: How to do it... 1. Set the URL to load the chart from: Ext.chart.Chart.CHART_URL = '../ext3/charts.swf'; 2. Define the data store: Ext.onReady(function() { var rentalsStore = new Ext.data.JsonStore({ url: 'chart-line-remote.php', root: 'rentals', fields: ['month', 'payments'], autoLoad: true }); This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 9 307 3. Create a panel that will contain your chart and define it within the panel: var pnl = new Ext.Panel({ title: 'Movie Rentals', renderTo: Ext.getBody(), width: 500, height: 300, layout: 'fit', items: { xtype: 'linechart', store: rentalsStore, xField: 'month', yField: 'payments', yAxis: new Ext.chart.NumericAxis({ displayName: 'Rentals', labelRenderer : Ext.util.Format. numberRenderer('0,0') }) } }); }); How it works... To specify a line chart, use xtype = 'linechart'; or use Ext.chart.LineChart if you want to create the chart explicitly. As with other Ext JS data-consuming widgets; to display remote data, simply use a store that acquires the data from a URL: url: 'chart-line-remote.php' Notice the presence of an Ext.util.Format.numberRenderer instance to format the labels of the Y-axis. See also... ff The previous recipe, Setting up a line chart to display local data, explains how to create a line chart and configure it to display data stored in a local array This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Well-charted Territory 308 Setting up a column chart to display local data Using a column chart to display local data is very simple. As seen previously in the How to set up a line chart to display local data recipe, the data source used in this recipe is an array that contains income-by-month information for a fictitious movie rental company. Similarly, the chart's X-axis will represent the time-scale (months); whereas income information will be reflected on the Y-axis, as shown in the following screenshot: How to do it... 1. Set the URL to load the chart from: Ext.chart.Chart.CHART_URL = '../ext3/charts.swf'; 2. Define the data store: Ext.onReady(function() { var rentalsStore = new Ext.data.JsonStore({ fields: ['month', 'payments'], data: [ { month: 'May 2005', payments: 4824.43 }, { month: 'June 2005', payments: 9631.88 }, { month: 'July 2005', payments: 28373.89 }, { month: 'August 2005', payments: 24072.13 }, { month: 'September 2005', payments: 33475.55 } ] }); This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 9 309 3. Create a panel that will contain your chart and define it within the panel: var pnl = new Ext.Panel({ title: 'Movie Rentals', renderTo: Ext.getBody(), width: 500, height: 300, layout: 'fit', items: { xtype: 'columnchart', store: rentalsStore, xField: 'month', yField: 'payments', yAxis: new Ext.chart.NumericAxis({ displayName: 'Rentals', labelRenderer : Ext.util.Format.numberRenderer('0,0') }) } }); }); How it works... The grid is connected to the data in the local array through its data store. To specify a column chart type, use xtype = 'columnchart'; or use Ext.chart. ColumnChart if you want to create the chart explicitly. Notice the presence of an Ext.util.Format.numberRenderer instance to format the labels of the Y-axis. See also... ff The next recipe, Setting up a column chart to display data retrieved from the server, explains what is needed to configure a column chart, so that it displays data that has been downloaded from the server This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Well-charted Territory 310 Setting up a column chart to display data retrieved from the server In this recipe, you'll see how a column chart is set up to display data that has been downloaded from the server. As seen in previous chart recipes, the chart will display income-by-month figures for a fictitious movie rental company. A server page will retrieve this information from a database and make it available to the chart, as seen in the following screenshot: How to do it... 1. Set the URL to load the chart from: Ext.chart.Chart.CHART_URL = '../ext3/charts.swf'; 2. Define the data store: Ext.onReady(function() { var rentalsStore = new Ext.data.JsonStore({ url: 'chart-line-remote.php', root: 'rentals', fields: ['month', 'payments'], autoLoad: true }); This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 9 311 3. Create a panel that will contain your chart and define it within the panel: var pnl = new Ext.Panel({ title: 'Movie Rentals', renderTo: Ext.getBody(), width: 500, height: 300, layout: 'fit', items: { xtype: 'columnchart', store: rentalsStore, xField: 'month', yField: 'payments', yAxis: new Ext.chart.NumericAxis({ displayName: 'Rentals', labelRenderer : Ext.util.Format.numberRenderer('0,0') }) } }); }); How it works... When it comes to data store configuration, the chart is no different than other Ext JS data-consuming widgets. To display remote data, simply use a store that acquires the data from a URL: url: 'chart-line-remote.php' Use xtype = 'columnchart' to specify a column chart type. You can also use Ext.chart.ColumnChart if you need to create the chart explicitly. Notice the Ext.util.Format.numberRenderer instance of the format for the labels of the Y-axis. See also... ff The previous recipe, Setting up a column chart to display local data, explains how to create a column chart and configure it, so that it displays data stored in a local array This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Well-charted Territory 312 Displaying local data with a pie chart This recipe teaches you to create a pie chart that displays data stored in a local array. The data source used in this recipe is an array that contains movie sales information for a fictitious movie rental company. Each slice of the pie represents the amount of sales for a given movie category, as seen in the following screenshot: How to do it... 1. Set the URL to load the chart from: Ext.chart.Chart.CHART_URL = '../ext3/charts.swf'; 2. Define the data store: Ext.onReady(function() { var salesStore = new Ext.data.JsonStore({ fields: ['category', 'total_sales'], data: [ { category: 'Action', total_sales: 4375.85 }, { category: 'Sci-Fi', total_sales: 4756.98 }, { category: 'Animation', total_sales: 4656.30 }, { category: 'Drama', total_sales: 3722.54 }, { category: 'Family', total_sales: 5226.07 } ] }); This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 9 313 3. Create a panel that will contain your chart and define the chart within the panel: var pnl = new Ext.Panel({ title: 'Movie Sales By Categories', renderTo: Ext.getBody(), width: 400, height: 400, items: { xtype: 'piechart', store: salesStore, categoryField: 'category', dataField: 'total_sales', extraStyle: { legend: { display: 'bottom', padding: 5, font: { family: 'Tahoma', size: 13 } } } } }); }); How it works... The grid is connected to the data in the local array through its data store. To specify a pie chart, use xtype = 'piechart'; or use Ext.chart.PieChart if you want to create the chart explicitly. While the categoryField configuration option specifies what data store field will define the chart's slices, the dataField option defines what field will define the dimensions of each slice. The extraStyle config option is used to add styles, or override the styles defined by the chartStyle config option. In this case, we use it to define the position of the chart's legend. You can also use it to override the default font, dataTip, padding, and animationEnabled options. This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Well-charted Territory 314 There's more... In general, the use of pie charts is controvertial due to the difficulty in comparing different slices of the same chart or data across different charts. However, these charts can be an effective way of displaying information in some cases—in particular if the purpose is to compare the size of a slice with the whole pie, rather than comparing the slices among themselves. See also... ff The next recipe, Displaying remote data with a pie chart, illustrates how to configure a pie chart, so that it displays data that has been downloaded from the server Displaying remote data with a pie chart This recipe teaches you how to create a pie chart that displays remote data. Movie sales information for a fictitious movie rental company will be retrieved from a database and made available to the chart by a server page. Each slice of the pie represents the sales amount for a given movie category, as seen in the following screenshot: This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 9 315 How to do it... 1. Set the URL to load the chart from: Ext.chart.Chart.CHART_URL = '../ext3/charts.swf'; 2. Define the data store: Ext.onReady(function() { var salesStore = new Ext.data.JsonStore({ url: 'chart-pie-remote.php', root: 'sales', fields: ['category', 'total_sales'], autoLoad: true }); 3. Create a panel that will contain your chart and define the chart within the panel: var pnl = new Ext.Panel({ title: 'Movie Sales By Categories', renderTo: Ext.getBody(), width: 500, height: 500, layout: 'fit', items: { xtype: 'piechart', store: salesStore, categoryField: 'category', dataField: 'total_sales', extraStyle: { legend: { display: 'right', padding: 5, font: { family: 'Tahoma', size: 13 } } } } }); }); This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Well-charted Territory 316 How it works... To display remote data, simply use a store that acquires the data from a URL: url: 'chart-pie-remote.php' To specify a pie chart, use xtype = 'piechart'; or use Ext.chart.PieChart if you need to create the chart explicitly. While the categoryField configuration option specifies what data store field will define the chart's slices, the dataField option establishes what field will define the dimensions of each slice. The extraStyle config option is used to add styles, or override the styles defined by the chartStyle config option. In this case, use it to define the position of the chart's legend. You can also use it to override the default font, dataTip, padding, and animationEnabled options. There's more... In general, the use of pie charts is controvertial due to the difficulty in comparing different slices of the same chart, or data across different charts. The chart built in this recipe, with more than a dozen slices, is a good example. However, pie charts can be an effective way of displaying information in some cases, in particular if the purpose is to compare the size of a slice with the whole pie, rather than comparing the slices among themselves. See also... ff The previous recipe, Displaying local data with a pie chart, explains how to create a pie chart and configure it to display data stored in a local array Using a chart component to display multiple data series The chart component supports the display of data series with different chart types. In this recipe, rental and payment information for a fictitious movie rental company will be retrieved from a database and will be made available to the chart component by a server page. This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 9 317 Rental data will be presented using a line chart, whereas payment data will be presented using a column chart as shown in the following screenshot: How to do it... 1. Set the URL to load the chart from: Ext.chart.Chart.CHART_URL = '../ext3/charts.swf'; 2. Define the data store: Ext.onReady(function() { var rentalsStore = new Ext.data.JsonStore({ url: 'chart-line-remote.php', root: 'rentals', fields: ['month', 'payments', 'filmsrented'], autoLoad: true }); 3. Create a panel that will contain our chart and define the chart within the panel: var pnl = new Ext.Panel({ title: 'Movie Rentals', renderTo: Ext.getBody(), width: 500, height: 300, layout: 'fit', items: { xtype: 'columnchart', store: rentalsStore, xField: 'month', This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Well-charted Territory 318 yAxis: new Ext.chart.NumericAxis({ displayName: 'Rentals', labelRenderer : Ext.util.Format. numberRenderer('0,0') }), 4. Define a column series: series: [{ type: 'column', displayName: 'Payments', yField: 'payments', style: { color: '#55bbcc' } }, 5. Define a line series: { type: 'line', displayName: 'Films rented', yField: 'filmsrented', style:{color:'#AA3366'} }], 6. Customize the look of the chart: chartStyle: { xAxis: { color: 0x69aBc8, majorTicks: {color: 0x69aBc8, length: 4}, minorTicks: {color: 0x69aBc8, length: 2}, majorGridLines: {size: 1, color: 0xeeeeee} }, yAxis: { color: 0x69aBc8, majorTicks: {color: 0x69aBc8, length: 4}, minorTicks: {color: 0x69aBc8, length: 2}, majorGridLines: {size: 1, color: 0xdfe8f6} } } } }); }); How it works... The type config option defines a different type for different series; type:'column' for the payment series and type'line' for the rental series. The xField config option is used once to define the X-axis as common to both the series. The yField option is different for each series. It maps to the payments and filmsrented fields of the data store. This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 9 319 The visual style of the chart can be changed using chartStyle. In this case, the color, majorTicks, minorTicks, and majorGridlines options are altered. See also... ff The Setting up a line chart to display data retrieved from the server recipe (seen earlier in this chapter) explains how to create and configure a line chart to display data that has been downloaded from the server ff The Setting up a column chart to display data retrieved from the server recipe (seen previously in this chapter) explains what is needed to configure a column chart, so that it displays data that has been downloaded from the server Creating an auto-refreshing chart Sometimes, it is desirable to have a chart that automatically refreshes its information. This recipe shows you how to implement periodic updates of a chart's data. The Crazy Stock Prices chart will display fictitious stock prices, as seen in the following screenshot: This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Well-charted Territory 320 The chart's data will refresh periodically without user intervention, as seen in the following screenshot: How to do it... 1. Set the URL to load the chart from: Ext.chart.Chart.CHART_URL = '../ext3/charts.swf'; 2. Create a function to generate dummy data: function generateData() { var data = []; var companies = ['MSFT','YHOO','GOOG','JAVA']; for (var i = 0; i < 4; ++i) { data.push([companies[i], (Math.floor(Math.random() * 11) + 1) * 10]); } return data; } This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 9 321 3. Define a data store to feed the chart: var store = new Ext.data.ArrayStore({ fields: ['stock', 'price'], data: generateData() }); 4. Create a function that will be called periodically to refresh the data: function refreshData() { store.loadData(generateData()); } 5. Create a panel that will contain your chart and define the chart within the panel: Ext.onReady(function() { var pnl = new Ext.Panel({ width: 400, height: 400, renderTo: document.body, title: 'Crazy Stock Prices', items: { xtype: 'columnchart', store: store, yField: 'price', xField: 'stock', xAxis: new Ext.chart.CategoryAxis({ title: 'Symbol' }), yAxis: new Ext.chart.NumericAxis({ title: 'Price', labelRenderer: Ext.util.Format.numberRenderer('$ 0,000.00') }) } }); 6. Set up a task to update the data at intervals: Ext.TaskMgr.start({ run: refreshData, interval: 5000 }); }); This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Well-charted Territory 322 How it works... What is needed in this scenario is a way to refresh the information that is saved in the chart's data store. This is accomplished with the use of the Ext.TaskMgr class. TaskMgr is a static Ext.util.TaskRunner instance that can be used to run arbitrary tasks. Here, it is set up to execute the refreshData() function after every five seconds: Ext.TaskMgr.start({ run: refreshData, interval: 5000 }); refreshData(), in turn, loads a new batch of dummy data into the data store. By way of the data-binding mechanism built into the chart, an update of the data store contents will cause the chart to render the updated information. Note that in a more robust implementation, the refresh interval would probably be a configurable option. Configuring the Slider component to display a custom tip The Slider component is a welcome addition to the Ext JS arsenal. One of the enhancements that you can make to this widget consists of adding a tool tip that displays the Slider component's current value as seen here: With little work, the tool tip can be made to display a friendlier message, as shown in the following screenshot: This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 9 323 How to do it... 1. Define the SliderTip custom component: The SliderTip component used in this recipe is provided with the Ext JS documentation and samples at http://www.extjs.com/ deploy/dev/examples/. Ext.ux.SliderTip = Ext.extend(Ext.Tip, { minWidth: 10, offsets: [0, -10], init: function(slider) { slider.on('dragstart', this.onSlide, this); slider.on('drag', this.onSlide, this); slider.on('dragend', this.hide, this); slider.on('destroy', this.destroy, this); }, onSlide: function(slider) { this.show(); this.body.update(this.getText(slider)); this.doAutoWidth(); this.el.alignTo(slider.thumb, 'b-t?', this.offsets); }, getText: function(slider) { return String(slider.getValue()); } }); 2. Create the first Slider and set an instance of the SliderTip component as a plugin: Ext.onReady(function() { var slider1 = new Ext.Slider({ renderTo:'slider1', minValue: 0, maxValue: 100, width:300, increment: 10, plugins: new Ext.ux.SliderTip() }); 3. Create a second Slider and set an instance of the SliderTip component as a plugin. Provide a custom message by overriding the getText() function of the SliderTip component: var slider2 = new Ext.Slider({ renderTo: 'slider2', minValue: 0, maxValue: 100, width: 300, increment: 10, This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Well-charted Territory 324 plugins: new Ext.ux.SliderTip({ getText: function(slider) { return String.format('{0}% of available space b>', slider.getValue()); } }) }); }); How it works... The custom tip functionality comes as a courtesy of the SliderTip plugin. For the second Slider, the plugin is passed a getText() function that provides the custom format. See also... ff The next recipe, Enhancing the Slider component with custom tick marks, explains another modification that you can make to the Slider component Enhancing the Slider component with custom tick marks Another enhancement you can make to the Slider component is adding custom tick marks. This recipe teaches you how this can be accomplished using a background image and cascading stylesheets, as seen in the following screenshot: This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 9 325 How to do it... 1. Create an image with tick marks, as seen below: 2. Create a CSS class that will be used to position the tick mark image behind the Slider: 3. Define the Slider: Ext.onReady(function() { var slider = new Ext.Slider({ renderTo:Ext.getBody(), vertical: true, minValue: 0, maxValue: 100, height: 214, increment: 10, plugins: new Ext.ux.SliderTip({ getText: function(slider) { return String.format('{0}% of available space ', slider.getValue()); } }) }); }); 4. Add the vertical-slider CSS class to the document's body: This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Well-charted Territory 326 How it works... The trick here is to position the tick marks image behind the Slider. This is accomplished by changing the background of the Slider's container. In the above example, the Slider's container is the body of the HTML document. Adding the vertical-slider CSS class to the document's body does the job. When creating the tick marks image, pay attention to the height of the Slider and the distance between the steps (the increment config option). You want the image to correctly align with the Slider, and the tick marks to match the distance between the steps. See also... ff The previous recipe, Configuring the slider component to display a custom tip, explains how the Slider component can be configured to display a custom tip This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 10 Patterns in Ext JS In this chapter, you will learn the following recipes: ff Sharing functionality with the Action class ff Autosaving form values ff Saving resources with lazy component instantiation ff Extending a class ff Using code modules to achieve encapsulation ff Implementing a publish/subscribe mechanism with relayEvents() ff Augmenting a class with a plugin ff Building preconfigured classes ff Implementing state preservation with cookies Introduction In this chapter, you will explore some additional design patterns used to build robust and flexible applications with Ext JS. Besides autosaving form elements and component state management, you will be able to take a look at the patterns for building your own custom components on top of the Ext framework, along with ways for modularizing your code. Sharing functionality with the Action class Components such as buttons, menus, and toolbars frequently perform similar functionalities. This recipe shows you how you can use the Action interface to abstract a hypothetical Import function out of a menu, a toolbar item, and a button's handlers, effectively implementing a variant of the command design pattern. You will also learn how to use Action to change different configuration options of the components that use it. This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Patterns in Ext JS 328 In the following screenshot; a toolbar item, a menu item, and a button are all wired to perform the Import functionality by using the Ext.Action interface: Clicking on any of the components that use the Action interface will execute the Import functionality, as seen in the following screenshot: The Action interface allows you to change the text of the components that use it. In this recipe, you'll use an Ext.Msg.prompt to change the Action's text, as seen in the following screenshot: After entering the new text, all of the components that implement the Action will reflect the change, as seen in the following screenshot: This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 10 329 How to do it... 1. Create an Action instance: Ext.onReady(function() { var action = new Ext.Action({ iconCls: 'icon-data', text: 'Import', handler: function() { Ext.Msg.alert('Action executed', 'This is the Import action executing...'); } }); 2. Now, create a panel with the different components that will use the Action instance: var panel = new Ext.Panel({ title: 'What you can do with Actions', width: 450, height: 200, bodyStyle: 'padding:10px;', tbar: [ action, { text: 'Action Menu', menu: [action] } ], items: [ new Ext.Button(action) ], renderTo: Ext.getBody() }); var tb = panel.getTopToolbar(); // Buttons added to the toolbar of the Panel above // to test/demo doing group operations with an action tb.add('-', { text: 'Disable action', handler: function() { action.setDisabled(!action.isDisabled()); this.setText(action.isDisabled() ? 'Enable action' : 'Disable action'); } }, { text: 'Change action text', handler: function() { Ext.Msg.prompt('Enter Text', 'Enter new text for the action:', function(btn, text) { if (btn == 'ok' && text) { This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Patterns in Ext JS 330 action.setText(text); action.setHandler(function() { Ext.Msg.alert('Click', 'This is the "' + text + '" action executing...'); }); } }); } }, { text: 'Switch action icon', handler: function() { action.setIconClass(action.getIconClass()='icon-data' ? 'icon-filter' : 'icon-data'); } }); tb.doLayout(); }); How it works... The Action class allows you to share handlers, configuration options, and UI updates across any components that support the Action interface. Start by creating an Action instance. The configuration options iconCls, text, and handler will be applied to the components that use it. Next, create the different components that will use the Action such as a toolbar item and menu: tbar: [ action, { text: 'Action Menu', menu: [action] } ], Next, create a button: items: [ new Ext.Button(action) ] Notice how the Action instance functions as an item for the toolbar items. For buttons, the action instance is passed as the config object in the button definition. This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 10 331 Finally, some more components will help you to exercise the Action features: A toolbar item allows you to toggle the enabled state of the Action: { text: 'Disable action', handler: function() { action.setDisabled(!action.isDisabled()); this.setText(action.isDisabled() ? 'Enable action' : 'Disable action'); } } A second toolbar item allows you to change the Action's text and handler: { text: 'Change action text', handler: function() { Ext.Msg.prompt('Enter Text', 'Enter new text for the action:', function(btn, text) { if (btn == 'ok' && text) { action.setText(text); action.setHandler(function() { Ext.Msg.alert('Click', 'This is the "' + text + '" action executing...'); }); } }); } } There's more... Any component that needs to use actions must support the config object interface, as well as the following method list: setText(string), setIconCls(string), setDisabled(boolean), setVisible(boolean), and setHandler(function). Autosaving form values Autosave is a useful feature found in many applications. This recipe teaches you how to execute code that simulates the saving of a TextArea's contents without requiring any user intervention. This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Patterns in Ext JS 332 Your TextArea will be accompanied by a couple of toolbar items that will show the character count and the last time the TextArea contents were saved, as seen in the following screenshot: How to do it... 1. Create the components that will show the character count and status message: Ext.onReady(function() { var statusMsg = new Ext.Toolbar.TextItem(''); var charCount = new Ext.Toolbar.TextItem('Chars: 0'); 2. Now, create a panel that will contain the the autosaving TextArea: new Ext.Panel({ title: 'Autosave of a form element', renderTo: 'autosave-form', width: 450, autoHeight: true, bodyStyle: 'padding:5px;', layout: 'fit', bbar: new Ext.Toolbar({ id: 'statusBar', items: [charCount, '->', statusMsg] }), 3. Define the autosaving TextArea inside the panel. A handler for the keypress event will update the character count in the status bar: items: { xtype: 'textarea', id: 'autosaveTextArea', enableKeyEvents: true, grow: true, growMin: 100, growMax: 200, listeners: { // Update the character count. 'keypress': { This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 10 333 fn: function(t) { var v = t.getValue(); var cc = v.length ? v.length : 0; Ext.fly(charCount.getEl()).update('Chars: ' + cc); }, buffer: 1 // buffer to allow the value to update first } } } }); 4. Another handler for the text area's keypress event will initiate a delayed task that, in turn, will execute the autosave routine: Ext.fly('autosaveTextArea').on('keypress', function() { statusMsg.setText('Last saved at ' + new Date(). format('g:i:s A')); // Put your save code here. }, this, { buffer: 1500 }); }); How it works... Your simple text editor consists of a panel with a TextArea and a toolbar, which will show the character count and the time that the TextArea value was last saved. Use the text area's keypress event to keep track of the character count. This keypress handler displays the length of the TextArea's value in the toolbar. listeners: { // Update the character count. 'keypress': { fn: function(t) { var v = t.getValue(); var cc = v.length ? v.length : 0; Ext.fly(charCount.getEl()).update('Chars: ' + cc); }, buffer: 1 // buffer to allow the value to update first } } } This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Patterns in Ext JS 334 The autosaving logic is also triggered by the keypress event in the TextArea. The global flyweight object allows you to acquire a reference to the TextArea and wire the event handler: Ext.fly('autosaveTextArea').on('keypress', function() …. You can use this event handler to call your implementation of the save feature right after updating the status bar message: statusMsg.setText('Last saved at ' + new Date().format('g:i:s A')); // Put your save code here. There's more... Use this recipe in scenarios where it's important to preserve the integrity of the information being worked on without requiring user intervention. This need is typically found when handling large documents or forms, or in environments with unreliable connectivity between your client application and the server. Saving resources with lazy component instantiation In large applications, instantiating all application objects when the page loads—those that are initially needed and those that might or might not be needed later—will cause a number of unused objects to be sitting in the memory. Lazy instantiation reduces the amount of consumed resources by initially committing to memory only the configuration of the objects (and not the actual object instances) and deferring instance creation until render time. This recipe explains how lazy instantiation is done. You will build two panels that look identical. The items of the first panel are explicitly instantiated, and this is how the panel will look: This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 10 335 The second panel's items are instantiated at render time. This is a screenshot of the second panel: How to do it... 1. Create a function that animates the progress bars used in the panels: Ext.onReady(function() { var loadFn = function(btn, textItem, statusBar, pBar) { btn = Ext.getCmp(btn); btn.disable(); Ext.fly(textItem).update('Saving...'); Ext.getCmp(pBar).wait({ interval: 200, duration: 5000, increment: 15, fn: function() { btn.enable(); Ext.fly(textItem).update('Done'); } }); }; 2. Explicitly instantiate a progress bar, a button, and some toolbar items. These will be placed on the first panel: var pBar = new Ext.ProgressBar({ id: 'pBar1', width: 100 }); var button = new Ext.Button({ xtype: 'button', id: 'btn1', text: 'Save', width: '75', handler: loadFn.createCallback('btn1', 'statusTxt1','sBar1', 'pBar1') }); This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Patterns in Ext JS 336 var textItem = new Ext.Toolbar.TextItem({ text: '', id: 'statusTxt1' }); var fill = new Ext.Toolbar.Fill(); 3. Create the first panel. This panel contains the items that were explicitly instantiated: var pnl1 = new Ext.Panel({ title: 'Panel with items explicitly created', renderTo: 'pnl1', width: 400, height: 200, bodyStyle: 'padding:10px;', items: [button], bbar: { id: 'sBar1', items: [textItem, fill, pBar] } }); 4. Create the second panel. In contrast with the first panel, this panel's items are defined inline: var pnl2 = new Ext.Panel({ title: 'Panel with items whose creation is deferred', renderTo: 'pnl2', width: 400, height: 200, bodyStyle: 'padding:10px;', items: [{xtype: 'button', id: 'btn2', text: 'Save', width: '75', handler: loadFn.createCallback('btn2', 'statusTxt2','sBar2', 'pBar2')}], bbar: { id: 'sBar2', items: [{ xtype: 'tbtext', text: '', id: 'statusTxt2' }, '->', { xtype: 'progress', id:'pBar2',width: 100 } ] } }); }); This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 10 337 How it works... As opposed to the first panel, the second panel's components are defined inline as a set of configuration options that include the xtype property. The xtype is a symbolic name given to a class, so that it can be instantiated by the framework's component manager. For example, if you look at the second progress bar, what is committed to memory is a small configuration object: { xtype: 'progress', id:'pBar2',width: 100 } When the progress bar needs to be rendered, the ComponentMgr class will execute the create(…) function: create: function(config, defaultType) { return new types[config.xtype || defaultType](config); } As you can probably tell, the code in create(…) is equivalent to: return new Ext.ProgressBar(config); And this effectively produces the progress bar. But notice that the process occurs when the progress bar is about to be rendered, and not at the page-load time. In short, lazy instantiation is achieved by using the component manager's ability to produce an instance of a class just when it is needed, based on a config object that includes the xtype, or type identifier, of the class. Extending a class This recipe explains how to use a class extension to create a custom field. As an example, you will build a custom TextField component that provides visual feedback indicating that it is a required field. The feedback will consist of a small glyph on the upper-left corner of the field's input elements, as seen in the following screenshot: How to do it... 1. Define the namespace that you will use: Ext.ns('Ext.ux.form'); This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Patterns in Ext JS 338 2. Define the custom field as an extension of ext.form.TextField and override the onRender() method: Ext.ux.form.RequiredTextField = Ext.extend(Ext.form.TextField, { allowBlank: false, onRender: function(ct, position) { 3. Within onRender() Call the onRender() method of the base class: Ext.ux.form.RequiredTextField.superclass.onRender.call(this, ct, position); 4. Moving on to the custom behavior, calculate the glyph's location: glyphX = this.el.dom.clientLeft + this.el.dom.offsetLeft + 1; glyphY = this.el.dom.clientTop + this.el.dom.offsetTop + 1; } }); 5. Create the glyph's element and insert it in the DOM tree: theGlyph = ''; Ext.DomHelper.insertAfter(this.el, theGlyph); 6. Register the custom field with the framework: Ext.reg('ux.requiredtextfield', Ext.ux.form.RequiredTextField); 7. Now create a panel that uses the custom field: Ext.onReady(function() { var commentForm = new Ext.FormPanel({ frame: true, renderTo:document.body, title: 'What is your name', bodyStyle: 'padding:5px', width: 450, layout: 'form', items: [ { xtype: 'ux.requiredtextfield', fieldLabel: 'Name', name: 'name', anchor: '98%', allowBlank:false }, { xtype: 'textfield', This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 10 339 fieldLabel: 'Email', name: 'email', anchor: '98%', vtype:'email' } ], buttons: [{ text: 'Send' }, { text: 'Cancel' }] }); }); How it works... To indicate that RequiredTextField modifies the behavior of the TextField class, you'll have to call Ext.extend(). The first modification consists of setting the allowBlank property to false in order to signal that the field is required. The second and most important change is overriding the onRender() function. Within onRender(), invoking the parent class's onRender() function guarantees that the field is rendered to the screen before the glyph is added: Ext.ux.form.RequiredTextField.superclass.onRender.call(this, ct, position); Up to this point, the rendered field would look just like the native TextField, as seen in the following screenshot: With the field rendered, you can use its location to determine the coordinates where the glyph will be inserted: glyphX = this.el.dom.clientLeft + this.el.dom.offsetLeft + 1; glyphY = this.el.dom.clientTop + this.el.dom.offsetTop + 1; This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Patterns in Ext JS 340 The glyph is an image inserted after the text box element of the field. It is absolute-positioned in such a way that it appears near the upper-left corner of the text box. theGlyph = ''; Ext.DomHelper.insertAfter(this.el, theGlyph); There's more... The best practice for extension consists of calling Ext.extend(), and passing the original class and configuration object as arguments. This is a simple template that you can use in your applications: MyGridPanel = Ext.extend(Ext.grid.GridPanel, { initComponent: function() { // Your preprocessing here. // Call the parent class' initComponent. MyGridPanel.superclass.initComponent.call(this); // Your postprocessing here. Event handling and other // things that require the object to exist. }, yourMethod: function() { // Your method's body } }); A more detailed extension template is available in the Extending a Ext2 Class tutorial at http://extjs.com/learn/Tutorial:Extending_Ext2_Class. See also... ff The Augmenting a class with a plugin recipe (to be seen later in this chapter), explains how to change the behavior of the Ext JS components by using plugins ff The Building preconfigured classes recipe (to be seen later in this chapter) illustrates a useful pattern that consists of creating classes with built-in configuration options This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 10 341 Using code modules to achieve encapsulation This recipe demonstrates how to achieve encapsulation by organizing your code into modules where you can have private, privileged, and public members. You will be building two modules, each with a private Action instance and a public run() method. Calling a module's public run() method will cause the module to build and render a panel: The first module's private Action, which is accessible only by the code within the module, will be assigned to the panel's Import toolbar button: This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Patterns in Ext JS 342 The second module's private Action will be assigned to the panel's Filter button: How to do it... 1. Create a namespace for your example and define the first module: Ext.namespace('BigSystem'); BigSystem.module1 = function() { // "action" is a private member of "module1". var action = new Ext.Action({ iconCls: 'icon-data', text: 'Import', handler: function() { Ext.Msg.alert('Action executed', 'This is an action form inside module1'); } }); return { // "run" is a public member of "module1". run: function() { var panel = new Ext.Panel({ title: 'Module 1', width: 450, height: 200, bodyStyle: 'padding:10px;', tbar: [ action ], This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 10 343 renderTo:'mod1' }); } } } 2. Define the second module: BigSystem.module2 = function() { // "action" is a private member of "module2". var action = new Ext.Action({ iconCls: 'icon-filter', text: 'Filter', handler: function() { Ext.Msg.alert('Action executed', 'This is an action form inside module2'); } }); return { // "run" is a public member of "module2". run: function() { var panel = new Ext.Panel({ title: 'Module 2', width: 450, height: 200, bodyStyle: 'padding:10px;', items: [ new Ext.Button(action) ], renderTo:'mod2' }); } } } 3. Build an instance of each module and call each module's public member run(): Ext.onReady(function() { var m1 = new BigSystem.module1(); m1.run(); var m2 = new BigSystem.module2(); m2.run(); }); This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Patterns in Ext JS 344 How it works... A module is simply a function that returns an object. The returned object's properties (that is; functions or variables) become the public members of the module, and members that do not appear inside the return statement are private to the module. For example, the following module defines a count private variable, an increaseCount() private function, and the public functions init(), getCount(), and checkCount(): module = function() { var count = 0; // Private Variable for the module. Not accessible from outside var increaseCount = function() { // Private function. Not accessible from outside count++; } return { init: function() { // Privileged method. Can be called from outside // Here comes the initialisation code }, getCount: function() { // Privileged method. Can be called from outside return count; }, checkCount: function() { increaseCount(); if (this.getCount() > 10) alert("count is greater than 10"); } } } In this recipe, you have taken advantage of the mechanism explained above to make the run() function of each module accessible to any calling code, whereas the Action instances can only be called by code within the module that defines each of them. There's more... Use the module pattern to expose services that can be consumed by other modules and to keep the inner workings of your modules hidden and free from coupling with external code. See also... ff The Building preconfigured classes recipe (to be seen later in this chapter) illustrates a useful pattern that consists of creating classes with built-in configuration options This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 10 345 Implementing a publish/subscribe mechanism with relayEvents() In this recipe, you'll learn how to implement a publish/subscribe mechanism, where a component listens to events generated from within other components. A very simple console listens to events that occur inside two panels, as seen in the following screenshot: How to do it... 1. Create a namespace for your code and define a utility function that will be used by your event-handling routines: Ext.ns('Dashboard'); function WriteToConsole(console, msg) { var prevValue = console.getValue(); if (null == prevValue) prevValue = ''; console.setValue(prevValue + msg); } 2. Create a Portlet class as an extension of Ext.Panel: Dashboard.Portlet = Ext.extend(Ext.Panel, { anchor: '100%', frame: true, collapsible: true, draggable: true, 3. Add some tools to the Portlet class and attach the click handlers to each tool. The click handlers will fire the custom events defined in the next step: tools: [{ id: 'gear', This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Patterns in Ext JS 346 handler: function(e, toolEl, panel) { panel.fireEvent('gearClick', panel); } }, { id: 'help', handler: function(e, toolEl, panel) { panel.fireEvent('helpClick', panel); } }], 4. Define the custom events of this portlet. These are the events invoked by the click handlers above: this.addEvents('gearClick', 'helpClick'); } }); 5. Now, it's time to use the Portlet class. Create a couple of portlets: Ext.onReady(function() { var portlet1 = new Dashboard.Portlet({ title: 'Portlet 1', renderTo: 'p1', id: 'portlet1' }) var portlet2 = new Dashboard.Portlet({ title: 'Portlet 2', renderTo: 'p2', id: 'portlet2' }) 6. A TextArea will serve as a logging console: var console = new Ext.form.TextArea({ renderTo: 'console', width: 350, grow: true, growMin: 200, growMax: 300 }) 7. Use relayEvents() to make the console listen to the portlet's custom events. The console is the subscriber in your publisher/subscriber model: console.relayEvents(portlet1, ['gearClick', 'helpClick']); console.relayEvents(portlet2, ['gearClick', 'helpClick']); This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 10 347 8. In the console, define the handlers for the portlet's gearClick and helpClick custom events: console.on('gearClick', function(panel) { var msg = 'Gear clicked. Portlet Id = ' + panel.id + '\n'; WriteToConsole(console,msg); }); console.on('helpClick', function(panel) { var msg = 'Help clicked. Portlet Id = ' + panel.id + '\n'; WriteToConsole(console,msg); }); }); How it works... After defining the portlets and the console, use the relayEvents(…) function to ensure that the console will subscribe to the gearClick and helpClick events of the portlets: console.relayEvents(portlet1, ['gearClick', 'helpClick']); console.relayEvents(portlet2, ['gearClick', 'helpClick']); The relayEvents()function relays selected events from a specified Observable instance—in your case, the two portlets—as if the events were fired by its caller; in this case, the TextArea that serves as login console. With the subscription created, you just need to add the code to handle the events that interest us: console.on('gearClick', function(panel) { var msg = 'Gear clicked. Portlet Id = ' + panel.id + '\n'; WriteToConsole(console,msg); }); console.on('helpClick', function(panel) { var msg = 'Help clicked. Portlet Id = ' + panel.id + '\n'; WriteToConsole(console,msg); }); }); There's more... Use this approach to achieve a loosely coupled architecture where components subscribe only to the events that are of interest to them, without requiring the event generators to have any knowledge about who the subscribers are. This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Patterns in Ext JS 348 This decoupling of publishers and subscribers allows for greater scalability, since publishers and subscribers can be added, changed, or removed with minimal impact on the rest of the system. Augmenting a class with a plugin A way to change the behavior of a class is by using plugins. This recipe explains the mechanics of a plugin through an example plugin that provides visual feedback that indicates when a TextField or TextArea is a required field. The feedback will consist of a small glyph on the upper-left corner of the field's input elements, as seen in the following screenshot: How to do it... 1. Define a couple of namespaces to encapsulate your plugin class: Ext.ns('Ext.ux', 'Ext.ux.plugins'); 2. Define the RequiredFieldGlyph plugin: Ext.ux.plugins.RequiredFieldGlyph = { init: function(field) { var thisXType = field.getXType(); 3. Inside the plugin's init() method, check that the host field is either a TextArea or TextField instance. Your plugin will only modify these types: // You only want to modify textfield fields. if ('textarea' != thisXType && 'textfield' != thisXType) return; onRender = field.onRender; 4. Define a rendering function that will execute right after the host field's onRender() function: Ext.apply(field, { allowBlank: false, onRender: onRender.createSequence(function(ct, position) { This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 10 349 5. Inside the rendering function, calculate the position of the glyph and add the glyph to the field: if ('textarea' == thisXType) { if (Ext.isGecko) { glyphX = this.el.dom.clientLeft + this. el.dom.offsetLeft + 2; glyphY = this.el.dom.clientTop + this. el.dom.offsetTop + 2; } else { glyphX = this.el.dom.clientLeft + this. el.dom.offsetLeft + 1; glyphY = this.el.dom.clientTop + this. el.dom.offsetTop + 1; } } else { // For textfield. glyphX = this.el.dom.clientLeft + this.el.dom. offsetLeft + 1; glyphY = this.el.dom.clientTop + this.el.dom. offsetTop + 1; } theGlyph = ''; Ext.DomHelper.insertAfter(this.el, theGlyph); }) // onRender }); } } 6. Create a form panel with a TextField that uses your plugin: Ext.onReady(function() { var commentForm = new Ext.FormPanel({ frame: true, renderTo:Ext.getBody(), title: 'What is your name?', bodyStyle: 'padding:5px', width: 450, layout: 'form', items: [ { xtype: 'textfield', This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Patterns in Ext JS 350 fieldLabel: 'Name', name: 'name', anchor: '98%', allowBlank:false, plugins: [Ext.ux.plugins.RequiredFieldGlyph] }, { xtype: 'textfield', fieldLabel: 'Email', name: 'email', anchor: '98%', vtype:'email' } ], buttons: [{ text: 'Send' }, { text: 'Cancel' }] }); }); How it works... A plugin is an object that provides custom functionality for its host component. The only requirement for a plugin is that it contains an init() method that accepts a reference of the Ext.Component type. This reference is assumed to be of the plugin's host. If any plugins are available when a component is created, the component will call the init() method on each plugin and pass a reference to itself. Each plugin can then call methods or respond to events on its host, as needed, to provide its functionality. Implementing this logic, your plugin's init() function will be called during the host component's initialization—an ideal time to add your modifications. Since this plugin targets TextField and TextArea instances, the first step is to check whether or not the host field is a TextField and TextArea instance. This preserves the behavior of other fields if they are accidentally assigned the plugin: // You only want to modify textfield and textarea fields. if ('textarea' != thisXType && 'textfield' != thisXType) return; This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 10 351 Your plugin will define a new onRender() function, which uses Function. createSequence() to execute code immediately after the host's native onRender() function is called. The plugin's onRender() function calculates the location where the glyph will be inserted, based on the input field's position. Observe that the calculation for text areas takes into account browser differences: if ('textarea' == thisXType) { if (Ext.isGecko) { glyphX = this.el.dom.clientLeft + this.el.dom. offsetLeft + 2; glyphY = this.el.dom.clientTop + this.el.dom. offsetTop + 2; } else { glyphX = this.el.dom.clientLeft + this.el.dom. offsetLeft + 1; glyphY = this.el.dom.clientTop + this.el.dom. offsetTop + 1; } } else { // For textfield. glyphX = this.el.dom.clientLeft + this.el.dom. offsetLeft + 1; glyphY = this.el.dom.clientTop + this.el.dom. offsetTop + 1; } The glyph is made up of an absolute-positioned image that is inserted with Ext.DomHelper. InsertAfter(): theGlyph = ''; Ext.DomHelper.insertAfter(this.el, theGlyph); Absolute positioning makes the glyph appear near the upper-left corner of the input field, as seen in the following screenshot: This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Patterns in Ext JS 352 See also... ff The Extending a class recipe (seen earlier in this chapter) explains how to use class extension to create custom fields ff The next recipe, Building preconfigured classes, illustrates a useful pattern that consists of creating classes with built-in configuration options Building preconfigured classes Preconfigured classes are extensions of the Ext classes with built-in configuration options. This pattern allows you to set up your components to perform specific tasks, without having to pass configuration options through the configuration object. In this recipe, you will build two portlets; instances of the Panel class, preconfigured with fixed dimensions, a choice of tools, and event handlers. The two portlets can be seen below: How to do it... 1. Create a naming container for your code and define the Portlet class as an extension of the Ext.Panel class: Ext.ns('Dashboard'); Dashboard.Portlet = Ext.extend(Ext.Panel, { 2. Preconfigure the portlet. All instances of this class will use these settings by default: initComponent: function() { Ext.apply(this, { anchor: '100%', frame: true, collapsible: true, draggable: true, height: 150, // Default height and widths. width: 200, tools: [{ This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 10 353 id: 'gear', handler: function() { // Put common handler code here. Ext.Msg.alert('Message', 'The Settings tool was clicked.'); } }, { id: 'close', handler: function(e, target, panel) { // Put common handler code here. Ext.Msg.alert('Message', 'The Close tool was clicked.'); } }] }); 3. Call the base class initComponent() method: Dashboard.Portlet.superclass.initComponent.apply(this, arguments); 4. Now, create a couple of instances of your custom class. You can combine the preconfigured properties with values passed at runtime: Ext.onReady(function() { var p1 = new Dashboard.Portlet({ title: 'Portlet 1', renderTo: 'p1', html:'A preconfigured panel' }) var p2 = new Dashboard.Portlet({ title: 'Portlet 2', renderTo: 'p2', html: 'Another preconfigured panel' }) }); How it works... You can create preconfigured classes by extending a base class. In this case, some properties, tools, and event handlers are added to the Panel class when Ext.extend(…) is called. This is a template you can use for your preconfigured classes: MyPreConfigClass = Ext.extend([BASE CLASS], { initComponent: function() { This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Patterns in Ext JS 354 Ext.apply(this, { // Preconfigure. Options here cannot be changed from outside. }); MyPreConfigClass.superclass.initComponent.apply(this, arguments); // Install event handlers on rendered components here. } }); See also... ff The Extending a class recipe (seen earlier in this chapter) explains how to use a class extension to create custom fields ff The previous recipe, Augmenting a Class with a plugin, explains how to change the behavior of the Ext JS components by using plugins Implementing state preservation with cookies In this recipe, you will learn to use the state management features in Ext JS for preserving the selected tab in a TabPanel across page loads. Specifically, the selected tab's ID will be saved to and read from a cookie. A notification of a State Change event will be displayed upon selecting a tab, as shown in the following screenshot: As seen in the following screenshot, after a page reload, the TabPanel recovers its previous state: This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Chapter 10 355 How to do it... 1. Create a naming container for your code: Ext.ns('Example'); 2. Define the StatefulTabPanel class as an extension of Ext.TabPanel and configure the state management options: Example.StatefulTabPanel = Ext.extend(Ext.TabPanel, { stateEvents: ['tabchange'], getState: function() { return { tab: this.getActiveTab().id} }, applyState: function(state) { this.setActiveTab(state.tab); } }); 3. Define the state provider, a CookieProvider instance: Ext.onReady(function() { var cp = new Ext.state.CookieProvider({ path: "/", expires: new Date(new Date().getTime() + TIME_OFFSET) }); Ext.state.Manager.setProvider(cp); 4. A handler for the state provider's statechange event will alert us when a change of state occurs: cp.on('statechange', function(provider, key, value) { Ext.Msg.alert('State Change', 'key: ' + key + ', value: ' + value); }); 5. Lastly, create the StatefulTabPanel instance: var tab = new Example.StatefulTabPanel({ stateful: true, stateId:'myTabPanel', renderTo: document.body, activeTab: 0, width: 500, height: 250, plain: true, defaults: { autoScroll: true }, items: [{ title: 'First Tab', bodyStyle:'padding:5px', html: 'Switch tabs and refresh' This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Patterns in Ext JS 356 }, { title: 'Second Tab', bodyStyle: 'padding:5px', html:'Remember me?' }, { title: 'Third Tab', bodyStyle: 'padding:5px', html:'I remember' }] }); }); How it works... Start by creating an extension of the TabPanel class with the state management features: stateEvents, getState(), and applyState(). As you want to save the index of the selected tab, use tabchange as the event that triggers the saving of the state. While getState() returns the ID of the selected tab, applyState (state) takes care of selecting the tab that is supplied in the state argument. State-aware components check the Ext.state.Manager singleton—the global state manager—for state information. The global state manager, in turn, needs a state provider in order to route state information to and from the location where it is persisted; for example, a cookie or a database. This is why the next step in the example consists of creating an Ext.state.CookieProvider instance that is supplied to the state manager: var cp = new Ext.state.CookieProvider({ path: "/", expires: new Date(new Date().getTime() + TIME_OFFSET) }); Ext.state.Manager.setProvider(cp); As an alternative, you can pass a custom state provider directly to the TabPanel. This is something you can do with any state-aware component. The last step is creating a handler for the state provider's statechange event, so that you can have visual confirmation when the event occurs. There's more... Note that you can specify an array of events as the value for the stateEvents config option. These events can be of any type supported by the component, including browser and custom events. This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Index Symbols (key, object) function 28 A absolute layout 39 accordion layout 43 add function 31 add(object) function 31 addRow() function 251 AdobeAs Air detecting, Ext.isAir used 9 anchor layout 41 array finding, in objects 19 items, removing 19 autocomplete implementing, combo box used 137 autocomplete implementation actors combo box, creating 138 combo box used 137 container for combo box, creating 138 data store, creating 138 preparing for 137 working 139 automatic field validation disabling 97, 98 auto-refreshing chart creating 319-321 autosave 331 B border layout about 55 nested border layout 56 browsers detection steps 8 working 10 BufferView class 233 ButtonGroup component working, with 282-284 buttons placing, in toolbar 280, 281 C card layout 46 change picture form using, for file uploads 124-127 chart component about 316 using, for displaying multiple data series 316- 318 Chrome browser detecting, Ext.isChrome used 8 class augmenting, with plugin 348-351 extending 337-340 clear function 31 clear() function 31 clickHandler() function 287 close menu adding, to tabs 259-261 This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 358 code modules using, to achieve encapsulation 340-344 column chart setting up, for displaying local data 308, 309 setting up, for displaying remote data 310, 311 column layout 50 columns, grid changing, at runtime 207-211 combo box about 131 cascading 144-146 data store, creating 133 defining 133 form, creating 133 items look, changing 148 local data, creating 132 local data, working with 134 populating, with data 132 remote data, displaying 135 type-ahead feature 140 using with local data 132, 133 with autocomplete 137 combo box items handling, paging used 152-154 combo box items look changing, templates used 148-152 movies data store, creating 149 styles, creating 148 template, defining 150 components positioning, in multiple columns 49, 50 components proportions maintaining, anchor layout used 41 maintaining, while resizing containers 40 container items laying out, CSS-style absolute layout used 38, 39 createCallback() function 287 custom column layout about 57 building 57-61 features 57 custom JavaScript classes building 32-34 classes interface, implementing 32 custom look, for status bar items creating 293, 294 custom tip displaying, slider component used 322-324 D data grouping, with live group summaries 175-179 data, edited in grid batch uploads, performing 202-206 uploading, automatically 197-201 data fields disabled dates, setting up 156-158 data paging implementing 172, 173 data previews creating 179-182 data store, grid changing 207-211 date ranges selecting 159-161 date range selector 159 dates formatting 23 formatting steps 24 manipulating 23 manipulating steps 23, 24 parsing 23 parsing steps 24 validating, relational field validation used 104-106 DOM elements retrieving 10 DOM queries examples 13 running 13 drag-and-drop using, between grids 246-251 drag-and-drop, from tree to panel about 274 process 275, 276 working 276 This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 359 drag-and-drop support about 271 process 272, 273 working 273 DropZone defining 76 E each(fn, scope) function 31 email address validating 100 encapsulation achieving, code modules used 340-344 Ext.get(element) function 11 Ext.override(originalClass, overrides) function 29 Ext JS array, finding in objects 19 array items, removing 19 browsers, detecting 8 custom JavaScript classes, building 31 dates, formatting 23, 24 dates, manipulating 23, 24 dates, parsing 23, 24 DOM elements, retrieving 10 DOM nodes, retrieving 10 DOM queries, running 13 Ext JS classes, features adding 28 JavaScript objects, extending 27 JSON, decoding 14 JSON, encoding 14 naming conflicts, preventing 26 numbers, cheking for range 22 object type, determining 19 platforms, detecting 8 scoping variables, preventing 26 strings, manipulating 20, 22 Ext JS classes features, adding 28-30 Ext JS components acquiring reference, to 11, 12 Ext JS DataView 179 Ext JS library absolute layout 39 accordion layout 43 anchor layout 41 border layout 55 card layout 43 column layout 50 component layout 37 fit layout 49 table layout 51, 53 F Firefox browser detecting 8 fit layout 49 form data loading, from server 111-118 form values autosaving 331, 334 friendlier forms building, text hints used 128, 129 functionality sharing, action class 328-331 G Gecko rendering engine detecting 8 Globally recognized avatars. See Gravatars grid cells tooltips, adding 242 grid control creating, server-side sorting used 169-171 grid panel data changing, cell editors used 192-196 grid panel, with expandable rows creating 183-185 grid rows selecting, checkboxes used 186-189 grid view adding, to tree panel 269 H HTML select element converting into Ext combo box 143, 144 This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 360 I insert(index, object) function 31 insertPortletRequested event handling 82 Internet Explorer detecting, Ext.isIE6 used 8 detecting, Ext.isIE7 used 8 detecting, Ext.isIE8 used 8 detecting, Ext.isIE used 8 item(index) function 31 items stacking, with accordion layout 41-43 J JavaScript objects extending, in Ext JS way 27, 28 JSON about 14 array of colors, encoding 15 decoding 14, 15 encoding 14, 15 JSON data, generated by server displaying 166, 167 L large recordsets displaying, with buffered grid 231-233 lazy instantiation 334 line chart setting up 304, 305 setting up, for displaying local data 304 setting up, for displaying remote data 306, 307 Linux detecting, Ext.isLinux used 9 list interface add function 31 add(object) function 31 clear function 31 clear() function 31 each(fn, scope) function 31 insert(index, object) function 31 item(index) function 31 removeAt(index) function 31 remove function 31 remove(object) function 31 ListView class about 234 using 234, 235 working 236 Loader class 297 loadFn function 292 local data displaying, column chart used 308, 309 displaying, line chart used 304, 305 displaying, pie chart used 312, 313 M Mac OS detecting, Ext.isMac used 9 master-detail page interface building, tree panel component used 266- 268 master-details view creating, with combo box and grid 222-226 creating, with grid and form 218-222 creating, with grid and panel component 214- 217 creating, with two grids 227-230 maximum length allowed setting 91-93 menu items about 287 checkbox menu 287 checkbox menu, creating 288 color menu, defining 289 data menu, defining 288 main menu, creating 289 pick a color 288 pick a date 288 radio options 287 menus placing, in toolbar 285-287 minimum length allowed setting 91-93 MixedCollection class 28 modern application layout creating, with collapsible regions 53, 54 Mozilla browser detecting 8 multicolumn tree panel This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 361 loader, defining 270 styles, adding 269 multiple data series displaying, chart component used 316-318 N naming conflicts preventing 26 preventing steps 26 notifyDrop() functions 251 numbers checking for range 22 O object type determining 19 Opera detecting, Ext.isOpera used 9 P panel creating, for taking whole browser window 48, 49 passwords confirming 104-106 pie chart using, for displaying local data 312, 313 using, for displaying remote data 314-316 platforms detecting 8, 9 Portal class instance, creating 81 PortalColumn class creating 74 portal implementation working 83-85 portals 72 portlet adding, to catalog 72-82 removing 73 Portlet class creating 74 portletRemoved event handling 82 PortletsCatalog class instance, creating 81 PortletsCatalog component creating 79, 80 preconfigured classes building 352-354 progress bar customizing 299-301 embedding, in status bar 291, 292 using, for indicating your application busy 295, 296 using, for reporting progress updates 297, 298 PropertyGrid class about 245 using 245, 246 working 246 publish/subscribe mechanism implementing, with relayEvents() 345-347 R remote data displaying, column chart used 310, 311 displaying, line chart used 306, 307 displaying, pie chart used 314-316 remote data, displaying combo box, creating 135 combo box, used 135, 137 data store, defining 135 form, creating 136 removeAt(index) function 31 remove function 31 remove(object) function 31 required fields specifying, in form 88, 89 reset() function 296 resources saving, with lazy component instantiation 334-337 RowEditor plugin 236 rows editing, with RowEditor plugin 236-241 rows, grid panel numbering 190-192 This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 362 S Safari detecting, Ext.isSafari2 used 9 scoping variables preventing 26 preventing steps 26 slider component configuring, for displaying custom tip 322- 324 enhancing, with custom tick marks 324-326 start() method 297 state preservation implementing, with cookies 354-356 status bar items, custom look creating 293 strings manipulation about 20 steps 20, 21 working 22 T tab activation handling 254, 255 tabbed GUI 46 tabbed look using 46 tab data loading, with Ajax 255-257 table layout about 51, 53 employing 51, 52 TabPanel enhancing, with TabCloseMenu plugin 259- 261 enhancing, with TabScrollerMenu plugin 261- 263 TabPanel class 47 tabs adding, dynamically 257, 258 creating 46 three-panel application layout details panel 62 master panel 62 navigation panel 62 panel, reconfiguring 62- 67 with, single line code 61 working 68-72 toolbar buttons, placing 280 menus, placing 285 tooltips adding, to grid cells 242-244 tree nodes populating, with server-side data 264, 265 type-ahead feature, combo box about 140 data store, creating 141 functions 142 local data, creating 140 U URL validating 101, 102 URL data decoding 16 encoding 16 V validation errors display location display locationchanging 94-96 validation strategy rounding up, with server-side validation 108, 109 W wait(…) function 296 Windows detecting, Ext.isWindows used 9 Wizard style UI about 44 card layout, using 46 wizard cards, creating 44 X XML data serving, to form 119-123 XML data, sent by server displaying 164-166 This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Thank you for buying Ext JS 3.0 Cookbook Packt Open Source Project Royalties When we sell a book written on an Open Source project, we pay a royalty directly to that project. Therefore by purchasing Ext JS 3.0 Cookbook, Packt will have given some of the money received to the Ext JS project. In the long term, we see ourselves and you—customers and readers of our books—as part of the Open Source ecosystem, providing sustainable revenue for the projects we publish on. Our aim at Packt is to establish publishing royalties as an essential part of the service and support a business model that sustains Open Source. If you're working with an Open Source project that you would like us to publish on, and subsequently pay royalties to, please get in touch with us. Writing for Packt We welcome all inquiries from people who are interested in authoring. Book proposals should be sent to authors@packtpub.com. If your book idea is still at an early stage and you would like to discuss it first before writing a formal book proposal, contact us; one of our commissioning editors will get in touch with you. We're not just looking for published authors; if you have strong technical skills but no writing experience, our experienced editors can help you develop a writing career, or simply get some additional reward for your expertise. About Packt Publishing Packt, pronounced 'packed', published its first book "Mastering phpMyAdmin for Effective MySQL Management" in April 2004 and subsequently continued to specialize in publishing highly focused books on specific technologies and solutions. Our books and publications share the experiences of your fellow IT professionals in adapting and customizing today's systems, applications, and frameworks. Our solution-based books give you the knowledge and power to customize the software and technologies you're using to get the job done. Packt books are more specific and less general than the IT books you have seen in the past. Our unique business model allows us to bring you more focused information, giving you more of what you need to know, and less of what you don't. Packt is a modern, yet unique publishing company, which focuses on producing quality, cutting-edge books for communities of developers, administrators, and newbies alike. For more information, please visit our website: www.PacktPub.com. This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 Learning Ext JS ISBN: 978-1-847195-14-2 Paperback: 324 pages Build dynamic, desktop-style user interfaces for your data-driven web applications 1. Learn to build consistent, attractive web interfaces with the framework components. 2. Integrate your existing data and web services with Ext JS data support. 3. Enhance your JavaScript skills by using Ext's DOM and AJAX helpers. 4. Extend Ext JS through custom components. Learning jQuery 1.3 ISBN: 978-1-847196-70-5 Paperback: 444 pages Better Interaction Design and Web Development with Simple JavaScript Techniques 1. An introduction to jQuery that requires minimal programming experience 2. Detailed solutions to specific client-side problems 3. For web designers to create interactive elements for their designs 4. For developers to create the best user interface for their web applications 5. Packed with great examples, code, and clear explanations Please check www.PacktPub.com for information on our titles This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606 jQuery UI 1.6: The User Interface Library for jQuery ISBN: 978-1-847195-12-8 Paperback: 440 pages Build highly interactive web applications with ready-to-use widgets of the jQuery user interface library 1. Packed with examples and clear explanations to easily design elegant and powerful front-end interfaces for your web applications 2. Organize your interfaces with reusable widgets like accordions, date pickers, dialogs, sliders, tabs, and more 3. Enhance the interactivity of your pages by making elements drag and droppable, sortable, selectable, and resizable jQuery Reference Guide ISBN: 978-1-847193-81-0 Paperback: 268 pages A Comprehensive Exploration of the Popular JavaScript Library 1. Organized menu to every method, function, and selector in the jQuery library 2. Quickly look up features of the jQuery library 3. Understand the anatomy of a jQuery script 4. Extend jQuery's built-in capabilities with plug-ins, and even write your own Please check www.PacktPub.com for information on our titles This material is copyright and is licensed for the sole use by JEROME RAYMOND on 30th October 2009 125 Louis ST, , So. Hackensack, , 07606