Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 15 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
15
Dung lượng
532,31 KB
Nội dung
Licensed to JamesCarlson@aol.com 292 jQuery: Novice to Ninja But the rise of the Rich Internet Application is changing that. Clients expect our web-based systems to rival their desktop applications for usability and design. Thankfully with jQuery by our side, that’s no problem! Lists Lists are the real unsung heroes of the post table-based layout period of the Web. As designers were freed from the constraints of the tyrannical table cell, they started to look for other (semantically correct) ways to recreate common user interface elements such as menus, navigation panels, tag clouds, and so on. And time after time, as the redundant layout cruft was stripped from the underlying data, all that was left behind was—a list! The StarTrackr! site is already home to an extensive array of lists: they form the basis of our tabs, accordions, menus, image galleries, and more—but there’s far more we can do to augment and enhance the humble list. jQuery UI Selectables The ocean of user-generated content is proving a handful for our client. Thousands of tags are pouring in from the site’s users—but now the legal department is saying that as the manager, he has to approve every single one manually, to avoid a repeat of a recent nasty litigation. Because the site employs an unconstrained tag system, there are stacks of duplicate tags in the lists—and with the current system that means stacks of extra administra- tion. What the client really wants is a way to easily see tags, select them (and any duplicates), and click a button to approve or reject them. Our plan of attack is to add jQuery UI’s selectable behavior to our list. Making an element selectable gives the user the ability to lasso any of the element’s children to select them: if you click on one element, then drag over subsequent elements, they become highlighted. You can than process the selection however you see fit. Perfect for administrating boring lists! The behavior we’re aiming to create is illus- trated in Figure 8.1. Licensed to JamesCarlson@aol.com Licensed to JamesCarlson@aol.com Lists, Trees, and Tables 293 Figure 8.1. Selectable list items In addition to lassoing, the selectable behavior also lets you add nonsequential items to the list using the Ctrl key (as you can do in most desktop applications)—and even navigate the selections with the keyboard. Keep Your Users in the Loop Although being able to click and drag selections on a list is very cool and very useful, it’s only cool and useful if your users know about it! Making selections in this manner is a nonstandard form of interaction on the Web, so you’ll need to provide instructions to your users to teach them how to use your new functionality. Let’s have a look at the markup. The server spits out a long list of tags, which is a fine base for our selector grid. We’ll also throw in a few buttons to allow users to approve or reject the tags in their selection, as well as a button to reset the selection: Licensed to JamesCarlson@aol.com Licensed to JamesCarlson@aol.com 294 jQuery: Novice to Ninja chapter_08/01_jquery_ui_selectable/index.html (excerpt) <ul id="tags"> <li>bad singer</li> <li>old</li> <li>plastic surgery</li> <li>broke</li> ⋮ </ul> <button id="approve">Approve</button> <button id="reject">Reject</button> <button id="clear">Clear</button> A big long list is a bit intimidating, so we’ll use some basic CSS to make the list into a grid, and convert each tag into a small box. With our grid ready to go, we have to add the jQuery UI library to the page. Now it’s time to tell the tag list to become selectable: chapter_08/01_jquery_ui_selectable/script.js (excerpt) $("#tags").selectable(); Fire up your browser and check it out. Hrrm … has anything actually happened? Well yes, it has, but it’s invisible! The selectable method works by adding class attributes to selected items, unless we assign styles to those classes, we’ll be unable to see anything happening. If you inspect the list items with Firebug as you select them, you’ll see the changes occurring. Let’s have a stab at styling selected elements: chapter_08/01_jquery_ui_selectable/style.css (excerpt) #tags .ui-selecting { background: #FEFF9F; } #tags .ui-selected { background-color:#eEeF8F; } The ui-selecting class is applied as the user is in the process of selecting elements, and the ui-selected class is added as soon they stop. If you try it now, you’ll see you can lasso some squares. It’s quite a natural interaction—which is exactly what Licensed to JamesCarlson@aol.com Licensed to JamesCarlson@aol.com Lists, Trees, and Tables 295 you want from your page components. You can also click while holding down the Ctrl key to select individual items. The next task we want to do is help with the duplicate tags. In a tagging system the number of tags for each term is important—so rather than just deleting duplicates, we’ll write some code to select any tags that match the user’s selection. For instance, if they click on “A-lister,” all the “A-lister” tags will be highlighted. We need to know which events we can hook into from the jQuery UI component. Consulting the documentation, 1 we find that we can capture the start, stop, se- lecting , unselecting, selected, and unselected events. We could capture the selecting event—and remove duplicates as the user moves the mouse—but it might be a bit confusing. We’ll stick with the stop event, which fires as soon as the user completes the selection: chapter_08/01_jquery_ui_selectable/script.js (excerpt) $('#tags').selectable({ stop: function() { // The user stopped selecting! } }); Now we can begin our quest to find the duplicate tags. Our general approach will be to make a list of all the tags the user has selected, then search for any duplicates of those tags that appear in the greater tag list: 1 http://docs.jquery.com/UI/Selectable Licensed to JamesCarlson@aol.com Licensed to JamesCarlson@aol.com 296 jQuery: Novice to Ninja chapter_08/01_jquery_ui_selectable/script.js (excerpt) var names = $.map($('.ui-selected, this'), function(element, i) { return $(element).text(); }); $('li', this) .filter(function() { if ($.inArray($(this).text(), names) != -1) { return true; } else { return false; }; }) .addClass('ui-selected'); To find the duplicates, we’ve called on the service of an assortment of new jQuery features, so hold on to your hats! The first of these is the oddest: $('.ui-selected', this). This looks like a regular jQuery selector, but there’s a second parameter. It turns out that the complete definition for the jQuery selector is actually $(expression, context)—we’ve just been omitting the second parameter. The context defines where jQuery should look for your selector; by default it looks everywhere on the page—but by specifying our unordered list as the context, the expression will be limited to elements inside the list. $.map and $.inArray Next we use a couple of jQuery utility methods: $.map and $.inArray to juggle the list items. The utility methods jQuery provides are mostly for working on JavaScript arrays—and that’s what we’re doing here. First we create an array called names, which we populate using the $.map method. The $.map method allows you to take each element in the array, process it in some way, and return the results as a new array. You use it when you want to transform every element in the same way. We want to transform our jQuery selection into a simple list of tag text—so we pass in the selection, and define an anonymous function to return each element’s text. Hey presto: an array of tag text! Licensed to JamesCarlson@aol.com Licensed to JamesCarlson@aol.com Lists, Trees, and Tables 297 We next use the context trick as before to retrieve all the list item elements, and filter them based on whether or not they’re duplicates. Our filter function uses the $.inArray utility method, which searches an array (but only plain JavaScript arrays—not jQuery selections, unfortunately) for a specified value. Given an array and a search term, like $.inArray(value, array), it will return the value’s index in the array. Helpfully, it will return -1 if the value is not found in the array. Re- member that filter expects us to return either true or false—so we just check to see if $.inArray returns -1, and return true or false as appropriate. Using filter in this way allows us to search our array of tag texts for each list item’s text—if it’s in there, it’s a duplicate, so we return it to the filter to be selected. Accessing the Data Now that we can make selections, how can we use them? The jQuery UI Selectable component works with class names, so we will too. To acquire the list of selected values, we simply search for any items that have the ui-selected class on them: chapter_08/01_jquery_ui_selectable/script.js (excerpt) $('#approve').click(function() { $('#tags') .find('.ui-selected') .addClass('approve') .removeClass('ui-selected reject'); }); $('#reject').click(function() { $('#tags') .find('.ui-selected') .addClass('reject') .removeClass('ui-selected approve'); }); $('#clear').click(function() { $('#tags') .find('li') .removeClass('ui-selected approve reject'); $('#approved').val(''); }); Licensed to JamesCarlson@aol.com Licensed to JamesCarlson@aol.com 298 jQuery: Novice to Ninja We’re just adding an approve or reject class when the user clicks on our but- tons—also being sure to remove the ui-selected class, since we want to style approved tags differently from selected ones. But what if we wanted to, say, send this information to the server? Perhaps it would be good to store the list of approved tags in a hidden form field, so that the server can access it for processing. Let’s update the #approve click handler to iterate over the approved items, and append each item’s index to a hidden field in a simple pipe-delimited format: chapter_08/01_jquery_ui_selectable/script.js (excerpt) $('#approve').click(function() { var approvedItems = ""; $('#tags') .find('.ui-selected') .addClass('approve') .removeClass('ui-selected reject') .each(function() { approvedItems += $(this).index() + "|"; }); $('#approved').val(approvedItems); }); We’ll also add a line to our #clear button click handler to clear that input’s value: chapter_08/01_jquery_ui_selectable/script.js (excerpt) $('#approved').val(''); Thanks to the index method, we now know which items in the list have been ap- proved. index will tell you an item’s position inside its parent element. Our control is impressive in how easy it is to use. The jQuery UI selectable behavior is doing a lot of work behind the scenes to allow lists to be selectable—but the end result is a natural-feeling component, and that’s exactly what we want. Sorting Lists With the tag system under control, it’s time to turn to some of the other lists that are scattered throughout the admin section. Many of these lists are populated by the server in the order they were entered into the system. This is good for seeing Licensed to JamesCarlson@aol.com Licensed to JamesCarlson@aol.com Lists, Trees, and Tables 299 what’s new, but bad for finding particular items. Our client has asked us to build some sorting capabilities into all of the lists in the admin section, so he can click a button and have the lists sorted in ascending or descending alphabetical order. The markup we’ll be dealing with is a simple unordered list made up of links: chapter_08/02_sorting_lists/index.html (excerpt) <ul class="sortable"> <li><a href="#">Beau Dandy</a></li> <li><a href="#">Glendatronix</a></li> <li><a href="#">BMX Spandex Corporation</a></li> <li><a href="#">Maxwell Zilog</a></li> <li><a href="#">Computadors</a> </ul> jQuery objects lack any built-in sorting functionality. This makes sense, after all; a selection could include different kinds of elements located in different parts of the page, so sorting them in a consistent manner would be impossible. To sort our jQuery selections, therefore, we need to fall back on some JavaScript array methods. jQuery selections aren’t actually arrays, but they’re “array-like,” and they allow us to use the JavaScript sort function on them. We’ll try to build a reusable list-sorting widget. We’ll call it SORTER, and we’d call SORTER.sort(list) to sort a list in ascending order, and SORTER.sort(list, 'desc') to sort in descending order. We’ll assume that the selector passed in will match ordered or unordered lists, but let’s see if we can make that happen: chapter_08/02_sorting_lists/script.js (excerpt) var SORTER = {}; SORTER.sort = function(which, dir) { SORTER.dir = (dir == "desc") ? -1 : 1; $(which).each(function() { // Find the list items and sort them var sorted = $(this).find("> li").sort(function(a, b) { return $(a).text().toLowerCase() > $(b).text().toLowerCase() ? SORTER.dir : -SORTER.dir; }); $(this).append(sorted); }); }; Licensed to JamesCarlson@aol.com Licensed to JamesCarlson@aol.com 300 jQuery: Novice to Ninja That code is deceptively short, because it happens to be doing a lot! First up, we check to see if desc was passed in as the dir parameter, and set the SORTER.dir variable accordingly. All we need to do is grab all of the first-level children list elements and give them a sort. We only want the first-level items; if we grabbed further levels, they’d be sorted and dragged up to the parent level. Because calling sort reverts our selections to raw JavaScript, we need to rewrap them in the $() to be able to call the jQuery text method and compare their values. We also convert the values to lowercase—which makes the sorting case-insensitive. The sort Function The sort function is plain old JavaScript: it sorts an array based on the results of the function you pass to it. sort will go over the contents of the array and pass them to your function in pairs. If your function returns 1, sort will swap the items and place the second one first. If your function returns -1, JavaScript will put the first item first. Finally, if your function returns 0, sort will consider that both items are equal and no sorting will take place. We’re doing a little magic to let us use the same function for sorting in ascending and descending order: we’ve set our SORTER.dir variable to -1 or 1, depending on the direction. Then in the sort comparison function, we do a further calculation: if a is less than b, we return -SORTER.dir. If the direction comes in as -1, we process it as -(-1), which is 1—so if we’re trying to sort descending, the return values are swapped. Once we’ve sorted the items, we can reinsert them into the list in the correct order. Remember, the append function removes the element first—so it removes the item and appends it in the correct position. To test it out, we’ll add some buttons to our HTML and call SORTER.sort from their click event handlers: chapter_08/02_sorting_lists/script.js (excerpt) $('#ascending').click(function() { SORTER.sort('.sortable'); }); $('#descending').click(function() { SORTER.sort('.sortable', 'desc'); }); Licensed to JamesCarlson@aol.com Licensed to JamesCarlson@aol.com Lists, Trees, and Tables 301 Manipulating Select Box Lists Although we covered forms in the previous chapter, there’s still time to have a look at certain form elements in the context of lists. Here we’re going to examine select elements, especially those with multiple="multiple" (that is, select boxes which appear as selectable lists of items). Swapping List Elements The StarTrackr! client has asked us to improve the admin functionality for assigning celebrities to the A-list. The current functionality consists of two select elements: one contains the A-list celebrities, and the other contains every other celebrity in the system. But the world of popularity is extremely fickle—and an A-lister today can be a nobody tomorrow. So the client wants to be able to easily swap the celebrities between each list. We’ll add a few controls to the interface to let him do just that, as shown in Figure 8.2. Figure 8.2. List boxes with controls This is the HTML we’re dealing with, consisting of the two select elements, and a few buttons for performing various operations: Licensed to JamesCarlson@aol.com [...]... JamesCarlson@aol.com SWAPLIST.swapAll = function(from ,to) { $(from) children() appendTo (to) ; } 304 jQuery: Novice to Ninja Calling attr with a Function Parameter This is a great trick: we’ve used the attr action—but it’s not the version we’re used to Previously we used the attr(key, value) action to set attributes to a static value, but attr also lets us pass in a function to determine the value The function will... Licensed to JamesCarlson@aol.com Licensed to JamesCarlson@aol.com Whichever elements remain in the jQuery selection after the filter function runs must contain the keyword we’re looking for—so once again we use the attr method to select them 306 jQuery: Novice to Ninja Expandable Tree Here’s the secret about trees: they’re really just nested lists! The key to dealing with trees in jQuery is to make... value—so each element is toggled We can do this kind of dynamic processing with stacks of commands: text, html, val, addClass, wrap … and many more! After having to listen to the client whine on and on about how hard it is to find the celebrities he’s trying to select, you decide to throw in a little freebie: a quick search feature that lets him type some characters and automatically select any matching... !selected; }); } All we have to do is retrieve every list item and swap its selected attribute We use the attr function to set our list items to !$(this).attr('selected') The JavaScript NOT (!) operator (the exclamation mark) inverts the Boolean value, so if the value was true it becomes false, and if it was false it becomes true! Licensed to JamesCarlson@aol.com Licensed to JamesCarlson@aol.com SWAPLIST.swapAll...302 jQuery: Novice to Ninja chapter_08/03_select_lists/index.html (excerpt) As stated, the client wants the ability to swap selected items from one list to another We’ll make a SWAPLIST object that will contain all the functionality we’ll build This can then be reused anytime we need to play with select elements: chapter_08/03_select_lists/script.js... elements: chapter_08/03_select_lists/script.js (excerpt) var SWAPLIST = {}; SWAPLIST.swap = function(from, to) { $(from) find(':selected') appendTo (to) ; } We’ve defined a swap function that accepts selector strings targeting two lists: a source list and a destination list The first task we want to do is to grab any items that are currently selected We can do this using the find action with the :selected... chapter_08/03_select_lists/script.js (excerpt) We just take all the child elements (instead of only the selected elements) and append them to the bottom of the destination—the whole list jumps from source list to destination list Inverting a Selection The next client request is to add a button that inverts the current selection, to make it easier for his staff when dealing with large selections When this link is clicked, all currently... true, the element stays Licensed to JamesCarlson@aol.com Licensed to JamesCarlson@aol.com Searching through Lists Lists, Trees, and Tables 305 in the selection But if the function returns false—it’s gone … out of the selection, and unaffected by further processing To find elements we care about, we check to see if the text they contain has the text we’re looking for in it To do this we use the text action... can move the selection over to the destination list with appendTo Easy! And once we’ve defined this functionality, we can apply it to any two lists by calling our swap method from appropriate click handlers: chapter_08/03_select_lists/script.js (excerpt) $('#swapLeft').click(function() { SWAPLIST.swap('#candidates', '#a-listers'); }); Licensed to JamesCarlson@aol.com Licensed to JamesCarlson@aol.com . CSS to make the list into a grid, and convert each tag into a small box. With our grid ready to go, we have to add the jQuery UI library to the page. Now it’s time to tell the tag list to become. for our selector grid. We’ll also throw in a few buttons to allow users to approve or reject the tags in their selection, as well as a button to reset the selection: Licensed to JamesCarlson@aol.com Licensed. Licensed to JamesCarlson@aol.com Licensed to JamesCarlson@aol.com 298 jQuery: Novice to Ninja We’re just adding an approve or reject class when the user clicks on our but- tons—also being sure to