ExtJS in Action+


Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 2 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 MEAP Edition Manning Early Access Program Copyright 2009 Manning Publications For more information on this and other Manning titles go to www.manning.com Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 3 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Part 1: Introduction to Ext JS 1 A framework apart 2 Back to the basics 3 Events, Components, and Containers Part 2: Ext components 4 Panels, TabPanels, and Windows 5. Organizing Components 6 Ext takes Form 7 The venerable GridPanel 8 The EditorGridPanel 9 Taking root with Trees 10 Toolbars and Menus 11 Drag and drop with Widgets Part 4: Building a configurable composite component 12 Developing object-oriented code with Ext 13 Building a composite widget 14 Applying advance UI techniques Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 1 A framework apart Envision a scenario where we are tasked to develop an application with many of the typical UI widgets such as menus, tabs, data grids, dynamic forms and styled pop-up windows. We want something that allows us to programmatically control the position of widgets, which means it has to have layout controls. We also desire detailed and organized centralized documentation to ease our learning curve with the framework. Lastly, this application needs to look mature and go into beta phase as quickly as possible, which means we don’t have lots of time to toy with HTML and CSS. Before entering the first line of code for the prototype, you need to decide on an approach to developing the front-end. What are your choices? We do some recon on the common popular libraries on the market and quickly learn that all of them can manipulate the DOM but only two of them have a mature UI library, YUI and Ext JS. At a first glance of YUI, we might get the sense that we need not look any further. We toy with the examples and notice that they look mature but not exactly professional quality, which means we need to modify CSS. No way. Next, we look at the documentation at http://developer.yahoo.com/yui/docs. It’s centralized and extremely technically accurate, but it is far from user friendly. Look at all of the scrolling required to locate a method or class. There are even classes that are cut off because the left navigation pane is too small. What about Ext JS? Surely it has to better -- right? 1.1 An easier way to get the job done Out of the proverbial box, you’re provided a rich set of widgets. These coupled with the Layout management tools give you full control to organize and manipulate the UI as requirements dictate. Most widgets are highly customizable, affording you the option to enable, disable features, override, and use custom extensions and plug-ins. One example of Licensed to Alison Tyler Download at Boykma.Com 2 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 a web application that takes full advantage of Ext JS is Conjoon. Below is a screenshot of Conjoon in action. Figure 1.1 Conjoon is an open source personal information manager that is a great example of a web application that leverages the framework to manage a UI that leverages 100% of the browser’s viewport. You can download it at http://conjoon.org/ . Conjoon is an open source Personal Information Manager and can be considered the epitome of web applications developed with Ext JS. It uses just about all of the framework’s native UI widgets and demonstrates how well the framework can integrate with custom extensions such as the YouTubePlayer, LiveGrid and ToastWindow. You can get a copy of Conjoon by visiting http://conjoon.org. What if you’re looking to just add some Ext flavor to your existing web application or site? 1.1.1 Integration with existing sites Any combination of widgets can be embedded inside an existing web page and or site with relative ease, giving your users the best of both worlds. An example of a public facing site that contains Ext JS is located at the Dow Jones Indexes site, which you can visit via http://djindexes.com. ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 3 Figure 1.2 The Dow Jones indexes web site, http://djindexes.com, is one example of Ext JS embedded in a traditional Web 1.0 site. This Dow Jones indexes web page gives its visitors rich interactive views of data by utilizing a few of the Ext widgets such as the TabPanel, DataGrid, and Window (not shown). Their visitors can easily customize the view of the stocks by selecting a row in the main data grid, which invokes an AJAX request to the server, resulting in an updated graph below the grid. The graph view can be modified as well by clicking on one of the time period buttons below it. OK, we now know that Ext JS can be leveraged both to build entire applications or can be integrated into existing ones. However, we still have not satisfied the requirement of API documentation. How does Ext solve this? 1.1.2 Rich API documentation When opening the API documentation for the first time (Figure 1.2), you get a sense that this unlike any other to date. Unlike competing frameworks, the Ext API Documentation leverages its own framework to present a clean and easy to use documentation tool that uses AJAX to present us with the documentation. ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Licensed to Alison Tyler Download at Boykma.Com 4 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 We’ll explore all of the features of the API and talk about some of the components used in this documentation tool. Figure 1.3 The Ext API documentation contains a wealth of information and is a great resource to learn more about components and widgets. It contains most of what you need to know about the API including constructor configuration options, methods, events, properties, component hierarchy and more. The API documentation tool is choc-full of gooey GUI goodness and uses six of the most commonly used widgets, which include the Viewport, Tree and Tab Panels, Toolbar with an embedded TextField and Toolbar buttons and the Center, which contains the TabPanel. Before we move on, I’m sure you’re wondering what all of these are and do. Lets take a moment to discuss them. Looking from the outside in, the Viewport is a class that leverages all of the browser viewing space to provide an Ext managed canvas for UI widgets. It is known as the foundation from which an entire Ext JS application is built upon. The “BorderLayout” layout manager is used, which divides the Viewport (or any Container) up into three regions; North (top) for the page title, link and Toolbar, West (left) which contains the TreePanel and the Center region, which contains the TabPanel to display documentation. ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 5 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 The Toolbar is a class that provides a means to present commonly used UI components such as Buttons and menus, but can also contain, as in this case, form fields. I like to think of the Toolbar as the common File-Edit-View menus that you see in popular Operating Systems and desktop applications. The TreePanel is a widget that displays hierarchical data visually in the form of a tree much like Windows Explorer displays your hard drive’s folders. The Tab Panel provides a means to have multiple documents or components on the canvas but only allows for one to be active. Using the API is a cinch. To view a document, click the class node on the tree. This will invoke an AJAX request to fetch the documentation for the desired class. Each document for the classes is an HTML fragments (not a full HTML page). With the TextField in the Toolbar, you can easily filter out the class tree with just a few strokes of the keyboard. If you’re connected to the Internet, API documentation itself can be searched in the “API Home” tab. OK, so the documentation is really good. What about rapid application development? Can Ext accelerate our development cycles? 1.1.3 Rapid development with prebuilt widgets With Ext JS can help you jump from conception to prototype because it offers many of the UI elements that you would require already built and ready for integration. Having these UI widgets already prebuilt means that much time is saved from having to engineer them. In many cases, the UI controls are highly customizable and can be modified or customized to your application needs. 1.1.4 It works with Prototype, jQuery, YUI and inside of Air Even though we have discussed how Ext JS stands out from Prototype, jQuery and YUI, Ext JS can easily be configured to leverage these frameworks as a base, which means if you’re using these other libraries, you don’t have to give them up to enjoy Ext JS UI goodness. Even though we won’t cover development of Ext JS applications in Air, it is worth noting that the framework has a complete set of utility classes to help with the integration of Air. These utility classes include items like Sound management, a video player panel access to the desktop clipboard. Even though we won’t go into Air in this book, we will cover many of the very important parts of the framework from which you’ll need to know when developing with Ext in Air. Before we talk any more about Ext JS, I think we should set the playing field and discuss what types of skills are necessary to utilize the framework. 1.2 What you need to know While being an expert in web application development is not required to develop with Ext JS, there are some core competencies that developers should have before attempting to write code with the framework. Licensed to Alison Tyler Download at Boykma.Com 6 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 The first of these skills is a basic understanding of Hyper Text Markup Language (HTML) and Cascading Style Sheets (CSS). It is important to have some experience with these technologies because Ext JS, like any other JavaScript UI library, uses HTML and CSS to build its UI controls and widgets. While its widgets may look like and mimic typical modern Operating System controls, it all boils down to HTML and CSS in the browser. Because JavaScript is the ‘glue’ that ties AJAX together, a solid foundation in JavaScript programming is suggested. Again, you need not be an expert, but you should have a solid grasp on key concepts such as arrays, reference and scope. It is a plus if you are very well familiar with Object Oriented JavaScript fundamentals such as objects, classes, and prototypal inheritance. If you are extremely new to JavaScript you’re in luck. JavaScript has existed since nearly the dawn of the Internet. An excellent place to start is W3Schools.com, which offers a lot of free online tutorials and even has sandboxes for you to play with JavaScript online. You can visit them here: http://w3schools.com/JS/ If you are required to develop code for the server side, you’re going to need a server side solution for Ext JS to interact with as well as a way to store data. Most developers choose databases, but some choose direct file system storage as well as well. Naturally, the range of solutions available is quite large. For this book, we won’t focus on a specific language. Instead, we’re going to use online resources at http://extjsinaction.com, where I’ve done the server side work for us, this way all you have to focus on is learning the Ext JS. We’ll begin our exploration of Ext JS with a bird’s eye view of the framework, where we’ll learn about the categories of functionality. 1.3 A bird’s eye view of the framework Ext JS is a framework that not only provides UI widgets, but also contains a host of other features. These can be divided up into six major areas of purpose; Core, UI Components, Data Services, Drag and Drop and general utilities. Below is an illustration of the six areas of purpose. Figure 1.4 The six areas of purpose for Ext Classes; Core, UI, Remoting, Data Services, Drag and Drop and Utilities. ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 7 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 The first feature set is the Ext JS Base and Core, which comprises of many of the basic features such as AJAX Communication, DOM Manipulation and event management. Everything else is dependent on the Core of the framework, but the Core is not dependant on anything else. The UI Components contains all of the Widgets, gadgets and gizmos that interface with the user. Web Remoting is a means for JavaScript to easily remotely execute method calls that are defined and exposed on the serve, which is commonly known as a Remote Procedure Call or RPC. It is extremely convenient for development environments where you would like to expose your server side methods to the client and not worry about all of the fuss of AJAX method management. The Data Services section takes care of all of your data needs, which includes fetching, parsing and loading information into Stores. With the Ext Data Services classes, you can read Array, XML and JSON (JavaScript Object Notation), which is a data format that is quickly becoming the standard for client to server communication. Stores typically feed UI components. Drag and Drop is like a mini framework inside of Ext, where you can apply Drag and Drop capabilities to an Ext Component or any HTML element on the page. It includes all of the necessary members to manage the entire gamut of all Drag and Drop operations. Drag and Drop is a complex topic. We’ll spend an entire chapter on this subject alone in chapter 11. The Utilities section is composed of cool utility classes that help you get some of your routine tasks easier. An example would be util.Format, which allows you to format or transform data really easily. Another neat utility is the CSS singleton, which allows you to create, update, swap and remove style sheets as well as request the browser to update its rule cache. Now that we have a general understanding of the framework’s major areas of functionality, lets take some time to look at some of the more commonly used UI widgets that Ext has to offer. 1.3.1 Containers and Layouts at a glance Even though we will cover these topics in greater detail later in this book, I think we should spend a little bit of time talking about Containers and Layouts. The term Container and Layout are used extensively throughout this book and I want to make sure you have at least a basic understanding of them before we continue. Afterwards, we’ll begin our view into visual components of the UI library. Containers are widgets that can manage one or more child items. A child item is generally any widget or component that is managed by a Container or parent, thus the parent-child paradigm. We’ve already seen this in action in the API. The TabPanel is a Container that manages one or more child items, which are presented as tabs. Please remember this term, as we will be using it a lot when we start to learn more about how to use the UI portion of the framework. Licensed to Alison Tyler Download at Boykma.Com 8 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 Layouts work with Containers to visually organize the child items on a canvas, which is commonly known as a Container’s content body. Ext JS has a total of 12 layouts from which to choose, which we will go into great detail in chapter 4 and learn the ins and outs of each layout. Now that we have a high level understanding Containers and Layouts, lets start to look at some containers in action. In the following illustration, we see two descendants of Container, Panel and Window each engaged in parent-child relationships. Figure 1.5 Here, we see two parent Containers, Panel (left) and Window(right), managing child items, which includes nested children. In Figure 1.5, we see a Panel (left) and a Window (right) managing two child items each. “Child Panel 1” of each of the parent containers simply contain HTML, while the children with the title “Child Panel 2” manage one child panel each using the no-frills ContainerLayout, which is the base for all other layouts. This parent-child relationship is the crux of all of the UI management of Ext JS and will be reinforced and referenced repeatedly throughout this book. We just learned that Containers manage child items and use layouts to visually organize them. Being that we now have these important concepts down, we will move on to see and discuss other Containers in action. 1.3.2 More Containers in Action We just saw Panel and Window being used, when we learned about Containers. Here are some other commonly used descendants of Container. ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 9 Figure 1.6 Commonly used descendants of Container, the FormPanel, TabPanel, FieldSet and Quick Tip and the layouts used to compose this UI Panel. We will build this in Chapter 05, where we learn about forms. In Figure 1.6, we see The FormPanel, TabPanel, FieldSet and QuickTip widgets. The FormPanel essentially works with the BasicForm class to wrap fields and other child items with a form element. Looking at this from a parent-child perspective, the FormPanel is being used to manage three child items, two instances of FieldSet and one instance of TabPanel. Fieldsets are generally used to display fields in a form much like a typical fieldset tag does in general HTML. These two fieldsets are managing child items, which are text fields. The TabPanel here is managing three direct children (tabs), which its first tab (“Phone Numbers”) is managing many children, which are text fields. Lastly, the QuickTip, which is used to display helpful text when the mouse is hovering over an element, is being displayed, but is not a child of any Ext Component. We will actually spend some time building this complex UI in Chapter 5, where we learn more about form panels. For now, lets move on to learn about what data presentation widgets the framework has to offer. 1.3.3 Grids, DataView and ListView We’ve already learned that the Data Services portion of the framework is responsible for the loading and parsing of data. The main consumers of the data for which the data Store manages, are the GridPanel, DataView and its descendant, the ListView. Below is a screenshot of the Ext GridPanel in action. ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Licensed to Alison Tyler Download at Boykma.Com 10 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 Figure 1.7 The Data Grid as see in the “Buffered Grid” Example in the Ext SDK. The GridPanel is a descendant of Panel, and presents data in a table-like format, but its functionality extends far beyond that of a traditional table, offering sortable, resizable and movable column headers and row or cell selection models. It can be customized to look and feel as you desire and can be coupled with a PagingToolbar to allow large data sets to be shown in pages. It also has descendants like the EditorGridPanel, which allow you to create a grid where users can edit data on the grid itself, leveraging any of the Ext form data input widgets. The Grid is great for displaying data, but is somewhat computationally expensive. This is because of the many DOM elements that each row contains. Ext offers some lighter-weight ways to display data from a store, which include the DataView and its descendant, the ListView as seen below. Figure 1.8 The DataView (left) and ListView (right) as seen in the Ext SDK Examples. The DataView is a class that consumes data from a Store and “paints” it on screen using what are called Templates and provides a simple selection model. An Ext Template is a ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 11 DOM utility that allows you to create a template, with placeholders for data elements, which can be filled in by individual records in a store and stamped out on the DOM. In Figure 1.8, the DataView (left) is displaying data from a store, which includes references to images. It uses a pre-defined template, which contains image tags, where the individual records are leveraged to fill in the location of the images. The Template then stamps out an image tag for each individual record, resulting in a nice collage of photos. The DataView can be used to display anything in a data store. The ListView, as picture in Figure 1.8 (right), is displaying data from a store in a grid- like fashion, but is a subclass of DataView. It is a great way to display data in a tabular format without the weight of the GridPanel. That is, if you’re not looking to use some of the GridPanel features like sortable and resizable columns. Grids and DataViews are essential tools for painting data on screen, but they do have one major limitation. They can only show lists of records. That is, they cannot display hierarchical data. This is where the TreePanel comes to the rescue. 1.3.4 Make like a TreePanel and leaf The TreePanel widget is an exception to the list of UI widgets that consume data, where it does not consume data from a data Store. Instead, it consumes hierarchical data via the usage of the data.Tree class. Below is an example of an Ext TreePanel widget. 1.9 An Ext JS Tree, which is an example from the Ext SDK. In Figure 1.9, the TreePanel is being used to display the parent-child data inside of the directory of an installation of the framework. The TreePanel can leverage what is known as a TreeLoader to fetch data remotely via AJAX or can be configured to use data stored on the browser. It can also be configured to use Drag and Drop and has its own selection model. We’ve already seen TextFields in a form when we discussed Containers a short while ago. Next, we’ll look at some of the other input fields that the framework has to offer. ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Licensed to Alison Tyler Download at Boykma.Com 12 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 1.3.5 Form Input fields Ext has a palette of eight input fields from which to use. They range from simple TextFields, as we’ve seen before, to complex fields such as the ComboBox and the HTML Editor. Below is an illustration of the available Ext form field widgets that come out of the box. Figure 1.10 All of the out of the box form elements displayed in an encapsulating Window. As we can see in Figure 1.10, some of the form input fields look like stylized versions of their native HTML counterparts. The similarities end here however. With the Ext Form Fields, there are much more than meets the eye! Each of the Ext fields (minus the HTML Editor) includes a suite of utilities to perform actions like get and set values, mark the field as invalid, reset and perform validations against the field. You can apply custom validation to the field via regex or custom validation methods, allowing you to have complete control over the data being entered into the form. The fields can validate data as it’s being entered, providing live feedaback to the user. The TextField and TextArea can be considered extensions of their generic HTML counterparts. The NumberField, however, is a descendant of the TextField and is a convenience class, which utilizes regular expressions to ensure users can only enter numbers. With the NumberField, you can configure decimal precision as well as specify the range of the value entered. The ComboBox and TimeField require a little extra time relative to the other fields, so we’re going to skip these two for now and jump back to them in a bit. Like the TextField, the Radio and Checkbox input fields are extensions of the plain old Radio and Checkbox, but include all of the Ext Element management goodness have convenience classes to assist with the creation of Checkbox and Radio groups with automatic layout management. Below is an example of Checkbox and Radio groups. ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 13 Figure 1.11 An example of the Checkbox and Radio Group convenience classes in action with automatic layouts. Figure 1.11 is just a small sample of how the Ext Checkbox and RadioGroups can be configured with complex layouts. To see the entire set of examples, you can visit http://extjs.com/deploy/dev/examples/form/check-radio.html. The HTML Editor is a WYSIWIG (What You See Is What You Get) is like the TextArea on steroids. The HTML editor leverages existing browser HTML editing capabilities and can be considered somewhat of a black sheep when it comes to fields. Because of its inherent complexities (using IFrames, etc), it does not have a lot of the abilities like validation and cannot be marked as invalid. There is much more to discuss about this field, which we’re going to save for Chapter 5. But for now, lets circle back to the ComboBox and its descendant the TimeField. The ComboBox is easily the most complex and configurable form input field. It can mimic traditional option dropdown boxes or can be configured to use remote data sets via the data Store. It can be configured to auto-complete text, known as “type-ahead” in Ext, that is entered by the user and perform remote or local filtering of data. It can also be configured to use your own instance of an Ext Template to display a custom list in the dropdown area, known as the ListBox. The following figure is an example of a custom ComboBox in action. Figure 1.12 A Custom ComboBox, which includes an integrated Paging Toolbar, as seen in the downloadable Ext Examples. ©Manning Publications Co. Please post comments or corrections to the Au http://www.manning-sandbox.com/forum.jspa?forumID= thor Online forum: 525 Licensed to Alison Tyler Download at Boykma.Com 14 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 In Figure 1.12, a custom combo box is being leveraged to search the Ext forums. The CombBox here shows information like the post title, date, author and a snippet of the post in the list box. Because some of the data set ranges are so large, it is configured to use a paging toolbar, allowing users to page through the resulting data. Because the ComboBox is so configurable, we could also include image references to the resulting data set, which can be applied to the resulting rendered data. Here we are, on the last stop of our UI Tour. Here, we’re going to take a peek at some of the other UI components that work anywhere. 1.3.6 Other widgets Here, we find a bunch of UI controls that kind of stand out, where they are not real major components, but play supporting roles in a grander scheme of a UI. Lets look at the illustration below and discuss what these items are and do. Figure 1.13 A collection of miscellaneous UI widgets and controls. The Toolbar is a widget, which allows you to place just about any widget that fits in it. Generally developers will place menus and buttons. In discussing the custom ComboBox, We’ve seen the Toolbar’s descendant, the PagingToolbar. Panels and any just about descendant thereof can use these toolbars on the top or bottom of their content body. The button widget is essentially a stylized version of the generic HTML button, which can include icons as well as text. Menus can be displayed by a click of a button on the toolbar or shown on demand at any X and Y coordinate on screen. While they typically contain menu Items, such as the items shown and the Color Menu Item, they can contain widgets such as ComboBoxes. The MessageBox is a utility class, which allows us to easily provide feedback to the user without having to craft up an instance of Ext.Window. In this illustration, it’s using an animated ProgressBar to display the status of an upload to the user. Lastly, the Slider is a widget that leverages drag and drop to allow users to change a value by repositioning the knob. They can be styled with images and CSS to create your ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 15 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 own custom look. The movement of the knob can be restricted so it only moves in increments. In this example, the slider has a ToolTip above the knob, displaying the value of the slider as the user moves it. In addition to the default horizontal orientation, Sliders can be configured to be vertical. We’ve recently learned how Ext can help us get the job done through a large palette of widgets. We learned that we could elect to use Ext to build an application without touching an ounce of HTML or we can integrate it with existing sites. We also performed a top-down view of the framework, which included a UI tour. All of the stuff discussed thus far was already in existence for Ext 2.0. Lets take a moment to discuss what’s new in Ext 3.0. 1.4 New Ext 3.0 Goodies Ext 2.0 introduced some radical changes, which made upgrading from 1.0 was rather difficult. This was mainly because of the introduction to the more modern layout manager and new robust component hierarchy, which broke most of the user developed Ext 1.x code. Thankfully, due to the great engineering of Ext 2.0, the migration from Ext 2.0 to 3.0 is much easier. While the additions to Ext 3.0 are not as drastic, there is excitement about this latest release and is certainly worthwhile discussing some of the additions. 1.4.1 Ext does Remoting with Direct Web Remoting is a means for JavaScript to easily execute method calls that are defined on the server side. It is extremely convenient for development environments where you would like to expose your server side methods to the client and not have to worry about all of the muck with AJAX connection handling. Ext.Direct takes care of this for us by managing AJAX requests and acts as a bridge between the client side JavaScript and any server side language. This functionality has great advantages to it, which include method management in a single location as well as unification of methods. Having this technology inside of the framework will ensure consistency across the consumers such as the data classes. Speaking of which, see how the addition of Ext.Direct brings new classes to the data classes. 1.4.2 Data Class The Ext.data class is the nerve center for all data handling in the framework. The data classes manage every aspect of data management including fetching, reading and parsing data to create Records, which are loaded into a Store. With the addition of Direct, Ext has added additional convenience Data classes, DirectProxy and DirectStore, to facilitate ease of integration with your web remoting needs. Now that we’ve reviewed some of the behind-the-scenes changes and additions to the framework, take a gander at some of the UI widgets that have been added. 1.4.3 Meet the new Layouts A total of five new layouts make their way into the Ext 3.0 framework, which include Menu, Toolbar, VBox and HBox. The MenuLayout is an abstraction of the way menu Licensed to Alison Tyler Download at Boykma.Com 16 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 items were organized with in the past but now provide a cleaner way to manage menu items and is not to be used directly. Likewise, the ToolbarLayout adds important features to the Toolbars like overflow management as illustrated below. 1.14 The new ToolbarLayout is responsible for detecting the toolbar’s size and creating menu stubs if menu items were to overflow. As we see in Figure 1.14, the toolbar layout will detect the overflow of toolbar items in a toolbar and will automatically create a menu, which lists and contains the rest of your items. The changes to the MenuLayout help support this effort. Just like the Menu layout, the ToolbarLayout is not to be used directly by the end developer. The last two layouts, the VBox and HBox, are great additions to the end-developer usable layouts. The HBox layout allows you to divide up a Container’s content body into horizontal slices, while the VBox layout does the same, except vertically as illustrated below. 1.15 An example of the VBox and HBox layouts in action. Many experienced ext developers would think that the HBox looks like the column layout in practice. While it does provide similar function it extends way beyond the capabilities, where it will vertically and horizontally stretch children based on weights, which is known as flex. These two layouts usher in a whole new era of layout capabilities within the framework. ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 17 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 In addition to the layout changes, the ColumnModel, a supporting class for the GridPanel has undergone some fundamental changes. Lets learn about some of these changes and why they are going to help us out in our development efforts. 1.4.4 Grid ColumnModel Enhancements The Grid ColumnModel is a class that models how the columns are organized, sized and displayed for the GridPanel widget. Prior to Ext 3.0, individual columns were generally configured a list of configuration objects in an array, which is consumed by ColumnModel. For each column in the ColumnModel, you could enhance or modify the way the data is displayed by creating a custom renderer, which is a method that is called for each data point for that column and returns the desired formatted data or HTML. This means that if you wanted, lets say, a date to be formatted or displayed a certain way, you had to configure, which many people found themselves doing a lot. In this release, the ColumnModel changed somewhat to make our jobs that much easier. The individual Column has been abstracted from the ColumnModel and an entirely new class was created called the grid.Column. From here, many convenience Column subclasses have been created, which includes NumberColumn, BooleanColumn, TemplateColumn and DateColumn each of which allow you to display your data, as you desire. To display formatted Dates, you could use the DateColumn and simply specify a format from which the dates are to be displayed. The Template Column is another welcomed change as it allows you to leverage XTemplates and display them in a GridPanel, which are convenience methods to create and stamp out HTML fragments based on data. To use any of these Column subclasses, no custom renderers are required, though you can if you wish. A lot of applications require data to be displayed in tabular format. While the GridPanel is a great solution, it is computationally expensive for generic data displays that require little or no user interaction. This is where ListView, an extension of one of DataView, comes to the rescue. 1.4.5 ListView, like GridPanel on a diet With this new addition to the framework, you can now display more data in a grid-like format without sacrificing performance. Below is an illustration of the ListView in action. While it looks similar to the GridPanel, in order to achieve better performance, we sacrifice features such these include drag and drop column reordering as well as keyboard navigation. This is mainly because the ListView does not have any of the elaborate feature-rich supporting classes, such as the ColumnModel we discussed just a moment ago. Licensed to Alison Tyler Download at Boykma.Com 18 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 Figure 1.16 The new Ext.ListView class, which is like a very light weight DataGrid. Using the ListView to display your data will ensure that you have faster response from DOM manipulation, but remember that it does not have all of the features of the GridPanel. Choosing which one to use will depend on your application requirements. Ext has always been excellent at displaying textual data on screen, but lacked a graphical means of data representation. Lets take a quick look at how this has changed with Ext 3.0. 1.4.6 Charts come to Ext One of thing that was missing in the 2.0 version of Ext JS, was charts. Thankfully the development team listened to the community and has introduced them for version 3.0. These are a great addition, which adhere to the Ext Layout models. Figure 1.17 These charts now bring rich graphical views of trend data to the framework. It is important to note that this new widget requires Flash. Use of these charts, however, requires Adobe Flash to be installed for the browser you’re using, which can be downloaded at http://get.adobe.com/flashplayer/. In addition to the Line and Column charts shown above, the framework has Bar, Pie and Cartesian charts available for your data visualization needs. ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 19 We now have all just about all of the things we need to lay down some code. Before we start our path to becoming Ext JS Ninjas, we must first download and setup the framework and have a discussion about development. 1.5 Downloading and Configuring Even though downloading Ext is a simple process, configuring a page to include Ext JS is not as simple as referencing a single file in the HTML. In addition to configuration, we’ll learn about the folder hierarchy and what they are and do. The first thing we need to do is get the darn source code. Visit the following link: http://extjs.com/products/extjs/download.php The downloaded file will be an ext-3.0.zip, which weighs in over 6MB in size. We’ll explain why this is so large in a bit. Now, extract the file in a place where you serve JavaScript. In order to leverage AJAX, you’re going to need a web server. I typically use Apache, which is cross-platform, but IIS for Windows will do. Let’s take a peek at what just got extracted. 1.5.1 Looking at the SDK contents If you’re like me, you probably checked the size of the files extracted from the downloaded SDK zip file. If your jaw dropped, feel free to pick it back up. Yes, over 30MB is rather large for a JavaScript framework. The reason it’s that big will be revealed in just a bit. For now, look at what got extracted. Figure 1.18 A view of the Ext JS SDK contents. ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Licensed to Alison Tyler Download at Boykma.Com 20 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Looking at the contents of the SDK, we see a lot of stuff. The reason there are so man folders and files is because the downloadable package contains a few copies of the entire code base and CSS. It is this way because you get the freedom to build or use Ext JS anyway you see fit. The following table explains what each of the folders are and what they do. adapter Contains the ext-base.js, which is the base ext library, which is used for an all-Ext JS setup. It also contains necessary adapters and supported versions of Prototype, jQuery or YUI libraries if you want to use any of those as a base. air Contains all of the required libraries to integrate Ext JS with Adobe Air. build Has each of the files that comprises of the Ext JS framework with all of the unnecessary whitespace removed. It’s essentially a deflated version of the source directory. docs This is where the full API documentation is located. examples All of the example source code, which is great source to learn by example (no pun intended). resources Contains all of the necessary images and CSS required to use the UI widgets. It contains all of the CSS files broken down by widget and a ext-all.css, which is a concatenation of all of the framework’s CSS. source Here is where the entire framework sits, which includes all of the comments. ext-all.js This is a minified version of the framework, which is intended to be used for production applications. ext-core.js If you just wanted to use the core library, which means none of the UI controls, ext-core.js is what you’d set your page up with. *debug.js Anything with ‘debug’ in the name means that the comments are stripped to reduce file space, but the necessary indentation is remained intact. When we develop, we’re going to use ext-all-debug.js. While there are quite a few files and folders in the distribution, you only need a few to get the framework running in your browser. I think now is a good time to talk about how to setup Ext JS for use. 1.5.2 Setting up Ext for the first time In order to get Ext JS running in your browser, you need to include at least two required JavaScript files and at least one CSS File. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 21 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Above, we’re linking the three core files for an all-Ext configuration. The first thing we do is link to the ext-all.css file, which is all of the CSS for the framework in one file. Next, we include ext-base.js, which is the underlying base of the framework. Lastly, we include ext-all-debug.js, which we’ll use for development. When setting up your initial page, be sure to replace extjs in your path with wherever you plan to reference the framework on your development web server. What if you wanted to use any of the other base frameworks? How do we include those? 1.5.3 Configuring Ext JS for use with others In order for Ext JS to work with the previously mentioned frameworks, an adapter must be loaded after the external base framework. The adapter essentially maps ext-base methods to the external library of choice, which is absolutely crucial. The following patterns can be used to use any of the other three base frameworks in addition to Ext JS. First up, prototype: As you can see, this is just like the generic Ext JS setup with two additional JS files. The prototype and scriptaculous libraries take the place of ext-base and the ext-prototype- adapter.js maps the external library methods to Ext. Note, that we are still loading ext-all-debug.js. We’ll continue to do so for the other two cases. Next is jQuery: Licensed to Alison Tyler Download at Boykma.Com 22 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 As you can see, it’s similar to the prototype setup. The YUI configuration will look similar, with the difference being that we’re loading different base library and adapter files. Lastly, YUI: And there you have it, the recipes for an Ext-all setup and all of the other three-supported base JS Libraries. Moving forward, we’ll be using the Ext-all configuration, but you are free to use whichever base library you wish. Before we move on to coding, we need to talk about one final crucial step to setting up Ext, and that’s configuring the reference for s.gif. 1.5.4 Configuring BLANK_IMAGE_URL The configuration of the Ext.BLANK_IMAGE_URL is one of those steps that developers often overlook and may lead to issues with the way the UI renders for your application. The BLANK_IMAGE_URL property specifies a location for the 1x1 pixel clear s.gif graphic, which is an essential piece of the UI portion of the framework and is used to create items like icons. Out of the box, BLANK_IMAGE_URL points to http://extjs.com/s.gif. For most users, that is OK, but if you’re in an area where extjs.com is not accessible, this will be come an issue. This also becomes a problem if you are using SSL, where sis being requested via HTTP instead of HTTPS, which will invoke security warnings in browsers. In order to prevent these issues, simply set Ext.BLANK_IMAGE_URL to s.gif locally on your web server as such: Ext.BLANK_IMAGE_URL = '/you.path.to.extjs/resources/images/default/s.gif'; Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 23 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 It is recommended that you set this parameter immediately after the inclusion of the Ext JS files or immediately before your application code is parsed. I’ll show you an example of where to place it when we take Ext JS for a test drive. If you’re like me, after all of this discussion, you’re probably itching to start using Ext JS. So, what are you waiting for? Let’s go ahead and dive in. 1.6 Take it for a test drive For this exercise, we’re going to create an Ext JS Window and we’ll use AJAX to request an HTML file for presentation in the content body of the Window. We’ll start by creating the main HTML file from which we’ll source all of our JavaScript files. Listing 1.1 Creating our helloWorld.html 1) Including ext-all.css 2) Ensure ext-base.js and ext-all-debug.js are loaded 3) A JavaScript block to configure BLANK_IMAGE_URL 4) The inclusion of our soon to be created helloWorld.js file. In Listing 1.1, I’ve included the HTML markup for a typical Ext-only setup, which includes the concatenated CSS file, ext-all-css{1} and the two required JavaScript files, ext-base.js and ext-all-debug.js{2} Next, we set create a JavaScript block{3}, where we set the ever- important Ext.BLANK_IMAGE_URL property. Lastly, we include our soon to be created helloWorld.js file{4}. If you have not noticed it, we’re using /extjs as the absolute path to our framework code. Be sure to change it if your path is different. Next, we’ll create our helloWorld.js file, which will contain our main JavaScript code. Listing 1.2 Creating helloWorld.js function buildWindow() { var win = new Ext.Window({ // 1 Licensed to Alison Tyler Download at Boykma.Com 24 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 id : 'myWindow', title : 'My first Ext JS Window', width : 300, height : 150, layout : 'fit', autoLoad : { // 2 url : 'sayHi.html', scripts : true } }); win.show(); // 3 } Ext.onReady(buildWindow); // 4 1) Instantiation of a new instance of Ext.Window. 2) Specifying an autoLoad configuration object 3) Calling upon our window to show(). 4) Passing buildWindow to Ext.onReady. In listing 1.2, we create the function buildWindow, which will be passed to Ext.onReady for later execution. Inside of this buildWindow, we create a new instance of Ext.Window, and setup a reference to it called win{1}. We pass a single configuration object to Ext.Window, which has all of the properties required to configure the instance we’re instantiating. In the configuration object, we specify an id of ‘mywindow’, which will be used in the future to look up the window using the Ext.getCmp convenience method. We then specify a title of the window, which will appear as blue text on the top most portion of the window, which is known as the title bar. Next, we specify height and width of the window. We then go on to set the layout as ‘fit’, which ensures that what ever is in the ContentBody of our Window is stretched to the dimensions of the ContentBody. We then move on to specify an autoLoad configuration object{2}, which will instruct the window to automatically fetch an HTML fragment (specified via the url property) and execute an JavaScript if found (specified via scripts : true). This ends the configuration object for our instance of Ext.Window. Next, we call on win.show{3}, which renders our window. This is where we find the conclusion of the buildWindow Method. The last thing we do is call Ext.onReady{4} and pass the our method, buildWindow, as a reference. This ensures that buildWindow is executed at just the right time, which is when the DOM is fully built and before any images are fetched. Lets see how our window renders. Go ahead and request helloWorld.html in your browser. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 25 Figure 1.19 Our first Ext JS window attempting to load content via AJAX. If you coded everything properly, you’d see a window that looks very similar to the one in Figure 1.19 with a spinning icon next to the ‘Loading…’ text, which is known as a loading indicator. Why do we see this? Well, this is because we have not created sayHi.html, which we referenced in the url property of the autoLoad configuration object. Essentially, we instructed Ext to load something that wasn’t on the web server. Next, we’ll construct sayHi.html, where we’ll create an HTML fragment, which will include some JavaScript. Listing 1.3 Creating sayHi.html
Hello from the world of AJAX!
1) The “Hello world” DIV tag. 2) A method to highlight the body of the window. 3) Delay the execution of highlightWindow by one second. In Listing 1.3, we are creating an HTML fragment file, sayHi.html. It contains a div {1} from which we have our hello world message. After that, we have a script tag with some JavaScript, which will get executed after this fragment is loaded by the browser. In our code, we create a new function called higlightWindow{2}, which is going to be executed after a delay of one second. Inside that function, we perform a highlight effect on the content body of the window. The execution of highlightWindow is delayed by one second. Here is how this method works. We first start by creating a reference to the Window we created in our helloWorld.js file by using a utility method called Ext.getCmp, which looks up an Ext Component by id. When we created our window, we assigned it an id of ‘myWindow’, which is what we’re passing to Ext.getCmp. The reason this works is because all Components (widgets) get ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Licensed to Alison Tyler Download at Boykma.Com 26 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 registered with the ComponentMgr upon instantiation. Ext.getCmp is a way to retrieve a reference by id from any context within your application. After we get the reference of our Window, we create a reference, winBody, to its content body via the body property. We then call its highlight method, which will perform the highlight (fade from yellow to white) operation on the element. This is where we conclude the highlight Window method. The last thing we do in this JavaScript block is to call highlightWindow.defer and pass a value of 1000, which defers the execution of highlightWindow by one thousand milliseconds (or one second). If you’ve never heard of defer in the JavaScript language, that’s because we’re using an Ext-introduced method. Ext leverages JavaScript’s extensibility to add convenience methods to important core language classes, such as Array, Date, Function, Number, and String. This means every instance of any of those classes, have the new convenience methods. In this case, we’re using defer, which is an extension of Function. If you’re an old-timer, you’re probably asking “why not just use setTimeout”? The first reason is because of ease of use. Call .defer on any method and pass the length of time to defer its operation. That’s it. Other reasons to use it is because it allows us to control the scope from which the deferred method is being executed and pass custom parameters, which setTimeout lacks. OK, Here we’ve ended our HTML fragment, which can now be fetched by our Window. Refresh helloWorld.html and you should see something like what we have below. Figure 1.20 Our Ext JS Window loading the our HTML fragment (left) and the highlight effect performed on the Window’s content body(right). If you did everything correctly, your results should be exactly like the one in figure 1.20, where we see the content body being populated with the HTML fragment(left), and exactly one second later, the content body of the window highlights yellow(right). Pretty cool, huh? I’d like to suggest taking some time to modify the example and using the API to do things like changing the color of the highlight effect. Here’s a hint, look under Ext -> Fx for the list of Effects and their parameters. ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 27 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 1.7 Summary In this Introduction to Ext JS, we learned how it can be used to build robust web applications or can be integrated into existing websites. We also learned how it measures up when stacked against other popular frameworks on the market and how it is the only UI based framework to contain UI-centric support classes such as the Component, Container and Layout models. Remember that Ext JS can “ride” on top of jQuery, prototype and YUI. We explored many of the core UI widgets that the framework provides and learned that the number of prebuilt widgets helps rapid application development efforts. In doing that, we talked about some of the changes that Ext JS 3.0 has brought forth, such as Flash charts. Lastly, we discussed where to download and how to setup the framework with each individual base framework. We created a “Hello world” example of how to use an Ext JS window to retrieve an HTML fragment via AJAX with just a few simple lines of JavaScript. In the chapters to follow, we will explore how Ext works from the inside out. This knowledge will empower you to make the best decisions when building well-constructed UIs and better enable you to leverage the framework effectively. This will be a fun journey. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 2 Back to the basics When working on applications, I often think metaphorically, which helps me develop parallels for concepts in my mind. I like to compare of the timing of an application’s launch to that of the space shuttle’s launch, where timing can mean the difference between a successful launch and inevitable frustration. Knowing when to initialize your JavaScript is one of the most critical things to know when dealing with anything that manipulates the DOM. We’ll learn how to launch our JavaScript using the Ext to ensure our application code initializes at the right time on each browser. We can then begin our discussions on using Ext.Element to manipulate the DOM. As we all know, DOM manipulation is one of the tasks that web developers are required to code for most of the time. Whether it’s the addition or the removal of elements, I’m sure you’ve felt the pain of performing these tasks with the out of the box JavaScript methods. After all, DHTML has been at the very center of dynamic web pages for ages now. We will look at the heart of Ext, known as the Ext.Element class, which is a robust cross-browser DOM element management suite. We’ll learn to use Ext.Element to add and remove nodes from the DOM and see how it makes this task easier. After we get familiar with the Ext.Element class, we’ll learn how to use Templates to literally stamp out HTML fragments into the DOM. We’ll also dive deep into the use of the XTemplate, which descend from Template and learn how to use it to easily loop through data and inject behavior modification logic while we’re at it. This is going to be a fun chapter. Before we can begin coding, we must learn the proper way of launching our code. 2.1 Starting off the right way Since the early days, when most developers wanted to initialize their JavaScript, they typically would add an onLoad attribute to the body tag of the html page that is loading: Licensed to Alison Tyler Download at Boykma.Com 2 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 While this method of invoking JavaScript works, it’s not ideal for AJAX enabled Web 2.0 sites or applications because the onLoad code is generally fired at different times for different browsers. For instance, some browsers fire this when the DOM is ready and all content has been loaded and rendered by the browser. For modern Web 2.0, this is not Kosher as the code generally wants to start managing and manipulating DOM elements when the DOM is ready but before any images are loaded. This is where the right balance of timing and performance can be achieved. I like to call this the “sweet spot” in the page loading cycle. Just like many things the world of browser development, each browser generally has its own way of knowing when its DOM nodes can be manipulated. 2.1.1 Fire only when ready! There are native browser solutions for detecting that the DOM is ready, but like a lot of things, they are not implemented uniformly across each browser. For instance, Firefox and Opera fire the DOMContentLoaded event. Internet Explorer requires a script tag to be placed in the document with a defer attribute, which fires when its DOM is ready. Safari fires no event, but sets the document.readyState property to ‘complete’, so a loop must be executed to check for that property and fire off a custom event to tell our code that the DOM is ready. Boy, what a mess! 2.1.2 Let Ext JS pull the trigger Luckily, we have Ext.onReady, which solves the timing issues and serves as the base from which to launch your application specific code. Ext JS achieves Cross Browser Compatibility (CBC) with by detecting which browser the code is executing on and manages the detection of the DOM ready state, executing your code at just the right time. Ext.onReady is actually a reference to Ext.EventManager.onDocumentReady and accepts three parameters; the method to invoke, the scope from which to call the method and any options to pass to the method. The second parameter, scope, is used when you’re calling an initialization method that is requires execution within a specific scope. All of your Ext-based JavaScript code can be anywhere below (after) the inclusion of Ext JS script. This is very important because JavaScript files are requested and loaded synchronously. Trying to call any Ext methods before Ext is actually defined in the namespace will cause an exception and your code will fail to launch. Here is a simple example of using Ext.onReady to fire up an Ext Messagebox alert window. Ext.onReady(function() { Ext.MessageBox.alert('Hello', 'The DOM is ready!'); }); In the preceding example, we pass, what is known as, an “anonymous” method to Ext.onReady as the only parameter, which will be executed when the DOM is ready to be manipulated. Our anonymous method contains a line of code to invoke an Ext Messagebox as seen in the figure below. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 3 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 2.1 the result of our Ext.onReady call, an Ext.Messagebox window. In short, an anonymous method is any method, which has no variable reference or label. Ext.onReady registers our anonymous method, which is to be executed when the internal docReadyEvent event is fired. In short, an event is like a message that something has occurred. A listener is a method that is registered to be executed or “called” when that event occurs or “fires”. Ext fires this docReadyEvent event when it finds exactly the right time (remember the sweet spot) in the page loading cycle to execute our anonymous method and any other registered “listeners”. If the concept of events sounds a bit confusing, don’t be alarmed. Event management is a complex topic and we’ll cover it later, in chapter 3. I cannot stress the importance of using Ext.onReady enough. All of our example code (and eventually your application) code has to be launched this way. Moving forward, if Ext.onReady is not explicitly detailed in the examples, please assume that you must launch the code with it and wrap the example code in the following manner: Ext.onReady(function() { // … Some code here … }); Now that we are comfortable with using Ext.onReady to launch our code, we should spend some time learning about the Ext.Element class, which is known as the heart of the framework. This is one of those essential topics that is used everywhere in the framework where DOM manipulation occurs. 2.2 The Ext.Element All JavaScript based web applications revolve around a nucleus, which is the HTML Element. JavaScript’s access to the DOM nodes gives us the power and flexibility to perform any action against the DOM we wish. These could include adding, deleting, styling or changing the contents of any node in the document. The traditional method to reference a DOM node by ID is: var myDiv = document.getElementById('someDivId'); The getElementById method works very well to allow you to perform some basic tasks such as changing the innerHTML, style or assign a CSS class. But, what if you wanted to do more with the node, such as manage its events, apply a style on mouse click or replace a Licensed to Alison Tyler Download at Boykma.Com 4 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 single CSS class? You would have to manage all of your own code and constantly update to make sure your code is fully cross browser compatible. I honestly cannot think of another thing that I wouldn’t want to spend my time on than this. Thankfully Ext takes care of this for us. 2.2.1 The heart of the framework Let's turn our heads to the Ext.Element class, which is known to many in the Ext JS community, as the ‘heart of Ext JS’ as it plays a role in each and every UI widget in the framework and can be generally accessed by the getEl() method or the el property. The Ext.Element class is a full DOM element management suite, which includes a treasure chest of utilities, enabling the framework to literally work its “magic” on the DOM and provide the robust UI that we have come to enjoy. This toolset and all of its power is available to us, the end developers. Due to its design, its capabilities are not relegated to simple management to DOM elements, but rather perform complex tasks such as manage dimensions, alignments and coordinates with relative ease. You can also easily update an element via AJAX, manage child nodes, animate, full event management and so much more. 2.2.2 Using Ext.Element for the first time Use of the Ext.Element is extremely easy and makes some of the hardest tasks simple. In order to go on with exercising Ext.Element, we need to setup a base page. Setup a page where you include the Ext JavaScript and CSS, just like we discussed in chapter 1. Next, include the following CSS and HTML.
What we’re doing here is setting the stage for our examples by ensuring our target div tags have specific dimensions and a border so we can clearly see it on the page. We include one div with the id of ‘div1’, which we’ll use as a target. If you setup your page correctly, the stylized div should be clearly visible as illustrated blow. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 5 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 2.2 Our base page with our stylized div ready for some Ext element action. In Figure 2.2, we see our generic HTML box, which we’ll use to exercise the fundamental Ext.Element methods. NOTE All of our Ext.element example code will reference the same base page which we’ve just setup. If you’re interested in watching changes to the DOM occur live, I suggest using the multiline Firebug console inside of Firefox with these examples. If you are unfamiliar with Firefox and FireBug, please see appendix XX for instructions. Conversely, these examples can be placed inside generic script blocks. Just be sure to remember to use Ext.onReady(). According to the CSS any div with the class “myDiv” is set to 35 pixels high and 200 pixels wide and looks a bit odd. Let’s make that perfectly square by setting the height to 200 pixels. var myDiv1 = Ext.get('div1'); myDiv1.setHeight(200); The execution of the previous two lines is pretty important. The first line uses Ext.get, which we pass the string ‘div1’ and returns an instance of Ext.Element referenced by the variable myDiv1. Essentially, Ext.get uses document.getElementById and wraps it with the Ext element management methods. We leverage our newly referenced instance of Ext.Element, myDiv1, and call its setHeight method, passing it an inter value of 200, which grows the box to 200 pixels tall. Conversely, you can use its setWidth method to change the width of the element, but we’ll skip that and jump to something more fun. Licensed to Alison Tyler Download at Boykma.Com 6 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 “OK, it now looks like a perfectly square size. Whoop-dee-doo,” you’re saying? Well, let’s change dimensions again, instead we’ll use setSize. Let’s make the width and height 350 pixels. We’ll leverage the already created reference, myDiv1. myDiv1.setSize(350, 350, {duration: 1, easing:'bounceOut'}); What happens when you execute the line of code above? Did it animate and have a bouncing effect? Cool - huh? I did this so you wouldn’t fall asleep on me. OK, I’m just teasing. Essentially, the setSize method is the composite setHeight and setWidth. For this method, we passed the target width, height and an object with two properties, duration and easing. The third property, if defined, will make setSize animate the size transition of the element. Don’t care of animations, simply omit the third argument, the box will change its size within an instant, much like when we set the height. Setting dimensions is just a single facet of the many sides of element management with the Element class. Some of the Ext.Element’s greatest power comes from its ease of use for full CRUD of elements, which is Create Update and Delete. 2.2.3 Creating Child Nodes One of the great powers of JavaScript is to manipulate the DOM, which includes the creation of DOM nodes. There are many methods that JavaScript provides natively that provide you this power. ExtJS conveniently wraps a lot of these methods with the Ext.Element class. Let's have some fun creating child nodes. To create a child node, we’ll use Element’s createChild method: var myDiv1 = Ext.get('div1'); myDiv1.createChild('Child from a string'); The code above adds a string node to the innerHtml of our target div. What if we wanted to create an element? Easy as pie. myDiv1.createChild('
Element from a string
'); The usage of createChild above will append a child div with the string of “Element from a string” to the innerHtml of div1. Personally, I really don’t like to append children this way because I find the string representation of elements to be messy. Ext helps me with this problem by accepting a configuration object instead of a string. myDiv1.createChild({ tag : 'div', html : 'Child from a config object' }); Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 7 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Here, we’re creating a child element by using a confuration object. We specify the tag property as ‘div’, and the ‘html’ as a string. This technically does the same thing as the prior createChild implementation but can be considered cleaner and self-documenting. What if we wanted to inject nested tags? With the configuration object approach, we can easily get this done. myDiv1.createChild({ tag : 'div', id : 'nestedDiv', style : 'border: 1px dashed; padding: 5px;', children : { tag : 'div', html : '...a nested div', style : 'color: #EE0000; border: 1px solid' } }); In the above code, we’re creating one last child, which has an ID, a bit of styling applied and a child element, which is a div with some more styling. The following illustrates what the changes to the div look like. Figure 2.3 A composite of our element additions using myDiv1.createChild(). Licensed to Alison Tyler Download at Boykma.Com 8 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 In the preceding illustration, we see all of the additions to myDiv1 including the live DOM view from FireBug, showing us that we added a string node and three child divs, one of which has its own child div. If you want to inject a child at the top of the list, you would use the convenience method insertFirst. For instance: myDiv1.insertFirst({ tag : 'div', html : 'Child inserted as node 0 of myDiv1' }); Element.insertFirst will always insert a new element at position zero, even no child elements exist in the DOM structure. If you want to target the insertion of a child node at a specific index, the createChild method can take care of that task. All you need to do is pass it the reference of where to inject the newly created node. For instance: myDiv1.createChild({ tag : 'div', id : 'removeMeLater', html : 'Child inserted as node 2 of myDiv1' }, myDiv1.dom.childNodes[3]); In the code above, we’re passing two arguments to createChild. The first of which is the configuration object representation of the newly created DOM element and the second is the actual DOM reference of the target childNode from which createChild will use as the target to inject the newly created node. Please keep in mind the id that we’ve set for this newly created item, we’re going to use this in a bit. Notice how we’re using myDiv1.dom.childNodes? Ext.Element gives us the opportunity to leverage all of the generic browser element management goodness by means of the dom property. NOTE The Element.dom property is the exact same reference as what is returned by document.getElementById(). The following illustration shows what our inserted nodes look like both in the page view and the DOM hierarchy using the firebug DOM inspection tool. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 9 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 2.4 The results of our targeted DOM element insertions with creatChild() using an index and usage of insertFirst(). As you can see in the figure above, the node insertions functioned, as we wanted them to. We used insertFirst to inject a new node at the top of the list and createChild to inject a node above childNode 3. Remember to always count child nodes starting with the number 0 instead of 1. Adding is something that we do often as web developers. After all, this is part of what DHTML is all about. But, on the flip side, removing is equally as important to know. Let’s learn how we can remove some of the child elements using Ext.Element. 2.2.3 Removing Child Nodes Removing nodes could be considered much easier than adding. All you need to do locate the node with Ext and call its remove method. To exercise removal of child nodes, we are going to start with a clean and controlled slate. Please create a new page with the following HTML:
Child 1
Child 2
Child 3
Nest Child 1
Child 5
Licensed to Alison Tyler Download at Boykma.Com 10 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Examining the HTML above, we have a parent div with the ID of ‘div1’. It has five direct descendants. The first of which has the id of ‘child1’. The second and third children have no IDs, but have CSS classes of ‘child2’ and ‘child3’. The fourth child element has an ID of ‘child4’ and a CSS class of ‘sameClass’. Likewise, it has a direct child with an ID of “nestedChild1” and the exact same css class as its parent. The last child of div1 has no ID or CSS class. The reason we have all this stuff going on is because we’re going to start using what is known as “CSS selectors” as well as targeting directly on the IDs of the elements. In the examples where we added child nodes, we always referenced the parent div (id=’div1’) by wrapping it in an Ext.Element class and leveraging its create methods. To remove child nodes the approach is completely different as we need to target the node specifically that is to be removed. Using the new DOM structure above, we’re going to exercise a few ways of doing this. The first approach we’ll examine is removing a child node for an already wrapped DOM element. That is, we’ll create an instance of Ext.Element wrapping div1, then use it to find its first child node using what is known as a CSS selector. var myDiv1 = Ext.get('div1'); var firstChild = myDiv1.down('div:first-child'); firstChild.remove(); In the preceding example, we create a reference to div1 using Ext.get. We then create another reference, firstChild, to the first child using the Element.down method and pass a pseudo-class selector, which basically causes ext to query the DOM tree within the context of div1 for the first child, which is a div and wrap it within an instance of Ext.Element. The Element.down method essentially queries the first-level DOM nodes for any given Ext.Element. It so happens that the elment that is found is the one with the div id of “child1”. We then call firstChild.remove, which actually removes that node from the DOM. Here is how you could remove the last child from the list using selectors. var myDiv1 = Ext.get('div1'); var lastChild = myDiv1.down('div:last-child'); lastChild.remove(); The Example above works similarly to the one prior. The biggest difference being that we use the selector ‘div:last-child’, which locates the last childNode for div1 and wraps it in an instance of Ext.Element. After that, we call lastChild.remove and poof, it’s gone. NOTE CSS selectors are an extremely powerful way of querying the DOM for items. Ext JS supports the CSS3 selector specification. If you’re new to CSS selectors, I highly advise Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 11 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 visiting the following W3C page which has a plethora of information. http://www.w3.org/TR/2005/WD-css3-selectors-20051215/#selectors OK, but what about if we want to simply target an element by an ID? Well, we can just use Ext.get to do our dirty work for us. This time, we’ll create no reference and just use what is known as chaining to take care of the job. Ext.get('child4').remove(); Executing the code above removes the child node with the id of ‘child4’ and its child node. Always remember that removing a node with children will remove its child nodes. NOTE If you would like to read more about chaining, Dustin Diaz, an industry leading developer, has an excellent article written in his site at http://www.dustindiaz.com/javascript- chaining/ The last thing we’ll look at is using Ext.Element to perform an AJAX request to load remote HTML fragments from the server and inject it into the DOM. 2.2.4 Using AJAX with Ext.Element The Ext.Element class has the ability to perform an AJAX call to retrieve remote HTML fragments and inject those fragments into its innerHTML. To exercise this, we’ll need to first write an HTML snippet to load:
Hello there! This is an HTML fragment.
In the above HTML fragment, we have a simple div with an embedded script tag, which performs an Ext.getBody call and uses chaining to execute the results of that call to execute its highlight method. Ext.getBody is a convenience method to get a reference to the document.body wrapped by Ext.Element. Save this file as htmlFragment.html. Next, we’ll perform the load of this snippet. Ext.getBody().load({ url : 'htmlFragment.html', scripts : true }); Licensed to Alison Tyler Download at Boykma.Com 12 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 In the above snippet, we use call the load method of the result of the Ext.getBody call, and pass a configuration object, which specifies the url to fetch, which is our ‘htmlFragment.html’ file and scripts set to true. What happens when execute this code? Figure 2.5 Loading an HTML fragment into the document body. When executing the preceding code snippet, we see that the document body performs and AJAX request to retrieve our ‘htmlFragment.html’ file. While the file is being retrieved, it shows a loading indicator. Once the request is complete, the HTML fragment is injected into the DOM. We then see the entire body element highlight yellow, which is an indication that our JavaScript was executed. We can now see that using the Ext.Element.load utility method is a great convenience over having to manually code an Ext.Ajax.request call. And there you have it. Adding and removing elements to the DOM is a cinch when using Ext.Element. Ext has another way to make adding elements even simpler, especially if you have repeatable DOM structures to be placed in the DOM. These are the Template and XTemplate utility classes. 2.3 Using Templates and XTemplates The Ext.Template class is a powerful core utility that allows you to create an entire DOM hierarchy with slots that can be filled in by data later. After you define a template, you can then use it to stamp out a one or more of the pre-defined DOM structures with your data filling in the slots. Mastering templates will help you master UI widgets that use templates, such as the GridPanel, DataView and ComboBox. 2.3.1 Exercising Templates We’ll start out first by creating an extremely simple template and then we’ll move on to create one that is much more complex. var myTpl = new Ext.Template("
Hello {0}.
"); myTpl.append(document.body, ['Marjan']); myTpl.append(document.body, ['Michael']); myTpl.append(document.body, ['Sebastian']); Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 13 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 In the above example, we create an instance of Ext.Template and pass it a string representation of a div with a slot, which is marked in curly braces and store a reference in the variable myTpl. We then call myTpl.append and pass it a target element, which is document.body and data to fill in the slots, which in this case happens to be a single element array that contains a first name. We do this three consecutive times, which results in three divs being appended to the DOM with the each different first name filling in a slot. Below is an illustration of the result from our append calls. Figure 2.6 Using our first template to append nodes to the DOM with the exploded view in Firebug. As we can see in figure 2.9, three divs were appended to the document body, each with different names. The benefits of using Templates should now be clear. We set the template once and apply it to the DOM with different values. In the prior example, the slots were integers in curly braces and we passed in single item arrays. Templates can also map object key-values from plain objects. Here is an example of how to create a template that uses such syntax. Listing 2.1 Creating a complex template var myTpl = new Ext.Template( // 1 '
', ' Name : {name}
', ' Age : {age}
', ' DOB : {dob}
', '
' ); myTpl.compile(); // 2 Licensed to Alison Tyler Download at Boykma.Com 14 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 myTpl.append(document.body,{ // 3 color : "#E9E9FF", name : 'John Smith', age : 20, dob : '10/20/89' }); myTpl.append(document.body,{ color : "#FFE9E9", name : 'Naomi White', age : 25, dob : '03/17/84' }) {1}Creating our complex template {2}Compiling the template for faster speed {3}Appending our template to the document body When creating our complex Template{1}, the first thing you’ll probably notice is that we pass in quite a few arguments. We do this because when creating our template, it’s much easier to view our pseudo HTML in a tab delimit format rather than a long string. The Ext developers were keen to this idea, so they programmed the Template constructor to read all of the arguments being passed, no matter how many. In the Template pseudo HTML, we have slots for four data points. The first is color, which will be used to stylize the background of the element. The three other data points are name, age and dob, which will be directly visible when the template is appended. We then move on to compile{2} our template, which speeds up the Template by eliminating regular expression overhead. Even though for these two operations, we technically don’t need to compile it as we wouldn’t see the speed benefits, but for larger applications, where many templates are stamped out, compiling has a very clear benefit. To be safe, I always compile templates after instantiating them Lastly, we perform two append calls, where we pass in the reference element and a data object. Instead of passing an array like we did when in our first exploration of Templates, we pass in a data object, which has keys that match the template slots. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 15 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 2.7 The result of our complex template with a DOM view in Firebug. In using the Template, we were able to get two differently styled elements in the DOM with our being displayed. What if we had an array of objects? For instance, what if an Ajax request returned an array of data objects and we needed to apply a template for each data object? One approach could be looping through the array, which is easily done with a generic for loop or the more sexy Ext.each utility method. I say “nay” for that approach however. I would use XTemplate instead, which makes our code much cleaner. 2.3.2 Looping with XTemplates XTemplates can technically be used for single data objects, but can make life much easier when you have to deal with looping through data to stamp out HTML fragments on screen. It extends Templates and offers much more functionality. We’ll start our exploration by creating an array of data objects and then create an XTemplate, which we’ll use to stamp out HTML fragments. Listing 2.1 Using an XTemplate to loop through data var tplData = [{ // 1 color : "#FFE9E9", name : 'Naomi White', age : 25, dob : '03/17/84', cars : ['Jetta', 'Pilot', 'S2000'] },{ color : "#E9E9FF", name : 'John Smith', age : 20, Licensed to Alison Tyler Download at Boykma.Com 16 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 dob : '10/20/89', cars : ['Civic', 'Accord', 'Pilot'] }]; var myTpl = new Ext.XTemplate( // 2 '', // 3 '
', ' Name : {name}
', ' Age : {age}
', ' DOB : {dob}
', '
', '
' ); myTpl.compile(); myTpl.append(document.body, tplData); {1} Creating data to be used in by our XTemplate {2} Instantiating an instance of XTemplate {3} Instruct the XTemplate to “auto-fill” from an array We start Listing 2.XX by first setting up an array of data objects{1}, which are like the data objects we used in our last Template exploration with the addition of a cars array, which we’ll use in our next example. Next, we instantiate an instance of XTemplate, which looks very much like our last Template configuration, except we encapsulate the div container with a custom tpl element with the attribute for, which contains the value “.”. The tpl tag is like a logic or behavior modifier for the template and has two operators, which are for and if, which alters the way the XTemplate generates the HTML fragments. In this case, the value of “.” simply instructs the XTemplate to loop through the root of the array for which it is passed and construct the fragment based on the pseudo HTML encapsulated inside the tpl element. When you look at the rendered HTML, there will be no tpl tags rendered to the DOM. The results of our efforts are… Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 17 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 2.8 The results of our XTemplate usage with an exploded DOM view from Firebug. …absolutely identical Template example. Remember, the advantage of using XTemplates in this case, is not having to write code to loop through the array of objects. We let the framework do the dirty work for us. The capabilities of XTemplates extend far beyond that of merely looping through arrays, which increases its usability exponentially. 2.3.3 Advanced XTemplate usage You can configure them to loop through arrays within arrays and even have conditional logic. The next example will flex some XTemplate muscle and demonstrate many of these advanced concepts. Some of the syntax you’re about to see will be foreign to you. Do not get discouraged. I will explain every bit. Listing 2.2 Advanced XTemplate usage var myTpl = new Ext.XTemplate( '', '
', ' Name : {name}
', ' Age : {age}
', ' DOB : {dob}
', ' Cars : ', '', // 1 '{.}', // 2 '', // 3 ' (same car)', '', '{[ (xindex < xcount) ? ", " : "" ]}', // 4 '', '
', '
', Licensed to Alison Tyler Download at Boykma.Com 18 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 '
', { // 5 isPilot : function(car) { return car == 'Pilot'; } } ); myTpl.compile(); myTpl.append(document.body, tplData); {1} XTemplate loops through the cars data {2} XTemplate displays all data in the array {3} Execute the this.isPilot method {4} Arbitrarily JS code, testing for the end of the array {5} An object containing a local member, isPilot This usage of XTemplate exercised quite a few advanced concepts. The first of which is looping within a loop{1}. Rememberm, the for attribute instructs the xtemplate to loop through a list of values. In this case, the for attribute has the value of ‘cars’, which differs from the value that is set for the first for attribute, “.”. This will instruct the XTemplate to loop through this block of pseudo HTML for each individual car. Remember that cars is an array of strings. Inside of this loop, we have a string with “{.}”{2}, which instructs XTemplate to place the value of the array at the current index of the loop. In simple terms, the name of a car will be rendered at this position. Next, we see a tpl behavior modifier with an if attribute{3}, which executes this.isPilot and passes values. The this.isPilot method is actually something that is generated at the very end of our XTemplate{5}. We’ll speak more about this in a bit. Essentially the if attribute is more like an if condition, where the XTemplate will generate HTML fragments if the condition is met. In this case, this.isPilot must return true for the fragment that is encapsulated inside of this tpl flag to be generated. The values property is an internal reference of the values for the array we’re looping through. Because we’re looping through an array of strings, it references a single string, which is the name of a car. In the next line, we are arbitrarily executing JavaScript code{4}. Anything encapsulated in curly braces and brackets (“{[ … JS code … ]}”), will be interpreted as generic JavaScript and has access to some local variables that are provided by XTemplate and can change with each iteration of the loop. In this case, we’re simply checking to see if the current index (xindex) is less than the amount of items in the array (xcount) and returning either a comma with a space or an empty string. Performing this test inline will ensure that commas are placed exactly between the names of cars. The last item of interest is the object which contains our isPilot method{5}. Including an object (or reference to an object) with a set of members with the passing arguments to the XTemplate constructor will result in those members being applied directly to the instance of XTemplate itself. This is why we called this.isPilot directly in the Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 19 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 if condition of one of our tpl behavior modifier pseudo elements. The all of these member methods are called within the scope of the instance of the XTemplate for which they are being passed. This concept is extremely powerful, but can be dangerous, as you can override an existing XTemplate member, so please try to make your methods or properties as unique. Our isPilot method simply uses JavaScript shorthand to test if the passed string, car, is equal to “Pilot” and will return true if it is, else false. Here the results of our advanced XTemplate exercise. Figure 2.9 The results from our advance XTemplate exercise In looking at the results from our advance XTemplate exercise, we see that all of our behavior injections worked as planned. We have all of our cars listed, which includes proper comma placement. We can tell that our arbitrary JavaScript injection worked because the string “(same car)” is placed to the right of the “Pilot” name. As we can see, Templates and XTemplates have myriad of benefits over generic DOM injections using Ext.Element to stamp out HTML fragments with data. I encourage you to look over the Template and XTemplate API pages for more details and examples on how to use these utilities. Our next exposure to Templates will be when we learn how to create a custom ComboBox. 2.4 Summary In this chapter we discussed how JavaScript application logic was launched in the olden days with the onLoad handler of body element. Remember that browsers typically have their own way of publishing when the DOM is ready for manipulation, which causes a code management nightmare. In exercising Ext.onReady, we learned that it takes care of launching our application code just at the right time for each browser, so we can worry about the important stuff - application logic. We then took an in depth look the Ext.Element class, which wraps and provides end- to-end management for DOM nodes. We exercised a few of the management utilities for DOM Nodes where we added and removed elements. All UI widgets use the Ext.Element, making it one of the most used components of the core framework. Each widget’s element can be access via the (public) getEl method or the (private) el property, but only after it has been rendered. Licensed to Alison Tyler Download at Boykma.Com 20 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Lastly, we learned about the Template class to inject HTML fragments into the DOM. We also jumped into advanced techniques with the XTemplates and learned how to embed behavioral modifying logic into the template definition itself, producing results depending on the data that it was provided. Looking forward, we’re going to start focusing on the UI side of the framework, where we’ll jump right into the core concepts and models that drive the framework. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 3 Events, Components and Containers I recall my early days with the framework when I started learning by toying with the examples and reading the API documentation. I spent many hours on some of the most important core UI concepts, such as adding user interaction, the reusability of widgets and how widget contain or control another. For instance, how does one make the click of an anchor tag display an Ext Window? Sure there is a generic JavaScript way of attaching an event handler, but I wanted to use Ext JS. Likewise, I needed to know how to get widgets to communicate with other. An example would be how to reload a GridPanel when a row of another GridPanel is clicked. Also, how do we add and remove items dynamically from a Panel? Or how can I find a particular field within a form panel based on the type field? This chapter is designed to cover these core concepts, which are essential to building rich and interactive user interfaces with the framework. Being that we just covered Ext.Element in Chapter 2, we will leverage what we learned to setup native click handlers on DOM elements and learn how events flow in the DOM. We’ll also touch upon how widget events work from registration to firing the event. We will also explore the deep caverns of the fundamental UI building block, the Component, and learn how it serves as the central model for all UI widgets by implementing a template for standard behaviors known as the Component Lifecycle. Lastly, we’ll take some time discussing the Container class, where we’ll get an in-depth understanding of how widgets can manage child items. We’ll learn how to add and remove items dynamically to widgets like the Panel, which can be used as a building block for dynamically updating UIs. Licensed to Alison Tyler Download at Boykma.Com 2 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 3.1 Managing Events with Observable For me, one of the most fun tasks for developing web applications is coding to manage events. Events can be thought of as a ‘signal’ that is sent by some source when something occurs that could require action. Understanding events is one of the key core concepts from which you must be very familiar with, as this will aid you in developing user interfaces, which provide truly rich user interaction. For instance, what if you wanted to display a context menu on a GridPanel when a user clicked on a row? You would setup an event handler for the rowcontextmenu event, which will create and display the context menu. Likewise, knowing how Ext components communicate with events is equally as important. This section will give you the fundamental knowledge that you’ll need. 3.1.1 Taking a step back Though we may not have realized it, but we use an event driven operating system every day. All modern User Interfaces are driven by events, which on a high level, generally come from inputs such as mouse or keyboard, but can be synthesized by software. Events that are sent are dispatched or ‘fired’. Methods that take actions on these events are ‘listeners’ and are sometimes called a ‘handler’. Just like modern Operating Systems, the browser too has an event model where it fires events because of user input. This powerful model allows us to leverage that input and perform complex tasks, such as refreshing a grid or applying a filter. Just about every interaction that is performed with the browser fires events that can be leveraged. These are known as DOM based events. 3.1.2 DOM Based events Recall that events can come from user input or synthesized by software. We need to first explore DOM based events, which are initiated by user input before we can have fun with software-based events. A lot of old school web developers attached listeners directly on the HTML element via the onclick property:
Click me
This method of adding event handlers was standardized by Netscape (remember them?) many years ago and is considered to be ancient practice by most modern JavaScript developers. This is due to the fact that it adds a dependency for embedded JavaScript in HTML and leads to a code management nightmare. I would suggest avoiding this method at all cost. The way events are managed across browsers is added to our ever grow list of cross- browser incompatibilities. Luckily for us, Ext JS takes care of that and presents a unified interface for us to leverage: Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 3 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 var el = Ext.get('myDiv'); el.on('click', doSomething); Here, we use the native method, Ext.get, which allows Ext to wrap its element management class, Ext.Element, around a referenced element. Embedded with each instance of Ext.Element is the usage of the Ext event management engine, Ext.util.Observable. It is important to remember that all event management with Ext stems from Ext.util.Observable, which serves as a base for all DOM elements and components that need to manage events. Next, we attach a listener by calling the el.on and pass the native event to be handled and a method to perform an action on the event. Ext takes event listener registration a step further by allowing you to pass a reference to the scope from which the event handler is to be called and any parameters: var el = Ext.get('myDiv'); el.on('click', doSomething, scopeRef, [opt1, opt2]); It’s important to note that the default scope is always the object from which the handler is being defined. If scope were not explicitly passed, the method doSomething would be called within the scope of the object el, which is an instantiation of Ext.Element. NOTE The concept of scope can be confusing for some. If you are unsure about what it is, the following URL has a great tutorial that covers scope in great detail. http://www.digital- web.com/articles/scope_in_javascript/ Now that have learned and exercised simple event handling on an Element, we’ll take a quick glance at how events flow in the DOM. 3.1.3 Event flow in the DOM In the early days of the Internet, Netscape and Microsoft had two completely separate approaches with regard to event flow direction. In the Netscape model, events flowed downward from the document body to the source, which is known as event capture. The Microsoft model, known as bubbling, was exactly the inverse of capture, where the event is generated from the node from which the user action is performed and bubbles up to the document object, which performs a default action based on what type of node was clicked. Thankfully, the W3C model combined these two models and is what we use now. Licensed to Alison Tyler Download at Boykma.Com 4 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 3.1 The W3C Event model, where events first flow downward (capture) and then return up the tree (bubbling). As we can see in Figure 3.1, the user clicking on the anchor tag generates a dom-level click event. This causes the event capture phase to occur, which cascades the event down the DOM tree, where it eventually lands at the “target” node, the anchor tag. If the event is not stopped, it will bubble upwards back to the browser window, where it will cause the window.location to change. NOTE If you are new to DOM events and want to learn more about the inner workings, Peter- Paul Koch has an excellent article on his site, which, he explains events in browsers in much greater detail than what is covered in this book. Here is the direct link: http://www.quirksmode.org/js/events_order.html. Most of the time you’re developing event handlers on the DOM, you’re going to have to worry about bubbling. To demonstrate why this is important, we’ll need to setup some HTML and attach event handlers to the node. We’re going to use the Firebug console to echo out messages. If you don’t wish to use firebug, the generic JavaScirpt alert box will work. Suppose you have the following HTML and you want to apply separate click listeners to both the div and the anchor tags.
MyDiv Text My Href
We will register a click handler to the outer most element, myDiv and will use chaining so we don’t have to set a static reference to the result of the Ext.get method call. We will also Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 5 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 pass an anonymous function as the second parameter instead of passing a reference to a function: Ext.get('myDiv').on('click', function(eventObj, elRef) { console.log('myDiv click Handler, source elment ID: ' + elRef.id); }); After rendering the page expand firebug and click on the anchor tag. You’ll see a firebug console message indicating that you clicked the myHref tag. Hold the phone… we only assigned a listener to the anchor’s parent element, ‘myDiv’. How could this be? This is event bubbling in action. Remember that the click event generated from the anchor tag bubbled upwards to the container div, where we attached the click handler. That bubbled event triggered the event handler, causing its execution, thus the console message. Next, attach a click handler to the anchor tag: Ext.get('myHref').on('click', function(eventObj, elRef) { console.log('myHref click handler, source elment ID: ' + elRef.id); }); Refresh your page and click on the anchor again, you’ll see two events fired: Figure 3.2 The results of our click event handlers in the firebug console. Having both event listeners fired could be troublesome and considered a waste of resources, especially if only one is needed. In order to stop this from happening, we need to stop the bubbling (propagation) of the click event in the anchor. Only then can we have separation from the anchor and the div element click event handlers. 3.1.4 Burst the bubble To prevent both event handlers from triggering, we’ll have to modify the anchor click event handler to include a stopEvent call on the instance of Ext.EventObject (eventObj) passed to the handler: Ext.get('myHref').on('click', function(eventObj, elRef) { Licensed to Alison Tyler Download at Boykma.Com 6 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 eventObj.stopEvent(); console.log('myHref click handler, source elment ID: ' + elRef.id); }) Refresh the page and click the anchor again. This time, you’ll only see one firebug console message per click, indicating that you’ve clicked on the anchor tag. Likewise, clicking on the div element will produce a message stating that you clicked on the div. The eventObj.stopEvent method call stops the event bubbling in its tracks. This is important to remember because there are other interactions for which you may want to cancel event propagation, such as contextmenu, where you would want to show your own context menu instead of the browser’s default menu. We’ll modify our example to listen to listen to the contextmenu event to perform this task: Ext.get('myDiv').on('contextmenu', function(eventObj, elRef) { console.log('myDiv contextmenu Handler, source elment ID: ' + elRef.id); }); Ext.get('myHref').on('contextmenu', function(eventObj, elRef) { eventObj.stopEvent(); console.log('myHref contextmenu Handler, source elment ID: ' + elRef.id); if (!this.ctxMenu) { this.ctxMenu = new Ext.menu.Menu({ items : [{ text : "This is" },{ text : "our custom" },{ text : "context menu" }] }); } this.ctxMenu.show(elRef); }); In this example, we change the registered event from ‘click’ to ‘contextmenu’ for the both div and anchor elements. For the div event handler, we simply echo out a console message, providing an indication that the handler was triggered. For the anchor event handler, we stop the event propagation, log the event trigger in the firebug console, create a new Ext Menu and display it below the anchor element. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 7 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 3.3 Displaying our custom context menu by means of a “contextmenu” event handler. If you right click on the div, you’ll see the default browser context menu. However, if you right click on the anchor (Figure 3.3), you’ll see your own custom menu because we halted the propagation of the contextmenu event, preventing it from bubbling back up to the browser. As we can see, the registration of event handlers on DOM nodes is pretty much a simple task. Knowing if you have to stop an event from bubbling up can be tricky. Certainly the contextmenu event is the only event that comes to mind that always needs to be stopped to prevent default browser behavior. Next, we’ll focus our attention on software driven events, which is another key concept of the framework as inter-component events are use extensively in the framework. 3.1.5 Software driven events Almost every Ext widget and component has custom events that it fires when it deems necessary. For instance, when a widget is done rendering in the DOM, it fires an This is due to the fact that just about everything extends Ext.util.Observable, which gives the widgets the ability to exhibit similar behavior to complex UI environments such as your desktop UI. Events from widgets and components can include DOM based events that are bubbled up, such as click and keyUp or Ext-internal events such as beforerender and datachanged. The API is a great place to read about events for a particular descendant of Observable. The last section of each API page is dedicated to Public Events from which other components can register listeners and take action. 3.1.6 Registration of Events and Event Listeners Before an Ext-based event can be fired, it must be added to the list of events for that instance of Observable. This is typically done with the addEvent method, which stems from the Observable class. Let's instantiate a new instance of Observable and add a custom event. var myObservable = new Ext.util.Observable(); Licensed to Alison Tyler Download at Boykma.Com 8 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 myObservable.addEvents('sayHello'); myObservable.addEvents('sayGoodbye'); If you want to register more than one event, you can pass one event label per argument: myObservable.addEvents('sayhello', 'saygoodbye'); Alternatively, you can pass in a configuration object that has a list of event labels and if they should be enabled by default: myObservable.addEvents({ 'sayHello' : true, 'sayGoodbye' : true }) Now that we have our events registered, we need to register an event handler. This should look familiar to you. myObservable.on('sayHello', function() { console.log('Hello stranger'); }); Here, we are providing an event handler for our custom event, ‘sayHello’. Notice how this is exactly how we registered events on the DOM. The handler will simply log a console message in firebug as an indication that it was executed. Being that we have our custom event defined and we have a listener registered, we need to fire the event so that we may watch the event handler get called: myObservable.fireEvent('sayHello'); We can see that firing the event causes the handler to execute, thus resulting in a Firebug console message being displayed. Figure 3.4 A Firebug console message being displayed, indicating that the registered handler for the sayHello event was triggered. A lot of events from the framework pass parameters when firing, which among the parameters is a reference to the component firing the event. Create an event handler for a Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 9 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 custom ‘sayGoodbye’ event so that it accepts two parameters, firstName and lastName: var sayGoodbyeFn = function(firstName, lastName) { console.log('Goodbye ' + firstName + ' ' + lastName + '!'); } myObservable.on('sayGoodbye', sayGoodbyeFn); Here, we define a method named sayGoodbyeFn, which accepts the two parameters. We then call myObservable.on, which is shorthand for myObservable.addListener, to register the event handler sayGoodbyeFn. Next, fire the ‘sayGoodbye’ event and pass a first and last name. myObservable.fireEvent('sayGoodbye', 'John', 'Smith'); When calling fireEvent, the only required parameter is the first one, which is the event name. Parameters passed on thereafter are relayed to the event handler. The result of the ‘sayGoodbye’ event being fired with the firstName and lastName parameters passed should appear in your firebug console. Figure 3.5 A Firebug Console message displays as the result of the firing of the sayGoodbye event. Remember that if you register an event handler without specifying a scope, it will be called within the scope of the component that is firing the event. As we continue our journey into the Ext JS world, we will revisit events again as we will use them to ‘wire up’ widgets together. Just as the registration of event handlers are important to get tasks complete, deregistration of event handlers are equally as important to ensure proper cleanup when an event handler is no longer required. Fortunately, the method call to do so is extremely simple: myObservable.removeListener('sayGoodbye', sayGoodbyeFn); Here, we call myObservable.removeListener, passing in the event from which the listener is to be deregistered and the listener method to deregister. The shorthand for removeListener is un (opposite of on) and is commonly used in the framework and application code. Here is what the preceding code looks like in shorthand. Licensed to Alison Tyler Download at Boykma.Com 10 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 myObservable.un('sayGoodbye', sayGoodbyeFn); Managing events and event listeners is as straightforward as described. Always remember to deregister event handlers when they are no longer needed. This will help reduce the memory footprint of your application and help ensure that no exceptions occur due to an event handler being triggered when it should not be. We’re now familiar with registering events to DOM elements and have glanced at software-driven events. We can now start to gravitate towards the UI portion of the framework. Before we dive into configuring and constructing widgets, we need to look at the Component model, which serves as the base model for all UI widgets. Having a solid grasp on the how the component model works in Ext 3.0 will allow you to better utilize the UI portion of framework, especially when managing child items of a Container. 3.2 The Component Model The Ext Component model provides a centralized model that provides many of the essential component related tasks, which includes a set of rules dictating how the component instantiates, renders and is destroyed, which is known as the Component lifecycle. All UI widgets are a descendant of Ext.Component, which means that all of the widgets conform to the rules dictated by the model. Figure 3.2 partially depicts how many items actually subclass Component, directly or indirectly. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 11 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 3.6 This illustration of the Ext class hierarchy focuses on some of the common descendants of Ext.Component and depicts how widely used the Component model is used in the framework. Knowing that each and every UI widget is going to behave introduces stability and predictability into the framework, which I enjoy. The Component model also supports direct instantiation of classes or deferred instantiation, which is known as XTypes. Knowing which to use when can enhance the responsiveness of your application. 3.2.1 XTypes and Component Manager Ext 2.0 introduced a radical new concept known as an XType, which allows for lazy instantiation of components, which can speed up complex user interfaces and can clean up our code quite a bit. In Short, an XType is nothing more than a plain JavaScript object, which generally contains an xtype property with a string value denoting which class the XType is for. Here is a quick example of an XType in action: var myPanel = { xtype : 'panel', height : 100, width : 100, html : 'Hello!' } Licensed to Alison Tyler Download at Boykma.Com 12 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 In the configuration object above, myPanel is XType configuration object that would be used to configure an Ext.Panel widget. This works because just about every widget is registered to the Ext.ComponentMgr class with a unique string key and a reference to that class, which is then referred to as an XType. At the tail end of each Ext UI widget class, you’ll find the registration of that widget in the Ext.ComponentMgr. Registration of a component is simple: Ext.reg('myCustomComponent', myApp.customClass); The act of registering a component to the ComponentMgr appends or replaces the new Component to the internal reference map of the ComponentMgr singleton. Once registration is complete, you can specify your custom component as an XType: new Ext.Panel({ ... items : { xtype : 'myCustomComponent', ... } }); When a visual component, that can contain children, is initialized, it looks to see if it has this.items and will inspect this.items for XType configuration objects. If any are found, it will attempt to create an instance of that Component using the ComponentMgr.create. If the xtype property is not defined in the configuration object, the said visual component will use its defaultType property when calling ComponentMgr.create. I realize that this may sound a tad confusing at first. I think we can better understand this concept if we exercise it. To do this, we’ll create a window with an accordion layout that includes two children, one of which will not contain an xtype property. First, let's create our configuration objects for two of the children: var panel1 = { xtype : 'panel', title : 'Plain Panel', html : 'Panel with an xtype specified' } var panel2 = { title : 'Plain Panel 2', html : 'Panel with no xtype specified' } Notice that panel1 has an explicit xtype value of ‘panel’, which in turn will get used to create an instance of Ext.Panel. Objects panel1 and panel2 are very similar, but have two distinct differences. Object panel1 has an xtype specified while panel2 does not. Next, we’ll create our window, which will use these xtypes: new Ext.Window({ Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 13 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 width : 200, height : 150, title : 'Accordion window', layout : 'accordion', border : false, layoutConfig : { animate : true }, items : [ panel1, panel2 ] }).show(); In our new instantiation of Ext.Window, we pass items, which is an array of references to the two configuration objects we created earlier. The rendered window should appear as illustrated in Figure 3.7. Clicking on a collapsed panel will expand and collapse any other expanded panels and clicking on an expanded panel will collapse it. Figure 3.7 The results of our XType exercise; an Ext Window, which has two child panels derived from XType configuration objects. One of the lesser-known advantages of using XTypes is developing somewhat cleaner code. Because you can use plain object notation, you can specify all of your XType child items inline, resulting in cleaner and more streamlined code. Here is the previous example reformatted for to include all of its children inline: new Ext.Window({ width : 200, height : 150, title : 'Accordion window', layout : 'accordion', border : false, layoutConfig : { animate : true }, items : [ { xtype : 'panel', title : 'Plain Panel', html : 'Panel with an xtype specified' }, { title : 'Plain Panel 2', html : 'Panel with no xtype specified' Licensed to Alison Tyler Download at Boykma.Com 14 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 } ] }).show(); As you can see above, we’ve included all of the child configuration items inline with Window configuration object. The performance enhancements with using XTypes cannot be seen with such a plain example. The biggest XType-based performance gains come from rather large applications, where there is a rather large number of Components to be instanted. Components also contain another performance-enhancing feature, which is known as lazy rendering. That is a component is only rendered when necessary. 3.2.2 Component rendering The Ext.Component class supports both direct and lazy (on-demand) render models. Direct rendering can happen when a descendant of Component is instantiated with either renderTo or applyTo attribute, where renderTo points to a reference from which the component renders itself in to and applyTo references an element that has HTML that is structured in such a way that allows the component to create its own child elements based on the referenced HTML. You typically would use these parameters when if you wanted a component to be rendered upon instantiation. For instance: var myPanel = new Ext.Panel({ renderTo : document.body, height : 50, width : 150, title : 'Lazy rendered Panel', frame : true }); The end result of the code above would be the immediate render of the Ext.Panel, which sometimes is favorable, and other times not. The times where it is not favorable can be when you want to defer rendering to another time in code execution or the Component is a child of another. If you want to defer the rendering of the Component, simply omit the renderTo and applyTo attributes, and call the Components render method when you (or your code) deem it necessary. var myPanel = new Ext.Panel({ height : 50, width : 150, title : 'Lazy rendered Panel', frame : true }); // … some business logic… myPanel.render(document.body); Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 15 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 In the above example, we instantiate an instance of Ext.Panel and create a reference to it, myPanel. After some hypothetic application logic, we call myPanel.render and pass a reference to document.body, which renders the panel to the document body. You could also pas an ID of an element to the render method: myPanel.render('someDivId'); When passing an element ID to the render method, Component will use that ID with Ext.get to manage that element, which gets stored in its local el property. If this rings a bell, you may recall the conversation we had in the last chapter, when we were discussing Ext.Element, where we learned you can access a widget’s el property or use it’s accessor method, getEl to get obtain the reference. There is one major exception to this rule however. You never specify applyTo or renderTo when the Component is a child of another. There is a parent-child relationship that Components that contain other Components have, which is known as the Container model. If a Component is a child of another component, it is specified in the items attribute of the configuration object, its parent will manage the call to its render method when required. This is known as lazy or deferred rendering. We’ll investigate Containers later in this chapter, where we’ll learn more about the parent-child relationship Components can have. But first, need to understand the component life cycle, which details how the components are created, rendered and eventually destroyed. Learning how each of phase works will better prepare you for building robust and dynamics interfaces and can assist troubleshooting issues. 3.3 The Component Life Cycle Ext Components, just like everything in the real world, have a life cycle where they are created, used and destroyed. This lifecycle is broken up into three major phases: initialization, render and destruction as displayed in figure 3.8. Figure 3.8 The Ext Component lifecycle always starts with initialization and always ends with destruction. The component need not enter the render phase to be destroyed. To better utilize the framework, we must understand how the lifecycle works in a finer detail. This is especially important if you will be building extensions, plugins or composite Licensed to Alison Tyler Download at Boykma.Com 16 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 components. Quite a bit of steps take place at each portion of a lifecycle phase, which is controlled by the base class, Ext.Component. 3.3.1 Initialization The initialization phase is when the component is born. All of the necessary configuration settings, event registration and pre-render processes take place in this phase as illustrated in Figure 3.9. Figure 3.9 The initialization phase of the component lifecycle executes important steps such as event and component registration as well as the calling the initComponent method. It’s important to remember that a component can be instantiated but may not be rendered. Let's explore each step of the initialization phase: 1. The configuration is applied: When instantiating an instance of a Component, you pass a configuration object, which contains all of the necessary parameters and references to allow the component to do what it’s designed to do. This is done within the first few lines of the Ext.Component base class. 2. Registration of the base Component events: Per the Component model, each descendant of Ext.Component has, by default, a set of core events that are fired from the base class. These are fired before and after some behaviors occur: enable/disable, show, hide, render, destroy, state restore, state save. The before events are fired and tested for a successful return of a registered event handler and will cancel the behavior before any real action has taken place. For instance, when myPanel.show is called, it fires the beforeshow event, which will execute any methods registered for that event. If the beforeshow event handler returns false, myPanel does not show. 3. ComponentMgr registration: Each component that is instantiated is registered with the ComponentMgr class with a unique Ext-generated string ID. You can choose to override the Ext-generated ID by passing an id parameter in the configuration object passed to a constructor. The main caveat is that if a registration request occurs with Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 17 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 a non-unique registration ID, the newest registration will override the previous one. Be careful to use unique IDs if you plan on using your own ID scheme. 4. initComponent is executed: The initComponent method is where a lot of work occurs for descendants of Component like registration of descendant-specific events, references to data stores and creation of child components. initComponent is used as a supplement to the constructor, thus is used as the main point to extending Component or any descendant thereof. We will elaborate on extending with initComponent later on. 5. Plugins are initialized: If plugins are passed in the configuration object to the constructor, their init method is called, with the parent component passed as a reference. It is important to remember that the plugins are called upon in the order in which they are referenced. 6. State is initialized: If the component is state-aware, it will register its state with the global StateManager class. Many Ext widgets are state-aware. 7. Component is rendered: If the renderTo or applyTo parameters are passed into the constructor, the render phase begins at this time, else the component then lies dormant, awaiting its render method to be called. This phase of a Component’s life is usually the fastest as all of the work is done in JavaScript. It is particularly important to remember that the component does not have to be rendered to be destroyed. 3.3.2 Render The render phase is the one where you get visual feedback that a component has been successfully initialized. If the initialization phase fails for whatever reason, the component may not render correctly or at all. For complex components, this is where a lot of CPU cycles get eaten up, where the browser is required to paint the screen and computations take place to allow all of the items for the component to be properly laid out and sized. Figure 3.8 illustrates the steps of the render phase. Licensed to Alison Tyler Download at Boykma.Com 18 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 3.10 The render phase of the a component’s life can utilize a lot of CPU as it requires elements to be added to the DOM and calculations to be performed to properly size and manage them. If renderTo or applyTo are not specified a call to the render method must be made, which triggers this phase. If the component is not a child of another Ext component, then your code must call the render method, passing a reference of the DOM element: someComponent.render(‘someDivId’); If the component is a child of another component, then its render method will be called by the parent component. Let's explore the different steps of the render phase: 1. beforerender is fired: The component fires beforerender event and checks the return of any of the registered event handlers. If a registered event handler returns false, the component halts the rendering behavior. Recall that step two of the initialization phase registers core events for descendants of Component and that “before” events can halt execution behaviors. 2. The container is set: A component needs a place to live, and that place is known as its container. Essentially, if you specify a renderTo reference to an element, the component adds a single child div element to the referenced element, known as its container and renders the component inside that newly appended child. If an applyTo element is specified, the element referenced in the applyTo parameter becomes the components container, and the component only appends items to the referenced element that are required to render itself. The DOM element referenced in applyTo will then be fully managed by the component. You generally pass neither when the component is a child of another component, in which the container is the parent component. It is important to note, that you should only pass renderTo or applyTo, not both. We will explore renderTo and applyTo later on, when we learn more about widgets. 3. onRender is executed: This is a crucial step for subclasses of Component, where all of the DOM elements are inserted to get the component rendered and painted on screen. Each subclass is expected to call its superclass.onRender first when extending Ext.Component or any subclass thereafter, which ensures that the Ext.Component base class can insert the core DOM elements needed to render a component. 4. The component is “unhidden”: Many components are rendered hidden using the default Ext CSS class like ‘x-hidden’. If the autoShow property is set, any Ext CSS classes that are used for hiding components are removed. It is important to note that this step does not fire the show event, thus any listeners for that event will not be fired. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 19 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 5. Custom CSS classes or styles are applied: Custom CSS classes and styles can be specified upon component instantiation by means of the cls and style parameters. If these parameters are set, they are applied to the container for the component. It is suggested that you use cls instead of style, as CSS inheritance rules can be applied to the components children. 6. The render event is fired: At this point, all necessary elements have been injected into the DOM and styles applied. The render event is fired, triggering any registered event handlers for this event. 7. afterRender is executed: The afterRender event is a crucial post-render method that is automatically called by the render method within the Component base class and can be used to set sizes for the Container or perform any other post-render functions. All subclasses of Component are expected to call their superclass.afterRender method. 8. The component is hidden and/or disabled: If either hidden or disabled is specified as true in the configuration object, the hide or disable methods are called, which both fire their respective “before” event, which is cancelable. If both are true, and the “before” registered event handlers do not return false, the component is both hidden and disabled. 9. State-specific events are fired: If the component is state-aware, it will initialize its state-specific events with its Observable and register the this.saveEvent internal method as the handler for each of those state events. 10. Once the render phase is complete, unless the component is disabled or hidden, it’s ready for user interaction. It stays alive until it’s destroy method is called, in which it then starts its destruction phase. The render phase is generally where a component spends most of its life until it meets its demise with the destruction phase. 3.3.3 Destruction Just like in real life, the death of a component is a crucial phase in its life. Destruction of a component performs critical tasks, such as removing itself and any children from the DOM tree, deregistration of the component from ComponentMgr and deregistration of event listeners as depicted in Figure 3.9. Licensed to Alison Tyler Download at Boykma.Com 20 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 3.11 The destruction portion of a component’s life is equally as important as its initialization as event listeners and DOM elements must be deregistered and removed, reducing over-all memory usage. The component’s destroy method could be called by a parent Container, or by your code. Here are the steps in this final phase of a Component’s life: 1. beforedestroy is fired: This, like many “before” events, is a cancelable event, preventing the component’s destruction if its event handler returns false. 2. beforeDestroy is called: This method is first to be called within the Component’s destroy method and is the perfect opportunity to remove any non-component items, such as toolbars or buttons. Any subclass to Component is expected to call its superclass.beforeDestroy. 3. Element and Element listeners purged: If a component has been rendered, any handlers registered to its Element are removed and the Element is removed from the DOM. 4. onDestroy is called: While the Component class itself does not perform any actions within the onDestroy method, subclasses are expected to use this to perform any post-destruction actions, such as removal of data stores. The Container class, which subclasses Component indirectly, manages the destruction of all registered children by in its onDestroy method, alleviating this task from the end developer. 5. Component is unregistered from ComponentMgr: The reference for this component in the ComponentMgr class is removed. 6. The destroy event is fired: Any registered event handlers are triggered by this event, which signals that the component is no longer in the DOM. 7. Component’s event handlers are purged: All event handlers are deregistered from the Component. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 21 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 And there you have it, an in-depth look at the Component lifecycle, which is one of the features of the Ext framework that makes it so powerful and successful. Be sure to not dismiss the destruction portion of a component’s lifecycle if you plan on developing your own custom components. Many developers have gotten into trouble where they’ve ignored this crucial step and have code that has left artifacts such as data stores which continuously poll web servers, or event listeners that are expecting an Element to be in the DOM, were not cleaned properly and cause exceptions and the halt of execution of a crucial branch of logic. Next, we’ll look at the Container class, which is a descendant of Component and gives Components the ability to manage other components in a parent-child relationship. 3.4 Containers The Container model is a behind-the-curtains class that provides a foundation for components to manage their child items and is often overlooked by developers. This class provides a suite of utilities, which includes add, insert and remove methods along with some child query, bubble and cascade utility methods. These methods are used by most of the descendants, which include Panel, Viewport and Window. In order to learn how these tools work, we need to build a Container with some child items for us to use. The following listing is rather long and involved, but stay with me on this. The reward is just around the corner. Listing 3.1 Building our first container var panel1 = { //1 html : 'I am Panel1', id : 'panel1', frame : true, height : 100 } var panel2 = { html : 'I am Panel2', id : 'panel2', frame : true } var myWin = new Ext.Window({ // 2 id : 'myWin', height : 400, width : 400, items : [ panel1, panel2 ] }); myWin.show(); {1} The first and second child panels {2} The last child, a form panel Licensed to Alison Tyler Download at Boykma.Com 22 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Let’s take a gander at what we’re doing in Listing 3.1. The first thing we do is create two vanilla panels {1} and create myWin {2}, an instance of Ext.Window, which contains the previously defined. The rendered UI should look like the one in Figure 3.1. Figure 3.12 The rendered container UI from Listing 3.1. We left some room at the bottom of myWin, which will come in handy when we add items. Each container stores references to its children via an items property, which can be accessed via someContainer.items and is an instance of Ext.util.MixedCollection. The MixedCollection is a utility that allows the framework to store and index a mixed collection of data, which includes strings, arrays and objects and provides a nice collection of handy utility methods. I like to think of it as the Array on steroids. Now that we’ve rendered our Container, let’s start to exercise the addition of children to a container. 3.4.1 Learning to tame children Normally, taming children involves a lot of yelling and timeouts. Unfortunately, these methods don’t really work with Ext component, so we must learn to use the tools that they provide for us. Mastering these utility methods will enable you to dynamically update your UI, which is in the spirit of AJAX web pages. Adding components is a simple task, in which we’re provided two methods; add and insert. The add method only appends a child to the container’s hierarchy, while insert allows you to inject an item into the container at a particular index. Let’s add to the Container that we created in Listing 3.1. For this, we’ll use our handy FireBug JavaScript console: Ext.getCmp('myWin').add({ title : 'Appended Panel', id : 'addedPanel', html : 'Hello there!' }); Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 23 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Running the preceding code adds the item to the Container. Wait a cotton-picking second! Why didn’t the new panel appear? Why does the container still look like Figure 3.1? It did not show up because we didn’t call the doLayout method for the Container. Let’s see what happens when we call doLayout: Ext.getCmp('myWin').doLayout(); Ah-ha! It showed up, but why? Well, the doLayout method forces the recalculation of Containers and its children and will render any un-rendered children. The only reason you would not need to call it is if the Container is not rendered. We went down this path of pain so that we may learn the valuable lesson of calling doLayout when we add items to a container at run time. Appending children is handy, but sometimes we need to be able to insert items at a specific index. Using the insert method and calling doLayout afterwards easily accomplishes this task: Ext.getCmp('myWin').insert(1, { title : 'Inserted Panel', id : 'insertedPanel', html : 'It is cool here!' }); Ext.getCmp('myWin').doLayout(); We simply insert a new Panel at index 1, which is right under Panel1. Because we called the doLayout method immediately after we did an insertion, we see the newly inserted panel in the Window instantaneously. The changes should look like Figure 3.13: Figure 3.13 The rendered results of our dynamically added and inserted child panels. As you can see, adding and inserting children is a cinch. Removing items is just as easy as adding them and accepts two arguments. The first of which is a reference to the Licensed to Alison Tyler Download at Boykma.Com 24 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 component or the component ID from which you want to be removed. The second parameter, however, specifies whether or not the destroy method should be called for that component, which gives you incredible flexibility, allowing you to move components from one container to another if you so desired. Here is how we would remove one of the child panels that we recently added using our handy Firebug console: var panel = Ext.getCmp('addedPanel'); Ext.getCmp('myWin').remove(panel); After you execute this code, you’ll notice that the panel immediately disappeared. This is because we didn’t specify the second parameter, which is, by default true. You can override this default parameter by setting autoDestroy to false on a Parent Container. Also, you don’t need to call the parent’s doLayout method, as the removed Component’s destroy method is called, initiating its destruction phase and deleting its DOM element. If you wanted to move a child to a different container, you simply specify false as the remove’s second parameter, and then add or insert it into the parent like this: var panel = Ext.getCmp('insertedPanel'); Ext.getCmp('myWin').remove(panel, false); Ext.getCmp('otherParent').add(panel); Ext.getCmp('otherParent').doLayout(); The preceding code snippet assumes that we already have another Parent container instantiated with the id of ‘otherParent’. We simply create a reference to our previously inserted panel, and perform a non-destructive removal from its parent. Next, we add it to its new parent and call its doLayout method to actually perform the DOM-level move operation of the child’s element into the new parent’s content body element. The utilities offered by the Container class extend beyond the addition and removal of child items. They provide you the ability to descend deep into the Container’s hierarchy to search for child components, which become useful if you want to gather a list of child items of a specific type or that meet special criteria and perform an operation on them. 3.4.2 Querying the container hierarchy Of all of the query utility methods, the easiest is findByType, which is used to descend into the container hierarchy to find items of a specific XType and returns a list of items that it finds. For instance, if you wanted to find all of the text input fields for a given container: var fields = Ext.getCmp('myForm').findByType('field'); Executing the preceding code against a Container with the Component ID of ‘myForm’ will result in a list of all input fields at any level in its hierarchy. The findByType leverages the Container’s findBy method, which we can use as well. We’re going to explore how findBy works. Please stay with me on this, as it may not make sense at first. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 25 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 The findBy method accepts two parameters, a custom method which we use to test for our search criteria and the scope from which to call our custom method. For each of the custom containers, our custom method gets called and is passed the reference of the child Component from which it’s being called at. If the custom search criteria are met, the custom method needs to returns true, which instructs findBy to add that recently referenced Component to a list. Once all components are exhausted, findBy returns the list, which contains any components that met our criteria. OK, with that out of the way, let’s explore this concept through code. For arguments sake, let’s say we wanted to find all child items that are hidden. We could use findBy in the following way to do so: var findHidden = function(comp) { if (! comp.isVisible()) { return true; } } var panels = Ext.getCmp('myContainer').findBy(findHidden); In our rather simplistic findHidden query method, we are testing on if the component is not visible. If the component’s isVisible method returns anything but true, our findHidden method returns true. We then call findBy on the Container with the ID of ‘myContainer’ and pass it our custom findHidden method with the results being stored in the panels reference. By now, you have the core-knowledge necessary to manage child items. Let’s shift focus on to flexing some Ext-UI muscle by exploring some of the commonly used descendants of containers. We’l see how we can use Ext to create a UI using all of the browser’s available viewing space. 3.4.3 The Viewport Container The Viewport class is the foundation from which all web applications that depend solely on Ext are built by managing 100% of the browser’s - you guessed it - viewport or display area. Weighing in at just a tad over 20 lines, this class is extremely lightweight and efficient. Being that it is a direct descendant of the Container class, all of the child management and layout usage is available to you. To leverage the viewport, you can use the following example code: new Ext.Viewport({ layout : 'border', items : [ { height : 75, region : 'north', title : 'Does Santa live here?' }, { width : 150, region : 'west', title : 'The west region rules' Licensed to Alison Tyler Download at Boykma.Com 26 Jesus Garcia / Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 }, { region : 'center', title : 'No, this region rules!' } ] }); The rendered Viewport from the code above utilizes the entire browser’s viewport and display three panels organized by the “border layout”. If you resize the browser window, you’ll notice that the center panel is resized automatically, which demonstrates how the Viewport listens and responds to the browser’s window resize event. Figure 3.14 Our first Viewport, which takes up 100% of the browser’s available viewing space. The Viewport class provides the foundation for all ext-based applications that leverage the framework as a complete web-based UI solution for their RIAs. Many developers run into a brick wall when they attempt to create more than one Viewport in a fully Managed Ext JS page to display more than one “screen”. To get around this, you can use the card layout with the viewport and “flip” through different application screens, which are resized to “fit” the viewport. We’ll dive into layouts in chapter 5, where you’ll get to understand key terms like “fit” and “flip” in the context of layouts. We’ve covered quite a few important core topics, which help us manage child items in a Container. We also learned how to use the Viewport to help us manage all of the browsers viewing space to layout out Ext UI widgets. One of the most commonly used UI widgets to display content is the Panel. Let’s explore this ubiquitous widget. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia / Ext JS in Action 27 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 3.5 Summary In this chapter we took a deep look at three fundamental areas of Ext JS. In doing so, we learned about managing DOM-level, where we exercised the registration of event listeners for DOM elements and learned how to stop them from bubbling up the DOM hierarchy. We also glanced at component-level events and learned how to register and event and fire that event. We took a real in-depth look at the Component Models, which gives the framework a unified method of managing instances of components. The component lifecycle is one of the most important concepts for the UI portion of the framework, which is why we covered it before we went too deep into learning about the many widgets. Lastly, we explored the world of Containers and learned how they are used to manage child components. In doing so, we also learned about the Viewport and how it is the foundation for web applications that are based entirely on Ext JS. We now have the foundation that will help propel us forward, as we’ll start to exercise the framework’s UI machinery. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 04 Panels, TabPanels, and Windows When developers start to experiment or build applications with Ext, they often start by copying examples from the downloadable SDK. While this approach is good for learning how a particular layout was accomplished, it falls short in explaining how the stuff actually works, which leads to those throbbing forehead arteries. In this chapter, we’ll explain some of the core topics, which are some of the building blocks to developing a successful UI deployment. In this chapter, we’ll going to dive into how the Panel works, and explore the areas where it can display content and UI widgets. We’ll then explore Windows and the MessageBox, which float above all other content in the page. Towards the end, we’ll dive into using Tab Panels, and explore some of the usability issues that may occur when using this widget. Upon completion of this chapter, you will have the ability to manage the full CRUD (CReate, Update and Delete) lifecycle for Containers and their child items, which you will depend on as you develop your applications. 4.1 The Panel The Panel, a direct descendant of Container, is considered another workhorse of the framework as it what many developers use to present your UI widgets. A fully loaded panel is divided into six areas for content as seen in figure 4.1. Recall that Panel is also a descendant of Component, which means that it follows the Component lifecycle. Moving forward, we will use the term Container to describe any descendent of Container. This is because I want to reinforce the notion that the UI widget in context is a descendant of Container. Licensed to Alison Tyler Download at Boykma.Com 2 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 4.1 An example of a fully loaded Panel, which has a title bar with an Icon and tools, top and bottom toolbars, and a button bar on the bottom. The title bar is a very busy place for a panel that offers both visual and interactive content for the end user. Like Microsoft Windows, an icon can be placed at the top left of the panel, offering your users a visual queue as to what type of panel they are focused on. In addition to the Icon, a title can be displayed on the panel as well. On the right most area of the title bar is a section for ‘tools’, which is where miniature icons can be displayed, which will invoke a handler when clicked. Ext provides many icons for tools, which include many common user related functions like help, print and save. To view all of the available tools, visit the Panel API. Of the six content areas, the Panel body is arguably the most important, which is where the main content or child items are housed. As dictated by the Container class, a layout must be specified upon instantiation. If a layout is not specified, the Container layout is used by default. One important attribute about layouts is that they cannot be swapped for another layout dynamically. Let’s build a complex panel with a top and bottom toolbars, with two buttons each. 4.1.1 Building a complex panel Being that we’re going to have toolbar buttons, we should have a method to be called when they are clicked. var myBtnHandler = function(btn) { Ext.MessageBox.alert('You Clicked', btn.text); } This method will be called when a button on any toolbar is clicked. The toolbar buttons will call handlers passing themselves as a reference, which is what we call btn. Next, let’s define our toolbars: Listing 4.1 Building toolbars for use in a Panel var myBtnHandler = function(btn) { // 1 Ext.MessageBox.alert('You Clicked', btn.text); } Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 3 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 var fileBtn = new Ext.Button({ // 2 text : 'File', handler : myBtnHandler }); var editBtn = new Ext.Button({ // 3 text : 'Edit', handler : myBtnHandler }); var tbFill = new Ext.Toolbar.Fill(); // 4 var myTopToolbar = new Ext.Toolbar({ // 5 items : [ fileBtn, tbFill, editBtn ] }); var myBottomToolbar = [ // 6 { text : 'Save', handler : myBtnHandler }, '-', { text : 'Cancel', handler : myBtnHandler }, '->', 'Items open: 1', ]; {Annotation 1} The button click handler method {Annotation 2} The ‘File’ button {Annotation 3} The ‘Edit’ button {Annotation 4} The “greedy” toolbar fill, {Annotation 5} The top toolbar instantiation {Annotation 6} The bottom toolbar array configuration. In the preceding code example, we do quite a lot and display two different ways of defining a toolbar and its child components. Firstly, we define myBtnHandler {#1}, By default, each button’s handler is called with two arguments, the button itself, and the browser event wrapped in an Ext.Event object. We use the passed button reference (“btn”) and pass that text on over to Ext.MessageBox.alert to provide the visual confirmation that a button was clicked. Next, we instantiate the File {#2}, Edit {#3} buttons and the “greedy” toolbar spacer {#4}, which will push all toolbar items after it to the right. We assign myTopToolbar to a new instance of Ext.Toolbar {#5}, referencing the previously created buttons and spacer as elements in the new toolbar’s items array. That was a lot of work for just a relatively simple toolbar. We did it this way to “feel the pain” of doing things the “old way” and better appreciate how much time (and end developer code) the Ext shortcuts and XTypes really save. The myBottomToolbar {#6} reference is a Licensed to Alison Tyler Download at Boykma.Com 4 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 simple array of objects and strings, which Ext translates into the appropriate objects when its parent container deems it necessary to do so. To get references to the top toolbar, you can use myPanel.getTopToolbar() and inversely, to get a reference to the bottom; myPanel.getBottomToolbar(). You would use these two methods to add and remove items dynamically to either toolbar. We’ll cover toolbars in much greater detail later. Next, lets create our panel body: var myPanel = new Ext.Panel({ width : 200, height : 150, title : 'Ext Panels rock!', collapsible : true, renderTo : Ext.getBody(), tbar : myTopToolbar, bbar : myBottomToolbar, html : 'My first Toolbar Panel!' }); We’ve created panels before, so just about everything here should look familiar except for the tbar and bbar properties, which reference the newly created toolbars. Also, there is a collapsible attribute, which when set to true, the panel creates a toggle button on the top right of the title bar Rendered, the panel should look like the one in figure 4.2. Remember, clicking on any of the toolbar buttons will result in an Ext.MessageBox displaying the button’s text, giving you visual confirmation that the click handler was called. Figure 4.2 The rendered results of Listing 4.1, where we create a complex-collapsible panel with a top and bottom toolbar that have each contain buttons. Toolbars are great places to put content, buttons or menus that are outside of the Panel body. There are two areas from which we still need to explore, buttons and tools. To do this, we’ll add to the myPanel example, but we’re going to do it using the Ext shortcuts with XTypes inline with all of the other configuration options. Listing 4.2 Adding buttons and tools to our existing panel Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 5 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 var myPanel = new Ext.Panel({ ... // 1 buttons : [ // 2 { text : 'Press me!', // 3 handler : myBtnHandler } ], tools : [ // 4 { id : 'gear', // 5 handler : function(evt, toolEl, panel) { var toolClassNames = toolEl.dom.className.split(' '); var toolClass = toolClassNames[1]; var toolId = toolClass.split('-')[2]; Ext.MessageBox.alert('You Clicked', 'Tool ' + toolId); } }, { id : 'help', // 6 handler : function() { Ext.MessageBox.alert('You Clicked', 'The help tool'); } } ] }); {1} Original properties from the previous example {2} The buttons Array begins {3} The ‘Press me!’ panel button {4} The tools array begins {5} The ‘gear’ tool and inline click handler {6} The ‘help’ tool and inline click handler In Listing 4.2, we added to the previous set of config options {#1}, and included two shortcut arrays, one for buttons and the other for tools. Because we specified a buttons array {#2}, when the Panel renders, it will create a footer div, a new instance of Ext.Toolbar with a special CSS class ‘x-panel-fbar’ and render it to the newly created footer div. The ‘Press Me!’ button {#3} will be rendered in the newly created footer toolbar, and when clicked will invoke our previously defined myBtnHandler method. If you look at the myBottomToolbar shortcut array in Listing 4.1 and the buttons shortcut array in Listing 4.3 you’ll see some similarities. This is because all of the panel toolbars (tbar, bbar and buttons) can be defined using the exact same shortcut syntax because they all will get translated into instances of Ext.Toolbar and rendered to their appropriate position in the panel. We also specified a tools array {#4} configuration object, which is somewhat different than the way we define the toolbars. Here, to set the icon for the tool, you must specify the id of the tool, such as ‘gear’ {#5} or ‘help’ {#6}. For every tool that is specified in the array, an icon will be created in the tools. Panel will assign a ‘click’ event handler to each tool, which will invoke the handler specified in that tools’ configuration object. The rendered version of the newly modified myPanel should look like the one in figure 4.3. Licensed to Alison Tyler Download at Boykma.Com 6 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 4.3 The rendered results from Listing 4.2, which add a button in the button bar as well as tools to the title bar. The preceding example is a meant to display all of the items that can be utilized on a panel, but is not the best example of elegant and efficient User interface design. While it may be tempting to load up your panels with buttons and toolbars, you must be careful not to overload a panel with too much on-screen “gadgetry”, which could overwhelm your user and take up valuable screen real-estate. Now that we have some experience with the Panel class, let’s look at one of its close descendants, the Window, which you can use to float content above everything else on the screen and can be used to replace the traditionally lame browser-based popup. 4.2 Popping up Windows The Window UI widget builds upon the Panel, providing you the ability to float UI components above all of the other content on the page. With Windows, you can provide a modal dialog, which masks the entire page, forcing the user to focus on the dialog and prevents any mouse-based interaction with anything else on the page. Figure 4.4 is a perfect example of how we can leverage this class to focus the user’s attention and request input. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 7 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 4.4 An Ext Modal Window, which masks the browser’s viewport. Working with the Windows class is a lot like working with the Panel class, except you have to consider issues like whether or not you want to disable resizing or want the Window to be constrained within the boundaries of the browsers viewport. Let’s look into how we can build a window. For this, we’ll need a vanilla Ext Page with no widgets loaded. Listing 4.3 Building an animated window var win; var newWindow = function(btn) { // 1 if (! win) { win = new Ext.Window({ // 2 animateTarget : btn.el, html : 'My first vanilla Window', closeAction : 'hide', // 3 id : 'myWin', height : 200, width : 300, // 4 constrain : true }); } win.show(); } new Ext.Button({ // 5 renderTo : Ext.getBody(), Licensed to Alison Tyler Download at Boykma.Com 8 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 text : 'Open my Window', style : 'margin: 100px', handler : newWindow }); {1} A handler that creates a new window {2} Instantiate a new Window {3} Prevent the automatic destruction upon close {4} Constrain the window to the browser’s viewport {5} Create a button that launches the window In listing 4.3, we do things a little differently in order for us to see the animation for our Window’s close and hide method calls. The first thing we do is create a global variable, win, for which we’ll reference the soon to be created window. We create a method, newWindow{1} that will be the handler for our future button and is responsible for creating the new Window{2}. Lets take a quick moment examine some of the configuration options for our Window. One of the ways we can instruct the Window to animate upon show and hide method calls is to specify an animateEl property, which is a reference to some element in the DOM or the element ID. If you don’t specify the element in the configuration options, you can specify it when you call the show or hide methods, which take the exact same arguments. In this case, we’re the launching button’s element. Another important configuration option is closeAction{3}, which defaults to ‘close’ and destroys the Window when the close tool is clicked. We don’t want that in this instance, so we set it to ‘hide’, which instruct the close tool to call the hide method instead of close. We also set the constrain{4} parameter to true, which instructs the Window’s drag and drop handlers to prevent the window from being moved from outside of the browser’s viewport. Lastly, we create a button{5} that, when clicked, will call our newWindow method, resulting in the window animating from the button’s element. Clicking on the (x) close tool will result in the window hiding. The rendered results will look like figure 4.5. FiIgure 4.5 The rendered results from Listing 4.3, where we create a window that animates from the button’s element upon click. Because we don’t destroy the window when the close tool is pressed, you can show and hide the window as many times as you wish, which is ideal for windows that you plan on reusing. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 9 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Whenever you deem that it is necessary to destroy the window, you can call it’s destroy or close method. Now that we have experiencing in creating a reusable Window, we can now begin exploring other configuration options to further alter the behavior of the Window. 4.2.1 Further Window configuration exploration There are times when we need to make a Window behave to meet requirements of our application. In this section, we’ll learn about some of the commonly used configuration options. Some time we need to produce a window that is modal and is very rigid. To do this, we need to set a few configuration options. Listing 4.4 Creating a rigid modal Window var win = new Ext.Window({ height : 75, width : 200, modal : true, // 1 title : 'This is one rigid window', html : 'Try to move or resize me. I dare you.', plain : true, border : false, resizable : false, // 2 draggable : false, // 3 closable : false, // 4 buttonAlign : 'center', buttons : [ { text : 'I give up!', handler : function() { win.close(); } } ] }) win.show(); {1} Ensuring the page is masked {2} prevent resizing from occurring {3} disabling window movement {4} preventing window closure In Listing 4.4, we create an extremely strict modal Window. To do this, we had to set quite a few options. The first of which is modal{1}, which instructs the window to mask the rest of the page with a transparent div. Next, we set resizable{2} to false, which prevents the window from being resized via mouse actions. To prevent the window from being moved around the page, we set draggable{3} to false. I only wanted a single center button to close the window, so closable{4} is set to false, which hides the close tool. Lastly, set some cosmetic parameters, plain, border and buttonAlign. Setting plain to true will make the content body background transparent. When coupled with setting the border to false, the window appears to be one unified cell. Being that I wanted to have the single button Licensed to Alison Tyler Download at Boykma.Com 10 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 centered, we specify the buttonAlign property as such. The rendered example should look like Figure 4.5. Figure 4.5 Our first strict modal Window rendered in the Ext SDK feed viewer example. There are other times when we want to relax the restrictions on the window. For instance, there are situations where we need a window to be resizable, but should not be resized less than specific dimensions. For this, you allow resize (resizable) and specify minWidth and minHeight parameters. Unfortunately, there is no easy way to set boundaries to how large a window can grow. While there are many reasons to create our own windows, there are times where we need something quick and dirty to, for instance, display a message, or prompt for user data. The Window class has a stepchild known as the MessageBox to fill this need. 4.2.2 Replacing alert and prompt with MessageBox The MessageBox class is a reusable, yet versatile, Singleton class that gives us the ability to replace some of the common browser based dialogs such as alert and prompt with a simple method call. The biggest thing to know about the MessageBox class is that it does not stop JavaScript execution like traditional alerts or prompts, which I consider an advantage. While the user is digesting or entering information, your code can perform AJAX queries or even manipulate the UI. If specified, the MessageBox will execute a callback method when the Window is dismissed. Before we start to use the MessageBox class, let’s create a callback method. We’ll need this later on. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 11 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 var myCallback = function(btn, text) { console.info('You pressed ' + btn); if (text) { console.info('You entered : ' + text) } } Our myCallback method will leverage Firebug’s console to echo out the button pressed and the text you entered if any. The MessageBox will only pass two parameters to the callback method, the button ID and any entered text. Now that we have our callback, lets launch an alert dialog. var msg = 'Your document was saved successfully'; var title = 'Save status:' Ext.MessageBox.alert(title, msg); Here, we call the MessageBox.alert method, which will generate a window, which will look like Figure 4.9 (left) and will dismiss when the OK button is pressed. If you wanted myCallback to get executed upon dimissal, add it as the third parameter. Now that we have looked at alerts, lets see how we can request user input with the MessageBox.prompt method. var msg = 'Please enter your email address.'; var title = 'Input Required' Ext.MessageBox.prompt(title, msg, myCallback); We call the MessageBox.prompt method, which we pass the reference of our callback method and will look like Figure 4.6. Enter some text and press the Cancel button. In the FireBug console, you’ll see the button ID pressed and the text entered. Figure 4.6 The MessageBox’s alert (left) and prompt (right) modal dialog Windows. And there you have it, alert and prompt at a glance. I find these very handy, as I don’t have to create my own singleton to provide these UI widgets. Be sure to remember them when you’re looking to implement a Window class to meet a requirement. I have to confess a little secret. Ready? The alert and prompt methods are actually shortcut methods for the much larger and highly configurable MessageBox.show method. Here is an example of how we can use the show method to display an icon with a multi-line textarea input box. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 05 Organizing Components When building an application, many developers often struggle on how to organize their UI and which tools to use to get the job done. In this chapter, you’ll gain the necessary experience to be able to make these decisions in a further educated manner. We’re going to explore all of the numerous layout models and try to identify some of the best practices and common issues that you will face. 5.1 Laying it all out The Layout management schemes are what are responsible for visual organization of widgets on screen. They include simple layout schemes such as ‘Fit’, where a single child item of a Container will be sized to fit the Container’s body or complex layouts such as border layout, which splits up a Container’s content boy into five manageable slices or “regions”. When exploring some of the layouts, we’ll hit upon examples that are verbose, thus lengthy and can serve as a great springboard or starting point for your layout endeavors. We’ll start our journey with taking a look at the Container Layout, which is the nucleus of the entire layout hierarchy. 5.2 The simple Container layout As you may be able to recall, the Container layout is the default layout for any instance of Container and simply places items on the screen, one on top of. I like to think of it as the Lincoln Logs of the Ext layouts. Though the Container layout does not explicitly resize child items, a child’s width may conform to the Container’s content body if it is not constrained. It also serves as the base class for all other layouts, providing a much of the base functionality for the descendant layouts. Figure 5.1 illustrates the Ext.layout class hierarchy. Licensed to Alison Tyler Download at Boykma.Com 2 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 5.1 The layout class hierarchy, where layouts are descendants of the Container layout Implementing a container layout is extremely simple, requiring you to just add and remove child items. In order to see this, we need to setup somewhat of a dynamic example, using quite a few components. Listing 5.1 Leveraging the container layout var childPnl1 = { // 1 frame : true, height : 50, html : 'My First Child Panel', title : 'First children are fun' } var childPnl2 = { // 2 width : 150, html : 'Second child', title : 'Second children have all the fun!' } var myWin = new Ext.Window({ // 3 height : 300, width : 300, title : 'A window with a container layout', autoScroll : true, // 4 items : [ // 5 childPnl1, childPnl2 ], tbar : [ // 6 { text : 'Add child', handler : function() { var numItems = myWin.items.getCount() + 1; myWin.add({ title : 'Child number ' + numItems, height : 60, frame : true, collapsible : true, collapsed : true, html : 'Yay, another child!' }); myWin.doLayout(); Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 3 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 } } ] }); {1} The first child item, a Panel {2} The third child item, another Panel {3} The Window which contains the three child items {4} Set autoScroll to true, to allow the content body to scroll automatically {5} The three child items are referenced {6} The toolbar with the add item button In Listing 5.1, we do are doing quite a lot to exercise the container layout. This because I want you to be able to see how the items stack and do not resize. The first thing we do is instantiate object references using XTypes for the two child items that will be managed by a Window; childPnl1{#1} and childPnl2{#2}. These three child items are static. Next, we begin our myWin{#3} reference, which is an instance of Ext Window. We also set the autoScroll property{#4} to true. This tells the Container to add the CSS attributes overflow-x and overflow-y to auto, which instructs the browser to show the scroll bars only when it needs to. Notice how we set the child “items”{#5} property to an array. The items property for any container can be an instance of an array to list multiple children or an object reference for a single child. The window contains a toolbar{#6} that has a single button that, when pressed adds a dynamic item to the window. The rendered window should look like the one in figure 5.2. Figure 5.2 The results of our first implementation of the container layout. While the container layout does not provide much to manage the size of child items, it is not completely useless. It’s lightweight relative to its descendants, which makes it ideal if you Licensed to Alison Tyler Download at Boykma.Com 4 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 want to simply display child items that have fixed dimensions. There are times, however, that you will want to have the child items dynamically resize to the container’s content body. This is where the Anchor Layout can be very useful. 5.3 The Anchor layout The Anchor layout is similar to the Container layout, where it stacks child items one on top of another, except it adds dynamic sizing to the mix using an anchor parameter specified on each child. This anchor parameter is used to calculate the size of the child item relative to the parent’s content body size and is specified as a percentage, an offset, which is an integer. The anchor parameter is a string, with using the following format: anchor : “width, height” // or “width height” Let’s take our first stab at implementing an anchor layout using percentages. Listing 5.2 The Anchor layout using percentages var myWin = new Ext.Window({ // 1 height : 300, width : 300, layout : 'anchor', // 2 border : false, anchorSize : '400', items : [ { title : 'Panel1', anchor : '100%, 25%', // 3 frame : true }, { title : 'Panel2', anchor : '0, 50%', // 4 frame : true }, { title : 'Panel3', anchor : '50%, 25%', // 5 frame : true } ] }); myWin.show(); {1} The parent container, myWin {2} Layout set to ‘anchor’ {3} Panel1’s anchor parameters, 100% width, 25% of parent’s height. {4} Panel2’s anchor parameters, full width, 50% of parent’s height. {5} Panel3’s anchor parameters, full width, 25% of parent’s height. In Listing 5.2, we instantiate a myWin{#1}, an instance of Ext.Window, specifying the layout as ‘anchor’{#2}. The first of the child items, Panel 1 has its anchor parameters{#3} specified as 100% of the parent’s width, and 25% of the parent’s height. Panel 2 has its Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 5 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 anchor parameters{#4} specified a little differently, where the width parameter is 0, which is shorthand for 100%. We set Panel2’s height to 50%. Panel3’s anchor parameters{#5} are set to 50% relative width and 25% relative height. The rendered item should look like Figure 5.5. Figure 5.3 The rendered results of our first implementation of the Anchor layout. Relative sizing with percentages is great, but we also have the option to specify an offset, which allows us to achieve greater flexibility with the Anchor layout. Offsets are calculated as the content body dimension + offset. Generally, offsets are specified as a negative number to keep the child item in view. Lets put on our Algebra hats on for a second and remember that adding a negative integer is exact same as subtracting an absolute integer. Specifying a positive offset would make the child’s dimensions greater than the content body’s, thus requiring a scroll bar. Lets explore offsets by using the previous example by modifying only the child item XTypes from Listing 5.7: items : [ { title : 'Panel1', anchor : '-50, -150', frame : true }, { title : 'Panel2', anchor : '-10, -150', frame : true } ] Licensed to Alison Tyler Download at Boykma.Com 6 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 The rendered panel from the preceding layout modification should look like figure 5.3. We reduced the number of child items to only two to easily explain how offsets work and how they can cause you a lot of pain. Figure 5.4 Using offsets with an Anchor layout with sizing calculations. It is very important to dissect what’s going on, which will require us to do a little math. Through inspecting the DOM with Firebug, I learned that the window’s content body is 285 pixels high and 288 pixels wide. Using simple math, we can easily determine what the dimensions of Panel1 and Panel 2 should be. Panel1 Width = 288px - 50px = 238px Panel1 Height = 285px - 150px = 135px Panel2 Width = 288px - 10px = 278px Panel2 Height = 285px - 150px = 135px We can easily see that both child panels fit perfectly within the Window. If we add the height of both of the panels, we see that it they easily fit with a total of only 270px. But what happens if you resize the window vertically? Notice any strangeness? Increasing the Window’s height by any more than 15 pixels results in Panel2’ being pushed off screen, and scroll bars appearing in the windowBody. Recall that with this layout, the child dimensions are relative to the parent’s content body minus a constant, which is the offset. To combat this problem, we can mix anchor Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 7 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 offsets with fix dimensions. To explore this concept, we’ll only need to modify Panel2’s anchor parameters and add a fix height: { title : 'Panel2', height : '150', anchor : '-10', frame : true } This modification makes Panel2’s height a fixed 150 pixels. The newly rendered window can now be resized to virtually any size and Panel1 will grow to Window content body minus 150 pixels, which leaves just enough vertical room for Panel2 to stay on screen. One neat thing about this is that Panel2 still has the relative width. Anchors are used for a multitude of layout tasks. A sibling of the Anchor layout, the form layout, is leveraged by the Ext.form.FormPanel class by default, but can be used by any Container or descendant that can contain other child items such as Panel or Window. 5.4 The Form layout The form layout is just like the anchor layout, except wraps each child element in a div with the class ‘x-form-item’, which makes each item stack vertically like an outline. It adds a ‘label’ element in front of each of the child items, using the element’s ‘for’ attribute, which when clicked, focuses on the child item. Listing 5.3 The form Layout var myWin = new Ext.Window({ height : 180, width : 200, bodyStyle : 'padding: 5px', layout : 'form', // 1 labelWidth : 50, // 2 defaultType : 'field', // 3 items : [ { fieldLabel : 'Name', // 4 width : 110 }, { fieldLabel : 'Age', // 5 width : 25 }, { xtype : 'combo', // 6 fieldLabel : 'Location', width : 120, store : [ 'Here', 'There', 'Anywhere' ] }, { xtype : 'textarea', // 7 Licensed to Alison Tyler Download at Boykma.Com 8 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 fieldLabel : 'Bio' }, { xtype : 'panel', // 8 fieldLabel : '', labelSeparator : '', frame : true, title : 'Instructions', html : 'Please fill in the form', height : 55 } ] }); myWin.show(); {1} Setting the window’s layout to ‘form’ {2} Setting the default label width to 50px {3} Setting the default XType to ‘field’ {4} First Child Item, Textfield, constant width {5} Second Child Item, Textfield {6} Third Child Item, a combo box {7} Fourth child Item, a text area {8} Fifth Child item, a panel with instructions There is a heck of a lot that we’re doing here to achieve a fairly complex form layout. Like all of the other layouts, we set the Window’s layout{#1} to ‘form’. We set a layout-specific attribute, labelWidth{#2}, to 50 pixels. Remember the label element we discussed earlier? This attribute sets the width of that element. Next, we specify the default XType by setting the ‘defaultType’{#3} attribute to ‘field’, which is used for the first{#4} and second{#5} child items, which automatically creates an instance of Ext.form.Field. The third child item{#6} is an xtype definition of a static combination autocomplete and drop down box, known as a combo box or Ext.form.ComboBox. The fourth child item is a simple xtype for a text area, while the last child item{#7} is a fairly complex XType object, specifying a panel. In order to keep the field label element, but show no text, we set the fieldLabel’s property to a string, containing a single space character. We also remove the label separator character, which is a colon (“:”) by default, by setting it as an empty string. The rendered code should look like Figure 5.5. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 9 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 5.5 Using the form layout. Remember that it is an ancestor to the Anchor layout, which makes it very powerful for dynamically resizing child items. While the layout in figure 5.9 works, it is static and could be improved. What if we wanted the Name, Location and Bio fields to dynamically size with its parent? Remember those anchor parameters? Lets use offsets to better our use of the form layout. Listing 5.4 Using offsets with the form layout { fieldLabel : 'Name', anchor : '-4' // 1 }, { fieldLabel : 'Age', width : 25 }, { xtype : 'combo', fieldLabel : 'Location', anchor : '-4', store : [ 'Here', 'There', 'Anywhere' ] }, { xtype : 'textarea', fieldLabel : 'Bio', anchor : '-4, -134' // 2 }, { xtype : 'panel', fieldLabel : ' ', labelSeparator : '', frame : true, title : 'Instructions', html : 'Please fill in the form', anchor : '-4' } In the preceding code, we are adding anchor parameters to the child items originally defined in Listing 5.5. The rendered changes should look like figure 5.6. When you resize the Licensed to Alison Tyler Download at Boykma.Com 10 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 example window, you’ll see how well the child items resize and conform to their parent container. Figure 5.6 Using offsets to create a much fuller looking form. Always try to remember that the form layout is a direct descendant of the Anchor layout, this way you will not forget to set proper anchor parameters for dynamically resizable forms. There are times where we need complete control over the positioning of the widget layout. The Absolute Layout is perfect for this requirement. 5.5 The Absolute layout Next to the Container layout, the Absolute layout is by far one of the simplest to use. It fixes the position of a child by setting the CSS “position” attribute of the child’s element to “absolute” and sets the top and left attributes to the x and y parameters that you set on the child items. Many designers place HTML elements as a “position:absolute” with CSS, but Ext leverages JavaScript’s DOM manipulation mechanisms to set attributes to the elements themselves, with out having to muck with CSS. Let’s create a window with an absolute layout: Listing 5.5 An absolute layout in action var myWin = new Ext.Window({ height : 300, width : 300, layout : 'absolute', // 1 autoScroll : true, border : false, items : [ { title : 'Panel1', x : 50, // 2 y : 50, height : 100, width : 100, html : 'x: 50, y:50', Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 11 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 frame : true }, { title : 'Panel2', x : 90, // 3 y : 120, height : 75, width : 77, html : 'x: 90, y: 120', frame : true } ] }); myWin.show(); {#1} Setting the window’s layout to ‘absolute’ {#2} Panel1’s X and Y coordinates {#3} Panel2’s X and Y coordinates By now, most of the code in here should look very familiar to you, except for a few new parameters. The first noticeable change should be the Window’s layout{#1} being set to ‘anchor’. We’ve attached two children to this Window. Being that we are using the absolute layout, we need to specify the X and Y coordinates. The first child, Panel1, has its X{#1} (CSS left attribute) set to 50 pixels and Y (CSS top attribute) coordinate set to 50. The second child, Panel2, has its X{#2} and Y parameters set to 90 pixels and 120 pixels. The rendered code should look like Figure 5.7. Figure 5.7 The results of our anchor layout implementation. One of the apparent attributes about this example is that Panel 2 overlaps Panel 1. Panel 2 is on top is because of its placement in the DOM tree. Panel2’s element is below Panel1’s element and being that Panel2’s CSS ‘position’ attribute is set to ‘absolute’ as well, it is going to show ‘above’ Panel1. Always keep the risk of overlapping in mind when you implement Licensed to Alison Tyler Download at Boykma.Com 12 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 this layout. Also, being that the position of the child items are fixed, which does not make the anchor layout an ideal solution for parents that resize. If you have one child item and want it to resize with its parent, the fit layout is the best solution. 5.6 Making components ‘fit’ The ‘fit’ layout forces a Container’s single child to “fit” to its body and is, by far, the simplest of the layouts to use. Listing 5.6 The fit layout var myWin = new Ext.Window({ height : 200, width : 200, layout : 'fit', // 1 border : false, items : [ { title : 'Panel1', // 2 html : 'I fit in my parent!', frame : true } ] }); myWin.show(); {1} The window’s layout set to ‘fit’ {2} The single child widget In the preceding example, we set the Window’s layout to ‘fit’{1} and instantiate a single child, an instance of Ext.Panel{2}. The child’s XType is assumed by the Window’s “defaultType” attribute, which is automatically set to ‘panel’ by the Window’s prototype. The rendered panels should look like figure 5.8. Figure 5.8 Using the fit layout for the first time Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 13 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 The fit layout is a great solution for a seamless look when a Container has one child. Often, however, we have multiple widgets being housed in a container. All other layout management schemes are generally used to manage multiple children. One of the best looking layouts is the Accordion layout, which allows you to vertically stack items, which can be collapsed, showing the users one item at a time. 5.7 The Accordion layout The Accordion layout, a direct descendant of the fit layout, is useful when you want to display multiple Panels vertically stacked, where only a single item can be expanded or contracted. Listing 5.7 The Accordion layout var myWin = new Ext.Window({ // 1 height : 200, width : 300, border : false, title : 'A Window with an accordion layout', layout : 'accordion', // 2 layoutConfig : { // 3 animate : true }, items : [ { // 4 xtype : 'form', title : 'General info', bodyStyle : 'padding: 5px', defaultType : 'field', labelWidth : 50, items : [ { fieldLabel : 'Name', anchor : '-10', }, { xtype : 'numberfield', fieldLabel : 'Age', width : 30 }, { xtype : 'combo', fieldLabel : 'Location', anchor : '-10', store : [ 'Here', 'There', 'Anywhere' ] } ] }, { // 5 xtype : 'panel', title : 'Bio', layout : 'fit', items : { xtype : 'textarea', value : 'Tell us about yourself' } }, { // 6 Licensed to Alison Tyler Download at Boykma.Com 14 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 title : 'Instructions', html : 'Please enter information.', tools : [ // 7 {id : 'gear'}, {id:'help'} ] } ] }); myWin.show(); {1} myWin, an instance of Ext.Window {2} specifying an Accordion layout {3} specifying the Accordion layout’s layoutConfig {4} the first child item, a form panel {5} the second child item, a panel, which contains a textarea {6} the last child item, a vanilla panel, which has some tools Listing 5.7 is quite large so we can demonstrate the usefulness of the Accordion layout. The first thing we do is instantiate a Window, myWin{1}, which has its layout set to ‘accordion’{2}. A new configuration option you have not seen thus far is layoutConfig{3}. Some layout schemes have specific configuration options, which you can define as a configuration option for a Component’s constructor. These layoutConfig parameters can change the way a layout behaves or functions. In this case, we set the layoutConfig for the accordion layout, specifying animate:true, which instructs the accordion layout to animate the collapse and expansion of a child item. Another behavior changing configuration option is activeOnTop, which if set to true, will move the active item to the top of the stack. When working with a layout for the first time, I suggest consulting the API for all the options available to you. Next, we start to define child items, which leverage some of the knowledge we’ve gained thus far. The first child, is a form panel{4}, which uses the anchor parameters we learned about earlier in this chapter. Next, we specify a panel{5} that has its layout set to fit and contains a child text area. Lastly, we define the last child item as a vanilla panel with some tools. The rendered code should look like Figure 5.9. Figure 5.9 The Accordion layout is an excellent way to present the user with multiple items with a single visible component. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 15 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 One important to note that the accordion layout can only function well with Panels and two of its descendants, GridPanel and TreePanel. This is because the Panel (and the two specified subclasses) has what is required for the accordion to function properly. If you desire anything else inside of an accordion such as a tab panel, simply wrap a panel around it and add that panel as a child of the Container that has the accordion layout. While the accordion layout is a good solution for having more than one panel on screen, it has its limitations. For instance, what if you needed to have 10 Components in a particular Container? The sum of the heights of the title bars for each item would take up a lot of valuable screen space. The card layout is perfect for this requirement, allowing you to show and hide child components or “flip” through them. 5.8 The Card layout A direct descendant of the Fit Layout, the Card Layout ensures that its children conform to the size of the Container. Unlike the Fit layout however, the Card layout can have multiple children under its control. This tool gives us the flexibility to create components that mimic wizard interfaces. Except for the initial active item, the Card layout leaves all of the flipping up to the end developer with its publicly exposed setActiveItem method. In order to create a wizard-like interface, we need to create a method which we can control the card flipping: var handleNav = function(btn) { var activeItem = myWin.layout.activeItem; var index = myWin.items.indexOf(activeItem); var numItems = myWin.items.getCount() - 1; var indicatorEl = Ext.getCmp('indicator').el; if (btn.text == 'Forward' && index < numItems) { myWin.layout.setActiveItem(index + 1); } else if (btn.text == 'Back' && index > 0) { myWin.layout.setActiveItem(index - 1); } indicatorEl.update((index + 1) + ' of ' + (numItems + 1)); } In the preceding code, we control the card flipping by determining the active item’s index and setting the active item based on if the Forward or Back button is pressed. We then update the indicator text on the bottom toolbar. Next, lets implement our Card layout. This particular code example is rather long and involving, so please stick with me. Listing 5.8 The Card layout in action var myWin = new Ext.Window({ height : 200, width : 300, border : false, Licensed to Alison Tyler Download at Boykma.Com 16 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 title : 'A Window with a Card layout', layout : 'card', //1 activeItem : 0, //2 items : [ { xtype : 'form', title : 'General info', bodyStyle : 'padding: 5px', defaultType : 'field', labelWidth : 50, items : [ { fieldLabel : 'Name', anchor : '-10', }, { xtype : 'numberfield', fieldLabel : 'Age', width : 30 }, { xtype : 'combo', fieldLabel : 'Location', anchor : '-10', store : [ 'Here', 'There', 'Anywhere' ] } ] }, { xtype : 'panel', autoEl : {}, title : 'Bio', layout : 'fit', items : { xtype : 'textarea', value : 'Tell us about yourself' } }, { title : 'Congratulations', html : 'Thank you for filling out our form!' } ], bbar : [ { text : 'Back', //3 handler : handleNav },'-', { text : 'Forward', handler : handleNav },'->', { xtype : 'box', //4 id : 'indicator', style : 'margin-right: 5px', autoEl : { tag : 'div', html : '1 of 3' } } ] }); Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 17 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 myWin.show(); {1} Setting the layout to ‘card’ {2} Set the Container’s active item to 0 {3} The back button and forward buttons {4} The BoxComponent with the id of ‘indicator’ In Listing 5.8, we detail the creation of a Window, which leverages the Card layout. While most of this should be familiar to you, I feel that we should point out a few things. The first obvious item should be the layout{1} property, which is set to ‘card’. Next, is the activeItem property{2}, which the Container passes to the layout at render time. We set this to 0, which tells the layout to call the child Component’s render method when the Container renders. Next, we define the bottom toolbar, which contains the Back and Forward{3} buttons which call our pervious defined handleNav method and our Box Component{4} that we use to display the index of the current active item. The rendered Container should look like the one in Figure 5.10. Figure 5.10 Our first card layout implementation with a fully interactive navigation toolbar. Pressing the Back or Forward buttons will invoke the handleNav method, which will take care of the card “flipping” and update the indicator BoxComponent. Remember that with the Card layout, the logic of the active item switching is completely up to the end developer to create and manage. In addition to the previously discussed layouts, Ext offers a few more schemes. The Column layout is one of the favorite schemes among UI developers for organizing UI columns that can span the entire width of the parent Container. 5.9 The Column layout Organizing components into columns allows you to display multiple components in a Container side by side. Like the Anchor layout, the Column layout allows you to set the absolute or relative width of the child Components. There are some things to look out for when using this layout. We’ll highlight these in just a little bit, but first, lets construct a column layout window. Licensed to Alison Tyler Download at Boykma.Com 18 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Listing 5.9 Exploring the column layout var myWin = new Ext.Window({ height : 200, width : 400, autoScroll : true, // 1 id : 'myWin', title : 'A Window with a Card layout', layout : 'column', // 2 defaults : { frame : true }, items : [ { title : 'Col 1', id : 'col1', columnWidth : .3 // 3 }, { title : 'Col 2', html : "20% relative width", columnWidth : .2 }, { title : 'Col 3', html : "100px fixed width", width : 100 // 4 }, { title : 'Col 4', frame : true, html : "50% relative width", columnWidth : .5 // 5 } ] }); myWin.show(); {1} Automatically scroll the container {2} Set the layout to ‘column’ {3} Set a relative column width attribute, 30% {4} Set a fixed width of 100 pixels {5} Another relative width. In a nutshell, the column layout is really easy to use. Declare child items, specify relative or absolute widths or a combination of both, like we do here. In Listing 5.9, we set the autoScroll{1} property of the Container to true, which ensures that scrollbars will appear if the composite of the child component dimensions grows beyond that of the Container’s. Next, we set the layout property to ‘column’{2}. We then go ahead and declare four child Components. The first of which has its relative width set to 30% via the columnWidth{3} attribute. Also, the second child has its relative width set to 20%. We mix things up a bit by setting an absolute width for the third child to 100 pixels{4}. Lastly, we set a relative width{5} for the last child to 50%. The rendered example should look like Figure 5.11. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 19 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 5.11 Our first column layout, which uses relative column widths with a fixed width entity. If we tally up the relative widths, we see that they total up to 100%. How can that be? Three Components, taking 100% width and a fixed width Component? To understand how this is possible, we need to dissect how the Column layout actually sets the sizes of all of the child components. Lets put on our mathematical thinking caps for a moment. The meat of the Column layout its onLayout method, which calculates the dimensions of the Container’s body, which in this case, is 388 pixels. It then goes through all of its direct children to determine the amount of available space to give to any of the children with relative widths. To do this, it first subtracts the width of each of the absolute-width child components from the known width of the containers body. In our example, we have one child with an absolute width of 100 pixels. The column layout calculates the difference between 388 and 100, which equals 288 (pixels). Now that the Column layout knows exactly how much horizontal space it has left, it can set the size of each of the child components based on the percentage. It now goes through each of the children and sizes them based on the known available horizontal width of the container’s body. It does this by multiplying the percentage (decimal) by the available width. Once complete, the sum of the widths of relatively sized Components turns out to be just about 288pixels. Now that we understand the width calculations for this layout, lets change our focus to the height of the child items. Notice how the height of the child components does not equal the height of the container body. This is because the column layout does not manage the height of the child components. This causes an issue with child items that may grow beyond the height of its Container’s body height. This is precisely why we set autoScroll to true for the Window. We can exercise this theory by adding an extra large child to the ‘Col 1’ Component by entering the following code inside of Firebug’s JavaScript input console. Make sure you have a virgin copy of Listing 5.14 running in your browser. Ext.getCmp('col1').add({ height : 250, title : 'New Panel', frame : true }); Ext.getCmp('col1').doLayout(); Licensed to Alison Tyler Download at Boykma.Com 20 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 You should now see a panel embedded into the ‘Col 1’ panel with its height exceeding that of the window’s Body. Notice how scroll bars appear in the window. If we did not set autoScroll to true, our UI would look cut off and might have its usability reduced or halted. You can scroll vertically and horizontally. The reason you can scroll vertically is because Col1’s over-all height is greater than that of the Window’s body. That is acceptable. The horizontal scrolling is the problem in this case. Recall that the Column layout only calculated 288 pixels to properly size the three columns with relative widths. Being that the vertical scrollbar is now visible, the physical amount of space from which the columns can be displayed is reduced by the width of the vertical scrollbar. To fix this issue, you must call doLayout on the Parent Container: Ext.getCmp('myWin').doLayout(), which will force a recalculation of the available horizontal space and resize the relatively sized columns so the Container’s body need not scroll horizontally. Remembering to call the parent’s doLayout method when adding a component to any of the direct children will help your UIs looking great. As you can see, the Column layout is great for organizing your child components in columns. With this layout, however, there are two limitations. All child items are always left justified and their heights are unmanaged by the parent Container. Ext provides us with the HBox layout that overcomes the limitations of the Column layout and extend far beyond its capabilities. 5.10 HBox and VBox layouts New to version 3.0 are the HBox layout’s behavior is very similar to the Column model, where it displays items in columns, but allows for much greater flexibility. For instance, you can change the alignment of the child items both vertically and horizontally. Another great feature of this layout scheme is the ability to allow the columns or rows to stretch to their parent’s dimensions if required. Lets dive into examining the HBox layout, where we’ll create a Container with three child panels for us to manipulate. Listing 5.10 HBox layout, exploring the packing configuration new Ext.Window({ layout : 'hbox', // 1 height : 300, width : 300, title : 'A Container with an HBox layout', layoutConfig : { // 2 pack : 'start' }, defaults : { frame : true, }, items : [ { title : 'Panel 1', height : 100 }, { title : 'Panel 2', Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 21 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 height : 75, width : 100 }, { title : 'Panel 3', height : 200 } ] }).show(); {1} Setting the layout to hbox {2} Specifying the layout configuration. For Listing 5.10, we set the layout to hbox{1} and specify the layoutConfig{2} configuration object. We created the three child panels with irregular shapes, allowing for us to properly exercise the different layout configuration parameters for which we can specify two, pack and align. Where pack means “vertical alignment” and align means “horizontal alignment”. Being able to understand the translated meanings for these two parameters is important as they are flipped for the HBox’s cousin, the VBox Layout. The pack parameter accepts three possible values; start, center and end. In this context, I like to think of them as left, center, and right. Modifying that parameter in Listing 5.15 will result in one of the rendered windows in Figure 5.12. The default value for the pack attribute is ‘start’. Figure 5.12 Different results with the three pack options. The align parameter accepts four possible values: ‘top’, ‘middle’, ‘stretch’ and ‘stretchmax’. Remember that with the HBox, the align property specifies vertical alignment. Remember that the default parameter for align is ‘top’. In order to change how the child panels are vertically aligned, we need to override the default, by specifying it in the layoutConfig object for the Container. Figure 5.13 illustrates how we can change the way the children are sized and arranged based on a few different combinations. Licensed to Alison Tyler Download at Boykma.Com 22 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 5.13 The ‘stretch’ alignment will always override any height values specified by on the child items. Specifying a value of ‘stretch’ for the align attribute instructs the HBox layout to resize the child items to the height of the Container’s body, which overcomes one limitation of the column layout. The last configuration parameter that we must explore is “flex”, which is similar to the columnWidth parameter for the columnLayout and gets specified on the child items. Unlike the columnWidth parameter, the flex parameter is interpreted as a weight or a priority instead of a percentage of the columns. Lets say for instance, you would like each of the columns to have equal widths. Simply set each column’s flex to the same value, and they will all have equal widths. If you wanted to have two of the columns expand to a total of one half of the width of the parent’s container and the third to expand to the other half, make sure that the flex value for each of the first two columns is exactly half of the third column. For instance: items : [ { title : 'Panel 1', flex : 1 }, { title : 'Panel 2', flex : 1 }, { title : 'Panel 3', flex : 2 } ] Stacking items vertically is also possible with the VBox Layout, which follows exactly the same syntax as the HBox Layout. To use the VBox layout, simply modify Listing 5.15 and change the layout to ‘vbox’ and refresh the page. Next, you can apply the flex parameters described above to make each of the panels relative in height to the parent Container. I like to think of VBox as the container layout on steroids. Contrasting the VBox Layout against HBox Layout, there is one parameter change. Recall that the align parameter for the HBox accepts a value of “top”. For the VBox Layout, however, we specify, “left” instead of “top”. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 23 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Now that we have mastered HBox and VBox layouts, we will switch gears and the Table Layout, where you can position child Components, just like a traditional HTML table. 5.11 The Table layout The table layout gives you complete control over how you want to visually organize your components. Many of us are used to building HTML tables the traditional way, where we actually write the HTML code. Building a table of Ext Components however, is different as we specify the content of the table cells in a single dimension array, which can get a little confusing. I’m sure that once you’re done with these exercises, you’ll be an expert in this layout. Lets create a 3 x 3 table layout: Listing 5.11 A vanilla table layout var myWin = new Ext.Window({ height : 300, width : 300, border : false, autoScroll : true, title : 'A Window with a Table layout', layout :'table', // 1 layoutConfig : { columns : 3 // 2 }, defaults : { // 3 height : 50, width : 50 }, items : [ { html : '1' }, { html : '2' }, { html : '3' }, { html : '4' }, { html : '5' }, { html : '6' }, { html : '7' }, { html : '8' }, { html : '9' } ] }); myWin.show(); Licensed to Alison Tyler Download at Boykma.Com 24 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 {1} Specifying the layout as table {2} Set the number of columns for the table to 3 {3} Set default size for each box to 50x50 The code in Listing 5.11 creates a Window Container that has nine boxes stacked in a 3x3 formation like in Figure 5.15. By now, most of this should seem very familiar to you, but I want to make sure we highlight a few items. The most obvious of which should be the layout parameter{1} being set to ‘table’. Next, we set a layoutConfig{2} object, which sets the number of columns. Always remember to set this property when using this layout. Lastly, we’re setting the defaults{3} for all of the child items to 50 pixels wide by 50 pixels high. Figure 5.14 The results of our first simple table layout. Often we need sections of the table to span multiple rows or multiple columns. To accomplish this, we specify either the rowspan or colspan parameters explicitly on the child items. Lets modify our table so the child items can span multiple rows or columns Listing 5.12 Exploring rowspan and colspan items : [ { html : '1', colspan : 3, // 1 width : 150 }, { html : '2', rowspan : 2, // 2 height : 100 }, { html : '3' }, { html : '4', rowspan : 2, // 3 height : 100 }, { html : '5' }, { html : '6' Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 25 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 }, { html : '7' }, { html : '8' }, { html : '9', colspan : 3, // 4 width : 150 } ] {1} Set colspan to 3 and width to the total width to 150 pixels {2} Set rowspan to 2 and height to 100 pixels {3} Set rowspan to 2 and height to 100 pixels {4} Set colspan to 3 and width to 150 pixels In Listing 5.12, we reuse the existing Container code from Listing 5.14 and replace the child items array. We set the colspan attribute for the first panel{1} to 3, and manually set its width to fit the total known width of the table, which is 150 pixels. Remember that we have 3 columns of default 50x50 child containers. Next, we set the rowspan of the second child{2} item to 2 and its height to the total of two rows, which is 100 pixels. We do the exact same thing for Panel 4{3}. The last change involves panel 9, which has the exact same attributes as panel 1{4}. The rendered change should look just like Figure 5.15. Figure 5.15 When using the table layout, you could specify rowspan and colspan for a particular Component, which will make it occupy more than one cell in the table. When using the Table layout, you should remember a few things. First of which is to determine the total number of columns that will be used and specify it in the layoutConfig parameter. Also, if you’re going to have Components span rows and/or columns be sure to Licensed to Alison Tyler Download at Boykma.Com 26 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 set their dimensions accordingly, otherwise the components laid out in the table will not seem to be aligned correctly. The Table Layout is extremely versatile and can be used to create any type of box-based layout that your imagination conjures up with the main limitation being that there is no parent-child size management. Moving to our last stop on the Ext Layout journey, we reach the ever-so-popular Border Layout, where you can divide any container into five collapsible regions that manage their children’s size. 5.12 The Border layout The Border Layout made its début in 2006, back when Ext was merely more than an extension to the YUI Library and has matured into an extremely flexible and easy to use layout that provides full control over its sub-parts or “regions”. These regions are aptly named by polar coordinates: north, south, east, west and center. Figure 5.16 illustrates what a border layout implementation from the Ext SDK. Figure 5.16 The border layout is what attracts many new developers to the Ext Framework and is widely used in many applications to divide the screen into task-specific functional areas. Each region of a border layout is actually managed by the BorderLayout.Region class, which is what provides all of the UI and programmatic controls to that specific division of the layout scheme. Depending on the configuration options provided, the region can be resized or collapsed by the user. There are also options to limit the resize of the region or prevent it from being resized altogether. To explore the Border Layout and the Region class, we will use the Viewport class, which we discussed in 5.1.6, earlier in this chapter. Listing 5.13 Flexing the Border Layout Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 27 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 new Ext.Viewport({ layout : 'border', defaults : { // 1 frame : true, split : true }, items : [ { title : 'North Panel', // 2 region : 'north', height : 100, minHeight : 100, maxHeight : 150, collapsible : true }, { title : 'South Panel', // 3 region : 'south', height : 75, split : false, margins : { top : 5 } }, { title : 'East Panel', // 4 region : 'east', width : 100, minWidth : 75, maxWidth : 150, collapsible : true }, { title : 'West Panel', // 5 region : 'west', collapsible : true, collapseMode : 'mini' }, { title : 'Center Panel', // 6 region : 'center' } ] }); {1} Setting split to true in the defaults object, which is a BorderLayout.Region specific parameter {2} The static “north” region panel. {3} The resizable “south” region panel, which is also collapsible {4} The resizable and collapsible East panel {5} The mini-collapse west panel {6} The no-frills Center panel. In Listing 5.13, we accomplish quite a lot using the viewport a bit in just a few lines of code. We set the layout to “border”{1} and set split to true in the defaults configuration object. Being that there is a lot going on here at one time, feel free to reference Figure 5.17, which depicts what the rendered code will look like. While all regions are technically divided, the split parameter instructs the Border Layout to render a five pixel high (or wide) divider between the center and regions. This divider is used as the resize handles for the regions. In order to work this magic, the border layout Licensed to Alison Tyler Download at Boykma.Com 28 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 employs the BorderLayout.SplitRegion class, which creates an absolutely position invisible div that intercepts the click and drag action of the user. When the drag action occurs, a proxy div appears, which is a direct sibling of the split bar handle div, allowing users to preview exactly how wide or high they are about to resize a region to. Next, we begin to instantiate child items, which have BorderLayout.Region specific parameters. In order to review many of them, we make each region’s behavior different from the other. Figure 5.17 The Border Layout’s versatility and ease of use makes it one of the most widely used in Ext- based RIAs. For the first child{2}, we set the region property to ‘north’ to ensure that it is at the top of the border layout. We play a little game with the BoxComponent-specific parameter, height and the Region-specific parameters minHeight and maxHeight. By specifying a height of 100, we’re instructing the Region to render the panel with an initial height of 100 pixels. The minHeight instructs the Region to not allow the split bar to be dragged beyond the coordinates that would make the northern region the minimal height of 100. The same is true for the maxHeight parameter, except it applies to expanding the region’s height. We also specify the Panel-specific parameter of collapsible as true, which instructs the region to allow it to be collapsed to a mere 30 pixels high. Defining the south region, the Viewport’s second child{2}, we play some games to make prevent it from being resized, but work to keep the layouts 5 pixel split between the regions. Setting the split parameter to false, we instruct the region to not allow it to be resized. Doing so also instructs the Region to omit the 5 pixel split bar, which would make the layout somewhat visually incomplete. In order to achieve a façade-split bar, we specify a Region-specific ‘margins’ parameter, which specifies that we want the south region to have a Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 29 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 5 pixel buffer between itself and anything above it. One word of caution about this however; while the layout will now look complete, end-users may try to resize it, possibly causing frustration on their end. The third child{3} is defined as the east region. This region is similarly configured to the north panel except it has sizing constraints that are a bit more flexible. Where the northern region starts its life out at its minimum size, the eastern region starts its life between its minWidth and maxWidth. Specifying size parameters like these allow the UI to present a region in a default or suggested size, but allow the panel to be resized beyond its original size. The western region{4} is has a special Region-specific parameter, collapseMode, set to the string ‘mini’. Setting this parameter that way instructs Ext to collapse a panel down to a mere five pixels, providing more visual space for the center region. Figure 5.18 illustrates how small By allowing the split parameter to stay as true (remember our defaults object) and not specifying minimum or maximum size parameters, the western region can be resized as far as the browser will physically allow. Figure 5.18 Our Border layout where two of the regions, north and east, are collapsed in regular mode and the west panel is collapsed in miniature mode. The last region is the center region{5}, which is the only required region for the border layout. While our center region seems a bit bare, it is very special indeed. The center region is generally the canvas in which developers place the bulk of their RIA UI components and its size is dependent on its sibling region’s dimensions. For all of its strengths, the Border Layout has one huge disadvantage, which is that once a child in a region is defined/created it cannot be changed. Because the BorderLayout.Region is a base class and does not extend container, it does not have the Licensed to Alison Tyler Download at Boykma.Com 30 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 power to replace a child once its instantiated. The fix for this is extremely simple. For each region where you wish to replace components, simply specify a Container as a region. Lets exercise this by replacing the center region section for listing 5.19: { xtype : 'container', region : 'center', layout : 'fit', id : 'centerRegion', autoEl : {}, items : { title : 'Center Region', id : 'centerPanel', html : 'I am disposable', frame : true } } Remember that the viewport can only be created once, so a refresh of page where the example code is required. The refreshed viewport should look nearly identical to Figure 5.18, except the center region now has HTML showing that it’s disposable. In the example above, we simply define the container XType with the layout of fit and an ID that we can leverage with FireBug’s JavaScript console. Recalling our prior discussion and exercises over adding and removing child Components to and from a Container, can you remember how to get a reference to a Component via its ID and remove a child? If you can, excellent work! If you can’t it’s OK, I’ve already worked it out for us. But be sure to review the prior sections as they are extremely important to managing the EXT UI. Lets take a swipe at replacing the center region’s child component: Listing 5.14 Replacing a Component in the center region var centerPanel = Ext.getCmp('centerPanel'); var centerRegion = Ext.getCmp('centerRegion'); centerRegion.remove(centerPanel, true); centerRegion.add({ xtype : 'form', frame : true, bodyStyle : 'padding: 5px', defaultType : 'field', title : 'Please enter some information', defaults : { anchor : '-10' }, items : [ { fieldLabel : 'First Name' }, { fieldLabel : 'Last Name' }, { xtype : 'textarea', fieldLabel : 'Bio' } ] }); Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 31 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 centerRegion.doLayout(); Listing 5.14 leverages everything we’ve learned thus far regarding Components, Containers and Layouts, providing us with the flexibility to replace the center region’s child, a panel with a form panel with relative ease. You can use this pattern in any of the regions to replace items at will. 5.13 Summary We took a lot of time to explore them many and versatile Ext Layout schemes. In doing so, we learned some of the strengths, weaknesses and pitfalls. Remember that while many layouts can do similar things, each has its place in a UI. Finding the correct layout to display components may not be immediately apparent and will take some practice if you’re new to UI design altogether. If you are exiting this chapter and are not 100% comfortable with the material, I would like to suggest moving forward and returning back to it after some time has past and the material has had some time to sink in. Now that we have much of the core topics behind us, put your seatbelt on because we’re going to be in for a wild ride, where we really start to learn more about and use Ext’s UI widgets starting off with Form Panels. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 06 Ext takes Form Developing and designing forms is a common task for web developers. Ext builds upon the basic HTML input fields to both add features of the developer and enhance the user experience. For instance, let’s say a user is required to enter HTML into a form. Using out of the box text area input field, the user would have to write the HTML by hand. This is not required with the HTML Editor, where you get a full WYSIWYG input field, allowing the user to input and manipulate richly formatted HTML easily. In this chapter, we’ll look into the FormPanel and learn about many of the Ext form input classes. We’ll also see how we can leverage what we know about layouts and the Container model to build a complex form and use that implementation to submit and load the data via AJAX. 6.1 FormPanel at a glance With the Ext FormPanel you can submit data using AJAX, provide live feedback to users if a field deemed invalid and perform pseudo AJAX file uploads. Because the FormPanel is a descendant of the Container class, you can easily add and remove input fields to create a truly dynamic form. An added benefit is the ability to leverage other layouts or components, such as the tab panel with the card layout, to create robust forms that take considerably less screen space than traditionally laid out single-page forms. Because the FormPanel is a direct descendant of Panel, you get all of Panel’s features, which include top and bottom toolbars and the button footer bar (fbar). Before we start to work with the FormPanel, we should take a glimpse at the underlying form element controller, known as the BasicForm. 6.1.1 Configuring the underlying form controller The FormPanel leverages the BasicForm class, which is what serves as the controller for the FormPanel’s form DOM element. While configuring your FormPanel, you can specify Licensed to Alison Tyler Download at Boykma.Com 2 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 properties for the BasicForm via the initialConfig property, which is defined as an object. Here is a sample FormPanel with an initialConfig object: var x = new Ext.Form.FormPanel({ ..., initialConfig : { method : 'GET', fileUpload : true, standardSubmit : false, baseParams : { foo : 'bar', sna : 'fu' } }, ... }); As illustrated above, some common attributes that developers usually set are method, baseParams, fileUpload and standardSubmit. In order to override the default HTTP POST method, you can specify the method property. One special attribute is fileUpload, which specifies if the form is going to perform a file upload function. If the form is to perform a standard submit operation (non-AJAX), you set the standardSubmit to true. The baseParams serves as an object, which provides a means to send the same parameters for each submission and can be used to replace the typical hidden fields. Next, we’ll begin our exploration of the various input fields that Ext provides. Once we’re comfortable with them, we’ll circle back to the FormPanel class and learn how we can submit and load data via AJAX. 6.2 The TextField The Ext TextField features to the existing HTML input field such as basic validations, a custom validation method, automatic resizing and keyboard filtering. To utilize some of the more powerful features such as keyboard filters (masks) and automatic character stripping, you will need to know a little about Regular Expressions. If you’re new to Regular Expressions, there is a plethora of information on the Internet. We’re going to explore quite a few features of the text field at once. Please stay with me, as some of example code can be pretty lengthy. Because the TextField class is a descendant of Component, we could renderTo or applyTo an element on the page. Instead we’re going to build them as children of a form panel, which will provide us a better presentation. To start, we’ll create our items array, which will contain the XType definitions of the different text fields. Listing 6.1 Our text fields Ext.QuickTips.init(); var fpItems =[ { fieldLabel : 'Alpha only', // 1 Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 3 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 allowBlank : false, // 2 emptyText : 'This field is empty!', // 3 maskRe : /[a-z]/i // 4 }, { fieldLabel : 'Simple 3 to 7 Chars', allowBlank : false, minLength : 3, // 5 maxLength : 7 }, { fieldLabel : 'Special Chars Only', stripCharsRe : /[a-zA-Z0-9]/ig // 6 }, { fieldLabel : 'Web Only with VType', vtype : 'urlOnly' // 7 } ]; {1} The field label is what is generally what is used to identify all fields in a form {2} Setting allowBlank to false, ensures that Ext performs the basic blank validation on the field {3} “Empty text” shows up in a form as helper when the field is empty {4} Ensure only alpha characters are entered here {6} Set the minimum and maximum number of characters {6) A regular expression to strip out any non-special character {7} Make use of the custom vType we will create later. In the preceding code example we work a lot of angles to demonstrate the capabilities of the simple text field. We create four text fields in the fpItems array. One of the redundant attributes that each child has is fieldLabel{#1}, which describes to the form layout (remember the form panel uses the form layout by default) what text to place in the label element for the field element. For the first child, we ensure that the field cannot be blank by specifying allowBlank{#2} as false, which ensures we use one of Ext’s basic field validations. We also set a string value for emptyText{#3}, which displays helper text and can be used as a default value. One important thing to be aware of is that it actually gets sent as the field’s value during its form submission. Next, we set maskRe{#4}, a regular expression mask, to filter keystrokes that resolve to anything other than alpha characters. The second text field is built so it cannot be left blank and must contain from 3 to 7 characters to be valid. We do this by setting the minLength{#6} and maxLength parameters. The third textfield can be blank, but has automatic alphanumeric character striping. We enable automatic stripping by specifying a valid regular expression for the stripCharsRe{#6} property. For the last child item, we’re going to veer off course for a bit to explore VTypes. Our last child item is a plain text field that makes use of a custom vtype{#7}, which we’ll build out in a little. A VType is a custom validation method that is called automatically by the form field by a field losing focus or some time after the field is modified. To create your own VType, you can use a regular expression OR a custom function. The anatomy of a VType is simple and can contain up to three ingredients. The validation method is the only required item with the input mask regular expression and invalid text string as optional. Licensed to Alison Tyler Download at Boykma.Com 4 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 The name of the VType is the validation method while the mask and text properties are a concatenation of the name with “Mask” or “Text”. Let’s create our custom VType: var myValidFn = function(v) { var myRegex = /https?:\/\/([-\w\.]+)+(:\d+)?(\/([\w/_\.]*(\?\S+)?)?)?/; return myRegex.test(v); } Ext.apply(Ext.form.VTypes, { urlOnly : myValidFn, urlOnlyText : 'Must a valid web url' }); OK, don’t run away! The regular expression is scary, I know. It does serve a purpose though, and you’ll see what I mean in a little bit. Our validation method, myValidFn, contains our monster regular expression and returns the result of using it test method, where we pass v, which is the value of the text field at the time the VType’s validation method is called. Next, we apply an object to the Ext.form.VTypes singleton, which contains urlOnly - the reference to our validation method. Our VType is now known to Ext.form.VTypes as ‘urlOnly’, and is why we set the vtype property as such on the last textfield. We also set the urlOnlyText property for the vtype as a string with our custom error message. Now that we have explored VTypes, let’s go on ahead to build the form from which our text fields will live: Listing 6.2 Building the FormPanel for our text fields var fp = new Ext.form.FormPanel({ renderTo : Ext.getBody(), width : 400, height : 160, title : 'Exercising textfields', frame : true, bodyStyle : 'padding: 6px', labelWidth : 126, defaultType : 'textfield', // 1 defaults : { msgTarget : 'side', // 2 anchor : '-20' }, items : fpItems }); {1} Overriding the default XType to textfield {2} Specifying default target for the validation message. Because we’ve already gone over the form layout, most of the code construct in Listing 6.2 should be very familiar to you. But, let’s review a few key items relating to the form layout and Component model. We override the default Component XType by setting the defaultType{#1} property to ‘textfield’, which, if you can recall, will ensure our objects are resolved into text fields. We also setup some defaults{#2}, which ensure our error Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 5 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 message target is to the right side of the field and our anchor property is set. Lastly, we reference the FormPanel’s items to the fpItems variable that we created earlier that contains the four text fields. The rendered FormPanel should look like figure 6.1. Figure 6.1 The rendered results of our FormPanel which contains our four text fields. Notice how in Figure 6.1 we have a little extra space to the right of the text fields. This is because we wanted to ensure that validation error messages are displayed to the right of the fields. This is why we set msgTarget to ‘side’ for our defaults object in our FormPanel definition. We can invoke validation one of two ways: Focus and blur (lose focus) of a field or invoking a form-wide isValid method call: fp.getForm().isValid(). Here is what the fields look like after validation has occurred: Figure 6.2 “Side” validation error messages. Each field can have its own ‘msgTarget’ property, which can be any of five possible attributes: qtip Displays an Ext quick tip on a mouse hover title Shows the error in the default browser “title” tooltip under Positions the error message below the field side Renders an exclamation icon to the right of the field [element id] Adds the text of the error message as the innerHTML of the target element It is important to note that the msgTarget property only effects how the error message is displayed when the field is inside a form layout. If the text field is rendered to some Licensed to Alison Tyler Download at Boykma.Com 6 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 arbitrary element somewhere on the page (i.e. using renderTo or applyTo), the msgTarget will only be set to title. I encourage you to spend some time experimenting with the different msgTarget values, this way when it comes down to building your first real-world form, you’ll have a good understanding of the way they work. Let’s see how we can create password and file upload fields using the TextField. 6.2.1 Password and File select fields To create a password field in HTML, you set its type attribute to ‘password’. Likewise, for a file input field, you set type to ‘file’. In Ext, to generate these var fpItems =[ { fieldLabel : 'Password', allowBlank : false, inputType : 'password' }, { fieldLabel : 'File', allowBlank : false, inputType : 'file' } ]; Here is a rendered version of the password and file input fields in a form panel: Figure 6.3 Our password and file upload fields with data filled in (left) and an example of the side validation error icons (right). When using file upload fields, remember to configure the underlying form element with fileUpload : true, otherwise your files will never get submitted. Also, if you haven’t noticed it, the file upload field in Figure 6.3 (right) does not have a red bounding box around it. This is because of the browser’s security model preventing styling of the upload field. 6.2.2 Building a TextArea The TextArea extends TextField and is a multiline input field. Constructing a TextArea is just like constructing a TextField, except you have to keep the component’s height into consideration. Here is an example TextArea with a fixed height but a relative width. { xtype : 'textarea', fieldLabel : 'My TextArea', Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 7 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 name : 'myTextArea', anchor : '100%', height : 100 } It’s as easy as that. Let’s take a quick look at how we can leverage the NumberField, which is another descendant of TextField. 6.2.3 The convenient NumberField Sometimes requirements dictate that we place an input field that only allows numbers to be entered. We could do this with the text field and apply our own validation, but why reinvent the wheel? The NumberField does pretty much all of the validation for us for integer and floating numbers. Lets create a NumberField that accepts floating point numbers with the precision to thousandths and only allow a specific value. { xtype : 'numberfield', fieldLabel : 'Numbers only', allowBlank : false, emptyText : 'This field is empty!', decimalPrecision : 3, minValue : 0.001, maxValue : 2 } In the above example, we create our NumberField configuration object. In order to apply our requirements, we specify decimalPrecsion, minValue and maxValue properties. This ensures that any floating number written with greater precision than 3 is rounded up. Likewise the minValue and maxValue properties are applied to ensure that valid range is 0.001 to 2. Any number outside of this range is considered invalid and Ext will mark the field as such. The NumberField looks exactly like the text field when rendered. There are a few more properties that can assist with the configuration of the NumberField. Please see the API documentation at http://extjs.com/docs/?class=Ext.form.NumberField for further details. Now that we have looked at the TextField and two of its subclasses, the TextArea and NumberField, lets look at its distant cousin, the ComboBox. 6.3 TypeAhead with the ComboBox The cleverly named ComboBox input field is like a Swiss Army Knife of all text input fields. It is a combination of a general text input field and a general dropdown box to give you a very flexible and highly configurable combination input field. The ComboBox has the ability for automatic text completion (known as “type ahead”) in the text input area and coupled with a remote data store, can work with the server side to filter results. If the combo box is performing a remote request against a large dataset, you can enable result paging via setting the pageSize property. The following is an illustration of the anatomy of a remote loading and paging ComboBox. Licensed to Alison Tyler Download at Boykma.Com 8 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 6.4 An example UI of a remote-loading and paging ComboBox with type ahead Before we look at how the ComboBox works, we should explore how to construct one. Being that you’re familiar with how to lay out child items, I think this is an excellent opportunity to leverage your new newly gained experience. So moving forward, when we discuss items that don’t contain children, such as fields, I will leave it up to you to build a container. As a hint, you can use the Form Panel in listing 6.2. 6.3.1 Building a ‘local’ ComboBox Creating a TextField is extremely simple compared to building a combo box. This is because the ComboBox has a direct dependency on a class called the DataStore, which is the main tool to manage data in the framework. We will just scratch the surface of this supporting class here and will go into much further detail in chapter 6. Lets move on to build our first combo box using an XType configuration object: Lisiting 6.3 Building our first ComboBox var mySimpleStore = new Ext.data.ArrayStore({ // 1 data : [ ['Jack Slocum'], ['Abe Elias'], ['Aaron Conran'], ['Evan Trimboli'] ], fields : ['name'] }); var combo = { xtype : 'combo', fieldLabel : 'Select a name', store : mySimpleStore, // 2 displayField : 'name', // 3 typeAhead : true, mode : 'local' // 4 } {#1} Building our first ArrayStore {#2} Specifying the store in the combo {#3} Setting the display field Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 9 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 {#4} Ensure there are no remote data AJAX requests In listing 6.3, we construct a simple store that reads array data, known as an ArrayStore{1}, which is a preconfigured extension of the Ext.data.Store class, which makes it easy for us to create a store that digests array data. We populate the consumable array data and set it as the data property for the configuration object. Next, we specify the fields property as an array of data points from which the DataStore will read and organize Records. Being that we only have one data point per array in our array, we specify only a single point and give it a name of ‘name’. Again, we’ll go into much greater detail on the DataStore further on, where we’ll learn the entire gamut from Records to connection Proxies. We specify our combo as a simple POJSO, setting the xtype property as ‘combo’ to ensure that its parent container will call the correct class. We specify the refrence of our previously created simple store as the store{2} property. Remember the fields property we set for the store? Well, the displayField{3} is directly tied to the fields of the data store that the combo box is using. Being that we have a single field, we will specify our displayField with that single field, which is ‘name’. Lastly, we set the mode{4} to ‘local’, which ensures that the DataStore does not attempt to fetch data remotely. This attribute is extremely important to remember because the default value for mode is ‘remote’, which ensures that all data is fetched via remote requests. Not remembering to set it to remote will cause some pain. Here is what the combo box looks like rendered: Figure 6.5 An example rendering of our ComboBox from Listing 6.3 inside of a Window. To exercise the filtering and type ahead features, you can immediately start to type inside the text input field. Now our record set only contains four records, but we can begin to see how this stuff works. Entering a simple ‘a’ into the text field will filter the list and display only two names in the list box. At the same time, the ComboBox will type ahead the rest of the first match, which will show up as ‘be Elias’. Likewise, entering in ‘aa’, will result in the store filtering in all but a single record and the type ahead will fill in the rest of the text, ‘ron Coran’. There you have it, a nice recipe for a local ComboBox. Using a local ComboBox is great if you have a minimal amount of static data. It does have its advantages and disadvantages however. Its main advantage being that the data does not have to be fetched remotely. This, however ends up being a major disadvantage when there is an extreme amount of data to parse through, which would make the UI slow Licensed to Alison Tyler Download at Boykma.Com 10 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 down, sputter or even grind to a halt showing that dreaded “This script is taking too long” error box. This is where the remote loading ComboBox can be called to service. 6.3.2 Implementing a ‘remote’ ComboBox Using a remote ComboBox is somewhat more complicated than a static implementation. This is because you have a server side code to manage, which will include some type of server side store like a database. To keep our focus on the ComboBox, we’ll use the pre- constructed PHP code at http://tdg-i.com/dataQuery.php on my site, which contains randomly generated names and addresses. Let’s get on to implementing our remote ComboBox. Listing 6.4 Implementing a remote loading combo box var remoteJsonStore = new Ext.data.JsonStore({ root : 'records', // 1 baseParams : { // 2 column : 'fullName' }, fields : [ // 3 { name : 'name', mapping : 'fullName' }, { name : 'id', mapping : 'id' } ], proxy : new Ext.data.ScriptTagProxy({ // 4 url : 'http://tdg-i.com/dataQuery.php' }) }); var combo = { xtype : 'combo', fieldLabel : 'Search by name', forceSelection : true, // 5 displayField : 'name', // 6 valueField : 'id', // 7 hiddenName : 'customerId', // 8 loadingText : 'Querying....', // 9 minChars : 1, // 10 triggerAction : 'name', // 11 store : remoteJsonStore }; {1} Specifing the root property for our records {2} Including base parameters on every remote request {3} Specifying the store fields, ensuring that the mapping is accurate {4} Allow Ext to request data from any domain {6} Ensuring that an item must be selected from the field {6} ensure the ‘name’ data field being displayed in the text field {7} send the ‘id’ data field when the form is submitted {8} specify a name for the hidden field who’s value is set by the valueField {9} Specify custom loading text Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 11 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 {10} Send a request as soon as a single character is entered {11} Always request all data when the trigger is pressed In Listing 6.4, we change the data store type to a JsonStore{1}, which is a pre-configured extension of the Ext.data.Store class to allow us to easily create a store that can consume JSON data. For the store, we specify a baseParams{2} property, which ensures that base parameters are sent out with each request. For this instance, we only have one parameter, column, which is set to ‘fullNname’ and specifies which column in the database the PHP code is to query from. We then specify fields{3}, which is now an array containing a single object and we translate the inbound ‘fullName’ property to ‘name’ with the name and mapping attributes. We also create a mapping for the ID for each record, which we’ll use for submission. We could have set fields to an array of strings, like we did in our local ArrayStore, but that makes the mapping order dependant. If you specify name and mapping, the order of the properties in each record will not matter, which I prefer. Lastly for the store, we specify a proxy{4}, property where we create a new instance of ScriptTagProxy, a tool that is used to request data from across domains. We instruct the ScriptTagProxy to load data from a specific URL via the url property. In creating our combo box, we are specifying forceSelection{6} to true, which is useful to use remote filtering (and typeAhead for that matter), but keeps users from entering arbitrary data. Next, we set the displayField{6} to ‘name’, which shows the name data point in the text field and we specify the valueField{7} as ‘id’, which ensures that the ID is used to send data when the combo’s data is being requested for submission. The hiddenName{8} property is much overlooked but very important. Because we’re displaying the name of the person, but submitting the ID, we need an element in the DOM to store that value. Because we specified valueField above, a hidden input field is being created to store the field data for the record that is being selected. To have control over that name, we specify hiddenName as ‘customerId’. We also customize the list box’s loading text by specifying a loadingText{9} string. The minChars{10} property defines the minimum number of characters that need to be entered into the text field before the combo executes a data store load and we override the default value of 4. Lastly, we specify triggerAction{11} as ‘all’, which instructs the Combo to perform a data store load querying for all of the data. An example of our newly constructed Combo can be seen below. Licensed to Alison Tyler Download at Boykma.Com 12 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 6.6 An example rendition of our remote-loading combo from Listing 6.4 Exercise the rendered results, and you’ll see how remote filtering can be a joy for a user to work with. Let’s take a look at how the data coming back from the server is formatted Figure 6.7 An exploded view of a slice of the served up JSON. In examining a snippet of the resulting JSON in Figure 6.7, you can see the root that we specified in our remote combo’s JSON store and the fullName field we mapped to. The root contains an array of Objects, which the DataStore will translate and pluck out any of the properties we map as “fields”. Notice how the id is the first property in the record and fullName is the second. Because we used name and mapping in our store’s fields array, our store will ignore id and all other properties in the records. Following the format in Figure 6.7 when implementing your server side code will help ensure that your JSON is properly formatted. If you’re unsure, you can use a free online tool at http://jsonlint.com, where you can paste your JSON and have it parsed and verified. When exercising the example code in Listing 6.4, you might notice that when you click on the trigger, the UI’s spinner stops for a brief moment. This is because all of the 2000 records in the database are being sent to the browser, parsed, and DOM manipulation is taking place to clear the list box and create a node. The transfer and parsing of the data is Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 13 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 relatively quick for this large data set. DOM manipulation, however, is one of the main reasons for JavaScript slowing down and is why you would see the spinner animation stop. The amount of resources required to inject the 2000 DOM elements is intense enough for the browser to halt all animation and focus its attention on the task at hand. Not to mention bombarding the user with that many records may present a usability issue. To mitigate these issues, we should enable paging. To do this, our server side code needs to be aware of these changes, which is the hardest part of this conversion. Luckily, the PHP code that we’re using already has the code in place necessary to adapt to the changes we’re going to make. The first change is adding the following property to your JSON store: totalProperty : 'totalCount' Next, we need to enable paging in our combo box. This can be done by simply adding a pageSize property to our combo box: pageSize : 20 That’s it! Ext is now ready to enable pagination to our combo box. Refresh the code in your browser and either click the trigger or enter a few characters into the text field and you’ll see the results of your changes. Figure 6.8 Adding pagination to our remote Combo Box. Thus far, we’ve explored the UI of the ComboBox and implemented both local and remote versions of using both the Array and JSON Stores. Alhough we’ve covered a lot of the ComboBox, we have just been using it as an enhanced version of a drop down box and have not learned how we can customize the resulting data’s appearance. In order to understand why we will be changing some things, such as the template and itemSelector, we will need to take a quick glance at the innards of the Combo. Licensed to Alison Tyler Download at Boykma.Com 14 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 6.3.3 The ComboBox deconstructed At the nucleus of the ComboBox lay two helper classes. We’ve touched on the DataStore, which provides the data fetching and loading, but we have not really discussed the DataView, which is the Component responsible for displaying the result data in the list box as well as providing the events necessary to allow users to select the data. DataViews work by binding to DataStores by subscribing to events such as ‘beforeload’, ‘datachanged’ and ‘clear’. They leverage the XTemplate, which actually provides the DOM Manipulation to stamp out the HTML based on the HTML template you provide. Now that we have taken a quick look at the components of a Combo box, let’s move forward in creating our custom combo. 6.3.4 Customizing our ComboBox When we enabled pagination in our ComboBox, we only saw names. But what if we wanted to see the full address along with the names that we’re searching? Our data store needs to k now of the fields. In modifying listing 6.4, we’ll need to add the mappings for address, city, state and zip. I’ll wait here while you finish that up. Ready? Ok, before we can create a template, we need create some CSS that we’ll need: .combo-result-item { padding: 2px; border: 1px solid #FFFFFF; } .combo-name { font-weight: bold; font-size: 11px; background-color: #FFFF99; } .combo-full-address { font-size: 11px; color: #666666; } In the preceding CSS, we create a class for each of the divs in our template. Nowwe now need to create a new template so our list box can display the data that we wish. Enter the following code before you create your combo: var tpl = new Ext.XTemplate( '
', '
{name}
', '
{address}
', '
{city} {state} {zip}
', '
' ); We won’t go too in depth into the XTemplate because it deserves its own section. It is important to note that any string encapsulated in curly braces (“{}”) is directly mapped to the record. Notice how we have all of our data points except for ‘id’, which we don’t need to Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 15 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 show and are just using for submission. The last change we need to make is to the combo itself. We need to reference the newly created template and specify an itemSelector: tpl : tpl, itemSelector : 'div.combo-result-item' It’s worth noting that the string for the itemSelector property is part of a pseudo sub- language called Selectors, which are patterns for which a query against the DOM can match. In this case, the Ext.DomQuery class is being used to select the div with the class ‘combo- result-item’ when any of its children are clicked. Your changes are now ready to be tested. If you did things correctly, your results should look something similar to Figure 6.9. Figure 6.9 An example rendition of our customized combo box. What we did to customize our combo box is the tip of the iceberg! Because you have complete control of the way the list box is being rendered, you can even include images or QuickTips in the list box. In this section, we learned how to create a local and remote ComboBox’s. We also learned about the ArraStore and JsonStore data store classes. We had some fun adding pagination to our remote implementation, dissected the ComboBox and customized the list box. The ComboBox has a descendant, the TimeField, which assists with creating ComboBox to select times from specific ranges. Let’s see how we can create a TimeField. 6.3.5 Finding the time The TimeField is another convenience class that allows us to easily add a time selection field to a form. To build a generic TimeField, you can simply create a configuration object with the xtype set to ‘timefield’ and you’ll get a ComboBox that has selectable items from 12:00 AM to 11:46 PM. Here is an example of how to do that: { Licensed to Alison Tyler Download at Boykma.Com 16 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 xtype : 'timefield', fieldLabel : "Please select time", anchor : '100%' } Here is an example of how the field above would render: Figure 6.10 Our rendered generic TimeField. The TimeField is configurable however, where you can set the range of time, increments and even the format. Let’s modify our TimeField by adding the following properties, which will allow us to use Military time, set an increment of 30 minutes and only allow from 9AM to 6PM: ... minValue : '09:00', maxValue : '18:00', increment : 30, format : 'H:i' In the above property list, we set the minValue and maxValue properties which sets the range of time that we want our TimeField to have. We also set the increment property to 30 and format to ‘H:i’ or 24 Hours and two digit minutes. The format property must be valid per the Date.parseDate method. The full API documentation should be consulted if you intend on using a custom format. Here is the direct API link: http://extjs.com /docs/?class=Date&member=parseDate Now that we’ve seen how The ComboBox and its descendant, the TimeField works, lets now take a look at the HTML Editor. 6.4 WYSIWhat? The Ext HTML editor is known as a WYSIWYG or What You See Is What You Get editor. It is a great way to allow users to enter rich HTML formatted text without having to push them to master HTML and CSS. It allows you to configure the buttons on the toolbar to prevent certain interactions by the user. Let’s move on to building our first HTML editor. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 17 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 6.4.1 Constructing our first HTML editor Just like the TextField, constructing a generic HTML editor is really simple: var htmlEditor = { xtype : 'htmleditor', fieldLabel : "Enter in any text", anchor : '100% 100%' } Our HTML Editor rendered to a form will look like the following: Figure 6.11 Our first HTML Editor in an Ext Window. We discussed how the HTML editor’s toolbar could be configured to prevent some items from being displayed. This is easily done by setting the enable properties to false. For instance, if you wanted to disable the font size and selection menu items, you would set the following properties as false: enableFontSize : false, enableFont : false And that’s all there is to it. After making the changes, refresh your page. You’ll no longer see the text dropdown menu and the icons to change font sizes. To see a full list of the available options, be sure to visit the API. The HTML Editor is a great tool, but it, like many things has some limitations. 6.4.2 Dealing with lack of validation The single biggest limitation to the HTML Editor is that it has no basic validation and no way to mark the field as invalid. When developing a form using the field, you will have to create your own custom validation methods. A simple validateValue method could be created as such: var htmlEditor = { xtype : 'htmleditor', fieldLabel : "Enter in any text", anchor : '100% 100%', Licensed to Alison Tyler Download at Boykma.Com 18 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 allowBlank : false, validateValue : function() { var val = this.getRawValue(); return (this.allowBlank || (val.length > 0 && val != '
')) ? true : false; } } While the validateValue method above will return false if the message box is empty or contains a simple line break element, it will not mark the field as such. We’ll talk about how to test the form for validity before form submissions a little later in this chapter. For now, we’ll switch gears and look at the date field. 6.5 Selecting a date The DateField is a fun little form widget that is chock full of UI goodness that allows a user to either enter a date via an input field or select one via the leveraged DatePicker widget. Let’s build out a DateField: var dateField = { xtype : 'datefield', fieldLabel : "Please select a date", anchor : '100%' } Yes, it’s that easy. Let’s look at the how the DateField renders. Figure 6.12 The DateField the DatePicker exposed (left) and the DatePicker’s month and year selection tool (right). This widget can be configured to prevent days from being selected by setting a disabledDate property, which is an array of strings that match the format property. The format property Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 19 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 defaults to ‘m/d/Y’ or 01/01/2001. Here are some recipes for disabling dates using the default format: [“01/16/2000”, “01/31/2009”] disables these two exact dates [“01/16”] disables this date every year [“01/../2009”] disables every day in January for 2009 [“^01”] disables every month of January Now that we’re comfortable with the DateField, lets move on to explore the Checkbox and Radio fields and learn how we can use the CheckboxGroup and RadioGroup classes to create clusters of fields. 6.6 CheckBoxes and Radios The Ext CheckBox field wraps Ext element management around the original HTML Checkbox field, which includes layout controls as well. Like with the HTML Checkbox, you can specify the value for the checkbox, overriding the default Boolean value. Let’s create some checkboxes, where we use custom values. Listing 6.5 Building Checkboxes var checkboxes = [ { xtype : 'checkbox', fieldLabel : "Which do you own", boxLabel : 'Cat', // 1 inputValue : 'cat' // 2 }, { xtype : 'checkbox', fieldLabel : "", labelSeparator : ' ', boxLabel : 'Dog', inputValue : 'dog' }, { xtype : 'checkbox', fieldLabel : "", labelSeparator : ' ', boxLabel : 'Fish', inputValue : 'fish' }, { xtype : 'checkbox', fieldLabel : "", labelSeparator : ' ', boxLabel : 'Bird', inputValue : 'bird' } ]; {1} Specifying text that will reside to the right of the field {2} Overriding the default input value The code in Listing 6.6 builds out four Checkboxes, where we override the default inputValue for each node. The boxLabel{1} property creates a field label to the right of the input field Licensed to Alison Tyler Download at Boykma.Com 20 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 and the inputValue{2}, of course, overrides the default Boolean value. An example rendering of the code above is as follows: Figure 6.13 Our first four checkboxes. While the above will work for a lot of forms, for some large forms, it is a waste of screen space. Let’s use the CheckboxGroup to automatically layout our checkboxes. Listing 6.6 Using a checkbox group var checkboxes = { xtype : 'checkboxgroup', fieldLabel : "Which do you own", anchor : '100%', items : [ { boxLabel : 'Cat', inputValue : 'cat' }, { boxLabel : 'Dog', inputValue : 'dog' }, { boxLabel : 'Fish', inputValue : 'fish' }, { boxLabel : 'Bird', inputValue : 'bird' } ] } Using the CheckboxGroup in this way will lay out your checkboxes in a single horizontal line as seen in figure 6.14. Specifying the number of columns is as simple as setting the columns attribute to the number of desired columns. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 21 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 6.14 Two implementations of the CheckboxGroup. Single horizontal line (left) and a two column layout (right). Your Implementation of the CheckboxGroup will depend on you needs and requirements. Implementing the Radio and RadioGroup classes is nearly identical to the Checkbox and CheckboxGroup classes. The biggest difference is that you can group radios by giving them the same name, which only allows one item to be selected at a time. Let’s build a group of radios. Figure 6.15 A single column of radios. Because the RadioGroup class extends the CheckboxGroup class, the implementation is identical, so we’ll save you from going over the same material. Now that we’ve gone over the Checkbox and Radio classes and their respective Group classes, we’ll begin to tie these together by taking a more in-depth look at the form panel, where we’ll learn to perform form-wide checks and complex form layouts. 6.7 Complex form layouts Like the other components, the FormPanel class can leverage any layout that is available from the framework to create exquisitely laid out forms. To assist with the grouping fields, the FormPanel has a cousin called the Fieldset. Before we actually build our componentry, lets take a sneak peak of what we’re going to achieve. Licensed to Alison Tyler Download at Boykma.Com 22 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 6.16 A sneak peek of the Complex form panel we’re going to build In constructing our complex form, we’re going to have to construct two FieldSets one for the name information and another for the address information. In addition to the FieldSets, we’re going to setup a TabPanel that has a place for text fields and two HTML editors. In this task, we’re going to leverage all of what we’ve learned thus far, so buckle your seatbelts; we’re going to go over quite a bit of code. Now that we know what we’re going to be constructing, let’s start by building out the FieldSet that will contain the TextFields for the name information. Listing 6.7 Constructing two FieldSets var fieldset1 = { xtype : 'fieldset', // 1 title : 'Name', flex : 1, border : false, // 2 labelWidth : 60, defaultType : 'field', defaults : { anchor : '-10', allowBlank : false }, items : [ { fieldLabel : 'First', name : 'firstName' }, { fieldLabel : 'Middle', name : 'middle' }, { fieldLabel : 'Last', Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 23 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 name : 'firstName' } ] } {1} Setting the xtype property to ‘fieldset’ {2} Removing the border from the fieldest In constructing our first fieldset{1} XType, the parameters may look like that of a Panel or Container. This is because the FieldSet class actually extends Panel and adds some functionality for the collapse methods to allow you to include fields in a form or not, which we do not exercise here. The reason we’re using the Fieldset in this instance is because it’s giving us that neat little title up top and we’re getting exposure to this Component. We’re going to skip rendering this first FieldSet because we’re going to use it in a form panel a little later on. Let’s go on to build the second FieldSet, which will contain the address information. This one is rather large, so please stick with me on this. Listing 6.8 Building our second fieldset. var fieldset2 = Ext.apply({}, { // 1 flex : 1, title : 'Address Information', items : [ { fieldLabel : 'Address', name : 'address' }, { fieldLabel : 'Street', name : 'street' }, { xtype : 'container', // 2 border : false, layout : 'column', anchor : '100%', items : [ { xtype : 'container', // 3 layout : 'form', width : 200, items : [ { xtype : 'textfield', // 4 fieldLabel : 'State', name : 'state', anchor : '-20' } ] }, { xtype : 'container', // 5 layout : 'form', columnWidth : 1, labelWidth : 30, items : [ { Licensed to Alison Tyler Download at Boykma.Com 24 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 xtype : 'textfield', // 6 fieldLabel : 'Zip', anchor : '-10', name : 'zip' } ] } ] } ] }, fieldset1); {1} Leverage Ext.apply to use some of our properties from our first FieldSet {2} Column-layout Containers for our state and zip fields {3} A form-layout Container for our State text field {4} The actual state text field {6} Another form-layout Container for the Zip Code TextField {6} The Zip Code TextField In Listing 6.8, we leverage Ext.apply{1} to copy many of the properties from fieldset1 and apply them to fieldset2. This utility method is commonly used to copy or override properties from one object or another. We’ll talk more about this method when we look into Ext’s toolbox-o-methods. To accomplish the desired layout of having the State and Zip Code fields side-by-side, we had to create quite a bit of nesting. The child{2} of our second fieldset is actually a Container, which has its layout set to column. The first child of that Container a form-layout Container{3} which contains our State TextField{4}. The second child{6} of our column-layout Container is another form-layout Container, which contains our Zip Code TextField{6}. You might be wondering why there are so many nested containers and perhaps why the code to get this done is so darn long. The Container nesting is required to use different layouts within other layouts. This might not make sense immediately. I think the picture will be clearer to you when we actually render the form. For now, lets move on to building a place for these two FieldSets to live. In order to achieve the side-by-side look of the form, we’re going to need to create a container for it that is setup to leverage the hbox layout. In order to have equal widths in the hbox layout, we’ve set both of our FieldSets to stretch property to 1. Let’s build a home for the two FieldSets: var fieldsetContainer = { xtype : 'container', layout : 'hbox', height : 120, layoutConfig : { align : 'stretch' }, items : [ fieldset1, fieldset2 ] } Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 25 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 In the preceding code block, we create a Container that has a fixed height but has no width set. This is because this Container’s width will be automatically set via the vbox layout, which our future FormPanel will use. Now that we have that done, we’re going to move on to building a tab panel, which will have three tabs, one with phone number form elements and the other two being html editors. This will use the bottom half of the FormPanels’ available height. We’re going to configure all of the tabs in one shot so this will be pretty lengthy. Please bear with me on this one. Listing 6.9 Building a tabpanel with form items var tabs = [ { xtype : 'container', // 1 title : 'Phone Numbers', layout : 'form', bodyStyle : 'padding:6px 6px 0', defaults : { xtype : 'textfield', width : 230 }, items: [ { fieldLabel : 'Home', name : 'home' }, { fieldLabel : 'Business', name : 'business' }, { fieldLabel : 'Mobile', name : 'mobile' }, { fieldLabel : 'Fax', name : 'fax' } ] }, { title : 'Resume', xtype : 'htmleditor', // 2 name : 'resume' }, { title : 'Bio', xtype : 'htmleditor', name : 'bio' } ]; {1} The Container that has four Text Fields {2} The two HTML Editors as Tabs Licensed to Alison Tyler Download at Boykma.Com 26 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 In Listing 6.9 we wrote a lot of code to construct the an array that comprises of the three tabs that will serve as children to our future TabPanel. The first tab{1} is a Container that leverages the Form Layout and has four text fields. The second{2} and third tabs are HTML editors that will be used to enter Resume and a short biography. Let’s move on to building our tab panel: var tabPanel = { xtype :'tabpanel', activeTab : 0, deferredRender : false, layoutOnTabChange : true, border : false, flex : 1, plain : true, items : tabs } In the preceding code block, we configure a TabPanel object that contains our tabs. We’re setting deferredRender to false because we want to ensure that the tabs are actually built and in the DOM when we get around to loading our data. We also set layoutOnTabChange to true to ensure that the doLayout method for the tab we’re activating is called, which ensures that the tab is properly sized. Our next task will be to construct the form panel itself, which is relatively trivial compared to all of it child items. Listing 6.10 Piecing it all together var myFormPanel = new Ext.form.FormPanel({ renderTo : Ext.getBody(), width : 700, title : 'Our complex form', height : 360, frame : true, id : 'myFormPanel, layout : 'vbox', layoutConfig : { align : 'stretch' }, items : [ fieldsetContainer, tabPanel ] }); Here, we’re finally getting to create our FormPanel. We set renderTo so we can ensure the formPanel is automatically rendered. In order to have the fieldsetContainer and the TabPanel properly sized, we’re using the vbox layout with layoutConfig’s align property set to stretch. We only specified a height for the fieldsetContainer. We do this because other than the height of the fielsetContainer we’re letting the vbox do its job in managing the size of the child items of the FormPanel. Lets take a look at what this beast of a form renders to. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 27 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 6.17 The results of our first complex layout form with the different containers used to comprise the complex layouts. In the preceding Figure, I’ve highlighted the different Containers that comprise the first half of the form, which includes our fieldsetContainer, two FieldSets and their child components. In using this many containers, we’re ensuring complete control on how the UI is laid. It’s common practice to have these long code batches to create a UI with this type of complexity. In exercising our newly built FormPanel, you can flip through the three different tabs and reveal the HTML editors underneath. By now you’ve seen how combining the usage of multiple components and layouts can result in something that is both usable and space saving. We now must focus our attention to learning to use for form for data submission and loading. 6.8 Data Submission and Loading Submitting data via the basic form submit method is one of the most common areas new developers get tripped up on. This is because for so many years, we were used to submitting a form and expecting a page refresh. With Ext, the form submission requires a little bit of know-how. Likewise, loading a form with data can be a little confusing for some, so we’ll explore the few ways you can do that as well. Licensed to Alison Tyler Download at Boykma.Com 28 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 6.8.1 Submitting the good old way As we said before, submitting our form the good old way is extremely simple, but we need to configure the FormPanel’s underlying form element with the standardSubmit property set to true. To actually perform the submission you simply call: Ext.getCmp('myFormPanel').getForm().submit(); This will actually call the generic DOM form submit method, which will submit the form the old fashioned way. If you are going to use the FormPanel in this way, I would still suggest going over submitting via AJAX, which will highlight some of the features that you can’t use when using the older form submission technique. 6.8.2 Submitting via AJAX To submit a form, we must access the FormPanel’s BasicForm component. To do this, we use the accessor method getForm or FormPanel.getForm(). From there, we have access to the BasicForm’s submit method, which we’ll use to actually send data via AJAX. Listing 6.11 Submitting our form var onSuccessOrFail = function(form, action) { var formPanel = Ext.getCmp('myFormPanel'); formPanel.el.unmask(); // 1 var result = action.result; if (result.success) { // 2 Ext.MessageBox.alert('Success',action.result.msg); } else { Ext.MessageBox.alert('Failure',action.result.msg); } } var submitHandler = function() { var formPanel = Ext.getCmp('myFormPanel'); formPanel.el.mask('Please wait', 'x-mask-loading'); formPanel.getForm().submit({ // 3 url : 'success.true.php', success : onSuccessOrFail, failure : onSuccessOrFail }); } {1} Unmask our form panel {2} Display a message based on the status of the returning JSON {3} Perform the actual form submission In Listing 6.12, we create a success and failure handler called onSuccessOrFail, which will be called if the form submission attempt succeeds or fails. It will display an alert MessageBox{2} depending on the status of the returning JSON from the web server. We then move on to create the submission handler method named submitHandler, which Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 29 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 actually performs the form submission{3}. While we specify the url on the submit call, we could have specified it at the BasicForm or FormPanel level, but we specify it here because I wanted to point out that the target URL could be changed at runtime. Also, if you are providing any type of wait message, like we do here, you should have success and failure handlers. At minimum, the returning JSON should contain a ‘success’ Boolean with the value of true. Our success handler is expecting a msg property as well, which should contain a string with a message to return back to the user: {success: true, msg : 'Thank you for your submission.'} Likewise, if your server side code deems that the submission was unsuccessful for any reason, the server should return a JSON object with the success property set to false. If you want to perform server side validation, which can return errors, your return JSON could include an errors object as well. Here is an example of a failure message with attached errors. { success : false, msg : 'This is an example error message', errors : { firstName : 'Cannot contain “!” characters.', lastName : 'Must not be blank.' } } If the returning JSON contains an errors object, the fields that are identified by that name will be marked invalid. Here is our form with the JSON code above served to it. Figure 6.18 The results from our server side errors object using the standard QuickTip error msg. Licensed to Alison Tyler Download at Boykma.Com 30 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 In this section, we learned how to submit our form using the standard submit methods as well as the AJAX way. We also saw how we could leverage the errors object to provide server side validation with UI level error notification. Next, we’ll look at loading data into our form using the load and setValues methods. 6.8.3 Loading data into our Form The use cycle of just about every form includes saving and loading data. With Ext, we have a few ways to load data, but we must have data to load, so we’ll dive right into creating some data to load. Let’s create some mock data and save it in a file called data.php. var x = { success : true, data : { firstName : "Jack", lastName : "Slocum", middle : "", address : "1 Ext JS Corporate Way", city : "Orlando", state : "Florida", zip : "32801", home : "123 346 8832", business : "832 932 3828", mobile : "", fax : "", resume : "Skills:
  • Java Developer
  • Ext JS Senior Core developer
", bio : " Jack is a stand-up kind of guy.
" } } Just like form submission, the root JSON object must contain a success property with the value of true, which will trigger the setValues call. Also, the values for the form need to be in an object, whose reference property is data. Likewise, it’s great practice to keep your form element names inline with the data properties to load. This will ensure that the right fields get filled in with the correct data. For the form to actually load the data via AJAX, you can call the BasicForm’s load method, whose syntax is just like submit: var formPanel = Ext.getCmp('myFormPanel'); formPanel.el.mask('Please wait', 'x-mask-loading'); formPanel.getForm().load({ url : 'data.php', success : function() { formPanel.el.unmask(); } }); Executing the code above will result in our form panel performing an XHR and ultimately the form being filled in with the values as illustrated in below. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 31 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 6.19 The results of loading our data via XHR. If you have the data on hand, lets say from another component such as a DataGrid, you can set the values via myFormPanel.getForm().setValues(dataObj). Using this, dataObj would contain only the proper mapping to element names. Likewise, if you have an instance of Ext.data.Record, you could use the form’s loadRecord method to set the form’s values. Loading data can be as simple as that. Remember that if the server side wants to deny data loading, you can set the success value to false, which will trigger the failure method as referenced in the load’s configuration object. 6.9 Summary In focusing on the FormPanel class, we’ve covered quite a few topics including many of the commonly used fields. We even got a chance to take an in-depth look at the ComboBox field, where we got our first exposure to of its helper classes, the DataStore and the DataView. Using that experience, we saw how we can customize the ComboBox’s resulting list box. We also took some time to build a relatively complex layout form and used our new tool to submit and load data. Moving forward, we’re going to take an in depth view at the data GridPanel, where we’ll learn about its inner components and see how we can customize the look and feel of a grid. We’ll also see how we can leverage the EditorGridPanel class to edit data inline. Along the way, we’ll learn more about the DataStore. Be sure to get some candy, this is going to be a fun ride! Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 7 The venerable GridPanel Since the very early days of the Ext JS, the GridPanel has been the centerpiece of the framework, which can display data like a table but is much more robust. In many respects, I believe this still holds true to this day and is arguably one of its more complicated widgets, as it has a dependency on five directly supporting classes. In this chapter, you’re going to learn a lot about the GridPanel and the class that feeds it data, the Data Store. We’ll start by constructing GridPanel that feeds from a Store that reads local in-memory Array data. At each step of the process, we’re going to learn more about the both the DataStore and GridPanel and their supporting classes. After we become more familiar with the data Store and GridPanel, we’re going to move on to building a remote loading data Store that can parse JSON that will feed a paging Toolbar. 7.1 Introducing GridPanel At a first glance, the GridPanel may look like a glorified HTML table, which have been used for ages to display data. If you take a moment to look at one of the Ext JS Grid Examples, you’ll come to the realization that this is no ordinary HTML table. You can see one example implementation of the GridPanel online, which uses an array store at: http://extjs.com/deploy/dev/examples/grid/array-grid.html. If you’re not online, that’s OK. I’ve included a snapshot of it below in Figure 7.1. In the “Array Grid” Example (below), you can see that the features provided by this widget extend beyond those of a typical HTML table. These include column management features such as sorting, resizing, reordering, showing and hiding. Mouse events are also tracked, out of the box, to allow you to highlight a row by hovering over it and even select it by clicking on it. Licensed to Alison Tyler Download at Boykma.Com 2 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 7.1 The “Array Grid’ Example, found in the examples folder of the downloadable SDK. The example also demonstrates how the GridPanel’s “view” (known as the GridView) can be customized with what are known as “custom renderers”, which are applied to the “Change” and “% Change” columns. These custom renderers color the text based on negative and positive values. This example merely skims the surface when it comes to how the GridPanel can be configured or extended. In order to fully understand more about the GridPanel and why it is so extensible, we need to know more about its supporting classes. 7.1.1 Looking under the hood The key supporting classes that drive the GridPanel are the ColumnModel, GridView, SelectionModel and a DataStore. Lets take a quick glance at an implementation of a grid panel and see how each class plays a role in making the GridPanel work. Figure 7.2 The GridPanel’s five supporting classes, the DataStore, GridView, ColumnModel, Column and selection model. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 3 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 In Figure 7.2, we can see a GridPanel and its five supporting classes highlighted. Starting from the very beginning, the data source, we have the DataStore class. DataStores work by leveraging a Reader, which is used to “map” data points from a data source and populate the data store. They can be used to read Array, XML or JSON data via the Array, XML and JSON readers. When the reader parses data, it is organized into Records, which are organized and stored inside the DataStore. This should be a little familiar to you, as you leveraged it with creating combo boxes. As we learned earlier, DataStores can get their data from either local or remote sources. Just like the ComboBox, the DataStore feeds a View. In this case, it’s the GridView. The GridView is the class is the Actual UI component of the Grid View. It is responsible for reading the data and controlling the painting of data on screen. It leverages the ColumnModel to control the way the data is presented on screen. The ColumnModel is the UI controller for each individual column. It is what provides the functions for Columns, such as resize, sort, etc. In order to do its job, it has to leverage one or more instances of Column. Columns are classes that actually map the data fields from each individual record for placement on screen. They do this by means of a dataIndex poperty, which is set for each column and is responsible for displaying the data it obtains from the field its mapped to. Lastly, the Selection Model is a supporting classes that work with a View to allow users to select one ore more items on screen. Out of the box, Ext supports Row, Cell and Checkbox Selection models. We now have a nice head-start on GridPanels and their supporting classes. Before we actually go ahead and construct our first grid, we should learn more about the DataStore class, which many widgets in the framework depend on for data. 7.2 The DataStore at a glance As we learned just a bit ago, the DataStore is the class that provides the data for the grid panel. The data store actually feeds quite a few widgets throughout the framework wherever data is needed. To put this into plain view, here is an illustration enumerating the classes that depend on the DataStore. Licensed to Alison Tyler Download at Boykma.Com 4 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 7.3 The DataStore and the classes it feeds data to. This illustration does not depict class hierarchy. As we can see in Figure 7.3, the DataStore supports quite a few widgets, which include the DataView, ListView, ComboBox, Charts, the GridPanel and all of its descendants. The only exception to this pattern is the TreePanel. The reason for this is the DataStore contains a list of records, where TreePanels require hierarchical data. Just as a quick warning, this may be one of those “dry” areas that you might not think is important - but hold on one second. Remember all of those classes that the DataStore feeds data to? Being proficient area of the framework better enable you to easily use any of those consumer widgets. 7.2.1 How DataStores work. When we got our first real exposure to the DataStore, we learned how to use descendants of the DataStore, ArrayStore and JsonStore, to read Array and JSON data. These descendants are convenience classes - or preconfigured versions of the actual DataStore, which take care of things for us like attaching the correct reader for data consumption. We used these convenience methods because they make life easier for us in the earlier chapters, but there is a lot that is going on under the hood that is not immediately exposed and is important to know. We’ll start by looking at exactly how the data flows from a data source to the store. We’ll begin with a simple flow illustration. Figure 7.4 The data flow from a data source to a DataStore consumer. As we can see in Figure 7.4, the data always starts from a DataProxy. The DataProxy classes facilitate the retrieval of unformatted data objects from a multitude of sources and contain their own event model for communication for subscribed classes such as the DataReader. In the framework, there is an abstract class aptly called DataProxy, which serves as a base class for the descendant classes, which are responsible for retrieving data from specific sources as illustrated below. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 5 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 7.5 The DataProxy and its four descendants. Each is responsible for retrieving data from a specific data source. The most commonly used proxy is the HTTP proxy, which leverages the browser’s XHR object to perform generic AJAX requests. The HTTP proxy is limited, however, to the same domain because of what is known as the “same origin policy”. This policy basically dictates that XHR requests via XHR cannot be performed outside of the domain from which a specific page is being loaded. This policy was meant to tighten security with XHRs, but has been construed as more of an annoyance than a security measure. The Ext developers were quick to come up with a work-around for this “feature”, which is where the ScriptTagProxy (STP) comes into the picture. The STP cleverly leverages the script tag to retrieve data from another domain and works very well, but requires that the requesting domain return JavaScript instead of generic data snippets. This is important to know because you can’t just use the STP against any third party website to retrieve data. The STP requires the return data to be wrapped in a global method call, passing the data in as the only parameter. We’ll learn more about the STP in just a little bit as we’ll be using it to leverage extjsinaction.com to retrieve data from our examples. The MemoryProxy is a class that offers Ext the ability to load data from a memory object. While you can load data directly to an instance of DataStore via its loadData method, usage of the MemoryProxy can be helpful in certain situations. One example is the task of reloading the data store. If you use DataStore.loadData, you need to pass in the reference to the data, which is to be parsed by the reader and loaded into the store. Using the memory proxy makes things simple, as you only need to call the DataStore.reload method and let Ext take care of the dirty work. The DirectProxy is new to Ext JS 3.0 and allows the DataStore to interact with the Ext.direct remoting providers allowing for data retrievals via Remote Procedure Calls (RPC). We will not be covering usage of Direct as there is a direct dependency on server side language to provide the remoting methods. NOTE If you’re interested in learning more about Ext.direct, I suggest visiting http://extjs.com/products/extjs/direct.php for details on specific server side implementations. Licensed to Alison Tyler Download at Boykma.Com 6 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 After a proxy fetches the raw data, a Reader then ‘reads’ or parses it. A Reader is a class that takes the raw-unformatted data objects and abstracts the data points known as ‘dataIndexes’ and arranges them into name data pairs, or generic objects. The following illustrates how this mapping works. Figure 7.6 A Reader maps raw or unformatted data so that they can be inserted into Records, which then get spooled into a data Store As you can see in Figure 7.6, the raw and unformatted data is organized and fed into records that the Reader then creates. These Records are then spooled into the Data Store and are now ready to be consumed by a widget. Ext provides readers for the three common data types. Array, XML and JSON. As the reader is chewing on records, it creates a Record for each row of data, which is to be inserted into the DataStore. A Record is a fully Ext-managed JavaScript object. Much like Ext manages Element, the Record has getter and setter methods and a full event model for which the DataStore is bound. This management of data adds usability and some cool automation to the framework. For example, changing a value of a record in a store that is bound to a consumer, like the GridPanel, will result in the UI being updated when the record is committed. We’ll learn much more about management of records next chapter, when we learn about editable grids. After the Records are loaded into the DataStore, the bound consumer refreshes its view and the load cycle then completes. Now that we have some fundamental knowledge of the DataStore and their supporting classes, we can begin to build our first GridPanel. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 7 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 7.3 Building a simple GridPanel When implementing GridPanels, I typically start by configuring the DataStore. The reason for this is because the configuration of the ColumnModel is directly related to the configuration of the DataStore. This is where we’ll start too. 7.3.1 Setting up an Array DataStore In the following Example, we’re going to create a complete end-to-end data Store that reads data already present in memory. This means that we’re going to instantiate instances of all of the supporting classes from the Proxy to the Store. This exercise will help us see the working parts being configured and instantiated. Afterwards, we’ll learn how to use some of the pre-configured data Store convenience classes to make constructing certain types of stores easier with much less code. Listing 7.1 Creating a data Store that loads local array data. var arrayData = [ // 1 ['Jay Garcia', 'MD'], ['Aaron Baker', 'VA'], ['Susan Smith', 'DC'], ['Mary Stein', 'DE'], ['Bryan Shanley', 'NJ'], ['Nyri Selgado', 'CA'] ]; var nameRecord = Ext.data.Record.create([ // 2 { name : 'name', mapping : 1 }, { name : 'state', mapping : 2 } ]); var arrayReader = new Ext.data.ArrayReader({}, nameRecord); // 3 var memoryProxy = new Ext.data.MemoryProxy(arrayData); // 4 var store = new Ext.data.Store({ // 5 reader : arrayReader, proxy : memoryProxy }); {1} Creating local array data {2} Using Ext.data.Record.create to create a constructor for an Ext.data.Record {3} Instantiating an ArrayReader {4} Constructing a new MemoryPRoxy {5} Building our Store In the above listing, we implement the full gamut of Data Store configuration. We first start by creating an array of arrays, which is referenced by the variable arrayData{1}. Please pay close attention to the format the array data is in, as this is the expected format for the ArrayReader class. The reason the data is an array of arrays is because the each child array contained within the parent array is treated as a singular record. Licensed to Alison Tyler Download at Boykma.Com 8 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Next, we create an instance of data.MemoryProxy, which is what will load our unformatted data from memory and is referenced by the variable memoryProxy{2}. We pass in the reference arrayData as the only argument. Next, we create an instance of data.Record{3} and reference it in the variable nameRecord, which will be used as the template to map our array data points to create actual records. We pass an array of object literals{4} to the Record.create method, which is known as the ‘fields’ and details each field name and its mapping. Each of these object literals are configuration objects for the Ext.data.Field class, which is the smallest unit of data managed data within a Record. In this case, we map the field ‘personName’ to the first data point in each array record and the ‘state’ field to the second data point. NOTE Notice how we’re not calling new Ext.data.Record(). This is because data.Record is a special class that is able to create constructors by using the its create method, which returns a new record constructor. Understanding how data.Record.create works is essential to performing additions to a Data Store. We then move on to create an instance of ArrayReader{5}, which is what’s responsible for sorting out the data retrieved by the proxy and creating new instances of the record constructor we just crated. From a 30 thousand foot view, the ArrayReader reads each Record, it creates a new instance of nameRecord by calling new nameRecord, passing the parsed data over to it, which is then loaded to the store. Lastly, we create our DataStore for which we pass the reader and proxy we created, which completes the creation of our array data store. This completes our end-to-end example of how to create a store that reads array data. With this pattern, you can change the type of data the store is able to load. To do this, you swap out the ArrayReader with either a JsonReader or an XmlReader. Likewise, if you wanted to change the data source, you can swap out the MemoryProxy for another such as the HttpProxy, ScriptTagProxy or DirectProxy. Recall that I mentioned something a bit earlier about convenience classes to make our lives a little easier. If we to recreate the Store above using the ArrayStore convenience class, this is what our code would look like using our arrayData from above. var store = new Ext.data.ArrayStore({ data : arrayData, fields : ['personName', 'state'] }); Just as we see in the above example, we use shortcut notation for the fields to create an instance of Ext.data.ArrayStore. We achieve this by and passing a reference of the data, which is our arrayData and a list of fields, which provide the mapping. Notice how the fields property is a simple list of strings? This is a completely valid configuration of Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 9 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 field mappings because Ext is smart enough to create the name and index mapping based on the string values passed in this manner. In fact, you could have a mixture of objects and strings in a fields configuration array. For instance, the following configuration is completely valid: fields : [ 'fullName', { name : 'state', mapping : 2} ] Having this flexibility is something that can be really cool to leverage. Just know that having am mixture of field configurations like this can make the code a bit hard to read. Using this convenience class saved us the step of having to create a proxy, record template and reader to configure the store. Usage of the JsonStore and XML Store are equally as simple, which we’ll learn more about later. Moving forward, we’ll be using the convenience classes to save us time. For now we’ll move on to creating the ColumnModel, which defines the vertical slices of data that our GridPanel will display along with our GridView component. 7.3.2 Completing our first GridPanel As we discussed before, the ColumnModel has a direct dependency on the Data Store’s configuration. This dependency has to do with a direct relationship between the data field records and the column. Just like the data fields map to a specific data point in the raw inbound data, columns map to the record field names. To finish our GridPanel construction, we need to create a ColumnModel, GridView, SelectionModel and then the we can configure the GridPanel itself. Listing 7.2 Creating an ArrayStore var colModel = new Ext.grid.ColumnModel([ // 1 { header : 'Full Name', sortable : true, dataIndex : 'fullName' // 2 }, { header : 'State', dataIndex : 'state' } ]); var gridView = new Ext.grid.GridView(); // 3 var selModel = new Ext.grid.RowSelectionModel({ // 4 singleSelect : true }) var grid = new Ext.grid.GridPanel({ // 5 title : 'Our first grid', renderTo : Ext.getBody(), autoHeight : true, width : 250, store : store, // 6 view : gridView, colModel : colModel, Licensed to Alison Tyler Download at Boykma.Com 10 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 selModel : selModel }); {1} Creating our ColumnModel {2} Mapping the dataIndexes to the columns themselves {3} Instantiating a new GridView {4} Creating a new single-selection RowSelectionModel {5} Instantiating our Grid {6} Referencing our Store, GridView, ColumnModel and SelectionModel In the above Listing, we configure all of the supporting classes before constructing the GridPanel itself. The first thing we do is create a reference for a newly instantiated instance of a ColumnModel, for which we pass in an array of configuration objects. Each of these configuration objects are used to instantiate instances of Ext.grid.Column (or any subclasses thereof), which is the smallest managed unit of the ColumnModel. These configuration objects{2} detail the text that is to be populated in the column header and which Record field the column maps to, which is specified by the dataIndex property. This is where we see the direct dependency on the configuration of the Store’s fields and the ColumnModel’s columns. Also, notice that we set sortable to true for the “Full Name” column and not the “State” column. This will enable sorting on just that one column. We then move on to create an instance of Ext.grid.GridView{3}, which is responsible for managing each individual row for the grid. It binds key event listeners to the Data Store, which it requires to do its job. For instance, when the Data Store performs a load, it fires the “datachanged” event. The GridView listens for that event and will perform a full refresh. Likewise, when a record is updated, the Data Store fires an “update” event, for which the GridView will only update a single row. We’ll see the update event in action later in the next chapter, when we learn how to leverage the EditableGrid. Next, we create an instance of Ext.grid.RowSelectionModel{4} and pass a configuration object that instructs the selection model to only allow single selection of rows to occur. There are two things to know this step. The first is that by default, the GridPanel always instantiates an instance of RowSelectionModel and uses it as the default selection model if we do not specify one. But we did create one because by default the RowSelectionModel actually allows for multiple selections. You can elect to use the CellSelectionModel in place of the RowSelectionModel. The CellSelectionModel does not allow for multiple selections of items, however. After we instantiate our selection model, we move on to configure our GridPanel{5}. GridPanel extends Panel, so all of the Panel-specific configuration items apply. The only difference is you never pass a layout to the grid panel as it will get ignored. After we set the Panel-specific properties, we set our GridPanel-specific properties. This includes configuring the references for the data Tore, ColumnModel, GridView and Selection Model. Loading the page will generate a grid panel that looks like the following illustration. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 11 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 7.7 Our first grid rendered on screen demonstrating the single-select configured RowSelectionModel and the sortable “Full Name” column. As we can see in Figure 7.7, the data is not in the same order that we specified. This is because before I took the snapshot, I performed a single click on the “Full Name” column, which invoked the click handler for that column. The click handler checks to see if this column is sortable (which it is) and invoked a DataStore sort method call passing in the data field (“dataIndex”), which is “fullName”. The sort method call then sorts all of the records in the store based on the field that was just passed. It first sorts in an ascending order, then toggles to descending thereafter. A click of the “State” column will result in no sorting because we didn’t specify “sort : true” like we did for the “Full Name” column. To exercise some of the other features of the ColumnModel, you can drag and drop the columns to reorder them, resize them by dragging the resize handle or click the column menu icon, which appears whenever the mouse hovers over a particular column. To exercise the Selection Model, simply select a row by clicking on it. Once you’ve done that, you can use the keyboard to navigate rows by pressing the up and down arrow keys. To exercise the multi-select RowSelectionModel, you can simply modify the SelectionModel by removing the “singleSelect: true” property, which defaults to false. Reloading the page will allow you to select many items by using typical Operating System multi select gestures such as shift click or control click. Creating our first grid was a cinch. Wasn’t it? Obviously there is much more to GridPanels than just displaying data and sorting it. Features like pagination and setting up event handlers for gestures like right mouse clicks are used frequently. These advanced usages are exactly where we’re heading next. 7.4 Advanced GridPanel construction In the prior section, we built a GridPanel that used static in-memory data. We instantiated every instance of the supporting classes, which helped us get some exposure to them. Like many of the components in the framework, the GridPanel and its supporting classes have alternate configuration patterns. In building our advanced grid panel, we’ll explore some of these alternate patterns in a couple of the supporting classes. Licensed to Alison Tyler Download at Boykma.Com 12 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 7.4.1 What we’re building. The GridPanel we’re going to constructing will leverage some advanced concepts, the first of which is using a remote data Store to query against a large data set of randomly generated data, which gives us the opportunity to use a paging toolbar. We will learn how to construct custom renders for two of these columns. One of which will simply apply color the ID column and the other will be more advanced, concatenating the address data into one column. After we build this grid panel, we’re going to circle around and setup a rowdblclick handler as well as get introduced to context menus as we learn to use the GridPanel’s rowcontextmenu event. Put on your propeller hat if you have one, we’ll be spending the rest of this chapter on this task and will be covering a lot of material. 7.4.2 Creating the store using shortcuts When creating our store, we’re going to learn some of the common shortcuts, which will save you time. If you need to customize the configuration beyond what’s covered here, you can mix and match shortcuts with long-hand versions of the configuration. Listing 7.3 Creating an ArrayStore var recordFields = [ //1 { name : 'id', mapping : 'id' }, { name : 'firstname', mapping : 'firstname' }, { name : 'lastname', mapping : 'lastname' }, { name : 'street', mapping : 'street' }, { name : 'city', mapping : 'city' }, { name : 'state', mapping : 'state' }, { name : 'zip', mapping : 'zip' }, { name : 'country', mapping : 'country' } ]; var remoteJsonStore = new Ext.data.JsonStore({ //2 fields : recordFields, url : 'http://tdg-i.com/dataQuery.php', totalProperty : 'totalCount', root : 'records', id : 'ourRemoteStore', autoLoad : false, remoteSort : true }); {1} Creating a list of fields mapped to raw data points. {2} A shortcut configuration of a remote JSON Data Store In the above code, we are configuring a remote JsonStore using some shortcuts. The first thing we do is create a reference, recordFields{2}, which is an array of field configuration objects. In this array, we’re mapping a lot of data fields, some of which we’ll specify in the column model. If the field labels map the data point labels, you wanted to further simplify the mappings, you could just specify an array of string values. var recordFields = [ Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 13 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 'id','firstname','lastname','street','city','state','zip','country' ]; You could also specify a mixture of objects and strings for the list of fields. When I build applications, I always configure objects instead of strings is because I like to think of the code as self-documenting. Also, if the data point on the backend needs to change, all you need to modify is the mapping attribute compared to having to modify the mapping and column model if you were to use just strings. We then move on to configure our JsonStore{2}, which will fetch data remotely. When configuring this store, we set the fields property to the reference of the recordFields array we just created. Ext uses this fields configuration array and use it to automatically create the data.Record that it will use to fill the store. We then move on to pass a url property, which is one of the shortcuts we’re using. Because we pass this property, the Store class will use it to instantiate a Proxy to fetch data. Also, being that this is a remote URL, an instance of ScriptTagProxy will be used. Remember that the ScriptTagProxy requires that the data get passed as the first parameter to the callback method that it automatically produces. The following figure illustrates the format that the server must respond with. Figure 7.8 The format that a remote server must respond within In the following illustration, we see that the server returns a method call to stcCallback1001. The callback method name the server responds with is passed to the server in the request via a callback property during each request. The number will increment for each STP request. The totalCount property is an optional value, which specifies how many records are available for viewing. In configuring our remote JsonStore, we specified the totalProperty configuration property as totalCount. This property will be leveraged by the PagingToolbar to calculate how many pages of data are available. The most important property is the data “root”, which is the property that contains our array of data. We specified the root configuration property as records in the remote JsonStore configuration. Licensed to Alison Tyler Download at Boykma.Com 14 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 We then instruct the store not to automatically fetch the data from the data source. We will need to specially craft the first request so we do not fetch all of the records in the database for the query that we’re performing. We also set a static id, “ourRemoteStore”, for the Store, which we’ll use later to get a reference of the store from the Ext.StoreMgr, which is to DataStores what the ComponentMgr is to Components. That is, each instance of DataStore can have a unique ID assigned to it or will assign one to itself and is registered to the StoreMgr singleton upon instantiation. Likewise, deregistration of the store occurs when a store is destroyed. NOTE We could configure the JsonStore using the XType “jsonstore”, but because we’re binding it to the GridPanel and the PagingToolbar, we must use an actual instance of Ext.data.Store. Lastly, we enable remote sorting by specifying remoteSort as the Boolean value of true. Because we’re paging, sorting locally would cause your UI to behave abnormally as the data sorting and page count would mismatch. Now that we’ve got that out of the way, we can move on to configure our advanced ColumnModel. 7.4.3 Building a ColumnModel with custom renderers The ColumnModel we constructed for our first GridPanel was pretty boring. All it did was map the column to the record data field. This new ColumnModel, however, will leverage two custom renderers, one of which will allow us to leverage the Address data Fields to build composite and stylized cells. Listing 7.4 Creating two custom renderers var colorTextBlue = function(id) { return '' + id + ''; } var stylizeAddress = function(street, column, record) { var city = record.get('city'); var state = record.get('state'); var zip = record.get('zip'); return String.format('{0}
{1} {2}, {3}', street, city, state, zip ); } In the listing above, we construct two custom renderers (methods) that will be used by two different columns. The first method, colorTextBlue, returns a concatenated string that consists of a span tag that wraps the id argument being passed to it. The span tag has a CSS style property that will result in blue text. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 15 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 The second custom renderer, stylizeAddress is a much more complex method that will create a composite view of all of the address data available to us minus the country. All custom renderers are called with six arguments. We’re using the first and third in this case. The first is the field value that the column is bound to. The second is the column metadata, which we’re not using. The third is a reference to the actual data Record, which we’ll use heavily. In this method, we create references to the city and state values of the record by using its get method, passing in the field for which we want to retrieve data. This gives us all the references we need to construct our composite data value. The last thing we do in this method is return the result of the String.format method call, which is one of the lesser-known power tools that Ext offers. The first argument is a string that contains integers wrapped in curly braces, which get filled in by the subsequent values passed to the method. Using this method is a nice alternative to the string concatenation we performed above. Excellent. Our custom renderers are set and we can now proceed to constructing our column configuration. This listing is going to be rather long because we’re configuring five columns, which requires quite a bit of configuration parameters. Please stick with me on this. Once you start to see the pattern, reading through this will be rather easy. Listing 7.5 Configuring oour advanced column model var columnModel = [ { header : 'ID', dataIndex : 'id', sortable : true, width : 50, resizable : false, hidden : true, // 1 renderer : colorTextBlue // 2 }, { header : 'Last Name', dataIndex : 'lastname', sortable : true, hideable : false, width : 75 }, { header : 'First Name', dataIndex : 'firstname', sortable : true, hideable : false, width : 75 }, { header : 'Address', dataIndex : 'street', sortable : false, id : 'addressCol', renderer : stylizeAddress // 3 }, { Licensed to Alison Tyler Download at Boykma.Com 16 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 header : 'Country', dataIndex : 'country', sortable : true, width : 150 } ]; {1} Hide the ID Collumn {2} Bind the colorTextBlue cusom renderer to the “ID” column {3} Bind the stylizeAddress custom renderer to the “Address” column Configuring this column model is much like the configuring the column model for our previous grid. The biggest difference being that instead of instantiating an instance of Ext.grid.ColumnModel, we’re using the shortcut method by creating an array of objects, which will be translated to a list of Ext.grid.Columns. However, we do some things different. For instance, for the “ID” column, is hidden{1} and bound to the colorTextBlue{2} custom renderer. We also set both the hideable property for the “Last Name” and “First Name” columns to false, which will prevent them from being hidden via the Columns menu. We’ll get a chance to see this in action after we render the GridPanel. The “Address” column is a bit special because disable sorting. This is because we are binding the column to the “street” field but are using the stylizeAddress custom renderer to provide cells based on a composite of other fields in the record, such as city, state and zip. We do, however, enable sorting on each individual column. This column also has an id property set to “addressCol” and no width property. This is configured this way because we’re going to configure the GridPanel to automatically expand this column so that it takes all of the available width after all of the statically sized columns are rendered. Now that we have constructed the array of Column configuration objects, we can move on to piece together our paging GridPanel. 7.4.4 Configuring our advanced GridPanel We now have just about all of the pieces required to configure our paging GridPanel. In order to do this, however, we will need to first configure the paging toolbar, which will be used as the bottom toolbar or bbar in the GridPanel. Listing 7.6 Configuring oour advanced column model var pagingToolbar = { //1 xtype : 'paging', store : remoteJsonStore, pageSize : 50, displayInfo : true } var grid = { //2 xtype : 'grid', columns : columnModel, store : remoteJsonStore, loadMask : true, bbar : pagingToolbar, autoExpandColumn : 'addressCol' Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 17 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 } {1} Configuring the PagingToolbar using the “paging” XType {2} Configuring the GridPanel using the “grid” XType In the previous listing, we use the XTypes as a shortcut to configure both the PagingToolbar and the GridPanel. For the PagingToolbar configuration, we bind the remoteJsonStore we configured earlier and set the pageSize property to 50. This will enable the PagingToolbar to bind to the data Store allowing it to control requests. The pageSize property will be sent to the remote server as the limit property, and will ensure that the Store receives bundles 50 (or less) records per request. The PagingToolbar will leverage this limit property along with the servers returning totalCount property to calculate how many “pages” there are for the data set. The last configuration property, displayInfo, instructs the p to display a small block of text, which contains text that displays the current page position and how many records (remember totalCount) are available to be flipped through. I’ll point this out when we render the GridPanel. We then move on to configure a GridPanel XType configuration object. In this configuration, we bind the earlier created configuration variables columnModel, remoteJsonStore and pagingToolbar. Because we set the columns property, Ext will automatically generate an instance of Ext.grid.ColumnModel based on the array of configuration objects in the columnModel variable. The loadMask property is set to true, which will instruct the GridPanel to create an instance of Ext.LoadMask and bind it to the bwrap (body wrap) element, which is the tag that ultimately wraps or contains all of the elements below the titlebar of a Panel. These elements include the top toolbar, content body and bottom toolbar and fbar, which is the bottom button footer bar. The LoadMask class binds to various events that the Store publishes to show and hide itself based on situation the store is in. For instance, when the Store initiates a request it will mask the bwrap element and when the request completes, it will unmask that element. We then set the bbar property to our pagingToolbar XType configuration Object, which will render an instance of the PagingToolbar widget with that configuration data as the bottom toolbar in the GridPanel. Lastly, we set the autoExpandColumn property to the string of ‘addressCol’, which is the ID of our Address Column, ensuring that this column will be dynamically resized based on all of the available viewport width minus the other fixed width columns. Our GridPanel is now configured and ready to be placed in a Container and rendered. We could render this GridPanel to the document body element, but I would like to place it as a child of an instance of Ext.Window, this way we can easily resize the GridPanel and see how features like the automatic sizing of the Address column works. Licensed to Alison Tyler Download at Boykma.Com 18 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 7.4.5 Configuring a Container for our GridPanel We’ll now move on to create the Container for our advanced GridPanel. Once we render the Container, we’re going to initiate the first query for the remote data store we created just a while ago. Listing 7.7 Placing our GridPanel inside a Window new Ext.Window({ // 1 height : 350, width : 550, border : false, layout : 'fit', items : grid }).show(); Ext.StoreMgr.get('ourRemoteStore').load({ // 2 params : { start : 0, limit : 50 } }); {1} Rendering our GridPanel inside of a Window {2} Using the Ext Store Manager class to cause our store to perform its initial request In Listing 7.7, we perform two tasks. The first of which is the creation of Ext.Window{1}, which uses the fit layout and has our GridPanel as its only item. Instead of creating a reference to the instance of Ext.Window and then calling the reference.show method, we use chaining to call the show method directly from the result of the constructor call. Lastly, we use the Ext.StoreMgr.get method, passing it our remote store ID string, and again use chaining to call the result’s load method. We pass an object, which contains a params property, which itself is an object specifying start and limit properties. The start property is instructed by the server as to which Record or row number to begin the query. It will then read the start plus the limit to return a “page” of data. We have to call this load method because the PagingToolbar does not initiate the first store load request on its own. We have to kind of nudge it a little to get it started. Our rendered grid panel should look like the one in the following illustration. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 19 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 7.9 The results of our advanced paging GridPanel implementation. As you can see from the fruit of our labor, our GridPanel’s Address Column displays a composite of the address fields in one neat column that is dynamically sized and cannot be sorted, while all of the other columns start life with a fixed size and can be sorted. A quick look at the communication from the first request via firebug will show us the parameters sent to the server. The following figure illustrates those parameters. Figure 7.10 A list of parameters sent to the remote server to request paged data. We already covered the callback, limit and start parameters a short while ago when we learned about the paging toolbar. What we see new here is the _dc and xaction parameters. The _dc parameter is what’s known as a “Cache Buster” parameter that is unique for every request and contains the timestamp for which the request was made in the UNIX epoch format, which is the number of seconds since the beginning of Computer time or 12 AM on January 1, 1970. Because the value for the requests are unique, the request bypasses proxies, thus prevents them from intercepting the request and returning cached data. Licensed to Alison Tyler Download at Boykma.Com 20 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 The xaction parameter is used by Ext.direct to instruct the controller on which action to execute, which in this case, happens to be the load action. The xaction parameter is sent with every request generated by stores and can safely be ignored if needed. I’m not sure if you have detected this already, we have not seen our ID column in action. This is because we configured it as a hidden column. In order to enable it, we can simply leverage the Column menu and check off the id column. Figure 7.11 Enabling the ID column via the Columns menu. After checking the ID column in the “Columns” menu, you’ll see it appear in the in the GridView. In this menu, you can also specify the direction for which a column is to be sorted. One thing you may notice right away is by looking at the Columns menu is that the menu options for the “First Name” and “Last Name” columns are missing. This is because we set the hideable flag to false, which prevents their respective menu option from being rendered. The Column menu is also a great way to sort a column directly by the order that you desire. Cool, we have our GridPanel constructed. We can now configure some event handlers for the GridPanel that will allow us to interact more with it. 7.4.6 Applying event handlers for interaction In order to create row-based user interaction, you need to bind event handlers to events that are published by the GridPanel. Here, we’ll learn how to leverage the rowdblclick event to pop up a dialog when a double click gesture is detected on a row. Likewise, we’ll listen for a contextmenu (right click) event to create and show a single item context menu using the mouse coordinates. We’ll begin by creating a method to format a message for the Ext alert dialog and then move on to create the specific event handlers. We’ll insert this code anywhere before our GridPanel configuration. Listing 7.8 Creating event handlers for our data grid Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 21 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 var doMsgBoxAlert = function(record) { // 1 var firstName = record.get('firstname'); var lastName = record.get('lastname'); var msg = String.format('The record you chose:
{0}, {1}', lastName , firstName); Ext.MessageBox.alert('', msg); } var doRowDblClick = function(thisGrid, rowIndex) { // 2 var record = thisGrid.getStore().getAt(rowIndex); doMsgBoxAlert(record); } var doRowCtxMenu = function(thisGrid, rowIndex, evtObj) { // 3 evtObj.stopEvent(); // 4 var record = thisGrid.getStore().getAt(rowIndex); if (! thisGrid.rowCtxMenu) { // 5 thisGrid.rowCtxMenu = new Ext.menu.Menu({ items : { text : 'View Record', handler : function() { doMsgBoxAlert(record); } } }); } thisGrid.rowCtxMenu.showAt(evtObj.getXY()); } {1} Create a utility method to generate an Ext alert dialog {2} The rowdoubleclick event handler {3} The rowcontextmenu event handler {4} Prevent event, hiding the browser’s context menu {5} Creating a static instance of an Ext Menu for the GridPanel In the above listing, we create three methods. The first of which, doMsgBoxAlert{1}, is a utility method that accepts a record as its only argument. It leverages the record.get method to extract the first and last name fields and uses them to display an Ext alert dialog that contains a message with those two properties. Next, we create the first handler, doRowDblClick{2}, which is configured to accept two of the parameters that the event is publishes. The first is the reference to the grid, thisGrid and the second is the index of the row, rowIndex, for which the event occurred. This handler uses the rowIndex that the event occurred on to locate the reference of the row’s source Record. It does this by calling thisGrid.getStore, which is the GridPanel’s store accessor method. Then we use chaining to tack on another method call, getAt, to the result of getStore and we pass rowIndex to it. This is what gets us the actual record reference. This handler then calls doMsgBoxAlert, passing that record reference, which will result in the alert dialog being displayed for the row that was double clicked. Licensed to Alison Tyler Download at Boykma.Com 22 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 The last method, doRowCtxMenu{3}, is much more complicated, as it does a lot more than just call doMsgBoxAlert and has some complex JavaScript code. If we look at the parameter list of the method, we can see that the list is the same as the other event handler with the addition of evtObj, which is an instance of Ext.EventObject. Knowing this is important because we need to prevent the browser’s own context menu from displaying. This is why calls evtObj.stopEvent{4} as the first task. Calling stopEvent stop the native context menu from showing. We then move on to get a reference to the record the event occurred on, exactly like the other event handler. We then test to see if thisGrid does not have rowCtxMenu property, which on the first execution of this method will be true and the interpreter will dive into this branch of code. We do this because we only want to create the menu once if it never existed. If we didn’t have this fork in logic, we would be creating menus every time the context menu was called, which would be wasteful. We then assign the rowCtxMenu property{5} to thisGrid as the result of a new instance of Ext.menu.Menu, which has one item, which is written in typical XType shorthand. The first property of the single menu item is the text that will be displayed when the menu item is shown. The other is a handler method that is defined inline and causes doMsgBoxAlert to be called with the record reference we created just a bit earlier. The last bit of code calls upon the newly created rowCtxMenu’s showAt method in which requires the X and Y coordinates to display the menu. We do this by directly passing the results of the evtObj.getXY() to the showAt method. The EventObject.getXY will return the exact coordinates that the event was generated at. Our event handlers are now armed and ready to be called upon. Before we can use them in the grid, we need to configure them as listeners. Listing 7.9 Attaching our event handlers to our grid var grid = { xtype : 'grid', columns : columnModel, store : remoteJsonStore, loadMask : true, bbar : pagingToolbar, autoExpandColumn : 'addressCol', stripeRows : true, listeners : { // 1 rowdblclick : doRowDblClick, rowcontextmenu : doRowCtxMenu } } {1} Attaching the event handlers to our grid To configure the event handlers to the grid, we simply add a listeners{1} configuration object, with the event to handler mapping and that’s it. Refresh the page and generate some double click and right click gestures on the grid. What happens? Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 23 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 7.12 The results of our context menu handler addition to our advanced gird. Now double clicking on any record will cause the Ext alert dialog to appear. Likewise, right clicking a row will cause our custom context menu to appear. If you click on the ‘View Record’ menu item, the Ext alert dialog will then appear. Adding user interaction to a grid can be as simple as that. One key to effective development of UI interactions is not to only instantiate and only render widgets once and when needed, as we did with the context menu. While this technique works to prevent duplicate items, it falls short of cleanup. Remember the destruction portion of the Component lifecycle? We can attach a quick method to destroy the context menu when the grid panel is destroyed by adding a destroy handler method to list of listeners. listeners : { rowdblclick : doRowDblClick, rowcontextmenu : doRowCtxMenu, destroy : function(thisGrid) { if (thisGrid.rowCtxMenu) { thisGrid.rowCtxMenu.destroy(); } } } In the above code snippet, we add the destroy event handler inline instead of creating a separate referenced method for it. The destroy event always passes the component for which is publishing the event, which we labeled thisGrid. In that method, we test for the existence of the rowCtxMenu variable. If this item exists, we call its destroy method. Context menu cleanup is one of those topics that developers often miss and can lead to lots of useless leftover DOM node garbage, which chews up memory and can contribute to over-all application performance degradation over time. So if you’re attaching context menus to any component, always be sure to register a destroy event handler for that component that destroys any existing context menus. Licensed to Alison Tyler Download at Boykma.Com 24 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 7.5 Summary In this chapter, we learned quite a bit about the GridPanel and the data Store classes. We started by constructing a local data-feeding GridPanel and learned about the both the supporting classes for both the data Store and GridPanel. While building our first GridPanel, got to see how the data Store uses proxies to read data, a reader to parse it and spools up Records, which are Ext-managed data objects. We also learned how the GridView knows when to render data from the Store by listening to events. When we constructed our remote-loading GridPanel, we learned about some of the shortcuts that can be used to configure the GridPanel and many of its supporting classes. We learned more about the ColumnModel and how it can have hidden columns or columns that cannot be hidden. While doing this, we configured the JSON reading data Store that allows for remote sorting as well. Lastly, we added grid interactions to the GriPanel, where mouse double click and right click gestures were captured and resulted in the UI responding. In doing this, we got a quick glance at menus and learned the importance of cleanup of menu items after their parent component is destroyed. Many of the concepts that we learned in this chapter will carry forward when we learn how to use the EditorGridPanel and its descendant, the PropertyGrid. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 8 EditorGridPanel In the last chapter, we learned about the GridPanel and how it can be used to display data. We integrated a PagingToolbar to our complex GridPanel to allow us to flip through large paginated data. In this chapter, we’ll build upon our previous work to construct a working EditorGridPanel, which will allow you to modify data inline much like you can in popular desktop spreadsheet applications like Microsoft Excel. We’ll also learn how to integrate context menus and toolbar buttons with Ajax requests for CRUD operations. We’ll start out by creating our first EditorGridPanel, where you’ll get an introduction to what an EditorGridPanel is and how edits are even possible. We’ll discuss ins and outs of setting up UI widgets for interaction to support insert and delete CRUD operations as well as how to get modified records from the store or even reject changes. Building up an EditorGridPanel without saving data is useless, so we’re going to take advantage of this opportunity to learn how we can code for CRUD operations. Afterwards we’re going to integrate Ext.data.DataWriter and learn how it can save us time by doing a lot of the heavy lifting and reducing the code that we need to write. This is going to be one of the most intense chapters yet. 8.1 A close look at the EditorGridPanel The EditorGridPanel panel is a class that builds upon the GridPanel class that works with the ColumnModel to allow the use of Ext form fields to edit data on the fly without the use of a separate FormPanel. It uses the Cell selection model by default, and has the an internal event model to detect user input such as clicks and keyboard keystrokes to trigger the cell selection and even the rendering or repositioning of the editor fields. It can leverage Licensed to Alison Tyler Download at Boykma.Com 2 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 the Ext.data.DataWriter class to automatically save data after its been edited, which we’ll explore later in this chapter. Excited yet? Before we break ground, we should discuss what we’re going to be constructing. We’re going to expand upon the complex GridPanel we constructed in the last chapter, but with some changes to allow us to edit data. The first obvious change is the use of the EditorGridPanel instead of the GridPanel widget. Another change we’re going to perform is the split up of the composite Address Column into separate columns for each dataIndex, which allows us to easily edit the separate address information instead of having to craft a complex editor. Figure 8.1 illustrates what our EditorGridPanel panel will look like. Figure 8.1 A quick view of what we’re going to be constructing. In building this EditorGridPanel, we’re going to leverage the necessary UI components to construct something that could be considered a mini application, as it will have simulated CRUD (Create, Update and Delete) capabilities for records. The reason it is simulated is because we cannot make Ajax requests to a different domain, and in order to keep server-side code out of the picture, we’re going to need to create some static responses. To enable CURD, we’ll add two buttons to the PagingToolbar that will allow us to save or reject changes. Likewise, we’ll create a usable context menu to add or delete rows based on which cell was right-clicked. This will absolutely be the most complex code thus far, and we’ll perform this in phases. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 3 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 The first of which is getting the EditorGridPanel functioning. We’ll then circle back and add a slice of each CRUD action one at a time. 8.2 Building our first EditorGridPanel Because we’re expanding upon the complex GridPanel we built last chapter, we’re going to see some of the exact same code and patterns. The reason we’re doing it this way is so the flow of the code is as smooth as possible. In most cases, there will be changes, so please take the time to read through every bit. I’ll point out all of the pertinent changes. Before we begin, we need to create two files that contain some JSON that will enable the simulation of server-side responses to calls we’re going to be making for the CRUD actions. The first of which will be called successTrue.js and will contain the following: {success : true} The second one will be called successFalse.js and will contain: {success: false} Please save these in your project space on your development web server. Now that we have that out of the way, we can now begin to construct our store. Listing 8.1 Creating our remote store var remoteProxy = new Ext.data.ScriptTagProxy({ url : 'http://tdg-i.com/dataQuery.php' }); var recordFields = [ { name : 'id', mapping : 'id' }, { name : 'firstname', mapping : 'firstname' }, { name : 'lastname', mapping : 'lastname' }, { name : 'street', mapping : 'street' }, { name : 'city', mapping : 'city' }, { name : 'state', mapping : 'state' }, { name : 'zipcode', mapping : 'zip' }, { name : 'newRecordId', mapping : 'newRecordId' } ]; var remoteJsonStore = new Ext.data.JsonStore({ proxy : remoteProxy, storeId : 'ourRemoteStore', root : 'records', autoLoad : false, totalProperty : 'totalCount', remoteSort : true, fields : recordFields, idProperty : 'id' }); Licensed to Alison Tyler Download at Boykma.Com 4 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 We begin by creating our ScriptTagProxy, which will allow us to fetch data from a remote domain. We then move on to create a list of fields, which map to the data points in the raw data. Remember, these are used to create actual instances of Ext.data.field, which are the smallest part of an Ext.data.Record. Notice the last field is added to the list. Our fictitious server side controller will use this ‘newRecordId’ for insert operations. We’ll discuss more about this later on when we talk about saving inserted records and see why only new records will have this property. Lastly, we create our remoteJsonStore, which uses our remoteProxy and recordFields. We could have set remoteJsonStore as an XType configuration object, but we’re going to need that reference later on when we create some of the handlers for our CRUD actions. This will keep things simple for us down the road, I promise. For the JsonStore, we also set the idProperty to ‘id’, which will ensure that the data Store tracks IDs, allowing us to monitor insert operations down the road. Our next step is to create the field editors that will be used for the ColumnModel later on. Listing 8.2 Creating our remote store var textFieldEditor = new Ext.form.TextField(); // 1 var comboEditor = { // 2 xtype : 'combo', triggerAction : 'all', displayField : 'state', valueField : 'state', store : { xtype : 'jsonstore', root : 'records', fields : ['state'], proxy : new Ext.data.ScriptTagProxy({ url : 'http://tdg-i.com/getStates.php' }) } } var numberFieldEditor = { // 3 xtype : 'numberfield', minLength : 5, maxLength : 5 } {1} Creating the TextField editor {2} An xtype configuration for a ComboBox Editor {3} A NumberField configuration object When creating our editors, we use two techniques; direct and lazy instantiation. We directly instantiate an instance of Ext.form.TextField{1} because we are going to be using it multiple times, and it would be wasteful to use an XType configuration. Conversely, the comboEditor{1} and numberFieldEditor{2} use XType configurations because they will only be used a for a single column. The comboEditor is an XType configuration for the Ext.Form.ComboBox and has a nested XType configuration for an Ext.data.JsonStore, Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 5 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 which uses a ScriptTagProxy so we can get a list of states from the remote database. Likewise, the numberFieldEditor is an XType configuration for the Ext.form.NumberField class. We’re using this in the zip code column so we set two basic validation rules that dictate the minimum and maximum character lengths for the field. Because the editor is a NumberField, no alpha characters can be entered, and only values with five integers will be accepted. We can now begin creating the ColumnModel, where we’ll use the editors we just configured. As in our complex GridPanel, this listing will be relatively lengthy. The patterns should be obvious, however. Listing 8.3 Creating ColumnModel var columnModel = [ { header : 'Last Name', dataIndex : 'lastname', sortable : true, editor : textFieldEditor // 1 }, { header : 'First Name', dataIndex : 'firstname', sortable : true, editor : textFieldEditor }, { header : 'Street Address', dataIndex : 'street', sortable : true, editor : textFieldEditor }, { header : 'City', dataIndex : 'city', sortable : true, editor : textFieldEditor }, { header : 'State', dataIndex : 'state', sortable : true, editor : comboEditor // 2 }, { header : 'Zip Code', dataIndex : 'zipcode', sortable : true, editor : numberFieldEditor // 3 } ]; {1} Using our TextField editor {2} Specifying the ComboBox configuration object {3} Setting the numberFieldEditor to the column Licensed to Alison Tyler Download at Boykma.Com 6 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 As we review the ColumnModel configuration array we see the already familiar properties such as header, dataIndex and sortable. We also see a new kid on the block, editor, which allows us to specify an editor for each of the Columns. Notice that the textFieldEditor{1} is used in four of the of the six Column configuration objects. The main reason we did this is because of performance. Instead of using an XType configuration object and having one instance of Ext.form.TextField instantiated for each column, the single TextField class is merely rendered and positioned where it’s needed, which saves memory and reduces DOM bloat. Consider this a performance saving technique. We’ll see this in action when we get around to rendering our EditorGridPanel. Lastly we have the comboEditor used for the state Column and the numberFieldEditor used for the Zip Code column. Remember that since these are only used once, using an XType configuration object is Okay. Cool, we now have our Store, editors and ColumnModel configured. We can now move on to creating our PagingToolbar and EditorGridPanel. Listing 8.4 Creating The PagingToolbar and EditorGridPanel var pagingToolbar = { xtype : 'paging', store : remoteJsonStore, pageSize : 50, displayInfo : true } var grid = { xtype : ‘editorgrid’, // 1 columns : columnModel, id : 'myEditorGrid', store : remoteJsonStore, loadMask : true, bbar : pagingToolbar, stripeRows : true, viewConfig : { forceFit : true } } new Ext.Window({ height : 350, width : 550, border : false, layout : 'fit', items : grid }).show(); remoteJsonStore.load({ // 2 params : { start : 0, limit : 50 } }); Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 7 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 {1} Specifying the ‘editorgrid’ xtype property {2} Loading our store after In the code above, we create the rest of our EditorGridPanel, starting with the PagingToolbar, which uses our remoteJsonStore and has the pageSize set to 50 records. Next, we create our EditorGridPanel{1}, which has its xtype property set to ‘editorgrid’ and uses our columnModel, remoteJsonStore and pagingToolbar. We then move on to create the container for our EditorGridPanel, which is an instance of Ext.Window, and has its layout set to ‘fit’. We use chaining to show the Window immediately after its instantiated. Lastly, we call remoteJsonStore.load and pass a configuration object that specifies the parameters to send to the server. This ensures we start at record 0 and limits the number of returning records to 50. Cool, all of the pieces of our puzzle are together for this phase. We can now render our EditorGridPanel and begin to edit data. Figure 8.2 Our first EditorGridPanel in action. We can see that our EditorGridPanel and PagingToolbar have rendered with data just waiting to be modified. Initially it seems like a normal GridPanel. But under the covers, lies a whole new level of functionality just waiting to be unleashed. We should take a quick moment to discuss how exactly you can use it. 8.3 Navigating our EditorGridPanel You can use mouse or keyboard gestures to navigate through the cells and enter or leave editing mode. Licensed to Alison Tyler Download at Boykma.Com 8 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 To initiate editing mode via the mouse, simply double click on a cell and the editor will appear, like in Figure 8.2. You can then modify the data and click or double click another cell or anywhere else on the page to cause the blur of the editor to occur. Simply repeat this process to update as many cells as you wish. You can modify how many clicks it takes to edit a cell by adding a clicksToEdit property to the EditorGridPanel configuration object and specify an integer value. Some applications like editing via a single click of a cell, so we would need to simply set clicksToEdit to 1 and we’re done. Being a command line junkie, I feel that the keyboard navigation, offers you much more power than the mouse. If you’re a power user of Excel or a similar spreadsheet application, you know what I’m talking about. To initiate keyboard navigation, I like to use the mouse to focus on the first cell I want to edit. This immediately places focus on exactly where I need it. I can use the Tab key or Shift + Tab key combination to move left or right. I can also use the arrow keys to focus any cell at will. To enter “edit mode” using the keyboard, I hit the Enter key, which displays the editor for that cell. While in Edit mode, I can elect to stay in edit mode and can modify adjacent cells by hitting the Tab key to move one cell to the right or Shift + Tab to move one cell to the left. To exit edit mode, I can elect to hit the Enter key again, or hit the Escape key. If the data you entered or modified validates properly, the record will be modified and the field will be marked as dirty. We can see quite a few fields being modified in the illustration below. Figure 8.3 Our first EditorGridPanel with an editor showing and dirty field markers. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 9 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 When exiting an editor, depending if the field has a validator and the results of the validation, the data will be discarded. To test this, edit a Zip Code cell and enter more or less than five integers. Then exit edit mode by hitting Enter or Escape. OK, we can now edit data, but the edits are useless unless we actually save our changes. This is where we enter the next building phase, adding the CRUD layers. 8.4 Getting the CRUD in With EditorGridPanel, CRUD server requests can be either automatically or manually fired. Automatic requests take place whenever a record is modified or when modifications occur and preset timer expires in the client side logic, firing off a request to the server. To setup automatic CRUD, we can create our own logic to send the requests or we can do things the easy way and use the Ext.data.DataWriter class, which is exactly what we’ll do later on in this chapter. For now, we’ll focus on manual CRUD, which is when the user invokes an action via UI, such as clicking on a menu Item or a Button somewhere. The reason we are focusing on manual CRUD is because even though Ext.data.DataWriter is helpful, it simply will not satisfy everyone’s needs and going through this exercise will give you valuable exposure to what’s going on with the Store and Records when data is modified. Also, because Create and Delete are part of CRUD actions, we will explore how to insert and delete records from the Store. 8.4.1 Adding Save and Reject logic We will begin by creating the save and change rejection methods, which we’ll tie into buttons that will live in our PagingToolbar. This code is quite complicated, so please bear with me. We’re going to have an in-depth discussion about it, in which I’ll walk you through just about every bit. Listing 8.5 Setting up our save and change rejection handlers var onSave = function() { var modified = remoteJsonStore.getModifiedRecords(); // 1 if (modified.length > 0) { var recordsToSend = []; Ext.each(modified, function(record) { // 2 recordsToSend.push(record.data); }); var grid = Ext.getCmp('myEditorGrid'); grid.el.mask('Updating', 'x-mask-loading'); // 3 grid.stopEditing(); recordsToSend = Ext.encode(recordsToSend); // 4 Ext.Ajax.request({ // 5 url : 'successTrue.js', params : { recordsToInsertUpdate : recordsToSend Licensed to Alison Tyler Download at Boykma.Com 10 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 }, success : function(response) { grid.el.unmask(); remoteJsonStore.commitChanges(); } }); } } var onRejectChanges = function() { remoteJsonStore.rejectChanges(); } {1} Getting a list of modified records {2} Gathering the record data into a list to send {3} Manually masking the grid’s element {4} Encoding the record JSON to be sent over the wire {5} Sending the Ajax request In the code above, we have two methods, onSave, which will be called when the “Save Changes” button is pressed. The other is called onRejectChanges, which will be called when the “Reject Changes” button is pressed. onSave contains quite a few lines to achieve our goal of data updates by means of Ajax requests. It begins with retrieving a list of modified records by calling the remoteJsonStore’s getModifiedRecords{1} method, which returns a list of Record instances. In essence, whenever a Record Field is modified, it is marked as dirty and placed into the Store’s modified list, which the getModifiedRecords returns. We test to see if the length of the returned array is greater than zero, which, of course, indicates that we have data to save. We then move on to create an empty array, recordsToSend, which we populate by looping through the modified records using Ext.each. When calling Ext.each, we pass two parameters, the modified records list and an anonymous method, which Ext.each will call for each item in the modified list. The anonymous method takes a single parameter, record, which changes with each item in the modified list. Inside the anonymous method, we push the record.data reference, which is an object that contains every data point in the record. We then move on to mask the EditorGridPanel’s element via the mask method. We pass two properties to the mask method, the first is a message to display while the mask is visible and the second is CSS class that Ext JS uses to show a “spinner” loading graphic. The recordsToSend reference is then overritten with the result of the Ext.encode method call, for which we pass the original recordsToSend list. What this does is “stringify” our list of JSON objects so we can send it over the wire. NOTE Ext.encode is a shortcut for Ext.util.JSON.encode. Its counterpart is Ext.decode or Ext.util.JSON.decode. The Ext.util.JSON class is a modified version of Douglas Crockford’s JSON parser, but does not modify the Object prototype. Please see http://www.json.org/js.html for more details on Douglas Crockford’s JSON parser. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 11 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Next, we actually perform our AJAX request, which is where the fun really begins. We pass Ext.Ajax.request a configuration object that has three properties to get the job done. The first is the url for which we have our successTrue.js dummy response file. Naturally, there would be some business logic here on the server side to insert or update records. This dummy file assumes the role of a central controller for all of our CRUD operations. The second property, params, is an object, which contains the recordsToInsertUpdate property with its value set to the “stringified” JSON, recordsToSend. Setting the params object ensures that the XHR being lobbed over at the server has parameters sent to it, which in this case will be one. The last property of the Ajax.request single parameter is success, which is a method to be called if the server returns successful status codes, such as 200 and so on. Because this is a simulation, we don’t check the response back from the request. In here, you generally would have some type of business logic to do something based on the results returned. For now, we simply unmask the grid’s element and call the remotJsonStore’s commitChanges method. This is a very important step because it clears the dirty flag from the Records and Fields that were modified, which clears the dirty flag on the modified cells within the EditorGridPanel panel. Failing to do this after a successful submission will result fields not being cleared in the UI and the modified Records not being purged from the Store’s internal modified list. The last method, onRejectChanges simply calls the remoteJsonStore’s rejectChanges method, which reverts the data back to the original values in the Fields and clears the dirty flag from the UI. Cool, we have the supporting methods for save and rejection all setup. We can move on to modifying our PagingToolbar to include the two buttons that will call our methods above. Listing 8.6 Reconfiguring the PagingToolbar to include save and reject buttons var pagingToolbar = { xtype : 'paging', store : remoteJsonStore, pageSize : 50, displayInfo : true, items : [ '-', // 1 { text : 'Save Changes', handler : onSave }, '-', { text : 'Reject Changes', handler : onRejectChanges }, '-' ] Licensed to Alison Tyler Download at Boykma.Com 12 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 } {1} Adding a vertical line spacer for a cleaner UI. In the listing above we reconfigure the PagingToolbar XType configuration object to include items{1}, which consists of five entities. The string entities that you see with the hyphens (“-“) are shorthand’s for the Ext.Toolbar.Separator, which will place a tiny vertical bar between toolbar child items. We’re doing this because we want to show some separation between the buttons and the generic PagingToolbar navigational items. Also in the list are generic objects, which are translated to instances of Ext.Toolbar.Button. Here, we have our “Save Changes” and “Reject” changes, which have their respective handlers set. Figure 8.4 Our editor Grid Panel with our save and reject buttons added. As we can see in the figure 8.4, the save and reject buttons are placed neatly inside of the PagingToolbar’s center, where empty space normally resides and the buttons are separated by neat button separators. We can now begin to edit data and exercise our newly modified PagingToolbar functionality and newly created CRUD methods. 8.4.2 Saving or Rejecting our changes To use our save and reject buttons, we need to modify data first. Leveraging what we know about the using the EditorGridPanel, change some data and press the “Save Changes” Button. We should see the EditorGridPanel’s element mask appear for a brief second and disappear after the save completes and the cells that are marked as “dirty” are marked “clean” or committed. The illustration below shows the masking in action. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 13 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 8.5 The load mask shows when save requests are being sent to the server Remember that our onSave method retrieved a list of modified records and used the Ext.encode method to convert the raw list of JavaScript objects to “stringified” JSON. It then used that JSON and POSTed it as the records parameter sent by the Ajax request. Below are what the request and the POST parameters look like in FireBug’s XHR inspection view. Figure 8.6 Inspecting our Ajax request post parameter in FireBug As we can see in the FireBug request inspection view, the records parameter is an array of items. In the case above, it’s two JavaScript objects, which represent the actual data records. Whenever developing or debugging, always remember that you can inspect the POST and GET parameters being sent to the web server via firebug. Also, sometimes JSON blobs can get absolutely enormous and extremely hard to read. What I typically do is copy the JSON from firebug and paste it in the web form at http://JSONLint.com, which tab indents and formats the data so it’s readable. If we were to have actual code on the server side to handle this update request, we would read the records parameter and decode the JSON and test to see if its an instance of an Array. If so, we’d loop through each object inside of that array and search the database for the ID that is presented in the object instance. If the ID is in the database, we’d code the proper SQL update. Once the updates have occurred, we return a JSON object with {success:true} and an optional msg or errors property, which could contain a message from the server or a list of IDs for which the records could not be updated do to some business rules. We could then use the success or failure callback handlers, Licensed to Alison Tyler Download at Boykma.Com 14 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 that we send to the Ajax.request method, to inspect what’s sent back from the server and perform commits or post error messages accordingly accordingly. The last bit we need to discuss regarding saving modifications has to do with sorting and pagination. Remember that changing data in a sorted column throws off the sorting completely. What I typically do after a successful change in a sorted column is call the Store’s reload method, which requests a new copy of the data set from the server and fixes sorting in the UI. Remember that we’re simulating a successful server side save, which is why we don’t reload the store in the Ajax request’s success handler. OK, we’ve saved our data and saw what it looks like going over the wire. We have yet to rejected changes though. Lets see what happens when we reject changes. To test this, simply modify data and press the “Reject Changes” button. What happened? Remember that the handler simply called the remote Store’s rejectChanges method, which looks at each Record in its modified list and calls its reject method. This, in turn, clears the dirty flag both on the Record and the UI. That’s it, no magic. Now that we’ve seen what it takes to perform remote saves to modified records, we’re going to add Create and Delete functionality our EditableGrid, which will complete our CRUD actions. 8.4.3 Adding Create and Delete When configuring the UI for our save and reject functionality, we added buttons to the PagingToolbar. While we could add the Create and Delete functionality in the same way, its best we use a context menu because it’s a much smoother flow to delete and add from a context menu. Think about it for a second. If you ever used a spreadsheet application, right clicking on a cell brings up a context menu that, among other things, has “Insert” and “Delete” menu items. We’re going to introduce the same paradigm here. As we did with the previously added functionality, we’re going to develop the supporting methods before we construct and configure the UI components. We’re going to ratchet up the complexity. Listing 8.7 Constructing our delete and new record methods var doDelete = function(rowToDelete) { // 1 var grid = Ext.getCmp('myEditorGrid'); var recordToDelete = grid.store.getAt(rowToDelete); if (recordToDelete.phantom) { // 2 grid.store.remove(recordToDelete); return; } grid.el.mask('Updating', 'x-mask-loading'); Ext.Ajax.request({ // 3 url : 'successTrue.js', parameters : { rowToDelete : recordToDelete.id }, success : function() { Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 15 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 grid.el.unmask(); grid.store.remove(recordToDelete); } }); } var onDelete = function() { // 4 var grid = Ext.getCmp('myEditorGrid'); var selected = grid.getSelectionModel().getSelectedCell(); Ext.MessageBox.confirm( // 5 'Confirm delete', 'Are you sure?', function(btn) { if (btn == 'yes') { doDelete(selected[0]); } } ); } {1} The method responsible for delete operations {2} Immediately delete records if they are phantoms {3} Perform the Ajax request to delete real records {4} The delete menu item handler {5} Get confirmation from the user, then call the doAjaxReqForDelete {6} The new record menu item handler I’m hoping that you have not run away by now, but chances are that if you’re reading this right now, you’re still with me. Awesome. Some of this will look familiar to you from the save feature we added to our EditorGridPanel panel earlier. The first method in our listing, doDelete{1}, is actually going to be called by the Delete menu item handler that we create just after it. Its only argument is rowToDelete, which is an integer indicating the index of the record that we’re to delete. This is the method that is responsible for removing Records from the store. Here’s how it works. Basically what this method does is first get a reference to the EditorGridPanel panel via the ComponentMgr’s get method. It immediately then gets the Record that the Store is to remove using the Store’s getAt method and passes the rowToDelete argument. Then, it checks to see if the record is a phantom (new Record) or not. If it is, the Record is immediately removed{2} from the Store and this method is aborted with a return call. When the record is removed from the Store, the GridView immediately shows the change by removing the Record’s row in the DOM. If the record is not a phantom, the grid’s element is masked, preventing any further user interaction and provides feedback that something is taking place. Ajax.request{3} is then called to our server side simulation file, successTrue.js with the single parameter rowToDelete, which is the ID of the record in the database. The success handler of this Ajax request will unmask the element and remove the Record from the Store. Licensed to Alison Tyler Download at Boykma.Com 16 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 The onDelete handler{4} method is going to query the selected cell from the selection model and request a confirmation from the user. If the user presses the yes button, it will call the doDelete method. Here’s how it works. When onDelete is first called, it gets a reference to our EditorGridPanel via the Ext.getCmp method. It then gets the selected cell via calling the EditorGridPanel’s SelectionModel.getSelecteCell method. What getSelectedCell returns is an array with two values, which are the coordinates of the cell; the row and column number. A call to Ext.MessageBox.confirm{5} is made, passing in three arguments; title, message body and button handler, which is an anonymous method. The button handler determines if the button pressed was ‘yes’ and call our doDelete method, passing the first value of the cell coordinates, which is the row of the selected cell. Before we move on to exercise delete, we should add the insert handler. This one is relatively small. var onInsertRecord = function() { // 6 var newRecord = new remoteJsonStore.recordType({ newRecordId : Ext.id() }); var grid = Ext.getCmp('myEditorGrid'); var selectedCell = grid.getSelectionModel().getSelectedCell(); var selectedRowIndex = selectedCell[0]; remoteJsonStore.insert(selectedRowIndex, newRecord); grid.startEditing(selectedRowIndex,0); } The purpose of this method is to locate the row index that was right clicked and insert a phantom record at the index. Here’s how it works. The very first thing it does is create a newRecord via a call to new remoteJsonStore.recordType. It does this by passing an object with a single property, newRecordId, which is a unique value by virtue of the Ext.id utility method call. Having this unique newRecordId will aid the server side in inserting new records and returning a mapping for the client to register real ids for each of the new records. We’ll discuss this more a little later, when we explore what the server side could be doing with the data we’re submitting. All data Stores have the default Record template accessible via the recordType property. Remember that to instantiate a new instance of a record, you must use the new keyword. Next, we create a reference, rowInsertIndex, to the row of the newly selected cell. We do this because it ends up in easier to read code when we use it in the following two statements. A call is then made to the remoteJsonStore’s insert method, which requires two parameters. The first is the index for which we wish to insert the Record and the second is a reference to an actual Record. This effectively inserts a record above the row that is right clicked, emulating one the spreadsheet features we discussed earlier. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 17 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Lastly, we want to initiate editing of that record immediately. We accomplish this by a call to the EditorGridPanel’s startEditing method, passing it the row for which we inserted the new record and 0, which means the first column. This concludes the supporting methods for create and delete functions. We can now move on to create the context menu handler and reconfigure the grid to listen to the cellcontextmenu event. Listing 8.8 Setting up our context menu handler and reconfiguring the EditorGridPanel var doCellCtxMenu = function(editorGrid, rowIndex, cellIndex, evtObj) { // 1 evtObj.stopEvent(); if (!editorGrid.rowCtxMenu) { // 2 editorGrid.rowCtxMenu = new Ext.menu.Menu({ items : [ { text : 'Insert Record', handler : onInsertRecord }, { text : 'Delete Record', handler : onDelete } ] }); } editorGrid.getSelectionModel().select(rowIndex,cellIndex); // 3 editorGrid.rowCtxMenu.showAt(evtObj.getXY()); } {1} The cell context menu listener method {2} Creating the context menu object {3} Selecting the cell that was right clicked Listing 8.8 contains doCellCtxMenu{1}, a method to handle the cellcontextmenu event from the EditorGridPanel, which is responsible for creating and showing the context menu for the insert and delete operations. Here is how it works. doCellCtxMenu accepts four arguments, which are passed by the cellcontextmenu handler. They are editorGrid, a reference to the EditorGridPanel that fired the event, rowIndex and cellIndex, which are the coordinates of the cell that was right-clicked and evtObj, an instance of Ext.EventObject. The first function that this method performs is preventing the right click event from bubbling upwards by calling the evtObj.stopEvent, preventing the browser from displaying its own context menu. If we did not prevent the event from bubbling, we would see the browser context menu on top of ours, which would just be silly and unusable. doCellCtxMenu then tests{2} to see if the EditorGridPanel does not have a rowCtxMenu property and creates an instance of Ext.menu.Menu and stores the reference as the rowCtxMenu property on the EditorGridPanel. This effectively allows for the creation of a single Menu, which is more efficient than creating a new instance of Licensed to Alison Tyler Download at Boykma.Com 18 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Ext.menu.Menu every time the event is fired and will last until the EditorGridPanel is destroyed, as we’ll see later. We pass a configuration object to the Ext.menu.Menu constructor, which has a single property, items, that is an array of configuration objects that get translated to instance of Ext.menu.MenuItem. The MenuItem configuration objects both reference the respective handlers to match the Item text. The last two functions that this method performs is selecting the cell that was right clicked and showing the context menu at the correct X and Y coordinates on screen. It does this by calling the select{3} method of the EditorGridPanel’s CellSelectionModel and passing it the rowIndex and cellIndex coordinates. Lastly, we display the context menu using the coordinates where the right-click event occurred. Before we move on to exercise our code, we’re going to have to reconfigure the grid to register the context menu handler. Please add the following to your grid configuration object. listeners : { cellcontextmenu : doCellCtxMenu } We now have everything we need to start exercising our new UI features. I want to see this thing in action. 8.4.4 Exercising Create and Delete At this point, we have our insert and delete handlers developed and ready to be used. We just finished creating the context menu handler and reconfigured our grid to call it when the cellctontextmenu event is fired. We’ll start our exploration by creating and inserting new Record. Figure 8.7 Adding a new record with our newly configured “Insert Record” Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 19 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 As illustrated in the figure above, we can display the context menu by right clicking on any cell, which calls the doCellCtxMenu handler. This causes the selection the cell to occur and displays the custom Ext menu at the mouse’s coordinates. Clicking on the “Insert Record” menu Item forces the call to the registered handler, onInsertRecord, which inserts a new record at the index of the selected cell and begins editing on the first column. Cool! Now in order to save changes, we need to modify the newly inserted record, we need to click the “Save Changes” button that we created earlier. Figure 8.8 The UI transitions when saving our newly inserted record. Clicking the “Save Changes” Button invokes the onSave handler, which performs the Ajax request to our mock server handler, the successTrue.js file. Here is what the JSON looks like being submitted in FireBug’s XHR inspection tool. Figure 8.9 Using FireBug to inspect the JSON being submitted for our newly inserted record. As you can see, there is no id associated with this record, but there is a newRecordId property, which is a tell tale sign that it is a new record. This property is important because Licensed to Alison Tyler Download at Boykma.Com 20 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 the controller code on the server could use this to know that this is a new record to be inserted versus an update operation. Remember that our onSave handler is setup to handle both inserts and update operations, thus the theoretical controller needs to be able to handle both situations. Here is a quick overview on how things might work if we had actual server code to submit to. The server would receive the records parameter and decode the JSON. It would then loop through the array and perform inserts on any of the records that have this newRecordIdProperty and return a list of database ids for the records that were newly inserted. Instead of a generic return of { success : true }, it could return something much more intelligent, like a list of objects that map the newRecordId to the record’s database id, which would look something like: { success : true, records : [ { newRecordId : 'ext-gen85', id : 2213 } ] } Our success handler can then search the store for the records that we just inserted and set the Record’s to the database id, which effectively makes the record a non-phantom or a real record. The success handler code would look something like this. success : function(response) { grid.el.unmask(); remoteJsonStore.commitChanges(); var result = Ext.decode(response.responseText); Ext.each(result.records, function(o) { var rIndex = remoteJsonStore.find('newRecordId', o.newRecordId); var record = remoteJsonStore.getAt(rIndex); record.set('id', o.id); delete record.data.newRecordId; }); } In the above snippet, we use Ext.decode to decode the returning JSON, which is the response.responseText property. We then use Ext.each to loop through the resulting array of objects. For each object in the list, we get the record index by using the remoteJsonStore’s find method and pass two properties. The first is the field we’re searching for, which is newRecordId and the other is the returned object’s newRecordId. We then get the reference of the record by calling the remoteJsonStore’s getAt method, and pass the Record index that we just obtained. We then use the Record’s set method to set the database id we just got back from the server and then move on to delete the record’s newRecordId property. This ensures that any further modifications to that record will only Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 21 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 results in an update as it will pass its database id to the controller. Here is what an update to the recently inserted record would look like in FireBug. Figure 8.10 A firebug view of an newly inserted record followed by a subsequent update. In the above figure, we can see that the newRecordId being sent to the server for the insertion. The server then returned the database id as 9999, and our newly modified success handler set the record’s database id and removed the newRecordId. Woah, that’s a lot of material just for the creation of records. What about delete? Surely that is simpler, right? Absolutely! Before we discuss the process of deleting records, we’ll examine how the UI works. Figure 8.11 The UI workflow for deleting a record. Licensed to Alison Tyler Download at Boykma.Com 22 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 When we right click on a record, an Ext.MessageBox displays to confirm the delete operation. We click yes and an Ajax request is made to the controller to delete the records. Here is what the delete request looks like in FireBug’s XHR inspection tool. Figure 8.12 The controller request to delete a record as viewed in FireBug’s XHR inspection tool. This works because our onDelete handler called MessageBox.confirm and will call our doDelete method, which checks to see if this is a new record or not. Because we happened to request a deletion of a non-phantom record, an Ajax request was made to the central controller with one parameter, rowToDelete, which is the database id of the record. If the record was a phantom, the request would have never been made and the record immediately moved from the store. We did a lot of work to get manual CRUD operations setup for our first EditorGridPanel. In doing so, we learned more about Stores, Records, how to detect changes and save them using Ajax requests. Along the way we got a chance to see a real-life case of an Ext confirmation MessageBox in action. Cool. Now that we have learned the nuts and bolts of manual crud, we can switch tracks to learn how we can leverage Ext.data.DataWriter to manage CRUD operations easily and even automatically. 8.5 Using Ext.data.DataWriter In our last example we learned how to code manual CRUD actions, which meant we had to code our own Ajax requests. But what if we wanted the editor grid to automatically save when we’re editing? In order to do this without writer, we’d have to write an entire event model that fired off requests when a CRUD UI action took place. This would, of course, have to take into account exception handling, which means we’d have to code for the rollback changes. I can personally tell you that it’s a lot of work. Luckily, we don’t have to do all of this for easy and automated CRUD. 8.5.1 Enter Ext.data.DataWriter Writer saves you time and effort by removing the requirement for you to have to code Ajax requests and exception handling, giving you more time to do more of the important stuff, Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 23 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 like building out the business logic for your application. Before we start coding our writer Implementation we should have a quick review how Writer fits into the picture. Please recall our conversation about the operation of data Stores from the last chapter, where we learned about the flow of data from the source to the consumer. Remember that the Proxy is the intermediary connection class for reads as well as writes. If things still seem a little fuzzy, the figure below is there to help clear things up. Figure 8.13 A depiction of the flow for data reads and writes when a data Store is used. In order to user Writer, we will need to reconfigure our data Store and the supporting Proxy. Instead of configure a url property for the Proxy, we’re going to create a configuration objects known as the api. The proxy api is a new concept for us and we’ll discuss it more in detail in just a bit, when we review the example code. We will then need to create an instance of Writer and plug it into our data Store as well as add some new configuration properties to the Store’s configuration object itself, thus completing the reconfiguration of the Store. To reconfigure the EditorGridPanel and the CRUD actions, we’ll keep all of the UI changes but remove the supporting code for the Ajax requests. Being that we’ll be familiar with most of this code, we’ll be moving at a faster pace, but we’ll slow down for the new material. 8.5.2 Adding DataWriter to our JsonStore Now that we have an understanding of what we’ll be doing, we can get our shovels out and start digging. Just as before, we’ll begin by reconfiguring our Store. Listing 8.9 Reconfiguring data Store to use Writer var remoteProxy = new Ext.data.ScriptTagProxy({ // 1 api : { read : 'http://tdg-i.com/dataQuery.php', create : 'http://tdg-i.com/dataCreate.php', update : 'http://tdg-i.com/dataUpdate.php', destroy : 'http://tdg-i.com/dataDelete.php' } }); Licensed to Alison Tyler Download at Boykma.Com 24 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 var recordFields = [ { name : 'id', mapping : 'id' }, { name : 'firstname', mapping : 'firstname' }, { name : 'lastname', mapping : 'lastname' }, { name : 'street', mapping : 'street' }, { name : 'city', mapping : 'city' }, { name : 'state', mapping : 'state' }, { name : 'zipcode', mapping : 'zip' } ]; var writer = new Ext.data.JsonWriter({ // 2 writeAllFields : true }); var remoteJsonStore = new Ext.data.JsonStore({ proxy : remoteProxy, storeId : 'ourRemoteStore', root : 'records', autoLoad : false, totalProperty : 'totalCount', remoteSort : true, fields : recordFields, idProperty : 'id', autoSave : false, // 3 successProperty : 'success', writer : writer, listeners : { exception : function () { console.info(arguments); } } }); {1} Configuring the api for the ScriptTagProxy {2} Instantiating a new instance of JsonWriter {3} Adding new properties to the Store configuration object In listing 8.9, we kick things off by creating a new ScriptTagProxy{1} and pass a configuration object, as the property api, which is a configuration object denoting URLs for each of the CRUD actions, with read being the request to load data. Instead of using the dummy successTrue.js file as a single controller, we’re going to actually use somewhat intelligent remote server side code, where a controller exists for each CRUD action. Writer requires intelligent responses, thus remote server side code was developed. Technically, we could use the exact same server side script for all of the CRUD actions, but I find it easier to create one for each action. We then move on to create the list of fields, which get translated into _____? Correct! Ext.data.Fields. These Fields are the lowest supporting class for the ____? Yes! The Ext.data.Record. You’re progressing in this framework nicely. Next, we create a subclass of the Ext.data.DataWriter, known as JsonWriter{2}, which has the ability to save a request to modify a single or batch (list) of records. In the JsonWriter configuration object, we specify writeAllFields as true, which ensure that for each operation, Writer returns all of the properties, which is great for development and debugging. Naturally, you want to set this to false when in production, which will reduce overhead over the wire and at the server side and database stack. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 25 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 The last thing we do in this listing is reconfiguring the Store. Note that everything is exactly the same except for a few property additions to enable Writer integration and some debugging capabilities. The first addition is autoSave{1}, which we set to false, but defaults to true. If left as true, the store would automatically fire requests for CRUD operations, which is not what we want just yet. I want to show you how easy it is to get invoke CRUD requests with Writer now in the picture. Next, we add the successProperty, which is used as an indication that the operation was a success or failure and is actually consumed by the JsonReader, which the JsonStore automatically instantiates for us. Remember, this is just like when we submitted data with the FormPanel, where we required at a minimum a return of { success : true } from the response from the web server. The same principle applies with the Store when using DataWriter. In our new remoteJsonStore, we’re specifying the successProperty of ‘success’, which is common and self-documenting. The last change we made is adding a global exception event listener to our JsonStore, which is needed if you wanted something to occur upon any exception that the Store raises. Here, we simply spit all of the arguments to the Firebug console, which I use when developing with Ext.data.DataWriter because it provides a wealth of information that is hard to find anywhere else during debugging. I highly suggest doing the same. Trust me, it will save you time in the long run. Cool! We’ve just recreated our ScriptTagProxy to work with our new instance of Ext.data.JsonWriter and reconfigured our Store to prevent autoSaves. Our next task is to modify the PagingToolbr and make the delete Context menu handler leaner Listing 8.10 Reconfiguring the PagingToolbar and delete handler var pagingToolbar = { xtype : 'paging', store : remoteJsonStore, pageSize : 50, displayInfo : true, items : [ '-', { text : 'Save Changes', handler : function () { // 1 remoteJsonStore.save(); } }, '-', { text : 'Reject Changes', handler : function () { // 2 remoteJsonStore.rejectChanges(); } }, '-' ] } Licensed to Alison Tyler Download at Boykma.Com 26 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 var onDelete = function() { // 3 var grid = Ext.getCmp('myEditorGrid'); var selected = grid.getSelectionModel().getSelectedCell(); var recordToDelete = grid.store.getAt(selected[0]); grid.store.remove(recordToDelete); } {1} Saving our changes via Writer {2} Rejecting changes from our Store {3} A leaner onDelete context menu handler In Listing 8.10, we reconfigure the PagingToolbar’s save button handler{1} to simply call the save method of the data Store, which is what uses Writer to gather the data to save and eventually invoke a request to save changes. This effectively replaces the previous onSave handler, which is what was responsible for sending Create and Update Ajax requests. This is where we start to see some of the code savings we discussed earlier. We also added an inline method to reject{2} the Store’s changes. Next, we refactor the onDelete{3} context menu handler. We removed the typical confirmation dialog box to make it much leaner. We can choose to reject the changes from here, which means that the records that are deleted from the UI are rolled back via the rejectChanges handler we created above. We also removed the Ajax request code, thus we have more code savings. Our changes to integrate Writer into our EditorGridPanel panel are now complete. The EditorGridPanel configuration code stays exactly the same as in Listing 8.8. Let’s see Writer in action. 8.5.3 Using DataWriter The change we made in the previous listings, where we Integrated writer were designed so we could use the exact same interaction to invoke a request, but see how we could reduce the amount of code when using Writer relative to creating our own Ajax requests and handlers. We’ll start by modifying some records and clicking the Save Changes button to inspect what Writer is sending to the server. Below is an illustration of the parameters being sent for an update request in Firebug’s request inspection tool. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 27 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 8.14 The result of our update request as view in Firebug’s request inspection tool. In Figure 8.14, we see the result of a request being sent to the update URL as configured in the Proxy API. Along with the usual _dc (cache buster) and callback parameters, we see id, which is a list of IDs affected and records, which is the list of records that were changed. We see the entire record contents here because we set the writeAllFields configuration parameter to true. Our server side code then processes the list of records and returns the following JSON in return. Here is what the return looks like. Figure 8.15 The server side response from our update request with Writer. Notice that the successProperty, success, has a value of true, which is an indication that the server processed the data successfully. It also returned the list of records after it was processed. This is used to apply any changes that may be required by business logic. For instance, what if someone put a forbidden word as one of the values. The server could reject the change by quietly replacing that forbidden value with what was previously in that field. The UI would then update accordingly, thus completing an update cycle. Next, we should exercise an insert and see what happens. This is where things get interesting. Insert a record via the context menu, add some values, and click the save button in the PagingToolbar. We see that a request was made to the create URL that we defined in the api configuration object for the ScriptTagProxy. Licensed to Alison Tyler Download at Boykma.Com 28 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 8.16 Inserting a record with Writer as viewed in the Firebug request inspection tool. The biggest thing to note in the records parameter being sent to the server is the id property. Notice that the id fictitious, where its value is “ext-record-1”. Being that the create server side action is different from the update action, the ID parameter is ignored when the record is inserted into the database. The server then gets the ID for the newly inserted record and returns the database id in return as illustrated below. Figure 8.17 Inspecting results of a record insert using Writer with Firebug. If an insert was successful, then the id value (along with all of the other values) returned to the browser will be applied to the recently inserted record. This ensures action requests for update and delete will submit the database ID of the record moving forward. This is where we can see the added value of using Writer, where we don’t have to manage this verification logic our selves. In our test case, we inserted a single record. If you insert multiple records, they will submitted to the server, upon save, in a list. It is important to note that the server must return the records back in the exact same order, or the database ID to record mapping or association will fail. The delete action is the simplest, where the delete request sends a list of IDs to the host for processing. To exercise this, right click on a record, and choose delete form the context menu. Heck, do this for a few records. Notice how they are removed from the store? Now click save and inspect the request in Firebug. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 29 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 8.18 The JSON parameters for a delete action request. For the delete action requests, we see that the records parameter simply is a list of IDs. Lets take a look at what the server side returns. Below is an illustration of the result from the server for the delete request. Figure 8.19 The result of our delete action request using Writer. The server side code takes the list of ids, removes the records from the database and returns the list of IDs for which the store is to permanently remove. And that’s all there is to delete operations. We’ve seen how we could use Writer for CRUD operations that were invoked manually, but required no special AJAX request code and handling of our own. The last topic for discussion is automated CRUD with Writer. 8.5.4 An Automated Writing Store To setup Writer for automated CRUD actions, all we need to do is set the autoSave property for the data Store configuration object to true. That’s it. We already setup all of the hard stuff, if you want to call it that. Now all you need to do is exercise CRUD operations and the Store will automatically invoke requests based on your CRUD action. The one thing to look out for with an automatic writing Store is that there is no undo for actions. That is, when data has been modified and successfully altered in the database, the rejectChanges method will have no effect. The database now reflects the latest changes. The same goes for delete and insert operations. 8.6 Summary In this chapter we got our first exposure to the EditableGrid class and learned how it uses Ext.form.Fields to allow for editing of data on the fly. This gave us an opportunity to learn about the CellSelectionModel and some of its methods, such as getSelectedCell. We also learned how we could leverage keyboard and mouse gestures to navigate the EditorGridPanel to edit data relatively rapidly. We learned how to manually code for CRUD operations with our own custom AJAX request logic and used a mock controller while doing so. We added two Menu items to the PagingToolbar and a context menu to the EditableGrid to allow us to Insert and delete records as well as reject changes. In doing this, we learned how to getModifiedRecords from the Store for submission the mock controller. Licensed to Alison Tyler Download at Boykma.Com 30 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Lastly, we learned how we could reduce the amount of code we needed to generate by leveraging the Ext.data.DataWriter class for CRUD operations. We also discussed how to setup an automated Store with Writer. In the next chapter, we’re going to learn all about another flagship UI widget, the Ext.tree.TreePanel. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 9 Taking root with Trees I can recall the first time I was tasked to create an application with what was known then as a “TreeView”. I had to allow for the navigation directory in a file system, which required that I allowed users to modify the names of the files easily. I was lucky that I had Ext JS in my toolbox to aid me in accomplishing my task. Using the framework not only made things easier, it sped up the development time of this task dramatically. In this chapter, you’re going to learn about the Ext JS TreePanel, which is used to display hierarchical data, much like a typical filesystem. You’re going to learn how to setup both static and dynamic implementations of this widget. After getting comfortable with this component, we’ll work to setup CRUD operations by use of a dynamically updating context Menu and Ajax requests to send data. This is going to be a fun chapter. 9.1 What is a Tree(Panel)? While the term “tree” is generally used to describe a woody plant, in the UI world, it is meant to describe a widget or control that displays hierarchical data which generally begins at some central point, which is known as a root. And like the tree plant, trees in UIs have branches, which means that they contain other branches or leafs. Unlike the tree plant, computer trees only have one root. In the computer world, this paradigm is ubiquitous and lives under our noses without much thought. Ever browse your computer’s hard disk? The directory structure is a tree structure. It has a root (any drive letter in Windows), branches (directories) and leafs (files). Trees are used in application UIs as well and are known by a few other monikers. In other UI libraries, other names for this type of widget include “TreeView”, “Tree UI” or simply “Tree”, while in Ext JS, it’s known as the TreePanel. The reason it’s called “TreePanel” is because it is a direct descendant of the Panel class. And much like the GridPanel, is not used to contain any children except what it was designed for. The reason Licensed to Alison Tyler Download at Boykma.Com 2 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 TreePanel extends from Panel is simple - convenience. This gives us the flexibility to leverage all of the Panel’s UI goodness, which include the top and bottom ToolBars and footer button bar. Like the EditorGrid, TreePanels can be configured to allow the edit data, but does not have a DataWriter equivalent, which means that we must code our own Ajax requests for CRUD actions. We’ll explore how to make a TreePanel editable and how to code for CRUD actions. Unlike the GridPanel, however, the number of the supporting classes is actually quite small, which makes the configuration a TreePanel is really simple in contrast, as we’ll see a little later on. On the flip side, many developers find that the server side code and related SQL to support them is much more challenging due to the relational nature of the data. Lastly, the Ext.data classes do not apply to TreePanels, thus Proxys and Readers are out of the picture. This means that the data for the TreePanel must either come from memory or remotely, but is limited to the same domain. Before we get down to building our first TreePanel, we will discuss how a TreePanel works. 9.2 Looking under the roots TreePanels work by loading data via a TreeLoader class, which either reads JSON- formatted data from memory, remotely from the web server or can be a mixture of both. Each Object in the JSON stream is converted to an instance of tree.TreeNode, which is a descendant of the Ext.data.Node class. This data.Node class is the core of all of the TreePanel data logic and includes many of the utilities such as cascade, bubble and appendChild. In order for the TreePanel to display the nodes visually, the TreeNode class uses the tree.TreeNodeUI class. The root node gets some special attention, as it’s the source of the entire structure and has its own RootTreeNodeUI class. If you want to customize the look and feel of nodes, you would extend this class. Cool, we have a high level understanding of what a TreePanel is and how it works. We can start constructing our first TreePanel, which will load data from memory. 9.3 Planting our first TreePanel As I mentioned before, coding a TreePanel, relative to the GridPanel, is pretty simple. We’ll start out by constructing the TreePanel, which loads its data from memory. This will gives us more insight into what we learned just some time ago. Listing 9.1 Building a static TreePanel var rootNode = { // 1 text : 'Root Node', expanded : true, children : [ // 2 { text : 'Child 1', leaf : true // 3 Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 3 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 }, { text : 'Child 2', leaf : true }, { text : 'Child 3', children : [ { text : 'Grand Child 1', children : [ { text : 'Grand... you get the point', leaf : true } ] } ] } ] } var tree = { // 4 xtype : 'treepanel', id : 'treepanel', autoScroll : true, root : rootNode } new Ext.Window({ height : 200, width : 200, layout : 'fit', border : false, title : 'Our first tree', items : tree }).show(); {1} The JSON data for our tree nodes {2} Including child nodes for this branch {3} Specifying that a node is a leaf {4} Configuring our treePanel Yikes! Most of the code in listing 9.1 is the data to support the TreePanel. In walking through the rootNode{1} JSON, we see that the root node (object) has a text attribute. This is important because it is the text property is what is used by the TreeNodeUI to actually display the node’s label. When coding the server-side code to support this widget, be sure to keep this property in mind. If you don’t set it, the nodes may appear in the TreePanel, but will have no label. We also see an expanded property, which is set to true. This ensures that, when rendered, the node is expanded immediately, thus displaying its contents. I set this here so you can see the root’s childNodes immediately upon the rendering of the TreePanel. This parameter is optional. Leave it out or set it to false to have the node render initially collapsed. A children property is set on the root node, which is an array of objects. When a node has a children array, the objects in that array will be converted to tree.TreeNodes Licensed to Alison Tyler Download at Boykma.Com 4 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 and populated in the parent node’s childNodes array. A similar paradigm can be found in the Container hierarchy, where a Container has children in its items MixedCollection. If we walk through the rootNode’s children, we see that the first and second child have no children property, but have a leaf{3} property, which is set to true. Setting a node’s leaf property to true will ensure that this node will never contain other child nodes, thus is a leaf and not a branch. In this case, ‘Child 1’ and ‘Child 2’ are leaf nodes, while ‘Child 3’ is a branch because it does not have a leaf property set to true. The ‘Child 3’ node contains one child node, which is a leaf because? Yes. It is a leaf because its leaf property is set to true. Wow you learn quickly. This node has a single child, which has a single child. After configuring the supporting data, we move on to configure the TreePanel{4} using an XType configuration object. This is where we see the simplistic nature of the configuration of this widget. All of these properties should make sense to you but the root, which is what we use to configure the root node. In this case, the top most level object of the rootNode JSON will be treated as the TreePanel’s root. You can change your TreeNode icons by adding either an icon or iconCls properties to the node’s configuration object, where icon specifies a direct location for an image and iconCls is the name of a CSS class for an icon style. However, iconCls property for the TreeNode works just like the Panel’s iconCls configuration object and is the preferred method for changing the icon. The last thing we do in this listing is create an instance of Ext.Window to display our TreePanel. Here is what our rendered TreePanel looks like. Figure 9.1 Our first (expanded) TreePanel with the root node visible (left) and the root node hidden (right). After rendering our TreePanel, we can the tree nodes displayed as we laid it out in the JSON. You can expand ‘Child 3’ and its child node to display the rest of the hierarchy. It’s easy exercise the selection model by clicking on a node. If you wanted to hide the root Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 5 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 node, simply set rootVisible in the TreePanel configuration object to false, as depicted in Figure 9.1 (left). And there you have it, a static TreePanel in action. Simple stuff, huh? Now that we have this out of the way, we’re going to move on to creating a remote TreePanel. 9.4 Dynamic TreePanels Grow Because our previous TreePanel is static, there was no need to directly create a TreeLoader. This changes with a remote-loading TreePanel. We’re going to develop a TreePanel that will use the same data that we used for our GridPanels, where we displayed people. It just so happens that those people are employees for “My Company” and belong each to different departments. Here, we are going to configure the TreePanel to utilize the server-side component to list employees by department. Listing 9.2 Building a static TreePanel var tree = { xtype : 'treepanel', autoScroll : true, loader : new Ext.tree.TreeLoader({ // 1 url : 'getCompany.php' }), root : { // 2 text : 'My Company', id : 'myCompany', // 3 expanded : true } } new Ext.Window({ height : 300, width : 300, layout : 'fit', border : false, title : 'Our first remote tree', items : tree }).show(); {1} Instantiating a TreeLoader for remote data calls {2} Configuring a root node inline {3} Setting the ID of the root node As we can see in Listing 9.2, we configure a TreeLoader{1}, which has a configuration object passed with a url property set to ‘getCompany.php’. When configuring your TreePanel, replace this PHP file with your controller of choice. Before you start coding your controller, however, please allow me to finish walking through the request and response cycle, which follows shortly after we look at the rendered version of this TreePanel implementation. The next thing we do when configuring this TreePanel is configure the root{2} inline. It is extremely important to notice that we added an id{3} property to this node. As we’ll see, this property is going to be used to request the child data from the server. Also notice Licensed to Alison Tyler Download at Boykma.Com 6 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 that we set expanded to true. This is going to ensure that the root node expands and loads its children as soon as it’s rendered. Lastly, we configure a bigger instance of Ext.Window to contain our TreePanel. Configuring the window a bit bigger for this demonstration will both increase the TreePanel’s viewing space and eliminate horizontal scrolling due to long names. Here is what the rendered TreePanel looks like. Figure 9.2 Our remote TreePanel displaying its ability to load data remotely. After rendering the TreePanel, we see the root node (My Company) load immediately, as shown in Figure 9.2 (left), displaying all of the departments in “My Company”. To view the employees in a particular department, click on the expand icon (+) or double click the label and you’ll see the remote loading indicator appear in place of the folder icon as seen in the center of Figure 9.2. After the employee nodes are loaded successfully, they will appear below the department node. We went through this pretty fast. Let’s rewind time a bit and take a look at the requests being fired. We’ll discuss what the server side controller is doing to support this implementation of the TreePanel. We’ll start by the load request fired off by the automatic expansion of the root node. Remember that we set the root’s expanded property to true and that this expands a node when its rendered, thus either rendering the children if they are in memory or firing a load request. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 7 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 9.3 The post parameter of the initial node request. As we see in Figure 9.3, the first request to the getCompany.php controller was made with a single parameter, node, which has a value of myCompany. Can you remember where we set that value and which property it was set as? If you said “the id property root node”, you’re correct! When an asynchronously loading node is being expanded for the first time, the loader will use its id property to pass to the controller for the child data. The controller will accept this parameter and query the database for all nodes associated with that id and return a list of objects illustrated below. Figure 9.4 the results of the initial request to the getCompany.php controller. In figure 9.4, we see an array of objects that define a list of departments. Each object has both a text and id property. How does the text property apply to the NodeUI? Correct! The text applies to the label of the NodeUI. Notice that the departments lack the leaf and children properties. Are these leaf or branch nodes? Correct. They are branch nodes. Because neither property is defined, they are treated as branch nodes. This means that when they are initially expanded, the TreeLoader will invoke an Ajax request, passing the department’s ID as the node parameter. The controller will accept the node parameter and return a list of employees for that department. Using what we just learned, we can safely predict that when we expand the Accounting department that a request to the getCompany.php controller will be made with a single parameter, node, passed with a value of ‘Accounting’. Lets take a quick look at the results from the controller request. Licensed to Alison Tyler Download at Boykma.Com 8 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 9.5 the results from the Accounting department node request. As we look at the JSON results, we see that a list of objects is returned, each with id, text and leaf properties. Remember, that it is because the leaf property is set, that the nodes appear as non-expanding leaf nodes. Configuring a TreePanel for loading is just a small part of the job if you’re tasked to build a UI that offers CRUD functionality for this type of widget. Next, we’ll look at how to construct a TreePanel for these types of interactions. 9.5 CRUD on a TreePanel To configure CRUD UI functionality, we’re going to need to add much more code to the mix. After all, the TreePanel doesn’t support these features natively. Here’s what we’re going to do. To enable CRUD actions, we’re going to modify our TreePanel by adding a contextmenu listener to it, which will call a method to select the node that was right-clicked and create an instance of Ext.menu.Menu to be displayed at the mouse cursor’s X and Y coordinates. This will be very similar to how we coded the EditorGridPanel’s context menu handler in the last chapter. We’ll create three menu items, for Add, Edit and Delete. Because we can only add employees to a department, we’re going to dynamically change text for, enable and disable the various menu items based on the type of node that was clicked; root, branch or leaf. Each of the handlers will perform an Ajax requests to mock controllers for each CRUD action. Again, much of this will function like the CRUD for the EditorGridPanel, but adapted to the TreePanel to deal with nodes instead of rows. Get ready. This will be the most complicated code yet. We’ll start by creating the context menu handler and context menu factory method. 9.5.1 Adding Context Menus to our TreePanel To add a context menu to the TreePanel, we’re going to have to register a listener for the contextmenu event. This is super simple. Add a listeners configuration option to the TreePanel as such: Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 9 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 listeners : { contextmenu : onCtxMenu } Adding the previous code will ensure that the onCtxMenu handler will be called when the contextmenu (or right-click) event is fired. Cool. Our TreePanel is now setup to call the onCtxMenu handler. Before we code it, we should generate a factory method to generate an instance of Ext.menu.Menu for us. This will help simplify onCtxMenu quite a bit. You’ll see what I mean after we are done with the factory method. Listing 9.3 Configuring a context menu factory method var onConfirmDelete = Ext.emptyFn; var onDelete = Ext.emptyFn; var onCompleteEdit = Ext.emptyFn; var onEdit = Ext.emptyFn; var onCompleteAdd = Ext.emptyFn; var onAddNode = Ext.emptyFn; var buildCtxMenu = function() { return new Ext.menu.Menu({ items: [ { itemId : 'add', handler : onAdd }, { itemId : 'edit', handler : onEdit, scope : onEdit }, { itemId : 'delete', handler : onDelete } ] }); } In Listing 9.3, we first setup a bunch of placeholder methods that point to Ext.emptyFn, which is the same thing as instantiating a new instance of Function, but is easier on the eyes. We’re adding them now so that when we circle back and fill in these methods in, you’ll know exactly where to place them. Next, we generate the buildCtxMenu factory method, which returns an instance of Ext.menu.Menu and will be used by the onCtxMenu handler that we’re going to generate next. If you’re never seen of or heard of a factory method, from a high level, it is a method that constructs (hence the name factory) something and returns what it constructed. That’s all there is to it. Notice that each of the Menu items has no text property, but each have itemId specified. This is because the onCtxMenu will actually dynamically set the text for each Licensed to Alison Tyler Download at Boykma.Com 10 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 menu item to provide feedback to the user that something may or may not be allowed. It will use the itemId property to locate a specific item in the Menu’s items mixed collection. The itemId configuration property is very similar to the id property of Components except it is local to a child Component’s Container. This means that unlike the Component’s id property, itemId is not registered with ComponentMgr, thus only the parent Component has the ability to look into its items MixedCollection to find a child component with a specific itemId. Each MenuItem currently has a hard-coded handler to Ext.emptyFn as a placeholder so we can see our menu display in the UI without having to code the real handler. We’ll go on to create each handler after we develop and review the onCtxMenu handler, which is next. This listing is going to be large, so please don’t run away. Listing 9.4 Configuring a context menu factory method var onCtxMenu = function(node, evtObj) { node.select(); evtObj.stopEvent(); if (! this.ctxMenu) { this.ctxMenu = buildCtxMenu(); // 1 } var ctxMenu = this.ctxMenu; var addItem = ctxMenu.getComponent('add'); var editItem = ctxMenu.getComponent('edit'); var deleteItem = ctxMenu.getComponent('delete'); if (node.id =='myCompany') { // 2 addItem.setText('Add Department'); editItem.setText('Nope, not changing the name'); deleteItem.setTex('Can\'t delete a company, silly'); addItem.enable(); deleteItem.disable(); editItem.disable(); } else if (! node.leaf) { addItem.setText('Add Employee'); deleteItem.setText('Delete Department'); editItem.setText('Edit Department'); addItem.enable(); editItem.enable(); deleteItem.enable(); } else { addItem.setText('Can\'t Add Employee'); editItem.setText('Edit Employee'); deleteItem.setText('Delete Employee'); addItem.disable(); editItem.enable(); deleteItem.enable(); } ctxMenu.showAt(evtObj.getXY() ; } Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 11 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 {1} Using our context menu factory method {2} Configuring the menu each type of node In listing 9.4 we construct our onCtxMenu handler, which does quite a bit to enable our context menu to be quite dynamic. The first task that this handler accomplishes is selecting the node in the UI by firing the node’s select method. We are selecting the node because we’re going to need to query the TreePanel for the selected node further on, after Ajax calls are made. It can do this because every time the TreePanel’s contextmenu event fires it passes two arguments; the node that the event occurred on and the instance of EventObject that was generated. If bells are ringing in your ears, it’s probably because that this is very similar to the GridPanel’s contextmenu event, where the row that the event occurred and an instance of EventObject is passed to the handler. Next we stop the browser’s default context menu from showing up by calling evtObj.stopEvent. You’ll see this pattern repeating anywhere you need to need to display your own context menu in placement of the browser’s. The handler then moves on to construct the context menu by calling the buildCtxMenu factory method we created just a bit ago. It stores the reference locally as this.ctxMenu so it does not have to reconstruct a menu for each subsequent handler calls. Next, we create local reference to the context menu, ctxMenu and each of the menu items. We’re doing this for ease of readability further on, where we actually manage the menu items. After we create the local references, we move on to an if{2} control block, where we detect the type of node and modify the menu items accordingly. This is the bulk of the code for this handler. Here is how this logic breaks down. If the node that was right-clicked is the root (node.id == ‘myCompany’), we configure the menu items to allow the addition of departments, but disallow the deletion and editing of the company text. We also disable those menu items so they cannot be clicked. After all, we don’t want anyone to destroy an entire company by a single mouse click, do we? Next, we detect to see if the node is not a leaf (department). We then move on to modify the text to allow the addition of employees and deletion of the entire department. Remember, the company needs to be able to downsize by removing entire department if need be. We also enable all menu items. The code will encounter the else block if the node that was right-clicked is a leaf item. In this case, the text for the add item is modified and disabled to reflect the inability to add an employee to an employee, which would be just weird. The edit and delete menu item texts are modified and enabled. Lastly, we show the context menu at the coordinates of the mouse by calling the EventObject’s getXY method. Here is what the menu looks like customized for each node. Licensed to Alison Tyler Download at Boykma.Com 12 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 9.6 Displaying our dynamic context menu for the company (left), department (center) and employee (center) nodes. As illustrated in Figure 9.6, our context menu displays and changes for each type of node that was right-clicked, which demonstrates how you can use the same menu to perform similar tasks with some modifications. If you wanted to not show or hide the menu items instead of enabling and disabling, simply swap the MenuItem enable calls for show and disable for hide. We can now begin to wire up handlers for our context menus. We’ll start with the easiest, edit. 9.5.2 Wiring up the Edit logic You probably noticed that clicking on a MenuItem resulted in nothing more than the menu disappearing. This is because we have our context menu set, but no real handlers for them to call. We’ll start by creating the edit handler, which is by far the easiest to code. This is where we’ll instantiate an instance of tree.TreeEditor to allow for inline edits of node names. In order to get the TreeEditor to invoke an Ajax request to submit the modified node name, we’re going to have to setup a listener for the TreeEditor’s published complete event, which indicates that an edit has completed and the actual value of the node has been changed. We’ll code for the complete handler first, then move on to create the onEdit handler, which we’ll attach to the edit MenuItem. Here is where we’ll start filling in some of those placeholder methods. Listing 9.5 Configuring a context menu factory method var onCompleteEdit = function(treeEditor, newValue, oldValue) { // 1 var treePanel = Ext.getCmp('treepanel') treePanel.el.mask('Saving...', 'x-mask-loading'); var editNode = treeEditor.editNode; var editNodeId = editNode.attributes.id; Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 13 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Ext.Ajax.request({ // 2 url : 'editNode.php', params : { id : editNodeId, newName : newValue }, success : function (response, opts) { treePanel.el.unmask(); var responseJson = Ext.decode(response.responseText); if (responseJson.success !== true) { // 3 editNode.setText(oldValue); Ext.Msg.alert('An error occured with the server.'); } }, failure : function (response, opts) { // 4 treePanel.el.unmask(); editNode.setText(oldValue); Ext.Msg.alert('An error occured with the server.'); } }); } var onEdit = function() { // 5 var treePanel = Ext.getCmp('treepanel'); var selectedNode = treePanel.getSelectionModel().getSelectedNode(); if (! this.treeEditor) { // 6 this.treeEditor = new Ext.tree.TreeEditor(treePanel, {}, { cancelOnEsc : true, completeOnEnter : true, selectOnFocus : true, allowBlank : false, listeners : { complete : onCompleteEdit } }); } this.treeEditor.editNode = selectedNode; this.treeEditor.startEdit(selectedNode.ui.textNode); } {1} Create our TreeEditor complete event handler {2} Invoke an Ajax request {3} Revert the change if the server returns success : false {4} If the request fails, revert the change {5} Configure our edit MenuItem handler {6} Create a tree editor if it does not exist. In Listing 9.3, we create two methods that take care of the edit functionality. The first is the handler that will be called when the edit event of the TreeEditor is fired and is responsible for firing off an Ajax request to the server to save the modification. The second is the handler that is fired when the edit MenuItem is clicked. Here is how they work. When the TreeEditor fires the complete event, it passes three parameters to the listeners; a reference to the TreeEditor that fired the event, and the new value and old value. Our onCompleteEdit{1} event handler uses all three parameters to do its job. This method first sets a local reference to the TreePanel that is being edited and masks its element. It then sets two more local references to the node being edited and its ID. Licensed to Alison Tyler Download at Boykma.Com 14 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Next, it fires of an Ajax request{2} to save the data. Notice that in the parameters configuration object of the Ajax request, it’s passing the id of the node and its new name. This would be important if you had to modify a value in a database where an ID identifies nodes. If there were other application-specific values for that node that you were looking to send as parameters to the server, you can find them in the attributes property of the node. Anything properties that are part of the node to create the tree are tucked away for use in the attributes property. For instance, employee nodes could have employee-specific personal attributes, such as DOB or sex on each node. Obviously, this depends on the application you’re developing for. Also, if you wanted to send an attribute from the node’s parent, you can access it by the node’s parentNode property and just tack it on to the params configuration object for the Ajax request. In the request’s success handler, the return JSON from the server is encoded and success{3} property tested. If the server returns a false success property the method reverts the value of the node. This is helpful if an invalid value is placed per business rules processed on the server. Likewise, if the request fails for some reason, the failure handler{4} is triggered and the node’s text value will be reverted. Both handlers will unmask the TreePanel when they are triggered. The second method in this listing, onEdit{5}, will be called when the edit MenuItem is clicked. It first creates a local reference for the TreePanel and the selected node. It then moves on to create an instance of TreeEditor{6} and sets a self (this) reference if it does not exist. Notice that it configures the onCompleteEdit listener for the complete event. Lastly, it will set the editNode property as the selectedNode on the TreeEditor and triggers the editing of that node by calling startEdit and passing the selectedNode’s ui.textNode reference, which will tell the TreeEditor where to render and position itself. Triggering the edit in the manner will ensure that the TreeEditor does not scroll the node to the top of the list if the TreePanel is scrollable, thus preventing this undesired effect from happening. Cool, we have our edit logic all in place, which means we can see it in action! Refresh your page and right click on a node and click on the edit MenuItem. I’ll follow along and show you what I see. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 15 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 Figure 9.7 The results of editing a node in the TreePanel using the TreeEditor with an Ajax request to save the data. In Figure 9.7, I modified the Accounting department name to Legal by right clicking on it, which selected the node. I then clicked on the edit MenuItem, which rendered the TreeEditor where the text of the node is. Remember that the TreeEditor knows where to render and position itself because we passed the textNode reference of the TreeNode’s ui. I then changed the name from “Accounting” to “Legal” and hit Enter on the keyboard. This caused the node value to change and the complete event to fire, thus triggering the onComplete method. Because the server accepted the value, the TreePanel’s element was unmasked and the new value persisted in the UI. Remember that if the server returned { success : false } or the request failed, the text value of the node would have been reverted. This wraps up the easiest of the CRUD functionality for our TreePanel. Editing names this widget is a quite a common task among web applications. Naturally, how you implement it is up to the needs of the business and requirements. Using the TreeEditor results in a cleaner application flow by saving you from having to use an input dialog box, such as MessageBox.prompt. Next, we’re going to ratchet up the level of difficulty by tackling the deletion of nodes. This will be far more complex than what we’ve done previously in this chapter. 9.5.3 Tackling delete To setup the delete functionality of our TreePanel, we’re going to have to create a handler for the delete MenuItem. Naturally, requirements usually dictate that a confirmation dialog is presented to the user, so we’re going to have to code for user confirmation. To make things a bit easier, we’ll use the out of the box MessageBox.confirm dialog. This means that we’ll have to construct a callback method for the confirmation dialog. The dialog Licensed to Alison Tyler Download at Boykma.Com 16 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 callback is what will trigger the Ajax request and ultimately delete the node if the server returns a favorable result. Now that we have an idea of what we need to do, we can get on with coding the handler methods. Listing 9.6 Adding deletion functionality to our TrePanel var onConfirmDelete = function(btn) { // 1 if (btn == 'yes') { var treePanel = Ext.getCmp('treepanel'); treePanel.el.mask('Deleting...', 'x-mask-loading'); var selNode = treePanel.getSelectionModel().getSelectedNode(); Ext.Ajax.request({ // 2 url : 'deleteNode.php', params : { id : selNode.id }, success : function (response, opts) { // 3 treePanel.el.unmask(); var responseJson = Ext.decode(response.responseText); if (responseJson.success === true) { selNode.remove(); // 4 } else { Ext.Msg.alert('An error occured with the server.'); } } }); } } var onDelete = function() { // 5 var treePanel = Ext.getCmp('treepanel'); var selNode = treePanel.getSelectionModel().getSelectedNode(); if (selNode) { Ext.MessageBox.confirm( // 6 'Are you sure?', 'Please confirm the deltion of ' + selNode.attributes.text, onConfirmDelete ) } } {1} The confirmation message box callback {2} Invoking an Ajax request to delete the selected node {3} The Ajax request success handler {4} Remove the selected node if the server returns true {5} The delete MenuItem handler {6} Confirming the deletion of the selected node In Listing 9.6, we create the two methods that take care of the delete operations of our CRUD functionality. The first method, onConfirmDelete{1}, is the handler for the confirmation dialog that we’ll create a little later on. If the ‘yes’ button is clicked in the confirmation dialog, it will mask the TreePanel and invoke an Ajax request{2} to delete the selected node. Notice that we’re only passing the ID of the node to the server. Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 17 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 In our fictitious min-application, the server would take value of the node ID and perform a delete operation in the database or file system and return something like {success:true}, which fires the request’s success{3} handler. This removes the TreePanel’s element mask and updates the UI by removing the node{4} from the TreePanel by using the node’s remove method. To reduce complexity for this listing, I left out the Ajax request failure handler, which would post a failure message to the user. Naturally, when you develop your applications, you’ll want to add it. In this case, it should remove the TreePanel’s element mask and alert the user that a failure occurred. The second method we create, onDelete{5}, is the handler for the delete MenuItem. When this method is called, it presents a confirmation dialog box by using the out of the box MessageBox.confirm{6} method and pass in three arguments; the title, message body and callback. This presents the user with a message and two options to proceed. Either button will trigger the callback, but remember that perform the deletion of the node the ‘yes’ button has to be clicked. Please refresh your UI and delete a node. I’ll do the same and we’ll discuss how things transpire. Below is an illustration of what happened when I refreshed my UI and deleted the Accounting department node. Figure 9.8 Deleting a node with a confirmation box and an Ajax request to the server. After right clicking on the Accounting department node, our customized context menu appeared. I then clicked on the delete MenuItem, which triggered the onDelete handler. This immediately displayed the confirmation dialog box. I clicked on yes, which then caused the TreePanel’s element to mask, giving me an indication that a request was being made to delete the node. When the server returned a favorable result, the mask was removed and the Accounting department disappeared. Licensed to Alison Tyler Download at Boykma.Com 18 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 In a real world application deleting a branch node would generally require the server to recursively gather a list of all of the child nodes and remove them from the database before removing the branch node itself. A clever way to do this could be to setup a trigger in the database to call a stored procedure to delete all associated child nodes when a delete operation is performed on a container node. Deleting nodes from our TreePanel required a bit more effort because of the typically required confirmation dialog. Adding a node however is equally more difficult because the UI code needs to know what type of node is being added. Is it a branch or leaf node? Next, we’ll see how to code for this type of branch in logic and have the UI react accordingly. 9.5.4 Creating nodes for our TreePanel To create a node using the TreeEditor, we’re going to have to do a lot of work, which makes this the hardest listing in this chapter but the results are going to be pretty sweet. Because the TreeEditor needs to bind and display on top of a node, we will need to inject a node into the TreePanel and trigger an edit operation on the node. Once the edit of the new temporary node is complete, an Ajax request is fired off to the server with the name of the new node. If the server returns favorably, the ID is set to the node. This works very similarly to the way nodes are added to the EditorGrid we created last chapter. Here is the code for the create node functionally. Listing 9.8 Adding create functionality to our TreePanel var onCompleteAdd = function(treeEditor, newValue, oldValue) { // 1 var treePanel = Ext.getCmp('treepanel'); if (newValue.length > 0) { // 2 Ext.Ajax.request({ url : 'createNode.php', params : { newName : newValue }, success : function (response, opts) { treePanel.el.unmask(); var responseJson = Ext.decode(response.responseText); if (responseJson.success !== true) { Ext.Msg.alert('An error occured with the server.'); treeEditor.editNode.remove(); // 3 } else { treeEditor.editNode.setId(responseJson.node.id); } } }); } else { treeEditor.editNode.remove(); // 4 } } var onAddNode = function() { // 5 var treePanel = Ext.getCmp('treepanel'); var selNode = treePanel.getSelectionModel().getSelectedNode(); Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 19 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 if (! this.treeEditor) { this.treeEditor = new Ext.tree.TreeEditor(treePanel, {}, { cancelOnEsc : true, completeOnEnter : true, selectOnFocus : true, allowBlank : false, listeners : { complete : onCompleteAdd } }); } selNode.expand(null, null, function() { // 6 var newNodeCfg = { text : '', id : 'tmpNode', leaf : (selNode.id != 'myCompany') } var newNode = selNode.insertBefore(newNodeCfg, selNode.firstChild); this.treeEditor.editNode = newNode; this.treeEditor.startEdit(newNode.ui.textNode); }, this); } {1} The TreeEditor complete event handler {2} Invoke an Ajax request if there is a new node name {3} Remove the temporary node if the server returns unfavorably {4} The temporary node is removed if the new node name is not present {5} The add MenuItem handler {6} Expand the selected node, insert a node and trigger an edit We are doing quite a bit of work in listing 9.8 to get the create functionality to work smoothly. Just like the code for the edit operations, to reduce complexity for the listing, we omitted the Ajax request failure handler. Here’s how all of this stuff works. The first method in the listing is the TreeEditor complete event handler, onCompleteAdd{1}. This handler is arguably more complex than the edit TreeEditor complete event handler for a multitude of reasons. When this method is fired, if a new name has been entered for the node, an Ajax request will be fired{2}. If the server responds favorably, it will return the database id of the newly inserted node. The code will the set the new node’s id via its setId method. This is important because it will ensure that subsequent edits to this node will occur against the proper database record. If the server returns unfavorably the temporary node is removed from the TreePanel{3}. This also occurs if onCompleteAdd is called without a new name{4}. Having the UI coded this way ensures that phantom nodes cannot exist in the TreePanel. If you plan on using this pattern to create nodes, be sure to add a failure handler, which should alert the user of a failure and remove the temporary node. The second method, onAddNode{5}, is the handler for our add MenuItem. This method does quite a bit of work as well to provide the user with a temporary node to add to the TreePanel. It does this by instantiating an instance of TreeEditor if it did not exist and binding the onCompleteAdd handler to its complete event. Next, it expands the Licensed to Alison Tyler Download at Boykma.Com 20 Jesus Garcia/Ext JS in Action Last saved: 6/29/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 selected node. We expand{6} the selected node because the TreeEditor needs a home, which is going to be the soon-to-be-created node. Notice that we’re passing nulls as the first two parameters. We pass null because we don’t need to set them. They are for deep expansion (to expand branches of branches) and to enable animation. We do pass the third and fourth parameters however, a callback method and scope for which it is to be called, which is the onAddNode method. The reason we use a callback is because the Ajax load is asynchronous and we need to make sure that the load is complete for a branch node and all of its children are rendered before we can inject a new node. The callback method creates a node configuration object with an empty string for the text, an id of ‘tmpNode’ and uses JavaScript shorthand to set the leaf value if the selected node’s id is not ‘myCompany’. This ensures that if the department has been selected to have a node added to it, it will set the leaf property to true, meaning a person, else it’s false, meaning a department node. It then uses the new node configuration to create a new TreeNode using the selected node’s insertBefore method, passing the newNode configuration object and the selected node’s firstChild reference to specify where to insert the new node. Lastly, an edit operation is triggered on the newly created node, giving the users the ability to add a name to it. Wow, that was a long one! Let’s refresh the UI and see our code in action. Figure 9.9 Adding a new node to our TreePanel using the TreeEditor. In Figure 9.9, we can see the how we can use the TreeEditor to add nodes to the TreePanel in a way that mimics operating system behavior. When I right clicked on the Accounting department node, the dynamic context menu appeared as expected. I clicked on the add MenuItem, which triggered our onAdd handler. This caused the Accounting node to expand. After the child nodes were loaded, a new node was inserted and an edit operation was immediately triggered on that node using the Licensed to Alison Tyler Download at Boykma.Com Last saved: 6/29/2009 Jesus Garcia/Ext JS in Action 21 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=525 TreeEditor. I typed in a new employee name and hit the Enter key on my keyboard. This caused the complete event to be fired by the TreeEditor, thus invoking the onCompleteAdd handler, which masked the TreePanel and performed an XHR. The server returned favorably so the new node stayed in the UI and the database ID of the newly inserted node was applied to it the TreePanel. You can also exercise this code to add department nodes to the tree and add employees to that. This wraps up our coverage for create, which is our last CRUD operations. As you can see, with a little bit of work, we can create a TreePanel that allows for full CRUD for nodes complete with inline editing. 9.6 Summary In this chapter, we covered quite a bit of code when learning about TreePanels and how to setup really cool CRUD interaction for nodes. We started by learning about the TreePanel and discussed supporting classes like the TreeLoader, TreeNode and TreeNodeUI. Remember that the TreePanel is a direct descendant of Panel and can be configured as a child for any Container. We constructed a static TreePanel, where the nodes were read by memory and analyzed how the JSON should be formatted. We then moved on to building a dynamic TreePanel that loaded data from a remote data source and spent lots of time enabling full CRUD operations. To enable CRUD, we learned how to dynamically modify, enable and disable the reusable context menu. We saw what it took to setup adding and editing with the TreeEditor class, which gives the TreePanel the ability to edit node names inline. Until now, we’ve only scratched the surface with some of the “tools” of the framework, the Toolbar, menus and buttons. In the next chapter, we’re going to dive deep and learn more about how they work and how we can better put them to work for our applications. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/10/2009 Jesus Garcia/Ext JS in Action 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=XYZ 10 Menus, Buttons, and Toolbars In the chapters leading up to this one, we’ve configured and used Menus, Buttons and Toolbars, but we really never got to take a moment to really look at these widgets and learn more about them and what else they have to offer. One may ask the question “Why are these three lumped into one chapter?” The answer is simple. Their use cases are related in one way or another. For instance, a Button can be configured to display a menu and a Toolbar can contain that Button. It is for this reason that we’ll use this time to take an in-depth look at the Ext.menu and Item classes, where we’ll learn about what can and how to display items in a menu, which include non-menu items. Afterwards, we’ll focus on the Button class and its cousin the SplitButton, where we’ll learn about things such as how to change the inner button layout and how to attach a menu to it. Soon after we become more familiar with the button class, we’ll create clusters of buttons, known as ButtonGroups. We’ll even take a moment to create a ButtonGroup that emulates one from MS Word 2007’s famous ribbon toolbar. Lastly, we’ll tie all of this together in a Toolbar and discuss how to abstract like- functionality with the use of Ext.Action to save us time while developing for Buttons and Menus. 10.1 Menus at a glance For a moment, let’s take a step back and ask ourselves, “What is a menu?” In the simplest definition, a Menu is something that displays a list of items to choose from. Menus are all around us, including your local coffee shop, where you choose the blend of java to next indulge on. This, by the way, is my favorite kind of menu, as I am a self-proclaimed coffee addict. Licensed to Alison Tyler Download at Boykma.Com 2 Jesus Garcia/Ext JS in Action Last saved: 7/10/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=XYZ If we think about it, in the computer world, we use menus all the time. The most common are the typical File, Edit and View menus on your application toolbar, where we choose a menu item to Open a document or Copy something to the clipboard. Other ways we see menus are by means of what is commonly known as a Context menu, where a menu is displayed within the context of an item that has been either clicked or right clicked. This context menu only displays options only available for the item that the menu was summoned to display. Right clicking on anything on your computer will display a context menu. In Ext JS, we can use and display menus in the same way. In Ext JS, Menus typically contain one or more MenuItems or descendants of MenuItems. While in 2.0, it was entirely possible to embed another widget inside of the menu, it was rather laborious to do so. In version 3.0 of ExtJS, accomplishing this task is so much easier because the Menu widget extends Container and uses the MenuLayout. This means that you get the power of the Container model managing your Menu, affording you the flexibility to manage the Menu’s children much like you would any Container. As in the Desktop menu model, Ext JS Menus, can be displayed a number of ways. In the previous GridPanel and TreePanel chapters, we learned how to configure an instance of Menu to display and replace the browser’s Context menu. While this is a common use case for this Menu widget, it’s not the only one. You have complete flexibility when developing with Menus. For instance, Menus can be attached to a button and displayed upon the click of that button. Or they can be anchored to and shown by any element on demand. How you employ the Menu will be based on the requirements of your applications, which means that you can employ Ext JS Menus much like menus are deployed in the desktop model. I’m itching to start writing some code, aren’t you? 10.1.1 Building a menu Even though we’ve built and displayed a context menu in the past, we’ve only scratched the surface on how to use the Menu widget. In the coming sections, we’re going to explore the different Menu items and how to use them. We’ll begin by creating a plain-Jane menu with a single menu item with a newDepartment handler that we’ll be reusing. Listing 10.1 Building our base menu var genericHandler = function(menu.Item) { // 1 Ext.MessageBox.alert('', 'Your choice is ' + menu.Item.text); } var newDepartment = { // 2 text : newDepartment Item', handler : genericHandler } var menuItems = [ // 3 newDepartment ]; Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/10/2009 Jesus Garcia/Ext JS in Action 3 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=XYZ var menu = new Ext.menu.Menu({ // 4 items : menuItems, listeners : { 'beforehide' : function() { // 5 return false; } } }); menu.showAt([100,100]); {1} A newDepartment handler to provide visual feedback {2} Xtype configuration for a Ext.menu.Menu.Item {3} A list of menu items for our menu {4} Create an instance of Ext.menu.Menu to display {5} Prevent the menu from hiding for ease of testing In Listing 10.1 we do a whole heck of a lot to setup our test bed, which contains a newDepartment handler and a base menu with a single Item. We accomplish this with just a few lines of code. Here’s how it works. The first thing we do is create a genericHandler{1} method, which we’ll use for just about every menu Item that we create and exercise. When clicked, menu Items call handlers with two arguments, the instance of menu Item that was clicked and the instance of Ext.EventObject that was generated due to the click action of the user. In our newDepartment handler, we only accept the menu Item argument and use its text property to display a MessageBox alert window to provide us with feedback that the handler was called. Next, we create a configuration object for a menu Item, newDepartment{2}, which as a text property and a handler property, which is a reference to our genericHandler. Notice that we’re not specifying an xtype property for this configuration object. This is because the defaultType property for the Menu widget is ‘menuitem’ out of the box. We then move on to create an array referenced as menuItems{3}. We do this so we can abstract the list of menu Items that we’re creating for our test Menu. As we continue on, we’ll add items to this list. An instance of Ext.menu.Menu{4} is created next, which has two properties. The first is items, which is the reference to the menuItems array of items. Next is a listeners configuration object, which contains a beforehide event listener that we place in here just for testing purposes. If you recall our Component model conversation from eons ago, you may remember that there are certain events that can be vetoed causing the cancelation of a specific behavior. By the listener explicitly returning false, we prevent the menu from hiding. This keeps the menu frozen on screen, which is perfect for testing. Lastly, we show the menu by calling its showAt method, and pass single argument, which is an array of coordinates, top and left. Let’s see this thing in action. Licensed to Alison Tyler Download at Boykma.Com 4 Jesus Garcia/Ext JS in Action Last saved: 7/10/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=XYZ Figure 10.1 Our newDepartment Menu Item (left) and the Item handler (right) in action. When we render our code, we see the Menu with the single menu Item (Figure 10.1, left) and when we click on it, the MessageBox appears (Figure 10.1, right), providing feedback that the handler was called. Cool! Something is missing however. The menu seems, well… a bit plain to me. What do you think about adding some icon flare to the menu? 10.1.2 Obtaining and using Icons Early in the book, we discussed how to add icons to widgets by means of the iconCls configuration property. With so much material covered between then and now, I think it’s relatively safe to say that a quick refresher is in order. Most developers who use Ext Js use 16x16 icons in their applications. The preferred method to specify icons is by means of CSS rules. Here is an example. .icon-accept { background-image: url(icons/accept.png) !important; } The icon-accept CSS class above, simply contains a background-image rule with an !important directive. That’s it, no magic. Per the Ext JS CSS (unwritten) standard, I typically prefix all CSS icon rules with “icon-“. This ensures that there is no cross pollution with any other CSS namespace in the CSS domain. I would highly recommend doing the same. By now you’re probably wondering, where can I get these icons? Well, the most widely used icon set is the famfamfam silk icon set has over 1000 16x16 icons at your disposal and was developed by Mark James. It is licensed under the Creative Commons Attribution 2.5 License. To download it, visit http://famfamfam.com/lab/icons/silk/. If that’s not enough, then you can add over 460 icons from developer Damien Guard, known as the Silk Companion and contains many useful derivatives of the famfamfam Silk icons. To download this set, visit http://damieng.com/creative/icons/silk- companion-1-icons. The Silk Companion set is Licensed both under the Creative Commons Attribution 2.5 and 3.0 Licenses. NOTE Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/10/2009 Jesus Garcia/Ext JS in Action 5 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=XYZ To read learn more about the Creative Commons Attribution 2.5 and 3.0 Licenses, see http://creativecommons.org/licenses/by/ and click on the 2.5 or 3.0 links. If you have been doing your math, you probably calculated over 1460 icons from which to choose from if you use both sets. This means 1460 CSS rules to write. Let me tell you that there is absolutely no way I would write those CSS rules by hand! How do we solve the problem of having to write all of those rules by hand? 10.1.3 Taming the icon madness I solved this problem easily by downloading both icon sets, merging them into one directory and writing a quick and dirty BASH shell script to compile a CSS file that contains a rule for each icon. To use this compliation, you will need to download the compiled set at http://extjsinaction.com/icons/icons.zip. When you extract the file you’ll find two CSS files, icons.css and icons.ie6.css, along with the directory icons, which contains the icon images. The first CSS file is a compilation for all of the icons that are in their native PNG (Portable Network Graphics) format, which works well for Mozilla-based browsers as well as Internet Explorer 7 and 8. PNGs, however, do not get rendered properly in Internet Explorer 6 without some extensive JavaScript hacking, which I refuse to do. To solve that problem, I created GIF versions (via shell script, of course) of the PNG icons to make them compatible with IE6 and compiled a list of the gif files named icons.ie6.css To use any one of these, you must include the required CSS in the head of your browser, as such: Next, we need to change the newDepartment configuration object to include the iconCls property. var genericItem = { text : 'Generic Item', handler : genericHandler, iconCls : 'icon-accept' } Refresh the page and see the result of our change. Figure 10.2 The results of adding an Icon to our menu Item via the iconCls configuration property. Licensed to Alison Tyler Download at Boykma.Com 6 Jesus Garcia/Ext JS in Action Last saved: 7/10/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=XYZ As we can see in Figure 10.2, the accept.png icon image file now displays to the left of our menu Item as instructed in the iconCls configuration property. Remember that you can use any of these icons in any widget that accepts the iconCls property, which includes Buttons, TreesNodes and Panels to just name a few. We’re going to be using this icon set for the rest of this chapter and every chapter moving forward. Please remember to include the icons when you generate new pages to work with. Now that we have this out of the way, we can move on to the next typical Ext JS menu problem, which is adding submenus to our Menus. 10.1.4 Adding a submenu Developing submenus for menus is something that is another pretty common task for Ext JS developers. They perform two useful functions at the same time. The first is the organization of like menu items and the second is a direct result of the grouping, which is cleanup of the parent menu. Let’s add a submenu to our Menu. You’ll want to add this somewhere before the menuItems array we created earlier. Listing 10.2 Adding a submenu to our base menu var newDepartment = { // 1 text : 'New Department', iconCls : 'icon-group_add', menu : [ // 2 { text : 'Management', iconCls : 'icon-user_suit_black', handler : genericHandler }, { text : 'Accounting', iconCls : 'icon-user_green', handler : genericHandler }, { text : 'Sales', iconCls : 'icon-user_brown', handler : genericHandler } ] } {1} The a menu Item configuration object with a submenu {2} The menu shortcut list of configuration objects In Listing 10.2, we create another newDepartment menu Item, newDepartment{1}, that contains the typical configuration properties such as text and iconCls. What’s new is the menu property, which contains a list of menu Item configuration objects, each using our genericHandler. This shortcut method of adding a submenu a menu Item is common. In the sprit of Ext JS, there is more than one way to skin this cat. You could elect to set menu as an instance of Ext.menu.Menu or specify a Menu XType configuration object Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/10/2009 Jesus Garcia/Ext JS in Action 7 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=XYZ Below is what the code would look like if we specified an xtype instead of a shortcut array of menu Items. var newDepartment = { text : 'New Department', iconCls : 'icon-group_add', menu : { xtype : 'menu', /* menu specific properties here */ items : [ /* menu Items here */ ] } } Choosing the implementation method is up to you. I suggest only creating a separate configuration object for a submenu if you need to set menu-specific properties. Obviously, if you have no menu-specific properties to apply to a submenu, just set the menu property to an array of menu Items. Next, we need to add our menu to the menuItems array. var menuItems = [ genericMenuItem, newDepartment ]; To see the fruits of our labor, we’re going to have to refresh our page. Figure 10.3 The addition of a menu Item with a submenu. Cool, we can now see the “New Department” menu Item displayed in our menu. Hovering over the MenuItem reveals the submenu. Clicking on any of menu Items in the submenu will result in the MessageBox alert dialog displaying the text of the submenu. While this is usable, it can be improved somewhat. Next, we’ll explore adding a SeparatorItem to the main menu and a TextItem to the submenu, cleaning up the Menu UI a little bit. Licensed to Alison Tyler Download at Boykma.Com 8 Jesus Garcia/Ext JS in Action Last saved: 7/10/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=XYZ 10.1.5 Adding a Separator and TextItem Much like the Toolbar separator widget is used to separate Toolbar items with a line, menu Separators are used to physically separate menu Items. These are typically used to separate groups or clusters of like menu items from another. Because all of the menu Items moving forward will contain submenus, we should separate them with a separator. Modify the menuItems array as such: var menuItems = [ genericMenuItem, '-', newDepartment ]; Simply add a string with a hyphen and to the menu’s items list and it will get interpreted as menu.Separator. There is now a horizontal line separating the first and the second menu Items. While the separator is generally used to group like items, it is much more powerful than a simple line. Above, we used the shortcut to generate the separator. Technically, it extends menu.BaseItem, which means that you can make it clickable, and assign a handler to it. While it’s technically possible to do this, in terms of UI design, I do not advocate using it in this manner. Cool, we’ve added the separator, but I think we can stand to dress up the department submenu a bit. For this, we’ll add a menu TextItem. To do this, we’ll have to create a configuration object to add it to the department submenu. Listing 10.3 Adding a text item to the departments menu { xtype : 'menutextitem', // 1 text : 'Choose One', style : { 'border' : '1px solid #999999', // 2 'margin' : "0px 0px 1px 0px", 'display' : 'block', 'padding' : '3px', 'font-weight' : 'bold', 'font-size' : '12px', 'text-align' : 'center' 'background-color' : '#D6E3F2', } }, In listing 10.3, we injected the above configuration object as the first element of the menu array for the newDepartment menu. Remember that the defaultType for menu is ‘menuitem’, so we have to override this setting by setting the xtype property as ‘menutextitem’{1}. We also set the style{2} of this TextItem to ensure that it looks presentable. I’ll show you in just a bit what the un-styled TextItem looks like. To see the TextItem in action, we need to refresh our page. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/10/2009 Jesus Garcia/Ext JS in Action 9 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=XYZ Figure 10.4 Choosing not to style a TextItem (left) will result in an incomplete look and feel for your menu. Adding a touch of style will clean up the menu. In looking at Figure 10.4, it’s easy to see the difference between the un-styled and styled TextItem. Obviously, the actual Style of the item is left up to you and requirements. We’ve just covered how to add both the menu Separator and TextItem, both which aid in the visual styles of our Menu. There are two other types of menus that I’d like you to become familiar with, which are the DateMenu and ColorMenu. 10.1.6 Picking a color and choosing a Date As developers, we’re often tasked to allow for the selection of a date or color. Perhaps the start and end date of an appointment or the color of text to be displayed. Ext JS provides to widgets that allow us to give this functionality to our users via Menus. They are the ColorMenu and DateMenu, which are both descendants of Ext.menu.Menu. This means that you can create a direct instance of either and anchor and display them like their super class. Unlike a traditional menu, they not manage any other child items other than what they are designed for. Instead of creating instances of these directly, I want to add them to our test-bed menu. But, in order to do that, we’re going to have to add menu Items that we can anchor them to. Also, their handlers pass somewhat different arguments than menu Items, so we’ll need to create a generic handler specifically for these newly added menus. Here comes that itch to code again. Lets get started. Listing 10.4 Using ColorMenu and DateMenu var colorAndDateHandler = function(picker, choice) { // 1 Ext.MessageBox.alert('', 'Your choice is ' + choice); } var colorMenuItem = { // 2 text : 'Choose Color', iconCls : 'icon-color_swatch', menu : { xtype : 'colormenu', // 3 handler : colorAndDateHandler } } Licensed to Alison Tyler Download at Boykma.Com 10 Jesus Garcia/Ext JS in Action Last saved: 7/10/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=XYZ var dateMenuItem = { text : 'Choose Date', iconCls : 'icon-calendar', menu : { xtype : 'datemenu', // 4 handler : colorAndDateHandler } } var menuItems = [ genericMenuItem, '-', genericWithSubMenu, colorMenuItem, // 5 dateMenuItem ]; {1} The color and date handler {2} The menu Item to display the ColorMenu {3} The actual ColorMenu configuration Object {4} The DateMenu configuration Object {5} Adding the ColorMenu and DateMenu to the menu items array In Listing 10.4, we create a common handler{2} for both the ColorMenu and DateMenu, which is very similar to the genericHandler that we created earlier, but accepts a second argument, choice. Here’s how this stuff works. When an item is chosen from either the ColorMenu or DateMenu, the handler is fired with two arguments. The first is the picker from which the item was selected and the first is the value of the item that was selected, which we aptly named choice. The reason this works the way it does is because the ColorMenu actually uses the ColorPallete widget and relays its select event. Likewise, the DateMenu uses the DatePicker widget in the exact same way. OK, that was a fun, but informative digression. Lets get back on track. The next task Listing 10.4 accomplishes is create the configuration objects{2} for the menu Items that will be used to display the ColorMenu{3} and DateMenu{4}. Notice that we register the colorAndDateHandler with each both the ColorMenu and DateMenu. Also, we’re choosing appropriate icons for these menu Items. Isn’t candy is awesome? Lastly, we append the newly created colorMenuItem and dateMenuItem references to the menuItems array. Let’s refresh our UI and see the changes to our Menu. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/10/2009 Jesus Garcia/Ext JS in Action 11 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=XYZ Figure 10.5 The rendered results of the ColorMenu(left) and DateMenu(right). As illustrated in Figure 10.5, our DateMenu and ColorMenu can be displayed via hovering over the respective menu Item. Clicking a color or a date in the respective Menu will trigger the handler, resulting a MessageBox alert displaying the value of the item selected. The respective ColorPallete for the ColorMenu and DatePicker for the DateMenu can be customize by applying ColorPallete or DatePicker specific properties to their respective containers. For instance, to show only the colors Red, Green and Blue on the ColorMenu, set the colors property with an array of color hex values as such: colors : ['FF0000', '00FF00', '0000FF'] Setting this property will result in the ColorMenu displaying just three boxes, one for each of the colors we described in the array. To learn more about the ColorPallete or DatePicker specific properties to configure, check out their respective API documents. We’ve just tackled using the ColorMenu and DateMenu widgets. We have yet one more menu topic to discuss, and that is menu CheckItems, which allows you to configure a menu item that works like a checkbox or radio group. 10.1.7 Put that menu Item in check In forms, check boxes and radio groups allow users to select an item, where the selection is visibly persisted in the UI. In Microsoft Word for Apple OS X, for instance, I can choose how to view a document by clicking in the View menu and selecting a view to activate. Licensed to Alison Tyler Download at Boykma.Com 12 Jesus Garcia/Ext JS in Action Last saved: 7/10/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=XYZ Figure 10.6 A check view menu in OS X, where the user selection is persisted. In the figure above, I selected the “Print Layout” view and the selection is persisted for each and every subsequent viewing of this menu. The same paradigm could exist in Ext JS application. One such paradigm could exist where a user chooses between a GridPanel or DataView to display data from a single store. First, I want to show you exactly how to leverage a single menu CheckItem and multiple CheckItems in a checkGroup. Afterwards, we’ll discuss a possible use for this widget in hopes that it will spark ideas in your mind on how to leverage it. We’ll create a single check item and add it to our menuItems array. Listing 10.5 Adding a single Checkitem var singleCheckItem = { // 1 text : 'Check me', checked : false, // 2 checkHandler : colorAndDateHandler // 3 } var menuItems = [ genericMenuItem, '-', genericWithSubMenu, colorMenuItem, dateMenuItem, '-', // 4 singleCheckItem ]; {1} Creating a single CheckItem configuration {2} Adding the ‘checked’ boolean value {3} Specifying a checkHandler {4} Adding a SeparatorItem and the CheckItem to the menuItems array In Listing 10.5 we create a single CheckItem{1} using typical XType configuration. If we run down the list, we only see three properties, none of which are the actual xtype. This is because we can be extremely lazy with this widget and just specify whether the item is checked or not. Ext JS will conveniently handle the instantiation of the menu.CheckItem widget for us if the checked{2} property is a Boolean and is present. The next property of importance is checkHandler{3}, which is similar to the typical menu Item handler property, except it passes whether the CheckItem has been checked Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/10/2009 Jesus Garcia/Ext JS in Action 13 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=XYZ or not, which is the second argument that is passed to this method. It’s much like the arguments passed to the ColorMenu and DateMenu handlers. It is for this reason, we’re reusing the colorAndDateHandler. Lastly, we add a menu Separator and our singleCheckItem to the menuItems array. Here is what the addition of the CheckItem looks like in our menu. Figure 10.7 Clicking our CheckItem(left) makes a check icon appear and triggers the checkHandler (right). Because we used the menu Separator, we can see the physical separation of our menu CheckItem. Because we set the checked value to false, the CheckItem appears with an icon that is unchecked. Clicking on it calls the checkHandler, which displays the MessageBox alert dialog. NOTE We didn’t set the iconCls for the CheckItem, but you technically can. Setting the iconCls, however, means that you’re responsible for programmatically changing the iconCls upon call of the checkHandler, which can be done by using the Item.setIconClass method. See the Menu.Item API documentation page for details. The use and configuration of a single CheckItem is obviously pretty easy. Remember that they can be grouped, which allows users to select a single CheckItem out of that group, much like the Microsoft Word View menu we saw above. Next, we’ll construct such a group. 10.1.8 Select only one item at a time The configuration of a single CheckItem is relatively trivial to that of a group of CheckItems. This is mainly because you’re configuring multiple CheckItems at the same time. Lets construct a cluster of grouped CheckItems. Listing 10.6 Adding a single Checkitem var setFlagColor = function(menuItem, checked) { // 1 if (checked === true) { var color = menuItem.text.toLowerCase(); var iconCls = 'icon-flag_' + color; Ext.getCmp('colorMenu').setIconClass(iconCls); Licensed to Alison Tyler Download at Boykma.Com 14 Jesus Garcia/Ext JS in Action Last saved: 7/10/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=XYZ } } var colorCheckMenuItem = { // 2 text : 'Favorite Flag', id : 'colorMenu', iconCls : 'icon-help', menu : { // 3 defaults : { checked : false, group : 'colorChkGroup', checkHandler : setFlagColor }, items : [ { text : 'Red' }, { text : 'Green' }, { text : 'Blue' } ] } } {1} A common checkHandler for each of the CheckItems {2} The menu Item that will contain a menu of the CheckItems {3} A Menu configuration object for a the group of CheckItems {4} The list of grouped CheckItems In listing 10.5, we create a new common checkHandler, setFlagColor{1}, for the grouped CheckItems we’re going to create later. We do this because when CheckItems are grouped, they will each call their respective checkHandler when any member in the group has been clicked with each call passing the checked value of the respective CheckItem. This shared checkHandler will test the value of the checked parameter. If it is true, it will call setIconCls on the parent menu Item of the CheckItems menu. This will effectively set the icon with a flag color that matches the selection of the grouped CheckItem. This is just a fun way to provide visual feedback that something was selected and flex some of the menu Item’s muscle and flexibility. We create the menu Item that, colorCheckMenuItem{2}, which has a question mark icon as default. Notice that the menu configuration is an object{3} instead of an array. This is precisely what we were talking about before, where you can use an Object to configure a menu instead of an array of menu Item configuration objects. We do this because we want to set defaults on all menu items, which are checked, which is set to false, the common group that each CheckItem will belong to and the checkHandler. This means that the items array of the Menu configuration object can be simple objects with just a text property for each Item. Now that we have that complete, please refresh the page and let’s see this in action. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/10/2009 Jesus Garcia/Ext JS in Action 15 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=XYZ Figure 10.8 A group of CheckItems in action (left), where only one item can be selected (left and right). Refreshing our page reveals our recently configured “Favorite Flag” menu Item in our test menu with a question icon. Hover over that Item and it will reveal the submenu with the three, grouped color CheckItems. Notice that none of the items bear the check mark. Remember, this is because all items are set to “checked : false” per the defaults configuration object. We could have easily set any one item as “checked : true”. Click on a CheckItem and it will change the iconCls for the parent menu Item. Revealing the menu that contains the checked items again will result in a radio icon showing to the left of the selected item. Selecting a different Item will result in the relative changes occurring for that CheckItem. This wraps up our long, but detailed view into the world of the Ext JS Menu where we learned about and exercised all of the menu Items along with the color and date menus. Next, we’ll dive into the world of Ext Buttons and Split Buttons, where we’ll learn how to configure them and associate menus to them. 10.2 Users know how to push your Buttons In this section, we’ll learn how to implement Buttons and then move on to learn about the SplitButton, which is a descendant of the Button widget. Buttons in Ext JS 2.0 were very rigid, where they did not scale very well and the icon position could not be configured. It was also particularly difficult to have a button take part in a layout as a managed child item. Some of these limitations had to do with how the Button widget itself was constructed, where the Button wlass in 2.0 extended Component, which means that it is not meant to take place in a layout. Also, the Button Sprite images were not designed to allow the Button to scale much larger than its intended size. In 3.0 of the framework, this changes for the better. The Buttons in this version of the framework extend BoxComponent, which is where all of the child size management methods are, meaning that buttons are now fully capable of Licensed to Alison Tyler Download at Boykma.Com 16 Jesus Garcia/Ext JS in Action Last saved: 7/10/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=XYZ taking part in any layout. Likewise, the button sprites were updated to allow for extremely large buttons. You could have a single button take one half of the screen if you wanted to, though it would probably scare away your users. As we transition to learning more about and constructing buttons, we’ll learn that buttons have a lot in common with menu Items. 10.2.1 Building Buttons We’ll start out by creating a simple handler and a Button that will call it. Create a blank page that includes all of the required Ext JS files and the icon CSS file that we worked with just ab it ago. From our work with menu Items earlier, a lot of this stuff will be very familiar to you, which means that we can increase our pace a little bit. Listing 10.7 Building a simple Button with a generic handler. var btnHandler = function(btn) { // 1 btn.el.frame(); } new Ext.Button({ // 2 renderTo : Ext.getBody(), text : 'Plain Button', iconCls : 'icon-control_power', handler : btnHandler }); {1} The generic button handler {2} A generic button In listing 10.7, we create generic button handler, btnHandler{1}, that will simply call the frame effect on the element of the button that calls it. Next, we create an instance of an Ext Button and render it to the document.body by means of the Ext.getBody method call. We also set the text, iconCls and handler properties, which are exactly like the menu Item configuration properties. Load the page with this code and let’s see the button in action. Figure 10.9 Our button in action (left) and the frame effect (right), which is a visual indication that the handler was called. As in figure 10.9, our Button renders on screen, seemingly just waiting to be pushed. Clicking on it forces the handler to be called, which applies the frame effect to the button’s element, giving us visual indication that the handler fired properly. Simple, isn’t it? Next, we’ll attach a Menu to a Button, which is common practice in Ext JS applications. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/10/2009 Jesus Garcia/Ext JS in Action 17 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=XYZ 10.2.2 Attaching a menu to a Button You attach a Menu to a Button exactly in the same way you attach a Menu to a menu Item, which is specifying either a list of menu Item configuration objects or a complete Menu configuration object. For this exercise, we’re going to do the latter. Listing 10.8 Attaching a menu to a Button var setFlagColor = function(menuItem, checked) { // 1 if (checked === true) { var color = menuItem.text.toLowerCase(); var iconCls = 'icon-flag_' + color; Ext.getCmp('flagButton').setIconClass(iconCls); } } new Ext.Button({ renderTo : Ext.getBody(), text : 'Favorite flag', iconCls : 'icon-help', handler : btnHandler, id : 'flagButton', menu : { // 2 defaults : { checked : false, group : 'colorChkGroup', checkHandler : setFlagColor }, items : [ { text : 'Red' }, { text : 'Green' }, { text : 'Blue' } ] } }); {1} A modified version of the previous setFlagColor handler {2} Associating the menu to the Button In listing 10.8, we recreate the scenario for the previous CheckItems exercise, but modify it to fit the Button. Here’s how it works. We first create the checkHandler, setFlagColor{1}, which works exactly like the one we constructed earlier, except it changes the iconCls of the Button. Next, we create another Button that is very similar to the first Button we created and is rendered to the document.body. The main difference between this Button and the other is that we configured{2} a Menu with three grouped CheckItems, all registered to call the setFlagColor handler. Let’s see what our code additions and changes bring forth. Licensed to Alison Tyler Download at Boykma.Com 18 Jesus Garcia/Ext JS in Action Last saved: 7/10/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=XYZ Figure 10.10 Adding a menu to a Button, which contains a few menu CheckItems. At first glance at our newly configured Button, we can see that a little black arrow now appears to the right of the text. This is the visual indication to the user that this button contains a menu to be revealed by a press of the button. To display the Menu, click on the button. What happens? We see that the menu appears and that the button frame effect is shown as well. Wait. What? How can this be? Well, that’s because we didn’t remove the handler property, which means when the button is clicked the menu shows and the handler is called. In some cases, this is desirable behavior, but in most cases, it’s not. If all this button is to do is display a menu, then simply remove the handler. However, if you want a handler to be called separately from the display of the associated Menu, this is where the SplitButton can be useful. 10.2.3 Do a Split(Button) Use of a SplitButton is very similar to that of a Button. The main difference being that the SplitButton has a second handler that it can call when clicked, which is known as the arrowHandler. To exercise the SplitButton, we’re going to slightly modify Listing 10.8. Listing 10.9 Building a simple Button with a generic handler. new Ext.SplitButton({ // 1 renderTo : Ext.getBody(), text : 'Favorite flag', iconCls : 'icon-help', handler : btnHandler, id : 'flagButton', menu : { defaults : { checked : false, group : 'colorChkGroup', checkHandler : setFlagColor }, items : [ { text : 'Red' }, { text : 'Green' }, { text : 'Blue' } ] } }); Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/10/2009 Jesus Garcia/Ext JS in Action 19 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=XYZ {1} Using the SplitButton instead of Button To exercise the SplitButton, we’re using the same setFlagColor handler we created in listing 10.8, and all of the code is the same except we’re instantiating an instance of SplitButton instead of Button. That’s it. Let’s render this on screen so we can examine the visual and functional differences. Figure 10.11 Our SplitButton in action. As illustrated in Figure 10.11, we see that there is a separator line that has been added to the button, which is the only visual difference between a regular Button and SplitButton. But, this visual difference actually depicts the functional difference between the Button and SplitButton. This visual separator between the arrow and the rest of the button is an indication of two distinct zones for the SplitButton, where the Button only has one, which is why when we clicked on our button, the handler was called and the menu appeared. Clicking on what I like to call the “Button zone” in the SplitButton will call its registered handler. Likewise, clicking on what I like to call its “Arrow zone” will display the menu and call an arrowHandler if it was registered. As we said earlier, the 3.0 Button family is a lot more flexible in terms of layout and can scale really well relative to its 2.0 ancestor. Next, we’re going to discuss how to modify the look of the button itself and learn about some of the configuration options available to allow you to do just that. 10.2.4 Customizing the layout of your Button Thus far, we’ve only configured Buttons and SplitButtons for use without changing altering the layout. With this version of the framework, you can elect to display the (menu indication) arrow at the bottom of the button if you wish. You would do this by configuring the button via the arrowAlign property, which can have the value of “right” or “bottom”. Likewise, the icons can be aligned at any side of the button, but left is the default. Below is a quick demonstration of each of the arrowAlign(ment) and iconAlign options. Licensed to Alison Tyler Download at Boykma.Com 20 Jesus Garcia/Ext JS in Action Last saved: 7/10/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=XYZ Figure 10.12 Various implementations of the SplitButton with the possible icon and arrow alignments. In the illustration above, we used 16x16 icons, which fit perfectly in just about every portion of the framework where custom icons can be used. You can use larger icons if you wish. Also, even though we see the SplitButton being used in Figure 10.12, remember that Buttons also can be configured in the same way. The height the Buttons and SplitButtons can be conveniently configured via the scale configuration property. This also controls the height of the icon that can be displayed in the button. There are three possible values for this property, which are small (16px high and is the default), medium (24px high) and large (32px high). We’ll see this configuration property in action when we work through ButtonGroups in just a bit. A great demonstration of larger icons in Buttons can be found in the examples that are available in the SDK. They can be found by viewing the following page in your browser: /examples/button/buttons.html. You now have the necessary skills to employ Buttons and SplitButtons with any possible icon and arrow alignment. Now that we have those important skills, we can learn how to cluster buttons together in what’s known as a ButtonGroup. 10.3 Grouping your Buttons New to Ext 3.0 is what is known as the ButtonGroup widget, which is an extension of Panel and has one sole purpose, which is for the grouping similar or like functionality into clusters for the user. Ext JS ButtonGroups do a pretty good job at emulating the button groups found in Microsoft Word 2007 for Windows. Being an extension of Panel means that just about any layout can be employed to control the way the ButtonGroup looks. Please be aware that just because most layouts will work within the confines of the ButtonGroup, please heed to my advice and stay within the confines well-known UI design patterns. Remember, we like to keep the users happy. Here is a pop quiz. Ready? What class does Button extend? Right! The Button widget extends BoxComponent. Next question. Why is this important? Right again! This is important because BoxComponent is what contains the component size management methods that allow a Component to take part in a layout. You’re sharp! Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/10/2009 Jesus Garcia/Ext JS in Action 21 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=XYZ Next, we’ll build our first ButtonGroup using the default layout. If you’re following along and want to display the icons, please remember to include the icon CSS file. Listing 10.10 Grouping Buttons with a ButtonGroup new Ext.ButtonGroup({ // 1 renderTo : Ext.getBody(), title : 'Manage Emails', items : [ { text : 'Paste as', // 2 iconCls : 'icon-clipboard', menu : [ { text : 'Plain Text', iconCls : 'icon-paste_plain' }, { text : 'Word', iconCls : 'icon-paste_word' } ] }, { text : 'Copy', iconCls : 'icon-page_white_copy' }, { text : 'Cut', iconCls : 'icon-cut' }, { text : 'Clear', iconCls : 'icon-erase' } ] }); {1} Constructing a new ButtonGroup {2} Use a SplitButton instead of Button In listing 10.10, we construct a new instance of ButtonGroup{1} with four buttons, one of which has an attached Menu{2}. Like the Buttons that we created earlier, we render the ButtonGroup to the document body. That’s all there is to it. Here is what our ButtonGroup looks like rendered on screen. Figure 10.13 Our first ButtonGroup in action. Licensed to Alison Tyler Download at Boykma.Com 22 Jesus Garcia/Ext JS in Action Last saved: 7/10/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=XYZ In Figure 10.13 we see our first ButtonGroup in action, which contains three buttons, the first of which is a SplitButton. By default, the ButtonGroup uses the TableLayout, which is why the buttons are arranged in a single row. Next, we’ll use our TableLayout ninja skills to rearrange these buttons so they look a little more like the edit button group in Word 2007 for Windows. If you’re unfamiliar with the toolbars of MS Word 2007, below is a snapshot of what we’re trying to achieve. Figure 10.14 The Clipboard button group as found in MS Word 2007 for Windows. Now that we know what we’re going to replicate, let’s get down to business. Listing 10.11 Visually reorganizing our ButtonGroup new Ext.ButtonGroup({ // 1 renderTo : Ext.getBody(), title : 'Clipboard', columns : 2, // 2 items : [ { text : 'Paste', iconCls : 'icon-clipboard_24x24', rowspan : '3', // 3 scale : 'large', // 4 arrowAlign : 'bottom', iconAlign : 'top', width : 50, menu : [ { text : 'Plain Text', iconCls : 'icon-paste_plain' }, { text : 'Word', iconCls : 'icon-paste_word' } ] }, { iconCls : 'icon-cut' }, { iconCls : 'icon-page_white_copy' }, { iconCls : 'icon-paintbrush' } ] }); Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/10/2009 Jesus Garcia/Ext JS in Action 23 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=XYZ {1} Recreating the ButtonGroup {2} Specifing 2 columns, which is a TableLayout config {3} The paste button spans tree rows in the table {4} Scale Button and icon as large In the above listing, we use all of our acquired knowledge thus far to leverage the default TableLayout that the ButtonGroup{1} uses to create an MS Word 2007-like button group. Here’s how it works. When recreating the ButtonGroup, we instruct the TableLayout to only render two columns{2}. This ensures that the Buttons within this group are going to be properly aligned. We also modified how we created the first button in the set. We set a TableLayout only child configuration property, rowspan{3}, to 3, which ensures that this button spans the first three rows of the table. We also set the scale{4} to large, which ensures the modified famfamfam clipboard icon that I stretched to fit the 24x24 form factor so that it fits in the large Button nicely. Also notice that we set the arrowAlign to bottom, which helps us complete the look. The rest of the three buttons have their text property removed, which means that their icon is the only visual representation of what the button does. Let’s see what our changes render and compare it to what the MS Word 2007 button group looks like. Figure 10.15 Our modified ButtonGroup (left) versus the Microsoft Word 2007 Button group (right). Our latest’s implementation of the Ext ButtonGroup is similar, but obviously it’s not a 100% match. It’s close though. And that’s what is important to our users - right? The shallower the learning curve for the applications that we build for them the happier they’ll be. The point of this exercise wasn’t to exactly duplicate the Word 2007 button group, but rather show that you can, if you choose to, configure the Ext ButtonGroup to organize the buttons it contains however your application requires. As we just learned, ButtonGroups are a good tool to leverage to group similarly functioning buttons. The learning curve to using the ButtonGroup is relatively shallow because we already know how to use the various layouts, etc. Now that we know how to use Menus, Buttons and ButtonGroups, we can shift gears and learn how to apply them to Toolbars, where they are most commonly used. Licensed to Alison Tyler Download at Boykma.Com 24 Jesus Garcia/Ext JS in Action Last saved: 7/10/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=XYZ 10.4 Toolbars Toolbars are generally an area where you can layout a cluster of items for the user to interact with. In Ext 3.0, Toolbars can easily manage just about anything you want to put in there. This is because of the recent enhancements to the Toolbar class and the introduction of the ToolbarLayout. Toolbars, just like any Component, can be renderedTo any element in the DOM, but are most commonly used in Panels and any descendant thereof. This is where we’ll keep our focus for this Widget. In the next example, we’re going to create a Window that contains a Toolbar with quite a few items including a ComboBox as an exercise to show how easy it is to add Components other than Buttons to the Toolbar. The following Listing is going to be lengthy because of the amount of child items that we’re going to create for the Toolbar. Listing 10.12 Building a Toolbar within a Window var tbar = { // 1 items : [ { text : 'Add', iconCls : 'icon-add' }, '-', // 2 { text : 'Update', iconCls : 'icon-update' }, '-', { text : 'Delete', iconCls : 'icon-delete' }, '->', // 3 'Select one of these: ', // 4 { xtype : 'combo', // 5 width : 100, store : [ 'Toolbars', 'Are', 'Awesome' ] } ] }; new Ext.Window({ width : 500, height : 200, tbar : tbar // 6 }).show(); {1} Creating the Toolbar configuration object {2} Shorthand for Toolbar separator {3} Shorthand for Toolbar greedy spacer {4} Shorthand for a Toolbar TextItem {5} A configuration object for a ComboBox Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/10/2009 Jesus Garcia/Ext JS in Action 25 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=XYZ {6} Adding the toolbar to the Window In Listing 10.12, we create a Toolbar configuration object{1}. This object has a single property, items, which is a list of configuration objects and some strings. Notice that we don’t have to specify an xtype for the toolbar configuration object. This is because when we associate it to the Window via the tbar or bbar properties, Ext will automatically assume that the configuration object is for a Toolbar for us. The items array contains quite a few, well, items that we should discuss. The first, we’ve seen, which is a typical Button configuration with a text property and iconCls. The reason you don’t see an xtype property for buttons is because the defaultType for toolbars is button. After the first Button is defined, we see the shorthand for the Toolbar.Separator{2} class, which provides a vertical line dividing items in the Toolbar. While we could write out the configuration of the Separator, I typically find myself just using the shorthand. If you wanted to have granular control over the separator, you would need to write out the complete configuration object for it, including the xtype property, which is “tbseparator”. Moving on, we create two more buttons with another separator between them. After that, you’ll find this funny looking string, ‘->’, which is the shorthand for the Toolbar. Fill{3} widget. This little widget will instruct the Toolbar widget to place any item(s) defined thereafter in the right side of the toolbar. This effectively pushes the items over the right of the Toolbar. NOTE Only one Fill item can be placed in the Toolbar. Think of it as an absolute divider, where between the left side of the Toolbar and the right. Once you place that divider, any items following after it will be placed to the right. Any other Fill definitions after the first will be ignored. Next, we use the shorthand for the Toolbar.TextItem{4} widget. Just like the other shorthand strings, the TextItem can be configured via configuration object, which will give you greater control over it, which includes styling. I honestly only configure a TextItem via object if I need this control. Else, I just use the string shorthand. After the TextItem configuration, we setup a quick and dirty ComboBox{5} configuration object. We do this to demonstrate how easy it is to embed items other than Buttons in the Toolbar. Lastly, we render a new instance of Window and affix our Toolbar configuration object to the top Toolbar position by referencing via the tbar{6} property. Let’s render it on screen and take a look at what it looks like. Licensed to Alison Tyler Download at Boykma.Com 26 Jesus Garcia/Ext JS in Action Last saved: 7/10/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=XYZ Figure 10.16 Our Toolbar rendered inside of a Window. When we look at our rendered Toolbar, we see that the three Buttons appear with Separators between them, all of which are left aligned. Right aligned are the TextItem and ComboBox. The placement of Buttons in a Toolbar is relatively common practice, but it can quickly get overcrowded with items. Using ButtonGroups helps alleviate this situation. Adding ButtonGroups to a Toolbar is as simple as referencing it as a child item of the Toolbar. The following illustration is a great example of ButtonGroups being used in the Ext Surf chat application. Figure 10.17 ButtonGroups used in Toolbar as seen in the Ext Surf live chat application. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/10/2009 Jesus Garcia/Ext JS in Action 27 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=XYZ As we’ve learned, using Toolbars is relatively straightforward and we can add all types of stuff in the Toolbar to give our users the ability to effectively interact with our applications. THE DANGERS OF USING A DIFFERENT LAYOUT Remember that the Toolbar uses the ToolbarLayout. This layout is what gives the Toolbar a lot of its power, which includes responsiveness to the Toolbar.Fill widget. We need to exercise some caution when thinking about using another layout with a Toolbar. While you technically can leverage another layout, you will lose features like the Toolbar Fill and automatic overflow protection, which are part of what make the Toolbar what it is. OK. This wraps up our discussion with Toolbars. We can now focus on the last topic, Ext.Action, which ties together everything we’ve learned and exercised thus far. 10.4 Read, set, Ext.Action! Often as developers, we’re tasked to provide a means for the same functionality to be accessed by different methods. For instance, we may be asked to create an Edit menu that contains the typical cut, copy and paste menu items along with adding them directly to a toolbar. With that request, you have two options. Either create instances of Button and menu Item each with similar configurations or abstract them to an Ext.Action. I vote the latter, as it’s much easier. Let’s build out a couple of actions and we’ll go into further detail about how this all works. Listing 10.13 Using Ext.Actions var genericHandler = function(menuItem) { // 1 Ext.MessageBox.alert('', 'Your choice is ' + menuItem.text); } var copyAction = new Ext.Action({ // 2 text : 'Copy', iconCls : 'icon-page_white_copy', handler : genericHandler }); var cutAction = new Ext.Action({ text : 'Cut', iconCls : 'icon-cut', handler : genericHandler }); var pasteAction = new Ext.Action({ text : 'Paste', iconCls : 'icon-paste_plain', handler : genericHandler }); Licensed to Alison Tyler Download at Boykma.Com 28 Jesus Garcia/Ext JS in Action Last saved: 7/10/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=XYZ var editMenuBtn = { // 3 text : 'Edit', menu : [ cutAction, copyAction, pasteAction ] } new Ext.Window({ width : 300, height : 200, tbar : [ editMenuBtn, '->', cutAction, // 4 copyAction, pasteAction ] }).show(); {1} Creating a generic handler for the actions {2} The creation of our first Ext.Action {3} Adding the actions as menu Items to a Menu {4} Adding the actions as buttons to a Toolbar In Listing 10.13 we do quite a lot to leverage Ext.Action to build out the hypothetically required UI. The first thing we do is recreate the genericHandler{1} that we used earlier in this chapter. Next, we create three Actions{2} for the copy, cut and paste functionality that we desire. Notice that the properties set on each action are syntactically the same to those of the Button and menu Items we’ve created in the past. This is part of the key to understanding what Actions are and how they work, which is why we’re going to digress just a little. Actions are essentially a class that do nothing more than encapsulate the common configuration of a Button or MenuItem. That’s it. When you create an instance of a Ext.Action and inspect it in Firebug, you’ll see a bunch of methods and properties. When we (or Ext JS) go to instantiate an instance of Button or MenuItem using Action, the properties and methods of the Action that we’re using are applied to the Button or MenuItem during the constructor call of either class. In other words Ext.Action is a configuration wrapper for either the Button or MenuItem, where many instances of each of those classes can share the Action configuration. Now what exactly Actions are, understanding the rest of this listing will be a breeze. After we create the Actions, we create a Button{3} configuration object and set a menu property, which is an array of each of our actions. Next, we create and show an instance of Ext.Window, where we specify a tbar property, which is a list containing the Button with a menu, a Separator and the three actions. This is where we can see the usability of Actions. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/10/2009 Jesus Garcia/Ext JS in Action 29 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=XYZ Without rendering this on screen, we can predict what code will construct. Let’s go ahead and display the page and see if we’re right. Figure 10.18 The results of Listing 10.13, where Actions are being used to configure Buttons and menu Items. Ah-ha! The rendered UI from Listing 10.13 rendered just like we expected. We see three menu Items appear under the Edit Button when clicked and three Buttons that are right aligned in the Toolbar. Unfortunately, Actions is one of those classes that is commonly overlooked by developers. I have to admit that I’m guilty of this myself. When developing Buttons and Menus for your UI, try to remember that if the functionality is the same for two or more Buttons or menu Items, use Actions. Trust me, it will save you time in the long run. 10.5 Summary In this chapter we covered quite a lot of topics, which revolve around Menus, Buttons, Toolbars and Actions. We started by learning the ins and outs of menus and all of the menu items that can be used. In doing so, we digressed a little to learn more about where to get free 16x16 icons that lots of web 2.0 applications are using. We also learned how to nest menus and even affix Color and Date menus to menu Items. We also took time to learn about the Ext Button and SplitButton, where we learned how to configure how they look and feel by setting the iconAlign and arrowAlign properties. Next, we learned how to leverage the ButtonGroup to cluster similar buttons together and took the time to leverage the TableLayout to organize our buttons in a special way. Remember to set the scale property to medium or large if you’re using an icon that is larger than 16x16. Lastly, we began to tie together what we learned about menus and Buttons together when we discussed the Toolbar. We also took some time to learn about time savings with the Ext.Action configuration wrapper class. Licensed to Alison Tyler Download at Boykma.Com 30 Jesus Garcia/Ext JS in Action Last saved: 7/10/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=XYZ Next, we’re going to tackle the challenge of using Drag and Drop with popular widgets such as the Grid and TreePanel. Download at Boykma.Com
还剩254页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 1 人已下载

下载pdf

pdf贡献者

sdxrh2005

贡献于2012-12-11

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf