This content was uploaded by our users and we assume good faith they have the permission to share this book. If you own the copyright to this book and it is wrongfully on our website, we offer a simple DMCA procedure to remove your content from our site. Start by pressing the button below!
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: October 2010
Production Reference: 1061010
Published by Packt Publishing Ltd. 32 Lincoln Road Olton Birmingham, B27 6PA, UK. ISBN 978-1-849511-20-9 www.packtpub.com
Credits Authors Shea Frederick Colin Ramsay Steve 'Cutter' Blades Nigel White Reviewers Jonathan Julian Jorge Ramon Acquisition Editor Usha Iyer Development Editor Wilson D'Souza Technical Editor Pallavi Kachare Copy Editor Laxmi Subramanian Indexer Tejal Daruwale
Editorial Team Leader Aanchal Kumar Project Team Leader Ashwin Shetty Project Coordinator Poorvi Nair Proofreader Aaron Nash Graphics Nilesh Mohite Production Coordinator Kruthika Bangera Cover Work Kruthika Bangera
About the Authors Shea Frederick began his career in web development before the term 'Web Application' was commonplace. By the late 1990s, he was developing web applications for Tower Records that combined the 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. Shea is the primary author of the first book published on Ext JS, a book which helps to ease beginners into the Ext JS library. He is also a core developer on the Ext JS project along with writing columns for JSMag and running the local Baltimore/DC JavaScript Meetup. His ramblings can be found on his blog, http://www.vinylfox.com and open source code contributions on Github at http://www.github.com/VinylFox/.
Colin Ramsay began his career building PHP and ASP websites as a part-time
developer while at university. Since then, he's been involved with a range of web technologies in his work with a range of companies in the North East of England—everything from flash-in-the-pan web frameworks to legacy applications. After forming his first company in 2007, putting his past experience into action, he went on be a partner at Go Tripod Ltd, a design and development company with clients across the UK. 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 Development Manager 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 videophile, and likes to relive his band days with a mike in hand.
I would like to thank a few people for their support while I have been working on this project. First, thanks to Abe, Aaron, and the Ext JS crew at Sencha for giving us such a great topic to keep writing about. Thanks to everyone at Dealerskins and Dominion Enterprises for all of their support. Special thanks to my parents and my Grandma for giving me their guidance and advice all of these years. But, most of all, thanks to my beautiful wife, Teresa, and daddy's Little Girl, Savannah, for their continued patience, love, and encouragement. I never could have done it without them. In memory of William "Pop Pop" Blades, who always believed in me and taught me that I could do anything I set my mind to do, and be anything that I chose to be.
Nigel White has 20 years of experience in the IT industry. He has seen computer systems evolve from batch processing, back room behemoths which dictated user behavior into distributed, user-centered enablers of everyday tasks. Nigel has been working with rich Internet applications, and dynamic browser updating techniques since before the term "Ajax" was coined. Recently he collaborated with Jack Slocum in the germination of the ExtJS project, and has contributed code, documentation, and design input to the ExtJS development team. Nigel works as a software architect at Forward Computers where he oversees development of both the Java server tier and the browser interface of the company's evolving web UI. He also runs Animal Software, a one man consultancy specializing in ExtJS UI development, consulting, and training. Work displacement activities include rock climbing and bicycling!
About the Reviewers Jonathan Julian is an application developer living in Baltimore, MD. He builds amazing web applications using Ruby on Rails and occasionally has the pleasure of creating web interfaces using Ext JS. You can find out more information about Jonathan on his website, http://jonathanjulian.com.
Jorge Ramon is currently the Vice President of Development for Taladro
Systems LLC, where he has led the design and development of a number of software products for the law industry. He is also a mobile software development instructor and coach. Jorge has over 16 years of experience as a software developer and has also worked on creating web applications, search engines, and automatic-control software. He actively contributes to the software development community through his blog MiamiCoder.com.
Jorge Ramon is also the author of Ext JS 3.0 Cookbook, with more than a hundred step-by-step recipes for building rich Internet applications using the Ext JS JavaScript library.
To my parents. They showed me the way.
Table of Contents Preface 1 Chapter 1: Getting Started 7 A word about JavaScript 7 I'm asynchronous! 8 About Ext JS 9 Ext JS: not just another JavaScript library 12 Cross-browser DOM (Document Object Model) 12 Event-driven interfaces 13 Ext JS and AJAX 13 Getting Ext JS 13 Where to put Ext JS 13 Including Ext JS in our pages 15 What do those files do? 16 Spacer image 16 Using the Ext JS library 17 Time for action 17 The example 18 Using the Ext.onReady function 19 Not working? 19 Adapters 20 Using adapters 21 Localization 22 English only 22 A language other than English 22 Multiple languages 23
Table of Contents
Ext JS online help 23 Online API docs 23 The FAQ 24 Ext JS forum 24 Summary 25
Chapter 2: The Staples of Ext JS
27
Meet the config object The old way The new way—config objects
27 28 28
What is a config object?
30
Widgets and classes 30 Time for action 30 What just happened? 31 More widget wonders 33 Time for (further) action 34 Lighting the fire 36 The workhorse—Ext.get 38 Minimizing memory usage 38 Can we use our own HTML? 39 Summary 42
Chapter 3: Forms
43
The core components of a form 43 Our first form 44 Nice form—how does it work? 45 Child items 45 Validation 47 Built-in validation—vtypes 49 Styles for displaying errors 50 Custom validation—creating our own vtype 51 Masking—don't press that key! 53 Radio buttons and check boxes 53 It's not a button, it's a radio button 54 X marks the checkbox 54 The ComboBox 55 A database-driven ComboBox 57 TextArea and HTMLEditor 59 Listening for form field events 60 ComboBox events 62 Buttons and form action 63 Form submission 64 Talking back—the server responses 65 [ ii ]
Table of Contents
Loading a form with data 67 Static data load 67 DOM listeners 69 Summary 70
Chapter 4: Menus, Toolbars, and Buttons
71
Chapter 5: Displaying Data with Grids
91
What's on the menu? 72 The menu's items 73 A toolbar for every occasion 75 Button configuration 77 A basic button 78 Button with a menu 78 Split button 79 Toggling button state 79 Toolbar item alignment, dividers, and spacers 80 Shortcuts 81 Icon buttons 82 Button events and handlers—click me! 83 Loading content on menu item click 83 Form fields in a toolbar 85 Buttons don't have to be in a toolbar 86 Toolbars in panels 87 Toolbars unleashed 89 Summary 90 What is a grid anyway? 92 A GridPanel is databound 92 The record definition 93 The Reader 94 ArrayReader 94 JsonReader 94 XmlReader 95 Loading our data store
Displaying structured data with a GridPanel Converting data read into the store Displaying the GridPanel How did that work? Defining a grid's column model Built-in column types BooleanColumn DateColumn NumberColumn
[ iii ]
95
96 97 98 99 100 101
101 102 102
Table of Contents TemplateColumn ActionColumn
102 103
Using cell renderers 103 Formatting data using the built-in cell renderers 103 Creating lookup data stores—custom cell rendering 104 Combining two columns 105 Generating HTML and graphics 106 Built-in features 106 Client-side sorting 107 Hidden/visible columns 107 Column reordering 108 Displaying server-side data in the grid 109 Loading the movie database from an XML file 109 Loading the movie database from a JSON file 110 Loading data from a database using PHP 112 Programming the grid 112 Working with cell and row selections 112 Listening to our selection model for selections 113 Manipulating the grid (and its data) with code 114 Altering the grid at the click of a button 115 Advanced grid formatting 116 Paging the grid 117 Grouping 119 Grouping store 119 Summary 121
Chapter 6: Editor Grids
What can I do with an editable grid? Working with editable grids Editing more cells of data Edit more field types Editing a date value Editing with a ComboBox
123 124 124 126 126
127 128
Reacting to a cell edit
128
Deleting and adding in the data store
130
What's a dirty cell? Reacting when an edit occurs
Removing grid rows from the data store Adding a row to the grid
Saving edited data to the server Sending updates back to the server Deleting data from the server Saving new rows to the server [ iv ]
128 129 131 132
134 134 135 136
Table of Contents
RowEditor plugin 138 Writable store 139 Summary 142
Chapter 7: Layouts
143
Chapter 8: Ext JS Does Grow on Trees
167
What is a layout manager? 143 So what layouts are available? 145 AbsoluteLayout 145 AccordionLayout 145 AnchorLayout 146 BorderLayout 146 CardLayout 146 ColumnLayout 146 FitLayout 146 FormLayout 147 HBoxLayout 147 TableLayout 147 VBoxLayout 147 A dynamic application layout 147 Our first Viewport 148 Nesting: child components may be Containers 150 Accordion layout 153 A toolbar as part of the layout 155 Using a FormPanel in the layout 156 AnchorLayout 159 More layouts 160 Vbox layout 161 Hbox layout 162 Dynamically changing components 163 Adding new components 165 Summary 166 Planting for the future 168 From tiny seeds... 168 Our first sapling 169 Preparing the ground 169 A tree can't grow without data 170 JSON 171 A quick word about ID Extra data
172 172
XML 172 [v]
Table of Contents
Tending your trees 173 Drag and drop 173 Sorting 175 Editing 176 Trimming and pruning 178 Selection models 178 Round-up with context menus 179 Handling the menu
180
Filtering 181 The roots 182 TreePanel tweaks 182 Cosmetic 182 Tweaking TreeNode 183 Manipulating 184 Further methods
185
Event capture Remembering state
186 187
StateManager 187 Caveats 188
Summary 188
Chapter 9: Windows and Dialogs
189
Opening a dialog 190 Dialogs 190 Off the shelf 191 Confirmation It's all progressing nicely
192 193
Roll your own
194
Behavior
196
Windows 197 Starting examples 197 Paneling potential 198 Layout 198
Configuration
199
Manipulating Events State handling
202 203 204
When I'm cleaning windows The extras Desktopping Further options Framing our window
199 200 201 201 201
[ vi ]
Table of Contents
Window management Default window manager behavior Multiple window example
205 205 205
Customer service WindowGroups
209
Summary 211
Chapter 10: Charting New Territory
213
Chapter 11: Effects
237
Just another component 213 What the flash? 214 A slice of data—pie charts 215 Chart in a layout 216 Pie chart setup 217 Styling the pie slices 218 Bar and column charts 220 From bar to column 222 Add some style 223 Stack them across 224 Get to the stacking 225 Charting lines 227 Tooltips 228 A real world example 229 Dynamically changing the data store 233 Programatically changing the styles 235 Summary 235 It's elementary Fancy features It's OK to love Fxcellent functions Methodical madness
237 238 238 238 239
Fading 239 Framing 240 Woooo: ghosting 241 Highlighting 242 Huffing and puffing 242 Scaling the Ext JS heights 243 Sliding into action 243 Switching from seen to unseen 244 Shifting 244
And now, the interesting stuff 245 The Fx is in 246 Anchoring yourself with Ext 246 Options 247 Easy does it
248
[ vii ]
Table of Contents
Multiple effects Chaining Queuing
250 250 250
Concurrency Blocking and Ext.Fx utility methods
250 251
Elemental 251 Making a move 251 Using Ext components 252 Reveal all 252 You're maskin', I'm tellin' 253 Data binding and other tales Considering components
254 254
QuickTipping 254 Summary 256
Chapter 12: Drag-and-drop
259
Drop what you're doing Life's a drag Sourcing a solution
260 260 260
Approximating Snap!
261 261
Drop me off
262
But wait: nothing's happening!
262
Interacting the fool Zones of control
263 263
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 Configuration It's all under control Managing our movement Global properties Scroll management
Dragging within components 274 TreePanel 274 GridPanel 274 Using it in the real world 275 Summary 276
Chapter 13: Code for Reuse: Extending Ext JS
277
Object-oriented programming with Ext JS 279 Inheritance 279 Break it down and make it simple 279 Sounds cool, but what does it mean? 281 Now, what was this overriding stuff?
281
What is an event-driven application?
289
Understanding packages, classes, and namespaces 282 Packages 282 Classes 282 Namespaces 282 What's next? 283 Ok, what do we extend? 283 Creating a custom namespace 283 Our first custom class 284 Overriding methods 287 Understanding the order of events 288 When can we do what? 288 Creating our own custom events 291 Our first custom component: complete 292 What's next? Breaking it down 295 Using xtype: the benefits of lazy instantiation 299 Using our custom components within other objects 299 Summary 300
Chapter 14: Plugging In
301
What can we do? 301 How it works 302 Using a plugin 302 Plugin structure 303 First signs of life 304 The search form 306 Interacting with the host 308 Configurable plugins 310 Extra credit 312 Summary 313 [ ix ]
Table of Contents
Chapter 15: It's All About the Data Understanding data formats Loading HTML into a panel Gotchas with remote data Other formats
315 315 316 319
319
The data Store object Defining data
320 321
Using a DataReader to map data Using a custom DataReader Writing a custom DataReader Getting what you want: finding data Finding data by field value
325 327 330 335 335
More on mapping our data Pulling data into the Store
Finding data by record index Finding data by record ID
323 324
336 336
Getting what you want: filtering data 336 Remote filtering: the why and the how 337 Dealing with Recordset changes 343 Taking changes further: the DataWriter 344 Many objects use a Store 351 Store in a ComboBox 352 Store in a DataView 352 Stores in grids 352 Summary 353
Chapter 16: Marshalling Data Services with Ext.Direct What is Direct? Building server-side stacks Configuration
355 355 356 357
Programmatic 357 JSON and XML 358 Metadata 359 Stack deconstruction—configuration 359
Building your API
360
Routing requests
363
Stack deconstruction—API
361
What is a Router
363
Putting the pieces together 369 Make your API available 369 Making API calls 369 Summary 373
[x]
Table of Contents
Chapter 17: The Power of Ext JS: What Else Can You Do?
375
So much to work with 376 Form widgets 376 DateField 376 TimeField 377 NumberField 379 CheckboxGroups and RadioGroups 379 HtmlEditor 379 Data formatting 380 Basic string formatting 381 Formatting dates 382 Other formatting 382 Managing application state 383 Basic state management 384 How do I get that window? 384 Using the back button in Ext JS applications 385 Accessing the DOM 385 Finding DOM elements 385 Manipulating the DOM 386 Working with styles 386 Ext JS for the desktop: Adobe AIR 387 Ext JS community extensions 388 DatePickerPlus 389 PowerWizard 389 TinyMCE 390 SwfUploadPanel 390 ColorPicker 391 Additional resources 391 Samples and Demos 391 Ext JS API 392 Ext JS forums 392 Step-by-step tutorials 392 Community Manual 392 Spket IDE 393 Aptana Studio 393 Google 393 Where do we go from here? 393 Summary 394
Index 395
[ xi ]
Preface Ext JS is a JavaScript library that makes it (relatively) easy to create desktop-style user interfaces in a web application, including multiple windows, toolbars, drop-down menus, dialog boxes, and much more. This book covers all of the major features of the Ext framework using interactive code and clear explanation coupled with loads of screenshots. Learning Ext JS will help you create rich, dynamic, and AJAX-enabled web applications that look good and perform beyond the expectations of your users.
What this book covers
Chapter 1, Getting Started, covers the basics of Ext JS and what it can do for us. Unlike other JavaScript libraries, Ext JS handles the messy foundation work for you, so with only a few lines of code, you can have a fully functional user interface. The main goal of this chapter was to get Ext JS installed and working, so we can start creating some really sweet widgets and web applications. Chapter 2, The Staples of Ext JS, shows how to create more functional widgets, and how to configure them to behave exactly as we require. We will start to use and interact with Ext JS widgets for the first time, by creating a series of dialogs that interact with each other, the user, and the web page. Chapter 3, Forms, shows how to create Ext JS forms, which are similar to the HTML forms that we use, but with a greater level of flexibility, error checking, styling, and automated layout. The form created in this chapter can validate user input, load data from a database, and send that data back to the server. Chapter 4, Menus, Toolbars, and Buttons, covers how to use menus, both as components in their own right—either static or floating as popups—and as dependent menus of buttons. We will have the chance to play with a couple of different ways to create toolbar items, including using a config object or its shortcut.
Preface
Chapter 5, Displaying Data with Grids, covers how to define the rows and columns, but more importantly, we will learn how to make the grid a very useful part of our application. Chapter 6, Editor Grids, shows how the data support provided by the grid offers an approach to data manipulating that will be familiar to many developers. It also shows how standard Ext JS form fields such as the ComboBox can be integrated to provide a user interface on top of this functionality. Chapter 7, Layouts, demonstrates 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. Chapter 8, Ext JS Does Grow on Trees, demonstrated that the strength of the TreePanel 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. Chapter 9, Windows and Dialogs, covers the difference between Ext.Window and Ext.MessageBox, built-in Ext JS methods to show familiar popups, and tweaks the
configuration of a window for advanced usage.
Chapter 10, Charting New Territory, starts off with a basic pie chart, and moves on to more complicated charts from there. It also shows how easy it can be to get a basic chart working, and switching between the different chart types can also be quite easy depending on the type of chart. Chapter 11, Effects, discusses the range of built-in Ext JS options for animation and effects, creating custom animations and tweaking the existing ones, using multiple animations together, and other Ext JS visual effects such as masking and tooltips. Chapter 12, Drag-and-drop, takes a look at one of the most typical examples of Web 2.0 glitz: drag-and-drop. In a typical Ext JS manner, we're going to see how it's not only simple to use, but also powerful in its functionality. Chapter 13, Code for Reuse: Extending Ext JS, discusses how we can create our own custom components by extending the Ext JS library. It talks about how we can create our own namespaces, differentiating our custom components from others. It also discusses some other core object-oriented concepts and the concept of Event-driven application architecture. Chapter 14, Plugging In, discusses how to write and use plugins, and how a plugin differs from a component. Chapter 15, It's All About the Data, covers the many different ways in which you can retrieve from and post data to your Ext JS based applications. [2]
Preface
Chapter 16, Marshalling Data Services with Ext.Direct, discusses how a developer can write his/her own Ext.Direct server-side stacks for marshalling data services under a single configuration. Chapter 17, The Power of Ext JS: What Else Can You Do?, shows you a few hidden gems of the Ext JS framework, and talks about some community resources for further information.
Who this book is 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 are re-vamping 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: "If you pass a function to Ext.onReady when the document is already fully initialized, it will be called immediately." A block of code is set as follows: var myVariable = "A string literal"; alert(myVariable); myVariable = function() { alert("Executing the function"); };
When we wish to draw your attention to a particular part of a code block, the relevant lines or items are set in bold: { xtype: 'datefield', fieldLabel: 'Released', name: 'released', disabledDays: [1,2,3,4,5] }
New terms and important words are shown in bold. Words that you see on the screen, in menus or dialog boxes for example, appear in the text like this: "One of the most striking examples is the Feed Viewer". [3]
Preface
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 send an e-mail to [email protected], and mention the book title via the subject of your message. 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 e-mail [email protected]. 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 this book You can download the example code files for all Packt books you have purchased from your account at http://www. PacktPub.com. If you purchased this book elsewhere, you can visit http://www.PacktPub.com/support and register to have the files e-mailed directly to you.
[4]
Preface
Errata
Although we have taken every care to ensure the accuracy of our content, mistakes do happen. If you find a mistake in one of our books—maybe a mistake in the text or the code—we would be grateful if you would report this to us. By doing so, you can save other readers from frustration and help us improve subsequent versions of this book. If you find any errata, please report them by visiting http://www.packtpub. com/support, selecting your book, clicking on the errata submission form link, and entering the details of your errata. Once your errata are verified, your submission will be accepted and the errata will be uploaded on our website, or added to any list of existing errata, under the Errata section of that title. Any existing errata can be viewed by selecting your title from http://www.packtpub.com/support.
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 us with the location address or website name immediately so that 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.
[5]
Getting Started In this chapter, we will cover the basics of Ext JS and what it can do for us. If you're accustomed to the standard Web development, then you'll be excited when you learn about the elegance in the architecture of Ext JS, just as I was. Unlike other JavaScript libraries, Ext JS handles the messy foundation work for you, so with only a few lines of code, we can have a fully functional user interface. In this chapter, we will cover: •
Some JavaScript fundamentals which are key to understanding Ext JS code
•
What Ext JS does and why you'll love using it
•
How to get Ext JS and start using it in your Web applications
•
Creating a simple "hello world" example
•
Using "adapters" to allow Ext JS to co-exist with other JavaScript libraries
•
Taking advantage of AJAX technology
•
Displaying Ext JS Components in your own language
A word about JavaScript
JavaScript is an object-based language. Every data item is an object. Numbers, strings, dates, and Booleans (true or false values) are all objects. JavaScript variables reference the objects we assign to them. Think of a variable as a "pointer" to the object, not a "box" into which a value is placed. A variable assignment statement does not move any data. It simply changes where the variable is pointing to. In JavaScript, functions are also objects, and may be assigned to variables and passed as parameters just the same as other objects. A function declaration is a literal in just the same way that a quoted string is a literal.
Getting Started
Consider the following example. A variable is assigned to a string and then a function. var myVariable = "A string literal"; alert(myVariable); myVariable = function() { alert("Executing the function"); }; myVariable();
The variable myVariable is set to reference (point to) a string literal which is then alerted to the user. Then the variable is set to reference a function literal. That variable is then used to call that function. Appending the () causes the referenced function to be called. This concept is central to your understanding of much of what follows. Functions will be passed as parameters into Ext JS methods to be called by Ext JS to handle user interface events or network communication (AJAX) events. Ext JS also provides its API in an Object Oriented manner. This means that it does not simply provide a mass of utility functions; instead, mnemonically named classes are provided which encapsulate discrete areas of functionality. Functions are called as member methods of a class. All Ext JS widgets: grids, trees, forms, and so on are objects. For more information on this concept, see http://en.wikipedia.org/ wiki/Object-oriented_programming. It is important to remember that when a function reference is passed, it's only a pointer to a function object. If that function was a member method of an object, then this information is not included. If the function is to be executed as a member method of an object, then that information must be included when passing the function. This is the concept of scope, which will be very important in later chapters. If that went a bit over your head, don't worry. Put a bookmark in this page, and refer to it later. It will be important!
I'm asynchronous!
The Web 1.0 way of doing things has all of our code happening in succession—waiting for each line of the 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 and then the walls must be complete before the roof is built.
[8]
Chapter 1
With Ext JS, 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. To visualize this, look at the following diagram. Contrast the linear assembly of the house on the left with the assembly on the right in which manufacture of the roof happens concurrently with the walls and foundation:
This introduces some things we're not used 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 JS helps us out by giving us events and handlers to which we can attach our functionality. As described above, we can specify a function to be called later, when the walls of the house are built, which 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. We must be aware of not only what our function does, but also when it does it. We might embed a function literal inside our code which does not run immediately, but just sits dormant until an event fires at some time in the future.
About Ext JS
We will be working with the most recent release version of Ext JS which, at the time of writing, is the 3.x branch. However, the examples used in this book will be compatible with the 2.x branch unless specifically noted. 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 the 1.x code mostly incompatible with 2.x, 3.x, and vice versa. An upgrade guide that explains in more detail what has changed is available on the Ext JS website: http://www.extjs.com/learn/w/index.php?title=Ext_1_to_2_Migration_ Guide
[9]
Getting Started
The 3.x branch is backwards-compatible with 2.x, so many of the examples in this book will work with both versions. The Ext JS development team is dedicated to making future releases backwards-compatible. The Ext JS 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 YUI Library 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 JS 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 JS provides an easy-to-use, rich user interface, much like you would find in a desktop application. This lets Web developers concentrate on the functionality of Web applications instead of the technical caveats. The examples given on the Ext JS 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 JS. However, it is a bit too complex to be used as a learning example. So for now, we can just revel in its brilliance. The following screenshot illustrates the familiar border layout with user-resizable regions, which you may notice being used in programs like email readers:
[ 10 ]
Chapter 1
Another excellent example is the Simple Tasks task-tracking program, which utilizes a Google Gears database.
[ 11 ]
Getting Started
Over the course of this book, we will learn how to build Web interfaces as impressive as these.
Ext JS: not just another JavaScript library
Ext JS is not just another JavaScript library. It is a fully featured client UI library capable of creating dynamic, fluidly laid out user interfaces which are bound to structured data which itself is linked to server-side data sources. Ext JS can, however, 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 website that requires a high level of user interaction—something more complex than your typical website, most commonly found in intranet applications. A website that requires processes and a work flow would be a perfect example, or Ext JS could just be used to make your boss gasp with excitement. Ext JS 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 user's keystrokes, mouse clicks, and monitoring events in a browser such as a window resize, or font size changes.
•
Communicating with the server in the background without the need to refresh the page. This allows us to request or post data to or from our 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 we create a DIV element and apply a style to it, it becomes apparent that it's not going to look the same in every browser unless we are very diligent. When we use Ext JS widgets, the browser compatibility is taken care of by the Ext JS library, so that each widget looks exactly the same in all supported browsers, which are: •
Internet Explorer 6+
•
Firefox 1.5 + (PC, Mac)
•
Safari 2+ [ 12 ]
Chapter 1
•
Opera 9 + (PC, Mac)
•
Chrome 1+
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 function 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 possible failure.
Ext JS and AJAX
The term AJAX (Asynchronous JavaScript and XML) is an overly-complicated acronym for saying that processes can take place in the background, talking to the server 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.
Getting Ext JS
Everything we will need can be downloaded from the Ext website, at http:// www.extjs.com/products/js/download.php. Grab the Ext JS SDK (Software Development Kit), which contains a ton of useful examples and the API reference. Most importantly, it contains the resources that Ext JS needs to run properly.
Where to put Ext JS
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.
[ 13 ]
Getting Started
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's rename the ext-3.2.0 folder to extjs. If your company is using source control, along with quality assurance testing, then sticking with a folder name based on the library version number might be more appropriate. The SDK contains a version of Ext JS that has everything we 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 our 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. 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 alongside Ext JS
•
docs: The documentation center (this will only work when run from
•
examples: Plenty of amazing and insightful examples, plugins, and extensions
•
pkgs: Packaged up Ext JS modules used when building Ext JS
a web server)
[ 14 ]
Chapter 1
•
resources: Dependencies of the Ext JS library, such as CSS and images
•
src: The complete source code for Ext JS
•
test: The test suite for Ext JS
•
welcome: Miscellaneous image files
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 in addition to the ext-all.js and ext-all-debug.js files.
Including Ext JS in our pages
Before we can use Ext JS in our pages, we need to reference the Ext JS 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. In our simple pages, we will not use a doctype. Ext JS supports running with no doctype (quirks mode), across all browsers. Getting Started Example <script src="lib/extjs/adapter/ext/ext-base.js"> <script src="lib/extjs/ext-all-debug.js">
The path to the Ext JS files must be correct and is relative to the location of our HTML file. These files must be included in the order shown. A theme CSS file may be included after the ext-all.css file to customize the look of the UI.
[ 15 ]
Getting Started
What do those files do?
We have included the following three files, which Ext JS requires to run in our page: •
ext-all.css: A stylesheet file that controls the look and feel of Ext JS
•
ext-base.js: This file provides the core functionality of Ext JS. It's the
•
ext-all-debug.js/ext-all.js: All of the widgets live in this file. The debug version should always be used during development, and then swapped out for the non-debug version for production.
widgets. This file must always be included as-is, with no modifications. Any changes to the CSS in this file would break future upgrades. If we decide that the look and feel of Ext JS needs to be adjusted, another stylesheet containing the overrides should be included after the ext-all.css file. foundation upon which Ext JS builds its capabilities, and provides the interface to the browser environment. This is the file that we would change if we wanted to use another library, such as jQuery, along with Ext JS.
Once these files are in place, we can start to actually use the Ext JS library and have some fun. If you are working with a server-side language such as PHP or ASP.NET, you might choose to "include" these lines in the header dynamically. For most of the examples in this book, we will assume that you are working with a static HTML page.
Spacer image
Ext JS needs to use a spacer image, a 1 pixel by 1 pixel, transparent, GIF image to stretch in different ways, giving a fixed width to its widgets. When run on modern browsers, Ext JS uses an image encoded as a data URL—the URL begins with data: not http:. But on legacy browsers, the default URL references a GIF at the extjs.com website. This may not always be accessible or desirable. We can change this to reference the image on the local server using the following code: if (Ext.BLANK_IMAGE_URL.substr(0, 5) != 'data:') { Ext.BLANK_IMAGE_URL = ' lib/extjs/ resources/images/default/s.gif'; } Ext.onReady(function(){ // do other stuff here });
[ 16 ]
Chapter 1
You're probably wondering why we need a spacer image at all. The user interface of Ext JS is created using CSS, but the CSS needs underlying HTML elements to style so that it can create the look and feel of the Ext JS components. The one HTML element that lays out inline, and is sizeable in both dimensions across all browsers is an image. So an image is used to size portions of the Ext JS Components. This is a part of how Ext JS maintains its cross-browser compatibility.
Using the Ext JS library
Now that we've added the Ext JS library to our page, we can start writing the code that uses it. In the first example, we will use Ext JS to display a message dialog. This might not sound like much, but we need to start somewhere.
Time for action
We can play with some Ext JS code by adding a script element in the head of our document, right after where the Ext JS library has been included. Our example will bring up an Ext JS style alert dialog: Getting Started Example <script src="lib/extjs/adapter/ext/ext-base.js"> <script src="lib/extjs/ext-all-debug.js"> <script> if (Ext.BLANK_IMAGE_URL.substr(0, 5) != 'data:') { Ext.BLANK_IMAGE_URL = 'lib/extjs/resources/images/default/s.gif'; } Ext.onReady(function(){ Ext.Msg.alert('Hi', 'Hello World Example'); });
[ 17 ]
Getting Started
We're not going to cover exactly what our example script is doing yet. First, let's make sure that the Ext JS library is set up properly by running this basic example. If we open up our page in a web browser, we should be able to see an alert message like the one shown as follows:
Just like a "real" dialog, we 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. We can also see that the Close and OK buttons get highlighted when we move the cursor over them—not bad for one line of code! Ext JS 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. In this example, we start with an empty document which contains no HTML. Ext JS does not require any pre-existing markup, and will create it as needed. However Ext JS also has the ability to import pre-existing markup, and use it as the basis for new widgets. This will be discussed in Chapter 2.
The example
Let's take a look at the example code, which we just ran: Ext.onReady(function(){ Ext.Msg.alert('Hi', 'Hello World Example'); });
After referring back to our discussion of JavaScript objects earlier in this chapter, you can see that this code fragment passes a function literal as a parameter to Ext.onReady. The Ext.onReady function accepts a function as its first parameter. It will call that function when the page has fully loaded, and the HTML document is ready to be manipulated. If you pass a function to Ext.onReady when the document is already fully initialized, it will be called immediately. [ 18 ]
Chapter 1
That passed function calls a method upon the Ext.Msg object which is a pre-initialized object which Ext JS provides to show simple dialogs. The Ext.Msg.alert function displays a dialog with the first parameter as the title and the second parameter as the message in the body.
Using the Ext.onReady function
Ext JS can only render widgets when the HTML document has been fully initialized by the browser. All Ext JS pages must only begin accessing the document within an Ext.onReady call. We could have written the code like this: var mainFunction = function(){ Ext.Msg.alert('Hi', 'Hello World Example'); }; Ext.onReady(mainFunction);
In this version, we can see more easily that it is a function reference which is passed to Ext.onReady. It's slightly more longwinded this way, but while still a beginner, this may be a useful style for us 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 JS library was not loaded. Usually, this is caused by having an incorrect path to one or more of the Ext JS 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, we should see an adapter folder along with the files ext-all.js and ext-all-debug.js in our lib/extjs folder. [ 19 ]
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 following example:
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. The best way to debug failures like this is to use the Firefox browser with the Firebug debugging add-on. This provides a display of the status of all network requests. If any fail, then they will be highlighted in red, as follows:
Adapters
When Ext JS 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). [ 20 ]
Chapter 1
This means that if we were previously using other libraries or if we felt some other base library was somehow superior or better suited our needs, we could continue using that library in conjunction with Ext JS by using the appropriate adapter. Either way, Ext JS functions the same, and all of the components will work identically, no matter which adapter we choose. Ext JS also has its own adapter which interfaces directly to the environment. If you have no preference for another library or framework, then go with the Ext JS built-in the adapter.
Using adapters
To use an adapter, we must first include the external library that we want to use, and then include the related adapter file that is located in the adapter's folder of the Ext JS SDK. Our example code uses the Ext JS adapter. To use any of the other libraries, just replace the default Ext JS adapter script include line with the lines for the specific libraries, as shown below: Default Ext JS adapter: <script src="lib/extjs/adapter/ext/ext-base.js">
For jQuery, include the jQuery library file in the head of the document, along with any plugins you might use: <script src="lib/jquery.js"> <script src="lib/jquery-plugins.js"> <script src="lib/extjs/adapter/jquery/ext-jquery-adapter.js">
For YUI 2, include these files in the head. The utilities file is located in the build/ utilities folder of the YUI 2 library download: <script src="lib/utilities.js"> <script src="lib/extjs/adapter/yui/ext-yui-adapter.js">
For "Prototype + Scriptaculous", include the Prototype library, along with the Scriptaculous effects in the head: <script src="lib/prototype.js"> <script src="lib/scriptaculous.js?load=effects"> <script src="lib/extjs/adapter/prototype/ext-prototype-adapter.js"> script>
After the adapter and base libraries have been included, we just need to include the ext-all.js or ext-all-debug.js file. [ 21 ]
Getting Started
Localization
Ext JS Components can be displayed in our 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 JS Components in their own native language. The included language files are to be used as a starting point. So we should take the language file 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 for our custom components without it being overwritten when we upgrade the Ext JS 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.
A language other than English
The second option requires that we include one of the language files from the build/ locale folder. These language files are named to comply with the two letter ISO 639-1 codes, for example: ext-lang-XX.js. Some regional dialects are also available, such as a Canadian version of French and British version of English. This option works by overwriting the English text strings present in the ext-all.js file, so it should be included after all of the other library files, as shown below: <script src="lib/extjs/adapter/ext/ext-base.js"> <script src="lib/extjs/ext-all-debug.js"> <script src="lib/extjs/build/locale/ext-lang-es.js">
I have included the Spanish translations for this example. Let's see what our test page looks like now:
[ 22 ]
Chapter 1
Elements that are part of the UI have been localized—these generally include calendar text, date formats, 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 our copy of the ext-lang-XX.js file (where 'XX' is your two letter language code) or added to a new language file of your own. Another 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; any component that has already been created must be re-created to switch languages. In other words, we can't do it entirely in real time and watch it happen on the screen.
Ext JS online help
If you have a problem which cannot be solved by reading this book, then there are several sources of help available at the Ext JS website.
Online API docs
The API documentation contains information about every class and every method in the library. This is not the regular API documentation we have become used to with many JavaScript libraries which simply informs you that a class exists, and that a method exists.
[ 23 ]
Getting Started
Classes are fully described, and all configuration options, properties, methods, and events are listed with each one being expandable and having a full description of its purpose and usage. It is also a great example of a rich Ext JS application: http://www.extjs.com/deploy/dev/docs/
The FAQ
The FAQ contains solutions for hundreds of issues which real-world Ext JS users have encountered in the past. This is the second line of help: http://www.extjs.com/learn/Ext_FAQ
Ext JS forum
The online community for Ext JS is full of very knowledgeable people, and often, the Ext core developers are answering questions on the forum. The forum is the place to go to pose your question for assistance if the answer cannot be found in this book, in the API docs, or in the FAQ. The API docs and the FAQ are actively maintained by volunteers, and are updated in response to frequent forum questions. 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 using the Google forum search tool that is available in the Learn section of the Ext JS website. http://www.extjs.com/learn/ When asking questions in the forum, be sure to include as much detail about the error(s) as possible. •
Post in the Help section
•
Use a meaningful thread title
•
Ensure that you are using ext-all-debug.js, and position the exact text of an error message and only the relevant portions of your code
[ 24 ]
Chapter 1
Summary
In this chapter, we have covered the basics of what we need to do to get Ext JS 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 JS 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 JS widget. The main goal of this chapter was to get Ext JS installed and working, so we can start creating some really sweet widgets and web applications. In the following chapter we will learn how to create more functional widgets, and how to configure them to behave exactly as we require.
[ 25 ]
The Staples of Ext JS In this chapter, we will start to use and interact with Ext JS 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 JS widgets easily
•
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 JS. We will take a look at how the example given in the Chapter 1 worked, and will expand upon it. The following core functions of Ext JS will be used on every project that we work on during the course of this book: •
Ext.Msg: This function creates application-style message boxes for us
•
configuration objects: These define how Ext widgets will act
•
Ext.get: This function accesses and manipulates elements in the DOM
Meet the config object
In the examples of this chapter, we will be configuring our widgets using what's called a config object. This is the primary way to get Ext JS Components to look and act the way we need. The config objects provide the specification of the different options that are available for the class that is being used.
The Staples of Ext JS
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 TestFunction( '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 what the arguments represent
•
It provides less flexibility in dealing with optional arguments
The new way—config objects
In JavaScript, an object is simply a collection of names and values. An object literal may be coded into a JavaScript statement just as a string literal may be: var myObject = { propertyName: 'String value', otherPropertyName: 3.14159 };
This is an assignment statement which creates on-the-fly a new object which contains two properties. It sets the variable myObject to reference this new object (Remember our discussion of JavaScript assignments in Chapter 1). By using objects to configure classes, we are able to have a greater level of flexibility, and can tell how the class is being configured because configuration property names are mnemonics where possible, and convey their meaning. 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 our functions, the arguments no longer needs to be tied down to a specific place. var test = new TestFunction({ firstWord: 'three', secondWord: 'fixed', thirdWord: 'arguments' });
[ 28 ]
Chapter 2
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 our functions will not be harmed by the addition or subtraction of arguments at a later point. var test = new secondWord: }); var test = new secondWord: fourthWord: });
Here are some key things to remember when working with an object literal: •
A matched pair of curly brackets encloses the literal. Within the brackets there may be zero or more property definitions.
•
Each property definition consists of a name/value pair, with the name and value separated by a colon. As with regular variables, a property references the value assigned to it which may be any type of object.
•
Multiple properties are separated by commas.
•
The property values can reference any type of data, including Boolean, array, function, or even another object: { name0: true, name1: { name2: value2 } }
•
Square brackets identify an array, and simply contain a comma separated list of objects, each of which may be of any type: { name: [ 'one', true, 3 ]
}
It is important to think of an array as an array of references. Setting an array item does not move data. It sets the reference at that index to point to the specified object.
[ 29 ]
The Staples of Ext JS
What is a config object?
If you are familiar with CSS, JSON, or the JavaScript object literal, you'll notice that a config object looks similar to these, mostly because they are all the same type of thing. In fact, an Ext JS config object is simply a JavaScript object literal. The point is to give us a way to structure data so that it can easily be read by a programming language—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); } }
Widgets and classes
Ext JS has many "widgets". As mentioned in Chapter 1, these are implemented as classes. These include components such as a message box, grid, window, tree, form, and pretty much everything else that serves a particular user interface function. These will mostly be referred to as 'Components'. There are also data, utility, and other base classes to aid with development. Methods like onReady are part of the core functions. We only refer to classes that provide a specific user interface role as "widgets"—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. In this example we will use a slightly more complex config object to describe how we want the dialog to look and behave: Ext.onReady(function(){
[ 30 ]
Chapter 2 Ext.Msg.show({ title: 'Milton', msg: 'Have you seen my stapler?', buttons: { yes: true, no: true, cancel: true } }); });
As we did in the previous chapter, we have passed a function which performs our desired actions into the onReady function. We configure a dialog using a config object. The config object used for this dialog has three elements, the last of which is a nested object which configures the three buttons we want to be shown. Here is how our example 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 JS 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 core Ext JS functions and Components we have just used: •
Ext.onReady: This was covered in the previous chapter. This function calls
•
Ext.Msg.show: This is the method used for showing a dialog in various different ways. In this case the Ext.Msg class is a singleton that shares one
a passed-in function only when DOM is ready for use.
single dialog instance. It takes care of everything needed to have a simple working dialog. There are other methods that can be used for common dialog types, such as prompt, alert, and wait which will help us save time. We will cover these in just a minute. [ 31 ]
The Staples of Ext JS
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, no: true, cancel: true } }); });
Again, we have passed an anonymous function into Ext.onReady. If we were executing a function that will be used again, then we could define and call it like this: var stapler = function(){ 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 like the one above, or even larger classes made up of many functions. The buttons config 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'}, or use the constants provided by Ext JS, such as Ext.Msg.YESNO.
[ 32 ]
Chapter 2
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 object to have an icon property, and an fn property which references a button handler function. 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 property just needs our style name as the value, milton-icon. We have also included a function to be executed when the user clicks on any of the buttons in the dialog. This function is created as an anonymous function, but the property could also reference a function that was defined elsewhere, possibly as part of a class. In this case, the function is merely used to alert the user which button was pressed, so we will keep it as an anonymous function: 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); } });
[ 33 ]
The Staples of Ext JS
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 JS makes the coding of web applications easier for us.
Time for (further) 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', 'I\'m going to burn the building down!'); break; case 'cancel': Ext.Msg.wait('Saving tables to disk...','File Copy'); break; } }
Note how an apostrophe may be inserted into an apostrophe-delimited string by prefixing the character with a backslash. The same principle applies to inserting a "double" quote into a quote-delimited string.
Remember those built in dialog types I mentioned earlier? Well we just used some of them. They offer us pre-configured dialogs which let us accomplish some common tasks without spending time writing the config needed for each standard scenario. Click OK and we 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. [ 34 ]
Chapter 2
Click No and we 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". Sometimes it's the little things that make us the happiest.
Click the Cancel button (or click the close button or press the Escape key) and we will get a wait message that's using a progress dialog.
The progress dialog we are using can be controlled by Ext JS 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 JS. Typically the OK or Yes button will be the default action. So pressing Enter on our keyboard will trigger that button, and pressing Tab will move us through the buttons and other items in the dialog.
[ 35 ]
The Staples of Ext JS
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 is 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;
If the user types 'the office' into the prompt box, the result will be as shown below:
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() { [ 36 ]
Ext JS 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. These are part of the Ext Core library. Of these functions, get is one of the most used. Var myDiv = Ext.get('my_id');
This gives us access to an element in the document with the ID, my_id by wrapping it in an Ext.Element object which provides cross-browser methods to work with DOM elements. 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 look 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 DOM element IDs must be unique. So once we have used my_id, we cannot use this ID again in our document. If duplicate IDs exist in our document, then our results will be unpredictable. When creating the DOM structure of widgets, Ext JS creates and tracks its own IDs. It is hardly ever necessary to create them on our own. One exception is when using HTML scaffolding—pre-existing HTML elements which are to be imported into Ext Components. Having duplicate IDs in our document can lead to strange behavior, such as a widgets always showing up in the upper-left corner of the browser, and must therefore be avoided.
Minimizing memory usage
If an element is not going to be repeatedly used, then we can avoid the caching mechanism which Ext.get uses to optimize element lookups. We can use something called a "flyweight" to perform simple tasks, which results in higher speed by not clogging up the browser's memory with cached elements which will not be needed again. [ 38 ]
Chapter 2
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. A flyweight should never be stored in a variable and used again. 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.
Can we use our own HTML?
As mentioned earlier, pre-existing HTML elements may be "imported" into Ext JS widgets. The easiest way is to configure an Ext Panel with a contentEl. The following page creates a Panel which uses a pre-existing HTML element as its content by specifying the element's ID as the contentEl: <style type="text/css"> .my-panel-class { font-family: tahoma,sans-serif; } <script type="text/javascript" src="../lib/extjs/adapter/ext/ ext-base.js"> <script type="text/javascript" src="../lib/extjs/ext-all.js" > [ 39 ]
The Staples of Ext JS <script type="text/javascript"> Ext.onReady(function(){ new Ext.Panel({ renderTo: Ext.getBody(), title: 'Panel with pre-existing content', height: 400, width: 600, cls: 'my-panel-class', contentEl: 'main-content' }); });
This is some pre-existing content.
This element is "imported" and used as the body of a Panel
Another way to do this is to use the Ext.BoxComponent class to encapsulate an existing element of a page. The BoxComponent class is a very lightweight widget which simply manages an HTML DIV as an Ext widget. As mentioned, it will create its own HTML structure if required, but we can configure it to use an existing element as its main element. In the following example we use a Panel to contain the BoxComponent: <style type="text/css"> .my-panel-class { font-family: tahoma,sans-serif; } <script type="text/javascript" src="../lib/extjs/adapter/ext/ext-base. js"> <script type="text/javascript" src="../lib/extjs/ext-all.js"> <script type="text/javascript"> Ext.onReady(function(){ new Ext.Panel({ renderTo: Ext.getBody(), title: 'Panel with pre-existing content',
This element is "imported" and used as the body of a Panel
Both the previous examples will produce the following output:
[ 41 ]
The Staples of Ext JS
Summary
Using only a few lines of code, we have created a fun program that will keep us 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 more of the Ext JS widgets. But the real point here is that the configuration object is something that is very fundamental when using Ext JS. So the quicker we can wrap your heads around it, the better off we 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.
[ 42 ]
Forms In this chapter, we will learn how to create Ext JS forms, which are similar to the HTML forms that we use, but with a greater level of flexibility, error checking, styling, and automated layout. This functionality is encapsulated in the FormPanel class. This class is a panel because it extends the Panel class, and so it can do everything a panel can do. It also adds the ability to manage a set of input fields that make up the form. We will use some different form field types to create a form that validates and submits form data asynchronously. Then we will create a database-driven, drop-down Combo Box, 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
A form has two main pieces, the functionality that performs form like actions, such as loading values and submitting form data, and the layout portion which controls how the fields are displayed. The FormPanel that we will be using combines these two things together into one easy-to-use Component, though we could use the form layout by itself if needed, which we will take a closer look at in Chapter 7.
Forms
The possibilities are endless with Ext JS form fields. Key listeners, validation, error messages, and value restrictions are all built-in with simple config options. Customizing a form for our own specific needs can be done easily, which is something we will cover later on in this chapter. To start with, we will be adding fields to a standard form. Here are some of the core form components that we will become familiar with: •
Ext.form.FormPanel: A specialized Panel subclass which renders a FORM
•
Ext.form.BasicForm: The class which the FormPanel uses to perform
•
Ext.form.Field: A base class which implements the core capabilities that all form fields need. This base class is extended to create other form field types.
element as part of its structure, and uses a form-specific layout to arrange its child Components.
field management, and which performs the AJAX submission and loading capabilities.
Extending is a major part of the Ext JS architecture, and just means that common functionality is created in a single place and built upon (extended) to make more specific and complex components.
Our first form
To start with, let's create a form with multiple field types, a date field, validation, error messages, and AJAX submission—just a simple one for our first try. For this example, our fields will be created using an array of config objects instead of an array of instantiated Ext.form.Field components. This method will work just the same as an instantiated component, but will take less time to code. Another benefit which may come in handy is that a config object used to create items in this way may be stored and reused. A basic HTML page like the one we used in the previous example will be used as a starting point. The standard Ext JS library files need to be included and, as with everything we create in Ext JS, 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: Ext.getBody(), frame: true, title: 'Movie Information Form', width: 250, items: [{ [ 44 ]
When we run this code in a browser, we end up with a form panel that looks like this:
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. As it is a Component (inherits from the Component base class), it also has a renderTo config, which defines into which existing element the form is appended upon creation. As we start to explore layouts in Chapter 7, the renderTo config will be used much less often.
Child items
The items config option is an important one as it specifies all of our child Components—in this case they are all input fields. The ability to house child Components is inherited from the Container level of the Component family tree—our FormPanel in this case. The items config is an array which may reference either component config objects, or fully created Components. In our example, each array element is a config object which has an xtype property that defines which type of Ext JS component will be used: text, date, number, or any of the many others. This could even be a grid or some other type of Ext JS component. [ 45 ]
Forms
Be aware that the default xtype is a panel. Any {…} structure with no xtype property, as an element in an items config, results in a panel at that position. But where do xtypes come from, and how many of them are there? An xtype is just a string key relating to a particular Ext JS Component class, so a 'textfield' xtype refers to its Ext.form.TextField counterpart. Here are examples of some of the form field xtypes that are available to us: • • • • • •
The key point to remember is that child items may be any class within the Component class hierarchy. We could easily be using a grid, toolbar, or button—pretty much anything! (Though getting and setting their values is a different story.) This is an illustration of the power of the Component class hierarchy mentioned in Chapter 2. All Ext JS component classes, because they share the same inheritance, can be used as child items, and be managed by a Container object—in this case a form. 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 display above the field. The name config is just the same as its HTML counterpart and will be used as the parameter name when sending form data to the server. 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.
Creating the subsequent date field isn't much different from the text field we just made. Change the xtype from textfield to datefield, update the label and name, and we're done. [ 46 ]
A few of our sample fields could have validations that present the users with error indicators if the user does something wrong, such as leaving a field blank. 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 JS field also has its own set of specialized configurations that are specific to the data type of that field. The following are some examples of the options available: Field Type numberfield
Option
Value type
Description
decimalPrecision
Integer
How many decimal places to allow
datefield
disabledDates
Array
An array of date strings that cannot be selected
timefield
increment
Integer
How many minutes between each time option
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] } [ 47 ]
Forms
In this example, every day 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 configuration options for each field type can be found in the API reference. There are many more configuration options, specific to each specialized Ext JS Component class. To easily find what options are available in the online documentation, use the Hide inherited members button at the top right. This will hide config options from the base classes of the Component's heritage, and only show you options from the Component you are interested in:
[ 48 ]
Chapter 3
Built-in validation—vtypes
Another more flexible type of validation is the vtype. This can be used to validate and restrict user input, along with reporting back error messages. It will work in just about any scenario you can imagine because it uses a function and regular expressions to do the grunt work. Here are some built-in vTypes that can come in handy: •
email
•
url
•
alpha
•
alphanum
These built-in vtypes come included by default, and we can use them as a starting point for creating our own vtypes, which we will cover in the next section. The following is an example of an alpha vtype being used on a text field: 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] }] }); }); [ 49 ]
Forms
All we did was add a vtype to the director field. This will validate that the value entered is composed of only alphabet characters. 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 alphabetic characters, with just one space between the first and last names. Capitalizing the first characters in the names could possibly make them look pretty. We will create our own custom vtype soon enough, but first let's take a look at displaying error messages. A search of the Ext JS 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 them instead of the default. One built-in option is to display the error message in a balloon using an Ext JS object called QuickTips. This utilizes the standard squiggly line, but also adds a balloon message that pops up when we mouse over the field, displaying error text within.
[ 50 ]
Chapter 3
We just need to add one line of code before our form is created that will initialize the QuickTips object. Once the QuickTips object is initialized, the form fields will automatically use it. This is just one simple statement at the beginning of the script: Ext.QuickTips.init();
This is all that needs to happen for our form fields to start displaying error messages in a fancy balloon. The QuickTips object can also be used to display informational tool tips. Add a qtip attribute which contains a message to any DOM node in the document, and when we hover the mouse over that element, a floating message balloon is displayed. Without the red "error" styling shown above.
Custom validation—creating our own vtype
In this section we will create a custom vtype to perform validation based upon matching the input field's value against a regular expression. Don't be afraid to experiment with regular expressions. They simply match characters in a string against a series of patterns which represent classes of characters, or sequences thereof. Our vtype will check that the input consists of two words, each of which begins with a capital letter, and is followed by one or more letters. A hint for working with regular expressions: use the browser's JavaScript debugging console to test regular expressions against strings until you come up with one that works.
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
[ 51 ]
Forms
As soon as we figure out the regular expressions we need to use, it's fairly straightforward to create our own vType—so let's 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? The Ext.apply function is just a simple way of copying properties from one object into another—in this case from an object literal which we create, into the Ext.form.VTypes object. Ext.apply(Ext.form.VTypes, { nameVal: /^[A-Z][A-Za-z]+\s[A-Z][A-Za-z]+$/, nameMask: /[A-Za-z ]/, nameText: 'Invalid Director Name.', name: function(v) { return this.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 value entered into our form field. In this case it's a regular expression used to test the value: nameVal: /^[A-Z][A-Za-z]+\s[A-Z][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: nameMask: /[A-Za-z ]/,
Then, we have the text to be displayed in a balloon message if there is an error: nameText: 'Invalid Director Name.',
And finally, the part that pulls it all together—the actual function used to test our field value: name: function(v) { return this.nameVal.test(v); }
Put all this together and we have our own custom vtype without much effort —one that can be used over and over again. We use it in just the same way as the 'alpha' vtype: vtype: 'name'
[ 52 ]
Chapter 3
The result should look like this:
Even though our example used a regular expression to test the value, this is not the only way vTypes can work. The function used can perform any type of comparison we need, and simply return true or false.
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, 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 only capital letters: { xtype: 'textfield', ... 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 updating. 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!
Radio buttons and check boxes
Radio buttons and check boxes can be clumsy and hard to work with in plain HTML. However, creating a set of radio buttons to submit one of a limited set of options is easy in Ext JS. Let's add a RadioGroup component to the form which submits the film type of the movie being edited. [ 53 ]
Forms
It's not a button, it's a radio button
The code below adds a set of radio buttons to our form by using the 'radiogroup' xtype. We configure this RadioGroup to arrange the buttons in a single column. The default xtype of items in a RadioGroup is 'radio' so we do not need to specify it: { xtype: 'radiogroup', columns: 1, fieldLabel: 'Filmed In', name: 'filmed_in', items: [{ name: 'filmed_in', boxLabel: 'Color', inputValue: 'color' },{ name: 'filmed_in', boxLabel: 'Black & White', inputValue: 'B&W' }] }
The RadioGroup's name allows the form to be loaded using a single property value. The name in the individual button configs allows submission of the selected inputValue under the correct name. These radio buttons work much like their HTML counterparts. Give them all the same name, and they will work together for you. The additional config for boxLabel is needed to show the informative label to the right of the input.
X marks the checkbox
Sometimes, we need to use checkboxes for Boolean values—sort of an on/off switch. By using the checkbox xtype, we can create a checkbox.
Both the radio and checkbox have a 'checked' config that can be set to true or false to check the box upon creation. { xtype: 'checkbox', fieldLabel: 'Bad Movie', name: 'bad_movie', checked: true }
Sets of checkboxes can be arranged in columns by containing them in a CheckboxGroup. This is configured in exactly the same way that a RadioGroup is, but the default xtype of its child items is 'checkbox'.
The ComboBox
The ComboBox class emulates the functionality of the HTML SELECT element. It provides a dropdown box containing a list from which one value can be selected. It goes further than simply imitating a SELECT box though. It has the ability to either provide its list from a loaded data store, or, it can dynamically query the server during typing to load the data store or demand to produce autosuggest functionality. First, let's make a combo using local data from a loaded data store. To do this, the first step is to create a data store. A data store is a client-side analogue of a database table. It encapsulates a set of records, each of which contains a defined set of fields. Full details about data stores are contained in Chapter 15. There are a few different types of data stores, each of which can be used for different situations. However, for this one, we are going to use an Array store, which is one of the shortcut data classes we will cover in more detail in both the Grids and Data chapters. The ArrayStore class is configured with the field names which its records are to contain and a two-dimensional array which specifies the data: var genres = new Ext.data.ArrayStore({ fields: ['id', 'genre_name'], data : [['1','Comedy'],['2','Drama'],['3','Action']] });
[ 55 ]
Forms
A few new config options are needed when we are setting up a combo box. •
store: This is the obvious one. The store provides the data which backs
•
mode: This option specifies whether the combo expects the data store
•
displayField: This option specifies the field name which provides the
•
valueField: This option specifies the field name which provides the value associated with the descriptive text. This is optional. If used, the hiddenName
up the combo. The fields of its records provide the descriptive text for each dropdown item, and the value which each item represents. to be pre-loaded with all available items ('local'), or whether it needs to dynamically load the store, querying the server based upon the typed characters ('remote').
descriptive text for each dropdown item.
option must be specified. •
hiddenName: This is the name of a hidden HTML input field which is used to
store and submit the separate value of the combo if the descriptive text is not what is to be submitted. The visible field will contain the displayField.
Just like the other fields in our form, we add the combo to our items config: { xtype: 'combo', hiddenName: 'genre', fieldLabel: 'Genre', mode: 'local', store: genres, displayField:'genre_name', valueField:'id', width: 120 }
This gives us a combo box that uses local, pre-loaded 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? A quick way to specify a few static options for a ComboBox is to pass an array to the store config. So if we wanted a ComboBox that had 'Yes' and 'No' as options, we would provide ['Yes','No'] as the store config value.
[ 56 ]
Chapter 3
A database-driven ComboBox
The biggest change that needs to happen is on the server side—getting our 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. 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: 0) { while ($obj = mysql_fetch_object($result)) { $arr[] = $obj; } } Echo '{rows:'.json_encode($arr).'}'; ?>
When we use remote data, there are a few more things that need to happen back on the JavaScript side. First, the data store needs to know what format the data is in. When we use shortcut store classes like ArrayStore, this is implicit (It's an array). In this example, we specify the format of the data being read 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' }) });
[ 57 ]
Forms
The data reader is configured using two config options: •
fields: This is an array of the field names which the records in the store will contain. For a JSON Reader, these names are also the property names within each row data object from which to pull the field values.
•
root: This specifies the property name within the raw, loaded data object which references the array of data rows. Each row is an object.
We can see the root and field properties specified in the configuration in this example of the returned JSON data: {rows:[ { "id":"1", "genre_name":"Comedy", "sort_order":"0" },{ "id":"2", "genre_name":"Drama", "sort_order":"1" },{ // snip...// }] }
We have also set up the proxy, a class which takes responsibility for retrieving raw data objects. Typically this will be an HttpProxy that retrieves data from a URL at 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 HttpProxy we are actually using Ajax. This requires that we have a web server running; otherwise Ajax will not work. Simply running our 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();
[ 58 ]
Chapter 3
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 });
TextArea and HTMLEditor
We are going to add a multiline text area to our movie information form, and Ext JS has a couple of options for this. We can either use the standard textarea that we are familiar with from using HTML, or we can use the HTMLEditor field, which provides simplistic rich text editing: •
textarea: Similar to a typical HTML textarea field
•
htmleditor: A rich text editor with a button bar for common
formatting tasks
We will use a couple of new config options for this input: •
hideLabel: This causes the form to not display a label to the left of the input field, thus allowing more horizontal space for the field.
•
anchor: This config is not a direct config option of the Field class. It is a "hint"
to the Container (in this case a FormPanel) that houses the field. It anchors the child item to the right or bottom borders of the container. A single value of '100%' anchors to the right border, resulting in a full-width child component. [ 59 ]
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: { xtype: 'htmleditor', name: 'description', hideLabel: true, height: 100, anchor: '100%' }
The HtmlEditor can now be seen occupying the full panel width at the bottom of the panel:
Listening for form field events
Ext JS makes it extremely simple to listen for particular events in a Component's lifecycle. These may include user-initiated events such as pressing a particular key, changing a field's value, and so on. Or they may be events initiated by code, such as the Component being rendered or destroyed. [ 60 ]
Chapter 3
To add event handling functions (which we call "listeners") to be called at these points, we use the listeners config option. This option takes the form of an object with properties which associate functions with event names. The events available to the listeners config option of a Component are not the same as the standard native DOM events which HTML offers. Some Components do offer events of the same name (for example a click event), but that is usually because they "add value" to the native DOM event. An example would be the click event offered by the Ext JS tree control. This passes to the listener the tree node that was clicked on. Adding our own listeners to DOM events of the constituent element of a Component is explained later in this chapter.
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(field, eventObj){ if (eventObj.getKey() == Ext.EventObject.ENTER) { movie_form.getForm().submit(); } } } }
The specialkey event is fired whenever a key related to navigation or editing is pressed. This includes arrow keys, Tab, Esc, and Enter. When an event is fired, the listener function associated with that event name is called. We want to check to see if it was the Enter key that was pressed before we take action, so we're using the event object—represented as the variable 'eventObj' in this example—to find out what key was pressed. The getKey method tells us which key was pressed, and the Ext.EventObject .ENTER is a constant that represents this key. With this simple listener and if statement, the form will be submitted when we press the Enter key.
[ 61 ]
Forms
ComboBox events
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_name'], data : [ ['0','New Genre'], ['1','Comedy'], ['2','Drama'], ['3','Action'] ] });
Then, we add the listener to our combo: { xtype: 'combo', name: 'genre', fieldLabel: 'Genre', mode: 'local', store: genres, displayField:'genre_name', width: 130, listeners: { select: function(field, rec, selIndex){ if (selIndex == 0){ Ext.Msg.prompt('New Genre', 'Name', Ext.emptyFn); } } } }
The listeners object in the code above specifies a function which is to be called when a combo item is selected. Each event type has its own set of parameters which are passed to listener functions. 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 [ 62 ]
Chapter 3
Inside our listener function, 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. The result should look like this:
A list of valid events to listen for can be found at the bottom of the API documentation page for each Component, along with the arguments the listeners are passed, which are unique to each event.
Buttons and form action
Now, we have quite a complex form with only one problem—it doesn't send data to the server, and we will want a way to reset the form, which was the actual point behind creating our form in the first place. To do this, we are going to add some buttons which will perform these actions. 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(form, action){ Ext.Msg.alert('Success', 'It worked'); [ 63 ]
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. movie_form.getForm().submit({ success: function(form, action){ Ext.Msg.alert('Success', 'It worked'); }, failure: function(form, action){ Ext.Msg.alert('Warning', 'Error'); } });
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: In order for our form submission to work properly, the HTML page must be run from a web server, not the file system.
The 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. [ 64 ]
Chapter 3
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 JSON string from the server would look like this: { success: false, errors: { title: "Sounds like a Chick Flick" } }
When the success property is set to false, it triggers the Ext JS form to read in the error messages from the errors property, and apply them to the form's validation to present the user with error messages. 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 form's 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 for the fields. A name/value pair exists in the errors object for each form field's error. [ 65 ]
Forms
Our example response also passes an errormsg property, which is not used by the form, but is going to be accessed separately to present our own error message in an Ext JS Message Box. The error objects messages are handled automatically by the form, so 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(form, action){ Ext.Msg.alert('Success', 'It worked'); }, failure: function(form, action){ Ext.Msg.alert('Warning', action.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 the Ext JS form Component we were using, and the second is an Ext JS action object. Let's take a look at what's available in the Ext JS action object: Option failureType
Data type
Description
String
The type of failure encountered, whether it was on the client or server
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
[ 66 ]
Chapter 3
Now that we know what is available to the failure handler, we can set up some simple error checking: failure: function(form, action){ if (action.failureType == Ext.form.Action.CLIENT_INVALID) { Ext.Msg.alert("Cannot submit", "Some fields are still invalid"); } else if (action.failureType === Ext.form.Action.CONNECT_FAILURE) { Ext.Msg.alert('Failure', 'Server communication failure: '+ action.response.status+' '+action.response.statusText); } else if (action.failureType === Ext.form.Action.SERVER_INVALID) { Ext.Msg.alert('Warning', action.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 inspecting the result and response properties.
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 is what 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—our form.
Static data load
We can take data from somewhere in our code, a variable for instance, or just plain static text, and display it as the value in our form field by calling the setValue method. This single line of code will set a field's value: movie_form.getForm().findField('title'). setValue('Dumb & Dumber');
[ 67 ]
Forms
Once we start working with larger forms with many fields, this method becomes a hassle. That's why we also have the ability to load our data in bulk via an AJAX request. The server side would work much as it did when we loaded the combo box: 0) { $obj = mysql_fetch_object($result); Echo '{success: true, data:'.json_encode($obj).'}'; }else{ Echo '{success: false}'; } ?>
This would return a JSON object containing a success property, 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 method: movie_form.getForm().load({ url:'data/movie.php', params:{ id: 1 } });
[ 68 ]
Chapter 3
Providing the load method 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.
DOM listeners
As mentioned when discussing adding listeners to Components, adding DOM event listeners to the constituent HTML elements of a Component is a different matter. Let's illustrate this by adding a click listener to the header of the FormPanel. When a panel is configured with a title, an element which houses that title is created when the Component is rendered. A reference to that element is stored in the panel's header property. The key point here is that no elements exist until the Component is rendered. So we use a listener function on the render lifecycle event to add the click listener: var movie_form = new Ext.FormPanel({ url: 'movie-form-submit.php', renderTo: document.body, frame: true, title: 'Movie Information Form', listeners: { render: fuction(component) { component.header.addListener({ click: function(eventObj, el) { Ext.Msg.alert("Click event", "Element id " + el.id + " clicked"); } }); } }
Let's take the time to read and understand that code. We specify a listener function for the render event. The listener function is passed as a reference to the Component which just rendered. At this point, it will have a header property. This is an Ext.Element object which was talked about in Chapter 2. We call the addListener method of the Ext.Element class to add our click listener function. The parameter to this class should be familiar to you. It is a standard listeners config object which associates an event name property with a listener function which is called on every mouse click.
[ 69 ]
Forms
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.
[ 70 ]
Menus, Toolbars, and Buttons 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. By following the examples in this chapter we will learn how to use menus, both as components in their own right—either static or floating as popups—and as dependent menus of buttons. We will then learn how to use toolbars, which contain buttons that call a function on click, or that pop up a submenu on click, or cycle between several submenu options on each click. The primary classes we will cover in this chapter are: •
Ext.menu.Menu: A Container class which by default displays itself as a
•
Ext.Toolbar: A Container class which arranges its child Components
•
Ext.Button: The primary handler for button creation and interaction. A Component class which renders a focusable element which may be configured with a handler function which is called upon mouse click, or a menu to display upon mouse click.
•
Ext.SplitButton: A subclass of button which calls a handler function
popup component, floating above all other document content. A menu's child items behave in a similar manner to buttons, and may call a handler function on mouse click. A menu may also be used as a static component within a page. horizontally in the available width, and manages overflow by offering overflowed Components in a popup menu.
when its main body is clicked, but also renders an arrow glyph to its right which can display a dropdown menu when clicked.
Menus, Toolbars, and Buttons
•
Ext.CycleButton: A subclass of SplitButton which cycles between checking individual menu options of its configured menu on each click. This is similar to cycling through different folder views in Windows Explorer.
•
Ext.ButtonGroup: A Panel class which lays out child Components in a tabular format across a configurable number of columns.
What's on the menu?
We will begin by introducing the Menu class which will be used in all following examples. We are going to demonstrate usage of the Menu class as both a static component within a page, and as a popup. Both menus will be configured with the same options by using a technique which was suggested in Chapter 2: we define a variable called menuItems to reference an array which specifies the menu's items, and use it in both cases. The Menu class inherits from Container, so any menu options are child Components specified in the items config. It also inherits the usual Component config options such as renderTo, and the width option. The static menu will be rendered to the document body, and in order for it to be rendered as a visible, static element in the document, we configure it with floating: false. So the configuration we end up with is as follows: new Ext.menu.Menu({ renderTo: document.body, width: 150, floating: false, items: menuItems });
The popup menu needs no extra configuring aside from its items. We do need to decide when and where to display it. In this case we will add a contextmenu (right click) event listener to the document, and show the menu at the mouse event's position: var contextMenu = new Ext.menu.Menu({ items: menuItems }); Ext.getDoc().on({ contextmenu: function(eventObj) { contextMenu.showAt(eventObj.getXY()); [ 72 ]
Chapter 4 }, stopEvent: true });
When we run this example, the static menu will be visible. When we right click on the document, the result should be the two menus shown below. Notice how only the second, popup menu has a shadow to indicate that it floats above the document.
The menu's items
The menuItems variable references an array which should be familiar by now. Just like the items config of a FormPanel used in the previous chapter, it's a list of child Components or config objects. In a menu, a config object with no xtype creates a MenuItem Component. The MenuItem class accepts the following config options in addition to those it inherits: •
icon: The URL of an image to display as an icon
•
iconCls: A CSS class name which allows a stylesheet to specify a background image to use as an icon
•
text: The text to display
•
handler: A function to call when the item is clicked
•
menu: A Menu object, or Menu configuration object or an array of menu
items to display as a submenu when the item is clicked
Because a menu inherits from Container, it can accept other Components as child items. If some complex, menu option dependent input is required, a menu may be configured with a panel as a child item. The menu config of "Menu Option 2" we're creating next contains a FormPanel as its sole child item: { text: 'Menu Option 2', iconCls: 'flag-green', menu: { [ 73 ]
The configurations in the above object will mostly be familiar by now. There is one extra config we use for the menu which contains the FormPanel. •
plain: Specify as true so that the menu does not have to show the incised
line for separating icons from text
The panel within the menu has the following configs: border: Specify as false to produce a panel with no borders. bodyStyle: A CSS style string to apply to the document body. We want to make
it transparent to allow the menu to show, and we apply padding.
[ 74 ]
Chapter 4
The ComboBox must render its dropdown list to the menu's element so that clicking on the list does not trigger the menu to hide: •
GetListParent: This is a function which a ComboBox may be configured
with. It must return the HTML element to render the dropdown list into. By default a ComboBox renders its dropdown into the document. We call the up function of the Ext.Element class to find the ancestor node of the combo's element which is a DIV which has the CSS class "x-menu".
The FormPanel as a child of a menu will display like this:
A toolbar for every occasion
An Ext JS Panel, and every Ext JS Component which inherits from the Panel class (This includes Window, TreePanel, and GridPanel) can be configured to render and manage a toolbar docked above, or below the panel's body—or both if really necessary. These are referred to as the top and bottom toolbars, or tbar and bbar for short. Panels and subclasses thereof may also be configured with a footer bar which renders buttons right at the bottom of the panel—below any bottom toolbar. The Toolbar class is also an Ext JS Component in its own way, and may when necessary be used on its own, or as a child Component of any Container. Our second example renders a toolbar standalone into the body of the document. We will use all the main button types to illustrate their usage before moving on to add handlers to react to user interaction. The toolbar will contain the following child components: •
A basic button
•
A button configured with a menu which is displayed when the button is clicked
•
A SplitButton which will display a menu only when its arrow glyph is clicked
•
A CycleButton which on click, cycles between three different options
•
A pair of mutually exclusive toggle buttons of which only one may be in a "pressed" state at once [ 75 ]
As usual, everything is inside our onReady event handler. The items config holds our toolbar's entire child Components—I say child Components and not buttons because as we now know, the toolbar can accept many different types of Ext JS Components including entire forms or just form fields—which we will be implementing later on in this chapter. The result of the above code looks like this:
The default xtype for each element in the items config is button. We can leave out the xtype config element if button is the type we want, but I like to include it just for clarity.
Button configuration
In addition to inherited config options, a button accepts the following configurations which we will be using in the following examples for this chapter: •
icon: The URL of an image to display as an icon
•
iconCls: A CSS class name which allows a stylesheet to specify a
background image to use as an icon
•
text: The text to display
•
handler: A function to call when the button is clicked
•
menu: A Menu object, or Menu configuration object, or an array of menu
•
enableToggle: Specify as true to make a single button toggleable between
•
toggleGroup: A mnemonic string identifying a group of buttons of which
•
toggleHandler: A function to be called when a button's "pressed" state
items to display as a submenu when the button is clicked pressed and unpressed state
only one may be in a "pressed" state at one time
is changed
[ 77 ]
Menus, Toolbars, and Buttons
A basic 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. A handler function is called when the button is clicked. Here is the most basic configuration of a button: { xtype: 'button', text: 'Button', handler: functionReference }
The following screenshot shows what happens when the mouse is hovered over the Button button:
Button with a menu
A button may be configured to act as a trigger for showing a dropdown menu. If configured with a menu option, clicking the button displays a menu below the button. The alignment of the menu is configurable, but defaults to being shown below the button. Each option within the menu may itself be configured with a menu option allowing a familiar cascading menu system to be built very easily. The following is a config for a button which displays a dropdown menu upon click: { xtype: 'button', text: 'Button', menu: [{ text: 'Better' },{ text: 'Good' },{ text: 'Best' }] }
The following screenshot shows what happens when the Menu Button is clicked on, and the mouse is hovered over the Best option: [ 78 ]
Chapter 4
Split button
A split button may sound like a complex component, but it is no more complex to create than a plain button. By using a split button we get the ability to specify a click handler which is called when the main body of the button is clicked. But we can also configure in a menu which will be displayed when the arrow glyph to the right of the main body is clicked. { xtype: 'split', text: 'Split Button', menu: [{ text: 'Item One' },{ text: 'Item Two' },{ text: 'Item Three' }] }
The following screenshot shows what happens when the Split Button's arrow glyph is clicked:
Toggling button state
Sometimes it is useful to have a button which "sticks" in the pressed state to indicate switching some state within our app. To enable a single button to be clicked to toggle between the pressed and unpressed state, configure the button with enableToggle: true. [ 79 ]
Menus, Toolbars, and Buttons
If a set of buttons are to toggleable, but only one may be pressed at once, configure each button in that set with a toggleGroup. This is an ID string which links buttons which enforce this rule. Toggleable buttons may be configured with a toggleHandler function which is called whenever the button's state changes in either direction. { text: 'Horizontal', toggleGroup: 'orientation-selector' }, { text: 'Vertical', toggleGroup: 'orientation-selector' }
This code produces the pair of buttons below in which only one orientation may be selected at once, Horizontal or Vertical:
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 item to the toolbar. This item is sometimes referred to as a 'greedy spacer'. If we want to have items split up between both the left and right sides, we can also use a fill, but place it between items: { xtype: 'tbfill' }
Pop this little guy in a toolbar wherever you want to add space and it 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 a visual vertical divider, 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: [ 80 ]
Chapter 4 { xtype: 'tbspacer' }
A divider can be added in the same way: { xtype: 'tbseparator' }
Shortcuts
Ext JS 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 that is distributed in the src folder of the SDK download, and see what you can find. Here is a list of the commonly-used shortcuts: Component
Shortcut
Description
Fill
'->'
The fill that is used to push items to the right side of the toolbar.
Separator
'-' or 'separator'
A vertical bar used to visually separate items.
Spacer
''
Empty space used to separate items visually. The space is two pixels wide, but can be changed by overriding the style for the xtb-spacer CSS class.
TextItem
'Your Text'
Add any text or HTML directly to a toolbar by simply placing it within quotes.
[ 81 ]
Menus, Toolbars, and Buttons
Icon buttons
The standard button can be used as an icon button like the ones we see used in text editors to make text bold or italic—simply a button with an icon and no text. 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' }
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' }
Another method for creating an icon button is to apply a CSS class to it that contains a background image. With this method we can keep the references to images in our CSS instead of our JavaScript, which is preferable whenever possible. { xtype: 'tbbutton', iconCls: 'bomb' }
The CSS to go along with this would have a background image defined, and the CSS 'important' flag set. .bomb { background-image: url( images/bomb.png ) !important; }
[ 82 ]
Chapter 4
Button events and handlers—click me!
A button needs to do more than just look pretty—it needs to react to the user. This is where the button's handler and events come in. A handler is a function that is executed when a button is clicked. The handler config is where we add our function, which can be an anonymous function like below or a method of a class—any function will do: { xtype: 'button', text: 'Button', handler: function(){ Ext.Msg.alert('Boo', 'Here I am'); } }
This code will pop up an alert message when the button is clicked. Just about every handler or event in Ext JS passes a reference to the component that triggered the event as the first argument. This makes it easy to work with whichever component that fired this handler, calling the disable method on it. { 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 non-functional. We can have more fun than just disabling a button. Why don't we try something more useful?
Loading content on menu item click
Let's take our button click and do something more useful with it. For this example, we are going to add a config option named helpfile to each menu item that will be used to determine what content file to load in the body of our page:
Note the helpfile config option that we have added to each of the menu items configs. 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 are copied into the Component upon construction, and become properties of the Component. In this case, we are using a config property as a reference to 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 object. var Movies = function() { var helpbody; return { showHelp : function(btn){ Movies.doLoad(btn.helpfile); }, doLoad : function(file){ helpbody = helpbody || Ext.getBody().createChild({tag:'div'}); helpbody.load({ url: 'html/' + file + '.txt' }); }, setQuality: function(q) { helpbody = helpbody || Ext.getBody().createChild({tag:'div'}); helpbody.update(q); [ 84 ]
Chapter 4 } }; }();
This piece of code is worth reading a few times until you understand it. It illustrates the creation of a singleton object. Notice that the function has () after it. It is called immediately returning an object, instead of a class that could be instantiated. The Movies variable is assigned to reference whatever that function returns. It does not reference the function. The object returned from that function contains three functions. These are the "member functions" of the Movies singleton object. It offers utility methods which our example code will use. All the functions have access to the helpbody variable because they were declared within the same function it was declared in. They can use that variable, but it is not available to outside code. This is an important capability of singleton objects built in this slightly confusing manner.
Form fields in a toolbar
As the Toolbar class inherits from Container, it can be used to house any Ext JS Component. Naturally, form fields and combo boxes are very useful items to have on a toolbar. These are added as child items just as with all Container classes we have encountered so far: { xtype: 'textfield' }
In the same way as we created form fields in the last chapter, we add 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 } }
[ 85 ]
Menus, Toolbars, and Buttons
This listener is added directly to the form field's 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(field, keyEvent){ if (keyEvent.getKey() == Ext.EventObject.ENTER) { Movies.doLoad(field.getValue()); } }
Now the text field in our toolbar that enables us to type in the name of the text file to load should look like the following. Try typing in some of the samples used in our menu, such as director or title:
Buttons don't have to be in a toolbar
Up until now, we have been dealing with buttons in toolbars. But because buttons are part of the Ext JS Components class hierarchy, they can be used as child items of any Container, not just toolbars. When used outside of a toolbar, they are themed in a slightly different way. A button can be added to the items array, just like we would add a panel or other child components. new Ext.Window({ title: 'Help', id: 'helpwin', width: 300, height: 300, items: [{ xtype: 'button', text: 'Close', handler: function(){ Ext.getCmp('helpwin').close(); } }] }).load("html/director.txt ").show();
The above code fragment would produce the following display:
[ 86 ]
Chapter 4
Toolbars in panels
As mentioned at the beginning of this chapter, toolbars can be configured into all Ext JS Panel classes (Panel, GridPanel, TreePanel, and Window) docked either above or below the panel, body (or both) Panel classes, such as the window and the grid, have a top and bottom toolbar config, along with a buttons config: •
tbar: A toolbar, or toolbar configuration object, or an array specifying toolbar
•
bbar: A toolbar, or toolbar configuration object, or an array specifying toolbar
•
fbar: Also may be specified as buttons. A toolbar, or toolbar configuration object, or an array specifying toolbar child items, which specifies a toolbar to be placed into the panel's footer element.
child items. This causes a toolbar to be docked above the panel's body.
child items. This causes a toolbar to be docked below the panel's body.
If we wanted to place a toolbar at the top of a window, we would specify a tbar config option which references an array of toolbar child config objects like this: new Ext.Window({ title: 'Help', width: 300, height: 300, renderTo: document.body, closeAction: 'hide', layout: 'fit', tbar: [{ text: 'Close', handler: function(){ winhelp.hide(); } [ 87 ]
When this window is activated by clicking the Director help menu entry, this produces the following display:
Of course if we wanted that same toolbar on the bottom of the window, we can change from a tbar to a bbar. We can also specify a toolbar (or config of a toolbar, or toolbar's items) to be rendered into the footer area of a panel or window. Buttons are themed as normal buttons in a footer bar, and by default are aligned to the right: new Ext.Window({ title: 'Help', width: 300, height: 300, renderTo: document.body, closeAction: 'hide', layout: 'fit', fbar: [{ text: 'Close', handler: function(){ winhelp.hide(); } }] });
If the help window was configured in this way, it would display like this:
[ 88 ]
Chapter 4
Ext JS also has a custom toolbar for paged grids which 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.
Toolbars unleashed
As mentioned above, the toolbar class is a special type of Container class which lays out its child components in a particular, preconfigured way. We can use it to house more complex Components, and we can experiment with using different layout types to size and arrange the child Components in different ways. Layouts will be covered in Chapter 7, but we can see some of their power in our final toolbar example. In the final example, we use the hbox (horizontal box) layout to arrange ButtonGroups across the toolbar, and force them to be vertically sized to the size of the highest one, to produce a pleasing, even ribbon effect. new Ext.Panel({ title: 'Panel with heavyweight toolbar', renderTo: document.body, width: 600, height: 400, tbar: new Ext.Toolbar({ layout: { type: 'hbox', align: 'stretchmax' }, items: [{ xtype: 'buttongroup', title: 'Group 1', columns: 1, items: [{ [ 89 ]
This example illustrates the concept that a toolbar is a Container which may contain any Component, and also that a ButtonGroup is a Container with the same heritage. The first three ButtonGroups contain Buttons; the last one for a slight change of style contains MenuItems.
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. In the next chapter we will get to know one of the most powerful and useful Components in the Ext JS Component family, The GridPanel.
[ 90 ]
Displaying Data with Grids The grid is, without doubt, one of the most widely-used components of Ext JS. 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 JS takes that concept and makes it flexible and downright amazing! In this chapter we will be: •
Using a GridPanel to display structured data in a user-friendly manner
•
Reading data from the server (which provides the data from a database) to display in the grid
•
Working with a grid's events and manipulating the grid's features
•
Using some advanced data formatting and display techniques for grids
•
Paging data in a grid
•
Exploring highly efficient grid types for displaying large datasets
We will cover how to define the rows and columns, but more importantly, we will learn how to make the grid a very useful part of our application. 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 simple spreadsheet data display!
Displaying Data with Grids
What is a grid anyway?
Ext JS 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 held in a data store.
A GridPanel is databound
Like many Ext JS Components, such as the ComboBox we worked with in Chapter 3, the GridPanel class is bound to a data store which provides it with the data shown in the user interface. So the first step in creating our GridPanel is creating and loading a store. The data store in Ext JS gives us a consistent way of reading different data formats such as XML and JSON, and using this data in a consistent way throughout all of the Ext JS widgets. Regardless of whether this data is originally provided in JSON, XML, an array, or even a custom data type of our own, it's all accessed in the same way thanks to the consistency of the data store and how it uses a separate reader class which interprets the raw data.
[ 92 ]
Chapter 5
Instead of using a pre-configured class such as the ArrayStore used in our ComboBox from Chapter 3, we will explicitly define the classes used to define and load a store.
The record definition
We first need to define the fields which a Record contains. A Record is a single row of data and we need to define the field names, which we want to be read into our store. We define the data type for each field, and, if necessary, we define how to convert from the raw data into the field's desired data type. What we will be creating is a new class. We actually create a constructor which will be used by Ext JS to create records for the store. As the 'create' method creates a constructor, we reference the resulting function with a capitalized variable name, as per standard CamelCase syntax: var Movie = Ext.data.Record.create([ 'id', 'coverthumb', 'title', 'director', 'runtime', {name: 'released', type: 'date', dateFormat: 'Y-m-d'}, 'genre', 'tagline', {name: 'price', type: 'float'}, {name: 'available', type: 'bool'} ]);
Each element in the passed array defines a field within the record. If an item is just a string, the string is used as the name of the field, and no data conversion is performed; the field's value will be whatever the Reader object (which we will learn more about soon) finds in the raw data. If data conversion is required, then a field definition in the form of an object literal instead of a string may contain the following config options: •
Name: The name by which the field will be referenced.
•
type: The data type in which the raw data item will be converted to when stored in the record. Values may be 'int', 'float', 'string', 'date', or 'bool'.
•
dateFormat: If the type of data to be held in the field is a date type, then we need to specify a format string as used by the Date.parseDate function. [ 93 ]
Displaying Data with Grids
Defining the data type can help to alleviate future problems, instead of having to deal with all string type data defining the data type, and lets us work with actual dates, Boolean values, and numbers. The following is a list of the built in data types: Field type string
Description
Information
int
Number
Uses JavaScript's parseInt function
float
Floating point number
Uses JavaScript's parseFloat function
boolean
True/False data
date
Date data
String data
dateFormat config required to interpret incoming data.
Now that the first step has been completed, and we have a simple Record definition in place, we can move on to the next step of configuring a Reader that is able to understand the raw data.
The Reader
A store may accept raw data in several different formats. Raw data rows may be in the form of a simple array of values, or an object with named properties referencing the values, or even an XML element in which the values are child nodes. We need to configure a store with a Reader object which knows how to extract data from the raw data that we are dealing with. There are three built in Reader classes in Ext JS.
ArrayReader
The ArrayReader class can create a record from an array of values. By default, values are read out of the raw array into a record's fields in the order that the fields were declared in the record definition we created. If fields are not declared in the same order that the values occur in the rows, the field's definition may contain a mapping config which specifies the array index from which we retrieve the field's value.
JsonReader
This JSONReader class is the most commonly used, and can create a record from raw JSON by decoding and reading the object's properties.
[ 94 ]
Chapter 5
By default, the field's name is used as the property name from which it takes the field's value. If a different property name is required, the field's definition may contain a mapping config which specifies the name of the property from which to retrieve the field's value.
XmlReader
An XMLReader class can create a record from an XML element by accessing child nodes of the element. By default, the field's name is used as the XPath mapping (not unlike HTML) from which to take the field's value. If a different mapping is required, the field's definition may contain a mapping config which specifies the mapping from which to retrieve the field's value.
Loading our data store
In our first attempt, we are going to create a grid that uses simple local array data stored in a JavaScript variable. The data we're using below in the movieData variable 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 server- side 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 following code should be placed before the Ext JS OnReady function: var movieData = [ [ 1, "Office Space", "Mike Judge", 89, "1999-02-19", 1, "Work Sucks", "19.95", 1 ],[ 3, "Super Troopers", "Jay Chandrasekhar", [ 95 ]
Displaying Data with Grids 100, "2002-02-15", 1, "Altered State Police", "14.95", 1 ] //...more rows of data removed for readability...// ]; var store = new Ext.data.Store({ data: movieData, , reader: new Ext.data.ArrayReader({idIndex: 0}, Movie) });
If we view 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 our data. The web browser's memory has our data in it. Now we need to configure the grid to display our data to the user.
Displaying structured data with a GridPanel
Displaying data in a grid requires several Ext JS classes to cooperate: •
A Store: As mentioned in Chapter 3, a data store is a client-side analogue of a database table. It encapsulates a set of records, each of which contains a defined set of fields. Full details about data stores are contained in Chapter 15.
•
A Record definition: This defines the fields (or "columns" in database terminology) which make up each record in the Store. Field name and datatype are defined here. More details are in Chapter 15.
•
A Reader which uses a Record definition to extract field values from a raw data object to create the records for a Store.
•
A ColumnModel which specifies the details of each column, including the column header to display, and the name of the record field to be displayed for each column.
•
A GridPanel: A panel subclass which provides the controller logic to bind the above classes together to generate the grid's user interface.
If we were to display the data just as the store sees it now, we would end up with something like this:
[ 96 ]
Chapter 5
Now that is ugly—here's a breakdown of what's happening: •
The Released date has been type set properly as a date, and interpreted from the string value in our data. It's provided in a native JavaScript date format—luckily Ext JS 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 an actual Boolean value, even if the raw data was not an actual Boolean value.
As you can see, it's quite useful to specify the type of data that is being read, and apply any special options that are needed so that we don't have to deal with converting data elsewhere in our code. Before we move on to displaying the data in our grid, we should take a look at how the convert config works, as it can come in quite useful.
Converting data read into the store
If we need to, we can convert data as it comes into the store, massage it, remove any quirky parts, or create new fields all together. This should not be used as a way to change the display of data; that part will be handled elsewhere. A common task might be to remove possible errors in the data when we load it, making sure it's in a consistent format for later actions. This can be done using a convert function, which is defined in the 'convert' config by providing a function, or reference to a function. In this case we are going to create a new field by using the data from another field and combining it with a few standard strings. var store = new Ext.data.Store({ data: movieData, reader: new Ext.data.ArrayReader({id:'id'}, [ 'id', 'title', [ 97 ]
This convert function when used in this manner will create a new field of data that looks like this: 'images/5m.jpg'
We will use this new field of data shortly, so let's get a grid up and running.
Displaying the GridPanel
The class that pulls everything together is the GridPanel. This class takes care of placing the data into columns and rows, along with adding column headers, and boxing it all together in a neat little package. The movie data store we created isn't much good to anybody just sitting in the computer's memory. Let's display it in a grid by creating a simple GridPanel: 1. Let's add our data store to the following GridPanel code: Ext.onReady(function() {… var grid = new Ext.grid.GridPanel({ renderTo: Ext.getBody(), frame: true, title: 'Movie Database', height: 200, width: 520, store: store, colModel: new Ext.grid.ColumnModel({ defaultSortable: false, columns: [ {header: "Title", dataIndex: 'title'}, {header: "Director", dataIndex: 'director'}, {header: "Released", dataIndex: 'released', [ 98 ]
2. Bring this page up in a browser, and here's what we will see:
How did that work?
All except two of the config options used here should be familiar to us now because they are inherited from base classes such as the panel Believe it or not, there are only two new config options that are really essential to make a GridPanel different from a Panel! They are: •
store: This references a store object which provides the data displayed
•
ColModel: This is a ColumnModel object which defines how the column headers and data cells are to be displayed. It defines a header for each column, and the name of the field to display in that column.
in the grid. Any changes to the store are automatically applied to the UI.
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 520; it will use our 'store' data store and have the columns specified. This again shows us the benefits of both object-based configuration, and the Ext JS class hierarchy. The configuration is readable, not a series of parameters whose order must be remembered.
[ 99 ]
Displaying Data with Grids
Also, the renderTo frame, title, height, and width options are all inherited from base classes, and are common to all Panel classes. So we will never have to think about these once we have mastered the Panel class.
Defining a grid's column model
The ColumnModel class encapsulates a set of column objects, each of which defines an individual column's characteristics. This is a mirror image of the field definitions in the Record definition which specify how to read in a field's value from a raw data object. The ColumnModel works at the opposite end of the data flow. It defines which fields from each record to display (you don't have to show them all), and also how the value from each field is to be converted back into string form for display in the UI. The ColumnModel also maintains defaults to be applied to the columns which it manages, and offers an API to manipulate the columns, and provide information about them. To define our grid's columns, we configure the ColumnModel with an array of column config objects. Each of the objects within a ColumnModel's columns array defines one column. The most useful options within a column definition are: •
header: The HTML to display in the header area at the top of the column.
•
dataIndex: The name of the record field—as defined in the Record definition—to display in each cell of the column.
•
xtype: The type of column to create. This is optional, and defaults to a basic
column which displays the referenced data field unchanged. But to display a formatted date using the default date format, we can specify 'datecolumn'. There are several other column xtypes described in the API documentation.
So a ColumnModel definition is like this: new Ext.grid.ColumnModel({ defaultSortable: false, columns: [ {header: 'Title', dataIndex: 'title'}, {header: 'Director', dataIndex: 'director'}, {header: 'Released', dataIndex: 'released'}, {header: 'Genre', dataIndex: 'genre'}, {header: 'Tagline', dataIndex: 'tagline'} ] })
[ 100 ]
Chapter 5
This will create grid column headers that look like the following. We have also set the default of sortable for each column to false by using a master config in the Column Model:
Here are some other useful config options for each column within the column model: Option renderer
Description
Usage
hidden
Hides the column
Boolean value defining whether or not the column should be displayed.
hideable
Allows the UI to offer checkboxes to hide/show the column
If a column must not be hidden (or indeed begins hidden and must not be shown) by the user, set this option to true.
width
Specifies the column width in pixels
The width of the column. Default is 100 pixels; overflowing content is hidden.
sortable
Specifies whether the column is sortable
Boolean value specifying whether or not the column can be sorted. Overrides the defaultSortable configuration of the ColumnModel.
Specifies a function which Can be used to format the data for this column into returns formatted HTML to your preferred format. Any type of data can be display in a grid cell transformed. We will learn about these in the next few pages.
Built-in column types
There are several built-in column types, all identified by their own unique xtype which provide special formatting capabilities for cell data. The usage of these is illustrated in the example code for this chapter.
BooleanColumn
Displays the text true or false (or the locale specific equivalents if you include the correct Ext JS locale file) in the column's cells depending on whether the cell's field value is true or false. It can be configured with alternative true/false display values. Example usage: { xtype: 'booleancolumn', header: 'Available', dataIndex: 'available', trueText: 'Affirmative', falseText: 'Negative' } [ 101 ]
Displaying Data with Grids
DateColumn
This displays the cell's field value as a formatted date. By default, it uses the date format 'm/d/Y', but it can be configured with a format option specifying an alternative format. The value in the column's associated field must be a date object. Example usage: { header: "Released", dataIndex: 'released', xtype: 'datecolumn', format: 'M d Y', width: 70 }
NumberColumn
Displays the cell's field value formatted according to a format string as used in Ext.util.Format.number. The default format string is "0.00". Example usage: { header: "Runtime", dataIndex: 'runtime', xtype: 'numbercolumn', format: '0', width: 70 }
TemplateColumn
Uses an Ext.XTemplate string to produce complex HTML with any fields from within the row's record embedded. { header: "Title", dataIndex: 'title', xtype: 'templatecolumn', tpl: ''+ '{title} '+ 'Director: {director} {tagline}' }
The tokens in the template (tpl) between braces are field names from the store's record. The values are substituted in to create the rendered value. See the API documentation for the Ext.XTemplate class for more information. [ 102 ]
Chapter 5
ActionColumn
Displays icons in a cell, and may be configurable with a handler function to process clicks on an icon. Example illustrating arguments passed to the handler function: { header: 'Delete', sortable: false, xtype: 'actioncolumn', width: 40, align: 'center', iconCls: 'delete-movie', handler: function(grid, rowIndex, colIdex, item, e) { deleteMovie(grid.getStore().getAt(rowIndex)); } }
Using cell renderers
If the built in column types cannot create the desired output in a grid cell (which is very unlikely given the data formatting capabilities of the XTemplate class), then we can write a custom renderer function. 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 we can 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 experiment with 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 of the built-in renderers include commonly-required formatting, such as money, capitalize, and lowercase.
[ 103 ]
Displaying Data with Grids
Here are a few of the renderers that I find most useful: Renderer dateRenderer
Description
Usage
uppercase lowercase capitalize
Upper and lower case conversion
Converts the string to completely upper or lower case text.
Pretty text
Formats a text string to have correct capitalization.
Formats a date for Can be used to format the data for this column into display our preferred date display format. Any type of date can be transformed.
Creating lookup data stores—custom cell rendering
We're going to start by taking the 'genre' column, which has a numeric value, and look up that value in the genre data store we created earlier in Chapter 3 to find the textual representation of our genre number. First, we add a config option to the column model that tells the columns which function to use for rendering each cell's content. {header: 'Genre', dataIndex: 'genre', renderer: genre_name}
Now we need to create that function. The function being called by the column is passed the value of its cell as the first argument. The second argument is a cell object, while the third is the record for the current row being rendered, followed by row index, column index, and the store—none of which we will use for this renderer. So let's just specify the first argument as 'val' and leave off the rest. function genre_name(val) { var rec = genres.getById(val); return rec ? rec.get('genre') : val; }
The renderer function is passed the value of the current cell of data. This value can be compared, massaged, and any actions we need can be performed on it— whatever value is returned by the function is rendered directly to the grid cell. A queryBy method is used to filter the data from our genre store and find the matching row of data. It accepts a function that performs a comparison against each row of data, and returns true to use the row that matches.
[ 104 ]
Chapter 5
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).get('genre'); }
Combining two columns
The lookup data store is a very useful renderer. However, it's also common for developers to need 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 into a fancy display. 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, and while the column will be hidden from display, the data still exists in our store. {header: 'Tagline', dataIndex: 'tagline', hidden: true}
The next step is our renderer function that will take care of combining the fields. Here we will start to use the rest of the arguments passed to the renderer function. function title_tagline(val, x, rec){ return ''+val+' '+rec.get('tagline'); }
This function simply concatenates a couple of strings along with the data and returns the modified value. I went ahead and bolded the title in this sample 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:
[ 105 ]
Displaying Data with Grids
Generating HTML and graphics
Let's get some good 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 file name of the image—we created in the store earlier—and write that into an IMG tag as the SRC attribute. function cover_image(val){ return ''; }
By creating this fairly straightforward function, and setting it as the 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 JS has some very nice built-in features to help complete the spreadsheet-like interface. Columns have built-in menus that provide access to sorting, displaying, and hiding columns:
[ 106 ]
Chapter 5
Client-side sorting
Unless specified as a server-side (remotely) sorted grid, an Ext JS grid is by default 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—just set a column's sortable config to true: {header: 'Tagline', dataIndex: 'tagline', id: 'tagline', sortable: true}
We can also accomplish this after the grid has been rendered; to make this easier and predictable we need to assign an ID to the column as shown above: 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', id: 'tagline', hidden: true}
The more exciting way is to do this after the grid has been rendered, by using the functions Ext JS provides: var colmodel = grid.getColumnModel(); colmodel.setHidden(colmodel.getIndexById('tagline'),true);
Grabbing a reference to the column model again will allow us to make this change. [ 107 ]
Displaying Data with Grids
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.
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.
[ 108 ]
Chapter 5
Displaying server-side data in the grid
With Ext JS 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 (database).
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. Let's take a look at an example of how our XML file would be laid out: 1Office SpaceMike Judge1999-02-191Work Sucks <price>19.95 13Super TroopersJay Chandrasekhar2002-02-151Altered State Police <price>14.95 1 //...more rows of data removed for readability...//
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.
[ 109 ]
Displaying Data with Grids
There are four basic changes that need to happen when moving from local to remote data: •
The url option, specifying the location of our data needs to be added—this will replace the data 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 option
•
We will need to call the store's load function that tells our data store to pull in the data from the file on the server and parse it into memory
var store = new Ext.data.Store({ url: 'movies.xml', reader: new Ext.data.XmlReader({ record:'row', idPath:'id' }, Movie), autoLoad: true });
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 need to make were to the data store. Ext JS isolates these types of changes by using a common data store that is able to use an external reader to read many formats and store them internally in the same way.
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 that looks like this: { success:true, rows:[ { "id":"1", "title":"Office Space", [ 110 ]
Chapter 5 "director":"Mike Judge", "released":"1999-02-19", "genre":"1", "tagline":"Work Sucks", "price":"19.95", "active":"1" },{ "id":"3", "title":"Super Troopers", "director":"Jay Chandrasekhar", "released":"2002-02-15", "genre":"1", "tagline":"Altered State Police", "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 of objects (the rows of 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', idProperty:'id' }, Movie), autoLoad: true });
This grid will have an identical look and the same functionality as the array and the XML grids that we created earlier. JSON and arrays are a format native to JavaScript called an Object Literal and Array Literal, and will end up being the quickest formats for the data store (JavaScript) to read, which means that our grid will be displayed much faster than with most other formats, specifically XML.
[ 111 ]
Displaying Data with Grids
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 JS is able to read:
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 to be displayed. Often, we will want the grid to do something in response to user input—interaction. 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 up a selection model.
Working with cell and row selections
Ext JS grids delegate the monitoring of user interaction with the grids rows, cells, and columns to a separate selection model object. 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. [ 112 ]
Chapter 5
The built-in 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, or multiple rows
•
CheckBoxSelectionModel: This one uses a checkbox to enable row selection
from the grid
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, and just happens to be the default. The selection model is defined in the GridPanel config by using the selModel config option—the shortform sm could also be used. selModel: new Ext.grid.RowSelectionModel({ singleSelect: true })
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: function(sm, index, record) { Ext.Msg.alert('You Selected',record.get('title')); } } })
[ 113 ]
Displaying Data with Grids
The above listener code will result in the following display:
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 There are many ways to programmatically manipulate the grid and its data. The key is to understand how the responsibility for managing the grid is broken down between the objects that we use to put a grid together.
In the following discussions we will show use of the store and its associated records for manipulating the data, and use of the selection model for determining how the user is interacting with the grid.
[ 114 ]
Chapter 5
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. This will use a toolbar and buttons which we explored in Chapter 4, along with the MessageBox from Chapter 2. tbar: [{ text: 'Change Title', handler: function(){ var sm = grid.getSelectionModel(), sel = sm.getSelected(); if (sm.hasSelection()){ Ext.Msg.show({ title: 'Change Title', prompt: true, buttons: Ext.MessageBox.OKCANCEL, value: sel.get('title'), fn: function(btn,text){ if (btn == 'ok'){ sel.set('title', text); } } }); } } }]
The result of this addition is as follows:
[ 115 ]
Displaying Data with Grids
All we are really doing here is changing the data in the data store, which updates the grid automatically. 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 we may prefer to use. This is covered in the next chapter in case you are wondering. 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.get: Using the get method of the currently selected record, we can grab
•
a field's value
sel.set: Using the set method of the currently selected record, we can change a field's value
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
As we are in the mood to create some user-grid interactions, let us add some more buttons that do fun stuff. We will now add a button 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—a fairly typical interaction: { text: 'Hide Price', handler: function(btn){ var cm = grid.getColumnModel(), pi = cm.getIndexById('price'); // is this column visible? if (cm.isHidden(pi)){ cm.setHidden(pi,false); btn.setText('Hide Price'); }else{ cm.setHidden(pi,true); btn.setText('Show Price'); } } }
[ 116 ]
Chapter 5
We have used a new method here—getIndexById, which, as you can imagine, gets the column index, which 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 three because indexes start at zero.
Paging the grid
Paging requires that we have a server-side element (script) which 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. The start value represents which row of data to start returning, and the limit specifies how many total rows of data to return from the starting point. Here is a typical PHP script that would handle paging. We will name the file movies-paging.php. [ 117 ]
Displaying Data with Grids
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). 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 as returned from the server side script. This lets the paging toolbar figure out when to enable and disable the previous and next buttons among other things. var store = new Ext.data.Store({ url: 'movies-paged.php', reader: new Ext.data.JsonReader({ root:'rows', totalProperty: 'results', idProperty:'id' }, Movie) });
Instead of autoLoading the store, we kick off the Store's loading process by asking the PagingToolbar to load the first page because it knows what parameters to send, so we do not need to pass any in a programmatic call of the store's load method. grid.getBottomToolbar().changePage(1);
[ 118 ]
Chapter 5
The result will look like this:
Grouping
Grouping grids are used to provide a visual indication that sets of rows are similar to each other, such as being in the same movie genre. 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
Grouping of data by common values of one field is provided by a special data store class called GroupingStore. 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({ root:'rows', idProperty:'id' }, Movie) }); [ 119 ]
Displaying Data with Grids
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', colModel: // column model goes here //, view: new Ext.grid.GroupingView({ forceFit: true, groupTextTpl: '{text} ({[values.rs.length]} {[values.rs.length > 1 ? "Items" : "Item"]})' }) });
After making the changes needed for a grouping grid, we end up with something that looks like this:
If you now expand the context menu for the column headings, you will see a new item in the menu labeled Group By This Field that will allow the user to change the grouping column on the fly.
[ 120 ]
Chapter 5
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 and 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.
[ 121 ]
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 JS provides an EditorGridPanel, which allows the use of form field type editing in-line—which we will learn about it in this chapter. This works much like Excel or other spreadsheet programs, allowing the user to click on and edit cell data in-line with the grid. In this chapter we 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 we can do with an editable grid.
Editor Grids
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 JS GridPanel, and we are left with a widget that can do pretty much whatever we want.
All of the fields in this table can be edited in-line using form fields such as the text field, date field, 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. Once we learn how to do it, that part can be quite simple as well. Let's see how we 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, [ 124 ]
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, which we will change to one click
•
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 our 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 }) [ 125 ]
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, though nothing really happens yet.
Here we see that 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 record 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}
Those are 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. Any form field type can be used as an editor. These are some of the standard types: • •
TextField NumberField [ 126 ]
Chapter 6
•
ComboBox
•
DateField
•
TimeField
•
CheckBox
These editors can be extended to achieve special types of editing if needed, but for now, let's 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}
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 editing the field, the renderer will take over formatting the field again.
[ 127 ]
Editor Grids
Editing 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.
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. Records within the data store which have been created, modified, or deleted are tracked, and maintained in a list of uncommitted changes until the store is told that the database has been synchronized with these changes. [ 128 ]
Chapter 6
We can tell the store to clear the dirty status of a record by calling the commit method. This signifies that we have synchronized the database, and the record can now be considered 'clean'. Let's imagine e as an edit event object. We could restore the edited record to its unmodified, 'clean' state by calling the reject method: e.record.reject();
Alternatively, we can tell the store that the database has been synchronized, and that the changes can be made permanent: e.record.commit();
Reacting when an edit occurs
To save our users changes to the data store, we are going to listen for an edit event 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(); } } } });
[ 129 ]
Editor Grids
As with other events in Ext JS, 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 edit object, which has a number of useful properties. We can use these properties to make a decision about the edit that just happened. 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 get method
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 method, 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 browser's 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's set up a top toolbar (tbar) in the grid to contain these buttons: [ 130 ]
Chapter 6 var grid = new Ext.grid.EditorGridPanel({ // more config options clipped //, tbar: [{ text: 'Remove Movie' }] }
Removing grid rows from the data store
Let's expand on the remove button that we just added 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(), 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); } } }); }; } }
[ 131 ]
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 store remove function will remove that record 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 new row, we use the record constructor that we create to represent a movie object, and instantiate a new record. We used the Ext.data.Record.create function to define a record constructor called Movie, so to insert a new movie into the store is as simple as this: { text: 'Add Movie', icon: 'images/table_add.png', cls: 'x-btn-text-icon', handler: function() { grid.getStore().insert(0, new Movie({ title: 'New Movie', director: '', genre: 0, tagline: '' }) ); grid.startEditing(0,0); } }
The first argument to the insert function is the point at which the record inserted. 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. As 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 index of the last item in the store. grid.getStore().insert( grid.getStore().getCount(), new Movie({ [ 132 ]
Now let's take a closer look at inserting that record. The second argument is the new record definition, which can be passed with the initial field values. new Movie({ title:'New Movie', director:'', genre:0, tagline:'' })
After inserting the new row, we call the startEditing method that will activate a cell's 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. Quite a nice user interface interaction which our end users will no doubt love.
[ 133 ]
Editor Grids
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 than 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 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. 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){ Ext.Ajax.request({ url: 'movie-update.php', params: { action: 'update', id: e.record.id, field: e.field, value: e.value }, success: function(resp,opt) { e.record.commit(); }, failure: function(resp,opt) { e.record.reject(); } [ 134 ]
Chapter 6 }); } }
This will send a request to the movie-update.php script with four parameters in the form of post headers. The params we will pass into the request methods config as an object, which 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 do to make the update happen on the server side. 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 to be done. For the delete trigger, we will use another button in the grids toolbar, along with a confirm dialog to ask the user if they are sure they want to delete the record before actually taking any action. { text: 'Remove Movie', icon: 'images/table_delete.png', cls: 'x-btn-text-icon', handler: function() { var sm = grid.getSelectionModel(), sel = sm.getSelected(); if (sm.hasSelection()){ Ext.Msg.show({ [ 135 ]
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 the record id that we passed; it will then execute the appropriate action to delete the record on the server side.
Saving new rows to the server
Now we're going to add another button that will add a new record. It sends a request to the server with the appropriate parameters and reads the response to figure out what the insert id from the database was. Using this insert id, we are able to add the record to our data store with the unique identifier generated on the server side for that record. { text: 'Add Movie', icon: 'images/table_add.png', cls: 'x-btn-text-icon', handler: function() { [ 136 ]
Much like editing and deleting, we are going to send a request to the server to have a new record inserted. This time, we are actually going to take a look at the response to retrieve the insert id (the unique identifier for that record) to pass to the record constructor, so that when we start editing that record, it will be easy to save our changes back to the server. success: function(resp,opt) { var insert_id = Ext.util.JSON.decode( resp.responseText ).insert_id; grid.getStore().insert(0, new Movie({ id: insert_id, title: 'New Movie', director: '', genre: 0, tagline: '' [ 137 ]
Our success handler accepts a couple of arguments; the first is the response object, which has a property that contains the response text from our movie-update.php script. As that response is in a JSON format, we're going to decode it into a usable JavaScript 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 this insert id that was retrieved.
RowEditor plugin
One of the more popular ways to edit data in a grid uses a User Extension called the RowEditor. This extension presents the user with all the editable fields at once, along with save and cancel buttons. This allows editing of all fields within a record before committing all changes at once. Since this is a plugin, we simply need to include the additional JavaScript and CSS files and configure our Grid to use the plugin. Using this plugin requires that we include the RowEditor JavaScript file, which can be found in the examples/ux folder of the Ext JS SDK download. Be sure to include it directly after the ext-all.js file. <script src="../ux/RowEditor.js">
We also need to include the RowEditor styles found in the ux/css folder, (the file is called RowEditor.css,) along with the two images needed that are found in the ux/images folder, called row-editor-bg.gif and row-editor-btns.gif. Now we just need to configure the EditorGrid to use this plugin. var grid = new Ext.grid.EditorGridPanel({ // more config options clipped //, title: 'Movie Database', store: store, columns: // column model clipped //, plugins: [new Ext.ux.grid.RowEditor()]
});
[ 138 ]
Chapter 6
From here we have the plugin in place, but it doesn't really do much as far as saving the changes. So let's throw a writable data store in the mix to make this editor plugin much more powerful.
Writable store
In our final example of this chapter we will combine use of the RowEditor with a writable data store to implement full CRUD functionality for movie database maintenance. First, let's examine how to set up a writable data store. An Ext JS 3.0+ data store understands four operations to perform which require synchronization with a server: •
Create: A record has been inserted into the data store
•
Read: Data store needs to be filled from the server
•
Update: A record has been modified within the data store
•
Destroy: A record has been removed from the data store
To enable the data store to perform this synchronization, we configure it with an API object which specifies a URL to use for each operation. In our case we use the same PHP script passing a parameter specifying the action to perform: api: { create : 'movies-sync.php?action=create', read : 'movies-sync.php?action=read', update: 'movies-sync.php?action=update', destroy: 'movies-sync.php?action=destroy' },
This config option takes the place of the url config which we specified when the store was a simple load-only store. We also need to configure a writer which will serialize the changes back into the format that the reader uses. In our case, we were using a JsonReader, so we use a JsonWriter. We tell the JsonWriter to submit the full record field set upon update, not only the modified fields because the PHP script creates an SQL statement which sets all of the row's columns: writer: new Ext.data.JsonWriter({ writeAllFields: true }),
[ 139 ]
Editor Grids
We configure the data store to not automatically synchronize itself with the server whenever its data changes, as otherwise it would synchronize during row editing, and we want to submit our changes only when we have edited the whole row: autoSave: false,
We must not forget to handle exceptions that may happen with the complex operations being performed by the data store. A data store may fire an exception event if it encounters an error, and handling these errors is highly recommended. It also relays exception events from the communication layer (The Proxy object we mentioned in the last chapter). These will pass a type parameter which specifies whether it was the client or the server which produced the error: listeners: { exception: function(proxy, type, action, o, result, records) { if (type = 'remote') { Ext.Msg.alert("Could not " + action, result.raw.message); } else if (type = 'response') { Ext.Msg.alert("Could not " + action, "Server's response could not be decoded"); } else { Ext.Msg.alert("Store sync failed", "Unknown error"); } } }
You can test this error handling by triggering a "deliberate mistake" in the PHP update script. If you submit one of the textual fields containing an apostrophe, the creation of the SQL statement will be incorrect because it concatenates the statement placing the values within apostrophes. The example will display an alert box informing the user of a server side error in an orderly manner. To configure the UI side of this, all we need to do is create a RowEditor, and specify it as a plugin of the grid: var rowEditor = new Ext.ux.grid.RowEditor({ saveText: 'Update', listeners: { afteredit: syncStore } }); var grid = new Ext.grid.GridPanel({ renderTo: document.body, plugins: rowEditor, ... [ 140 ]
Chapter 6
The RowEditor uses the editors configured into the ColumnModel in the same way as our previous examples did. All we need to do is specify a listener for the afteredit event which the RowEditor fires when the user clicks the Update button after a successful edit. The function to make the store synchronize itself with the server is very simple: function syncStore(rowEditor, changes, r, rowIndex) { store.save(); }
Running the final example in the chapter, and double-clicking to edit a row will look like this:
[ 141 ]
Editor Grids
Summary
The Ext JS Editor 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 numerous configuration options available, we can present this data easily, and 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. For the first time in this book we have utilized a User Extension to create additional functionality that did not previously exist in the Ext JS library. This functionality can be easily shared and updated independent of the Ext JS library, which opens up a huge world of possibilities. 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.
[ 142 ]
Layouts One of those foundation classes that we have already seen in action is the Container class. We have used panels, and panels are Containers (because they inherit from that base class). They can contain child components. These child components may of course be Containers themselves. A Container delegates the responsibility for rendering its child components into its DOM structure to a layout manager. The layout manager renders child components, and may, if configured to do so, perform positioning and sizing on those child components. This is one of the most powerful concepts within the Ext JS library, and it is what turns a collection of forms, grids, and other widgets, into a dynamic, fluid web application which behaves like a desktop application. It takes some time to understand this concept, so let's examine it.
What is a layout manager?
A layout manager is an object which renders child components for a Container. The default layout manager simply renders child components serially into the Container's DOM, and then takes no further responsibility. No sizing is performed by the default layout manager and so if the Container ever changes size, the child components will not be resized. Other built-in layout managers, depending on their type and configuration, apply sizing and positioning rules to the child components. A layout manager may also examine hints configured into the child components to decide how the child component is to be sized and positioned. The form and border layout are examples of this.
Layouts
To see these principles in action, let's take a look at our first example that will use an hbox layout in our panel to arrange two child BoxComponents. The requirement is that the two child boxes are arranged horizontally, with a 50/50 width allocation, and they must fill the container height. The code which creates the panel is: new Ext.Panel({ renderTo: document.body, title: "I arranged two boxes horizontally using 'hbox' layout!", height: 400, width: 600, layout: { type: 'hbox', align: 'stretch', padding: 5 }, items: [{ xtype: 'box', flex: 1, style: 'border: 1px solid #8DB2E3', margins: '0 3 0 0', html: 'Left box' }, { xtype: 'box', flex: 1, style: 'border: 1px solid #8DB2E3', margins: '0 0 0 2', html: 'Right box' }], style: 'padding:10px' });
The layout configuration is an object which specifies not only which layout class is to be used, but also acts as a config object for that class. In this case we use layout type hbox, which is a layout that arranges child components as a row of horizontal boxes. The configuration options are all documented in the API docs, but two that we use here are: •
padding: Specifies how much internal padding to leave within the Container's structure when sizing and positioning children. We want them to be inset so that we can visually appreciate the structure.
•
align: Specifies how to arrange the height of the children. 'stretch'
means that they will be stretched to take up all available height.
[ 144 ]
Chapter 7
The hbox layout manager reads hint configurations from the child components when arranging them. Two of the configuration options that we use here are: •
flex: A number which specifies the ratio of the total of all flex values to use to allocate available width. Both children have a value of 1, so the space allocated to each will be 1/2 of the available width.
•
margins: The top, right, bottom, and left margin width in pixels. We want
to see a neat five pixel separation between the two child boxes.
The result of running this example is:
This simple panel is not resizable as we have constructed it. As we are using a layout which applies rules to how child items are arranged, if the Panel were to change size, both child items would have their sizes and positions recalculated according to the configuration. This is the power of the layout system which is an integral part of Container/ Component nesting: dynamic sizing.
So what layouts are available?
While designing your UI, you must first plan how you require any child components within it to be arranged and sized. With this in mind, you must then plan which layouts to use to achieve that goal. This requires an appreciation of what is available. The layout types built into Ext JS will now be discussed.
AbsoluteLayout
Type: 'absolute'. This layout allows you to position child components at X and Y coordinates within the Container. This layout is rarely used.
AccordionLayout
Type: 'acordion'. This layout may only use panels as child items. It arranges the panels vertically within the Container, allowing only one of the panels to be expanded at any time. The expanded panel takes up all available vertical space, leaving just the other panel headers visible. [ 145 ]
Layouts
Note that this layout does not by default set the child panels' widths, but allows them to size themselves. To force it to size the child panel's widths to fit exactly within the Container, use autoWidth: false in the layout config object.
AnchorLayout
Type: 'anchor'. This layout manager reads hints from the child components to anchor the widths and heights of the child components to the right and bottom Container borders. This layout reads an optional anchor hint from the child items. This is a string containing the width anchor and the height anchor. This layout can be quite useful for form items.
BorderLayout
Type: 'border'. This layout manager docks child components to the north, south, east, or west borders of the Container. Child components must use a region config to declare which border they are docked to. There must always be a region: 'center' child component which uses the central space between all docked children. North and south child components may be configured with an initial height. East and west child components may be configured with an initial width.
CardLayout
Type: 'card'. This layout manager arranges child components in a stack like a deck of cards, one below the other. The child components are sized to fit precisely within the Container's bounds with no overflow. Only one child component may be visible (termed active) at once. The TabPanel class uses a CardLayout manager internally to activate one tab at a time.
ColumnLayout
Type: 'column'. The ColumnLayout manager arranges child components floating from left to right across the Container. Hints may be used in the child components to specify the proportion of horizontal space to allocate to a child. This layout allows child components to wrap onto the next line when they overflow the available width.
FitLayout
Type: 'fit'. This layout manager manages only one child component. It handles sizing of the single child component to fit exactly into the Container's bounds with no overflow. This layout is often used in conjunction with a Window Component. [ 146 ]
Chapter 7
FormLayout
Type: 'form'. This is the default layout for FormPanels and FieldSets. It extends AnchorLayout, so has all the sizing abilities of that layout. The key ability of FormLayout is that it is responsible for rendering labels next to form fields. If you configure a field with a fieldLabel, it will only appear if the field is in a layout which is using FormLayout.
HBoxLayout
Type: 'hbox'. This layout manager arranges child components horizontally across the Container. It uses hints in the child components to allocate available width in a proportional way. It does not wrap overflowing components onto another line.
TableLayout
Type: 'table'. This layout manager allows the developer to arrange child components in a tabular layout. A wrapping
element is created, and children are each rendered into their own
elements. TableLayout does not size the child components, but it accepts rowspan and colspan hints from the children to create a table of any complexity in which each cell contains a child component.
VBoxLayout
Type: 'vbox'. The vertical brother of 'hbox', this layout manager arranges child components one below the other, allocating available Container height to children in a proportional way.
A dynamic application layout
Our first example showed how an individual Container—a panel—which we programmatically render can arrange its child items according to a layout. To attain a desktop-like application layout, we will use the whole browser window. There is a special Container class called a Viewport which encapsulates the whole document body, and resizes itself whenever the browser changes size. If it is configured with a size-managing layout, that layout will then apply its sizing rules to any child components. A Viewport uses the as its main element. It does not need to be programmatically rendered. In the remaining layout examples, we will use a Viewport to create a fully dynamic application. [ 147 ]
Layouts
Our first Viewport
The most common layout manager used in Viewports is the border layout. Applications frequently make use of border regions to represent header, navigation bar, footer, etc. In our examples we will use all five regions (don't forget that only the center region is required!). Border layouts offer split bars between regions to allow them to be resized. It also allows border regions to be collapsed, with the center region then expanding to fill the free space. This offers great flexibility to the user in arranging child components. Viewports may of course use any layout manager. However, we will be using a border layout in our Viewports from now on. All our regions will be panels. (Config objects with no xtype generally default to using a panel as the child component). As with all Containers, any component can be used as a child component. We make use of the automatic border display, and the header element which the panel class offers. In the child components of our border layout example, we specify several hints for the layout manager to use. These include: •
region: The border to dock the child component to.
•
split: This is only used by the border regions, not the center region. If specified as true, the child component is rendered with a splitbar separating
it from the center region, and allows resizing. Mouse-based resizing is limited by the minWidth/minHeight and maxWidth/maxHeight hint configs. •
collapsible: This is only used by border regions. It specifies that the
child component may collapse towards its associated border. If the child component is a panel, a special collapse tool is rendered in the header. If the region is not a panel (only panels have headers), collapseMode: 'mini' may be used (see below).
•
collapseMode: If specified as 'mini', then a mini collapse tool is rendered
•
margins: This specifies the margin in pixels to leave round any child component in the layout. The value is a space-separated string of numeric values. The order is the CSS standard of top, right, bottom, left. I recommend use of five pixel margins between border regions to provide visual separation for the user. Remember that where regions adjoin, only one of them needs a margin! This is illustrated in our first example.
•
cmargins: A margins specification to use when a region is collapsed.
in the child component's splitbar.
[ 148 ]
Chapter 7
Running the first Viewport example, you should see the following user interface:
Notice the margins providing visual separation of the regions. Also, notice the mini collapse tool in the splitbar of the west region. If you resize the browser, the north and south regions maintain their height, and east and west their width, while the center occupies the remaining space. Let's examine the code which created this sample layout: var viewport = new Ext.Viewport({ layout: "border", defaults: { bodyStyle: 'padding:5px;', }, items: [{ region: "north", html: 'North', margins: '5 5 5 5' },{ region: 'west', split: true, collapsible: true, collapseMode: 'mini', title: 'Some Info', width: 200, minSize: 200, [ 149 ]
All the child components are specified by simple config objects. They have a region hint and they use the html config which is from the Component base class. The html config specifies HTML content for the Component. This is just to indicate which region is which. There are better ways for the child Panels to be given content, which we will see later in this chapter. Take note of how the north region has a five pixel border all around. Notice then how all the regions under it (west, center and east) have no top border to keep consistent separation.
Nesting: child components may be Containers
In our third example, we will make the center region into something more useful than a panel containing simple HTML. We will specify the center child component as a TabPanel. TabPanel is a Container, so it has a single child component. A panel with some simple HTML in this case! The only difference in this example is that the center config object has an xtype which means it will be used to create a TabPanel (instead of the default panel), and it has an items config specifying child panels:
The TabPanel class uses a card layout to show only one of several child components at once. Configure it with an activeTab config specifying which one to show initially. In this case it's our 'Movie Grid' panel. Of course the child component we have configured in our TabPanel is not a grid. Let's add the GridPanel we created in Chapter 5 to our TabPanel to demonstrate the card switching UI which the TabPanel offers: { region: 'center', xtype: 'tabpanel', bodyStyle: '', activeTab: 0, items: [{ [ 151 ]
Layouts title: 'Movie Grid', xtype: 'grid', store: store, autoExpandColumn: 'title', colModel: new Ext.grid.ColumnModel({ columns: […] // not shown for readability }), view: new Ext.grid.GroupingView(), sm: new Ext.grid.RowSelectionModel({ singleSelect: true }) },{ title: 'Movie Descriptions' }], margins: '0 0 0 0' }
In this example, we can see that the first child component in the TabPanel's items array is a grid. Take note of this to avoid a common pitfall encountered by first time Ext JS users. It can be tempting to overnest child components within extra Container layers, by placing a grid within a panel. Many that attempt to use a grid as a tab first time end up wrapping the grid within a panel like this: { xtype: 'tabpanel', items: [{ // Open Panel config with no layout items: [{ xtype: 'grid', // Panel contains a grid … }] }] }
If you read the above code correctly, you can see why it is incorrect and inefficient, and will result in a buggy display. Recall that the default child Component type to create if no xtype is specified is a panel. So that single child Component of the TabPanel is a panel which itself contains a grid. It contains a grid, but it has no layout configured. This means that the grid will not be sized, and has an extra Component layer that serves no purpose. This problem is referred to as overnesting and when overnesting is combined with omission of layout configuration, results are unpredictable. Do not think of putting a grid in a tab, think of using a grid as a tab.
[ 152 ]
Chapter 7
Our correctly configured layout will result in the following display:
In the above example, the Viewport has sized its center child—the TabPanel—and the TabPanel's card layout have sized its children. The grid is sized to fit as a tab. The second tab is not functional as yet. Let's move on to that next.
Accordion layout
Now that we have a functioning application of sorts, we can demonstrate another layout manager. An accordion is a familiar layout pattern in modern web applications. Multiple child components are stacked up vertically with their content areas collapsed. Only one may be expanded at a time. The accordion layout allows this to be set up very easily. Just use panels as children of a Container which uses the accordion layout, and the panel's headers are used as placeholders which may be clicked to expand the content area: { xtype: 'container', title: 'Movie Descriptions', layout: 'accordion', defaults: { border: false [ 153 ]
Notice that here, we are using xtype: 'container' as the accordion. We do not need borders, or toolbars, or a header, so we do not need the complex DOM structure of the panel class; the simple Container will do. The defaults config is applicable to any Container to provide default configs for child components. In this case, because the TabPanel provides its own borders, we do not want another layer of borders. Running this example code and selecting the Movie Descriptions tab will result in the following display:
[ 154 ]
Chapter 7
Clicking the headers in the accordion expands the panel's body, and displays the description which was loaded into the child panel using the autoLoad config option. This option may be used to load purely decorative HTML. Loading HTML in this way only loads HTML fragments, not full HTML documents. Scripts and stylesheets will not be evaluated. This option should not be used as a substitute for adding child components if Components are really what is required.
A toolbar as part of the layout
To make the application more useful, we can add a toolbar to the user interface to add buttons and menus. We encountered these classes in Chapter 4, Menus, Toolbars, and Buttons. We can use a toolbar as the north region of the Viewport. Again, note that we do not put the toolbar inside a north region; we use it as a north region to avoid the overnesting problem. To ease the clarity of code which creates the Viewport, we create the config object for the toolbar and reference it with a variable: var toolbarConfig = { region: 'north', height: 27, xtype: 'toolbar', items: [' ', { text: 'Button', handler: function(btn){ btn.disable(); } }, '->', { … };
The config object we create contains the expected region and xtype properties. Border layout requires that border regions be sized, so we give the toolbar a height of 27 pixels. The items in it, buttons, spacers, separators, fills, and input fields have been covered in Chapter 4. As with the toolbar examples in Chapter 4, the handling functions are members of a singleton object. In this case, we have an object referenced by a var Movies which offers methods to operate our application. [ 155 ]
Layouts
The resulting display will look like this:
Using a FormPanel in the layout
We can use that empty west region to do something useful now. We can use a FormPanel in the west region to edit the details of the selected movie. We will configure a listener on the rowselect event of the SelectionModel to load the selected movie record into the FormPanel: sm: new Ext.grid.RowSelectionModel({ singleSelect: true, listeners: { rowselect: Movies.loadMovieForm } })
The listener function is a member of the Movies object: loadMovieForm: function(sm, rowIndex, rec) { editRecord = rec; movieForm.getForm().loadRecord(rec); }, [ 156 ]
Chapter 7
The selected record is assigned to the editRecord variable which is private to the Movies object. This is so that other member functions can use the record later. The getForm method of the FormPanel returns the BasicForm object which manages the input fields within a FormPanel. The loadRecord method of the BasicForm copies values from the data fields of the record into the form's input fields, using the form fields names to match up the data. Our example does not handle form submission yet, but it does update the record when you click the Submit button. The submit handler function is a member function of the Movies singleton: submitMovieForm: function(){ if (editRecord) { movieForm.getForm().updateRecord(editRecord); if (store.groupField && editRecord.modified[store.groupField]) { store.groupBy(store.groupField, true); } movieForm.getForm().submit({ success: function(form, action){ Ext.Msg.alert('Success', 'It worked'); }, failure: function(form, action){ Ext.Msg.alert('Warning', action.result.errormsg); } }); } },
First we update the record we are currently editing using the updateRecord method of the BasicForm. This will automatically update the movie's row in the grid. However if we modify the field that the grid is grouped by, the store will not detect this and regroup itself, so the above contains code to ensure that the grouping state is kept up to date. If the store is grouped and the modified property of the record (see the API docs for the Record class) contains a property named after the store's group field, then we explicitly tell the store to regroup itself. As usual, a modification of the store causes automatic update of the grid UI.
[ 157 ]
Layouts
If we change the genre of the first movie, and Submit it, the resulting display will look like this:
The form you see above shows how the FormLayout class will render labels attached to its child components. The FormLayout class injects into each child component, a reference to the Component's