jQuery Cookbook - listic.ru

Loading...
jQuery Cookbook

jQuery Cookbook

jQuery Community Experts

Beijing • Cambridge • Farnham • Köln • Sebastopol • Taipei • Tokyo

jQuery Cookbook by jQuery Community Experts Copyright © 2010 Cody Lindley. All rights reserved. Printed in the United States of America. Published by O’Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA 95472. O’Reilly books may be purchased for educational, business, or sales promotional use. Online editions are also available for most titles (http://my.safaribooksonline.com). For more information, contact our corporate/institutional sales department: 800-998-9938 or [email protected]

Editor: Simon St.Laurent Production Editor: Sarah Schneider Copyeditor: Kim Wimpsett Proofreader: Andrea Fox Production Services: Molly Sharp

Indexer: Fred Brown Cover Designer: Karen Montgomery Interior Designer: David Futato Illustrator: Robert Romano

Printing History: November 2009:

First Edition.

O’Reilly and the O’Reilly logo are registered trademarks of O’Reilly Media, Inc. jQuery Cookbook, the image of an ermine, and related trade dress are trademarks of O’Reilly Media, Inc. Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in this book, and O’Reilly Media, Inc. was aware of a trademark claim, the designations have been printed in caps or initial caps. While every precaution has been taken in the preparation of this book, the publisher and author assume no responsibility for errors or omissions, or for damages resulting from the use of the information contained herein.

TM

This book uses RepKover, a durable and flexible lay-flat binding. ISBN: 978-0-596-15977-1 [S] 1257774409

Table of Contents

Foreword . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xi Contributors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiii Preface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xvii 1. jQuery Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.1 Including the jQuery Library Code in an HTML Page 1.2 Executing jQuery/JavaScript Coded After the DOM Has Loaded but Before Complete Page Load 1.3 Selecting DOM Elements Using Selectors and the jQuery Function 1.4 Selecting DOM Elements Within a Specified Context 1.5 Filtering a Wrapper Set of DOM Elements 1.6 Finding Descendant Elements Within the Currently Selected Wrapper Set 1.7 Returning to the Prior Selection Before a Destructive Change 1.8 Including the Previous Selection with the Current Selection 1.9 Traversing the DOM Based on Your Current Context to Acquire a New Set of DOM Elements 1.10 Creating, Operating on, and Inserting DOM Elements 1.11 Removing DOM Elements 1.12 Replacing DOM Elements 1.13 Cloning DOM Elements 1.14 Getting, Setting, and Removing DOM Element Attributes 1.15 Getting and Setting HTML Content 1.16 Getting and Setting Text Content 1.17 Using the $ Alias Without Creating Global Conflicts

9 10 13 15 16 18 19 20 21 23 24 26 27 29 30 31 32

2. Selecting Elements with jQuery . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 2.1 Selecting Child Elements Only 2.2 Selecting Specific Siblings

36 37

v

2.3 2.4 2.5 2.6 2.7 2.8 2.9 2.10 2.11 2.12

Selecting Elements by Index Order Selecting Elements That Are Currently Animating Selecting Elements Based on What They Contain Selecting Elements by What They Don’t Match Selecting Elements Based on Their Visibility Selecting Elements Based on Attributes Selecting Form Elements by Type Selecting an Element with Specific Characteristics Using the Context Parameter Creating a Custom Filter Selector

39 41 42 43 43 44 46 47 48 50

3. Beyond the Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 3.1 3.2 3.3 3.4 3.5 3.6 3.7 3.8 3.9

Looping Through a Set of Selected Results Reducing the Selection Set to a Specified Item Convert a Selected jQuery Object into a Raw DOM Object Getting the Index of an Item in a Selection Making a Unique Array of Values from an Existing Array Performing an Action on a Subset of the Selected Set Configuring jQuery Not to Conflict with Other Libraries Adding Functionality with Plugins Determining the Exact Query That Was Used

53 56 59 62 64 67 69 72 74

4. jQuery Utilities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 4.1 4.2 4.3 4.4 4.5 4.6 4.7 4.8

Detecting Features with jQuery.support Iterating Over Arrays and Objects with jQuery.each Filtering Arrays with jQuery.grep Iterating and Modifying Array Entries with jQuery.map Combining Two Arrays with jQuery.merge Filtering Out Duplicate Array Entries with jQuery.unique Testing Callback Functions with jQuery.isFunction Removing Whitespace from Strings or Form Values with jQuery.trim 4.9 Attaching Objects and Data to DOM with jQuery.data 4.10 Extending Objects with jQuery.extend

77 79 80 81 81 82 82 83 84 85

5. Faster, Simpler, More Fun . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 5.1 5.2 5.3 5.4 5.5 5.6 5.7

That’s Not jQuery, It’s JavaScript! What’s Wrong with $(this)? Removing Redundant Repetition Formatting Your jQuery Chains Borrowing Code from Other Libraries Writing a Custom Iterator Toggling an Attribute

vi | Table of Contents

87 88 91 92 94 96 99

5.8 5.9 5.10 5.11 5.12 5.13 5.14 5.15 5.16 5.17 5.18 5.19 5.20 5.21

Finding the Bottlenecks Caching Your jQuery Objects Writing Faster Selectors Loading Tables Faster Coding Bare-Metal Loops Reducing Name Lookups Updating the DOM Faster with .innerHTML Debugging? Break Those Chains Is It a jQuery Bug? Tracing into jQuery Making Fewer Server Requests Writing Unobtrusive JavaScript Using jQuery for Progressive Enhancement Making Your Pages Accessible

101 105 107 109 112 115 117 118 120 121 123 126 128 130

6. Dimensions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135 6.1 6.2 6.3 6.4 6.5 6.6 6.7 6.8 6.9

Finding the Dimensions of the Window and Document Finding the Dimensions of an Element Finding the Offset of an Element Scrolling an Element into View Determining Whether an Element Is Within the Viewport Centering an Element Within the Viewport Absolutely Positioning an Element at Its Current Position Positioning an Element Relative to Another Element Switching Stylesheets Based on Browser Width

135 137 139 141 143 146 147 147 148

7. Effects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151 7.1 7.2 7.3 7.4 7.5 7.6 7.7 7.8 7.9 7.10

Sliding and Fading Elements in and out of View Making Elements Visible by Sliding Them Up Creating a Horizontal Accordion Simultaneously Sliding and Fading Elements Applying Sequential Effects Determining Whether Elements Are Currently Being Animated Stopping and Resetting Animations Using Custom Easing Methods for Effects Disabling All Effects Using jQuery UI for Advanced Effects

153 156 157 161 162 164 165 166 168 168

8. Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171 8.1 8.2 8.3 8.4

Attaching a Handler to Many Events Reusing a Handler Function with Different Data Removing a Whole Set of Event Handlers Triggering Specific Event Handlers

172 173 175 176 Table of Contents | vii

8.5 8.6 8.7 8.8 8.9 8.10

Passing Dynamic Data to Event Handlers Accessing an Element ASAP (Before document.ready) Stopping the Handler Execution Loop Getting the Correct Element When Using event.target Avoid Multiple hover() Animations in Parallel Making Event Handlers Work for Newly Added Elements

177 179 182 184 185 187

9. Advanced Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191 9.1 9.2 9.3 9.4 9.5 9.6 9.7

Getting jQuery to Work When Loaded Dynamically Speeding Up Global Event Triggering Creating Your Own Events Letting Event Handlers Provide Needed Data Creating Event-Driven Plugins Getting Notified When jQuery Methods Are Called Using Objects’ Methods as Event Listeners

191 192 195 198 201 205 208

10. HTML Form Enhancements from Scratch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211 10.1 10.2 10.3 10.4 10.5 10.6 10.7 10.8 10.9 10.10 10.11

Focusing a Text Input on Page Load Disabling and Enabling Form Elements Selecting Radio Buttons Automatically (De)selecting All Checkboxes Using Dedicated Links (De)selecting All Checkboxes Using a Single Toggle Adding and Removing Select Options Autotabbing Based on Character Count Displaying Remaining Character Count Constraining Text Input to Specific Characters Submitting a Form Using Ajax Validating Forms

212 213 216 218 219 221 222 224 226 228 229

11. HTML Form Enhancements with Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237 11.1 11.2 11.3 11.4 11.5 11.6 11.7 11.8 11.9 11.10

Validating Forms Creating Masked Input Fields Autocompleting Text Fields Selecting a Range of Values Entering a Range-Constrained Value Uploading Files in the Background Limiting the Length of Text Inputs Displaying Labels Above Input Fields Growing an Input with Its Content Choosing a Date

238 247 249 250 253 255 256 257 259 260

12. jQuery Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263 12.1 Where Do You Find jQuery Plugins? viii | Table of Contents

263

12.2 12.3 12.4 12.5 12.6 12.7 12.8 12.9

When Should You Write a jQuery Plugin? Writing Your First jQuery Plugin Passing Options into Your Plugin Using the $ Shortcut in Your Plugin Including Private Functions in Your Plugin Supporting the Metadata Plugin Adding a Static Function to Your Plugin Unit Testing Your Plugin with QUnit

265 267 268 270 272 273 275 277

13. Interface Components from Scratch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279 13.1 13.2 13.3 13.4 13.5 13.6 13.7 13.8

Creating Custom Tool Tips Navigating with a File-Tree Expander Expanding an Accordion Tabbing Through a Document Displaying a Simple Modal Window Building Drop-Down Menus Cross-Fading Rotating Images Sliding Panels

280 285 288 293 296 303 305 310

14. User Interfaces with jQuery UI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315 14.1 14.2 14.3 14.4 14.5 14.6 14.7 14.8 14.9 14.10

Including the Entire jQuery UI Suite Including an Individual jQuery UI Plugin or Two Initializing a jQuery UI Plugin with Default Options Initializing a jQuery UI Plugin with Custom Options Creating Your Very Own jQuery UI Plugin Defaults Getting and Setting jQuery UI Plugin Options Calling jQuery UI Plugin Methods Handling jQuery UI Plugin Events Destroying a jQuery UI Plugin Creating a jQuery UI Music Player

317 318 319 320 321 323 323 324 326 327

15. jQuery UI Theming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 341 15.1 15.2 15.3 15.4 15.5

Styling jQuery UI Widgets with ThemeRoller Overriding jQuery UI Layout and Theme Styles Applying a Theme to Non-jQuery UI Components Referencing Multiple Themes on a Single Page Appendix: Additional CSS Resources

345 360 370 379 388

16. jQuery, Ajax, Data Formats: HTML, XML, JSON, JSONP . . . . . . . . . . . . . . . . . . . . . . . 391 16.1 16.2 16.3 16.4

jQuery and Ajax Using Ajax on Your Whole Site Using Simple Ajax with User Feedback Using Ajax Shortcuts and Data Types

391 394 396 400 Table of Contents | ix

16.5 16.6 16.7 16.8 16.9

Using HTML Fragments and jQuery Converting XML to DOM Creating JSON Parsing JSON Using jQuery and JSONP

403 404 405 406 407

17. Using jQuery in Large Projects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 411 17.1 17.2 17.3 17.4 17.5 17.6 17.7

Using Client-Side Storage Saving Application State for a Single Session Saving Application State Between Sessions Using a JavaScript Template Engine Queuing Ajax Requests Dealing with Ajax and the Back Button Putting JavaScript at the End of a Page

411 414 416 417 420 422 423

18. Unit Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 425 18.1 18.2 18.3 18.4 18.5 18.6 18.7 18.8

Automating Unit Testing Asserting Results Testing Synchronous Callbacks Testing Asynchronous Callbacks Testing User Actions Keeping Tests Atomic Grouping Tests Selecting Tests to Run

425 427 429 429 431 432 433 434

Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 437

x | Table of Contents

Foreword

When I first started work on building jQuery, back in 2005, I had a simple goal in mind: I wanted to be able to write a web application and have it work in all the major browsers—without further tinkering and bug fixing. It was a couple of months before I had a set of utilities that were stable enough to achieve that goal for my personal use. I thought I was relatively done at this point; little did I know that my work was just beginning. Since those simple beginnings, jQuery has grown and adapted as new users use the library for their projects. This has proven to be the most challenging part of developing a JavaScript library; while it is quite easy to build a library that’ll work for yourself or a specific application, it becomes incredibly challenging to develop a library that’ll work in as many environments as possible (old browsers, legacy web pages, and strange markup abound). Surprisingly, even as jQuery has adapted to handle more use cases, most of the original API has stayed intact. One thing I find particularly interesting is to see how developers use jQuery and make it their own. As someone with a background in computer science, I find it quite surprising that so many designers and nonprogrammers find jQuery to be compelling. Seeing how they interact with the library has given me a better appreciation of simple API design. Additionally, seeing many advanced programmers take jQuery and develop large, complex applications with it has been quite illuminating. The best part of all of this, though, is the ability to learn from everyone who uses the library. A side benefit of using jQuery is its extensible plugin structure. When I first developed jQuery, I was sure to include some simple ways for developers to extend the API that it provided. This has blossomed into a large and varied community of plugins, encompassing a whole ecosystem of applications, developers, and use cases. Much of jQuery’s growth has been fueled by this community—without it, the library wouldn’t be where it is today, so I’m glad that there are chapters dedicated to some of the most interesting plugins and what you can do with them. One of the best ways to expand your preconceived notion of what you can do with jQuery is to learn and use code from the jQuery plugin community.

xi

This is largely what makes something like a cookbook so interesting: it takes the cool things that developers have done, and have learned, in their day-to-day coding and distills it to bite-sized chunks for later consumption. Personally, I find a cookbook to be one of the best ways to challenge my preconceived notions of a language or library. I love seeing cases where an API that I thought I knew well is turned around and used in new and interesting ways. I hope this book is able to serve you well, teaching you new and interesting ways to use jQuery. —John Resig Creator, Lead Developer, jQuery

xii | Foreword

Contributors

Chapter Authors Jonathan Sharp has been passionate about the Internet and web development since 1996. Over the years that have followed, he has worked for startups and for Fortune 500 corporations. Jonathan founded Out West Media, LLC, in greater Omaha, Nebraska, and provides frontend engineering and architecture services with a focus on custom XHTML, CSS, and jQuery development. Jonathan is a jQuery core team member and an author and presenter when not coding. Jonathan is most grateful for his wife, Erin; daughter, Noel; two dogs, and two horses. Rob Burns develops interactive web applications at A Mountain Top, LLC. For the past 12 years he has been exploring website development using a wide range of tools and technologies. In his spare time, he enjoys natural-language processing and the wealth of opportunity in open source software projects. Rebecca Murphey is an independent frontend architecture consultant, crafting custom frontend solutions that serve as the glue between server and browser. She also provides training in frontend development, with an emphasis on the jQuery library. She lives with her partner, two dogs, and two cats in Durham, North Carolina. Ariel Flesler is a web developer and a video game programmer. He’s been contributing to jQuery since January 2007 and joined the core team in May 2008. He is 23 years old and was born in Buenos Aires, Argentina. He’s studying at the National Technological University (Argentina) and is hoping to become a systems analyst by 2010 and a systems engineer by 2012. He started working as an ASP.NET(C#) programmer and then switched to client-side development of XHTML sites and Ajax applications. He’s currently working at QB9 where he develops AS3-based casual games and MMOs. Cody Lindley is a Christian, husband, son, father, brother, outdoor enthusiast, and professional client-side engineer. Since 1997 he has been passionate about HTML, CSS, JavaScript, Flash, interaction design, interface design, and HCI. He is most well known in the jQuery community for the creation of ThickBox, a modal/dialog solution. In 2008 he officially joined the jQuery team as an evangelist. His current focus has been

xiii

on client-side optimization techniques as well as speaking and writing about jQuery. His website is http://www.codylindley.com. Remy Sharp is a developer, author, speaker, and blogger. Remy started his professional web development career in 1999 as the sole developer for a finance website and, as such, was exposed to all aspects of running the website during, and long after, the dotcom boom. Today he runs his own development company called Left Logic in Brighton, UK, writing and coding JavaScript, jQuery, HTML 5, CSS, PHP, Perl, and anything else he can get his hands on. Mike Hostetler is an inventor, entrepreneur, programmer, and proud father. Having worked with web technologies since the mid-1990s, Mike has had extensive experience developing web applications with PHP and JavaScript. Currently, Mike works at the helm of A Mountain Top, LLC, a web technology consulting firm in Denver, Colorado. Heavily involved in open source, Mike is a member of the jQuery core team, leads the QCubed PHP5 Framework project, and participates in the Drupal project. When not in front of a computer, Mike enjoys hiking, fly fishing, snowboarding, and spending time with his family. Ralph Whitbeck is a graduate of the Rochester Institute of Technology and is currently a senior developer for BrandLogic Corporation in Rochester, New York. His responsibilities at BrandLogic include interface design, usability testing, and web and application development. Ralph is able to program complex web application systems in ASP.NET, C#, and SQL Server and also uses client-side technologies such as XHTML, CSS, and JavaScript/jQuery in order to implement client-approved designs. Ralph officially joined the jQuery team as an evangelist in October 2009. Ralph enjoys spending time with his wife, Hope, and his three boys, Brandon, Jordan, and Ralphie. You can find out more about Ralph on his personal blog. Nathan Smith is a goofy guy who has been building websites since late last century. He enjoys hand-coding HTML, CSS, and JavaScript. He also dabbles in design and information architecture. He has written for online and paper publications such as Adobe Developer Center, Digital Web, and .NET Magazine. He has spoken at venues including Adobe MAX, BibleTech, Drupal Camp, Echo Conference, Ministry 2.0, Refresh Dallas, and Webmaster Jam Session. Nathan works as a UX developer at FellowshipTech.com. He holds a Master of Divinity degree from Asbury Theological Seminary. He started Godbit.com, a community resource aimed at helping churches and ministries make better use of the Web. He also created the 960 Grid System, a framework for sketching, designing, and coding page layouts. Brian Cherne is a software developer with more than a decade of experience blueprinting and building web-based applications, kiosks, and high-traffic e-commerce websites. He is also the author of the hoverIntent jQuery plugin. When not geeking out with code, Brian can be found ballroom dancing, practicing martial arts, or studying Russian culture and language.

xiv | Contributors

Jörn Zaefferer is a professional software developer from Cologne, Germany. He creates application programming interfaces (APIs), graphical user interfaces (GUIs), software architectures, and databases, for both web and desktop applications. His work focuses on the Java platform, while his client-side scripting revolves around jQuery. He started contributing to jQuery in mid-2006 and has since cocreated and maintained QUnit, jQuery’s unit testing framework; released and maintained a half dozen very popular jQuery plugins; and contributed to jQuery books as both author and tech reviewer. He is also a lead developer for jQuery UI. James Padolsey is an enthusiastic web developer and blogger based in London, UK. He’s been crazy about jQuery since he first discovered it; he’s written tutorials teaching it, articles and blog posts discussing it, and plenty of plugins for the community. James’ plans for the future include a computer science degree from the University of Kent and a career that allows him to continually push boundaries. His website is http://james .padolsey.com. Scott González is a web application developer living in Raleigh, North Carolina, who enjoys building highly dynamic systems and flexible, scalable frameworks. He has been contributing to jQuery since 2007 and is currently the development lead for jQuery UI, jQuery’s official user interface library. Scott also writes tutorials about jQuery and jQuery UI on nemikor.com and speaks about jQuery at conferences. Michael Geary started developing software when editing code meant punching a paper tape on a Teletype machine, and “standards-compliant” meant following ECMA-10 Standard for Data Interchange on Punched Tape. Today Mike is a web and Android developer with a particular interest in writing fast, clean, and simple code, and he enjoys helping other developers on the jQuery mailing lists. Mike’s recent projects include a series of 2008 election result and voter information maps for Google; and StrataLogic, a mashup of traditional classroom wall maps and atlases overlaid on Google Earth. His website is http://mg.to. Maggie Wachs, Scott Jehl, Todd Parker, and Patty Toland are Filament Group. Together, they design and develop highly functional user interfaces for consumer- and business-oriented websites, wireless devices, and installed and web-based applications, with a specific focus on delivering intuitive and usable experiences that are also broadly accessible. They are sponsor and design leads of the jQuery UI team, for whom they designed and developed ThemeRoller.com, and they actively contribute to ongoing development of the official jQuery UI library and CSS Framework. Richard D. Worth is a web UI developer. He is the release manager for jQuery UI and one of its longest-contributing developers. He is author or coauthor of the Dialog, Progressbar, Selectable, and Slider plugins. Richard also enjoys speaking and consulting on jQuery and jQuery UI around the world. Richard is raising a growing family in Northern Virginia (Washington, D.C. suburbs) with his lovely wife, Nancy. They have been blessed to date with three beautiful children: Naomi, Asher, and Isaiah. Richard’s website is http://rdworth.org/.

Contributors | xv

Tech Editors Karl Swedberg, after having taught high school English, edited copy for an advertising agency, and owned a coffee house, began his career as a web developer four years ago. He now works for Fusionary Media in Grand Rapids, Michigan, where he specializes in client-side scripting and interaction design. Karl is a member of the jQuery project team and coauthor of Learning jQuery 1.3 and jQuery Reference Guide (both published by Packt). You can find some of his tips and tutorials at http://www.learningjquery.com. Dave Methvin is the chief technology officer at PCPitstop.com and one of the founding partners of the company. He has been using jQuery since 2006, is active on the jQuery help groups, and has contributed several popular jQuery plugins including Corner and Splitter. Before joining PC Pitstop, Dave served as executive editor at both PC Tech Journal and Windows Magazine, where he wrote a column on JavaScript. He continues to write for several PC-related websites including InformationWeek. Dave holds bachelor’s and master’s degrees in computer science from the University of Virginia. David Serduke is a frontend programmer who is recently spending much of his time server side. After programming for many years, he started using jQuery in late 2007 and shortly after joined the jQuery core team. David is currently creating websites for financial institutions and bringing the benefits of jQuery to ASP.NET enterprise applications. David lives in northern California where he received a bachelor’s degree from the University of California at Berkeley in electrical engineering and an MBA from St. Mary’s College. Scott Mark is an enterprise application architect at Medtronic. He works on web-based personalized information portals and transactional applications with an eye toward maintaining high usability in a regulated environment. His key interest areas at the moment are rich Internet applications and multitouch user interface technologies. Scott lives in Minnesota with his lovely wife, two sons, and a black lab. He blogs about technology at http://scottmark.wordpress.com and long-distance trail running at http:// runlikemonkey.com.

xvi | Contributors

Preface

The jQuery library has taken the frontend development world by storm. Its dead-simple syntax makes once-complicated tasks downright trivial—enjoyable, even. Many a developer has been quickly seduced by its elegance and clarity. If you’ve started using the library, you’re already adding rich, interactive experiences to your projects. Getting started is easy, but as is the case with many of the tools we use to develop websites, it can take months or even years to fully appreciate the breadth and depth of the jQuery library. The library is chock-full of features you might never have known to wish for. Once you know about them, they can dramatically change how you approach the problems you’re called upon to solve. The goal of this cookbook is to expose you, dear reader, to the patterns and practices of some of the leading frontend developers who use jQuery in their everyday projects. Over the course of 18 chapters, they’ll guide you through solutions to problems that range from straightforward to complex. Whether you’re a jQuery newcomer or a grizzled JavaScript veteran, you’re likely to gain new insight into harnessing the full power of jQuery to create compelling, robust, high-performance user interfaces.

Who This Book Is For Maybe you’re a designer who is intrigued by the interactivity that jQuery can provide. Maybe you’re a frontend developer who has worked with jQuery before and wants to see how other people accomplish common tasks. Maybe you’re a server-side developer who’s frequently called upon to write client-side code. Truth be told, this cookbook will be valuable to anyone who works with jQuery—or who hopes to work with jQuery. If you’re just starting out with the library, you may want to consider pairing this book with Learning jQuery 1.3 from Packt, or jQuery in Action from Manning. If you’re already using jQuery in your projects, this book will serve to enhance your knowledge of the library’s features, hidden gems, and idiosyncrasies.

xvii

What You’ll Learn We’ll start out by covering the basics and general best practices—including jQuery in your page, making selections, and traversing and manipulation. Even frequent jQuery users are likely to pick up a tip or two. From there, we move on to real-world use cases, walking you through tried-and-true (and tested) solutions to frequent problems involving events, effects, dimensions, forms, and user interface elements (with and without the help of jQuery UI). At the end, we’ll take a look at testing your jQuery applications and integrating jQuery into complex sites. Along the way, you’ll learn strategies for leveraging jQuery to solve problems that go far beyond the basics. We’ll explore how to make the most of jQuery’s event management system, including custom events and custom event data; how to progressively enhance forms; how to position and reposition elements on the page; how to create user interface elements such as tabs, accordions, and modals from scratch; how to craft your code for readability and maintainability; how to optimize your code to ease testing, eliminate bottlenecks, and ensure peak performance; and more. Because this is a cookbook and not a manual, you’re of course welcome to cherry-pick the recipes you read; the individual recipes alone are worth the price of admission. As a whole, though, the book provides a rare glimpse into the problem-solving approaches of some of the best and brightest in the jQuery community. With that in mind, we encourage you to at least skim it from front to back—you never know which line of code will provide the “Aha!” moment you need to take your skills to the next level.

jQuery Style and Conventions jQuery places a heavy emphasis on chaining—calling methods on element selections in sequence, confident in the knowledge that each method will give you back a selection of elements you can continue to work with. This pattern is explained in depth in Chapter 1—if you’re new to the library, you’ll want to understand this concept, because it is used heavily in subsequent chapters. jQuery’s features are organized into a handful of simple categories: core functionality, selecting, manipulating, traversing, CSS, attributes, events, effects, Ajax, and utilities. Learning these categories, and how methods fit into them, will greatly enhance your understanding of the material in this book. One of the best practices this book will cover is the concept of storing element selections in a variable, rather than making the same selection repeatedly. When a selection is stored in a variable, it is commonplace for that variable to begin with the $ character, indicating that it is a jQuery object. This can make code easier to read and maintain, but it should be understood that starting the variable name with the $ character is merely a convention; it carries no special meaning, unlike in other languages such as PHP.

xviii | Preface

In general, the code examples in this book strive for clarity and readability over compactness, so the examples may be more verbose than is strictly necessary. If you see an opportunity for optimization, you should not hesitate to take it. At the same time, you’ll do well to strive for clarity and readability in your own code and use minification tools to prepare your code for production use.

Other Options If you’re looking for other jQuery resources, here are some we recommend: • Learning jQuery 1.3, by Jonathan Chaffer, Karl Swedberg, and John Resig (Packt) • jQuery in Action, by Bear Bibeault, Yehuda Katz, and John Resig (Manning) • jQuery UI 1.6: The User Interface Library for jQuery, by Dan Wellman (Packt)

If You Have Problems Making Examples Work Before you check anything else, ensure that you are loading the jQuery library on the page—you’d be surprised how many times this is the solution to the “It’s not working!” problem. If you are using jQuery with another JavaScript library, you may need to use jQuery.noConflict() to make it play well with others. If you’re loading scripts that require the presence of jQuery, make sure you are loading them after you’ve loaded the jQuery library. Much of the code in this book requires the document to be “ready” before JavaScript can interact with it. If you’ve included code in the head of the document, make sure your code is enclosed in $(document).ready(function() { ... }); so that it knows to wait until the document is ready for interaction. Some of the features discussed in this book are available only in jQuery 1.3 and later. If you are upgrading from an older version of jQuery, make sure you’ve upgraded any plugins you’re using as well—outdated plugins can lead to unpredictable behavior. If you’re having difficulty getting an example to work in an existing application, make sure you can get the example working on its own before trying to integrate it with your existing code. If that works, tools such as Firebug for the Firefox browser can be useful in identifying the source of the problem. If you’re including a minified version of jQuery and running into errors that point to the jQuery library itself, you may want to consider switching to the full version of jQuery while you are debugging the issue. You’ll have a much easier time locating the line that is causing you trouble, which will often lead you in the direction of a solution. If you’re still stuck, consider posting your question to the jQuery Google group. Many of this book’s authors are regular participants in the group, and more often than not, someone in the group will be able to offer useful advice. The #jquery IRC channel on Freenode is another valuable resource for troubleshooting issues. Preface | xix

If none of this works, it’s possible we made a mistake. We worked hard to test and review all of the code in the book, but errors do creep through. Check the errata (described in the next section) and download the sample code, which will be updated to address any errata we discover.

If You Like (or Don’t Like) This Book If you like—or don’t like—this book, by all means, please let people know. Amazon reviews are one popular way to share your happiness (or lack of happiness), or you can leave reviews at the site for the book: http://oreilly.com/catalog/9780596159771/ There’s also a link to errata there. Errata gives readers a way to let us know about typos, errors, and other problems with the book. That errata will be visible on the page immediately, and we’ll confirm it after checking it out. O’Reilly can also fix errata in future printings of the book and on Safari, making for a better reader experience pretty quickly. We hope to keep this book updated for future versions of jQuery, and will also incorporate suggestions and complaints into future editions.

Conventions Used in This Book The following typographical conventions are used in this book: Italic Indicates Internet addresses, such as domain names and URLs, and new items where they are defined. Constant width

Indicates command lines and options that should be typed verbatim; names and keywords in programs, including method names, variable names, and class names; and HTML element tags, switches, attributes, keys, functions, types, namespaces, modules, properties, parameters, values, objects, events, event handlers, macros, the contents of files, or the output from commands. Constant width bold

Indicates emphasis in program code lines. Constant width italic

Indicates text that should be replaced with user-supplied values. This icon signifies a tip, suggestion, or general note.

xx | Preface

This icon indicates a warning or caution.

Using Code Examples This book is here to help you get your job done. In general, you may use the code in this book in your programs and documentation. You do not need to contact us for permission unless you’re reproducing a significant portion of the code. For example, writing a program that uses several chunks of code from this book does not require permission. Answering a question by citing this book and quoting example code does not require permission. Selling or distributing a CD-ROM of examples from O’Reilly books does require permission. Incorporating a significant amount of example code from this book into your product’s documentation does require permission. We appreciate, but do not require, attribution. An attribution usually includes the title, author, publisher, and ISBN. For example: “jQuery Cookbook, by Cody Lindley. Copyright 2010 Cody Lindley, 978-0-596-15977-1.” If you feel your use of code examples falls outside fair use or the permission given above, feel free to contact us at [email protected]

Safari® Books Online Safari Books Online is an on-demand digital library that lets you easily search over 7,500 technology and creative reference books and videos to find the answers you need quickly. With a subscription, you can read any page and watch any video from our library online. Read books on your cell phone and mobile devices. Access new titles before they are available for print, and get exclusive access to manuscripts in development and post feedback for the authors. Copy and paste code samples, organize your favorites, download chapters, bookmark key sections, create notes, print out pages, and benefit from tons of other time-saving features. O’Reilly Media has uploaded this book to the Safari Books Online service. To have full digital access to this book and others on similar topics from O’Reilly and other publishers, sign up for free at http://my.safaribooksonline.com.

How to Contact Us Please address comments and questions concerning this book to the publisher: O’Reilly Media, Inc. 1005 Gravenstein Highway North Sebastopol, CA 95472 Preface | xxi

800-998-9938 (in the United States or Canada) 707-829-0515 (international or local) 707-829-0104 (fax) To comment or ask technical questions about this book, send email to: [email protected] For more information about our books, conferences, Resource Centers, and the O’Reilly Network, see our website at: http://oreilly.com —Rebecca Murphey and Cody Lindley

xxii | Preface

CHAPTER 1

jQuery Basics

Cody Lindley

1.0 Introduction Since you’ve picked up a cookbook about jQuery, the authors of this book for the most part are going to assume that you have a loose idea about what exactly jQuery is and what it does. Frankly, cookbooks in general are typically written for an audience who seeks to enhance a foundation of knowledge that has already been established. Thus, the recipe-solution-discussion format is used to quickly get you solutions to common problems. However, if you are a jQuery newbie, don’t throw this book against the wall and curse us just yet. We’ve dedicated this chapter to you. If you are in need of a review or are jumping into this cookbook with little or no working knowledge of jQuery, this first chapter alone (the other chapters assume you know the basics) will aid you in learning the jQuery essentials. Now, realistically, if you have absolutely zero knowledge of JavaScript and the DOM, you might want to take a step back and ask yourself whether approaching jQuery without a basic understanding of the JavaScript core language and its relationship with the DOM is plausible. It would be my recommendation to study up on the DOM and JavaScript core before approaching jQuery. I highly recommend JavaScript: The Definitive Guide by David Flanagan (O’Reilly) as a primer before reading this book. But don’t let my humble opinion stop you if you are attempting to learn jQuery before you learn about the DOM and JavaScript. Many have come to a working knowledge of these technologies by way of jQuery. And while not ideal, let’s face it, it can still be done. With that said, let’s take a look at a formal definition of jQuery and a brief description of its functionality: jQuery is an open source JavaScript library that simplifies the interactions between an HTML document, or more precisely the Document Object Model (aka the DOM), and JavaScript.

In plain words, and for the old-school JavaScript hackers out there, jQuery makes Dynamic HTML (DHTML) dead easy. Specifically, jQuery simplifies HTML document 1

traversing and manipulation, browser event handling, DOM animations, Ajax interactions, and cross-browser JavaScript development. With a formal explanation of jQuery under our belts, let’s next explore why you might choose to use jQuery.

Why jQuery? It might seem a bit silly to speak about the merits of jQuery within this cookbook, especially since you’re reading this cookbook and are likely already aware of the merits. So, while I might be preaching to the choir here, we’re going to take a quick look at why a developer might choose to use jQuery. My point in doing this is to foster your basic knowledge of jQuery by first explaining the “why” before we look at the “how.” In building a case for jQuery, I’m not going to compare jQuery to its competitors in order to elevate jQuery’s significance. That’s because I just don’t believe that there really is a direct competitor. Also, I believe the only library available today that meets the needs of both designer types and programmer types is jQuery. In this context, jQuery is in a class of its own. Of the notorious JavaScript libraries and frameworks in the wild, I truly believe each has its own niche and value. A broad comparison is silly, but it’s nevertheless attempted all the time. Heck, I am even guilty of it myself. However, after much thought on the topic, I truly believe that all JavaScript libraries are good at something. They all have value. What makes one more valuable than the other depends more upon who is using it and how it’s being used than what it actually does. Besides, it has been my observation that micro differences across JavaScript libraries are often trivial in consideration of the broader goals of JavaScript development. So, without further philosophical ramblings, here is a list of attributes that builds a case for why you should use jQuery: • It’s open source, and the project is licensed under an MIT and a GNU General Public License (GPL) license. It’s free, yo, in multiple ways! • It’s small (18 KB minified) and gzipped (114 KB, uncompressed). • It’s incredibly popular, which is to say it has a large community of users and a healthy amount of contributors who participate as developers and evangelists. • It normalizes the differences between web browsers so that you don’t have to. • It’s intentionally a lightweight footprint with a simple yet clever plugin architecture. • Its repository of plugins is vast and has seen steady growth since jQuery’s release. • Its API is fully documented, including inline code examples, which in the world of JavaScript libraries is a luxury. Heck, any documentation at all was a luxury for years. • It’s friendly, which is to say it provides helpful ways to avoid conflicts with other JavaScript libraries.

2 | Chapter 1: jQuery Basics

• Its community support is actually fairly useful, including several mailing lists, IRC channels, and a freakishly insane amount of tutorials, articles, and blog posts from the jQuery community. • It’s openly developed, which means anyone can contribute bug fixes, enhancements, and development help. • Its development is steady and consistent, which is to say the development team is not afraid of releasing updates. • Its adoption by large organizations has and will continue to breed longevity and stability (e.g., Microsoft, Dell, Bank of America, Digg, CBS, Netflix). • It’s incorporating specifications from the W3C before the browsers do. As an example, jQuery supports a good majority of the CSS3 selectors. • It’s currently tested and optimized for development on modern browsers (Chrome 1, Chrome Nightly, IE 6, IE 7, IE 8, Opera 9.6, Safari 3.2, WebKit Nightly, Firefox 2, Firefox 3, Firefox Nightly). • It’s downright powerful in the hands of designer types as well as programmers. jQuery does not discriminate. • Its elegance, methodologies, and philosophy of changing the way JavaScript is written is becoming a standard in and of itself. Consider just how many other solutions have borrowed the selector and chaining patterns. • Its unexplainable by-product of feel-good programming is contagious and certainly unavoidable; even the critics seem to fall in love with aspects of jQuery. • Its documentation has many outlets (e.g., API browser, dashboard apps, cheat sheets) including an offline API browser (AIR application). • It’s purposely bent to facilitate unobtrusive JavaScript practices. • It has remained a JavaScript library (as opposed to a framework) at heart while at the same time providing a sister project for user interface widgets and application development (jQuery UI). • Its learning curve is approachable because it builds upon concepts that most developers and designers already understand (e.g., CSS and HTML). It is my opinion that the combination of the aforementioned jQuery points, and not any single attribute on its own, sets it apart from all other solutions. The total jQuery package is simply unmatched as a JavaScript tool.

The jQuery Philosophy The jQuery philosophy is “Write less, do more.” This philosophy can be further broken down into three concepts: • Finding some elements (via CSS selectors) and doing something with them (via jQuery methods) • Chaining multiple jQuery methods on a set of elements 1.0 Introduction | 3

• Using the jQuery wrapper and implicit iteration Understanding these three concepts in detail is foundational when it comes time to write your own jQuery code or augment the recipes found in this book. Let’s examine each of these concepts in detail.

Find some elements and do something with them Or more specifically stated, locate a set of elements in the DOM, and then do something with that set of elements. For example, let’s examine a scenario where you want to hide a
from the user, load some new text content into the hidden
, change an attribute of the selected
, and then finally make the hidden
visible again. This last sentence translated into jQuery code would look something like this:
old content


Let’s step through these four jQuery statements: • • • •

Hide the
element on the page so it’s hidden from the user’s view. Replace the text inside the hidden
with some new text (new content). Update the
element with a new attribute (class) and value (updatedContent). Show the
element on the page so it’s visible again to the viewing user.

If the jQuery code at this point is mystical syntax to you, that’s OK. We’ll dive into the basics with the first recipe in this chapter. Again, what you need to take away from this code example is the jQuery concept of “find some elements and do something with 4 | Chapter 1: jQuery Basics

them.” In our code example, we found all the
elements in the HTML page using the jQuery function (jQuery()), and then using jQuery methods we did something with them (e.g., hide(), text(), addClass(), show()).

Chaining jQuery is constructed in a manner that will allow jQuery methods to be chained. For example, why not find an element once and then chain operations onto that element? Our former code example demonstrating the “Find some elements and do something with them” concept could be rewritten to a single JavaScript statement using chaining. This code, using chaining, can be changed from this: //hide all divs on the page jQuery('div').hide(); //update the text contained inside of the div jQuery('div').text('new content'); //add a class attribute with a value of updatedContent to all divs jQuery('div').addClass("updatedContent"); //show all divs on the page jQuery('div').show();

to this: jQuery('div').hide().text('new content').addClass("updatedContent").show();

or, with indenting and line breaks, to this: jQuery('div') .hide() .text('new content') .addClass("updatedContent") .show();

Plainly speaking, chaining simply allows you to apply an endless chain of jQuery methods on the elements that are currently selected (currently wrapped with jQuery functionality) using the jQuery function. Behind the scenes, the elements previously selected before a jQuery method was applied are always returned so that the chain can continue. As you will see in future recipes, plugins are also constructed in this manner (returning wrapped elements) so that using a plugin does not break the chain. If it’s not immediately obvious, and based on the code in question, chaining also cuts down on processing overhead by selecting a set of DOM elements only once, to then be operated on numerous times by jQuery methods by way of chaining. Avoiding unnecessary DOM traversing is a critical part of page performance enhancements. Whenever possible, reuse or cache a set of selected DOM elements.

1.0 Introduction | 5

The jQuery wrapper set A good majority of the time, if jQuery is involved, you’re going to be getting what is known as a wrapper. In other words, you’ll be selecting DOM elements from an HTML page that will be wrapped with jQuery functionality. Personally, I often refer to this as a “wrapper set” or “wrapped set” because it’s a set of elements wrapped with jQuery functionality. Sometimes this wrapper set will contain one DOM element; other times it will contain several. There are even cases where the wrapper set will contain no elements. In these situations, the methods/properties that jQuery provides will fail silently if methods are called on an empty wrapper set, which can be handy in avoiding unneeded if statements. Now, based on the code we used to demonstrate the “Find some elements and do something with them” concept, what do you think would happen if we added multiple
elements to the web page? In the following updated code example, I have added three additional
elements to the HTML page, for a total of four
elements:
old content
old content
old content
old content


You may not have explicitly written any programmatic loops here, but guess what? jQuery is going to scan the page and place all
elements in the wrapper set so that the jQuery methods I am using here are performed (aka implicit iteration) on each DOM element in the set. For example, the .hide() method actually applies to each element in the set. So if you look at our code again, you will see that each method that we use will be applied to each
element on the page. It’s as if you had written a loop here to invoke each jQuery method on each DOM element. The updated code example will result in each
in the page being hidden, filled with updated text, given a new class value, and then made visible again. Wrapping your head around (pun intended) the wrapper set and its default looping system (aka implicit iteration) is critical for building advanced concepts around looping. Just keep in mind that a simple loop is occurring here before you actually do any additional looping (e.g., jQuery('div').each(function(){}). Or another way to look at

6 | Chapter 1: jQuery Basics

this is each element in the wrapper will typically be changed by the jQuery method(s) that are called. Something to keep in mind here is there are scenarios that you will learn about in the coming chapters where only the first element, and not all the elements in the wrapper set, is affected by the jQuery method (e.g., attr()).

How the jQuery API Is Organized There is no question that when I first started out with jQuery, my main reason for selecting it as my JavaScript library was simply that it had been properly documented (and the gazillion plugins!). Later, I realized another factor that cemented my love affair with jQuery was the fact that the API was organized into logical categories. Just by looking at how the API was organized, I could narrow down the functionality I needed. Before you really get started with jQuery, I suggest visiting the documentation online and simply digesting how the API is organized. By understanding how the API is organized, you’ll more quickly navigate the documentation to the exact information you need, which is actually a significant advantage given that there are really a lot of different ways to code a jQuery solution. It’s so robust that it’s easy to get hung up on implementation because of the number of solutions for a single problem. I’ve replicated here for you how the API is organized. I suggest memorizing the API outline, or at the very least the top-level categories. • jQuery Core — The jQuery Function — jQuery Object Accessors — Data — Plugins — Interoperability • Selectors — Basics — Hierarchy — Basic Filters — Content Filters — Visibility Filters — Attribute Filters — Child Filters — Forms — Form Filters • Attributes

1.0 Introduction | 7













— Attr — Class — HTML — Text — Value Traversing — Filtering — Finding — Chaining Manipulation — Changing Contents — Inserting Inside — Inserting Outside — Inserting Around — Replacing — Removing — Copying CSS — CSS — Positioning — Height and Widths Events — Page Load — Event Handling — Live Events — Interaction Helpers — Event Helpers Effects — Basics — Sliding — Fading — Custom — Settings Ajax — AJAX Requests

8 | Chapter 1: jQuery Basics

— AJAX Events — Misc. • Utilities — Browser and Feature Detection — Array and Object Operations — Test Operations — String Operations — Urls Before we jump into a sequence of basic jQuery recipes, I would like to mention that the recipes found in this chapter build on each other. That is, there is a logical formation of knowledge as you progress from the first recipe to the last. It’s my suggestion, for your first reading of these recipes, that you read them in order from 1.1 to 1.17.

1.1 Including the jQuery Library Code in an HTML Page Problem You want to use the jQuery JavaScript library on a web page.

Solution There are currently two ideal solutions for embedding the jQuery library in a web page: • Use the Google-hosted content delivery network (CDN) to include a version of jQuery (used in this chapter). • Download your own version of jQuery from jQuery.com and host it on your own server or local filesystem.

Discussion Including the jQuery JavaScript library isn’t any different from including any other external JavaScript file. You simply use the HTML

1.1 Including the jQuery Library Code in an HTML Page | 9



Notice that I am using—and highly recommend using for public web pages—the Google-hosted minified version of jQuery. However, debugging JavaScript errors in minified code is not ideal. During code development, or on the production site, it actually might be better to use the nonminified version from Google for the purpose of debugging potential JavaScript errors. For more information about using the Googlehosted version of jQuery, you can visit the Ajax libraries API site on the Web at http:// code.google.com/apis/ajaxlibs/. It’s of course also possible, and mostly likely old hat, to host a copy of the jQuery code yourself. In most circumstances, however, this would be silly to do because Google has been kind enough to host it for you. By using a Google-hosted version of jQuery, you benefit from a stable, reliable, high-speed, and globally available copy of jQuery. As well, you reap the benefit of decreased latency, increased parallelism, and better caching. This of course could be accomplished without using Google’s solution, but it would most likely cost you a dime or two. Now, for whatever reason, you might not want to use the Google-hosted version of jQuery. You might want a customized version of jQuery, or your usage might not require/have access to an Internet connection. Or, you simply might believe that Google is “The Man” and wish not to submit to usage because you are a control freak and conspiracy fanatic. So, for those who do not need, or simply who do not want, to use a Google-hosted copy of the jQuery code, jQuery can be downloaded from jQuery.com and hosted locally on your own server or local filesystem. Based on the template I’ve provided in this recipe, you would simply replace the src attribute value with a URL or directory path to the location of the jQuery JavaScript file you’ve downloaded.

1.2 Executing jQuery/JavaScript Coded After the DOM Has Loaded but Before Complete Page Load Problem Modern JavaScript applications using unobtrusive JavaScript methodologies typically execute JavaScript code only after the DOM has been completely loaded. And the reality of the situation is that any DOM traversing and manipulation will require that the DOM is loaded before it can be operated on. What’s needed is a way to determine when the client, most often a web browser, has completely loaded the DOM but has possibly not yet completely loaded all assets such as images and SWF files. If we were to use the window.onload event in this situation, the entire document including all assets would

10 | Chapter 1: jQuery Basics

need to be completely loaded before the onload event fired. That’s just too timeconsuming for most web surfers. What’s needed is an event that will tell us when the DOM alone is ready to be traversed and manipulated.

Solution jQuery provides the ready() method, which is a custom event handler that is typically bound to the DOM’s document object. The ready() method is passed a single parameter, a function, that contains the JavaScript code that should be executed once the DOM is ready to be traversed and manipulated. The following is a simple example of this event opening an alert() window once the DOM is ready but before the page is completely loaded:

The DOM is ready!



Discussion The ready() event handler method is jQuery’s replacement for using the JavaScript core window.onload event. It can be used as many times as you like. When using this custom event, it’s advisable that it be included in your web pages after the inclusion of stylesheet declarations and includes. Doing this will ensure that all element properties are correctly defined before any jQuery code or JavaScript code will be executed by the ready() event. Additionally, the jQuery function itself provides a shortcut for using the jQuery custom ready event. Using this shortcut, the following alert() example can be rewritten like so:

The DOM is ready!



The use of this custom jQuery event is necessary only if JavaScript has to be embedded in the document flow at the top of the page and encapsulated in the element. I simply avoid the usage of the ready() event by placing all JavaScript includes and inline code before the closing element. I do this for two reasons. First, modern optimization techniques have declared that pages load faster when the JavaScript is loaded by the browser at the end of a page parse. In other words, if you put JavaScript code at the bottom of a web page, then the browser will load everything in front of it before it loads the JavaScript. This is a good thing because most browsers will typically stop processing other loading initiatives until the JavaScript engine has compiled the JavaScript contained in a web page. It’s sort of a bottleneck in a sense that you have JavaScript at the top of a web page document. I realize that for some situations it’s easier to place JavaScript in the element. But honestly, I’ve never seen a situation where this is absolutely required. Any obstacle that I’ve encountered during my development by placing JavaScript at the bottom of the page has been easily overcome and well worth the optimization gains. Second, if speedy web pages are our goal, why wrap more functionality around a situation that can be elevated by simply moving the code to the bottom of the page? When given the choice between more code or less code, I choose less code. Not using the ready() event results in using less code, especially since less code always runs faster than more code. With some rationale out of the way, here is an example of our alert() code that does not use the ready() event:

The DOM is ready!



12 | Chapter 1: jQuery Basics

Notice that I have placed all of my JavaScript before the closing element. Any additional markup should be placed above the JavaScript in the HTML document.

1.3 Selecting DOM Elements Using Selectors and the jQuery Function Problem You need to select a single DOM element and/or a set of DOM elements in order to operate on the element(s) using jQuery methods.

Solution jQuery provides two options when you need to select element(s) from the DOM. Both options require the use of the jQuery function (jQuery() or alias $()). The first option, which uses CSS selectors and custom selectors, is by far the most used and most eloquent solution. By passing the jQuery function a string containing a selector expression, the function will traverse the DOM and locate the DOM nodes defined by the expression. As an example, the following code will select all the elements in the HTML document: link link link link link link

If you were to run this HTML page in a web browser, you would see that the code executes a browser alert() that informs us that the page contains six elements. I passed this value to the alert() method by first selecting all the elements and then using the length property to return the number of elements in the jQuery wrapper set.

1.3 Selecting DOM Elements Using Selectors and the jQuery Function | 13

You should be aware that the first parameter of the jQuery function, as we are using it here, will also accept multiple expressions. To do this, simply separate multiple selectors with a comma inside the same string that is passed as the first parameter to the jQuery function. Here is an example of what that might look like: jQuery('selector1, selector2, selector3').length;

Our second option for selecting DOM elements and the less common option is to pass the jQuery function an actual JavaScript reference to DOM element(s). As an example, the following code will select all the
elements in the HTML document. Notice that I’m passing the jQuery function an array of elements collected using the getElementsByTagName DOM method. This example produces the same exact results as our previous code example: link link link link link link

Discussion The heavy lifting that jQuery is known for is partially based on the selector engine, Sizzle, that selects DOM element(s) from an HTML document. While you have the option, and it’s a nice option when you need it, passing the jQuery function DOM references is not what put jQuery on everyone’s radar. It’s the vast and powerful options available with selectors that make jQuery so unique. Throughout the rest of the book, you will find powerful and robust selectors. When you see one, make sure you fully understand its function. This knowledge will serve you well with future coding endeavors using jQuery.

14 | Chapter 1: jQuery Basics

1.4 Selecting DOM Elements Within a Specified Context Problem You need a reference to a single DOM element or a set of DOM elements in the context of another DOM element or document in order to operate on the element(s) using jQuery methods.

Solution The jQuery function when passed a CSS expression will also accept a second parameter that tells the jQuery function to which context it should search for the DOM elements based on the expression. The second parameter in this case can be a DOM reference, jQuery wrapper, or document. In the following code, there are 12 elements. Notice how I use a specific context, based on the
element, to select only particular elements:

type="checkbox" /> type="radio" /> type="text" /> type="button" />



type="checkbox" /> type="radio" /> type="text" /> type="button" />


type="checkbox" /> type="radio" /> type="text" /> type="button" />

name="" name="" name="" name=""



Discussion It’s also possible, as mentioned in the solution of this recipe, to select documents as the context for searching. For example, it’s possible to search within the context of an XML document that is sent back from doing an XHR request (Ajax). You can find more details about this usage in Chapter 16.

1.5 Filtering a Wrapper Set of DOM Elements Problem You have a set of selected DOM elements in a jQuery wrapper set but want to remove DOM elements from the set that do not match a new specified expression(s) in order to create a new set of elements to operate on.

Solution The jQuery filter method, used on a jQuery wrapper set of DOM elements, can exclude elements that do not match a specified expression(s). In short, the filter() method allows you to filter the current set of elements. This is an important distinction from the jQuery find method, which will reduce a wrapped set of DOM elements by finding (via a new selector expression) new elements, including child elements of the current wrapped set. To understand the filter method, let’s examine the following code: link link link link

16 | Chapter 1: jQuery Basics

link link link link

The HTML page in the code example just shown contains a web page with 10 elements. Those links that are external links are given a class name of external. Using the jQuery function, we select all elements on the page. Then, using the filter method, all those elements that do not have a class attribute value of external are removed from the original set. Once the initial set of DOM elements are altered using the filter() method, I invoke the length property, which will tell me how many elements are now in my new set after the filter has been applied.

Discussion It’s also possible to send the filter() method a function that can be used to filter the wrapped set. Our previous code example, which passes the filter() method a string expression, can be changed to use a function instead: alert( jQuery('a') .filter(function(index){ return $(this).hasClass('external');}) .length + ' external links' );

Notice that I am now passing the filter() method an anonymous function. This function is called with a context equal to the current element. That means when I use $(this) within the function, I am actually referring to each DOM element in the wrapper set. Within the function, I am checking each
element in the wrapper set to see whether the element has a class value (hasClass()) of external. If it does, Boolean true, then keep the element in the set, and if it doesn’t (false), then remove the element from the set. Another way to look at this is if the function returns false, then the element is removed. If the function returns any other data value besides false, then the element will remain in the wrapper set. You may have noticed that I have passed the function a parameter named index that I am not using. This parameter, if needed, can be used to refer numerically to the index of the element in the jQuery wrapper set.

1.5 Filtering a Wrapper Set of DOM Elements | 17

1.6 Finding Descendant Elements Within the Currently Selected Wrapper Set Problem You have a set of selected DOM elements (or a single element) and want to find descendant (children) elements within the context of the currently selected elements.

Solution Use the .find() method to create a new wrapper set of elements based on the context of the current set and their descendants. For example, say that you have a web page that contains several paragraphs. Encapsulated inside of these paragraphs are words that are emphasized (italic). If you’d like to select only elements contained within

elements, you could do so like this:

Ut ad videntur facilisis elit cum. Nibh insitam erat facit saepius magna. Nam ex liber iriure et imperdiet. Et mirum eros iis te habent.

Claram claritatem eu amet dignissim magna. Dignissim quam elit facer eros illum. Et qui ex esse tincidunt anteposuerit. Nulla nam odio ii vulputate feugait.

In quis laoreet te legunt euismod. Claritatem consuetudium wisi sit velit facilisi.



Keep in mind that we could have also written this code by passing a contextual reference as a second parameter to the jQuery function: alert('The three paragraphs in all contain ' + jQuery('em',$('p')).length + ' italic words');

Additionally, it’s worth mentioning that the last two code examples are demonstrative in purpose. It is likely more logical, if not pragmatic, to use a CSS selector expression to select all the descendant italic elements contained within the ancestor

elements.

18 | Chapter 1: jQuery Basics

alert('The three paragraphs in all contain ' + jQuery('p em').length + ' italic words');

Discussion The jQuery .find() method can be used to create a new set of elements based on context of the current set of DOM elements and their children elements. People often confuse the use of the .filter() method and .find() method. The easiest way to remember the difference is to keep in mind that .find() will operate/select the children of the current set while .filter() will only operate on the current set of elements. In other words, if you want to change the current wrapper set by using it as a context to further select the children of the elements selected, use .find(). If you only want to filter the current wrapped set and get a new subset of the current DOM elements in the set only, use .filter(). To boil this down even more, find() returns children elements, while filter() only filters what is in the current wrapper set.

1.7 Returning to the Prior Selection Before a Destructive Change Problem A destructive jQuery method (e.g., filter() or find()) that was used on a set of elements needs to be removed so that the set prior to the use of the destructive method is returned to its previous state and can then be operated as if the destructive method had never been invoked.

Solution jQuery provides the end() method so that you can return to the previous set of DOM elements that were selected before using a destructive method. To understand the end() method, let’s examine the following HTML.

text

Middle text

text



The first alert() statement in the code contains a jQuery statement that will search the document for all

elements and then apply filter() to the selected

elements in the set selecting only the one(s) with a class of middle. The length property then reports how many elements are left in the set: alert(jQuery('p').filter('.middle').length); //alerts 1

The next alert() statement makes use of the end() method. Here we are doing everything we did in the prior statement except that we are undoing the filter() method and returning to the set of elements contained in the wrapper set before the filter() method was applied: alert(jQuery('p').filter('.middle').end().length); //alerts 3

The last alert() statement demonstrates how the end() method is used twice to remove both the filter() and find() destructive changes, returning the wrapper set to its original composition: alert(jQuery('p').filter('.middle').find('span').end().end().length); //alerts 3

Discussion If the end() method is used and there were no prior destructive operations performed, an empty set is returned. A destructive operation is any operation that changes the set of matched jQuery elements, which means any traversing or manipulation method that returns a jQuery object, including add(), andSelf(), children(), closes(), filter(), find(), map(), next(), nextAll(), not(), parent(), parents(), prev(), prevAll(), siblings(), slice(), clone(), appendTo(), prependTo(), insertBefore(), insertAfter(), and replaceAll().

1.8 Including the Previous Selection with the Current Selection Problem You have just manipulated a set of elements in order to acquire a new set of elements. However, you want to operate on the prior set as well as the current set.

Solution You can combine a prior selection of DOM elements with the current selection by using the andSelf() method. For example, in the following code, we are first selecting all

elements on the page. Next we manipulate this set of elements by finding all

elements contained within the

elements. Now, in order to operate on both the
and the

elements found within the

, we could include the
into 20 | Chapter 1: jQuery Basics

the current set by using andSelf(). Had I omitted the andSelf(), the border color would have only been applied to the

elements:

Paragraph

Paragraph



Discussion Keep in mind that when you use the andSelf() method, it will only add into the current set being operated on and the prior set, but not all prior sets.

1.9 Traversing the DOM Based on Your Current Context to Acquire a New Set of DOM Elements Problem You have selected a set of DOM elements, and based on the position of the selections within the DOM tree structure, you want to traverse the DOM to acquire a new set of elements to operate on.

Solution jQuery provides a set of methods for traversing the DOM based on the context of the currently selected DOM element(s). For example, let’s examine the following HTML snippet:


href="#">link
href="#">link
href="#">link href="#">link

1.9 Traversing the DOM Based on Your Current Context to Acquire a New Set of DOM Elements | 21

Now, let’s select the second
  • element using the :eq() index custom selector: //selects the second element in the set of
  • 's by index, index starts at 0 jQuery('li:eq(1)');

    We now have a context, a starting point within the HTML structure. Our starting point is the second
  • element. From here we can go anywhere—well, almost anywhere. Let’s see where we can go using a couple of the methods jQuery provides for traversing the DOM. Read the comments in the code for clarification: jQuery('li:eq(1)').next() //selects the third
  • jQuery('li:eq(1)').prev() //selects the first
  • jQuery('li:eq(1)').parent() //selects the


  • 11.1 Validating Forms | 239



    Any field with the class required is checked to have any content at all. Other methods in this example include the following: email

    Checks that the field contains a valid email address url

    Checks that the field contains a valid URL minlength

    Checks that the field contains at least x characters; here x is specified via an attribute: minlength="2"

    Discussion The validation plugin promotes one specific approach to client-side validation: perform as much work as possible in the browser, and ask the server for help only in special cases, which are covered by the remote method, for example, to check whether a username is still available. A different approach would avoid replicating validation rules and methods on both the client and server sides, instead sending the whole form via Ajax to the server, usually on submit of the form. It could then use the same logic on the server side that is in place already. The drawback is that user feedback is slower, because it is impractical to send a request for every keypress. It’s also not very likely that the server validation was written with Ajax validation in mind, making it impractical to reuse it. In that case, you’d have to plan up front to use it that way. The validation plugin can be added to a form later, and apart from remote validation, there is no need to adapt the application in any way. This makes it useful for a simple comment form on a blog, as well as more complex forms on some intranet application and anything in between. The most important building blocks for the plugin are rules and methods. Methods contain validation logic, like the email method that uses a regular expression to determine whether a value is a valid email address. Rules wire input fields together with methods, where a single rule is a pair of an input field and a method. The email field then has one rule for making it required and one for making it an email address.

    Methods The plugin has about 19 built-in methods. The essential method is required—when specified, the field has to be filled out. When left out, most other methods will be ignored on an empty field. The only exception to that is the equalTo method, which checks that the content of a field is exactly the same as some other field, which even

    240 | Chapter 11: HTML Form Enhancements with Plugins

    applies for an empty field. The rule itself is most commonly used for “Confirm password” fields. The email, url, date, dateISO, dateDE, number, numberDE, digits, and creditcard methods all check for certain data types, with simple variations for different locales. For example, number requires a U.S. number format like 1,000.00, and numberDE requires the German number format 1.000,00. The min and max and range methods check the value of a number, while minlength, maxlength, and rangelength check the number of characters. In case of a select input or checkboxes, min, max, and range validate the number of selected options or checked checkboxes. In case of file inputs, the accept method comes in handy and checks the file extension, by default looking for .gif, .png, .jpg, or .jpeg. The remote method is the only method that delegates the actual validation logic elsewhere, to the server side. It gets a URL as the parameter, pointing at some server-side resource. This could be a script that does a database query, for example, for checking if a username is already taken or if a specified email address is already registered. An example of a registration form using the remote method for both username and email fields can be found at http://jquery-cookbook.com/go/plugin-validation-remote-demo. Custom methods. Custom methods are a good way to extend the plugin with applicationspecific requirements. You may have a form where users enter URLs that have to start with a certain corporate domain. A custom method could encapsulate the necessary validation: jQuery.validator.addMethod("domain", function(value, element) { return this.optional(element) || /^http:\/\/mycorporatedomain.com/.test(value); }, "Please specify the correct domain for your documents");

    The first argument to jQuery.validator.addMethod is the name of the custom method, and it must be a valid JavaScript identifier. The second argument is a function that implements the actual validation. If it returns true, the input is considered valid. It uses this.optional(element) to determine whether that input has no value and should therefore be skipped—all default methods use the same call. The third argument specifies the default message for the new method. Writing methods that accept a parameter works very similarly: jQuery.validator.addMethod("math", function(value, element, params) { return this.optional(element) || value == params[0] + params[1]; }, jQuery.format("Please enter the correct value for {0} + {1}"));

    In this case, the default message is specified with the help of jQuery.format, a templating helper the plugin provides. The indexed curly-braced placeholders are replaced with the actual parameters when the validation is run.

    11.1 Validating Forms | 241

    Custom methods can also reuse existing methods, which is useful to specify different default messages for a single method. In this example, the required method is aliased to customerRequired with a different default message: $.validator.addMethod("customerRequired", $.validator.methods.required, "Customer name required");

    A collection of ready-to-use custom methods are bundled with the plugin in additionalMethods.js.

    Rules There are four distinct ways to specify rules: two in code and two inline as metadata. The previous example uses classes and attributes as metadata, which the plugin supports by default. When the metadata plugin is available, rules can be embedded in various ways, for example, inside the class attribute:

    Here the class contains JavaScript literals inside curly braces, which is very similar in syntax to specifying rules in code via the rules option: $("#myform").validate({ rules: { name: { required: true, minlength: 2 }, email: { required: true, email: true }, url: "url", comment: "required" } });

    The object keys like name, email, url, and comment always refer to the name of the element, not the ID. Note the shortcuts used for url and comment, where only a single rule is necessary. This isn’t available when specifying rules with parameters, like minlength. Some rules need to be added later, which is possible using the fourth way, the rules plugin method: // initialize the validation first $("#myform").validate(); // some time later, add more rules $("#username").rules("add", { minlength: 2});

    Rules can also be removed that way: $("#username").rules("remove", "required");

    242 | Chapter 11: HTML Form Enhancements with Plugins

    This can come in handy when implementing a “Forgot password” link on a login form: $("#loginform").validate({ username: "required", password: "required" }); $("a#forgotPassword").click(function(e) { $("#password").rules("remove", "required"); $("#loginform").submit(); $("#password").rules("add", "required"); return false; });

    That click event code removes the required rule from the password, tries to submit the form (triggering the validation), and adds the rule back. That way, the username field is still being validated, and if the validation fails, the password field will be required again (in case of a normal form submit). Dependencies. Often the validation behavior of a field depends on some more factors than just a link being clicked. Those can be handled using parameters for the required method. The parameter can be a selector or a callback. The selector is useful when the dependency can be written in a simple expression. An email field may be required only when the newsletter checkbox is selected: email: { required: "#newsletter:checked" }

    A callback can be used for expressions of any complexity, for example, when the field depends on the state of multiple other fields: email: { required: function(element) { return $("#newsletter:checked").length && $("#telephone:blank"); } }

    Custom expressions. The previous example used the :blank expression to select an element only when it has no value at all or only whitespace. The plugin also provides the :filled expression, the inversion of :blank. jQuery itself provides :checked, and the validation plugin adds the inversion :unchecked. Both are useful when specifying dependencies on radio buttons or checkboxes. While you could use the :not expression to inverse :filled or :checked, :blank and :unchecked make the selector more readable and therefore easier to understand at a glance.

    Error messages Similar to rules, there are a few ways to specify messages, both in code and inline. Inline messages are read from the title attribute:

    11.1 Validating Forms | 243



    That will produce a single error message for each rule. An alternative inline approach is to use the metadata plugin (see “Rules” on page 242):

    With this approach, you can specify a message for each rule, which is also possible when using the messages option: $("#myform").validate({ messages: { email: { required: "Required", email: "Not a valid email address" } } });

    Again, the keys—here, email—refer to the name of the input, not the ID, just the same as specifying rules. For more dynamic scenarios, the rules plugin method can be used: $("#myform").validate(); // sometime later $("#email").rules("add", { messages: { email: "A valid email address, please!" } });

    If you use some of the alternatives to the title attribute while using a regular title, you can suppress the plugin from checking the attribute for messages: $("#myform").validate({ ignoreTitle: true });

    Localization. The default messages are in English (with the exception of dateDE and numberDE). In addition, the plugin provides (at the time of this writing) 17 localizations. Usage is plain and simple: just copy the messages_xx.js file you need to your project, and include it after the validation plugin. For example, here’s the code for the Swedish localization:

    With that in place, instead of “Please enter a valid email address.” you’ll get “Ange en korrekt e-postadress.” Error element. By default error messages are inserted into the DOM next to the element that they are referring to. An error message is inserted as a label element, with the for 244 | Chapter 11: HTML Form Enhancements with Plugins

    attribute set to the id of the validated element. Using a label with the for attribute leverages the browser feature where a click on the label gives focus to the input field. So by default, the user can click the error message to give the invalid field focus. If you need a different element type, use the errorElement option: $("#myform").validate({ errorElement: "em" });

    The plugin will still use the for attribute then, but the auto linking the browser provides won’t work. Layout. If you want to customize the position where error messages are inserted, the errorPlacement option is useful. We may have a form that uses a table for layout, where the first column contains the regular label, the second the input, and the third the messages:
    $("#signupform").validate({ errorPlacement: function(error, element) { error.appendTo( element.parent("td").next("td") ); } });

    Another common requirement is to display a general message above the form. The errorContainer option helps with that: $("#myform").validate({ errorContainer: "#messageBox1" });

    In this example, an element with the ID messageBox1 would be shown when the form is invalid and would be hidden when valid. This can also be combined with the errorLabelContainer option. When specified, error labels aren’t placed next to their input elements but instead added to a single element above or below the form. Combined with the errorContainer and wrapper options, messages are added to a list of errors above the form:

    11.1 Validating Forms | 245

    There are a few problems, please see below for details.

      var container = $('div.container'); // validate the form when it is submitted $("#myform").validate({ errorContainer: container, errorLabelContainer: $("ul", container), wrapper: 'li' });

      Handling the submit Once the form is valid, it has to be submitted. By default that just works as any other form submit. To submit the form via Ajax, the submitHandler option can be used, together with the form plugin (see Recipe 11.6 for more details): $(".selector").validate({ submitHandler: function(form) { $(form).ajaxSubmit(); } });

      The invalidHandler callback is useful for running code on an invalid submit. The following example displays a summary of the missing fields: $("#myform").validate({ invalidHandler: function(e, validator) { var errors = validator.numberOfInvalids(); if (errors) { var message = errors == 1 ? 'You missed 1 field. It has been highlighted below' : 'You missed ' + errors + ' fields. They have been highlighted below'; $("div.error span").html(message); $("div.error").show(); } else { $("div.error").hide(); } } });

      The Marketo demo shows this behavior in action (http://jquery-cookbook.com/go/plu gin-validation-marketo-demo).

      Limitations So, when does it make sense to not use the plugin and write a validation solution from scratch? There are certain limitations: forms where groups of inputs, like checkboxes, have different name attributes are hard to validate as a group. Lists of inputs that all have 246 | Chapter 11: HTML Form Enhancements with Plugins

      the same name can’t be validated, because each individual input needs its own unique name. If you stick with the naming convention of unique names for individual inputs and one name for groups of checkboxes or radio buttons, the plugin works fine. If your application has only a login form, the plugin is probably overkill, and it would be difficult to justify the file size; however, if you use the plugin somewhere else on a site, it can be used for the login form as well.

      11.2 Creating Masked Input Fields Problem There are certain input types that are quite error prone, like a credit card number. A simple typo that goes unnoticed at first can cause weird errors much later. That also applies to dates or phone numbers. These have a few features in common: • A fixed length • Mostly numbers • Delimiting characters at certain positions

      Solution A jQuery plugin that can improve the feedback is the masked input plugin. It is applied to one or more inputs to restrict what can be entered while inserting delimiters automatically. In this example, a phone input is masked:


      The plugin file is included in addition to jQuery itself. In the document-ready callback, the input with ID phone is selected, and the mask method is called. The only argument specifies the mask to use, here describing the format of a U.S. phone number. 11.2 Creating Masked Input Fields | 247

      Discussion There are four characters with a special meaning available when specifying the mask: a

      Any alpha character from a−z and A−Z 9

      Any digit from 0–9 *

      Any alphanumeric character, that is, a–z, A–Z, and 0–9 ?

      Anything after this is optional Any other character, like the parentheses or hyphen in the phone mask, are considered literals, which the plugin automatically inserts into the input and which the user can’t remove. By default, the plugin inserts an underscore ( _ ) for each variable character. For the phone example, the input would display the following value once focused: (___) ___-____

      When the user starts typing, the first underscore gets replaced if it is a valid character, here a digit. The other literals are skipped as well. The underscore placeholder can be customized by passing an additional argument: $("#phone").mask("(999) 999-9999", {placeholder: " "});

      In this case, whitespace would be displayed instead of the underscore. It’s also possible to define new mask characters: $.mask.definitions['~'] = '[+−]'; $("#eyescript").mask("~9.99 ~9.99 999");

      Here the new mask character is a tilde, and allowed values for it are + and −, specified as a regular expression character class. The tilde can then be used in a mask. The quotation mark enables masks with a fixed part and an optional part. A phone number with an optional extension could be defined like this: $("#phone").mask("(999) 999-9999? x99999");

      When a masked input is combined with the validation plugin (Recipe 11.1), it’s important the field proper rules are defined for it. Otherwise, the validation plugin may accept the placeholder characters of the mask plugin as valid input, irritating the user when an invalid field is marked as valid while he just inserted the first character.

      248 | Chapter 11: HTML Form Enhancements with Plugins

      Limitations The significant limitation of the plugin is the fixed-length requirement. It can’t be used for anything with a variable length, like currency value. For example, “$ 999,999.99” would require a value between 100,000.00 and 999,999.99 and can’t accept anything above or below.

      11.3 Autocompleting Text Fields Problem There are two HTML input types that allow a user to select one value out of a list of existing values: radio buttons and selects. Radio buttons work well for lists with up to eight items, and selects work well with up to 30 to 150, depending on the type of data. Both fall short when the user can enter a new value as well—in this case they are usually accompanied by an “Other” field. Both become useless when the list is big, maybe 500 or 500,000 items.

      Solution The jQuery UI autocomplete widget can solve the various situations where a select isn’t enough. In the simplest case, the data to display is available in a JavaScript array: var months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; $("#month").autocomplete({ source: months });

      Here we apply the autocomplete plugin to a month input, with the data being a plain JavaScript array. When the data isn’t already available on the client side, the plugin can get it from a server-side resource: $("#month").autocomplete({ source: "addresses.php" });

      The plugin then sends a GET request to that resource, with the user-entered value appended as the q parameter, e.g., addresses.php?q=ma. As a response, the plugin expects a list of newline separated values: Mainstreet Mallstreet Marketstreet

      11.3 Autocompleting Text Fields | 249

      Discussion The first decision to make when using the plugin is deciding on local or remote data. With local data, the complete data set is already present in the browser’s memory. It could have been loaded as part of the page or via a separate Ajax request. In any case, it’s loaded just once. This mode is practical when the data is small and static—less than 500 rows of data—and doesn’t change while selecting a value. The big advantage of local data is that it’s extremely fast to find matching values. Remote data is loaded from the server in small chunks (up to 100 rows per chunk makes sense). This works with both small data sets as well as very big data sets (say, more than half a million rows). As the data is loaded from the server, finding matching values is slower when compared to local data. This is mitigated by loading big enough chunks, which can then be filtered down on the client side without additional requests.

      11.4 Selecting a Range of Values Problem Imagine a car search interface: the user inputs the price range that’s acceptable for him, and while changing the value, the list of available cars in that range is updated. The HTML form elements for that type of input—plain text input, radio buttons, selects— aren’t good enough. On the one hand, each requires an exact value. On the other, they fail to visualize the price range. It’s also not possible to move the entire range; instead, the user has to update both the start and end values, one by one.

      Solution The jQuery UI slider widget can transform two text inputs into a range slider. The start and end values of the range can be dragged using the mouse or using the cursor keys. The default slider is applied to a simple
      , with no options necessary:
      $("#slider").slider();

      For that to work, jQuery, jQuery UI core, and the slider .js files must be included, in addition to a UI theme:

      While this adds a nice-looking slider to the page, it doesn’t yet really do anything useful.

      250 | Chapter 11: HTML Form Enhancements with Plugins

      In the case of the car search, we want to put the selected values into an input field and display them to the user:





      Based on that markup, we can create a range slider: var slider = $("#slider-range").slider({ range: true, min: 0, max: 500, values: [75, 300], slide: function(event, ui) { $("#amount").val('$' + ui.values[0] + ' − $' + ui.values[1]); } }); $("#amount").val('$' + slider.slider("values", 0) + ' − $' + slider.slider("values", 1));

      Setting the range option to true instructs the plugin to create two handles instead of just one. The min and max options specify the total range available; the values option the starting positions. The slide callback is triggered when a handle is moved. Here it updates the amount input to display the selected price range.

      Discussion Binding a slider to a text input is one option; binding it to a select, and using the options of the select as values, is another. Let’s take the example of a room reservation form where the user enters the minimum number of beds. The maximum number of beds is six; therefore, a slider isn’t a bad choice to start with. Using progressive enhancement, we can enhance the select with a slider and feed changes to the slider back to the var select = $("#minbeds"); var slider = $('
      ').insertAfter(select).slider({ min: 1, max: 6,

      11.4 Selecting a Range of Values | 251

      range: "min", value: select[0].selectedIndex + 1, slide: function(event, ui) { select[0].selectedIndex = ui.value − 1; }

      }); $("#minbeds").click(function() { slider.slider("value", this.selectedIndex + 1); });

      Instead of using existing markup, which doesn’t have any semantic meaning, we generate the
      on the fly and insert it into the DOM, right after the

      to which you then apply the spinner plugin: $("#value").spinner();

      This will create and position the up/down buttons and add the necessary keyboard handling events. Use the spinner plugin to add buttons to in- and decrement the value, either by clicking the buttons or giving the input focus and using the cursor keys. It also restricts the input to numeric values—when entering abc into the spinner, it’ll get replaced with the default value on blur. Unless specified otherwise, it’s a zero.

      Discussion The plugin offers a few options to restrict the input further: • min sets the lower limit, e.g., −10 or 100. • max sets the upper limit, e.g., 10 or 200. • stepping restricts the value to certain increments, e.g., 5; the default is 1. When the spinner is used to input a currency value, the currency option can be used to display the appropriate symbol inside the input. The following example puts these all together and creates a form for donating money:

      11.5 Entering a Range-Constrained Value | 253




      We have a select for the currency and a text input for the amount: var currency = $("#currency").change(function() { $("#amount").spinner("option", "currency", $(this).val()).blur(); }); $("#amount").spinner({ currency: currency.val(), min: 5, max: 1000, step: 5 });

      We bind a change event to the currency select to update the currency option of the spinner whenever the selection changes. The spinner itself is initialized with the current value, as well as limits for min, max, and step, restricting the value somewhere between 5 and 1,000, with increments of 5, e.g., 10, 15, 20, and so on.

      Google Maps integration The value may also be a decimal number; in that case, the decimal option can be used to specify the number of allowed digits after the decimal point. In the following example, we display a Google map and use spinners to specify the latitude and longitude values. To start with, we include the Google Maps API scripts:

      With that in place, we can add markup for the spinners and the actual map, along with some minimal styles: id="lng" name="lng" value="-93.278046" />



      Based on that, we can initialize the map and link it with the spinners: function latlong() { return new google.maps.LatLng($("#lat").val(),$("#lng").val()); } function position() {

      254 | Chapter 11: HTML Form Enhancements with Plugins

      map.set_center(latlong()); } $("#lat, #lng").spinner({ precision: 6, change: position }); var map = new google.maps.Map($("#map")[0], { zoom: 8, center: latlong(), mapTypeId: google.maps.MapTypeId.ROADMAP });

      The position function sets the center of the map to the latitude and longitude values obtained from the spinners. They are initialized with the decimal option set to 6, and passing the position function for the change option. With that, the map is updated whenever one of the spinners changes. Then the map itself is initialized, using the Google Maps API. The drawback of the spinner in this case is that increments and decrements affect only the digits before the decimal point, so scrolling is rather rough. The increment option rounds any value below one up to one, so it can’t help here.

      11.6 Uploading Files in the Background Problem File upload is part of many web applications but badly supported by browsers. The biggest problem is the lack of feedback of the upload status, while any action of the users disrupts the upload. A simple progress bar could improve the feedback but requires quite some work on the server side, while the problem of disruptive actions remains.

      Solution To improve the situation, file uploads should be performed in the background. This allows the application to continue accepting other user input. The jQuery form plugin makes it trivial to switch from the native browser upload to Ajax background uploading. With this form:


      all you need to add is a call to ajaxForm: $("#uploadform").ajaxForm();

      11.6 Uploading Files in the Background | 255

      However, just doing the upload in the background without any feedback of the completed upload isn’t enough, so we use the success option to display an alert about the successful upload: $("#uploadform").ajaxForm({ success: function() { alert("Upload completed!"); } });

      Discussion The ajaxForm method binds itself to the submit event of the form, which allows it to also include the button used to submit the form in the Ajax request. The latter isn’t available when using ajaxSubmit. The ajaxSubmit method is useful on its own when the form submit is handled elsewhere, for example, by the validation plugin. To integrate validation and Ajax submit, ajaxSubmit should be used in the submitHandler option: $("#commentform").validate({ submitHandler: function(form) { $(form).ajaxSubmit({ success: function() { $(form).clearForm(); alert("Thanks for your comment!"); } }); } });

      In addition to the alert, the clearForm method, also provided by the form plugin, removes all values from the form. This makes it easy for the user to upload another file.

      11.7 Limiting the Length of Text Inputs Problem It is common to limit the amount of characters in a textarea, like the 140 characters for Twitter or the 500 characters for a YouTube comment. Informing the user that he entered too much, after he submitted a form, is frustrating, so it makes sense to display an indicator of the available characters left.

      Solution The maxlength plugin solves this by adding a “Characters left: x” indicator in front or after the textarea. The plugin, after being applied on a text input or textarea, looks for an element with the class charsLeft to update with the count:

      Characters left: 10



      256 | Chapter 11: HTML Form Enhancements with Plugins

      $('textarea').maxlength();

      To make this less intrusive, we can create the necessary elements with jQuery, resulting in a simpler form markup:
      var textarea = $('textarea'); $('

      Characters left: 10

      ').insertBefore(textarea); textarea.maxlength();

      Discussion In the case of Twitter, the textarea allows you to go over the 140-character limit, but you can’t submit. This helps a lot when pasting longer text that wouldn’t fit into the 140-character limit and editing it afterward. To get a similar effect with the maxlength plugin, we can set the hardLimit option to false. However, that doesn’t affect the actual submit but could be handled elsewhere, e.g., by the validation plugin (see Recipe 11.1). The plugin also supports counting words instead of characters, by setting the words option to true. Instead of having the plugin look for the default .charsLeft selector, we can also set the feedback option. Here is another example using all three of these options:

      x characters left

      $('textarea').maxlength({ feedback: "p>span", hardLimit: false, words: true });

      11.8 Displaying Labels Above Input Fields Problem A page layout doesn’t have enough space in front of an input element to display a label, the function of the input is obscured, and a title alone isn’t visible enough. Search and login forms are often subject to space constraints. There just isn’t enough visual space to display a label in front of the input field. Though without the label, the function of the input is obscured. A title attribute isn’t enough to fix the problem, 11.8 Displaying Labels Above Input Fields | 257

      because it’s rather hard to spot, requiring the user to mouse over the input and rest there.

      Solution The most common example, the search field, can be solved by displaying “search” inside the field with a light gray to emphasize that it’s just a label, not the actual text to search for. When focusing the field, the text is removed. When blurring the field, the text is returned, unless something else was entered. The less common example is a space-constrained login form, consisting of username and password fields. The password field needs to display the watermark as plain text, while the password to be entered (or prefilled by the browser) must still be obfuscated. In both cases, the watermark shouldn’t be submitted as a value. The watermark plugin solves this problem by displaying a label element above the actual input, hiding the label when the input gets focus, and displaying it again when the empty field is blurred. Using a label above the field, instead of modifying the text inside the field, makes this solution also work with password fields and avoids having to clear watermark values on submit. The default usage calls the watermark plugin method and passes the value to display: $("#search").watermark("Search");

      Discussion Instead of passing the value to the plugin, it can also be specified as metadata, using the metadata plugin, in the markup, which is more practical when several watermarks are used or when those are generated on the server side:
      $("#loginform input").watermark();

      Metadata has the drawback that it doesn’t build on progressive enhancement. To improve that, label elements should be used as for a normal form, with the plugin positioning the labels at the right position:


      258 | Chapter 11: HTML Form Enhancements with Plugins



      In this case, the plugin is applied to the labels instead of the inputs: $("#loginform label").watermark();

      The plugin then uses the for attribute of each label to find the associated input and position it above the input.

      11.9 Growing an Input with Its Content Problem A textarea is part of an interface and is often too large or too small, depending on the user’s input. Either it’s too big and other important elements get out of sight, or it’s too small and the user has to scroll too much.

      Solution Use the elastic plugin to start with a small default height and have the height autogrow when the user enters a certain amount of text. Usage is plain and simple. Start with a textarea:

      And apply the plugin to it: $("#commentbody").elastic();

      Discussion The plugin binds both a timer and a blur event to the textarea to look for changes. When the content changes, it copies the content into a hidden textarea with the same styles applied to it as the original, calculates the new height for that, and if it exceeds the current height of the original, starts an animation to adapt. This allows the textarea to both grow and shrink as content is added or removed. An alternative is to let the user resize the textarea. Safari offers that by default for any textarea. The jQuery UI resizable plugin can add that to other browsers as well. Starting with the same textarea, we apply the resizable plugin, customizing the handle option to display only one handle on the bottom right: $("#resizable").resizable({ handles: "se" });

      11.9 Growing an Input with Its Content | 259

      With that and the jQuery UI base theme included, the handle gets displayed below the textarea. To move it into the bottom-right corner of the textarea, we have to add some CSS: .ui-resizable-handle { bottom: 17px; }

      11.10 Choosing a Date Problem Date inputs are necessary for searching for events, flights, or hotels, or entering a birth date in a registration form. A common solution is to use three selects, for the day, month, and year components. While that works OK for a date of birth, it can get very cumbersome when searching for a flight in a certain time period.

      Solution The jQuery UI datepicker can solve the problem by offering a calendar together with a lot of customization options to optimize for various applications. The default datepicker works by simply applying it to an input: $("#startAt").datepicker();

      This will bind the events necessary to show the datepicker when the input gets focused, starting with the current date. Next and previous buttons can be used to select the next or previous month, and a calendar can be used to select a day. To make the datepicker more useful, we need to adapt it to the application where it’s used. For the flight-search example, we can assume that the user looks for a flight sometime in the next three months, and therefore it displays three months at once, starting with the next week from the current date: type="text" id="from" name="from"/> for="to">to type="text" id="to" name="to"/>

      We start with two inputs, each associated with an appropriate label, and then apply the datepicker to both: var dates = $('#from, #to').datepicker({ defaultDate: "+1w", changeMonth: true, numberOfMonths: 3, onSelect: function(selectedDate) { var option = this.id == "from" ? "minDate" : "maxDate";

      260 | Chapter 11: HTML Form Enhancements with Plugins

      });

      }

      dates.not(this).datepicker("option", option, new Date(selectedDate));

      The default date for the datepicker is the current date plus one week, specified using the defaultDate option. A select for changing the months is displayed as well, via changeMonth: true. The option numberOfMonths: 3 indicates that three calendars should be displayed at once. The onSelect option is an event triggered whenever the user selects a date. When the from date is selected, the minDate option for the to date is set to the from date, and when the to date is selected, the maxDate option for the from date is set. With that in place, the user can start selecting any of the two dates, and when he continues to select the other, the input is restricted to a positive range already.

      Discussion By default, the datepicker is shown when the input field receives focus. Using the showOn option, we can configure the calendar to appear only when clicking a calendar icon next to the input: $("#datepicker").datepicker({ showOn: 'button', buttonImage: 'images/calendar.gif', buttonImageOnly: true });

      The buttonImage option specifies the path to an image to use as the button, where buttonImageOnly specifies to use only that image, instead of a button element with an embedded image. The showOn option also supports both as a value, displaying the datepicker on focus of the input and on clicks on the button.

      Localization The jQuery UI datepicker supports 41 locales, provided as ui.datepicker-xx.js files, where xx is the locale. Each file adds a property to $.datepicker.regional. The ui.date picker-ar.js file adds these: $.datepicker.regional['ar'] = { closeText: '‫'إغلاق‬, prevText: '<‫'السابق‬, nextText: '‫&التالي‬#x3e;', currentText: '‫'اليوم‬, dayNames: ['‫ 'الخم‬,'‫ 'الأربعاء‬,'‫ 'الثلاثاء‬,'‫ 'الاثنين‬,'‫ 'الأحد‬,'‫السبت‬ ‫ 'الجمعة‬,'‫]'يس‬, dayNamesShort: ['‫ 'خم‬,'‫ 'أربعاء‬,'‫ 'ثلاثاء‬,'‫ 'اثنين‬,'‫ 'أحد‬,'‫سبت‬ ‫ 'جمعة‬,'‫]'يس‬, dayNamesMin: ['‫ 'خم‬,'‫ 'أربعاء‬,'‫ 'ثلاثاء‬,'‫ 'اثنين‬,'‫ 'أحد‬,'‫سبت‬ ‫ 'جمعة‬,'‫]'يس‬, dateFormat: 'dd/mm/yy',

      11.10 Choosing a Date | 261

      };

      firstDay: 0, isRTL: true

      To initialize a datepicker with the Arabic locale, we refer to that property: $("#datepicker").datepicker($.datepicker.regional.ar);

      To mix in other options as well, we use $.extend: $("#datepicker").datepicker($.extend({}, $.datepicker.regional.ar, { changeMonth: true, changeYear: true });

      We create an empty object literal via {} and then use $.extend to copy the regional options as well as values for changeMonth and changeYear into the empty object, which is then used to initialize the datepicker.

      262 | Chapter 11: HTML Form Enhancements with Plugins

      CHAPTER 12

      jQuery Plugins

      Mike Hostetler

      12.0 Introduction A primary goal of the jQuery JavaScript library is to remain a fast and concise alternative to other JavaScript libraries that are available in the open source world. A key principle toward this goal is ensuring that the jQuery core addresses the needs of most developers, while remaining fast and concise. Developers may have needs that aren’t completely satisfied by the jQuery core. Or, a developer may write an extension to core jQuery functionality that may be useful to a significant segment of jQuery users but shouldn’t be included in the jQuery core. jQuery was designed to be extensible in a variety of ways. The recipes in this chapter are intended to introduce the reader to the world of jQuery plugins.

      12.1 Where Do You Find jQuery Plugins? Problem You’re trying to build something with jQuery that requires functionality that doesn’t exist in the jQuery core. The problem is one that other developers have likely run into before, and you think a plugin may exist. Where should you start looking to find plugins, and how should you evaluate the plugins that you find?

      Solution Search through the following repositories for jQuery plugins: jQuery Plugin Repository http://plugins.jquery.com Google Code http://code.google.com

      263

      GitHub http://github.com Google with special queries http://google.com SourceForge http://sourceforge.net

      Discussion There are a few places around the Web that jQuery plugins may be found. Because of the nature of jQuery plugins, there are certain open source hosting sites that tend to attract jQuery plugins more than others. Additionally, the jQuery project hosts a central repository for jQuery plugins at http://plugins.jquery.com. It’s best to look through all of the available resources and collect several potential plugins, if they are available, for your review. Plugins that are built to solve the same problem often take very different approaches or were built for alternate versions of the jQuery core library. When looking for a jQuery plugin, the following are the best steps to find the most updated and recent versions of plugins.

      Search through the jQuery Plugin Repository The jQuery Project hosts a plugin repository that currently boasts more than 1,200 plugins at the time of this writing. Most authors who host their own plugins will post their plugins here. Plugins hosted in the jQuery Plugin Repository are organized into a number of categories, which can assist with narrowing your search. Plugins may be organized into multiple categories. Plugins are also required to be listed by API compatibility, ensuring that any plugins you find are likely to work with a particular version of the jQuery core library. Lastly, you may also browse plugins by release date, allowing you to keep up with your favorite plugins as new versions are released.

      Search through Google Code Google Code hosting offers a very rich repository of jQuery plugins. More often than not, if you can’t find a plugin hosted on the main Plugin Repository, there’s a good chance it could be on Google Code.

      Search through GitHub GitHub is a rising star in the code hosting world that many jQuery plugin authors are turning toward. More and more plugins end up here, and it is certainly a site that warrants a search when looking for a specific plugin. One of the most compelling features of GitHub is the ability to “fork” a repository in a friendly way by utilizing the 264 | Chapter 12: jQuery Plugins

      features of the Git source code management system. In the event that you need to modify an existing plugin, utilizing the features of GitHub are a compelling way to keep track with upstream updates. The best way to find a plugin on GitHub is to utilize GitHub’s excellent search. GitHub supports a number of advanced operators when searching. All of these options may be viewed in greater detail at http://github.com/search. When looking specifically for a jQuery plugin, searching for repositories using JavaScript will return the best results.

      Perform a Google search While the previous suggestions are known sources of plugins, searching throughout the entire Web via Google is useful as well. Because the body of search material tends to be larger, so does the number of potential results to sift through. Using a few of the suggested searches can result in finding plugins quicker: {searchterm} "jquery*.js" - Best practice plugin naming is jquery-{myplugin}.js or jquery.{myplugin}.js {searchterm} "*jquery.js" - Alternate best practice plugin naming

      Search through SourceForge There tends to be very few actual jQuery plugins hosted on SourceForge. However, a number of projects on this site offer jQuery support tools, such as IDE code completion extensions. If you’re out of options, or are looking for something unique, SourceForge is a good place to do a quick search.

      12.2 When Should You Write a jQuery Plugin? Problem After searching for an existing jQuery plugin to fit your needs, the plugins that were found either don’t meet your needs or are not constructed in a way that you can take advantage of them properly. Is it worth writing a new jQuery plugin that can be shared with others who have the same need?

      Solution There’s no cut and dried solution to this problem. The number of available jQuery plugins is large, but there are valid cases where plugins don’t exist to meet a particular need. In my opinion, the decision to write and publish your own jQuery plugin comes down to three things: • Is it likely there are others who have the same problem?

      12.2 When Should You Write a jQuery Plugin? | 265

      • What level of support are you willing to provide? • What level of community participation do you desire?

      Discussion Build a plugin if there is a potential audience If you’re facing a problem that remains unsolved, there are likely other developers who have encountered the same issue. How others before you have solved the issue is the key question. It’s assumed that you’ve done some homework at this point, searching for a solution. During that search, clues that may surface that point toward a need for a plugin can be found in forum posts or mailing list questions that have gone unanswered. There’s no easy way to decide whether a plugin is worth building, and the decision ultimately comes down to the person who is planning to build the plugin. However, a general feel of whether there is a potential audience is worth exploring. The other potential reason to build and publish your own plugin is if a plugin exists to meet your needs but does not fully do what you want. If this is the case, it is worth considering the potential for writing a patch and submitting that patch back to the original author for inclusion in the plugin. Participating in the open source process by submitting a patch to an existing project tends to be a much more efficient application of a developer’s most precious resource: time.

      Know and communicate the level of support you are willing to provide If writing your own plugin is the best option, a bit of forethought and planning will help make sure the process of hanging out your own open source shingle goes well. Whenever you decide to publish your code, the first and biggest consideration is licensing. The jQuery core project is dual-licensed as MIT and GPL, but many other open source licenses are worthy of consideration. A more thorough discussion on the intricacies of open source licensing can be found at Wikipedia. Second, it is important to consider and communicate the level of support that you, the plugin author, are willing to provide to others who may download and use your code. Choosing to simply publish your code and provide no support is a completely valid option and is much better than keeping your code to yourself for fear of the potential support issues. The key is communication; writing a quick note about your support plan into the comments of your plugin will go a long way. If you are willing to provide deeper support for a plugin that you want to publish, there are several great source code hosting sites that offer several features to assist in supporting your plugin. See Recipe 12.1 for a list of the best places to host and support your plugin.

      266 | Chapter 12: jQuery Plugins

      Plan for participation from others Lastly, think through and gauge your willingness to accept participation from others. Participation is a key component of the open source ecosystem, and it is wise to communicate your intention from the moment you publish your plugin. The attraction of allowing participation is that you can benefit from the work of others. Plugins that accept participation from others tend to attract additional users, partly because of the appearance of activity and partly because active code tends to be more trustworthy code. Communicating the path to participation is key. Whether you intend to or not, any piece of code that is published tends to attract some sort of participation once users find it. Having a plan to engage that participation in an open and public way is essential. One last word of advice: engaging participation simply by publishing your email address and allowing people to email you with comments and questions is generally a bad idea for a couple reasons. First, email isn’t a public forum that displays activity to potential users, and second, it introduces you, the plugin author, as a bottleneck to integrating that activity back into the plugin.

      12.3 Writing Your First jQuery Plugin Problem You’ve decided that you want to write a jQuery plugin. How do you write a plugin in jQuery? What best practices should you follow?

      Solution jQuery is designed to make writing a plugin very simple and straightforward. You can extend the existing jQuery object by writing either methods or functions. Simply declaring the following JavaScript after inclusion of the jQuery core library will allow your code to use your new custom method or function.

      Writing a custom jQuery method jQuery methods are available to be chained and thus can take advantage of jQuery selectors. jQuery methods are defined by extending the jQuery.fn object with your method name. Because the jQuery object must be able to handle multiple results, you must wrap your custom functionality inside a call to the each() function to apply your code to all of the results: jQuery.fn.goShop = function() { return this.each(function() { jQuery('body').append('
      Purchase: ' + this.innerHTML + '
      '); }); };

      12.3 Writing Your First jQuery Plugin | 267

      Accessing this new plugin is as simple as calling jQuery like you normally would and utilizing your new method name: jQuery('p').goShop();

      Writing a custom jQuery function Functions are attached to the main jQuery object. Functions are designed to be called outside of a jQuery selection: jQuery.checkout = function() { jQuery('body').append('

      Checkout Successful

      '); };

      This new function can be manipulated and called normally: jQuery.checkout();

      Discussion Attaching new methods and functions to the main jQuery object are a powerful feature of jQuery. Many of the core methods are built into the library using this same technique. By leveraging this existing foundation in jQuery, users of jQuery and users of your plugin have a fast and concise way to add new functionality, extend existing functionality, and mold the jQuery code into whatever form suits them best. This flexibility is a key feature and enables jQuery and its plugins to be used by a wider audience. The choice to extend jQuery via a new method or a function mainly depends on the needs of the developer. In general, focusing on extending jQuery via adding a new method is best because this allows that new method to be chained along with other methods, and it allows the code in the method to take advantage of jQuery’s selector engine.

      12.4 Passing Options into Your Plugin Problem Your first plugin adds a method to jQuery. However, there are a few options that would be helpful to others if they were exposed properly. What is the best method of passing options into a custom method?

      Solution Options are best passed into your custom plugin method via an options object. Using a single options object to pass in parameters promotes cleaner code, is easier to work with, and provides flexibility down the road. When allowing options to be utilized in your plugin, it’s wise to provide sensible defaults. After providing sensible default options, it’s also important that your plugin

      268 | Chapter 12: jQuery Plugins

      provide a method for the user of the plugin to override the defaults. Both of these goals are easily accomplished by declaring a default options object, overriding the default options with user-supplied options and the jQuery extend() method, and then utilizing the options in your code: jQuery.fn.pulse = function(options) { // Merge passed options with defaults var opts = jQuery.extend({}, jQuery.fn.pulse.defaults, options); return this.each(function() { // Pulse! for(var i = 0;i
      };

      // Pulse plugin default options jQuery.fn.pulse.defaults = { speed: "slow", pulses: 2, fadeLow: 0.2, fadeHigh: 1 };

      By specifying option defaults, developers using your plugin have the ability to provide as many or as few options when they call your function. It is important to place your options’ defaults after you’ve defined your plugin entry method; otherwise, you will encounter an error: // Override only one option jQuery('p').pulse({pulses: 6}); // Override all options jQuery('p').pulse({speed: "fast", pulses: 10, fadeLow: 0.3, fadeHigh: 0.8});

      Lastly, by specifying your options as an object attached as a child to your plugin function, the default options may be overridden only once in a project. A developer then has the ability to specify their own set of default options, minimizing the amount of code required to produce the desired behavior: // Plugin code included above // Reset pulse default options jQuery.fn.pulse.defaults = { speed: "fast", pulses: 4, fadeLow: 0.2, fadeHigh: 1 };

      12.4 Passing Options into Your Plugin | 269

      // This call will use the new defaults jQuery('p').pulse();

      Discussion Supporting options in your plugin is a powerful way to add tremendous flexibility to the plugin. Plugins that support a rich set of options are more likely to fit the needs of a wider audience, perform a wider variety of tasks, and generally gain more popularity than plugins that don’t support options. Including a set of default options with your plugin is another way to give developers who use your plugin flexibility and choice in how the plugin is implemented. A handy side benefit is that the plugin can always rely on certain options being defined, reducing the amount of code required to check whether an option has been passed. This leaves plugin users with the ability to override a single option, multiple options, or even all of the options every time they call your plugin. Lastly, by attaching the default options to the jQuery object, the options can be overridden globally, giving your users another tool to leverage in new and creative ways.

      12.5 Using the $ Shortcut in Your Plugin Problem Other JavaScript libraries make use of the $ shortcut. jQuery itself uses $ only as a shortcut, with the main object being named jQuery. How can you ensure that your plugin maintains compatibility with other plugins and libraries?

      Solution jQuery itself uses the $ function as a custom alias for the jQuery object. When jQuery is set into compatibility mode, it passes back control of the $ alias to the original library that defined it. Plugins can be crafted to use the same technique. By wrapping your plugin in an anonymous function and immediately executing that function, the $ shortcut is kept inside the plugin. Code outside of the plugin can use $ normally. Inside the plugin, $ will reference the jQuery object as normal: ;(function($) { $.fn.pulse = function(options) { // Merge passed options with defaults var opts = $.extend({}, $.fn.pulse.defaults, options); return this.each(function() { // Pulse! for(var i = 0;i
      270 | Chapter 12: jQuery Plugins

      $(this).fadeTo(opts.speed,1); });

      };

      // Pulse plugin default options $.fn.pulse.defaults = { speed: "slow", pulses: 2, fadeLow: 0.2, fadeHigh: 1 }; })(jQuery);

      Discussion Wrapping your distributed code in an anonymous function is a very straightforward and simple step that adds several features and ensures that your plugin code can play nicer in the wider world that your users may live within. Adding a semicolon at the beginning of your function definition helps protect against another developer who may have forgotten to include an ending semicolon in their library. The JavaScript language breaks statements on newline by default, but many users take advantage of minimization tools that compress the entire set of JavaScript in their projects into a single file. This process removes the line endings and can cause errors if your code follows immediately after. Adding the initial semicolon is a quick and easy trick to protect against that possibility. The open parenthesis immediately begins the anonymous function definition. Within our anonymous function, we define a function that passes the variable that we want to use in place of the fully named jQuery object. In this case, we want to take advantage of using $ as the variable. Defining an additional function is required because of the way the JavaScript language handles scoping. In more traditional languages such as Java and C++, scope is limited to the block statement. In JavaScript, scope is wrapped in functions. Therefore, the reason for using a function here is really to set up a scope boundary that we can define our plugin within. What follows is a new version of our plugin, with the sole change of swapping out the way we utilize the jQuery object. Because we’ve wrapped this plugin anonymously and limited the scope of the $ variable, we can now use $ freely without conflict from any other code. The last line wraps up the scoping function and anonymous function with a close bracket and close parenthesis, respectively. The last bit is what actually calls our anonymous function immediately after it has been defined. This is where we tell our function to pass in the jQuery object, which is what gets renamed to $ within our function. Lastly, we close off our new statement with a semicolon to protect against JavaScript minimization and compression errors.

      12.5 Using the $ Shortcut in Your Plugin | 271

      The $ shortcut can be incredibly useful in writing JavaScript code. It cuts down on code size, promotes good code design, and has become extremely popular and well known. Thus, many libraries take advantage of the $ shortcut, tying it into their own context. With each library supporting their own version of the $ shortcut, conflicts can easily arise. By wrapping your plugin code within an anonymous function, you can ensure that your plugin maintains a level of scope around usage of the $ shortcut that will reduce the potential for conflicts with other JavaScript libraries. One additional side effect of wrapping your plugin in an anonymous function, as described earlier, is that a closure is created. Utilizing a closure in JavaScript aids in properly namespacing any methods or variables that you may need to define, further reducing the chance for variable names or function names to conflict with other code.

      12.6 Including Private Functions in Your Plugin Problem Your plugin code is growing and needs to be organized. How can you implement a private method that’s unavailable to code outside your plugin?

      Solution By utilizing the plugin design pattern started in Recipe 12.4, private functions may be defined normally within the anonymous function that we’ve wrapped our plugin in. Because the function is enclosed in an anonymous function, outside code won’t see our private method. Code outside will only be able to see functions or methods that are attached to the jQuery object. ;(function($) { $.fn.pulse = function(options) { // Merge passed options with defaults var opts = $.extend({}, $.fn.pulse.defaults, options); return this.each(function() { doPulse($(this),opts); });

      };

      function doPulse($obj,opts) { for(var i = 0;i
      }

      // Reset to normal $obj.fadeTo(opts.speed,1);

      272 | Chapter 12: jQuery Plugins

      // Pulse plugin default options $.fn.pulse.defaults = { speed: "slow", pulses: 2, fadeLow: 0.2, fadeHigh: 1 }; })(jQuery);

      Discussion Because we now have our plugin wrapped in an anonymous function, defining private functions within our plugin is as simple as adding a new function as you normally would. Grouping and organizing your plugin with public and private methods offers many advantages to your users and to the plugin author. As your plugin matures and you receive feedback from the community, you can leverage the use of public and private methods to provide a consistent API between plugin versions. The consistency of your API can be a major factor in your plugin’s success. The ability to break code down into private and public messages also has significant advantages in code organization as your plugin grows. Well-organized code is easier to read, to maintain, and to test. Well-tested, clean code can lead to less error-prone code.

      12.7 Supporting the Metadata Plugin Problem Several plugins utilize the metadata plugin to pass custom options into their methods. How can integration with the metadata plugin be constructed?

      Solution Leveraging the metadata plugin is as simple as checking whether the plugin is available and then extending your plugin options with the metadata parameters. Using this technique, you can supply default options when making the call to your plugin and override those default options for each object to be operated on through the metadata written into the markup:

      Starship Enterprise

      Battlestar Galactica

      Serenity

      ;(function($) {

      12.7 Supporting the Metadata Plugin | 273

      $.fn.pulse = function(options) { // Merge passed options with defaults var opts = $.extend({}, $.fn.pulse.defaults, options); return this.each(function() { // Merge in the metadata elements for this specific node var o = $.metadata ? $.extend({}, opts, $.metadata.get(this)) : opts; doPulse($(this),o); });

      };

      function doPulse($obj,opts) { for(var i = 0;i
      }

      // Reset to normal $obj.fadeTo(opts.speed,1);

      // Pulse plugin default options $.fn.pulse.defaults = { speed: "slow", pulses: 2, fadeLow: 0.2, fadeHigh: 1 }; })(jQuery);

      Discussion Including the metadata plugin is a great example of how jQuery plugins can build off of one another. The jQuery plugin ecosystem is vast, and chances are there are other plugins that you can utilize. To include and use the metadata plugin, you first must actually include it into your script. The metadata plugin is hosted along with jQuery at Google Code. The metadata plugin works by allowing you to embed additional data into your HTML, while still producing valid HTML. We take advantage of this by allowing users to embed elementspecific options into the class element of the items we can operate on. The options are embedded into the HTML using standard JSON. All of the options may be embedded, or none may be embedded; it’s up to your users. There are several other methods and options for using the metadata plugin that are described on its documentation page. Within our plugin, we first check to see whether a user has included the metadata plugin. This is done to ensure that we keep this additional feature optional and to provide backward compatibility, if necessary. Because the metadata plugin operates on a single element, we split up how we handle options. The first step is to use the options

      274 | Chapter 12: jQuery Plugins

      provided when the plugin was called. These options are extended with our default options, creating our starting point for this first instantiation of our plugin. The second step is to extend those locally default options with the metadata that may be defined for each element. All that is required is for us to extend our locally default options with the metadata options, if the metadata plugin exists. The metadata plugin provides another option for users of your plugin to pass in options. Providing options to potential users is a great way to show that you are committed to your plugin, being a good citizen of the jQuery ecosystem. The metadata plugin is also a great way to offer your users the ability to write less code by embedding custom options into the HTML elements.

      12.8 Adding a Static Function to Your Plugin Problem In addition to making your plugin available through the jQuery function, you want to expose a static function. How can you add a static function to your jQuery plugin?

      Solution Adding a static method to your plugin requires extending the jQuery object in much the same way you would add a method. The difference is simply that functions are called without using jQuery selectors: ;(function($) { $.fn.pulse = function(options) { // Merge passed options with defaults var opts = $.extend({}, $.fn.pulse.defaults, options); return this.each(function() { // Merge in the metadata elements for this specific node var o = $.metadata ? $.extend({}, opts, $.metadata.get(this)) : opts; doPulse($(this),o); });

      };

      function doPulse($obj,opts) { for(var i = 0;i
      }

      // Reset to normal $obj.fadeTo(opts.speed,1);

      // Define our base to add to $.pulse = {};

      12.8 Adding a Static Function to Your Plugin | 275

      // Static Function $.pulse.impulse = function($obj) { var opts = { speed: 2500, pulses: 10, fadeLow: 0.2, fadeHigh: 0.8 }; doPulse($obj,opts); } // Static Function $.pulse.warpspeed = function($obj) { var opts = { speed: 25, pulses: 100, fadeLow: 0.2, fadeHigh: 0.8 }; doPulse($obj,opts); } // Pulse plugin default options $.fn.pulse.defaults = { speed: "slow", pulses: 2, fadeLow: 0.2, fadeHigh: 1 }; })(jQuery);

      Calling the static methods available in your plugin is very straightforward, requiring only that you explicitly pass a valid object to operate on: // Call the impulse method on the first element returned jQuery.pulse.impulse(jQuery('p:first')); // Call the warpspeed method on the first element returned jQuery.pulse.impulse(jQuery('p:first'));

      Discussion Adding a static function within the scope of your plugin only requires adding a way for code outside of your plugin to call it. This is accomplished by attaching the functions to the jQuery object. In the previous example, we’ve added a namespace object to aid in organizing our code better. If all that your plugin required was a single static function, it would be completely appropriate to expose your static function without adding a namespacing object. After adding our namespace object, we simply define our functions like normal and attach them to the namespace object we created. Doing this exposes our function

      276 | Chapter 12: jQuery Plugins

      to the global namespace, while allowing the contents of the functions to access private functions and variables. Taking advantage of the static function is as simple as calling it using the jQuery object we attached it to. This function is called without utilizing jQuery selectors, so in order to operate on a DOM element, that element must be explicitly passed to the function. A static function attached to the jQuery object is another example of the flexibility of the jQuery library. Your entire plugin could be made up of adding static functions that simply extend the jQuery core in interesting new ways. A static function could be the entry point you provide to your plugin, or it could be a simple shortcut method you’ve found useful that’s packaged in your plugin in a way that makes it easier to share with other developers. Whatever the need, static functions can be a useful and powerful tool when building your own jQuery plugin.

      12.9 Unit Testing Your Plugin with QUnit Problem You want to raise the quality and reliability of your jQuery plugin by creating unit tests for it. How do you write and ship tests with your jQuery plugin?

      Solution The easiest method to write unit tests for a jQuery plugin is to utilize QUnit, the same unit testing framework that the jQuery project uses. With QUnit, you can write your tests right in JavaScript and ship them with your plugin for your users to run in their own browsers:
      USS Enterprise - NC-1701-A
      Battlestar Galactica


      Discussion Learning how to effectively test code is beyond the scope of this chapter. The tests written in the previous example are intended to simply show an example of what can be done with unit testing. Chapter 18 goes into great detail about unit testing and specifically the QUnit framework. For a discussion on how to use QUnit, what types of things you can test, and how to effectively test your code, please refer to that chapter. Shipping unit tests with your plugin is another great way to show developers you are committed to the success and stability of the code that you publish. This builds trust with your user base and shows that your plugin is a good member of the jQuery plugin ecosystem. Tests also make it easy for users of your plugins to find bugs that can creep up in another runtime environment, such as a different browser. This allows you, the plugin author, to better address the bug that was found by having a working test bed, allowing you, the plugin developer, to directly address the bug that was found.

      278 | Chapter 12: jQuery Plugins

      CHAPTER 13

      Interface Components from Scratch

      Nathan Smith

      13.0 Introduction While the official jQuery UI widget collection offers a wealth of ready-made functionality, from time to time you might choose to create a customized element that meets a specific need. Perhaps you desire greater control over the HTML markup or simply want to keep your JavaScript code base lighter. Maybe you are looking to build something not already covered by existing solutions. Whatever the reason, this chapter will show you how to tersely write customized components for your projects. These recipes have been written with ease of use in mind, favoring simplicity over configuration. Recipe 13.1 will show you how to create custom tool tips, for occasions where you need to direct the user’s attention via providing additional content or instruction. Recipe 13.2 will explain how to build a file tree–style menu, allowing the user to drill down and explore the depth of a site hierarchy. In Recipe 13.3 you will learn how to create a vertically folding accordion. Recipe 13.4 will explain how to use interpage links and their respective targets to create document tabs. Recipe 13.5 shows how to create a basic modal window via appropriate action. Recipe 13.6 explains how to build a simple drop-down menu. Recipe 13.7 delves into the creation of an image rotator that can be controlled via buttons, reusing the interpage link technique from Recipe 13.4. Recipe 13.8 takes lessons learned from Recipe 13.3, creating horizontal panels instead of a vertical accordion. The following paradigms are used throughout this chapter and will not be called out specifically for each example.

      Each recipe begins by checking whether the necessary element actually exists in the document. If not, then we exit the function. There is no need to go any further if that criterion is not met, and this keeps code from executing unnecessarily: 279

      // Does element exist? if (!$('#foobar').length) { // If not, exit. return;

      }

      A snippet of generic code is used throughout to cancel the following links that only serve to trigger JavaScript events. The blur() method is applied to get rid of dotted borders that would otherwise be permanent (until the user clicked something new), and return false tells the browser not to follow the link’s href: // Nofollow. this.blur(); return false;

      To actually kick off the dynamic functionality, each recipe ends with a call to jQuery’s document.ready() function, ensuring that the DOM has finished loading (but not necessarily all image assets) before attempting to apply event listeners and so forth: // Kick things off. $(document).ready(function() { init_foobar(); });

      Some of the recipes have the following bit of code in the of the HTML document. For the most part, document.write() is considered an antiquated practice in JavaScript, because it forces the browser to pause, rendering the page when it encounters such a command. However, when prehiding content with CSS that will later be shown via JavaScript, this is exactly the outcome we want:

      Essentially, before the page even begins to render, a CSS file is written to the that prehides all the content that will later be shown as the user interacts with the page. The reason we write the CSS reference with JavaScript is that with JavaScript off, all the content is visible and fully accessible. For more on that technique, read Peter-Paul Koch’s “Three JavaScript articles and one best practice”.

      13.1 Creating Custom Tool Tips Problem From time to time, a graphical element or interface aspect may need further clarification, but because of restraints on space (or for the sake of aesthetics), a designer might not want to take up precious screen real estate by adding explanatory text. In such a case, there is a need to provide a bit of guidance to the user, who would need it initially 280 | Chapter 13: Interface Components from Scratch

      but whose need would diminish as familiarity with the interface grew. In such cases, a tool tip makes for an ideal solution. However, HTML leaves us with scarce resources to create a tool tip, and often the title="..." attribute does not cut it. Tool tips can be a good solution for user interface clarification, especially if tied to some sort of dismissible user preference (i.e., “Don’t show me this again”). However, dynamic tool tips have often been abused, most notably on blogs, where every single element on a page with a title="..." attribute causes a tool tip to appear when the mouse passes over it. Such cases should be avoided, because if everything is treated as a special case via a tool tip, then the importance is diminished, and in reality nothing on the page is emphasized. It is the equivalent of shouting every single word in a sentence. Just as with any web project, the context of the content should dictate the approach, not vice versa.

      Solution To solve this problem, we can use jQuery to get the mouse position within our area of interest on the page and then dynamically place a
      element offset from the point of origin, which could contain instructions, additional information (in the case of e-commerce), or just about anything the developer needs to appear. This would be done by creating a dynamically generated
      before the close of the tag, allowing it to have a higher z-index than the rest of the page, which would look like Figure 13-1. Additionally, just to be sure the tool tip takes precedence, it is explicitly given an extremely high z-index of 9999.

      Figure 13-1. A tool tip generated with jQuery

      Tool tip—HTML code jQuery Cookbook - Ch.13 - Creating Custom Tooltips

      13.1 Creating Custom Tool Tips | 281

      Mouse over me to read my title in a tooltip!

      Guess what? I have a tooltip too. Please read it.



      Tool tip—jQuery code // Initialize. function init_tooltip() { // Does element exist? if (!$('.tooltip').length) {

      }

      // If not, exit. return;

      // Insert tool tip (hidden). $('body').append('
      '); // Empty variables. var $tt_title, $tt_alt; var $tt = $('#tooltip_outer'); var $tt_i = $('#tooltip_inner'); // Watch for hover. $('.tooltip').hover(function() { // Store title, empty it. if ($(this).attr('title')) { $tt_title = $(this).attr('title'); $(this).attr('title', ''); } // Store alt, empty it. if ($(this).attr('alt')) { $tt_alt = $(this).attr('alt'); $(this).attr('alt', ''); } // Insert text.

      282 | Chapter 13: Interface Components from Scratch

      $tt_i.html($tt_title); // Show tool tip. $tt.show();

      }, function() {

      // Hide tool tip. $tt.hide(); // Empty text. $tt_i.html(''); // Fix title. if ($tt_title) { $(this).attr('title', $tt_title); } // Fix alt. if ($tt_alt) { $(this).attr('alt', $tt_alt); } // Watch for movement. }).mousemove(function(ev) { // Event coordinates. var $ev_x = ev.pageX; var $ev_y = ev.pageY; // Tool tip coordinates. var $tt_x = $tt.outerWidth(); var $tt_y = $tt.outerHeight(); // Body coordinates. var $bd_x = $('body').outerWidth(); var $bd_y = $('body').outerHeight();

      }

      // Move tool tip. $tt.css({ 'top': $ev_y + $tt_y > $bd_y ? $ev_y − $tt_y : $ev_y, 'left': $ev_x + $tt_x + 20 > $bd_x ? $ev_x − $tt_x − 10 : $ev_x + 15 }); });

      // Kick things off. $(document).ready(function() { init_tooltip(); });

      13.1 Creating Custom Tool Tips | 283

      Discussion It is worth mentioning that $('.tooltip') is not the most performant way to retrieve elements. For the sake of this demo, all tags on the page are being parsed, which is the equivalent of document.getElementsByTagName('*'). Depending on the size of the document, and depending on the browser, this can be quite slow. So, when actually employing this code, be sure to specify which tags you are looking for. For example, you would use $('a.tooltip, span.tooltip') instead of just $('.tooltip'). While more modern browsers will map such class selectors to getElementsByClassName or querySelectorAll (if available), older browsers have to first iterate through tag names and then determine whether the relevant class is present. Assuming that one or more elements exist that match class="tooltip", we append the dynamic markup at the end of the page, right before the close of the body. It does not yet appear anywhere visibly, because in the CSS file we have applied display: none to the #tooltip_outer ID. Next, we create empty variables called $tt_title and $tt_alt. These will be used to temporarily store the title and alt (if it exists) attributes of our matched class="tool tip" elements. The astute reader might wonder, “Aren’t we just interested in the title attribute? Why worry about alt?” Good question. We store the alt attribute in addition to the title, just in case class="tooltip" is used on an image. Internet Explorer causes its own tool tip to appear showing the alt contents, and we don’t want that. The rest of the code deals with class="tooltip" elements. When one such element is hovered over with the mouse, we store the contents of the title and (possibly) alt attributes and then zero them out by setting each one equal to an empty text string. This way, there is no browser default tool tip interfering with our custom one. The contents of the title attribute are copied to #tooltip_inner, and then the #tooltip_outer is shown. Likewise, when the mouse leaves the target element, we want to undo what happened when it was it first entered. The #tooltip is hidden, the #tooltip_inner content is set to an empty string, and the title and alt attributes are restored to their original values. Lastly, the .mousemove() method monitors mouse movement once it has entered the boundaries of a class="tooltip" element. The tool tip is offset relative to the mouse position, appearing to the right side of the cursor; that is, unless the tool tip is dangerously close to extending beyond the width of the browser. In such a case, a horizontal scroll bar would appear, and we do not want that. To solve this snafu, we have a bit of logic that flips the tool tip to the left side of the cursor. The same is true vertically. If the tool tip is too far at the bottom of a page, it will flip itself to be above the mouse cursor.

      284 | Chapter 13: Interface Components from Scratch

      13.2 Navigating with a File-Tree Expander Problem On content-heavy sites with multiple tiers of information architecture, occasionally a need arises to present several levels of nested data. If all of the info was presented in its entirety, it would be too unwieldy to be useful and would take up too much vertical space on a page. Enter the file-tree paradigm. This functionality, seen most notably in the desktop UI of Windows Explorer (not to be confused with Internet Explorer), allows a user to expand and compact layers of directories.

      Solution By using jQuery’s descendant selectors on nested unordered lists, we can hide/show additional portions of a tree structure, as needed. This is done by adding class="tree" to the top-level unordered list and using a combination of CSS and JavaScript to unveil its sublevels, producing a tree like that in Figure 13-2. Additionally, we make use of event delegation to support numerous tiers without the overhead of attaching event listeners to multiple elements. Instead, the event is captured at the top level of the
        via jQuery’s .live() method.

        Figure 13-2. Presenting multiple levels of data through a file tree

        File tree—HTML code

        13.2 Navigating with a File-Tree Expander | 285

        jQuery Cookbook - Ch.13 - Navigating a File Tree Expander

        Expand all ~ Collapse all

        •   Primary
          •   Secondary
            •   Tertiary
              •   Quaternary
              •   Quaternary
              •   Quaternary
              •   Quaternary
              •   Quaternary
              ...
            • ...
            ...
          • ...
          ...

          286 | Chapter 13: Interface Components from Scratch

        • ...
        ...

        File tree—jQuery code // Initialize. function init_tree() { // Does element exist? if (!$('ul.tree').length) {

        }

        // If not, exit. return;

        // Expand and collapse. $('p.tree_controls a.expand_all, p.tree_controls a.collapse_all').click(function() { // Look at the class. if ($(this).hasClass('expand_all')) { $(this).parent('p').next('ul').find('a.tree_trigger') .addClass('trigger_expanded') .end().find('ul').addClass('tree_expanded'); return false; } else { $(this).parent('p').next('ul').find('a.tree_trigger') .removeClass('trigger_expanded') .end().find('ul').removeClass('tree_expanded'); } // Nofollow. this.blur(); return false; }); // Listen for tree clicks. $('ul.tree a.tree_trigger').live('click', function() { // Is the next
          hidden? if ($(this).next('ul').is(':hidden')) { $(this).addClass('trigger_expanded').next('ul') .addClass('tree_expanded'); } else { $(this).removeClass('trigger_expanded').next('ul') .removeClass('tree_expanded'); } // Nofollow. this.blur(); return false;

          13.2 Navigating with a File-Tree Expander | 287

          }); // Add class for last
        • . $('ul.tree li:last-child').addClass('last');

          }

          // Change state of trigger. $('ul.tree_expanded').prev('a').addClass('trigger_expanded');

          // Kick things off. $(document).ready(function() { init_tree(); });

          Discussion The tree code begins by attaching event handlers to links with class names of expand_all and collapse_all. If either link is clicked, then we traverse the DOM upward to the parent

          , over to the next

            , and then down into its children. Each child link with class="tree_trigger" receives the class of trigger_expanded, and each subsequent
              receives the class tree_expanded. These class names correspond to the CSS rules that change their visual appearance. In the case of the trigger links, they have an expanded icon. As for the lists, they are now display: block instead of display: none. The “live” event listener listens for clicks anywhere within the tree. Basically, this listens for clicks anywhere within the
                and then determines whether the click happened on a link with class="trigger". If so, it executes the associated code. The benefit of using .live(), as opposed to adding a click handler directly to each link, is that the code is associated with all existing and future elements that match the criteria. The benefit of this is twofold: you don’t waste time attaching event listeners to numerous elements, and if dynamic content is inserted via Ajax, it is affected by the “live” event listener as well. Next, we add a style hook of class="last" via JavaScript to each
              • that is the :lastchild of its parent. This allows us to position a background image that simulates connectivity throughout the tree, via a light gray line. Finally, if any child
                  has been hard-coded to be visible when the page loads via class="tree_expanded", we traverse the DOM and add class="tree_trigger_expanded" to the nearest trigger link.

                  13.3 Expanding an Accordion Problem The situation in which one might use an accordion could be somewhat akin to when a file tree might be useful. The paradigms are similar in that each one allows for additional information to be initially obscured from view and then revealed as the user

                  288 | Chapter 13: Interface Components from Scratch

                  interacts further. They differ, however, in that an accordion is not meant to contain an entire taxonomy of data but rather is used more as a novelty to draw attention to several facets of a site or product. One such accordion example can be seen at http://www.apple .com/iphone. This allows for panels of info to be expanded at the user’s leisure, without completely dominating the vertical space allotted to the sidebar. It conserves space not unlike high-density shelving or movable bookcases in a library, allowing one aisle to serve several racks of storage versus having an ever-present aisle between each one. It is worth noting that there is a jQuery UI accordion widget that is highly customizable and can be given a theme/skin to match the rest of the UI widgets. You can see it in action at http://jqueryui.com/demos/accordion. The benefit of using the official widget is that it is officially supported by the jQuery UI community and will continue to evolve and become more robust. The potential drawback is the amount of extra code required, if all you need is a simple accordion. On the flip side, the reason one might choose to build a custom accordion component is for a smaller code footprint. This comes at the disadvantages of having the animation not be pixel-perfect and having to set the height in pixels of each accordion panel. It is advised that you consider both options and do what best fits the project at hand.

                  Solution Using jQuery’s excellent DOM traversal capabilities, namely, adjacent sibling selectors, it is possible to write a script generically enough to handle multiple accordion elements. Additionally, this script is able to handle more elements being added to the accordion, if need be. Figure 13-3 shows an accordion that hasn’t yet expanded, while Figure 13-4 shows its contents, revealed by expanding it.

                  Figure 13-3. Accordion, waiting for the user to expand

                  13.3 Expanding an Accordion | 289

                  Figure 13-4. The expanded accordion

                  Accordion—HTML code jQuery Cookbook - Ch.13 - Expanding an Accordion
                  Title goes here

                  Lorem ipsum...



                  290 | Chapter 13: Interface Components from Scratch

                  Title goes here

                  Lorem ipsum...

                  Title goes here

                  Lorem ipsum...

                  ...


                  Accordion—jQuery code // Initialize. function init_accordion() { // Does element exist? if (!$('dl.accordion').length) {

                  }

                  // If not, exit. return;

                  // Gather all accordions. $('dl.accordion').each(function() { // Reveal first accordion item. $(this).find('dt:first a').addClass('accordion_expanded') .end().find('dd:first').show(); // Added to round corners via CSS. $(this).find('dt:last').addClass('last'); }); // Event listener for click. $('dl.accordion dt a').click(function() { // Get parent
                  . var $dl = $(this).parents('dl:first'); // Get the next
                  . var $dd = $(this).parent('dt').next('dd'); // Class final
                  . function findLast() {

                  13.3 Expanding an Accordion | 291

                  }

                  if ($dl.find('dd:last').is(':hidden')) { $dl.find('dt:last').addClass('last'); }

                  // Is it visible? if ($dd.is(':hidden')) { // Expand the
                  , hide others. $dd.slideDown('fast').siblings('dd:visible').slideUp('fast', findLast);

                  }

                  }

                  // Change arrow state, remove class="last" from
                  . $(this).addClass('accordion_expanded').parent('dt') .removeClass('last').siblings('dt').find('a') .removeClass('accordion_expanded');

                  // Nofollow. this.blur(); return false; });

                  // Kick things off. $(document).ready(function() { init_accordion(); });

                  Discussion This function begins by finding all definition lists with the class of accordion and applying jQuery’s .each() method to them. Inside each one, the first
                  link is given the class accordion_expanded, and the first
                  is shown (the rest remain hidden because of CSS display: none). Additionally, the last
                  is given class="last", allowing us to style it uniquely with rounded corners for those browsers that support it. This differs from the file-tree example, in which we patched browsers that lacked :lastchild. In the case of the accordion, class="last" will be removed and reapplied based on user interaction. The second part of the code handles the crux of the accordion. All links that reside inside the accordion’s
                  are given a click event listener. When any of these links is clicked, we traverse the DOM upward to the parent
                  and then over to the next
                  . If that
                  is hidden, then we animate it into place via jQuery’s .slideDown() method, while simultaneously calling .slideUp() on all the other sibling
                  . When this is completed, the callback function findLast is executed, which determines whether to assign class="last" to the last visible
                  , depending on whether its accompanying
                  is hidden. If that last
                  is visible, then no action is taken, because the
                  itself is being rounded via CSS, targeted via :last-child. Again, the astute reader may wonder, “Why are we not patching Internet Explorer 6 and 7, since they don’t understand :last-child?” The 292 | Chapter 13: Interface Components from Scratch

                  reason is, while IE 6 and 7 don’t support :last-child, neither do they support rounded corners via CSS, so in this case there is nothing to be gained. Finally, the class of accordion_expanded is added to the
                  link that was clicked, and that class is removed from all other
                  links. This causes the arrows in each
                  to all point to the right, indicating that they are collapsed, with the exception of the most recently clicked
                  link.

                  13.4 Tabbing Through a Document Problem You might have a page that has quite a bit of data that all belongs together because of site architecture, as opposed to separating it into distinct pages. In such a case, instead of simply having a lengthy document with headings and paragraphs, a tabbed interface often makes better sense. In this case, the tabs work as one might expect a desktop application to function. Instead of leaving the page that you are on, the relevant information associated with each tab is brought to the forefront, as shown in Figure 13-5. One such example of this type of functionality is the Yahoo! home page.

                  Solution By grabbing the href="..." of an interpage anchor link, we can use jQuery to then find the ID of the target, hide its siblings, and bring the target element into the foreground. This is by far one of the simpler applications of jQuery yet can be used to great effect.

                  Figure 13-5. Using tabs to help users navigate information

                  Tabs—HTML code

                  13.4 Tabbing Through a Document | 293

                  jQuery Cookbook - Ch.13 - Tabbing Through a Document

                  Content Area 01

                  Lorem ipsum...

                  Content Area 02

                  Duis ultricies ante...

                  Content Area 03

                  Morbi fringilla...

                  294 | Chapter 13: Interface Components from Scratch

                  Content Area 04

                  Sed tempor...

                  Content Area 05

                  Nulla facilisi...

                  ...


                  Tabs—jQuery code // Initialize. function init_tabs() { // Does element exist? if (!$('ul.tabs').length) {

                  }

                  // If not, exit. return;

                  // Reveal initial content area(s). $('div.tab_content_wrap').each(function() { $(this).find('div.tab_content:first').show(); }); // Listen for click on tabs. $('ul.tabs a').click(function() { // If not current tab. if (!$(this).hasClass('current')) { // Change the current indicator. $(this).addClass('current').parent('li').siblings('li') .find('a.current').removeClass('current');

                  }

                  // Show target, hide others. $($(this).attr('href')).show().siblings('div.tab_content').hide();

                  13.4 Tabbing Through a Document | 295

                  }

                  // Nofollow. this.blur(); return false; });

                  // Kick things off. $(document).ready(function() { init_tabs(); });

                  Discussion When the function runs initially, the first tabbed content area is revealed, while the rest remain hidden because of the display: none style rule in our preload.css file. Beyond that, all we have to do is listen for any link within
                    to be clicked. If it doesn’t already have class="current", then we know its content is obscured, so we add class="current" to the clicked link and remove it from any sibling tabs that might have it. Next, we grab the href="..." or the clicked link, which points to an ID in the same page, and we reveal that element via jQuery’s .show() method, while simultaneously hiding any sibling tabbed content areas that might be visible. Note that if you want enhanced functionality, such as firing custom events when tab states change or loading remote content via Ajax, be sure to check out the official jQuery UI Tab widget.

                    13.5 Displaying a Simple Modal Window Problem With the prevalence of pop-up blocking features being included in most browsers, gone are the days of being able to reliably use window.open() to create a dialog box. Instead, a much more popular and usable solution is to create a modal overlay within the current page, which will take visual precedence until the user interacts with or dismisses it. It is worth noting that there is a jQuery UI dialog widget that is highly customizable and can be given a theme/skin to match the rest of the UI widgets. You can see it in action at http://jqueryui.com/demos/dialog.The benefit of using the official widget is that it is officially supported by the jQuery UI community and will continue to evolve and become more robust. The potential drawback is the amount of extra code required, if all you need is a simple modal. On the flip side, the reason one might choose to build a custom modal component is for a smaller code footprint. It is advised that you consider both options and do what best fits the project at hand.

                    296 | Chapter 13: Interface Components from Scratch

                    If you want an even more robust solution, one particularly geared toward showing a wide variety of content and particularly well suited to image galleries, look no further than ThickBox. It is a popular jQuery add-on written by Cody Lindley (one of the coauthors of this book). You can see it in action at http://jquery.com/demo/thickbox/.

                    Solution Using jQuery, we can easily find the width and height of the browser viewport and create a dimmed layer to sit atop the entire site design. Using CSS positioning, we can then place our modal “window” (which in fact is simply a
                    layer) front and center to draw the user’s attention to it, as shown in Figure 13-6. Various types of content can be displayed, including images, Ajax-loaded HTML fragments, and in-page markup.

                    Figure 13-6. A modal window, created with jQuery

                    Modal—HTML code

                    13.5 Displaying a Simple Modal Window | 297

                    jQuery Cookbook - Ch.13 - Displaying a Simple Modal Window
                    In-page | Remote markup | Remote text | Image file.



                    Lots of line breaks, to simulate scrolling content...
                    It's the end of the world, as we know it, and I feel fine.


                    Modal—jQuery code // Initialize. function init_modal() { // Does element exist? if (!$('a.modal').length) {

                    }

                    // If not, exit. return;

                    // Detect IE6 (boolean). var $IE6 = typeof document.addEventListener !== 'function' && !window.XMLHttpRequest; // Do some math. function sizeModal() { // Modal dimensions. var $modal = $('#modal_window'); var $modal_width = $modal.outerWidth(); var $modal_height = $modal.outerHeight(); var $modal_top = '-' + Math.floor($modal_height / 2) + 'px';

                    298 | Chapter 13: Interface Components from Scratch

                    var $modal_left = '-' + Math.floor($modal_width / 2) + 'px';

                    }

                    // Set modal. $('#modal_window').css('margin-top', $modal_top) .css('margin-left', $modal_left);

                    /* For IE6. */ function positionModal() { // Force modal into place. $('#modal_wrapper').css('top', $(document).scrollTop() + 'px'); } // Reveal the modal. function showModal() { if ($IE6) { positionModal(); } // Unveil the wrapper. $('#modal_wrapper').show(); // Size it. sizeModal(); // Reveal modal window. $('#modal_window').css('visibility', 'visible').show();

                    }

                    // Resize as images load. $('#modal_content img').each(function() { $(this).load(function() { $(this).removeClass('modal_placeholder').show(); sizeModal(); }); });

                    // Insert modal at end of . $('body').append('