1,024 19 4MB
English Pages 324 Year 2008
Learning Ext JS
Build dynamic, desktop-style user interfaces for your data-driven web applications
Shea Frederick Colin Ramsay Steve 'Cutter' Blades
BIRMINGHAM - MUMBAI
Learning Ext JS Copyright © 2008 Packt Publishing
All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews. Every effort has been made in the preparation of this book to ensure the accuracy of the information presented. However, the information contained in this book is sold without warranty, either express or implied. Neither the authors, Packt Publishing, nor its dealers or distributors will be held liable for any damages caused or alleged to be caused directly or indirectly by this book. Packt Publishing has endeavored to provide trademark information about all the companies and products mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy of this information.
First published: November 2008
Production Reference: 1201108
Published by Packt Publishing Ltd. 32 Lincoln Road Olton Birmingham, B27 6PA, UK. ISBN 978-1-847195-14-2 -14-2 14-2 www.packtpub.com
Cover Image by Michelle O'Kane ([email protected])
Credits Authors Shea Frederick
Project Manager Abhijeet Deobhakta
Colin Ramsay Steve 'Cutter' Blades
Project Coordinator Neelkanth Mehta
Reviewer James Kennard
Indexer Monica Ajmera
Senior Acquisition Editor David Barnes
Proofreader Dirk Manuel
Development Editor Swapna V. Verlekar
Production Coordinator Rajni R. Thorat
Technical Editor Gagandeep Singh
Cover Work Rajni R. Thorat
Copy Editor Sumathi Sridhar
Editorial Team Leader Akshara Aware
About the Authors Shea Frederick began his career in web development before the term 'Web Application' became commonplace. By the late 1990s, he was developing web applications for Tower Records that combined a call center interface with inventory and fulfillment. Since then, Shea has worked as a developer for several companies, building and implementing various commerce solutions, content management systems, and lead tracking programs. Integrating new technologies to make a better application has been a driving point for Shea's work. He strives to use open-source libraries, as they are often the launching pad for the most creative technological advances. After stumbling upon a young user interface library called yui-ext several years ago, Shea contributed to its growth by writing documentation, tutorials, and example code. He has remained an active community member for the modern yui-ext library—Ext JS. Shea's expertise is drawn from community forum participation, work with the core development team, and his own experience as the architect of several large Ext JS-based web applications. He currently lives in Baltimore, Maryland, with his wife and two dogs, and spends time skiing, biking, and watching the Steelers. A big loving thanks goes out to my wife Becky for looking over my shoulder to correct the many grammatical errors my fingers produce, and for always being there to support me.
Colin Ramsay began his career building ASP websites as a part-time developer at university. Since then, he's been involved with a range of web technologies and employers in the North East of England, working on everything from flashin-the-pan web frameworks to legacy applications. Most recently, he has used this experience to provide a springboard for the formation of his UK-based web development company, Plastiscenic Limited. From writing articles and blog posts across the web, Colin has made the leap to book authoring with the patience and kind assistance of his friends and family.
Steve Blades (who goes by the name of 'Cutter'), a Virginia native, raised in Georgia, began his computing career when he started learning BASIC at age 12, hammering out small programs on a Timex Sinclair 1000. As a linguist and Intelligence Analyst for the US Army, Cutter began learning HTML while stationed at the National Security Agency. On leaving the service, Cutter became part-owner of a growing Advertising Specialty company, developing business automation processes for the company by writing MS Office-based applications. From there, Cutter went on to become a Customer Support Technician with a local Internet Service Provider. Upon showing programming aptitude, he was later moved into their Corporate Support department, providing maintenance and rewrites to existing websites and applications. It was here that Cutter began to really dive into web application programming, teaching himself JavaScript, CSS, and ColdFusion programming. Cutter then took the position of IT Director for Seacrets, a large resort destination in Ocean City, Maryland, while also holding the same position for one of its owner's other companies, Irie Radio. Now, Cutter is the Senior Web Developer for Dealerskins, a company that develops and hosts websites for the automobile dealership industry. He lives and works in Nashville, Tennessee with his wife Teresa and daughter Savannah. Apart from work, side projects, and maintaining his blog (http://blog. cutterscrossing.com), Cutter also enjoys spending time with his family, is an avid reader and a videophile, and likes to relive his band days with a mic in hand. I would like to thank a few people for their support while I have been working on this project. First, thanks to Jack Slocum and the entire Ext JS team for giving us such a great library to write about. Thanks to the Dev Team at Dealerskins for helping proof my chapters. Thanks to my Mom, for buying me my first book on programming. But, most of all, thanks to my wife, Teresa, and my daughter, Savannah, for giving me the time, space, love, and support needed to work on this project. I could never have done it without them.
About the Reviewer James Kennard is an all-round computer specialist with a particular interest in web-based technologies. He authored the Joomla! CMS book Mastering Joomla! 1.5 Extension and Framework Development. He holds a B.Sc. in Computer Science and has worked for organisations such as LogicaCMG. James has recently taken an interest in user interfaces and overall UX—it is this which led him to the truly superb Ext JS project.
Dedicated to our family, friends, and the Ext JS team.
Table of Content Preface Chapter 1: Getting Started About Ext Ext: Not just another JavaScript library Cross-browser DOM (Document Object Model) Event-driven interfaces Ext and AJAX Getting Ext Where to put Ext Including Ext in your pages What do those files do? Using the Ext library Time for action The example Not working? Adapters Using adapters I'm asynchronous! Localization English only A language other than English Multiple languages Ext JS online community Summary
1 9 9 11 12 12 12 13 13 14 15 15 16 17 17 18 18 19 20 20 21 21 22 22
Table of Contents
Chapter 2: The Staples of Ext
23
Ready, set, go! Spacer image Widget Time for action What just happened? Using onReady More widget wonders Meet JSON and the config object The old way The new way—config objects
23 24 24 24 25 25 26 28 28 28
What is a config object?
29
How does JSON work? Time for action Lighting the fire The workhorse—Ext.get Speed tip Summary
30 30 32 33 34 35
Chapter 3: Forms
37
The core components of a form Our first form Nice form—how does it work? Form fields Validation Built-in validation—vtypes Styles for displaying errors Custom validation—create your own vtype Masking—don't press that key! Radio buttons and check boxes It's not a button, it's a radio button X marks the check box The ComboBox Database-driven ComboBox TextArea and HTMLEditor Listening for form field events ComboBox events Buttons and form action Form submission Talking back—the server responses
[ ii ]
37 38 39 39 40 41 43 44 45 46 46 46 47 47 50 51 51 53 53 54
Table of Contents
Loading a form with data Static data load Object reference or component config Instantiated Component config Summary
Chapter 4: Buttons, Menus, and Toolbars A toolbar for every occasion Toolbars The button Menu Split button Toolbar item alignment, dividers, and spacers Shortcuts Icon buttons Button handlers—click me! Load content on menu item click Form fields in a toolbar Toolbars in windows, grids, and panels Summary
Chapter 5: Displaying Data with Grids What is a grid anyway? Displaying structured data with a GridPanel Setting up a data store Adding data to our data store Defining your data for the data store Specifying data types
56 56 58 58 58 59
61 61 61 63 63 64 65 66 66 67 68 69 70 71
73 74 74 75 75 76 77
Displaying the GridPanel How did that work? Configuring the GridPanel Defining a Grids column model Using cell renderers Formatting data using the built-in cell renderers Creating lookup data stores—custom cell rendering Combining two columns Generating HTML and graphics Built-in features Client-side sorting Hidden/visible columns Column reordering [ iii ]
78 80 80 81 82 82 83 84 84 85 86 86 86
Table of Contents
Displaying server-side data in the grid Loading the movie database from an XML file Loading the movie database from a JSON file Loading data from a database using PHP Programming the grid Working with cell and row selections Listening to our selection model for selections Manipulating the grid (and its data) with code Altering the grid at the click of a button Advanced grid formatting Paging the grid Grouping Grouping store Summary
Chapter 6: Editor Grids
88 88 90 91 92 92 93 94 94 95 96 98 98 100
101
What can I do with an editable grid? Working with editable grids Editing more cells of data Edit more field types
101 102 104 104
Editing a date value Edit with a ComboBox
105 106
Reacting to a cell edit
106
What's a dirty cell? Reacting when an edit occurs
107 107
Deleting and adding in the data store Removing grid rows from the data store Adding a row to the grid
108 109 110
Saving edited data to the server Sending updates back to the server Deleting data from the server Saving new rows to the server Summary
Chapter 7: Layouts
112 112 114 115 117
119
What are layouts, regions, and viewports? Our first layout Splitting the regions I want options Tab panels Adding a tab panel
[ iv ]
119 121 122 123 124 124
Table of Contents
Widgets everywhere Adding a grid into the tabpanel Accordions Nesting an accordion layout in a tab Placing a toolbar in your layout A form to add new movies Tricks and advanced layouts Nested layouts Icons in tabs Programmatically manipulating a layout Now you see me, now you don't Give me another tab Summary
126 126 128 128 129 131 132 132 134 135 135 136 136
Chapter 8: Ext JS Does Grow on Trees
137
Planting for the future From tiny seeds... Our first sapling Preparing the ground A tree can't grow without data JSON
137 138 138 139 140 141
A quick word about ID Extra data
141 142
XML Tending your trees Drag and drop Sorting Editing Trimming and pruning Selection models Round-up with context menus
142 143 143 145 146 147 147 148
Handling the menu
149
Filtering The roots
150 151
TreePanel tweaks Cosmetic Tweaking TreeNode
151 152 152
Manipulating
153
Further methods
154
Event capture
155
[v]
Table of Contents
Remembering state
156
StateManager Caveats
156 157
Summary
157
Chapter 9: Windows and Dialogs
159
Opening a dialog Dialogs Off the shelf
159 160 160
Confirmation It's all progressing nicely
162 163
Roll your own
164
Behavior
165
Windows Starting examples Paneling potential
166 166 167
Layout
168
Configuration
169
When I'm cleaning windows The extras Desktopping Further options Framing our window
169 169 170 171 171
Manipulating Events State handling Window management Default window manager behavior Multiple window example
172 173 174 175 175 175
Customer service WindowGroups
179
Summary
180
Chapter 10: Effects
183
It's elementary Fancy features It's ok to love Fxcellent functions Methodical madness
183 184 184 184 184
Fading Framing Woooo: ghosting Highlighting Huffing and puffing Scaling the Ext JS heights
185 186 186 187 188 189
[ vi ]
Table of Contents Sliding into action Switching from seen to unseen Shifting
189 190 190
And now, the interesting stuff The Fx is in Anchoring yourself with Ext Options
191 191 192 192
Easy does it
194
Multiple effects Chaining Queuing
195 195 196
Concurrency Blocking and Ext.Fx utility methods
196 196
Elemental Making a move Using Ext components Bring out the flash You're maskin', I'm tellin'
197 197 198 198 198
Data binding and other tales Considering components
200 200
QuickTipping Summary
200 202
Chapter 11: Drag-and-Drop
205
Drop what you're doing Life's a drag Sourcing a solution
205 206 206
Approximating Snap!
206 207
Drop me off
207
But wait: Nothing's happening!
208
Interacting the fool Zones of control
209 209
Changing our lists
211
Registering an interest Extreme drag-and-drop DataView dragging Dealing with drag data Proxies and metadata Dropping in the details Drag-drop groups Nursing our drag-drop to health It's all in the details
211 212 212 213 214 215 216 216 217 [ vii ]
Table of Contents
Configuration It's all under control Managing our movement Global properties Scroll management Dragging within components TreePanel GridPanel Using it in the real world Summary
217 217 218 218 219 220 220 221 221 222
Chapter 12: It's All about the Data Understanding data formats Basic remote panel data Gotchas with HTML data
223 223 223 227
Other formats
227
The data store object Defining data
228 229
More on mapping our data Pulling data into the store
230 231
Using a DataReader to map data Using a custom DataReader Getting what you want: Finding data Finding data by field value Finding data by record index Finding data by record ID
233 234 237 237 237 238
Getting what you want: Filtering data
238
Remote filtering: The why and the how
238
Dealing with Recordset changes Many objects take a Store Store in a ComboBox Store in a DataView Stores in Grids Summary
244 246 246 247 247 248
Chapter 13: Code for Reuse: Extending Ext JS Object-oriented JavaScript Object-oriented programming with Ext JS Inheritance Break it down and make it simple Sounds cool, but what does it mean? Now, what was this overriding stuff?
249 249 251 251 251 253 253
[ viii ]
Table of Contents
Understanding packages, classes, and namespaces Packages Classes Namespaces What's next? Ok, what do we extend? Creating a custom namespace Our first custom class Overriding methods Understanding the order of events When can we do what? What is an event-driven application?
254 254 254 254 254 255 255 256 259 260 261 261
Creating our own custom events Our first custom component: Complete What's next? Breaking it down Using xtype: The benefits of lazy instantiation Using our custom components within other objects Summary
Chapter 14: The Power of Ext JS: What Else Can You Do? So much to work with Form widgets DateField TimeField NumberField CheckboxGroups and RadioGroups HtmlEditor Data formatting Basic string formatting Formatting dates Other formatting Managing application state Basic 'state' How do I get that window? Using the back button in Ext JS applications Accessing the DOM Finding DOM elements Manipulating the DOM Working with styles Ext JS for the desktop: Adobe AIR
[ ix ]
262 264 267 271 271 272
273 273 273 274 275 276 276 277 278 278 279 280 281 281 282 282 282 283 283 284 284
Table of Contents
Ext JS community extensions DatePickerPlus PowerWizard TinyMCE SwfUploadPanel ColorPicker Additional resources Samples and demos Ext JS API Ext JS forums Step-by-step tutorials Community manual Spket IDE Aptana Studio Google Summary Where do we go from here?
286 286 287 287 288 288 289 289 289 289 290 290 290 290 290 291 291
Index
293
[x]
Preface Ext JS was developed by a unified team of programmers working toward a single goal—to provide a consistent core user interface and interaction library. Because of this, the code used for different functionalities and widgets is more coherent than in some other JavaScript libraries. Ext JS really shines in making web applications easyto-use and intuitive. If you are a web application developer, it's a great library to have in your arsenal. We start by outlining how to download and configure the Ext JS library. Covering everything from the simplest alerts to complex grids, layouts, and forms, this book will enable you to start creating rich, interactive web applications. We will use plenty of real-world examples that can be applied immediately to your ongoing projects. Sample code is broken down to its simplest form, allowing us to concentrate on learning the usage of the library. By the end of this book, we will end up with a sample application that uses the full spectrum of Ext JS components.
What this book covers Chapter 1 introduces you to the process of installing the required Ext JS library files, and setting up a basic page that displays an alert-style message. This provides us with a way to test whether your setup was done correctly, and whether you're ready to play with some code. We also cover how to set up other base libraries such as jQuery, YUI, and Prototype, to work in conjunction with Ext JS. Chapter 2 covers how to interact with the web page and the user. With example code that uses simple components, we quickly start to see the level of user interactivity that Ext JS provides right out of the box. We assemble a series of dialogs that appear and modify the existing pages depending upon the users' inputs.
Preface
Chapter 3 launches us into using the first major widget—forms. We start by creating a simple form with three fields, explore the different form field types, and then add some simple validation to our form. From there we move on to creating custom validation and database-driven combo-box'es and handling form submissions. Chapter 4 provides an overview of how to use toolbars and buttons within your application. These components are typically undervalued, yet they provide crucial user interface functions. We jump straight into creating toolbars with buttons, split buttons, and menus, along with adding mechanical elements such as spacers and dividers. Next, we cover customizing the toolbar with stylized icon buttons and form fields. Chapter 5 covers grids—the most widely-utilized component in the Ext JS library. In this chapter, we learn how to set up a grid panel using both local and remote data, and in both in XML and JSON formats. We also discuss how to prepare different data types and how to create renderers that will style and format the data to your preference. Using the selection model and paging are among the many interesting points covered in this chapter. Chapter 6 dives into editor grids. Here, we learn how to set up an editor grid using different form field types, and how to save changes made in the grid back to the server or database. We also discuss tactics for adding and removing rows of data to and from our data store, and the server or the database. Chapter 7 explores the concept of using the layout component to bring all the portions of your application together into a cohesive web application. We start by using a viewport with a border layout to contain the many parts of our application. From there we are able to add other layout features such as tab panels, accordions, and toolbars. We finish up by learning how to nest layouts and make dynamic changes to the layout components. Chapter 8 discusses the presentation of hierarchical information using the Ext JS Tree support. Using real-world examples of hierarchical data, you will discover how to display and manipulate a Tree view. You will use AJAX techniques to persist the modifications to a server and learn how to tweak the Tree to support advanced scenarios. Chapter 9 demonstrates how Ext JS can provide attractive user prompts that can either present information or accept input. We then discuss the extension of these dialogs in the form of Ext.Window, a fully-fledged means of creating customizable pop-up windows. In Chapter 10, we take a tour of the visual effects available in the Ext JS effects package. You will learn how to apply animations to create smooth transitions and notifications to enhance the user experience. [2]
Preface..
Chapter 11 shows how you can harness Ext.dd—the rich drag-and-drop functionality provided by Ext JS. A variety of different demonstrations allow you to understand the concepts behind Ext.dd, and how you can harness its potential within your own applications. Chapter 12 gets straight to the heart of every application—the data. Ext JS provides several different methods for retrieving data, with each method having its own pros and cons. This chapter will help you to decide what will work for your application, with step-by-step examples to guide you on your way. Chapter 13 shows the true power of Ext JS, providing an introduction to creating your own custom components by expanding upon Ext JS's extensible architecture. You will see how to create your own components by extending the existing framework, making pieces that you can re-use in your own applications. Chapter 14 wraps it all up, by showing you that with Ext JS there is more than meets the eye. You will discover some of the invisible architecture that allows you to perform important tasks such as data formatting and application state management. You will also find that you have a broad array of resources at your fingertips, as we show you the rich user community that exists around the library, and introduce you to additional resources to continue your journey in Learning Ext JS.
What you need for this book At the ground level, this book requires the knowledge to write HTML pages by hand—if you can write an HTML document from memory in Windows Notepad (or in a good text editor) then that will be good enough. Familiarity with including external files such as style sheets and JavaScript files will also be necessary. Only a basic understanding of JavaScript, or another scripting or programming language, will be required. One of the things that will make life easier is having access to a web server, or a local development web server such as XAMPP or something similar. The XAMPP local web server is developed by Apache Friends (www.apachefriends.org) and comes in an easy–to-use install file. By default, it sets up Apache, PHP, and MySQL, which allows you to perform local web development easily. Running this book's examples from a local web server is useful and can save lots of time.
[3]
Preface
A good editor and debugger are extremely useful, particularly if they are specific to JavaScript, as Aptana is. The makers of Aptana have created a very powerful tool for developing web applications in JavaScript. Their editor can debug JavaScript, CSS, PHP, and many other languages as you type, and the best part is that you can link the editor up with your libraries and classes to get code auto-completion specific to your development. The debugger can alert you to errors in your code before you get to the browser (enable the JSLint debugger), and can suggest fixes for the errors. The final point here is an absolute necessity—get Firefox and Firebug installed on your computer! Don't even ask why, because Firebug will soon become the program you just cannot do your job without. Soon, you will be wondering how you ever got any work done before Firebug. What it does is allows you to monitor and interact with the web page in real time. When you start working with singlepage web applications and AJAX, you quickly lose the ability to look at the requests and responses for communications such as form submission. One of the things that Firebug provides you with is a way to watch and inspect this communication. The other main thing that it does is allow you to modify the HTML and JavaScript in your web page and watch these changes take effect in real time. The built-in script debugger lets us pause code execution and inspect or even modify code and variables. Once you are set up with a local (or remote) development web server, your favorite editor and debugger, and Firefox with Firebug, you are ready to start Learning Ext JS.
Who is this book for This book is written for Web Application Developers who are familiar with HTML, but may have little to no experience with JavaScript application development. If you are starting to build a new web application, or you are revamping an existing web application, then this book is for you.
Conventions In this book, you will find a number of styles of text that distinguish between different kinds of information. Here are some examples of these styles, and an explanation of their meaning. Code words in text are shown as follows: "The config object used for this dialog has three elements "
[4]
Preface..
A block of code is set as follows: Ext.onReady(function(){ Ext.Msg.show({ title: 'Milton', msg: 'Have you seen my stapler?', buttons: { yes: true, no: true, cancel: true } }); });
When we wish to draw your attention to a particular part of a code block, the relevant lines or items will be shown in bold: { xtype: 'datefield', fieldLabel: 'Released', name: 'released' }
New terms and important words are introduced in bold. Words that you see on the screen, in menus or dialog boxes for example, appear in the text like this: "We also have elements that can add space and vertical dividers, like the one used between the Menu and the Split buttons."
Warnings or important notes appear in a box like this.
Tips and tricks appear like this.
Reader feedback Feedback from our readers is always welcome. Let us know what you think about this book, what you liked or may have disliked. Reader feedback is important for us to develop titles that you really get the most out of. To send us general feedback, simply drop an email to [email protected], and mention the book title in the subject of your message. [5]
Preface
If there is a book that you need and would like to see us publish, please send us a note in the SUGGEST A TITLE form on www.packtpub.com or email suggest@ packtpub.com. If there is a topic that you have expertise in and you are interested in either writing or contributing to a book, see our author guide on www.packtpub.com/authors.
Customer support Now that you are the proud owner of a Packt book, we have a number of things to help you to get the most from your purchase.
Downloading the example code for the book Visit http://www.packtpub.com/files/code/5142_Code.zip to directly download the example code.
The downloadable files contain instructions on how to use them.
Errata Although we have taken every care to ensure the accuracy of our contents, mistakes do happen. If you find a mistake in one of our books—maybe a mistake in text or code—we would be grateful if you would report this to us. By doing this you can save other readers from frustration, and help to improve subsequent versions of this book. If you find any errata, report them by visiting http://www.packtpub. com/support, selecting your book, clicking on the let us know link, and entering the details of your errata. Once your errata are verified, your submission will be accepted and the errata added to any list of existing errata. Existing errata can be viewed by selecting your title from http://www.packtpub.com/support.
[6]
Preface..
Piracy Piracy of copyright material on the Internet is an ongoing problem across all media. At Packt, we take the protection of our copyright and licenses very seriously. If you come across any illegal copies of our works in any form on the Internet, please provide the location address or website name immediately so we can pursue a remedy. Please contact us at [email protected] with a link to the suspected pirated material. We appreciate your help in protecting our authors, and our ability to bring you valuable content.
Questions You can contact us at [email protected] if you are having a problem with any aspect of the book, and we will do our best to address it.
[7]
Getting Started In this chapter, we will cover the basics of Ext and what it can do for us. If you're accustom to the standard web development, then you'll be excited when you learn about the elegance in the architecture of Ext, just as I was. Unlike other JavaScript libraries, Ext handles the foundation for you, so with only a few lines of code, you can have a fully functional user interface. In this chapter, we will cover: •
What Ext does and why you'll love using it
•
How to get Ext and start using it in your web applications
•
Using "adapters" to allow Ext to co-exist with other JavaScript libraries
•
Taking advantage of AJAX technology
•
Displaying Ext objects in your own language
About Ext We will be working with the most recent release version of Ext which, at the time of writing, is the 2.x branch. The change from 1.x to 2.x was a major refactoring that included taking full advantage of the newly-created Component model, along with renaming many of the components to provide better organization. These changes have made 1.x code mostly incompatible with 2.x and vice versa (an upgrade guide that explains in more detail what has changed is available on the Ext web site). The 3.x branch is backwards-compatible with 2.x and compatible with everything that we will cover in this book. The Ext development team is dedicated to making future releases backwards-compatible.
Getting Started
The Ext library started out as an extension to the moderately popular, yet very powerful Yahoo User Interface library, providing what the YUI library lacked: an easy to use API (Application Programming Interface), and real world widgets. Even though the Yahoo User Interface tried to focus on the 'User Interface', it didn't contain much that was useful right out of the box. It wasn't long before Ext had developers and open-source contributors chipping in their knowledge to turn the basic YUI extension into one of the most powerful client-side application development libraries around. Ext provides an easy-to-use, rich user interface, much like you would find in a desktop application. This lets the web developers concentrate on the functionality of web applications instead of the technical caveats. The examples given on the Ext website speak the loudest about how amazing this library is: http://www.extjs.com/deploy/dev/examples/
One of the most striking examples is the Feed Viewer. This demonstrates the many aspects of Ext. However, it is a bit too complex to be used as a learning example. So for now, you can just revel in its brilliance.
[ 10 ]
Chapter 1
Another excellent example is the Simple Tasks task-tracking program, which utilizes a Google Gears database.
Over the course of this book, you will learn how to build web interfaces as impressive as these.
Ext: Not just another JavaScript library Ext is not just another JavaScript library—in fact, Ext can work alongside other JavaScript libraries by using adapters. We'll see how to work with adapters later in this chapter. Typically, we would use Ext in a web site that requires a high level of user interaction—something more complex than your typical web site. A web site that requires processes and a work flow would be a perfect example, or Ext could just be used to make your boss gasp with excitement. Ext makes web application development simple by: •
Providing easy-to-use cross-browser compatible widgets such as windows, grids, and forms. The widgets are already fine-tuned to handle the intricacies of each web browser on the market, without us needing to change a thing.
•
Interacting with the user and browser via the EventManager, responding to the users keystrokes, mouse clicks, and monitoring events in a browser such as a window resize, or font size changes. [ 11 ]
Getting Started
•
Communicating with the server in the background without the need to refresh the page. This allows you to request or post data to or from your web server using AJAX and process the feedback in real time.
Cross-browser DOM (Document Object Model) I am sure I don't need to explain the pitfalls of browser compatibility. From the first time you create a DIV tag and apply a style to it, it becomes apparent that it's not going to look the same in every browser unless you are very diligent. When we use Ext widgets, the browser compatibility is taken care of by the Ext library, so that each widget looks exactly the same in most of the popular browsers, which are: •
Internet Explorer 6+
•
Firefox 1.5+ (PC, Mac)
•
Safari 2+
•
Opera 9+ (PC, Mac)
Event-driven interfaces Events describe when certain actions happen. An event could be a user action such as a click on an element, or it could be a response to an AJAX call. When a user interacts with a button, there is a reaction, with not just one but many events happening. There is an event for the cursor hovering over the button, and an event for the cursor clicking on the button, and an event for the cursor leaving the button. We can add an event listener to execute some code block when any or all of these events take place. Listening for events is not strictly related to the user interface. There are also system events happening all the time. When we make AJAX calls, there are events attached to the status of that AJAX call to listen for the start, the completion, and the failure.
Ext and AJAX The term AJAX (Asynchronous JavaScript and XML) is an overly-complicated acronym for saying that processes can take place in the background while the user is performing other tasks. A user could be filling out a form while a grid of data is loading—both can happen at the same time, with no waiting around for the page to reload.
[ 12 ]
Chapter 1
Getting Ext Everything we will need can be downloaded from the Ext website, at http://www.extjs.com/download. Grab the Ext SDK (Software Development Kit), which contains a ton of useful examples and the API reference. Most importantly, it contains the resources that Ext needs to run properly.
Where to put Ext Once you get the SDK file, uncompress it onto your hard drive, preferably in its own folder. My approach to folder naming conventions is based on the standard Linux structure where all libraries go into a lib folder. So for the sake of the examples in this book, uncompress all of the files in the SDK into a folder named lib. After extracting everything from the SDK download file, your directory tree should look like this:
To make it easier when we upgrade our Ext library to the most recently-released version, let us rename the ext-2.0.1 folder to extjs. The SDK contains a version of Ext JS that has everything you need included in it, commonly called ext-all. It also contains a version used for development referred to as the debug version, which is what we will primarily use. The debug version makes it easier to locate errors in your code because it's uncompressed and will report back relevant line numbers for errors. When it's time to release our creation to the general public, we can switch our application to use the standard ext-all, and everything will continue to work as it was.
[ 13 ]
Getting Started
Included in the SDK file are a specification of dependencies, documentation, example code, and more. The adapter and resources folders shown in bold are required for Ext to work properly; everything else is just for development purposes. •
adapter: Files that allow you to use other libraries along side Ext
•
build: Files that can be used to custom-build an ext-all.js
•
docs: The documentation center (this will only work when run on
a web server) •
examples: Plenty of amazing and insightful examples
•
resources: Dependencies of the Ext library, such as CSS and images
•
source: The complete source code for Ext
When you're ready to host your page on a web server, the adapter and resources folders will need to be uploaded to the server.
Including Ext in your pages Before we can use Ext in our pages, we need to reference the Ext library files. To do this, we need to include a few of the files provided in the SDK download in the HEAD portion of our HTML page.
Getting Started Example
We're not going to cover exactly what our example script is doing yet. First, let's make sure that the Ext library is set up properly. If we open up our page in a web browser, we should be able to see an alert message like the one shown here:
Just like a "real" dialog, you can drag it around, but only within the constraints of the page. This is because this isn't a real dialog; it's a collection of DIV tags and images put together to imitate a dialog. You can also see that the Close and Ok buttons get highlighted when you move the cursor over them—not bad for one line of code! Ext is taking care of a lot of the work for us here, and throughout this book, we'll see how to get it to do much more for us. [ 16 ]
Chapter 1
You may have noticed that we are working with an empty document that has no elements in the body. Ext does not require any pre-existing markup for it to function properly; it generates everything it needs on its own.
The example Let's take a look at that example code we just ran. Every Ext component we use will start with "Ext" Ext" and will most likely be contained within an onReady function that we will cover with more detail in the next chapter. Ext.onReady(function(){ Ext.Msg.alert('Hello', 'World'); });
Ext has a very human-readable interface. You can almost read it as a sentence—when Ext is ready, it displays a message box in the alert style with Hello for a title and World as the body. Our alert message starts with Ext.Msg, which is the starting point for all message style windows, and is shorthand for "MessageBox". The alert portion tells Ext exactly which style of message window to use.
Not working? If the library is not set up correctly, we might receive an 'Ext' is undefined error.
This message means the Ext library was not loaded. Usually, this is caused by having an incorrect path to one or more of the Ext library files that are included in our document. Double-check the paths to the included library files, and make sure they are pointing to the right folders and that the files exist. If everything is in its correct place, you should see an adapter folder along with the files ext-all.js and ext-all-debug.js in your lib/extjs folder. [ 17 ]
Getting Started
Another common problem is that the CSS file is either missing or is not referenced correctly, which will result in a page that looks awkward, as shown in the example below:.
If this happens, check to make sure that you have extracted the resources folder from the SDK file, and that your paths are correct. The resources folder should reside under the lib/extjs folder.
Adapters When Ext was first being developed (initially called "yui-ext"), it required the YUI library to be in place to do the behind-the-scenes work. Later on, Ext was given the option of using two other frameworks—jQuery or Prototype with Scriptaculous (Protaculous). This means that if we were using other libraries already or if we felt some other base library was somehow superior or better suited your needs, we could continue using that library in conjunction with Ext by using the appropriate adapter. Either way, Ext will function the same, and all of the components will work identically, no matter which adapter you choose. Ext also has its own adapter, an adapter to itself. If you have no preference for another library or framework, then go with the Ext built-in the adapter.
Using adapters To use an adapter, you must first include the external library that you want to use, and then include the related adapter file that is located in the adapters folder of the Ext SDK. Our example code uses the Ext adapter. To use any of the other libraries, just replace the default Ext adapter script include line with the lines for the specific libraries, as shown below: [ 18 ]
Chapter 1
Default Ext adapter:
For jQuery, include these files in the head of your document:
For YUI, include these files in the head. The utilities file is located in the build/ utilities folder of the YUI Library download:
For "Prototype + Scriptaculous", include these files in the head:
After the adapter and base libraries have been included, we just need to include the ext-all.js or ext-all-debug.js file.
I'm asynchronous! The Web 1.0 way of doing things has all of our code happening in succession—waiting for each line of code to complete before moving on to the next. Much like building a house, the foundation must be complete before the walls can be built, then the walls must be complete before the roof is built. With Ext, we can easily start working on the roof of our house before the foundation has even been thought about. Imagine the roof of our house is being built in a factory, while at the same time we are building the foundation, then the walls, and we come in when all of this is done and set the roof that has already been built on top of it all. Web 1.0
>
=
>
[ 19 ]
=
Getting Started
This introduces some things we're not use to having to cope with, such as the roof being complete before the walls are done. No longer are we forced to take a line-by-line approach to web development. Ext helps us out by giving us events and handlers to which we can attach our functionality. We can set up an event that waits around, watching to see when the walls of the house are built, and then sets the roof on top once this has happened. This method of thinking about web pages is hard for most people who have grown up in web development. But it won't be long before you are an expert at it. Standard JavaScript alert messages pause the code execution, which can cause unexpected results. You should not be using the built in JavaScript alert messages, and instead use Ext's MessageBox widget, which does not pause that code execution.
Localization Ext objects can be displayed in your specific language, and currently there are over 40 translations (unfortunately, Klingon is not yet available).. All of these translations are created by the community—users like you and I who have the need to use Ext in their own native language. The included language files are to be used as a starting point. So let's take the language we want to use and copy it to our lib folder. By copying the language file to our lib folder, we can edit it and add translated text to it without it getting overwritten when we upgrade the Ext library files. There are three scenarios for localization that require three separate approaches: •
English only
•
A single language other than English
•
Multiple languages
English only This requires no modifications to the standard setup, and there are no extra files to include because the English translation is already included in the ext-all.js file.
[ 20 ]
Chapter 1
A language other than English The second option requires that we include one of the language files from the build/locale folder. This option works by overwriting the English text strings, so it should be included after all of the other library files, as shown below:
I have included the Spanish translations for this example. Let's see what our test page looks like now:
Elements that are part of the UI have been localized—these generally include calendar text, error messages, tool tip info messages, paging info, and loading indicators. Messages that are specific to your application, such as the Hi title, and Hello World Example text will need to be translated and added to the ext-lang-XX.js file (where 'XX' is your two letter language code) or added to a new language file of your own. The preferred method is to create a language file of our own with just the additions and changes we need, this leaves us prepared for upgrades and fixes in the primary language file.
Multiple languages The third method of switching between different languages is basically the same as the second. We would just need to add some server-side scripting to our page to enable the switching between language files. Unfortunately, switching between languages cannot be done entirely dynamically. In other words, we can't do it in real time and watch it happen on the screen.
[ 21 ]
Getting Started
Ext JS online community The online community for Ext is full of quite a few very knowledgeable people, and often, the Ext core developers are answering questions on the forum. http://www.extjs.com/forum/
If you run into problems, or run up against a wall, a search of the forum is likely to yield what you are looking for. I would suggest getting the Google forum search tool that is available in the Learn section of the Ext web site. http://www.extjs.com/learn/ When asking questions in the forum, be sure to include as much detail about the error(s) as possible. Posting the exact text of an error message and only the relevant portions of your code is the best way to get a response from the community.
Summary In this chapter, we have covered the basics of what you need to do to get Ext up and running, and what a simple script looks like. It's easy to miss a minor detail and get stuck with an error message that makes no sense. But now, you should be prepared to conquer any initial errors that you might come across. The example we created showcases what Ext excels at: providing the user interface. We only used dialogs, but, as you now know, a few lines of code are all that are needed to display an Ext widget. The main goal of this chapter was to get Ext installed and working, so we can start creating some really sweet widgets.
[ 22 ]
The Staples of Ext In this chapter, we will start to use and interact with Ext widgets for the first time, by creating a series of dialogs that interact with each other, the user, and the web page. We will be using the onReady, MessageBox, and get functions to learn how to create different types of dialogs and modify HTML and styles on our page. Furthermore, in this chapter, we will be: •
Finding out how to configure Ext widgets easily
•
Waiting for the DOM (Document Object Model) to be made available for interaction
•
Using dialogs to figure out what the user wants to do
•
Dynamically changing the HTML and CSS on our page in response to the user's inputs
We will start by covering some of the core functions of Ext. We will take a look at how the example given in the first chapter worked, and will expand upon it. The following core functions of Ext will be used on every project that we work on during the course of this book: •
Ext.onReady: This function makes sure that our document is ready to be
thrashed out •
Ext.Msg: This function creates application-style message boxes for us
•
configuration objects: This function defines how Ext widgets will act
•
Ext.get: This function accesses and manipulates elements in the DOM
Ready, set, go! In this section, we'll look at the onReady event—the first thing that you need to deal with when you are working with Ext. We will also see how to display some different types of dialogs, and how to respond to the users' interaction with those dialogs. Before we get to that, we need to cover some ground rules about working with Ext.
The Staples of Ext
Spacer image Before we proceed any further, we should provide Ext with something it needs—a spacer image. Ext needs a 1 pixel by 1 pixel, transparent, GIF image to stretch in different ways, giving a fixed width to its widgets. We need to set the location of this spacer image using the following line: Ext.onReady(function(){ Ext.BLANK_IMAGE_URL = 'images/s.gif'; });
You're probably wondering why we need a spacer image at all. The user interface of Ext is created using CSS, but the CSS needs underlying HTML elements to style so that it can create the look and feel of Ext components. The one HTML element that is an exact, predictable size across all browsers is an image. So an image is used to define how an Ext component is drawn. This is a part of how Ext maintains its cross-browser compatibility.
Widget Ext has many "widgets". These include components such as a message box, grid, window, and pretty much everything else that serves a particular user interface function. I prefer to view components like onReady more as core functions, and only refer to components that provide a specific user interface role as a "widget"—like the grid that is used to present tabular data to the user.
Time for action Let's create a new page (or just modify the 'getting started' example page) and add the code to display a dialog when the page is ready: Ext.onReady(function(){ Ext.BLANK_IMAGE_URL = 'images/s.gif'; Ext.Msg.show({ title: 'Milton', msg: 'Have you seen my stapler?', buttons: { yes: true, no: true, cancel: true } }); });
[ 24 ]
Chapter 2
As we did in the previous chapter, we have placed our code inside an onReady function. We can then start to code our dialog and configure it using a config object. The config object used for this dialog has three elements, the last of which is a nested object for the three buttons. Here is how our code now looks in a browser:
This displays what appears to be a very minimal dialog, but if we start clicking on things, the built-in functionality of Ext becomes apparent. The dialog can be dragged around the screen by grabbing the title bar, just like the dialog in a typical desktop application. There is a close button built–in, and pressing the Escape key when the dialog has focus, or clicking on the Cancel button will close the dialog.
What just happened? Let's take a closer look at the two core Ext functions we have just used: •
Ext.onReady: This function provides a way to make our code wait until the
DOM is available, before doing anything. This is needed because JavaScript starts executing as soon as it is encountered in the document, at which point, our DOM elements might not exist. •
Ext.Msg.show: This is the core function used for the creation of a dialog. It
takes care of everything needed to have a working dialog. There are some shortcuts that can be used for common dialog types, which will help you save time. We will cover these in just a minute.
Using onReady It's time to examine the code we just used to display our dialog. Ext.onReady(function(){ Ext.Msg.show({ title: 'Milton', msg: 'Have you seen my stapler?', buttons: { yes: true, [ 25 ]
The Staples of Ext no: true, cancel: true } }); });
The onReady function is what we use to make our code wait until the document is ready. The argument passed to onReady is a function, which can be passed in as a function name, or created in-line, as we have done in the example code. This method of creating a function in-line is referred to as an anonymous function, which is used when you plan on calling a particular function only once. If we were executing a function that will be used again, then we could define and call it like this: Function stapler(){ Ext.Msg.show({ title: 'Milton', msg: 'Have you seen my stapler?', buttons: { yes: true, no: true, cancel: true } }); } Ext.onReady(stapler());
When we start to make our application bigger, we are not likely to use many anonymous functions, and will probably opt for creating re-usable functions. The buttons record can also specify the text to display on the button. Instead of passing a boolean value, just pass it the text you want, for example, {yes: 'Maybe'}.
More widget wonders Let's get back to making our little application as annoying as possible by adding an icon and buttons! This can be done by adding a style for the icon, and modifying the config to have an icon record along with a buttons record.
[ 26 ]
Chapter 2
First, let's discuss the CSS we need. Add the following code into the head of the document, within a style tag: .milton-icon { background: url(milton-head-icon.png) no-repeat; }
Also, we will make some changes to our widgets configuration. The icon record just needs our style name as the value, milton-icon. We have also included a function to be executed when a user clicks on any of the buttons in the dialog. This function is created as an anonymous function, and in this case, it is merely used to pass variables: Ext.Msg.show({ title: 'Milton', msg: 'Have you seen my stapler?', buttons: { yes: true, no: true, cancel: true }, icon: 'milton-icon', fn: function(btn) { Ext.Msg.alert('You Clicked', btn); } });
In our case, the function has only one argument, which is the name of the button that was clicked. So if our user was to click the Yes button, the btn variable would contain a value of yes. Using the example code, we are taking the name of the button clicked, and passing it to alert, as the message. The built-in functionality takes care of making sure the Cancel button, the close icon in the upper right corner, and the Esc key are all tied together to perform the cancel action. This is one of the many ways in which Ext makes the coding of web applications easier for us.
[ 27 ]
The Staples of Ext
Meet JSON and the config object In our example, we are utilizing what's called a config object, which is the primary way to get Ext to do what you want. This is what provides the configuration of the different options that are available for the function that is being used.
The old way We used to call functions with a pre-determined set of arguments. This means that we had to remember the order of the arguments every time the function was used. var test = new TestFuntion( 'three', 'fixed', 'arguments' );
This old way of using functions can create many problems: • • •
It requires us to remember the order of the arguments It does not describe about what the arguments represent It provides less flexibility in dealing with optional arguments
The new way—config objects Using a config object, we are able to have a larger level of flexibility, and can tell what our variables are in descriptive plain text. The order of our arguments no longer matters—firstWord could be the last item, and thirdWord could be the first, or they could be in any random order. With the config object method of passing arguments to your functions, the arguments no longer needs to be tied down to a specific place. var test = new TestFunction({ firstWord: 'three', secondWord: 'fixed', thirdWord: 'arguments' });
This method also allows for unlimited expansion of our function's arguments. Using fewer arguments or adding new arguments is simple. Another great result that comes by using a config object is that the prior usage of your functions will not be harmed by the addition or subtraction of arguments at a later point. var test = new TestFunction({ secondWord: 'three' }); [ 28 ]
Chapter 2 var test = new TestFunction({ secondWord: 'three', fourthWord: 'wow' });
What is a config object? If you are familiar with CSS or JSON, you'll notice that a config object looks similar to these, mostly because they are all the same. Config objects are just ways of structuring data so that it can easily be read by programming languages—in our case, JavaScript. For an example, let's take a look at the config portion of our example code: { title: 'Milton', msg: 'Have you seen my stapler?', buttons: { yes: true, no: true, cancel: true }, icon: 'milton-icon', fn: function(btn) { Ext.Msg.alert('You Clicked', btn); } }
The particular config that we are using here may appear complex at first, but once we get to know it, it becomes an extremely fast way of configuring widgets. Just about every Ext widget uses a configuration object, so this is something that we will want to become very familiar with. The config object will become our new best friend. Here are some key things to remember when working with a config object: •
Curly brackets wrap around your whole record set, which symbolizes the records inside the brackets as being part of an object—{records}.
•
Each record consists of a set of name/value pair, with the name and value separated by a colon, and pairs separated by commas—{name0: value0, name1: value1}.
•
The records' values can contain any type of data, including boolean, array, function, or even another object—{ name0: true, name1: { name2: value2 } }. [ 29 ]
The Staples of Ext
•
Square brackets identify an array—{name: [ 'one', 'two', 'three' ] }. An array can also contain objects with records, values, or any number of other things.
The best thing about using JSON to configure our widgets is that if we want more options, we just start typing them out. Presto! Unlike a typical function call, the order of your config options has become irrelevant, and there can be as few or as many as necessary.
How does JSON work? Sometimes, you will hear people talk about eval, which generally refers to JSON. The eval function is what JavaScript uses to interpret a JSON string, converting it into the objects, arrays, and functions that we are using.
Time for action Ok! So now we've seen how to get our Ext JS party started and ask the user a question. Now let's see what we can do with their answers. Let's add to our dialog's function so that we can decide what to do in response to each of the button-clicks. A switch statement can take care of deciding what to do in each case: fn: function(btn) { switch(btn){ case 'yes': Ext.Msg.prompt('Milton', 'Where is it?'); break; case 'no': Ext.Msg.alert('Milton', 'Im going to burn the building down!'); break; case 'cancel': Ext.Msg.wait('Saving tables to disk...','File Copy'); break; } }
Remember those built in dialog types I mentioned earlier? Well we just used some of them. They let us accomplish some common tasks without spending time writing the config needed for each standard scenario.
[ 30 ]
Chapter 2
Click OK and you get a prompt. A prompt is the common name for a small window that allows you to enter a single value, and is a standard element in almost every user interface.
Click No and you get an alert. I'm sure you are familiar with the standard alert dialog in JavaScript. I remember the first time I used an alert dialog in JavaScript. I was so excited to have an alert message on my home page that I made it pop up and say "Click OK if you are a moron".
Click the Cancel button(or (or click the close button or press the Escape key) and you will get a wait message that's using a progress dialog.
The progress dialog we are using can be controlled by Ext and be notified when it should disappear. But for the sake of simplicity, in this example, we are letting it run forever. Button focus and tab orders are built into Ext. Typically the OK or Yes button will be the default action. So pressing Enter on your keyboard will trigger that button, and pressing Tab will move you through the buttons and other items in the dialog.
[ 31 ]
The Staples of Ext
Lighting the fire Now, we can start causing some reactions in our page, based on the users' responses to the dialogs. We are going to add to our switch statement, which takes care of a Yes button click. The prompt function can handle a third argument, which is the function to be executed after the Yes button has been clicked. We are defining this so that the function will check to see if the value entered into our prompt dialog is equal to the office and then write this text to a DIV in our page if it is, and a default text of Dull Work if it does not. The code also applies a style to the same DIV, which uses a "Swingline" stapler background image. case 'yes': Ext.Msg.prompt('Milton', 'Where is it?', function(btn,txt) { if (txt.toLowerCase() == 'the office') { Ext.get('my_id').dom.innerHTML = 'Dull Work'; }else{ Ext.get('my_id').dom.innerHTML = txt; } Ext.DomHelper.applyStyles('my_id',{ background: 'transparent url(images/stapler.png) 50% 50% no-repeat' }); }); break;
The no case will display an alert message, which also styles the document when the No button is clicked. case 'no': Ext.Msg.alert('Milton', 'Im going to burn the building down!', function() { Ext.DomHelper.applyStyles('my_id',{ 'background': 'transparent url(images/fire.png) 0 100% repeat-x' }); [ 32 ]
Chapter 2 Ext.DomHelper.applyStyles(Ext.getBody(),{ 'background-color': '#FF0000' }); Ext.getBody().highlight('FFCC00',{ endColor:'FF0000', duration: 6 }); }); break;
The workhorse—Ext.get Ext is able to work so well, because it has a foundation that provides access to the DOM, and to many functions that allow manipulation of the DOM. Of these functions, get is one of the most used. Ext.get('my_id');
[ 33 ]
The Staples of Ext
This gives us access to an element in the document with the ID, my_id. If we take a look at the first example, it is using getBody , which retrieves the body element and applies our effect to that. Let's switch that around to use my_id instead. But first, we will need to create a my_id element in our document: test
If we add this to the body section of our document, and change our effect to reference this instead of the body, then our effect will happen only to the my_id div we created: Ext.get('my_id').highlight('FF0000',{ endColor:'0000FF', duration: 3 });
If we now looked at our document in a browser, we would see a 200-pixel square box changing color, instead of the entire body of the document changing color. Bear in mind that IDs are unique. So once we have used my_id, we cannot use this ID again in our document. If duplicate IDs exist in your document, then the last one found will be used. But this should be considered as a bug, and not a design practice. For the most part, Ext creates and tracks its own IDs, and most of the time, we will default to Ext's tracking of the document elements and not create them on our own. Having duplicate IDs in your document can lead to strange behavior, such as a widgets always showing up in the upper left corner of the browser, and is therefore best avoided.
Speed tip This isn't exactly a speed tip, but is more about conserving memory by using something called a "flyweight" to perform simple tasks, which results in higher speed by not clogging up the browser's memory. The same highlight effect we just used, could be written using a flyweight instead: Ext.fly('my_id').highlight('FF0000',{ endColor:'0000FF', duration: 3 });
This is used when we want to perform an action on an element in a single line of code, and we do not need to reference that element again. The flyweight re-uses the same memory over and over each time it is called.
[ 34 ]
Chapter 2
Here is an example of using a flyweight incorrectly: var my_id = Ext.fly('my_id'); Ext.fly('another_id'); my_id.highlight('FF0000',{ endColor:'0000FF', duration: 3 });
Because the flyweight re-uses the same memory each time it is called, by the time we run the highlight function on our my_id reference, the memory has changed to actually contain a reference to another_id.
Summary Using only a few lines of code, we have created a fun program that will keep you entertained for hours! Well, maybe not for hours, but for at least a few minutes. Nonetheless, we have the beginnings of the basic functionality and user interface of a typical desktop application. We have learned the basics of using configuration objects, and I'm sure this will make even more sense after we have had the chance to play with some more Ext widgets. But the real point here is that the configuration object is something that is very fundamental when using Ext. So the quicker you can wrap your head around it, the better off you will be. Don't worry if you are not entirely comfortable with the configuration object yet. We have plenty of time to figure it out. For now, let's move on to one of my favorite things—forms.
[ 35 ]
Forms In this chapter, we will learn how to create Ext forms, which are similar to the HTML forms that we use, without the usability restrictions and boring user interface. We use some different form field types to create a form that validates and submits asynchronously. Then we will create a database-driven, drop-down menu (ComboBox), and add some more complex field validation and masking. We will then finish it off with a few advanced topics that will give our forms some serious 'wow' factor. The goals of this chapter include: •
Creating a form that uses AJAX submission
•
Validating field data and creating custom validation
•
Loading form data from a database
The core components of a form The possibilities are endless with Ext forms. Key listeners, validation, error messages, and value restrictions are all built in with simple config options. Extending a form option for your own specific needs can be done easily, which is something we will cover later on in this chapter. Here are some of the core form components that you should become familiar with: •
Ext.form.FormPanel: Groups fields together in a panel, much as the FORM
tag does for a standard HTML form •
Ext.form.Field: As the primary handler of form field creation and interaction, it can be compared to the INPUT tag in HTML
Forms
Our first form To start with, let's create a form with multiple field types, a date picker, validation, error messages, and AJAX submission—just a simple one for our first try. For this example, our fields will be created using a config object instead of an instantiated Ext.form.Field component. This method will work just fine, will take less time to code, and will help our code run faster. A basic HTML page like the one we used in the previous example will be used as a starting point. The standard Ext library files need to be included and, as with everything we create in Ext, our code will need to be wrapped in the onReady function. Ext.onReady(function(){ var movie_form = new Ext.FormPanel({ url: 'movie-form-submit.php', renderTo: document.body, frame: true, title: 'Movie Information Form', width: 250, items: [{ xtype: 'textfield', fieldLabel: 'Title', name: 'title' },{ xtype: 'textfield', fieldLabel: 'Director', name: 'director' },{ xtype: 'datefield', fieldLabel: 'Released', name: 'released' }] }); });
When we run this code in a browser, we end up with a form panel that looks like this:
[ 38 ]
Chapter 3
Nice form—how does it work? The FormPanel is very similar to an HTML form. It acts as the container for our form fields. Our form has a url config so the form knows where to send the data when it is submitted. It also has a renderTo config, which defines where the form is displayed on the page. The items config element is the important one as it contains all of our form fields. The items config element is an array of fields. Each field element has an xtype that defines which type of Ext component will be used: text, date, or number. This could even be a grid or some other type of Ext component.
Form fields Now we know that each type of field is defined by its xtype. But where do xtypes come from, and how many of them are there? An xtype is just a reference to a particular Ext component, so a 'textfield' xtype is the same as its Ext.form.TextField counterpart. Here are examples of some of the xtypes that are available to us: •
textfield
•
timefield
•
numberfield
•
datefield
•
combo
•
textarea
Because these are all just Ext components, we could easily be using a grid, toolbar, or button—pretty much anything! A recurring theme in Ext components is that everything is interchangeable, and everything shares the same core functions. This ensures that just about any scenario can be handled with the Ext library. Our basic field config is set up like this: { xtype: 'textfield', fieldLabel: 'Title', name: 'title' }
Of course, we have the xtype that defines what type of a field it is—in our case it is a textfield. The fieldLabel is the text label that is displayed to the left of the field, although this can also be configured to be displayed on the top or the right side of the field. The name config is just the same as its HTML counterpart and will be used as the variable name when sending the form data to the server. [ 39 ]
Forms
The names of most of the config options for Ext components match their counterparts in HTML. This is because Ext was created by web developers, for web developers.
Making our date field isn't much different from making the text field. Change the xtype to a datefield, and we're done. { xtype: 'datefield', fieldLabel: 'Released', name: 'released' }
Validation A few of our sample fields could have validations that present the users with errors if the user does something wrong. Let's add some validation to our first form. One of the most commonly-used types of validation is checking to see if the user has entered any value at all. We will use this for our movie title field. In other words, let's make this field a required one: { xtype: 'textfield', fieldLabel: 'Title', name: 'title', allowBlank: false }
Setting up an allowBlank config option and setting it to false (the default is true) is easy enough. Most forms we build will have a bunch of required fields just like this. Each type of Ext field also has its own set of specialized validations that are specific to the data type of that field. For instance, a date field has ways to disable certain days of the week, or to use a regular expression to disable specific dates. The following code disables every day except Saturday and Sunday: { xtype: 'datefield', fieldLabel: 'Released', name: 'released', disabledDays: [1,2,3,4,5] }
[ 40 ]
Chapter 3
In this example, everyday except Saturday and Sunday is disabled. Keep in mind that the week starts on 0 for Sunday, and ends on 6 for Saturday. When we use other types of fields, we have different validations, like number fields that can restrict the size of a number or how many decimal places the number can have. The standard validation options for each field type can be found in the API reference.
Built-in validation—vtypes Another more complex type of validation is the vtype. This can be used to validate and restrict user input, and report back error messages. It will work in just about any scenario you can imagine because it uses regular expressions to do the grunt work. Here are some built-in vTypes that can come in handy: •
email
•
url
•
alpha
•
alphanum
[ 41 ]
Forms
These built-in vtypes are intended to be simplistic, and mostly used as a starting point for creating your own vtypes. Here is an alpha vtype being used with a QuickTips balloon error message: Ext.onReady(function(){ var movie_form = new Ext.FormPanel({ url: 'movie-form-submit.php', renderTo: document.body, frame: true, title: 'Movie Information Form', width: 250, items: [{ xtype: 'textfield', fieldLabel: 'Title', name: 'title', allowBlank: false },{ xtype: 'textfield', fieldLabel: 'Director', name: 'director', vtype: 'alpha' },{ xtype: 'datefield', fieldLabel: 'Released', name: 'released', disabledDays: [1,2,3,4,5] }] }); });
All we did was add a vtype to the director field. This will validate that the value entered is composed of only alphabetic characters.
[ 42 ]
Chapter 3
Now we're starting to see that the built-in vtypes are very basic. The built-in alpha vtype restricts our fields to alphabet characters only. In our case, we want the user to enter a director's name, which would usually contain only alphabet characters, with just one space or a hyphen. Capitalizing the first characters in the names could possibly make them look pretty.
A search of the Ext forum is likely to come back with a vType that someone else has created that is either exactly what you need, or close enough to use as a starting point for your own requirements.
Styles for displaying errors Forms are set up by default with a very bland error display which shows any type of error with a squiggly red line under the form field. This error display closely mimics the errors shown in programs like Microsoft Word when you spell a word incorrectly. We do have other options for displaying our error messages, but we will need to tell Ext JS to use it. The preferred option is to display the error message in a balloon. This utilizes the standard squiggly line, but also adds a balloon message that pops up when you mouse over the field.
We just need to add a line of code before our form is created that will initialize the balloon messages. Typically this is the first line within the OnReady function. For example: Ext.onReady(function(){ Ext.QuickTips.init(); // our form here });
This is all that needs to happen for your form fields to start displaying error messages in a fancy balloon.
[ 43 ]
Forms
Custom validation—create your own vtype If you're like me, regular expressions can leave you in a stupefied gaze at your monitor, so I always try to find something that is close to what I need and then modify it, rather than start from scratch. To create our own vtype, we need to add it to the vtype definitions. Each definition has a value, mask, error text, and a function used for testing: •
xxxVal: This is the regular expression to match against
•
xxxMask: This is the masking to restrict user input
•
xxxText:This is the error message that is displayed
As soon as we figure out the regular expressions we need to use, it's fairly straight forward creating our own vType—so lets try one out. Here is a validation for our director's name field. The regular expression matches a pair of alpha strings, separated by a space, and each starting with a capital letter. Sounds like a good way to validate a name—right? Ext.form.VTypes['nameVal']
= /^[A-Z][A-Za-z\-]+ [A-Z][A-Za-z\-]+$/; Ext.form.VTypes['nameMask'] = /[A-Za-z\- ]/; Ext.form.VTypes['nameText'] = 'In-valid Director Name.'; Ext.form.VTypes['name'] = function(v){ return Ext.form.VTypes['nameVal'].test(v); }
It's hard to look at this all at once, so let's break it down into its main parts. We first start with the regular expression that validates the data entered into our form field: Ext.form.VTypes['nameVal'] = /^([A-Z]{1})[A-Za-z\-]+ ([A-Z]{1})[A-Za-z\-]+/;
Next, we add the masking, which defines what characters can be typed into our form field. This is also in the form of a regular expression: Ext.form.VTypes['nameMask'] = /[A-Za-z]/;
Then, we have the text to be displayed in a balloon if there is an error: Ext.form.VTypes['nameText'] = 'In-valid Director Name.';
[ 44 ]
Chapter 3
And finally, the part that pulls it all together—the actual function used to test our field value: Ext.form.VTypes['name'] = function(v){ return Ext.form.VTypes['nameVal'].test(v); }
Put all this together and we have our own custom vtype without much effort, and that can be used over and over again.
Masking—don't press that key! Masking is used when a particular field is forced to accept only certain keystrokes, such as numbers only, or letters only, or just capital letters. The possibilities are limitless, because regular expressions are used to decide what keys to filter out. This mask example would allow an unlimited string of capital letters: maskRe: /[A-Z]/
Instead of using the masking config, consider creating a vType to accomplish your masking. If the formatting requirements should happen to change, it will be centrally-located for easy changing. So when the day arrives where your boss comes to you freaking out and tells you, "Remember those product codes that I said would always be ten numbers, well it turns out they will be eight letters instead", you can make the change to your vType, and go play Guitar Hero for the rest of the day! [ 45 ]
Forms
Radio buttons and check boxes Radio buttons and check boxes are a necessary evil. They are clumsy, and hard to work with. I try to use them only as a last resort, when nothing else will do the job. But let's add them to our form just so we can say that we did.
It's not a button, it's a radio button Lets first add a set of radio buttons to our form: { xtype: 'radio', fieldLabel: 'Filmed In', name: 'filmed_in', boxLabel: 'Color' },{ xtype: 'radio', hideLabel: false, labelSeparator: '', name: 'filmed_in', boxLabel: 'Black & White' }
These radio buttons work much like their HTML counterparts. Give them all the same name, and they will work together for you. I also like to hide the labels for the trailing radio buttons by setting hideLabel to true and labelSeperator to an empty value. This gives the form a cleaner look.
X marks the check box Sometimes, we need to use check boxes for boolean values—sort of an on/off switch. { xtype: 'checkbox', fieldLabel: 'Bad Movie', name: 'bad_movie' }
[ 46 ]
Chapter 3
The ComboBox The ComboBox, or SELECT as its known in HTML, also called a drop-down menu, is a highly-useful form element. It reduces the users' need to touch the keys on their keyboards. The Ext ComboBox has a ton of practical uses, and just as many configuration options to keep track of. First, let's make a combo using local data. To do this, we need to create a data store. There are a few different types of data store, each of which can be used for different situations. However, for this one, we are going to use a simple store: var genres = new Ext.data.SimpleStore({ fields: ['id', 'genre'], data : [['1','Comedy'],['2','Drama'],['3','Action']] });
Just like the other fields in our form, we add it to the items config. A few other config options are needed when we are setting up a combo box. The store is the obvious one-this is the data that populates the options for our combo. The other things we need are the mode, which determines if the data is coming from a local source or a remote source, and the displayField, which determines which column of data is displayed in the combo options: { xtype: 'combo', name: 'genre', fieldLabel: 'Genre', mode: 'local', store: genres, displayField:'genre', width: 120 }
This gives us a combo box that uses local data, which is good for small lists, or lists that don't change often. What happens when our list needs to be pulled up from a database?
Database-driven ComboBox The biggest change that needs to happen is on the server side—getting your data and formatting it into a JSON string that the combo box can use. Whatever server-side language is used, we will need a JSON library to 'encode' the data. If we're using PHP 5.1 or higher, this is built in.
[ 47 ]
Forms
To check our version of PHP, we can either execute a command in a terminal window or run a single line of PHP code. If we have access to this command line we can run php –v to check our version, otherwise, running a script that just has the single line will do the job.
This is what we would use to generate our JSON data using PHP 5.1 or higher:
When we use remote data, there are a few more things that need to happen. First, the data store needs to know what format the data is in. We specify this by using a data reader—in our case, it's the JSON Reader. var genres = new Ext.data.Store({ reader: new Ext.data.JsonReader({ fields: ['id', 'genre_name'], root: 'rows' }), proxy: new Ext.data.HttpProxy({ url: 'data/genres.php' }) });
The first argument for the data reader is an object containing the configuration of our reader—specifically, which fields will be read and what the root element is. The fields list is simply an array of field names; notice that we left out sort_order—this field will not be available to our data set. Our root is the element that contains our array of data, in this case it's rows, but could just as easily be bobs-crab-shack, or whatever you felt like: {rows:[ { "id":"1", "genre_name":"Comedy", "sort_order":"0" },{ [ 48 ]
Chapter 3 "id":"2", "genre_name":"Drama", "sort_order":"1" },{ // snip...// }] }
We have also set up the proxy—typically this will be an HTTP Proxy that retrieves data from the same domain as the web page. This is the most common method, but there is also a ScriptTagProxy that can be used to retrieve data from a different domain. All we need to provide for our proxy is the URL to fetch our data from. Whenever we specify a 'proxy' we are actually using AJAX. This requires that you have a web server running, otherwise AJAX will not work. Simply running your code from the file system in a web browser will not work.
Let's throw in a call to the load function at the end, so the data is loaded into our combo box before the user starts to interact with it. genres.load();
This gives us a combo box that's populated from our database, and should look like this:
Another way to pre-load the data store is to set the autoLoad option to true in our data store configuration: var genres = new Ext.data.Store({ reader: new Ext.data.JsonReader({ fields: ['id', 'genre_name'], root: 'rows' }), proxy: new Ext.data.HttpProxy({ url: 'data/genres.php' }), autoLoad: true }); [ 49 ]
Forms
TextArea and HTMLEditor We are going to add a text field to our movie information form, and Ext has a couple of options for this. We can either use the standard textarea that we were familiar with from using HTML, or we can use the HTMLEditor field, which provides rich text editing: •
textarea: Similar to a typical HTML textarea field
•
htmleditor: A rich text editor with a button bar for common
formatting tasks If we set hideLabel to true and clear out the label separator then we can have a textarea that spans the entire width of our form panel. This gives a nice look to the form: { xtype: 'textarea', name: 'description', hideLabel: true, labelSeparator: '', height: 100, anchor: '100%' }
By changing just the xtype, as shown below, we now have a fairly simple HTML editor with built-in options for font face, size, color, italics, bold, and so on. This is the first Ext component we have used that requires the QuickTips component to be initialized before we can use it. { xtype: 'htmleditor', name: 'description', hideLabel: true, labelSeparator: '', height: 100, anchor: '100%' }
[ 50 ]
Chapter 3
Listening for form field events Ext makes it extremely simple to listen for particular user actions, such as clicking on an element or pressing a particular key. A common task would be listening for the Enter key to be pressed, and then submitting the form. So let's see how this is accomplished: { xtype: 'textfield', fieldLabel: 'Title', name: 'title', allowBlank: false, listeners: { specialkey: function(f,e){ if (e.getKey() == e.ENTER) { movie_form.getForm().submit(); } } } }
The specialkey listener is called whenever a key related to navigation is pressed. This listener is also called every time the arrow keys are pressed, along with Tab, Esc, and so on. That's why we have to check to see if it was the Enter key before we take action. Now the form will only be submitted when you press Enter.
ComboBox events It seems that combo boxes commonly need to have events attached to them. Let's take our genre combo box and attach a listener to it that will run when an item in the list is selected. First let's add a dummy item to our data as the first item in the list and call it New Genre: var genres = new Ext.data.SimpleStore({ fields: ['id', 'genre'], data : [ ['0','New Genre'], ['1','Comedy'], ['2','Drama'], ['3','Action'] ] }); [ 51 ]
Forms
Then, we add the listener to our combo: { xtype: 'combo', name: 'genre', fieldLabel: 'Genre', mode: 'local', store: genres, displayField:'genre', width: 130, listeners: { select: function(f,r,i){ if (i == 0){ Ext.Msg.prompt('New Genre','Name',Ext.emptyFn); } } } }
The listener is set up to wait for a select event and then run the function that is specified. Each listener type has its own set of variables that is passed to the function—these can be looked up in the API reference. For the select event, our function is passed three things: • • •
The form field The data record of the selected combo item The index number of the item that was clicked on
Once the list item is selected, we can see which item in the list was selected. The third argument in our listener function is the index of the item that was clicked. If that has an index of zero (the first item in the list), then we will prompt the user to enter a new genre using the prompt dialog we learned about in the previous chapter. [ 52 ]
Chapter 3
Just about every component in Ext has a listener. A list of valid events for these listeners can be found at the bottom of the API documentation page for each component.
Buttons and form action Now, we have quite a mess of a form with only one problem – it doesn't send data to the server, which was the actual point behind creating our form in the first place. To do, this we are going to add some buttons. Our buttons are added to a buttons config object, similar to the way that the form fields were added. These buttons really only need two things: the text to be displayed on the button, and the function(which is called the handler) to execute when the button is clicked. buttons: [{ text: 'Save', handler: function(){ movie_form.getForm().submit({ success: function(f,a){ Ext.Msg.alert('Success', 'It worked'); }, failure: function(f,a){ Ext.Msg.alert('Warning', 'Error'); } }); } }, { text: 'Reset', handler: function(){ movie_form.getForm().reset(); } }]
The handler is provided with a function—or a reference to a function—that will be executed once the button is clicked. In this case, we are providing an anonymous function.
Form submission Our FormPanel has a url option that contains the name of the file that the form data will be sent to. This is simple enough—just like an HTML form, all of our fields will be posted to this url, so they can be processed on the server side. [ 53 ]
Forms
Inside our Save button, we have an anonymous function that runs the following code. This will run the actual submission function for our form, which sends the data to the server using AJAX. No page refresh is needed to submit the form. It all happens in the background, while the page you are looking at remains the same: movie_form.getForm().submit({ success: function(f,a){ Ext.Msg.alert('Success', 'It worked'); }, failure: function(f,a){ Ext.Msg.alert('Warning', 'Error'); } });
In order for our form submission to work properly, it must be run from a web server. success and failure options provided to the submit call handle the server's
response. These are also anonymous functions, but could just as easily be references to functions created earlier on in the code. Did you notice that the functions have a pair of arguments passed to them? These will be used to figure out what response the server gave. But first, we need to discuss how to provide that response on the server side.
Talking back—the server responses When our form is submitted to the server, a script on the server side will process the post data from the form, and decide if a true or false 'success' message should be sent back to the client side. Error messages can be sent back along with our response, and these can contain messages that correspond to our form field names. When using forms and server-side validation, a success boolean value is required. An example of a response from the server would look like this: { success: false, errors: { title: "Sounds like a Chick Flick" } }
When the success flag is set to false, it triggers the Ext form to read in the error messages and apply them to the form's validation to present the user with error messages. [ 54 ]
Chapter 3
Server-side validation of our form submission gives us a way to look up information on the server side, and return errors based on this. Let's say we have a database of bad movie names, and we don't want users to submit them to our database. We can submit the form to our script, which checks the database and returns a response based on the database lookup of that name. If we wanted to filter out chick flicks the response could look something like this: { success: false, errors: { title: "Sounds like a Chick Flick" }, errormsg: "That movie title sounds like a chick flick." }
The false success response triggers the forms error messages to be displayed. An errors object is passed with the response. The form uses this object to determine each of the error messages. A name/value pair exists in the errors object for each form field's error. Our example response also passes an errormsg, which is not used by the form, but is going to be accessed separately to present our own error message. Let's take the extra error message that we were passing back, and display it in a message box. buttons: [{ text: 'Save', handler: function(){ movie_form.getForm().submit({ success: function(f,a){ Ext.Msg.alert('Success', 'It worked'); }, failure: function(f,a){ Ext.Msg.alert('Warning', a.result.errormsg); } }); } }, { text: 'Reset', handler: function(){ movie_form.getForm().reset(); } }]
Our submit form action passes information back to the success and failure handlers. The first argument is an Ext form object, and the second is an Ext action object. Let's take a look at what's available in the Ext action object: [ 55 ]
Forms
Option
Description
failureType
String
Reports both client-side and server-side errors
response
Object
Contains raw information about the server's response, including useful header information
result
Object
Parsed JSON object based on the response from the server
type
String
The type of action that was performed–either submit or load
Now that we know what is available to the failure handler, we can set up some simple error checking: failure: function(f,a){ if (a.failureType === Ext.form.Action.CONNECT_FAILURE) {Ext.Msg.alert('Failure', 'Server reported: '+a.response.status+' '+a.response.statusText); } if (a.failureType === Ext.form.Action.SERVER_INVALID){ Ext.Msg.alert('Warning', a.result.errormsg); } }
By checking the failure type, we can determine if there was a server connection error and act accordingly, even providing details about the server's specific error message by using the result property.
Loading a form with data There are three basic ways in which forms are used in a user interface: • • •
To input data for a separate action—say, Google search To create new data To change existing data
It's the last option that we are interested in now. To accomplish this, we need to learn how to load that data from its source (static or database) into our user interface.
Static data load We can take data from somewhere in our code, and display it as the value in a form field. This single line of code sets a fields value: movie_form.getForm().findField('title'). setValue('Dumb & Dumber'); [ 56 ]
Chapter 3
Once we start working with more complex forms, this method becomes a hassle. That's why we also have the ability to load our data via an AJAX request. The server side would work much as it did when we loaded the combo box:
This would return a JSON object containing a success flag, and a data object that would be used to populate the values of the form fields. The returned data would look something like this: { success: true, data:{ "id":"1", "title":"Office Space", "director":"Mike Judge", "released":"1999-02-19", "genre":"1", "tagline":"Work Sucks", "coverthumb":"84m.jpg", "price":"19.95", "available":"1" } }
To trigger this, we need to use the form's load handler: movie_form.getForm().load({ url:'data/movie.php', params:{ id: 1 } }); [ 57 ]
Forms
Providing it with a url and params config will do the trick. The params config represents what is sent to the server side script as post/get parameters. By default, these are sent as post parameters.
Object reference or component config Throughout these first few chapters, we have started to use more and more configuration objects to set up our Ext JS components, instead of instantiating them. Let's do a quick comparison of the two methods.
Instantiated var test = new Ext.form.TextField({ fieldLabel: 'Title', name: 'title', allowBlank: false });
Here, the component has been created and memory used right away, even if it is not displayed on the screen yet. Depending on how your end users work with your application, they might never even need or use this particular text field. However, when it is the time to display this field to the end users, it shows up really fast.
Component config { xtype: 'textfield', fieldLabel: 'Title', name: 'title', allowBlank: false }
[ 58 ]
Chapter 3
With the component config, we have a 'description' of what has to happen when it is time to use the field. No memory is used right away. It's only when the user needs it that the memory is used. At that point, the field is rendered after the user has clicked on or interacted with something else, which can slow the initial display slightly. This method of setting up components has many other advantages; one of them is being able to send configurations 'over the wire'. The method of sending configurations 'over the wire' means that server-side code can generate a configuration to create a client-side component.
Summary We have taken the foundation of the classic web application—forms—and injected them with the power of Ext JS, creating a uniquely-flexible and powerful user interface. The form created in this chapter can validate user input, load data from a database, and send that data back to the server. From the methods outlined in this chapter, we can go on to create forms for use in simple text searches, or a complexly validated data entry screen.
[ 59 ]
Buttons, Menus, and Toolbars The unsung heroes of every application are the simple things like buttons, menus, and toolbars. In this chapter, we will cover how to add these items to our applications. Our example will contain a few different types of buttons, both with and without menus. A button can simply be an icon, or text, or both. Toolbars also have some mechanical elements such as spacers and dividers that can help to organize the buttons on your toolbars items. We will also cover how to make these elements react to user interaction.
A toolbar for every occasion Just about every Ext component—panels, panels, windows, grids can accept a toolbar on either the top or the bottom. The option is also available to render the toolbar standalone into any DOM element in our document. The toolbar is an extremely flexible and useful component that will no doubt be used in every application. •
Ext.Toolbar: The main container for the buttons
•
Ext.Button: The primary handler for button creation and interaction
•
Ext.menu: A menu
Toolbars Our first toolbar is going to be rendered standalone in the body of our document. We will add one of each of the main button types, so we can experiment with each: • •
Button—tbbutton: This is the standard button that we are all familiar with. Split Button—tbsplit: A split button is where you have a default button action and an optional menu. These are used in cases where you need to have many options in the same category as your button, of which there is a most commonly used default option.
Buttons, Menus, and Toolbars
•
Menu—tbbutton+menu: A menu is just a button with the menu config filled in with options. Ext.onReady(function(){ new Ext.Toolbar({ renderTo: document.body, items: [{ xtype: 'tbbutton', text: 'Button' },{ xtype: 'tbbutton', text: 'Menu Button', menu: [{ text: 'Better' },{ text: 'Good' },{ text: 'Best' }] },{ xtype: 'tbsplit', text: 'Split Button', menu: [{ text: 'Item One' },{ text: 'Item Two' },{ text: 'Item Three' }] }] }); });
As usual, everything is inside our onReady event handler. The items config holds all of our toolbars elements—I say elements and not buttons because the toolbar can accept many different types of Ext components including form fields—which we will be implementing later on in this chapter. The default xtype for each element in the items config is tbbutton. We can leave out the xtype config element if tbbutton is the type we want, but I like to include it just to help me keep track. [ 62 ]
Chapter 4
The button Creating a button is fairly straightforward; the main config option is the text that is displayed on the button. We can also add an icon to be used alongside the text if we want to. Here is a stripped-down button: { xtype: 'tbbutton', text: 'Button' }
Menu A menu is just a button with the menu config populated—it's that simple. The menu items work along the same principles as the buttons. They can have icons, classes, and handlers assigned to them. The menu items could also be grouped together to form a set of option buttons, but first let's create a standard menu. This is the config for a typical menu config: { xtype: 'tbbutton', text: 'Button', menu: [{ text: 'Better' },{ text: 'Good' },{ text: 'Best' }] }
[ 63 ]
Buttons, Menus, and Toolbars
As we can see, once the menu array config is populated, the menu comes to life. To group these menu items together, we would need to set the group config and the boolean checked value for each item: menu: [{ text: 'Better', checked: true, group: 'quality' }, { text: 'Good', checked: false, group: 'quality' }, { text: 'Best', checked: false, group: 'quality' }]
Split button The split button sounds like a complex component, but it's just like a button and a menu combined, with a slight twist. By using this type of button, you get to use the functionality of a button while adding the option to select an item from the attached menu. Clicking the left portion of the button that contains the text triggers the button action. However, clicking the right side of the button, which contains a small down arrow, triggers the menu. { xtype: 'tbsplit', text: 'Split Button', menu: [{ text: 'Item One' },{ text: 'Item Two' },{ text: 'Item Three' }] } [ 64 ]
Chapter 4
Toolbar item alignment, dividers, and spacers By default, every toolbar aligns elements to the leftmost side. There is no alignment config for a toolbar, so if we want to align all of the toolbar buttons to the rightmost side, we need to add a fill as the first item in the toolbar. If we want to have items split up between both the left and right sides, we can also use a fill: { xtype: 'tbfill' }
Pop this little guy in a tool-bar wherever you want to add space and he will push items on either side of the fill to the ends of the tool bar, as shown below:
We also have elements that can add space or vertical dividers, like the one used between the Menu Button and the Split Button. The spacer adds a few pixels of empty space that can be used to space out buttons, or move elements away from the edge of the toolbar: { xtype: 'tbspacer' }
A divider can be added in the same way: { xtype: 'tbseparator' }
[ 65 ]
Buttons, Menus, and Toolbars
Shortcuts Ext has many shortcuts that can be used to make coding faster. Shortcuts are a character or two that can be used in place of a configuration object. For example, consider the standard toolbar filler configuration: { xtype: 'tbfill' }
The shortcut for a toolbar filler is a hyphen and a greater than symbol: '->'
Not all of these shortcuts are documented. So be adventurous, poke around the source code, and see what you can find. Here is a list of the commonly-used shortcuts: Component Fill
Shortcut '->'
Separator Spacer
'-' or 'separator' ''
TextItem
'Your Text'
Description The fill that is used to push items to the right side of the toolbar. A vertical bar used to visually separate items. Empty space used to separate items visually. The space is two pixels wide, but can be changed by overriding the ytb-spacer CSS class. Add any text or HTML directly to a toolbar by simply placing it within quotes.
Icon buttons The standard button can act as an icon button like the ones you see used in text editors to make text bold or italic. Two steps need to be taken to make an icon button—defining an image to be used as the icon and applying the appropriate class to the button. { xtype: 'tbbutton', cls: 'x-btn-icon', icon: 'images/bomb.png' }
[ 66 ]
Chapter 4
This could just as easily be an icon beside text by changing the style class and adding the text config. { xtype: 'tbbutton', cls: 'x-btn-text-icon', icon: 'images/bomb.png', text: 'Tha Bomb' }
Button handlers—click me! A button needs to do more than just look pretty—it needs to react to the user. This is where handlers come in. A handler is a function that is executed when a button or menu item is clicked. The handler config is where we add our function: { xtype: 'tbbutton', text: 'Button', handler: function(){ Ext.Msg.alert('Boo', 'Here I am'); } }
This code will pop up an alert message when the button is clicked. Sometimes, we need to make changes to the button when it's clicked, so each ach button handler passes a reference to itself for this purpose. The first argument of our handler is a reference to the component that triggered the event. { xtype: 'tbbutton', text: 'Button', handler: function(f){ f.disable(); } }
We can take this reference to the button—a reference to itself—and access all of the properties and functions of that button. For this sample, we have called the disable function which grays out the button and makes it unselectable. We can have more fun than just disabling a button. Why don't we try something more useful? [ 67 ]
Buttons, Menus, and Toolbars
Load content on menu item click Lets take our button click and do something more useful with it. For this example, we are going to add a config option to each menu item that will be used to determine what content file to load in the body of our page: { xtype: 'tbsplit', text: 'Help', menu: [{ text: 'Genre', helpfile: 'genre', handler: Movies.showHelp },{ text: 'Director', helpfile: 'director', handler: Movies.showHelp },{ text: 'Title', helpfile: 'title', handler: Movies.showHelp }] }
Note the helpfile config option that we have added to each of the menu items config. We have made this config property up so that we have a way to store a variable that is unique to each menu item. This is possible because config properties can be anything we need them to be, and can be created on the fly. In this case, we are using a config property as a variable that holds the name of the file we want to load. The other new thing we are doing is creating a collection of functions to handle the menu item click. These functions are all organized into a Movies class. var Movies = function() { return { showHelp : function(btn){ var helpbody = Ext.get('helpbody'); if (!helpbody) { Ext.DomHelper.append(Ext.getBody(), { tag:'div', id:'helpbody' }); } Movies.doLoad(btn.helpfile); }, [ 68 ]
Chapter 4 doLoad : function(file){ Ext.get('helpbody').load({ url: 'html/' + file + '.txt' }); } }; }();
I don't want to get bogged down with too much detail about this Movies class just yet, but essentially all it does is handle our menu item clicks. This class will load a text file into the body of our web page via an AJAX request—which text file it loads is related to which menu item is clicked. So once this Movies class is in place in our page, we will be able to bring up this page in our browser and click on each menu item to load the relevant help file into the body of the page.
.
Next, we will try using a text field to perform this same type of action.
Form fields in a toolbar Like most things in Ext, a tool bar can accept just about any Ext component. Naturally, form fields and combo boxes are very useful items to have on a toolbar. { xtype: 'textfield' }
In the same way as we created form fields in the last chapter, we have added the form fields to the items array, which will place the form fields within the toolbar. Now let's make the form field do something useful, by having it perform the same functionality as our help menu, but in a more dynamic way. { xtype: 'textfield', listeners: { specialkey: Movies.doSearch } } [ 69 ]
Buttons, Menus, and Toolbars
This listener is added directly to the form field's 'ss config. For this, we are using a specialkey listener, which we used in the previous chapter. This is the listener
that is used to capture edit keystrokes, such as Enter and Delete among others. The handler function will be added to our small Movies class created earlier: doSearch : function(frm,evt){ if (evt.getKey() == evt.ENTER) { Movies.doLoad(frm.getValue()); } }
Now we have a text field in our toolbar that enables us to type in the name of the text file to load. Try some of the samples used in our menu, such as director or title.
Toolbars in windows, grids, and panels All of the toolbars we have been working with have an items config. If we want to place one of these toolbars into another Ext component, such as a panel or a window, we can simply take the contents of the items config for a toolbar, and place it within one of the two pre-set containers that exist for panel-type components.
Panel-type components, such as the window and the grid, have a top and bottom toolbar config: •
tbar: The top toolbar
•
bbar: The bottom toolbar
[ 70 ]
Chapter 4
If we wanted to place a toolbar at the top of a window, filling the tbar config with an array of toolbar items would give us what we wanted: new Ext.Window({ title: 'Help', id: 'helpwin', width: 300, height: 300, tbar: [{ text: 'Close', handler: function(){ Ext.getCmp('helpwin').close(); } },{ text: 'Disable', handler: function(t){ t.disable(); } }], autoLoad: 'html/' + btn.helpfile + '.txt' }).show();
Ext also has a custom toolbar for paged grids that contains all of the buttons for moving through pages of results. We will cover this special toolbar in the grid chapter later in this book.
Summary In this chapter, we had the chance to play with a couple of different ways to create toolbar items, including using a config object or its shortcut. The many options available for toolbars make them a useful component for everything from the simplest button bar, to a complex combination of buttons, menus, and form fields. Interacting with the buttons, menus and form fields is easy using the built-in handlers.
[ 71 ]
Displaying Data with Grids The grid is, without doubt, one of the most widely-used components of Ext. We all have data, and this needs to be presented to the end user in an easy-to-understand manner. The spreadsheet (a.k.a.: grid) is the perfect way to do this—the concept has been around for quite a while because it works. Ext takes that concept and makes it flexible and downright amazing! In this chapter we shall be: •
Using a GridPanel to display structured data in a user-friendly grid
•
Reading data from the server (or database) to display in the grid
•
Working with a grid's events and manipulating the grid's features
•
Using some advanced data formatting techniques for grids
•
Paging data in a grid
We will cover how to define the rows and columns, but more importantly, we will learn how to make the grid flashier. We can do this by adding custom rendered cells that contain images, and change styles based on data values. In doing this we are adding real value to our grid by breaking out of the boundaries of spreadsheets!
Displaying Data With Grids
What is a grid anyway? Ext grids are similar to a spreadsheet; there are two main parts to each spreadsheet: •
Columns
•
Rows
Here our columns are Title, Released, Genre, and Price. Each of the rows contains movies such as The Big Lebowski, Super Troopers, and so on. The rows are really our data; each row in the grid represents a record of data.
Displaying structured data with a GridPanel Displaying data in a grid requires two Ext components: •
A store that acts like an in-memory database, keeping track of the data we want to display
•
A grid panel that provides a way to display the data stored in a data store [ 74 ]
Chapter 5
Before we start to create each of these, let's look at some of the terminology that will be used, because this can be confusing at first: •
Columns: This refers to a whole column of data, and would contain information only relevant to the display of data down through the entire column, including the heading. In Ext JS, this information is part of the Column Model.
•
Fields: This also refers to an entire column of data, but is refers to the actual data values. With Ext JS, this is used in the reader, for loading data.
Setting up a data store The first thing we need to do is set up our data, which will be placed into a data store. The data store types available in Ext give us a consistent way of reading different data formats such as XML and JSON, and reading this data in a consistent way throughout all of the Ext widgets. Regardless of whether this data it is JSON, XML, an array, or even a custom data type of your own, it's all accessed in the same way thanks to the data store. Some data stores available, by default, in Ext are: •
Simple (Array)
•
XML
•
JSON
A custom data store could be created to read data that does not fit into these categories. The Ext JS forums provide user-contributed data readers for things such as CSV and ColdFusion formats of data.
Adding data to our data store In our first attempt, we are going to create a grid that uses simple local array data. The data we're using below is taken from a very small movie database of some of my favorite movies, and is similar to the data that will be pulled from an actual database later in this chapter. The data store needs two things: the data itself, and a description of the data—or what could be thought of as the fields. A reader will be used to read the data from the array, and this is where we define the fields of data contained in our array. The reader acts as an interpreter of sorts; it knows how to interpret a string of data as rows of data to be used with Ext JS.
[ 75 ]
Displaying Data With Grids
The following code should be placed inside the Ext OnReady function: var store = new Ext.data.Store({ data: [ [ 1, "Office Space", "Mike Judge", "1999-02-19", 1, "Work Sucks", "19.95", 1 ],[ 3, "Super Troopers", "Jay Chandrasekhar", "2002-02-15", 1, "Altered State Police", "14.95", 1 ] //...more rows of data removed for readability...// ], reader: new Ext.data.ArrayReader({id:'id'}, [ 'id', 'title', 'director', {name: 'released', type: 'date', dateFormat: 'Y-m-d'}, 'genre', 'tagline', 'price', 'available' ] });
If we viewed this code in a browser we would not see anything—that's because a data store is just a way of loading and keeping track of your data. The web browser's memory has our data in it. Now we need to decide how to display it to the user.
Defining your data for the data store The reader needs to know which fields to read in as data for our data store, so we will need to define these. [ 76 ]
Chapter 5
Fields are defined using an array of objects—or if the data is to be read verbatim, just a string specifying the field name. All except one of our fields in this example can be defined with a simple name. For example, the title field could be defined using an object like this: {name: 'title'}
However, in our case, because we are just reading in the data as a string, we can simply pass the field name and save some typing: 'title'
The released field is different because we want to treat its data appropriately, as a date type. For each field format type, there may be options to define the format of the data more explicitly. With the date type, there is a dateFormat string that needs to be defined. If you have used PHP, these date format strings will look familiar, because Ext uses the same date format strings that PHP does. {name: 'released', type: 'date', dateFormat: 'Y-m-d'}
Specifying data types Ext JS has many ways to properly read in particular data types. They are shown here: Field Type string
String data
Description
Information
int
Number
Uses JavaScripts parseInt function
float
Floating point number
Uses JavaScripts parseFloat function
boolean
True/False data
date
Date data
dateFormat config needed
Below are a few useful data types that I take advantage of very often: Field Type date
Description
Usage
Data containing a date
You also have to specify the dateFormat. This tells Ext how to turn text data into dates Y-m-d means 'full 4 digit year-numbered month-numbered day' dateFormat in Ext is the same as in PHP, and there's a handy reference to different date formats online here: http://www.php.net/date
[ 77 ]
Displaying Data With Grids
Field Type int
Description
Usage
Numeric data
Treats the value as an integer—this is useful when you plan on making comparisons of your data to perform other actions, such as adding two columns together
boolean or bool
True/False data
Takes care of reading the different ideas of what boolean values are, such as converting a string to an actual boolean value, or translating zero and one to boolean values
If we were to display the data just as the reader sees it, we would end up with something like this:
Now that is ugly—here's a breakdown of what happened: •
The released date has been type set properly as a date, and interpreted from the value in our data. It's provided in the standard ugly JavaScript date format—luckily Ext has ways to make this look pretty.
•
The Price column has been type set as a floating point number. Note that there is no need to specify the decimal precision.
•
The Avail column has been interpreted as a true boolean value, even if the raw data was not a true boolean value.
This is why it's useful to specify the type of data that is being read, and apply any special options that are available.
Displaying the GridPanel The thing that pulls everything together is the GridPanel, which takes care of placing the data into columns and rows, along with adding column headers, and boxing everything together in a neat little package. [ 78 ]
Chapter 5
The movie data isn't much good to anybody just sitting in the computer's memory. Let's display it in a grid: 1. Add your data store to the following GridPanel code: Ext.onReady(function(){ // add your data store here var grid = new Ext.grid.GridPanel({ renderTo: document.body, frame:true, title: 'Movie Database', height:200, width:500, store: store, columns: [ {header: "Title", dataIndex: 'title'}, {header: "Director", dataIndex: 'director'}, {header: "Released", dataIndex: 'released', renderer: Ext.util.Format.dateRenderer('m/d/Y')}, {header: "Genre", dataIndex: 'genre'}, {header: "Tagline", dataIndex: 'tagline'} ] }); });
2. Load it in a browser, and here's what you will see:
[ 79 ]
Displaying Data With Grids
How did that work? Our data store is passed into the grid along with a column model that determines how the columns and column headers are to be displayed. This is different from the field data used in the reader, which was defined so that the reader knew how to read data.
Configuring the GridPanel The GridPanel is the widget that ties everything together: Ext.grid.GridPanel
Only a few basic things are needed to set up the GridPanel: Field type
Description
Usage
renderTo
Where should the grid panel be displayed?
This needs to be a valid DOM object, or the ID of a DOM element. Later on, we will pass the GridPanel directly into the other widgets to be rendered. So this config option will become obsolete.
frame
Frames the grid panel
This just adds a nice border around the GridPanel along with a title bar. It's not required, but looks good when rendered to the page.
height and width
Size in pixels
A height is almost always required when using a grid, as the grid itself cannot determine its own height. When we get into using grids in a layout, this will no longer be needed.
store
Our data
This is a reference to a valid data store where our data lives.
columns
Column model
This is an array of objects defining the columns of our grid.
stripeRows
Stripe rows
This is set to true to alternate the colors in the rows of data.
Our basic setup for a grid panel will look something like this: var grid = new Ext.grid.GridPanel({ renderTo: Ext.getBody(), frame:true, title: 'Movie Database', height:200, width:500, store: store, columns: [ insert columns here ] }); [ 80 ]
Chapter 5
We can almost read through the configuration like a sentence: Render our grid into the body of the document, frame it, and give it a title of 'Movie Database'. The height will be 200 and the width 500; it will use our 'store' data store and have the columns specified. The one reason why I love object-based configuration so much is that it is human readable. We never have to go to the manual to look up what argument 3 of function x is; we simply say "make it 200 tall and 500 wide".
Defining a Grids column model To define our grid's columns, we need to create an array of objects that define how these columns are to be displayed and treated. columns: [ {header: {header: {header: {header: {header: ]
'Title', dataIndex: 'title'}, 'Director', dataIndex: 'director'}, 'Released', dataIndex: 'released'}, 'Genre', dataIndex: 'genre'}, 'Tagline', dataIndex: 'tagline'}
This will create grid column headers that look like this:
The object defining each column can have many config options, but requires that at least a header and dataIndex be defined. The header config is simply the text to be displayed in the column header. The dataIndex config is the name of the data field to be used in that column. We defined these when we set up the data stores reader. Here are some other useful config options for the column model: Option renderer
Description
Usage
Specifies how the data should be displayed
Can be used to format the data for this column into your preferred format. Any type of data can be transformed. We will learn about these in the next few pages.
hidden
Hides the column
Boolean value defining whether or not the column should be displayed.
[ 81 ]
Displaying Data With Grids
Option width
sortable
Description
Usage
Specifies the column width in pixels
The width of the column. Default is 100 pixels; overflowing content is hidden.
Specifies whether the column is sortable
Boolean value specifying whether or not the column can be sorted.
Using cell renderers We can do some pretty neat things with cell rendering. There are few limitations to stop us from making the cell look like or contain whatever we want. All that needs to be done is to specify one of the built-in cell formatting functions provided by Ext JS, such as usMoney, or create our own cell renderer that returns a formatted value. Let's take a look at using the built-in cell renderers first. Then we can look at creating our own.
Formatting data using the built-in cell renderers Many built-in formatting functions exist to take care of common rendering requirements. One that I use quite often is the date renderer: renderer: Ext.util.Format.dateRenderer('m/d/Y')
Some other renderers include some commonly-required formatting, such as money, capitalize, and lowercase.
Here are some renderers that most people find useful: Renderer dateRenderer
Description
Usage
Formats a date for display
Can be used to format the data for this column into your preferred date format. Any type of date can be transformed.
uppercase lowercase
Upper and lower case conversion
Converts the string to completely upper or lower case text.
capitalize
Pretty text
Formats a text string to have correct capitalization.
[ 82 ]
Chapter 5
Creating lookup data stores—custom cell rendering We're going to start by taking the 'genre' column, which has a numeric value, and looking up that value in the data store we created in the Forms chapter to find the textual representation of our genre number. First, we add a config option to the column model that tells it which function to use for rendering the cell's content. {header: 'Genre', dataIndex: 'genre', renderer: genre_name}
Now let's create that function. The function call is passed the value of its cell as the first argument. The second argument is a cell object, while the third is the data store for that grid—neither of which we will use for this renderer. So let's just leave them. function genre_name(val){ return genres.queryBy(function(rec){ if (rec.data.id == val){ return true; }else{ return false; } }).itemAt(0).data.genre; }
The renderer function is passed the value of the current cell of data. This value can be tested, and actions can be performed on it—whatever value is returned by the function is rendered to the grid cell. A queryBy handler is used to filter the data from our store. It accepts a function that performs a comparison against each row of data, and returns true to use the row that matches. Just for good measure, here is a compacted version of the same function. It's not as easy to read as the first version, but accomplishes the same result. function genre_name(val){ return genres.queryBy(function(rec){ return rec.data.id == val; }).itemAt(0).data.genre; }
[ 83 ]
Displaying Data With Grids
Combining two columns The lookup data store is a very useful renderer. However, it's more common for developers to combine two columns to form a single cell, for example, to perform a calculation on a pair of columns to figure out a total, percentage, remainder, and so on or to concatenate two or more text fields. Let's just take the title of our movie, and append the tagline field underneath the title. The first step will be to hide the tagline column, since it will be displayed along with the title field—we don't need it shown in two places. Hiding the column can be done in our column model. {header: 'Tagline', dataIndex: 'tagline', hidden: true}
The next step is our renderer function that will take care of combining the fields. function title_tagline(val, x, store){ return ''+val+'
'+store.data.tagline; }
I went ahead and bolded the title as well, to provide some contrast between the two pieces of data. As you can see, HTML tags work just fine within grid cells. The next step would be to add the renderer config to our column model, referencing the title_tagline function that we just created. {header: 'Title', dataIndex: 'title', renderer: title_tagline}
This will make the title column look like this:
Generating HTML and graphics Let's get some visuals by placing an image into each row, which will show the cover art for each movie title. As we just found out, we can use plain HTML within the cell. So all that needs to happen is to create a renderer that grabs our field containing the filename of the image and write that into an IMG tag as the SRC attribute. function cover_image(val){ return ''; }
[ 84 ]
Chapter 5
With this fairly straightforward function, and setting a column renderer, we have an image in our grid: {header: 'Cover', dataIndex: 'coverthumb', renderer: cover_image}
If you make all these renderer additions, the grid should now look like this:
Built-in features Ext has some very nice built-in features to help complete the spreadsheet-like interface. Columns have a built-in menus that provide access to sorting, displaying, and hiding columns.
[ 85 ]
Displaying Data With Grids
Client-side sorting Unless specified as a server-side (remotely) sorted grid, an Ext grid is able to sort columns on the client side. Server-side sorting should be used if the data is paged, or if the data is in such a format that client-side sorting is not possible. Client-side sorting is quick, easy, and built-in: {header: 'Tagline', dataIndex: 'tagline', sortable: true}
We can also accomplish this after the grid has been rendered: var colmodel = grid.getColumnModel(); colmodel.getColumnById('tagline').sortable = true;
Our column model controls the display of columns and column headers. If we grab a reference to the column model by asking for it from the grid, then we can make changes to the columns after it has been rendered. We do this by using the getColumnById handler that the column model provides us with, and which accepts the column ID as the argument.
Hidden/visible columns Using the column header menu, columns can be hidden or shown. This can also be changed at a config level, to have columns hidden by default, as shown below: {header: "Tagline", dataIndex: 'tagline', hidden: true}
The more exciting way is to do this after the grid has been rendered, by using the functions Ext provides: Var colmodel = grid.getColumnModel(); colmodel.setHidden(colmodel.getIndexById('tagline'),true);
Grabbing a reference to the column model again will allow us the make this change.
Column reordering Dragging a column header will allow the user to reorder the entire column into a new order within the grid. All of this is enabled by default as part of the built-in functionality of the grid.
[ 86 ]
Chapter 5
Any column can be dragged to a different order in the grid . This screenshot shows the Price column being moved to between the Title and Director columns. We can disable this functionality entirely by setting a config option in the GridPanel: enableColumnMove: false
This move event—and many other events in the grid—can be monitored and responded to. For example, we could monitor the movement of columns and pop up a message based on where the column was moved to: grid.getColumnModel().on('columnmoved', function(cm,oindex,nindex) { var title = 'You Moved '+cm.getColumnHeader(nindex); if (oindex > nindex){ var dirmsg = (oindex-nindex)+' Column(s) to the Left'; }else{ var dirmsg = (nindex-oindex)+' Column(s) to the Right'; } Ext.Msg.alert(title,dirmsg); } );
Many different events can be monitored using the same technique. The grid, data store, and column model each have their own set of events that can be monitored, all of which we will learn about in more detail later in this chapter.
[ 87 ]
Displaying Data With Grids
Displaying server-side data in the grid With Ext we can pull data into our web page in many ways. We started by pulling in local array data for use in the grid. Now we are going to pull the data in from an external file and a web server.
Loading the movie database from an XML file We have this great movie database now, but each time I want to add a new movie I have to edit the JavaScript array. So why not store and pull our data from an XML file instead? This will be easier to update, and the XML file could even be generated from a database query or a custom script. Lets take a look at an example of how our XML file would be laid out like:
1 Office Space Mike Judge 1999-02-19 1 Work Sucks 84m.jpg 19.95 1
3 Super Troopers Jay Chandrasekhar 2002-02-15 1 Altered State Police 42m.jpg 14.95 1
//...more rows of data removed for readability...//
[ 88 ]
Chapter 5
The other change we would need to make is to alter the data reader, and set the location of our XML file so that the data store knows where to fetch the data from. There are four basic changes that need to happen when moving from local to remote data: •
The url config option, specifying the location of our data, needs to be added—this will replace the data config option that we used to store local data
•
The reader is changed from an ArrayReader to an XmlReader to deal with the differences involved in reading from an XML format instead of an array format
•
The XmlReader is told which element contains a record or row of data by setting the record config option
•
A call needs to be added to the load function that tells our data store to pull in the data from the file and parse it into memory
var store = new Ext.data.Store({ url: 'movies.xml', reader: new Ext.data.XmlReader({ record:'row', id:'id' }, [ 'id', 'coverthumb', 'title', 'director', {name: 'released', type: 'date', dateFormat: 'Y-m-d'}, 'genre', 'tagline', {name: 'price', type: 'float'}, {name: 'available', type: 'bool'} ]) }); store.load();
Try making these changes and see if your grid still works—there should be no noticeable difference when changing data sources or formats. Note that to make the change from local to remote data, and from an array format to an XML format, the only changes we needed to make were to the data store. Ext isolates these types of changes by using a common data store that is able to use an external reader to read many formats. [ 89 ]
Displaying Data With Grids
Loading the movie database from a JSON file We're in the same boat as XML with this data format. Just changing the reader and setting up some config options will take care of everything. The JSON rows of data are expected to be in the form of an array of objects— our movies.json file will therefore contain data like this: { success:true, rows:[ { "id":"1", "title":"Office Space", "director":"Mike Judge", "released":"1999-02-19", "genre":"1", "tagline":"Work Sucks", "coverthumb":"84m.jpg", "price":"19.95", "active":"1" },{ "id":"3", "title":"Super Troopers", "director":"Jay Chandrasekhar", "released":"2002-02-15", "genre":"1", "tagline":"Altered State Police", "coverthumb":"42m.jpg", "price":"14.95", "active":"1" } //...more rows of data removed for readability...// ] }
The main difference between setting up a JSON reader versus an XML reader, is that the JSON reader needs to know the name of the root element that holds our array objects (the data). So instead of specifying a record config, we need to specify a root config: var store = new Ext.data.Store({ url: 'movies.json', reader: new Ext.data.JsonReader({ root:'rows', [ 90 ]
Chapter 5 id:'id' }, [ 'id', 'coverthumb', 'title', 'director', {name: 'released', type: 'date', dateFormat: 'Y-m-d'}, 'genre', 'tagline', {name: 'price', type: 'float'}, {name: 'available', type: 'bool'} ]) }); store.load();
This grid will have an identical look and the same functionality as the array and the XML grids that we created earlier. JSON is a format native to JavaScript, and will end up being the quickest format for the data store to read, which means that our grid will be displayed much faster.
Loading data from a database using PHP The setup for our GridPanel stays the same. But instead of grabbing a static file with the JSON data, we can pull the data from a PHP script that will fetch the data from a database, and format it into JSON that Ext is able to read:
[ 91 ]
Displaying Data With Grids
The PHP code used in these examples is meant to be the bare minimum needed to get the job done. In a production environment you would want to account for security against SQL injection attacks, other error checking, and probably user authentication—which the example code does not account for.
Programming the grid Most of the code we have written so far concerns configuring the grid prior to it being displayed. Often, we will want the grid to do something in response to user input. One of the common interactions in a grid is to select or move the rows of data. Ext JS refers to this interaction and how it's handled as the "selection model". Let's see how to set one up.
Working with cell and row selections Ext grids provide ways of monitoring user interaction with the grids rows, cells and columns with a thing called the selection model. The selection model is used to determine how rows, columns, or cells are selected, and how many items can be selected at a time. This allows us to create listeners for these selection events, along with giving us a way to query which rows have been selected. Some of the selection models are: •
CellSelectionModel: This lets the user to select a single cell from
the grid •
RowSelectionModel: This lets the user select an entire row from the grid
•
ColumnSelectionModel: This lets the user select an entire column from the
grid •
CheckBoxSelectionModel: This one uses a checkbox to enable row selections
Choosing a selection model is something that depends on your project's requirements. For our movie database, we will use a row selection model, which is the most commonly used type of selection model. The selection model is defined in the GridPanel config by using the sm config option. sm: new Ext.grid.RowSelectionModel({ singleSelect: true })
[ 92 ]
Chapter 5
We will also pass the selection model a config that specifies single row selections only. This stops the user from selecting multiple rows at the same time.
Listening to our selection model for selections Listeners for a grid can be included in many different places depending on the desired interaction. Earlier, we applied a listener to our column model because we wanted to listen for column activity. Here, we will add a listener to the selection model because we want to know when a user has selected a movie. sm: new Ext.grid.RowSelectionModel({ singleSelect: true, listeners: { rowselect: { fn: function(sm,index,record) { Ext.Msg.alert('You Selected',record.data.title); } } } })
[ 93 ]
Displaying Data With Grids
Selecting a row now brings up an alert dialog. Let's take a look at what is happening here: •
A listener is set for the rowselect event. This waits for a row to be selected, and then executes our function when this happens.
•
Our function is passed a selection model, the numeric index of the row selected (starting with zero for the first row), and the data record of the row that was selected.
•
Using the data record that our function received, we can grab the title of the movie selected and put it into a message dialog.
Manipulating the grid (and its data) with code Many functions are available for manipulating the grid and the data in the grid. These can be tied into other Ext widgets to create pretty much any functionality needed.
Altering the grid at the click of a button Here, we are going to add a top toolbar, which will have a button that brings up a prompt allowing the movie title to be edited. tbar: [{ text: 'Change Title', handler: function(){ var sm = grid.getSelectionModel(); if (sm.hasSelection()){ var sel = sm.getSelected(); Ext.Msg.show({ title: 'Change Title', prompt: true, buttons: Ext.MessageBox.OKCANCEL, value: sel.data.title, fn: function(btn,text){ if (btn == 'ok'){ sel.set('title', text); } } }); } } }]
[ 94 ]
Chapter 5
All we are really doing here is changing the data store. The data in our database on the web server has stayed the same, and the web server has no idea whether anything has changed. It's up to us to communicate this change to the server via an AJAX request or via some other method you may prefer to use. Let's take a quick look at what's happening here: •
sm: The selection model is retrieved from our grid
•
sel: We used the selection model to retrieve the row that has been selected
•
sel.data: Using the data object of the selected item, we can grab its data
This basic method can be used to create many fun user interactions. Our limitation is that there are only 24 hours in a day, and sleep catches up with everyone!
Advanced grid formatting Because we are in the mood to create some user-grid interactions, let us add some more buttons that do fun stuff.
[ 95 ]
Displaying Data With Grids
Here is a button we can add to the top toolbar to allow us to hide or show a column. We will also change the text of the button based on the visibility of the column: { text: 'Hide Price', handler: function(btn){ var cm = grid.getColumnModel(); var pi = cm.getIndexById('price'); if (cm.isHidden(pi)){ cm.setHidden(pi,false); btn.setText('Hide Price'); }else{ cm.setHidden(pi,true); btn.setText('Show Price'); } btn.render(); } }
We use a new handler here—getIndexById, which, as you can imagine, gets the column index, and will be a number from zero to one less than the total number of columns. This number is an indicator of where that column is in relation to the other columns. In our grid code, the column price is the fourth column, which means that the index is 3 because indexes start at zero.
Paging the grid Paging requires that we have a server-side element (script) that will break up our data into pages. Let's start with that. PHP is well-suited to this, and the code is easy to understand and interpret into other languages. So we will use PHP for our example. When a paging grid is paged, it will pass start and limit parameters to the server-side script. This is typical of what's used with a database to select a subset of records. Our script can read in these parameters and use them pretty much verbatim in the database query. Here is a typical PHP script that would handle paging. We will name the file movies-paging.php.
This PHP script will take care of the server-side part of paging. So now we just need to add a paging toolbar to the grid—it's really quite simple!
Earlier we had used a top toolbar to hold some buttons for messing with the grid. Now we are going to place a paging toolbar in the bottom toolbar slot (mostly because I think paging bars look dumb on the top).
[ 97 ]
Displaying Data With Grids
The following code will add a paging toolbar: bbar: new Ext.PagingToolbar({ pageSize: 3, store: store })
And of course we need to change the url of our data store to the url of the PHP server-side paging code. A totalProperty is also required when paging data. This is the variable name that holds the total record count of rows in the database. var store = new Ext.data.Store({ url: 'movies-paged.php', reader: new Ext.data.JsonReader({ root:'rows', totalProperty: 'results', id:'id' }, [ // data column model removed for readability ]) });
Grouping Grouping grids are used to provide a visual indication that sets of rows are similar to each other. It also provides us with sorting that is confined to each group. So if we were to sort by the price column, the price would sort only within each group of items.
Grouping store A special store is required, which is called...wait for it...the GroupingStore...tada! The setup is similar to a standard store. We just need to provide a few more configuration options, such as the sortInfo and the groupField. No changes to the actual data are needed because Ext JS takes care of grouping on the client side. var store = new Ext.data.GroupingStore({ url: 'movies.json', sortInfo: { field: 'genre', direction: "ASC" }, groupField: 'genre', reader: new Ext.data.JsonReader({
[ 98 ]
Chapter 5 root:'rows', id:'id' }, // reader column model here //) });
We also need to add a view configuration to the grid panel. This view helps the grid to visually account for grouped data. var grid = new Ext.grid.GridPanel({ renderTo: document.body, frame:true, title: 'Movie Database', height:400, width:520, store: store, autoExpandColumn: 'title', columns: // column model goes here //, view: new Ext.grid.GroupingView() });
After making the changes needed for a grouping grid, we end up with something that looks like this:
[ 99 ]
Displaying Data With Grids
If you now expand the context menu for the column headings, you will see a new item in the menu for Group By This Field that will allow the user to change the grouping column on the fly.
Summary We have learned a lot in this chapter about presenting data in a grid. With this new-found knowledge we will be able to organize massive amounts of data into easy to understand grids. Specifically, we covered: •
Creating data stores and grids for display
•
Reading XML and JSON data from a server and displaying it in a grid
•
Rendering cells of data for a well formatted display
•
Altering the grid based on user interaction
We also discussed the intricacies of each of these elements, such as reading data locally or from a server—along with paging. We also covered formatting cells using HTML, images, and even lookups into separate data stores. Now that we've learned about standard grids, we're ready to take it to the next level, by making our grid cells editable just like a spreadsheet—which is the topic of the next chapter.
[ 100 ]
Editor Grids In the previous chapter we learned how to display data in a structured grid that users could manipulate. But one major limitation was that there was no way for the users to edit the data in the grid in-line. Fortunately, Ext provides an EditorGridPanel, which allows the use of form field type editing in-line—and we will learn about it in this chapter. This works much like Excel, allowing the user to click on and edit cell data immediately. In this chapter you will learn to: •
Present the user with editable grids that are connected to a data store
•
Send edited data back to the server, enabling users to update server-side databases using the Ext JS editor grid
•
Manipulate the grid from program code, and respond to events
•
Use tricks for advanced formatting and creating more powerful editing grids
But first, let's see what you can do with an editable grid.
What can I do with an editable grid? The EditorGridPanel is very similar to the forms we were working with earlier. In fact, an editor grid uses the exact same form fields as our form. By using form fields to perform the grid cell editing we get to take advantage of the same functionality that a form field provides. This includes restricting input, and validating values. Combine this with the power of an Ext GridPanel, and we are left with a widget that can do pretty much whatever we want.
Editor Grids
All of the fields in this table can be edited in-line using form fields such as the text field, date picker, and combo box.
Working with editable grids The change from a non-editable grid to an editable grid is quite a simple process to start with. The complexity comes into the picture when we start to create a process to handle edits and send that data back to the server. But once you learn how to do it, that part can be quite simple as well. Let's see how you would update the grid we created at the start of Chapter 5 to make the title, director, and tagline editable. Here's what the modified code will look like: var title_edit = new Ext.form.TextField(); var director_edit = new Ext.form.TextField({vtype: 'name'}); var tagline_edit = new Ext.form.TextField({ maxLength: 45 }); var grid = new Ext.grid.EditorGridPanel({ renderTo: document.body, frame:true, title: 'Movie Database', height:200, width:520, clickstoEdit: 1, store: store, columns: [ {header: "Title", dataIndex: 'title', [ 102 ]
Chapter 6 editor: title_edit}, {header: "Director", dataIndex: 'director', editor: director_edit}, {header: "Released", dataIndex: 'released', renderer: Ext.util.Format.dateRenderer('m/d/Y')}, {header: "Genre", dataIndex: 'genre', renderer: genre_name}, {header: "Tagline", dataIndex: 'tagline', editor: tagline_edit} ] });
There are four main things that we need to do to make our grid editable. These are: •
The grid definition changes from being Ext.grid.GridPanel to Ext.grid. EditorGridPanel
•
We add the clicksToEdit option to the grid config—this option is not required, but defaults to two clicks
•
Create a form field for each column that we would like to be editable
•
Pass the form fields into our column model via the editor config
The editor can be any of the form field types that already exist in Ext JS, or a custom one of your own. We start by creating a text form field that will be used when editing the movie title. var title_edit = new Ext.form.TextField();
Then add this form field to the column model as the editor: {header: "Title", dataIndex: 'title', editor: title_edit}
The next step will be to change from using the GripPanel component to using the EditorGridPanel component, and to add the clicksToEdit config: var grid = new Ext.grid.EditorGridPanel({ renderTo: document.body, frame:true, title: 'Movie Database', height:200, width:520, clickstoEdit: 1, // removed extra code for clarity })
[ 103 ]
Editor Grids
Making these changes has turned our static grid into an editable grid. We can click on any of the fields that we set up editors for, and edit their values.
Here we see some changes have been made to the titles of a few of the movies, turning them into musicals. The editor gets activated with a single click on the cell of data; pressing Enter, the Tab key, or clicking away from the field will save the change, and pressing the Escape key will discard any changes. This works just like a form field, because, well… it is a form field. The little red tick that appears in the upper-left corner indicates that the cell is 'dirty', which we will cover in just a moment. First, let's make some more complex editable cells.
Editing more cells of data For our basic editor grid, we started by making a single column editable. To set up the editor, we created a reference to the form field: var title_edit = new Ext.form.TextField();
Then we used that form field as the editor for the column: {header: "Title", dataIndex: 'title', editor: title_edit}
That's the basic requirements for each field. Now let's expand upon this knowledge.
Edit more field types Now we are going to create editors for the other fields. Different data types have different editor fields and can have options specific to that field's needs.
[ 104 ]
Chapter 6
Any form field type can be used as an editor. These are some of the standard types: •
TextField
•
NumberField
•
ComboBox
•
DateField
•
TimeField
•
CheckBox
These editors can be extended to achieve special types of editing if needed, but for now, lets start with editing the other fields we have in our grid—the release date and the genre.
Editing a date value A DateField will work perfectly for editing the release date column in our grid. So let's use that. We first need to set up the editor field and specify which format to use: release_edit = new Ext.form.DateField({ format: 'm/d/Y' });
Then we apply that editor to the column, along with the renderer that we used earlier: {header: "Released", dataIndex: 'released', renderer: Ext.util.Format.dateRenderer('m/d/Y'), editor: release_edit}
[ 105 ]
Editor Grids
This column also takes advantage of a renderer, which will co-exist with the editor. Once the editor field is activated with a single click, the renderer passes the rendering of the field to the editor and vice versa. So when we are done with editing the field, the renderer will take over formatting the field again.
Edit with a ComboBox Let's set up an editor for the genres column that will provide us with a list of the valid genres to select from—sounds like a perfect scenario for a combo box. var genre_edit = new Ext.form.ComboBox({ typeAhead: true, triggerAction: 'all', mode: 'local', store: genres, displayField:'genre', valueField: 'id' });
Simply add this editor to the column model, like we did with the others: {header: "Genre", dataIndex: 'genre', renderer: genre_name, editor: genre_edit}
Now we end up with an editable field that has a fixed selection of options.
Reacting to a cell edit Of course, we now need to figure out how to save all of this editing that we have been doing. I am sure the end user would not be so happy if we threw away all of their changes. We can start the process of saving the changes by listening for particular edit events, and then reacting to those with our own custom handler. Before we start coding this, we need to understand a bit more about how the editor grid works. [ 106 ]
Chapter 6
What's a dirty cell? A field that has been edited and has had its value changed is considered to be 'dirty' until the data store is told otherwise. This 'dirty' value has been saved to a temporary data store that contains a version of our data with all of the changes made. Our original data store stays unchanged. We can save the changes to the primary data store by calling the commit function, or we can discard the changes by calling the reject handler. These handlers can be called for the entire grid, for a single cell of data, or as the result of an event that is happening. Let's imagine e is an edit event object. We could reject a record by calling the reject handler: e.record.reject();
Alternatively, we can save our change by committing it: e.record.commit();
Reacting when an edit occurs To save our changes to the data store, we are going to listen for an edit being completed, which is accomplished by listening for the afteredit event. The listener we need is added to the grid panel: var grid = new Ext.grid.EditorGridPanel({ // more config options clipped //, title: 'Movie Database', store: store, columns: // column model clipped //, listeners: { afteredit: function(e){ if (e.field == 'director' && e.value == 'Mel Gibson'){ Ext.Msg.alert('Error','Mel Gibson movies not allowed'); e.record.reject(); }else{ e.record.commit(); } } } });
As with other listeners in Ext, the editor grid listeners are given a function to execute when the event occurs. The function for afteredit is called with a single argument: an object, which has a number of useful properties. We can use these properties to make a decision about the edit that just happened. [ 107 ]
Editor Grids
Property
Description
grid
The grid that the edit event happened in
record
The entire record that's being edited; other column values can be retrieved using this objects 'data' property
field
The name of the column that was edited
value
A string containing the new value of the cell
originalValue
A string containing the original value of the cell
row
The index of the row that was edited
column
The index of the column that was edited
For instance, if we wanted to make sure that movies directed by Mel Gibson never made it into our database, we could put a simple check in place for that scenario: if (e.field == 'director' && e.value == 'Mel Gibson'){ Ext.Msg.alert('Error','Mel Gibson movies not allowed'); e.record.reject(); }else{ e.record.commit(); }
First, we check to see that the director field is the one being edited. Next, we make sure the new value entered for this field is not equal to Mel Gibson. If either of these is false, we commit the record back to the data store. This means that once we call the commit handler, our primary data store is updated with the new value. e.record.commit();
We also have the ability to reject the change—sending the changed value into the black hole of space, lost forever. e.record.reject();
Of course, all we have done so far is update the data that is stored in the browsers' memory. I'm sure you're just dying to be able to update a web server. We will get to that soon enough.
Deleting and adding in the data store We are going to create two buttons to allow us to alter the data store—to add or remove rows of data. Let us set up a top toolbar (tbar) in the grid to house these buttons: var grid = new Ext.grid.EditorGridPanel({ // more config options clipped //, [ 108 ]
Chapter 6 tbar: [{ text: 'Remove Movie' }] }
Removing grid rows from the data store Let's add a remove button to the toolbar in our grid. When this button is clicked, it will prompt the user with a dialog that displays the movie title. If the Yes button is clicked, then we can remove the selected row from the data store, otherwise we will do nothing. { text: 'Remove Movie', icon: 'images/table_delete.png', cls: 'x-btn-text-icon', handler: function() { var sm = grid.getSelectionModel(); var sel = sm.getSelected(); if (sm.hasSelection()){ Ext.Msg.show({ title: 'Remove Movie', buttons: Ext.MessageBox.YESNOCANCEL, msg: 'Remove '+sel.data.title+'?', fn: function(btn){ if (btn == 'yes'){ grid.getStore().remove(sel); } } }); }; } }
[ 109 ]
Editor Grids
Let's take a look at what is happening here. We have defined some variables that we will use to determine if there were selections made, and what the selections were: •
sm: The selection model is retrieved from our grid
•
sel: We used the selection model to retrieve the row that has been selected
•
grid.getStore().remove(sel): Passing the data stores remove function,
a row will remove that row from the store and update the grid It's as simple as that. The local data store that resides in the browser's memory has been updated. But what good is deleting if you can't add anything—just be patient, grasshopper!
Adding a row to the grid To add a row we have a bit of a twist. We need to have a definition of what the data looks like in order to be able to create a new row—just like it was created for the data reader. var ds_model = Ext.data.Record.create([ 'id', 'coverthumb', 'title', 'director', {name: 'released', type: 'date', dateFormat: 'Y-m-d'}, 'genre', 'tagline', {name: 'price', type: 'float'}, {name: 'available', type: 'bool'} ]);
Once we have this definition of the data, we can insert a new row fairly easily. A button can be added to the same top toolbar that will be used to insert the row: { text: 'Add Movie', icon: 'images/table_add.png', cls: 'x-btn-text-icon', handler: function() { grid.getStore().insert( 0, new ds_model({ title:'New Movie', director:'', genre:0, tagline:''
[ 110 ]
Chapter 6 }) ); grid.startEditing(0,0); } }
The first argument to the insert function is the point at which to insert the record. I have chosen zero, so the record will be inserted at the very top. If we wanted to insert the row at the end we could simply retrieve the row count for our data store. Because the row index starts at zero, and the count at one, incrementing the count is not necessary because the row count will always be one greater than the last item in the index. grid.getStore().insert( grid.getStore().getCount(), new ds_model({ title:'New Movie', director:'', genre:0, tagline:'' }) ); grid.startEditing(grid.getStore().getCount()-1,0);
Now we are back to inserting a row. The second argument is the new record definition, which can be passed with some default values. new ds_model({ title:'New Movie', director:'', genre:0, tagline:'' }) [ 111 ]
Editor Grids
After inserting the new row, we call a function that will activate a cells editor. This function just needs a row and column index number to activate the editor for that cell. grid.startEditing(0,0);
This gives our user the ability to start typing the movie title directly after clicking the Add Movie button.
Saving edited data to the server Everything we have done so far is related to updating the local data store residing in the memory of the web browser. More often that not, we will want to save our data back to the server to update a database, file system, or something along those lines. This section will cover some of the more common requirements of grids used in web applications to update server-side information. •
Updating a record
•
Creating a new record
•
Deleting a record
Sending updates back to the server Earlier, we had set up a listener for the afteredit event. We will be using this afteredit event to send changes back to the server on a cell-by-cell basis.
[ 112 ]
Chapter 6
To update the database with cell-by-cell changes, we need to know three things: •
field: What field has changed
•
Value: What the new value of the field is
•
record.id: Which row from the database the field belongs to
This gives us enough information to be able to make a distinct update to a database. We communicate with the server (using AJAX) by calling the connection request method. listeners: { afteredit: function(e){ var conn = new Ext.data.Connection(); conn.request({ url: 'movie-update.php', params: { action: 'update', id: e.record.id, field: e.field, value: e.value }, success: function(resp,opt) { e.commit(); }, failure: function(resp,opt) { e.reject(); } }); } }
This will send a request to the movie-update.php script with four parameters in the form of post headers. The params that we pass as a config object into the data connection are all sent through the headers to our script on the server side. The movie-update.php script should be coded to recognize the 'update' action and then read in the id, field, and value data and then proceed to update the file system or database, or whatever else we need to make it happen.
[ 113 ]
Editor Grids
This is what's available to us when using the afteredit event: Option
Description
grid
Reference to the current grid
record
Object with data from the row being edited
field
Name of the field being edited
value
New value entered into the field
originalValue
Original value of the field
row
Index of the row being edited—this will help in finding it again
column
Index of the column being edited
Deleting data from the server When we want to delete data from the server, we can handle it in very much the same way as an update—by making a call to a script on the server, and telling it what we want done. For the delete trigger, we will use another button in the grids toolbar. { text: 'Remove Movie', icon: 'images/table_delete.png', cls: 'x-btn-text-icon', handler: function() { var sm = grid.getSelectionModel(); var sel = sm.getSelected(); if (sm.hasSelection()){ Ext.Msg.show({ title: 'Remove Movie', buttons: Ext.MessageBox.YESNOCANCEL, msg: 'Remove '+sel.data.title+'?', fn: function(btn){ if (btn == 'yes'){ var conn = new Ext.data.Connection(); conn.request({ url: 'movie-update.php', params: { action: 'delete', id: e.record.id }, success: function(resp,opt) { grid.getStore().remove(sel); [ 114 ]
Chapter 6 }, failure: function(resp,opt) { Ext.Msg.alert('Error', 'Unable to delete movie'); } }); } } }); }; } }
Just as with edit, we are going to make a request to the server to have the row deleted. The movie-update.php script would see that the action is delete and execute the appropriate action.
Saving new rows to the server Here we are going to add another button that will add a new row. It sends the request to the server with the appropriate parameters and reads the insert id from the server's response. Using this insert id, we are able to add the row to our data store with the unique identifier generated on the server side for that row. { text: 'Add Movie', icon: 'images/table_add.png', cls: 'x-btn-text-icon', handler: function() { var conn = new Ext.data.Connection(); conn.request({ url: 'movies-update.php', params: { action: 'insert', title:'New Movie' }, success: function(resp,opt) { var insert_id = Ext.util.JSON.decode( resp.responseText ).insert_id; grid.getStore().insert(0, new ds_model({ id:insert_id, title:'New Movie', director:'', genre:0, tagline:'' [ 115 ]
Editor Grids }) ); grid.startEditing(0,0); }, failure: function(resp,opt) { Ext.Msg.alert('Error','Unable to add movie'); } }); } }
Much like editing and deleting, we are going to send a request to the server to have a new row inserted. This time, we are actually going to take a look at the response to get the insert id (the unique identifier for that row) to pass back to our grid so that when we start editing that row, it will be easy to save our changes. success: function(resp,opt) { var insert_id = Ext.util.JSON.decode( resp.responseText ).insert_id; grid.getStore().insert(0, new ds_model({ id:insert_id, title:'New Movie', director:'', genre:0, tagline:'' }) ); grid.startEditing(0,0); }
Our success handler function has a couple of arguments, the first is the response object, which contains the response text from our movie-update.php script. Because that response is in a JSON format, we're going to decode it into a usable object and grab the insert id value. var insert_id = Ext.util.JSON.decode( resp.responseText ).insert_id;
When we insert this row into our data store, we can use the insert id that was retrieved.
[ 116 ]
Chapter 6
Summary The Ext JS grid functionality is one of the most advanced portions of the framework. With the backing of the Ext.data package, the grid can pull information from a remote server in an integrated manner—this support is built into the grid class. Thanks to the array of configuration options available, we can present this data easily in a variety of forms, and set it up for manipulation by our users. In this chapter, we've seen how the data support provided by the grid offers an approach to data manipulating that will be familiar to many developers. The amend-and commit approach allows fine-grained control over the data that is sent to the server when used with a validation policy, along with the ability to reject changes. As well as amending the starting data, we've seen how the grid provides functionality to add and remove rows of data. We've also shown how standard Ext JS form fields such as the ComboBox can be integrated to provide a user interface on top of this functionality. With such strong support for data entry, the grid package provides a very powerful tool for application builders. In the next chapter, we'll demonstrate how components such as the grid can be integrated with other parts of an application screen by using the extensive layout functionality provided by the Ext JS framework.
[ 117 ]
Layouts A layout turns your forms, grids, and other widgets into a true web application. The most widely-used layout styles can be found in operating systems such as Microsoft's Windows, which uses border layouts, resizable regions, accordions, tabs, and just about everything else you can think of. To keep looks consistent between browsers, and to provide common user interface features, Ext JS has a powerful layout management system. The sections are manageable, and can be moved or hidden, and they can appear at the click k of a button, right when and where you need them to. In this chapter you will learn to: • • • •
Lay out an application style screen Create tabbed sections Manage Ext widgets within a layout Learn advanced and nested layouts
What are layouts, regions, and viewports? Ext uses Panels, which are the basis of most layouts. We have used some of these, such as FormPanel and GridPanel, already. A viewport is a special panel-like component that encloses the entire layout, fitting it into the whole visible area of our browser. For our first example, we are going to use a viewport with a border layout that will encapsulate many panels.
Layouts
A viewport has regions that are laid out in the same way as a compass, with North, South, East and West regions—the Center region represents what's left over in the middle. These directions tell the panels where to align themselves within the viewport and, if you use them, where the resizable borders are to be placed:
North
West
Center
East
South
The example we're creating will look like the following image, and combines many of the previous examples we have created:
[ 120 ]
Chapter 7
This layout is what's called a 'border' layout, which means that each region is separated by a somewhat three dimensional border bar that can be dragged to resize the regions. This example contains four panel regions: • • • •
North: The toolbar West: A form Center: Grid in a tab panel East: A plain panel containing text
Note that there is no 'South' panel in this example—not every region needs to be used in every layout.
Our first layout Before we create our layout that uses only four regions let's go ahead and create a layout that utilizes all the regions, and then remove the South panel. We are going to create all of the regions as 'panels', which can be thought of as blank canvases to which we will add text, HTML, images, or even Ext JS widgets. var viewport = new Ext.Viewport({ layout: 'border', renderTo: Ext.getBody(), items: [{ region: 'north', xtype: 'panel', html: 'North' },{ region: 'west', xtype: 'panel', split: true, width: 200, html: 'West' },{ region: 'center', xtype: 'panel', html: 'Center' },{ region: 'east', xtype: 'panel', split: true, width: 200, html: 'East' },{ region: 'south', xtype: 'panel', html: 'South' }] }); [ 121 ]
Layouts
Each region is defined as one of the four compass directions—East, West, North, and South. The remainder in the middle is called the center region, which will expand to fill all of the remaining space. Just to take up some blank space in each region and to give a visual indicator as to where the panels are, we defined an 'HTML' config that has just text. (This could also contain complex HTML if needed, but there are better ways to set the contents of panels which we will learn about soon.)
Ext JS provides an easy, cross-browser compatible, speedy way to get a reference to the body element, by using Ext.getBody().
If everything works out ok, you should see a browser that looks like this:
Now we have a layout with all five regions defined. These regions can have other text widgets added into them, seamlessly, by using the xtype config. Alternatively they can be divided up separately into more nested regions—for instance, the center could be split horizontally to have its own South section. A 'Center' region must always be defined. If one is not defined, the layout will produce errors and appear as a jumbled set of boxes in the browser.
Splitting the regions The dividers are set up for each panel by setting the split flag—the positioning of the dividers is determined automatically based on the region the panel is in. split: true
For this page, we have set the West and East regions as 'split' regions. This, by default, makes the border into a resizing element for the user to change the size of that panel.
[ 122 ]
Chapter 7
I want options Typically, when a split is used, it's combined with a few other options that make the section more useful, such as width, minSize, and collapseMode. Here are some of the more commonly-used options: Option split
Value
Description
true/false
Boolean value that places a resizable bar between the sections
collapsible
true/false
Boolean value that adds a button to the title bar which lets the user collapse the region with a single click
collapseMode
When set to 'mini', this adds a smaller collapse button that's located on the divider bar, in addition to the larger collapse button on title bar; the panel also collapses into a smaller space
title
Only option is mini mode, or undefined for normal mode String
bodyStyle
CSS
CSS styles applied to the body element of the panel.
minSize
Pixels, ie: 200
The smallest size that the user can drag this panel to
maxSize
Pixels, ie: 250
The largest size that the user can drag this panel to
margins
In pixels: top, right, bottom, left, i.e.,: 3 0 3 3
Can be used to space the panel away from the edges or away from other panels; spacing is applied outside of the body of the panel
cmargins
In pixels: top, right, bottom, left, i.e.,: 3 0 3 3
Same idea as margins, but applies only when the panel is collapsed
Title string placed in the title bar
Let's add a couple of these options to our west panel: { region: 'west', xtype: 'panel', split: true, collapsible: true, collapseMode: 'mini', title: 'Some Info', bodyStyle:'padding:5px;', width: 200, minSize: 200, html: 'West' } [ 123 ]
Layouts
Adding these config options to our west panel would give us the following look: Normal/Expanded
Collapsed
Expanding and collapsing a panel that does not have a width specified can produce rendering problems. Therefore, it's best to specify a width for panels—of course this is not needed for the center, as this panel automatically fills the remaining space.
Tab panels With Ext JS, tab panels are also referred to as a "card" layout because they work much like a deck of cards where each card is layered directly above or below the others and can be moved to the top of the deck, to be visible. We also get pretty much the same functionality in our tab panel as a regular panel, including a title, toolbars, and all the other usual suspects (excluding tools).
Adding a tab panel If the Ext JS component is a panel type component,, for instance GridPanel and FormPanel, then we can add it directly to the layout using its xtype. Let's start by creating a tabPanel: { region: 'center', xtype: 'tabpanel', items: [{ title: 'Movie Grid', html: 'Center' }] } [ 124 ]
Chapter 7
The items config is an array of objects that defines each of the tabs contained in this tabpanel. The title is the only option that's actually needed to give us a tab, and right now html is just being used as a placeholder, to give our empty tab some content. We will also need to add an activeTab config that is set to zero to our tab panel. This is the index of the tabs in the panel left to right starting with zero and counting up for each tab. This tells the tab panel at position zero to make itself active by default, otherwise, we would have no tabs displayed, resulting in a blank section until the user clicked a tab. { region: 'center', xtype: 'tabpanel', activeTab: 0, items: [{ title: 'Movie Grid', html: 'Center' }] }
If we take a look at this in a browser, we should see a tab panel in the center section of our layout.
Adding more tabs is as easy as adding more items into the items array. Each tab item is basically its own panel, which is shown or hidden, based on the tab title that has been clicked on the tab panel. { region: 'center', xtype: 'tabpanel', activeTab: 0, items: [{ title: 'Movie Grid',
[ 125 ]
Layouts html: 'Center' },{ title: 'Movie Descriptions', html: 'Movie Info' }] }
Both the Movie Grid and Movie Descriptions tabs are just plain panels right now. So let's add some more configuration options and widgets to them.
Widgets everywhere Earlier, I mentioned that any type of panel widget could be added directly to a layout, just as we had done with the tabs. Let's explore this by adding another widget to our layout—the grid.
Adding a grid into the tabpanel As we now have these tabs as part of our layout, let's start by adding a grid panel to one of the tabs. Adding the xtype config option to the grid config code you created in Chapter 5, will produce a grid that fills one entire tab: { region: 'center', xtype: 'tabpanel', activeTab: 0, items: [{ title: 'Movie Grid', xtype: 'gridpanel', store: store, autoExpandColumn: 'title', columns: // add column model //, view: // add grid view spec // },{ title: 'Movie Descriptions', html: 'Movie Info' }] }
[ 126 ]
Chapter 7
xtypes offer a quick way to instantiate a new component with minimal typing. This is sometimes referred to as 'lazy rendering' because the components sit around waiting to be displayed before they actually execute any code. This method can help conserve memory in your web application.
As we are adding this grid to a tab—which is essentially just a panel—there are some things that we no longer need (like the renderTo option, width, height, and a frame). The size, title, and border for the grid are now handled by our tab panel. Now we should have a layout that looks like this:
[ 127 ]
Layouts
Accordions The accordion is a very useful layout that works somewhat like a tab panel, where we have multiple sections occupying the same space, with only one showing at a time. This type of layout is commonly used when we're lacking the horizontal space needed for a tab panel, but instead have more vertical space available. When one of the accordion panels is expanded, the others will collapse. Expanding and collapsing the panels can be done either by clicking the panel's title bar or by clicking the plus/ minus icons along the rightmost side of the panel.
Nesting an accordion layout in a tab We can nest a layout within a panel to create a more complex layout. For this example, we will nest an accordion panel within one of our tabs. By setting the layout to 'accordion' and adding three items, we will end up with three panels in our accordion. { title: 'Movie Descriptions', layout: 'accordion', items: [{ title: 'Office Space', autoLoad: 'html/1.txt' },{ title: 'Super Troopers', autoLoad: 'html/3.txt' },{ title: 'American Beauty', autoLoad: 'html/4.txt' }] }
This gives us a tab that has within it three accordion panels, which will load text files into their body sections. Note that the config on this is very similar to a tab panel— the consistency between widgets in Ext JS makes it easy to set up different types of widgets without having to look at the API reference for each one.
[ 128 ]
Chapter 7
Now we should have a layout that looks like, this when we switch to the Movie Descriptions tab:
Each panel now has a description of the movie, which was loaded from a text file on the web server. Let's take a closer look at that. autoLoad: 'html/1.txt'
This loads the file from the URL specified into the body section of the panel. The file loaded can contain any type of HTML, which will show up just as if it were in a browser by itself. As this is done via AJAX, if you're running the examples from your file system instead of a local web server they will not load. Note that the JavaScript contained within the loaded content will not be executed and any HTML will be ignored.
Placing a toolbar in your layout Next, let's add a toolbar to the North section of our layout. We can use the toolbar for menus, buttons, and a couple of form fields, or maybe just a blinking marquee with our name scrolling across it. We can change these easily later. Let's take our toolbar items we used in the example code in Chapter 4—Buttons, Menus, and Toolbar—and add them to this toolbar. We should also copy over the Movies class we created in the toolbar chapter, if we want the buttons to work. [ 129 ]
Layouts
By changing the xtype to toolbar and copying the array of toolbar items over, we will end up with a snazzy menu bar at the top of the screen. { region: "north", xtype: 'toolbar', items: [{ xtype: 'tbspacer' },{ xtype: 'tbbutton', text: 'Button', handler: function(btn){ btn.disable(); } },{ xtype: 'tbfill' }, // more toolbar items here //] }
This gives us a toolbar that fits nicely into the top of our layout—like an application toolbar or menu bar that you would typically see in desktop applications. You should end up with something that looks like this:
[ 130 ]
Chapter 7
Even though this toolbar does not contain a blinking marquee with my name in it, it will be quite useful for our application. All of the navigation for your application can be placed in it, which might trigger new tabs to be added to the center region, or be used used to search for movie titles, or whatever else that is needed.
A form to add new movies A form panel will fit nicely into that currently-empty West region, and since it's a panel type component, it can be added directly to our layout. Let's add the movie form that we used in the forms chapter to the West section. But instead of instantiating it, let's use the xtype config to perform lazy instantiation for the entire form panel. { region: 'west', xtype: 'form', items: // form fields // buttons: // form panel buttons // }
The items config holds all of our form fields: items: [{ xtype: 'textfield', fieldLabel: 'Director', name: 'director', anchor: '100%', vtype: 'name' },{ xtype: 'datefield', fieldLabel: 'Released', name: 'released', disabledDays: [1,2,3,4,5] },{ xtype: 'radio', fieldLabel: 'Filmed In', name: 'filmed_in', boxLabel: 'Color' }, // more fields go here //]
Many xtypes exist, and the names are not exactly what you would guess—for a full reference, look under the Component section in the API reference.
[ 131 ]
Layouts
After adding the form items and buttons, our layout should look like this:
Tricks and advanced layouts Some of the more complex web applications will need to do things that are not as simple as setting a few configuration values, for example nesting one layout within another layout, or adding icons to your tabs. But with Ext JS these kinds of things are made easy.
Nested layouts When we nest one layout within another layouts region, we will occupy that entire region's body so it cannot be used any more. Instead, the nested layout regions are used for content.
[ 132 ]
Chapter 7
For example, if we wanted the center region split into two horizontal regions, we could add a nested layout with center and North regions. This is typical of an application where you have a data panel (Center) to list email messages and a reader panel (South) to preview the entire email when it's selected in the list in the North panel.
North North West
East Center
South
A couple of things are needed for a nested layout—the layout type must be set, and in this case, we are turning off the border so we don't get a doubled-up border, as the container has its own border. Each of the items represents one of our nested regions: { title: 'Nested Layout', layout: 'border', border: false, items: [{ region: 'north', height: 100, split: true, html: 'Nested North' },{ region: 'center', html: 'Nested Center' }] }
[ 133 ]
Layouts
This produces a layout that would look like this:
Percentages are not allowed in border type layouts. There must always be a 'center' region that fills the remaining space not taken up by the other regions', which are all defined in pixel sizes.
Icons in tabs Don't you just love it when you can have a visual indicator to show which tab does what? That's where icons come into play. The icons on tabs work much like the icons we added to the buttons in an earlier chapter. All we need to do is create a style with the icon and add that style to our tabs configuration. The style would look like this: bomb { background-image:url(images/bomb.png) !important; }
The tab config will need to have the iconCls property set to the style we've just created: { title: 'The Bomb', iconCls: 'bomb', html: 'Boom!' } [ 134 ]
Chapter 7
Don't click on that tab too quickly, it might go off!
Programmatically manipulating a layout We have the ability to modify just about anything, after the layout has been rendered. For example, we can, like add new tabs, hide, and display panels and change the content of any of the panels. Let's experiment with a couple of different things we can do.
Now you see me, now you don't Expanding and collapsing sections of your layout programmatically is a requirement in most applications. So it should be no surprise by now that this can be done in Ext JS as well. The first thing we need to do is give our panel and viewport ids, so that we can locate them. We can do this by setting the id config option in our layout and panel configurations: var viewport = new Ext.Viewport({ layout: 'border', id: 'movieview', renderTo: document.body, items: [{ // extra code removed // region: 'east', xtype: 'panel', id: 'moreinfo' // extra code removed // } });
Now that both the layout and the panel have been given unique ids, we can use the ids to interact with these components by using getCmp. var moreinfo = Ext.getCmp('movieview').findById('moreinfo'); if (!moreinfo.isVisible()){ moreinfo.expand(); } [ 135 ]
Layouts
This little bit of code will check to see if the panel is visible (expanded), and if its not will expand it.
Give me another tab Adding a tab is as easy as creating a single tab. We first need to locate the tab panel within our layout. Luckily we just need to add an id config to our tab panel so that we can easily locate it. { region: 'center', xtype: 'tabpanel', id: ' movietabs', activeTab: 0, items: [{ title: 'Movie Grid', // extra code removed // },{ title: 'Movie Descriptions', html: 'Movie Info' }] }
Then we can call the add handler for our tab panel and pass a basic config into it: Ext.getCmp('movieview').findById('movietabs').add({ title: 'Office Space', html: 'Movie Info' });
This will add a tab that is titled Office Space to the movietabs tab panel. The add function is a way to add new items to a layout or a widget. Typically, anything that you can pass into the items config of a component can also be passed to the add handler.
Summary In this chapter, we have been able to use many of the components outlined in the previous chapters, within a layout. The layout really takes the various components of Ext JS and uses them to create a true web application. We have seen that the layout can integrate the different components of Ext JS into one fluid application. We also learned how to change the state of panels, create nested layouts, and load content dynamically. [ 136 ]
Ext JS Does Grow on Trees Hierarchical data is something that most developers are intimately familiar with. The root-branch-leaf structure is the underlying feature for many user interfaces, from the file and folder representations in Windows Explorer to the classic family tree showing children, parents, and grandparents. The Ext.tree package enables developers to bring these data structures to the user with only a few lines of code, and provides for a range of advanced cases with a number of simple configuration options. Although the default Ext JS icon set shows tree nodes as files and folders, it is not restricted to the file system concept. The icons and text of the items, or nodes in your tree, can be changed based on the dynamic or static data used to populate it—and without requiring custom code. How about a security screen showing permission groups containing a number of users, with icons showing a photo of each user, or a gallery showing groups of photos, or image previews in the icons? Ext JS's tree classes puts all of these scenarios within your grasp.
Planting for the future Ultimately, the Ext JS tree doesn't care about the data you're displaying, because it's flexible enough to deal with any scenario that you can come up with. Data can be instructed to load up-front, or in logical bursts, which can become a critical feature when you've got a lot of information to load. You can edit data directly in the tree, changing labels and item positions, or you can modify the appearance of the overall tree or of each individual node, all of which will contribute to a customized end-user experience. The Ext JS tree is built on top of the Component model, which underlies the whole Ext JS framework. This means that developers receive the benefits of working with the familiar Component system, that users get a consistent and integrated interface experience, and that you can be sure your tree will work seamlessly with the rest of your application.
Ext JS Does Grow On Trees
From tiny seeds... In this chapter, we'll see how you can build a tree from first principles with a minimal of code. We'll also discuss the unique data structure that is used to populate the tree, and the way in which clever use of that data can let you harness important configuration options. The Ext JS tree natively supports advanced features such as sorting, and drag-and-drop, so we'll be discussing those as well. But if you need a truly bespoke tree, we'll also explore the way in which configuration options, methods, and events can be overridden or augmented to provide it. The tree itself is created via the Ext.tree.TreePanel class, which in turn contains many Ext.tree.TreeNodes classes. These two classes are the core of the Ext JS tree support, and as such will be the main topics of discussion throughout this chapter. However there are a number of other relevant classes that we'll also cover. Here's the full list from the Ext.tree package: AsyncTreeNode
Allows TreeNode children to be loaded asynchronously
DefaultSelectionModel
Standard single-select for the TreePanel
MultiSelectionModel
Provides support for multiple node selection
RootTreeNodeUI
Specialized TreeNode for the root of TreePanel
TreeDragZone
Provides support for TreeNode dragging
TreeDropZone
Provides support for TreeNode dropping
TreeEditor
Allows node labels to be edited
TreeFilter
Filter support for TreePanel child nodes
TreeLoader
Populates a TreePanel from a specified URL
TreeNode
The main class representing a node within a TreePanel
TreeNodeUI
Provides the underlying interface for the TreeNode
TreePanel
A tree-like representation of data—the main tree class
TreeSorter
Supports sorting of nodes within a TreePanel
Ouch! Fortunately, you don't have to use all of them all at once. TreeNode and TreePanel provide the basics, and the rest of the classes are bolted on to provide extra functionality. We'll cover each of them in turn, discussing how they're used, and showing a few practical examples along the way.
Our first sapling By now, you're probably thinking of the various possibilities for the Ext JS tree, and want to get your hands dirty. Despite the fact that the Ext.tree classes are some of the most feature-rich available in the framework, you can still get everything up and running with only a few lines of code. [ 138 ]
Chapter 8
In the examples that follow, we'll assume that you have a blank-slate HTML page ready and waiting, with all of the Ext JS dependencies included. Most of the code we will use builds on what came before, to make sure that we're only working with bite-sized pieces. Bear this in mind when you look at them in isolation. It is best practice to put the JavaScript in a separate file and wrap it in an Ext. onReady call. However, you can also do it according to your individual coding style.
Preparing the ground First, we need to create a containing element on our HTML page. We will be rendering our TreePanel into this container. So we have to set it to the size we want our tree to be:
The JavaScript for the tree can be broken down into three parts. Firstly, we need to specify the manner in which it's going to be populated. The Ext.tree. TreeLoader class provides this functionality, and here we're going to use it in the simplest manner: var treeLoader = new Ext.tree.TreeLoader({ dataUrl:'http://localhost/samplejson.php' });
The dataUrl configuration parameter specifies the location of the script that is going to supply the JavaScript Object Notation (JSON) used to populate our tree. I'm not going to go into the details of the structure of JSON now; let's save that for later. Each tree requires a root node, which acts as a great-granddaddy for all of its descendants. To create that root node, we use the Ext.tree.AsyncTreeNode class: var rootNode = new Ext.tree.AsyncTreeNode({ text: 'Root' });
The reason we're using AsyncTreeNode, rather than the basic TreeNode that is also available, is because we're fetching our nodes from the server and are expecting child nodes to be populated branch-by-branch rather than all at once. This is the most typical scenario for a tree. AsyncTreeNode uses AJAX on-the-fly to ensure your users aren't waiting too long for your data to load and for the first nodes to be rendered.
[ 139 ]
Ext JS Does Grow On Trees
Finally, we create the tree itself, using the Ext.tree.TreePanel class: var tree = new Ext.tree.TreePanel({ renderTo:'treecontainer', loader: treeLoader, root: rootNode });
This is just a matter of passing the root node and the TreeLoader in as configuration options, as well as using the renderTo config to specify that we'd like the TreePanel rendered into our treeContainer element. Again, you should remember that you need to wrap all of this code in a call to Ext.onReady, to make sure that the DOM is available when our code runs.
A tree can't grow without data We've seen that it only takes eleven lines of code to create a tree interface using Ext JS. You can see an example of the finished product here:
I guess it doesn't look like much, but we've got quite a bit of functionality for our eleven lines of code. We've got a consistent and attractive look and feel, with asynchronous remote loading of child nodes. To be fair, it's not as simple as that, because we skimmed over a crucial part of building an Ext JS tree—the data.
[ 140 ]
Chapter 8
JSON The standard TreeLoader supports JSON in a specific format—an array of node definitions. Here's a cut-down example: [ { id: '1', text: 'No Children', leaf: true }, { id: '2', text: 'Has Children', children: [{ id: '3', text: 'Youngster', leaf: true }] } ]
The text property is the label of the node as it appears in the tree. The id property is used to uniquely identify each node, and will be used to determine which nodes are selected or expanded. Using the id property can make your life a whole lot easier if you're using some of the advanced features of the TreePanel, which we'll see later. The children property is optional. The leaf property can be thought of as marking a node as either a folder or a file. As a leaf, the file is contained within the folder. In the tree, leaf nodes will not be expandable and won't have the plus icon which identifies folders.
A quick word about ID By default, TreeNodes are assigned an automatically-generated ID, meaning that the ID configuration property is actually optional. The generated ID is a text string in the form ynode-xx, with xx being replaced by a number. IDs can be useful for retrieving a node you have previously referenced. However, it is quite likely that you'd want to assign the ID value yourself. Whenever you expand a node with children to trigger an asynchronous load of data from the server, your server script needs to know exactly which node was clicked in order to send its children back. By explicitly setting the ID, you'll find it a lot easier to match nodes with their actions when you're working with the server.
[ 141 ]
Ext JS Does Grow On Trees
Extra data Although the id, text, and leaf properties are the most commonly-used properties, the way in which they are populated by JSON isn't exclusive to them. In fact, any configuration property of a TreeNode can be initialised by JSON, which will prove to be a useful trick when we begin to explore the other features of the tree. You're also able to include application-specific data; perhaps your nodes are products and you want to hold the price of them. Any property that isn't recognized as a TreeNode config option will still be included on the TreeNode.attributes property for later access.
XML XML is not natively supported by the tree. However, it is possible to use Ext JS's data support to make this happen. Generally, using JSON will ease your pain, although some applications may use XML as their data transport. So it's worth discussing some general approaches. We can use Ext.data.HttpProxy to pull in the data, but we need to transform the XML as it is being read: var xmltree = new Ext.tree.TreePanel({el: 'treeContainer'}); var proxy = new Ext.data.HttpProxy({url: 'http://localhost:81/ext/treexml.php'}); proxy.load(null, { read: function(xmlDocument) { parseXmlAndCreateNodes(xmlDocument); } }, function(){ xmltree.render(); });
We create a new TreePanel and HttpProxy, and specify that when the proxy loads we want an Ext.data.Reader to handle the incoming XML data. We then tell the reader to pass the XML to parseXmlAndCreateNodes. In this function, you would create a root TreeNode and children based on the XML data, which is pretty straightforward given that HttpProxy is XML-aware and passes you a true XML document rather than a plain string. JavaScript is fully capable of handling XML data, although you may be more comfortable approaching it as you would approach traversing the DOM of an XHTML document. By navigating and reading the XML document you can build up a TreeNode hierarchy, incorporating XML attributes as extra data for each node, and using textnodes as the text label. Because you have access to the raw XML nodes in this manner, you have full control over the resultant tree structure and the TreeNodes that comprise it. [ 142 ]
Chapter 8
Tending your trees We're now going to discuss the main features that you can bolt on to your tree to make it a little bit more useful. Drag-and-drop, sorting, and node editing, are the kinds of things that lift the TreePanel from being a clever way of displaying data, to being a great way of manipulating it.
Drag and drop Ext JS takes care of all of the drag-and-drop UI for you when you're using a TreePanel. Just add enableDD: true to your configuration, and you'll be able to rearrange nodes with a drop target graphic, and add them to folders, with a green plus icon to indicate what you're about to do. The TreePanel doesn't care about just its own nodes. If you've got more than one TreePanel on the page, then you can happily drag-and-drop branches or leaves between them.
But that's only half the story. When you refresh your page, all of your rearranged nodes will be back to their starting positions. That's because the TreePanel doesn't automatically know how you want to persist your changes, and in order to educate it, we've got to hook into some events.
[ 143 ]
Ext JS Does Grow On Trees
The TreePanel's beforemovenode event fires at just the right time for us—after the mouse button is released to signify we want to do a drop, but before the TreePanel UI is updated to reflect that. We are most likely to add code such as the following to tell the server about node move events: tree.on('beforemovenode', function(tree, node, oldParent, newParent, index) { Ext.Ajax.request({ url: 'http://localhost/node-move.php', params: { nodeid: node.id, newparentid: newParent.id, oldparentid: oldParent.id, dropindex: index } }); });
Augmenting our previous code, we're adding a new event handler for the beforemovenode event. The handler function is called with a few useful arguments: 1.
tree: The TreePanel that raised the event
2.
node: The TreeNode being moved
3.
oldParent: The previous parent of the node being moved
4.
newParent: The new parent of the node being moved
5.
index: The numerical index where the node was dropped
We use these arguments to form the parameters of an AJAX call to the server. As you can pull out pretty much any information you need about the current state of the tree, your server-side script can perform any action that it needs to. In some cases, that could include canceling the move action. If the logic you place within the beforemovenode handler fails, you need to roll back your changes. If you're not doing an AJAX call, this is pretty straightforward—just return false at the end of the handler and the action will be canceled. For AJAX though, it's more difficult, because the XMLHttpRequest happens asynchronously, and the event handler will proceed with its default action, which is to allow the move. In these circumstances, you need to make sure that you provide a failure handler for your AJAX request, and pass enough information back to that failure handler to allow it to manually return the tree to its previous state. Because beforemovenode provides a lot of information through its arguments, you can pass the necessary data to take care of these error events. [ 144 ]
Chapter 8
Sorting We can sort nodes in a TreePanel in a very flexible manner by using the TreeSorter. Again, building on our previous code, we can create a TreeSorter such as this: new Ext.tree.TreeSorter(tree, { folderSort: true, dir: “asc" });
Because TreeSorter assumes a couple of defaults—specifically, that your leaf nodes are marked with a property called leaf and that your labels are in a property called text—we can perform an alphabetical sort very easily. The dir parameter tells the TreeSorter to sort in either ascending (with the asc value) or descending (desc) order, and the folderSort parameter indicates that it should sort leaf nodes that are within folders—in other words, the whole tree hierarchy. If you've got data that isn't simple text, you can specify a custom method of sorting with the sortType configuration option. sortType takes a function as its value, and that function will be called with one argument: a TreeNode. The purpose of the sortType function is to allow you to cast a custom property of the TreeNode—presumably something you've passed from the server and that is specific to your business needs—and convert it to a format that Ext JS can sort, in other words, one of the standard JavaScript types such as integer, string, or date. This feature can be useful in cases where data passed to the tree is in a format that isn't conducive to normal searching. Data generated by the server might serve multiple purposes, and hence may not always be right for a particular purpose. For example, we may need to convert dates into a standard format—from US style MM/DD/YY to YYYYMMDD format that is suitable for sorting—or maybe we need to strip extraneous characters from a monetary value so that it can be parsed as a decimal. sortType: function(node) { return node.attributes.creationDate }
In the above example, we return some custom data from our node, and because this value is a valid JavaScript date, Ext JS will be able to sort against it. This is a simple demonstration of how the sortType option can be used to allow the TreeSorter to work with any kind of server data.
[ 145 ]
Ext JS Does Grow On Trees
Editing There are many scenarios in which editing the value of your nodes could be useful. When viewing a hierarchy of categorized products, you may wish to rename either the categories or the products in-line, without navigating to another screen. We can enable this simple feature by using the Ext.tree.TreeEditor class: var editor = new Ext.tree.TreeEditor(tree);
The defaults of the TreeEditor mean that this will now give your tree nodes a TextField editor when you double-click on their label. However, as with basic drag-and-drop functionality, enabling this feature doesn't automatically mean that your changes will be persisted to the server. We need to handle the event that fires when you've finished editing the node: editor.on('beforecomplete', function(editor, newValue, originalValue) { // Possible Ajax call? });
The beforecomplete event handler gets called with three arguments: 1.
editor: The editor field used to edit the node
2.
newValue: The value that was entered
3.
originalValue: The value before you changed it
However, it is important to note that the editor parameter is no ordinary Ext.form.Field. It is augmented with extra properties, the most useful of which is editNode, a reference to the node that was edited. This allows you to get information such as the node ID, which would be essential in making a server-side call to synchronize the edited value in the database.
As with the TreePanel's beforemovenode event, beforecomplete allows cancellation of the edit action by returning False at the end of its handler processing; AJAX requests will need to provide a failure handler to manually restore the edited value. [ 146 ]
Chapter 8
This has been a quick overview of how to create a very simple in-line editor. There are also means of using this class to create more complicated features. The TreeEditor constructor can take up to two optional parameters on top of the single mandatory parameter shown in the example above. These are a field configuration object and a configuration object for the TreeEditor. The field config can be one of two things: a field config object to be applied to the standard TextField editor, or an already-created instance of a different form field. If it is the latter, it will be used instead of the default, which means that you can add NumberField, DateField or another Ext.form.Field in a similar manner. The second parameter allows you to configure the TreeEditor, and is more for fine-tuning rather than introducing any exciting functionality. For example, we can use cancelOnEsc to allow the user to cancel any editing by pressing the Escape key, or use ignoreNoChange to avoid firing completion events if a value has not changed after an edit.
Trimming and pruning There a few other tricks that the TreePanel supports, which assist in the creation of rich applications. Varying selection models, node filtering, and context menus are commonly-used features in many solutions. So let's take a look at these now.
Selection models In our previous example code, we dragged and edited TreeNodes to alter them immediately. But nodes can also be selected for further processing. The TreePanel uses a single-selection model by default. In our previous code, we've already done everything we need to enable node selection. As with many aspects of the tree, simply selecting the node doesn't do anything; instead we need to hook in to some of the features provided to manipulate the selection. A great example of this would be to select a node and have an information panel automatically populated with further details of that node. Perhaps you have a tree of named products, and clicking a node will display the price and stock level of the selected product. We can use the selectionchange event to make this happen. Again, using our previous code as a starting point, we could add the following: tree.selModel.on('selectionchange', function(selModel, node) { var price = node.attributes.price; });
The second node argument that is passed to the selectionchange event makes it very easy to grab any custom attributes in your node data. [ 147 ]
Ext JS Does Grow On Trees
What if we want to allow multiple nodes to be selected? How can we do that, and how can we handle the selectionchange event in that configuration? We can use Ext.tree.MultiSelectionModel when creating our TreePanel: var tree = new Ext.tree.TreePanel({ renderTo:'treeContainer', loader: treeLoader, root: rootNode, selModel: new Ext.tree.MultiSelectionModel() });
Configuration is as simple as that. Although handling the selectionchange event is very similar to the default selection model, there is an important difference. The second argument to the event handler will be an array of nodes rather than a single node.
Selection models don't just expose the means of retrieving selection information. They also allow manipulation of the current selection. For example, the MultiSelectionModel.clearSelections() method is useful for wiping the slate clean after you have finished handling an event involving multiple nodes. DefaultSelectionModel has methods (selectNext and selectPrevious) for navigating the tree, moving up or down the node hierarchy as required.
Round-up with context menus We've already covered a lot of the functionality that the TreePanel can provide, so let's consolidate a little bit with a practical example. Adding a context menu that appears when you right-click a TreeNode is a trivial task with Ext JS. However, it can be an extremely useful means of adding shortcuts to your interface. We'll be building on the code that has been used in the previous sections. First, let's create the menu, and then we'll hook it up to the TreePanel: var contextMenu = new Ext.menu.Menu({ items: [ { text: 'Delete', handler: deleteHandler }, [ 148 ]
Chapter 8 { text: 'Sort', handler: sortHandler } ] }); tree.on('contextmenu', treeContextHandler);
The TreePanel provides a contextmenu event which fires when the user right-clicks on a node. Note that our listeners are not anonymous functions as they have been in the previous examples—instead they have been split off for easy reading. First, the treeContextHandler that handles the contextmenu event: function treeContextHandler(node) { node.select(); contextMenu.show(node.ui.getAnchor()); }
The handler gets called with a node argument, so we need to select the node to allow us to act upon it later. We then pop up the context menu by calling the show method with a single parameter that tells the pop-up menu where to align itself—in this case it's the text of the TreeNode we've clicked on.
Handling the menu We've got two context menu entries—Delete and Sort. Let's first take a look at the handler for Delete: function deleteHandler() { tree.getSelectionModel().getSelectedNode().remove(); }
Using our previous knowledge of selection models, we get the node that we selected in the treeContextHandler, and simply call its remove method. This will delete the node and all of its children from the TreePanel. Note that we're not dealing with persisting this change to the server, but if this was something that you needed to do, TreePanel has a remove event that you could use a handler for to provide that functionality. [ 149 ]
Ext JS Does Grow On Trees
The handler for our Sort menu entry is given here: function sortHandler() { tree.getSelectionModel().getSelectedNode().sort( function (leftNode, rightNode) { return (leftNode.text.toUpperCase() < rightNode.text. toUpperCase() ? 1 : -1); } ); }
Again, we use the selection model to get the selected node. Ext JS provides a sort method on the TreeNode that takes a function as its first parameter. This function gets called with two arguments: the two nodes to compare. In this example, we are sorting by the node's text property in descending order, but you can sort by any custom node attribute you like. You can use this sorting method in conjunction with a TreeSorter without issues. That's because TreeSorter only monitors the beforechildrenrendered, append, insert, and textchange events on the TreePanel. Any other changes will be unaffected.
The Delete context menu action will completely remove the selected node from the TreePanel, while the Sort action will order its children according to their text label.
Filtering The Ext.tree.TreeFilter class is marked as "experimental" in Ext JS 2.2, so I'm going to touch upon it only briefly. It's designed for scenarios where the user needs to search for nodes based on a particular attribute. This attribute could be the text, the ID, or any custom data that was passed when the node was created. Let's take the context menu that we just built and use it to demonstrate filtering. First, we have to create the TreeFilter: var filter = new Ext.tree.TreeFilter(tree);
You need to go back to the configuration for the context menu and add a new entry to the items configuration property: { text: 'Filter', handler: filterHandler }
We now need to create a filterHandler function that performs the filter action: function filterHandler() { var node = tree.getSelectionModel().getSelectedNode(); filter.filter('Bee', 'text', node); } [ 150 ]
Chapter 8
As with our other handler functions, we start by getting the currently-selected node in the tree, and then call the filter function. This function takes three arguments: 1. The value to filter by 2. The attribute to filter on; this is optional and defaults to text 3. The starting node for the filter We pass the selected node as the starting node for the filter, which means that the node we right-clicked on in order to to pop up the menu will have its children filtered by the specified value. Our aardvark, bee, and cockroach examples don't really require this level of filtering, but there are other situations in which this could prove to be a useful user feature. Online software documentation, with multiple levels of detail, could be represented in a tree and a TreeFilter could be used to search by topic. In a more advanced scenario, you could use checkboxes or pop-up dialogs to get the user's input for the filter, providing a much more flexible experience.
The roots Although we've demonstrated a number of powerful techniques using the Ext tree support, its real strength lies in the wealth of settings, methods, and hook points that the various classes expose. We've already reviewed a number of ways of configuring the TreePanel and TreeNode classes, which give access to a number of powerful features. However, there are more configuration options that can be used to tweak and enhance your tree, and we're going to review some of the more interesting ones now.
TreePanel tweaks By default, there are a number of graphical enhancements enabled for the TreePanel which, depending on your application requirements, may not be desirable. For example, setting animate to false will prevent the smooth animated effect being used for the expansion and contraction of nodes. This can be particularly useful in situations where nodes will be repeatedly expanded and collapsed by a user and slower animated transitions can be frustrating. Because TreePanel extends from Ext.Panel, it supports all of the standard Panel features. This is easy to remember, because it means that support for toolbars at the top and the bottom (via the tbar and bbar config options), separate header and footer elements, and expand/collapse functionality for the Panel are all supported. The TreePanel can also be included in any Ext.ViewPort or Ext.layout. [ 151 ]
Ext JS Does Grow On Trees
Cosmetic In terms of purely cosmetic options, TreePanel provides the lines option, which, when set to false, will disable the guide-lines that show the hierarchy of the TreeNodes within the panel. This can be useful if you're creating a very simple tree for which lines would just clutter the interface.
hlColor is applicable for drag-and-drop enabled trees, and controls the start color
for the fading highlight (supplied as a hex string, such as 990000) which is triggered when a node is dropped. This can be completely disabled by setting dlDrop to false. Setting trackMouseOver to false will disable the highlight that appears when you hover over a node.
Tweaking TreeNode In many cases, you won't be manually creating TreeNodes, other than your root node, so you might think that the configuration options aren't of much use to you. Not so, because it's not just the id and text properties from your JSON that are used when generating nodes—any property in your JSON that matches with a config option on the TreeNode will be used to create the node. If you have a JSON that like this: [ { text: 'My Node', disabled: true, href: 'http://extjs.com'} ]
[ 152 ]
Chapter 8
You'll get a node that starts off as disabled, but when enabled will act as a link to the Ext JS website.
This feature is extremely useful for passing application-specific information to your TreeNodes. For example, your server logic may dictate that particular nodes cannot have children. Setting allowChildren:false means that the node can't be used as a drop target for a node that is being dragged. Similarly , you can set individual nodes to be disallowed for dragging by using the draggable: false option. We can set the status of a checkbox on the node by using checked: true. In fact, simply specifying the checked option—whether true or false—will cause the checkbox to appear next to the node. These configuration options allow you to set the behavior of your nodes based on some server logic, but do not require any manual handling in order to see your preferences enacted. There are a few other useful configuration options available for TreeNode. You can provide custom icons by using the icon option, or provide a CSS styling hook by using the cls option. The qtip option lets you provide a pop-up tooltip, perhaps providing a description of the node, while the text label shows its name.
Manipulating Once the TreePanel is configured, we can begin to work with its nodes. The panel mostly allows for navigation of the hierarchy, starting at a selected node and moving to a parent or child, or up and down the current branch. We can also select nodes or expand them by their path, which could be used to search for specific nodes. The expandAll and collapseAll methods are pretty self-explanatory, and can be useful for resetting the tree to a default state. Each method takes a single Boolean parameter to state whether the change should be animated or not. [ 153 ]
Ext JS Does Grow On Trees
The expandPath method's first parameter is the "path" of a node. The path uniquely identifies the node within the hierarchy, and takes the form of a string which fully qualifies the location of a node in the tree. For example, a path could look like this: /n-15/n-56/n-101
Here, we have a representation of the location of the node with the ID n-101. n-15 is the root node, with a child n-56; and n-101 is in turn a child of n-56. If you're familiar with XPath, then this notation will be well-known to you. If you're not familiar with XPath then you can think of it as a postal address or a web IP address— a unique way of referring to this node. By passing this value to expandPath, the tree will drill down to the specified node, expanding branches as necessary. Imagine the following code: Ext.Msg.prompt('Node', 'Please enter a product name', function(btn, text){ if (btn == 'ok'){ var path = GetNodePathFromName(text); tree.expandPath(path); } });
The GetNodePathFromName function could perform a server lookup and return the node ID, enabling quick navigation of the tree based on the user's input. Alternatively, TreePanel.getNodeById could be used in a similar way. Rather than expand to the node, further manipulation could occur. In some circumstances, you may need to perform the reverse action, that is, you have a node but you need to get the path for it. TreeNode.getPath is provided for just this purpose, and can be used as a means of storing the location of a node.
Further methods The TreeNode has a number of other useful methods as well. We've already covered sort and remove, but now we can add some basic utility methods such as collapse and expand, enable and disable, as well as some handy extras such as expandChildNodes and collapseChildNodes, which can traverse all child nodes of an arbitrary root, and change their expansion states. The findChild and findChildBy methods allow both simple and custom searching of child nodes, as shown in the following example where we search for the first node with a price attribute of 300: var node = root.findChild('price', 300);
[ 154 ]
Chapter 8
In some cases you may need to mass-manipulate the attributes of your node hierarchy. You can do this by using the TreeNode.eachChild method: root.eachChild(function(currentNode) { currentNode.attributes.price += 30; });
Because the first parameter to eachChild is a function, we can perform any logic that is required of our application.
Event capture We've already demonstrated a couple of methods of watching for user interaction with the tree, but there are many events available as hooks for your custom code. Earlier, we discussed the use of the checked configuration option on a TreeNode. When the node checkbox is toggled, the checkchange event is fired. This could be useful for visually highlighting the check status: tree.on('checkchange', function(node, checked) { node.eachChild(function(currentNode) { currentNode.ui.toggleCheck(); }); }
We're propagating the check down through the children of the TreeNode. We could also highlight the nodes in question to clearly show that their check status has changed, or perform some other logic, such as adding information about the newly-checked nodes to an informational display elsewhere on the page.
A more common use of the TreePanel events is to verify changes or persist them to a server-side store. For example, a tree of categorized products may have some logical restrictions—certain bargain categories may specify the maximum price of a product. We could use the beforeappend event to check for this: tree.on('beforeappend', function(tree, parent, node) { return node.attributes.price < parent.attributes.maximumPrice; }); [ 155 ]
Ext JS Does Grow On Trees
This example demonstrates a pattern that you have seen throughout Ext JS—returning false from an event handler will cancel the action. In this case, if the price of the node being added is greater than the maximumPrice assigned to its parent, the function will return false, and the node will not be added.
Remembering state In many applications, TreePanels are used as navigation aids, showing a hierarchical structure, with its nodes being HTML links to node detail pages. In this scenario, if a user wishes to view multiple node detail pages, one after the other, the default behavior of the TreePanel can lead to frustration. This is because the tree doesn't save its state between page refreshes, so any expanded node will be rendered as collapsed when the user navigates back to the page. If the user needs to drill down to the branch they are interested in every time they navigate back to the tree, they are quickly going to lose patience with the interface.
StateManager Now that we have a good grasp of the way we can manipulate the TreePanel, working out how we can save and restore its state should be fairly straightforward. Essentially, what we need to do is record each expansion of a TreeNode, and when the page reloads, "playback" those expansions. We can use Ext.state.Manager with a CookieProvider to store our expansion. We can initialize this with: Ext.state.Manager.setProvider(new Ext.state.CookieProvider());
This is standard fare for setting up a state provider. We now need to establish exactly what we're going to store, and the logical choice would be the path of the last expanded node. This means that we can simply expand out that path and present the user with the last part of the hierarchy they were interested in. Here's a naive implementation of that idea: tree.on('expandnode', function (node){ Ext.state.Manager. set(“treestate", node.getPath()); });
In this code, we simply handle the TreePanel's expandnode event to record the path, using TreeNode.getPath, of any node that is expanded. Because we overwrite that value on each expansion, the treestate should hold the path of the last node that was expanded. We can then check for that value when the page is loaded: var treeState = Ext.state.Manager.get(“treestate"); if (treeState) tree.expandPath(treeState);
[ 156 ]
Chapter 8
If treestate has previously been recorded, we use that to expand the tree out to the last-expanded node.
Caveats As mentioned, this is a naive implementation. It doesn't handle cases where the user expands and, then collapses a node, and then navigates away and back. In such cases, the collapse of the node wouldn't be saved. So when we restore the state, the user will see it expanded again. By handling the collapsenode event, we could take this issue into account. We also have a problem with the expansion of multiple nodes. If more than one branch is expanded our code will only expand the one the user clicked most recently. Storing an array of expanded nodes is one approach that could address this shortcoming.
Summary Getting a feature-rich tree interface such as Ext.tree.TreePanel up and running in eleven lines of code is pretty impressive, and we've shown that it is possible. Over and above that, this chapter has demonstrated that the TreePanel's strength is not simply in its ease of use, but in the way we can use its wealth of configuration options to deliver application-specific functionality. The use of asynchronous loading is an important feature of the TreePanel, because it provides a way of consuming large amounts of dynamic data in a scalable fashion. It's also handled transparently by Ext.tree, which means that the implementation is as beneficial for the developer as it is for the end user. Despite all of their power, the Ext.tree classes still manage to feel pretty lightweight in use. It's easy to tame that power by using the configuration options, the methods, and the events that the TreePanel and TreeNode provide, but it's not just about these classes. TreeSorter and TreeNodeUI are key parts of the puzzle, adding functionality and allowing customization for a standardized look and feel. Because the Ext.TreePanel extends the Panel, which in turn extends BoxComponent, we get all of the strong component and layout support that comes from a fully-fledged Ext JS component. BoxComponent support will be particularly interesting as we move forward, because it means that trees can easily be included in various configurations within an Ext.Window. Which just happens to be our next topic.
[ 157 ]
Windows and Dialogs In the olden days of the web, users of traditional backend systems would spend their time crunching data in list and form-based interfaces. Pick an item from a list of customer orders, then navigate to a detail form, rinse, and repeat. The trouble is that we're talking about thousands of entries in a list, and lots of detail in the forms. The chances are that in our customer order example, we might even need sub-forms to show all of the information that is available, and each time we move to another screen we're refreshing the whole page and getting all of that data all over again. That's fine; it's how the web works. But in a data processing scenario, where your users are going back and forth throughout the day, there's a real benefit in optimizing the speed at which screens appear and data is refreshed. The Ext JS grid plays a key part in this, as we've seen, by bringing AJAX-powered paging and sorting of GridViews into play, to replace the old-style static lists. Now we're going to take a look at the other part of the puzzle—Ext JS Window and dialog support. These classes allow developers to present any kind of information to their users, without forcing the users to navigate to another screen. By popping up as an overlay on top of the current page, a window, or dialog can present detailed data in the form of grids, tree, images, and text. We can also use them in a simplified form to show informational alerts or to quickly capture user data.
Opening a dialog In this chapter, we'll talk about the main Ext.Window class and the Ext.MessageBox subclass, both of which have extensive applications in our enhanced user interface. While the Window itself is designed to be a flexible, multipurpose component, the MessageBox is a more specialized solution. It is used in a similar way to standard JavaScript pop ups such as alert and prompt, albeit with many more behavioral and presentational options available for it.
Windows and Dialogs
Ext.Window is another fully-blown Ext.Container, giving it a wealth of underlying
settings which will be familiar from the other parts of the Ext JS framework. It also hints at the types of interfaces that we can build in a Window, given that we can set the internal layout of a Container to a range of different options. We're also going to cover some extra classes which help us to manipulate multiple Windows: Ext.WindowGroup and Ext.WindowManager. In advanced applications, we can use more than one window to present drill-down views of information, or we can allow the user to view more than one record at once in separate windows. Window groups assist with these applications, giving us the power to act upon many windows at once. Despite the fact that dialogs build on Ext.Window, we're going to address dialogs first. That's because the dialogs abstract away many of the powerful options of Ext. Window, making it a cut-down version of the superclass.
Dialogs As previously mentioned, dialogs can be likened to the prompt and alert functions that are available in most browser implementations of JavaScript. Although the appearance of those pop ups is controlled by the browser and operating system, and their behavior is limited to the most common cases, these restrictions don't apply to the Ext JS dialogs. Although the dialog class is actually Ext.MessageBox, Ext JS provides a shorthand version - Ext.Msg. This shorthand version can make your code a little more concise, but it's up to you which one you use, as they're both functionally equivalent.
Let's take a look at the ready-made pop ups that Ext JS makes available.
Off the shelf We've talked about how Ext provides a replacement for the JavaScript alert, so let's have a look at that first: Ext.Msg.alert('Hey!', 'Something happened.');
[ 160 ]
Chapter 9
The first thing to notice is that Msg.alert takes two parameters, whereas the standard alert takes only one. The first allows you to specify a title for the alert dialog, and the second specifies the body text. The previous code results in a messagebox like this:
As you can see, it performs very much the same function as a standard alert but with that familiar Ext JS look and feel. We can also convey a little bit more information by using the title bar. Showing Msg.alert doesn't temporarily halt script execution in the same way that a normal alert will; be conscious of this when using the Ext version. Later, we'll look at ways to use callbacks to replicate that halt functionality should you need it. You can only use a single Ext.MessageBox at a time. If you try to pop up two boxes at the same time, the first will be replaced by the second. So in some cases, you'll want to check for the presence of an existing dialog in case you inadvertently overwrite the message it is presenting.
Let's take a look at another replacement, the Ext.Msg.prompt. This allows the capture of a single line of text in the same way that the JavaScript prompt does. However, instead of simply returning a value, it gives you a few more options. Here's a comparison of doing the same thing with each method: var data = prompt('Tell me something'); Ext.Msg.prompt('Hey!', 'Tell me something', function(btn, text){ if (btn == 'ok'){ var data = text; } }, this, false, '');
[ 161 ]
Windows and Dialogs
Again, Msg.prompt allows you to pass a title as the first argument, and the body text is the second. The third argument is a callback function that will be called when a button—either OK or Cancel—is clicked in the prompt. The callback has two arguments: the button that was clicked and the text that was provided by the user; you can use these as demonstrated in the example code.
Note the other three options that come after the callback function. They are scope, multiline, and initial value, respectively. The multiline argument is interesting—accepting a boolean value, it allows a more flexible capture of text than the standard prompt.
Confirmation Our final replacement messagebox is for the confirm pop up, which allows the user to choose between confirming an action or rejecting it. The code should be pretty familiar by now: Ext.Msg.confirm('Hey!', 'Is this ok?', function(btn, text){ if (btn == 'yes'){ // go ahead and do more stuff } else { // abort, abort! } });
Again we're using the title, body text, and callback arguments that we saw in the Msg.prompt, so there are no surprises here. An interesting difference between this and the standard confirm is that whereas the standard confirm gives the options OK and Cancel, the Ext JS one gives the user the choice of Yes and No. This is arguably a better default, particularly when you use a question in the body text of the confirm messagebox. [ 162 ]
Chapter 9
It's all progressing nicely There's a fourth standard messagebox which is included with Ext JS one that isn't just a replacement for a basic JavaScript pop up. This is the progress dialog. Ext.Msg.progress isn't designed to be used independently like the other Ext messageboxes, and doesn't need user input. In fact, if you trigger it like this: Ext.Msg.progress('Hey!', 'We\'re waiting...', 'progressing');
Then you're going to be waiting for a while, because you'll get a modal dialog which never progresses anywhere. The first two arguments are the title and body text as in the previous examples, while the third is the text that will appear in the progress bar. So, if we don't want to be stuck with an eternal progress bar, how can we get things moving? The Ext.Msg.updateProgress method is provided just for this purpose. Here's an example which illustrates its use: var count = 0; var interval = window.setInterval(function() { count = count + 0.04; Ext.Msg.updateProgress(count); if(count >= 1) { window.clearInterval(interval); Ext.Msg.hide(); } }, 100);
This is a very contrived example, in which we're calling updateProgress every 100 milliseconds via a timer, and incrementing the progress using the count variable every time. The first argument to updateProgress is a value between zero and one, with zero representing start and one representing finish, the second allows you to update the progress bar text, and the third lets you change the body text. Updating the text can be handy if you'd like to provide additional feedback to the user, even if this is just to show a percentage representation—"x% complete"—of the current progress. Back in our example code, note that you must also call Ext.Msg.hide in order to clear the progress dialog from the screen—updateProgress doesn't handle this for you, even if you set the current progress to a value of greater than one. The four included messageboxes, alert—prompt, confirm, and progress—are the foundation of the Ext JS dialog support, but we can also weak their functionality to support some custom scenarios. [ 163 ]
Windows and Dialogs
Roll your own The four methods for creating messageboxes that we've just covered are essentially shortcuts to a fifth method: Ext.Msg.show. This method takes a configuration object as its single argument, and the configuration options within this object allow the creation of a messagebox that supports all of the features available via our shortcut methods. The simplest example of this method is: Ext.Msg.show({ msg: 'AWESOME.' });
This is closer replication of the JavaScript alert than the standard Ext JS one—but it's not as functional. Something a little better would be: Ext.Msg.show({ title: 'Hey!', msg: 'Icons and Buttons! AWESOME.', icon: Ext.MessageBox.INFO, buttons: Ext.MessageBox.OK });
Now we've got our title back as well as our button, but there's an icon as well.
The means of configuring buttons and icons is interesting: pass in one of the constants that Ext JS provides, and you'll get a pre-configured button or CSS class that shows an icon. Here's the list of the icon constants: •
Ext.Msg.ERROR
•
Ext.Msg.INFO
•
Ext.Msg.QUESTION
•
Ext.Msg.WARNING
And the button constants: •
Ext.Msg.CANCEL
•
Ext.Msg.OK [ 164 ]
Chapter 9
•
Ext.Msg.OKCANCEL
•
Ext.Msg.YESNO
•
Ext.Msg.YESNOCANCEL
This variety of ready-made options provides you with a fair bit of flexibility when it comes to the appearance of your messageboxes, but we can go further. As mentioned, the icon constants are simply strings representing CSS class names. For example, Ext.Msg.QUESTION provides the ext-mb-question string. This ties in to the Ext JS stylesheets and provides the styles for a question icon. The logical conclusion is that we can provide our own strings in place of these constants, allowing full customization of the icon areas. The button constants are a bit less flexible, and contain object literals specifying how the dialog buttons should be displayed. For example, Ext.Msg.YESNOCANCEL contains the following (represented in JavaScript Object Notation for easy reading): { cancel:true, yes: true, no:true }
This is specifying that all of the yes, cancel, and no buttons should be included. You can use this to selectively turn off particular buttons, but you can't change the order or add new buttons in this manner. This means that providing custom button definitions in this way is of limited use. In addition to accepting the Ext.Msg button constants, the show method options will also accept a standard Ext JS button configuration object.
However, we can tweak the dialog in other ways. It's possible to supply Ext.Msg. show with width and height options to restrict the dimensions of your pop up. This could be handy in a situation where you have a long message to display and would like to prevent it from stretching to one long line across the screen. The show configuration object also allows us to use the cls option to specify a CSS class to apply to the container of the dialog. A developer could use this to target any child objects of the container by using custom CSS rules, potentially paving the way for multiple dialogs that have totally different appearances. Do you need to provide a bright-pink pop up for your users? This configuration option allows you to do this.
Behavior So far, the configuration options for Ext.Msg.show have addressed appearance, but there are a few options that can also adjust behavior. If we use the progress property, then we can replicate the standard Ext JS progress dialog: Ext.Msg.show({progress:true}); [ 165 ]
Windows and Dialogs
By using this in tandem with other options such as title and msg, you can create a custom progress dialog. Similarly, the prompt and multiline options allow the creation of a custom input pop up: Ext.Msg.show({prompt:true, multiline:true});
Here, we create a pop up that accepts multiple lines of input. But by omitting the multiline value or setting it to false, we can limit the pop up to a single line. Again, using the other configuration options for Ext.Msg.show allows us to expand this sample code into a fully-featured input dialog. Another option that changes the default behavior of a pop up is modal. This option allows you to specify whether the user can interact with the items behind the pop up. When set to true, a semi-transparent overlay will prevent interaction. As we have discussed earlier, the Ext.Msg pop ups don't block script execution in the same way as standard JavaScript pop ups. This means that we need to use a callback to trigger code after the dialog is dismissed. We can do this using show's fn configuration option, which gets called with two arguments: the ID of the button that has been clicked, and the text entered into the text field in the dialog (where the dialog includes an input field). Obviously, for a simple alert prompt, you're not going to receive any text back, but this function does provide a consistent means of consuming callbacks across the whole range of dialogs that you can build using Ext. Msg.show. We briefly touched on the fact that the Ext.Msg messageboxes are actually customized Ext.Windows. If you think we're able to tweak Ext.Msg a lot…wait till you see what Ext.Window can let us do.
Windows Any computer user will be familiar with the concept of windows; an informational panel that appears on the screen to provide more data on the current user's actions. We can replicate this concept using the Ext.Window class, a powerful component that supports many advanced scenarios.
Starting examples We can open a window using a very minimal amount of code: var w = new Ext.Window({height:100, width: 200}); w.show(); [ 166 ]
Chapter 9
Running this gives you an empty pop up window that in itself is…well, completely useless; but it does show off a few of the interesting default features of an Ext. Window. Straight out of the box, without any configuration, your window will be draggable, resizable, and will have a handy close icon in the upper right corner of the dialog box. It's still not a very impressive demonstration, however, because our window doesn't actually show anything. The easiest way to populate a window is with plain old HTML. Here's an extended example that demonstrates this feature: var w = new Ext.Window({ height: 150, width: 200, title: 'A Window', html: 'Oh
HI THERE EVERYONE
' }); w.show();Started at 12:31pm
Is dealing with 3 sessions...