Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 60 trang
THÔNG TIN TÀI LIỆU
Cấu trúc
Practical Web 2.0 Applications with PHP
Contents at a Glance
Contents
About the Author
About the Technical Reviewer
Introduction
Who This Book Is For
How This Book Is Structured
Prerequisites
Downloading the Code
Contacting the Author
Application Planning and Design
What Is Web 2.0?
Database Connectivity
Web Site Templates
Web Site Features
Main Home Page and User Home Page
User Registration
Account Login and Management
User Blogs
Web Site Search
Application Management
Other Aspects of Development
Search-Engine Optimization
PHPDoc-Style Commenting
Security
Application Logging
Maintainability and Extensibility
Version Control and Unit Testing
Summary
Setting Up the Application Framework
Web Server Setup
Operating System
Installing the Apache HTTP Server
Installing MySQL 5
Installing PHP 5.2.3
Application Filesystem Structure
Web Root Directory
Data Storage Directory
PHP Classes Directory
Templates Directory
Full Directory Structure
Installing the Zend Framework
Configuring the Web Server
Creating a Virtual Host in Linux
Creating a Virtual Host in Windows
Restarting Your Web Server
Setting Up the Database
Using the Model-View-Controller Pattern
Separating Application Logic from Presentation Logic
Directing All Requests to index.php
Introduction to the Zend_Controller Class
How Requests Work with Zend_Controller
Creating the IndexController
Defining Application Settings
Connecting to the Database
Testing the Database Connection
The Smarty Template Engine
Why Not Use a Different Template Engine?
Improving Smarty Performance
Using a Metalanguage for Templates
Downloading and Installing Smarty
Automatic View Rendering with Zend_Controller
Integrating Smarty with the Web Site Controllers
Adding Logging Capabilities
Writing to the Log File
Summary
User Authentication, Authorization, and Management
Creating the User Database Table
Timestamps
User Profiles
Introduction to Zend_Auth
Instantiating Zend_Auth
Authenticating with Zend_Auth
Introduction to Zend_Acl
A Zend_Acl Example
Combining Zend_Auth, Zend_Acl, and Zend_ Controller_Front
Managing User Records with DatabaseObject
The DatabaseObject_User Class
Using DatabaseObject_User
Managing User Profiles
Using Profile_User
Integrating Profile_User with DatabaseObject_User
Summary
User Registration, Login, and Logout
Adding User Registration to the Application
Creating the Form Processor for User Registration
The Initial FormProcessor_UserRegistration Class
The usernameExists() Method
The IsValidUsername() Method
Adding Username Validation to FormProcessor_UserRegistration
Validating the User’s Name
Validating the User’s E-mail Address
The Complete FormProcessor_UserRegistration Class
Displaying the Registration Form and Processing Registrations
The Initial AccountController Class
Developing the Templates
Handling the Form Submission
Adding CAPTCHA to the User Registration Form
Circumventing CAPTCHA
CAPTCHA and Accessibility
PEAR’s Text_CAPTCHA
Generating a CAPTCHA Image
Adding the CAPTCHA Image to the Registration Form
Validating the CAPTCHA Phrase
Adding E-mail Functionality
Implementing Account Login and Logout
Creating the Login Template
Adding the Account Controller Login Action
Logging Successful and Failed Login Attempts
Logging Users Out of Their Accounts
Dealing with Forgotten Passwords
Resetting a User’s Password
Functions for Resetting Passwords
Implementing Account Management
Creating the Account Home Page
Updating the Web Site Navigation
Allowing Users to Update Their Details
Summary
Introduction to Prototype and Scriptaculous
Downloading and Installing Prototype
Prototype Documentation
Selecting Objects in the Document Object Model
The $() Function
The getElementsByClassName() Function
The $$() Function
The getElementsBySelector() Function
Prototype’s Hash Object
Other Element Extensions
Showing and Hiding Elements
Retrieving Dimensions of Elements
Managing Classes of Elements
Manipulating Strings with Prototype
Ajax Operations in Prototype
Ajax Request Options
Ajax Callback Functions
The XMLHttpRequest Callback Argument
JavaScript Object Notation (JSON)
An Ajax.Request Example
Handling XML Data from an Ajax Request
Handling XML That Isn’t Well Formed
Completing the onFailure Error Handler
The Complete Ajax.Request Example
Event Handling in Prototype
Observing an Event
Finding Out Which Element an Event Occurred On
Canceling an Event
Creating JavaScript Classes in Prototype
Creating a Class
Binding Function Calls to Objects
From Prototype to Scriptaculous
Prebuilt Controls
Drag and Drop
Visual Effects
DOM Element Builder
JavaScript Unit Testing
Downloading and Installing Scriptaculous
Combining Prototype, Scriptaculous, Ajax, and PHP in a Useful Example
Creating the Main HTML Page: index.php
Styling the Application: styles.css
Creating and Populating the Database: schema.sql
Managing the List Items on the Server Side: items.php
Connecting to the Database
Retrieving the List Items
Processing and Saving the List Order
Processing Ajax Requests on the Server Side: processor.php
Handling the Load Action
Handling the Save Action
Creating the Client-Side Application Logic: scripts.js
Application Settings
Initializing the Application with init()
Updating the Status Container with setStatus()
Loading the List of Items with loadItems()
Handling the Response from the Ajax Request in loadItems()
Handling a Change to the List Order with saveItemOrder()
Handling the Response from the Ajax Request in saveItemOrder()
Summary
Styling the Web Application
Adding Page Titles and Breadcrumbs
The Breadcrumbs Class
Generating URLs
Generating URLs in Controller Actions
Generating URLs in Smarty Templates
Setting the Title and Trail for Each Controller Action
Creating a Smarty Plug-In to Output Breadcrumbs
Displaying the Page Title
Integrating the Design into the Application
Creating the Static HTML
Moving the HTML Markup into Smarty Templates
Modifying header.tpl
Modifying footer.tpl
Highlighting the Active Navigation Section
Constructing the CSS
Specifying Media Types and Loading the CSS File
Creating the Application CSS
Creating the Three-Column Layout
Styling the Page Header
Styling the Tabbed Navigation Bar
Setting the Global Styles
Styling the Page Content
Creating a Print-Only Style Sheet
Modifying the Screen Style Sheet
The Full Application Style Sheet
Styling the Application Web Forms
Loading Prototype and Scriptaculous
Implementing Client-Side Form Validation
Adding JSON Support to CustomControllerAction
Modifying the Form Processor
Modifying the Registration Controller Action
Detecting Ajax Requests
Returning Form Errors Using JSON
Creating the JavaScript Form Validator
Initializing the UserRegistrationForm JavaScript Class
Hiding Form Errors
Displaying Form Errors
Handling the Form Submission
Handling the Form Validation Response
Loading the UserRegistrationForm Class
Summary
Building the Blogging System
Creating the Database Tables
Setting Up DatabaseObject and Profile Classes
Creating the DatabaseObject_BlogPost Class
Creating the Profile_BlogPost Class
Creating a Controller for Managing Blog Posts
Extending the Application Permissions
The BlogmanagerController Actions
Linking to Blog Manager
Creating and Editing Blog Posts
Creating the Blog Post Submission Form Template
Instantiating FormProcessor_BlogPost in editAction()
Implementing the FormProcessor_BlogPost Class
Generating a Permanent Link to a Blog Post
Filtering Submitted HTML
Why Filter Embedded JavaScript?
Types of Filtering
Implementing the cleanHtml() Method
Creating a New Blog Post
Previewing Blog Posts
Creating the Preview Action
Implementing the Preview Template
Requesting Confirmation for User Actions
Updating the Status of a Blog Post
Completing setstatusAction()
Notifying the User
Adding FlashMessenger to CustomControllerAction
Writing Messages to FlashMessenger
Outputting FlashMessenger Messages on the Web Site
Summary
Extending the Blog Manager
Listing Blog Posts on the Blog Manager Index
Fetching Blog Posts from the Database
Creating the _GetBaseQuery() Method
Creating the GetPostsCount() Function
Creating the GetPosts() Function
Retrieving a Monthly Summary of Posts
Assigning Recent Posts and the Monthly Summary to the Template
Displaying Recent Posts in the Template
Displaying the Monthly Summary
Calling the Smarty Plug-in in the Side Columns
Including Additional Data in the Side Column Sometimes
Ajaxing the Blog Monthly Summary
Creating the Ajax Request Output
The BlogMonthlySummary JavaScript Class
Installing the BlogMonthlySummary Class
Notifying the User About the Content Update
Managing Message Containers
Updating the Messages Container with BlogMonthlySummary
Integrating a WYSIWYG Editor
Downloading and Installing FCKeditor
Configuring FCKeditor
Loading FCKeditor in the Blog Editing Page
Summary
Personalized User Areas
Controlling User Settings
Presenting Customizable Settings to Users
Processing Changes to User Settings
Creating Default User Settings
The UserController Class
Routing Requests to UserController
Creating a New Route
Injecting the Route into the Router
Dynamically Generating URLs for Custom Routes
Generating Other Required Routes
Handling Requests to UserController
Displaying the User’s Blog
Displaying the Blog Index Page
Implementing the indexAction() Method
Displaying Blog Posts on the User Home Page
Displaying Individual Blog Posts
Loading Live Blog Posts Using the URL
Implementing the viewAction() Method
Displaying the Blog Post Details
Creating the Template for postNotFoundAction()
Generating Blog Archive Links
Displaying the Monthly Archive
Implementing the archiveAction() Method
Populating the Application Home Page
Loading Recent Public Posts
Implementing the Application Home Page
Loading Multiple User Records
Retrieving the Latest Posts for the Home Page
Creating the Application Home Page Template
Summary
Implementing Web 2.0 Features
Tags
Implementing Tagging
Managing Blog Post Tags
Displaying a User’s Tags on Their Blog
Displaying a Tag Space
Retrieving Posts Based on a Tag
Routing Requests to the Tag Space
Handling Requests to the Tag Space
Outputting the Tag Space
Displaying Tags on Each Post
Web Feeds
Data Formats for Web Feeds
Creating an Atom Feed with Zend_Feed
Adding the Feed to UserController
Linking to Your Feed
Other Feed Options
Microformats
An Example of Using Microformats
Why Use Microformats?
The Firefox Operator Plug-In
Microformatting Your Tags
Allowing Users to Create a Public Profile
Allowing Users to Create a Public Profile
Processing the User Details Form
Displaying the User Profile Options
Displaying a User’s Profile
Summary
A Dynamic Image Gallery
Storing Uploaded Files
Creating the Database Table for Image Data
Controlling Uploaded Images with DatabaseObject
Uploading Files
Setting the Form Encoding
Adding the Form
Specifying the File Input Type
Setting the Maximum File Size
Handling Uploaded Files
Creating the Blog Manager Action Handler
Creating the Image-Upload Form Processor
Writing Files to the Filesystem
Sending Images
Resizing Images
Creating Thumbnails
Determining the Width and Height of the Thumbnail
Determining the Input and Output Functions
Generating the Thumbnail Filename
Creating the Thumbnail
Linking the Thumbnailer to the Image Action Handler
Generating an Image Hash
Generating Image Filenames
Updating imageAction() to Serve the Thumbnail
Managing Blog Post Images
Automatically Loading Blog Post Images
Displaying Images on the Post Preview
Deleting Blog Post Images
Using Scriptaculous and Ajax to Delete Images
Modifying the PHP Deletion Code
Creating the BlogImageManager JavaScript Class
Loading BlogImageManager in the Post Preview
Deleting Images when Posts Are Deleted
Reordering Blog Post Images
Drag and Drop
Saving the Order to Database
Adding Sortable to BlogImageManager
Displaying Images on User Blogs
Extending the GetPosts() Function
Displaying Thumbnail Images on the Blog Index
Displaying Images on the Blog Details Page
Displaying Larger Images with Lightbox
Installing Lightbox
Loading Lightbox on the Blog Details Page
Linking the Blog Post Images to Lightbox
Summary
Implementing Site Search
Introduction to Zend_Search_Lucene
Comparison to MySQL Full-Text Indexing
Zend_Search_Lucene Field Types
Field Naming
Indexing Application Content
Indexing Multiple Types of Data
Creating a New Zend_Search_Lucene_Document
Retrieving the Index Location
Building the Entire Index
Indexing and Unindexing a Single Blog Post
Adding a Single Blog Post to the Index
Removing a Blog Post from the Index
Triggering Search Index Updates
When a Post Is Created
When a Post Is Updated
When a Post Is Deleted
When a Post’s Tags Are Changed
Creating the Search Tool
Adding the Search Form
Handling Search Requests
Querying the Search Index
Displaying Search Results
Types of Searches
Adding Autocompletion to the Search Tool
Providing Search Suggestions
Creating an Action Handler to Return Search Results
Retrieving Search Suggestions
Loading the SearchSuggestor Class
Displaying Search Suggestions
Adding Mouse Navigation to Results
Adding Keyboard Navigation to Results
Summary
Integrating Google Maps
Google Maps Features
Geocoding
Displaying Maps
Map Controls
Map Overlays
Controlling Maps
Planning Integration
Limitations of Google Maps
Browser Compatibility
Documentation and Resources
Creating a Google Maps API Key
Adding Location Storage Capabilities
Creating the Database Table
Creating the DatabaseObject_BlogPostLocation Class
Modifying Blog Posts to Load Locations
Creating Our First Map
Creating a New Blog Manager Controller Action
Linking to the locationsAction() Function
Displaying Your First Google Map
Loading the Google Maps API
Beginning the BlogLocationManager JavaScript Class
Loading BlogLocationManager
Managing Locations on the Map
Handling Location Management Ajax Requests
The New Location Form Processor
Creating the locationsManage Controller Action
Creating the Address Lookup Form
Extending the BlogLocationManager JavaScript Class
Required Methods
Class Initialization
The loadMap() Function
The zoomAndCenterMap() Function
Adding Locations with addMarkerToMap()
Removing Markers Using removeMarkerFromMap()
Checking to See Whether a Marker Exists with hasMarker()
Displaying Saved Locations with loadLocationsSuccess()
Handling the Add Location Form Submission
Handling the Geocoder Response with createPoint()
Handling Successful Location Creation
Saving New Coordinates for Dragged Locations
Handling the Response from Saving a Dragged Location
Removing Markers from the Map
Confirming the Deletion of the Marker
Unloading the Map
Using BlogLocationManager
Displaying the Map on Users’ Public Blogs
Outputting Locations Using the Geo Microformat
Creating the BlogLocations Class
Updating the Blog Post Display Template
Summary
Deployment and Maintenance
Application Logging
E-mailing Critical Errors to an Administrator
Creating the Log Writer
Specifying the E-mail Recipient
Adding the EmailLogger Writer to Zend_Log
Using Application Logs
Site Error Handling
Objectives of Error Handling
Handling Predispatch Errors
Notifying the User of Errors
Catching Errors
Application Runtime Errors
Creating the Error Display Templates
Web Site Administration
Administrator Section Features
User Management
Blog Post Management
Auditing Application Logs
Implementing Administration
Permissions
Creating the AdminController Class
Application Deployment
Different Configurations for Different Servers
Telling the Bootstrap Which Configuration to Use
Deploying Application Files with Rsync
Backup and Restore
Exporting a Database
Importing a Database
Summary
Index
Nội dung
{ var json = transport.responseText.evalJSON(true); this.showSuggestions(json); }, showSuggestions : function(suggestions) { this.clearSuggestions(); if (suggestions.size() == 0) return; var ul = Builder.node('ul'); for (var i = 0; i < suggestions.size(); i++) { var li = $(Builder.node('li')); li.update(suggestions[i]); ul.appendChild(li); } this.container.appendChild(ul); }, Another thing we do in the showSuggestions() function is clear any existing terms before new ones are shown. We do this using clearSuggestions(), which is shown in Listing 12-37. This is called regardless of whether any search suggestions have been found; if there are no suggestions, there is nothing to show, and if there are suggestions, then we want to show only the new ones, not ones that were previously there. Listing 12-37. Removing Existing Search Suggestions from the Search Container (SearchSuggestor.class.js) clearSuggestions : function() { this.container.getElementsBySelector('ul').each(function(e) { e.remove(); }); this.query = null; } }; One more minor change we must now make is to the loadSuggestions() function. Cur- rently in this function if the search term is empty, then we don’t bother performing this Ajax request. We must now make it so in addition to not performing the Ajax request, the current list of suggestions is hidden. The reason we add this is because if the user highlights the search input and presses Backspace, the term would be deleted but the suggestions would remain. The code in Listing 12-38 fixes this issue. CHAPTER 12 ■ IMPLEMENTING SITE SEARCH 459 9063Ch12CMP3 11/13/07 9:26 PM Page 459 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Listing 12-38. Clearing Suggestions When the Search Term Is Cleared (SearchSuggestor.class.js) loadSuggestions : function() { var query = $F(this.input).strip(); if (query.length == 0) this.clearSuggestions(); if (query.length == 0 || query == this.query) return; // other code }, Adding Mouse Navigation to Results Although the search suggestions are now being displayed when the user begins to enter a search term, it is not yet possible to do anything useful with these suggestions. The first thing we are going to do is allow users to click one of the suggestions. This will trigger the search form being submitted using the selected term. To do so, we must first handle the mouseover, mouseout, and click events for each list item. The functionality we want to occur for each event is as follows: • When the mouse is over a suggestion, highlight the suggestion. We do this by creating a new CSS style called .active and adding it using the Prototype addClassName() method. • When the mouse moves away from a suggestion, remove the .active class using removeClassName(). • When a search term is clicked, replace the term currently in the search input with the clicked term and then submit the form. First, we will add the new CSS style. We will simply make the active item display with a red background and white text. Listing 12-39 shows the new CSS selector we add to styles.css. Listing 12-39. Styling the Active Search Suggestion (styles.css) #search li.active { background : #f22; color : #fff; cursor : pointer; } Now we use the observe() method to handle the three events discussed earlier. Listing 12-40 shows the code we add to the showSuggestions() function to observe these events, as well as the suggestionClicked() function that we call from within the click event. CHAPTER 12 ■ IMPLEMENTING SITE SEARCH460 9063Ch12CMP3 11/13/07 9:26 PM Page 460 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Listing 12-40. Handling the Mouse Events with the Search Suggestions (SearchSuggestor.class.js) // other code showSuggestions : function(suggestions) { this.clearSuggestions(); if (suggestions.size() == 0) return; var ul = Builder.node('ul'); for (var i = 0; i < suggestions.size(); i++) { var li = $(Builder.node('li')); li.update(suggestions[i]); li.observe('mouseover', function(e) { Event.element(e).addClassName('active') }); li.observe('mouseout', function(e) { Event.element(e).removeClassName('active') }); li.observe('click', this.suggestionClicked.bindAsEventListener(this)); ul.appendChild(li); } this.container.appendChild(ul); }, suggestionClicked : function(e) { var elt = Event.element(e); var term = elt.innerHTML.strip(); this.input.value = term; this.input.form.submit(); this.clearSuggestions(); }, // other code CHAPTER 12 ■ IMPLEMENTING SITE SEARCH 461 9063Ch12CMP3 11/13/07 9:26 PM Page 461 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com As you can see, in the suggestionClicked() event handler, the first thing we do is determine which suggestion was clicked using the Event.element() function. We can then determine what the search term is by retrieving the innerHTML property of the element (we also use strip() to clean up this code in case extra whitespace is added to it). We then update the value of the form element and submit the form. Additionally, we clear the suggestions after one has been clicked, preventing the user from clicking a different sug- gestion while the form is being submitted. Adding Keyboard Navigation to Results The final thing we do to improve the search suggestions is to add keyboard controls to the sug- gestions. Essentially what we want to be able to do is let the user choose a suggestion using their up and down arrow keys. The keyboard handling rules we will add are as follows: •If the user presses the down arrow and no term has been highlighted (that is, set to use the .active class), then select the first term. •If the user presses the down arrow and a suggestion is highlighted, move to the next suggestion. If the user presses down when the last suggestion is highlighted, then select no suggestion so the user can hit Enter on what they have typed so far. •If the user presses up and no term is selected, then select the last suggestion. •If the user presses up and a suggestion is highlighted, move to the previous suggestion. Select no suggestion if up is pressed when the first suggestion is selected. •Submit the search form with the highlighted term when Enter is pressed. •Hide the suggestions if the Escape key is pressed. As you can probably tell, the work involved with adding keyboard controls is slightly more involved than adding mouse controls. The first thing we are going to do is to write some utility functions to help us select items and to determine which item is selected. Listing 12-41 shows the getNumberOfSuggestions() function that we add to SearchSuggestor.class.js, which simply counts the number of list items present and returns that number. This is helpful in determining the item index of the next or previous item when using the arrow keys. Listing 12-41. Determining the Number of Suggestions Showing to the User (SearchSuggestor.class.js) SearchSuggestor = Class.create(); SearchSuggestor.prototype = { // other code CHAPTER 12 ■ IMPLEMENTING SITE SEARCH462 9063Ch12CMP3 11/13/07 9:26 PM Page 462 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com getNumberOfSuggestions : function() { return this.container.getElementsBySelector('li').size(); }, ■Note When you add this function and the other new functions in this section to SearchSuggestor.class.js, make sure the comma is correctly placed after the close brace of each func- tion in the class (except for the final one). Next we write a function to select an item (that is, to apply the .active class) based on its numerical index in the list of items. This list is zero-indexed. Listing 12-42 shows the selectSuggestion() class, which works by looping over all list items and adding the .active class if it matches the passed-in argument. Note that this function also deselects every other list item. In effect we can use this function to ensure no items are selected at all by passing an invalid index (such as -1). Listing 12-42. Selecting a Single Suggestion Based on Its Index (SearchSuggestor.class.js) selectSuggestion : function(idx) { var items = this.container.getElementsBySelector('li'); for (var i = 0; i < items.size(); i++) { if (i == idx) items[i].addClassName('active'); else items[i].removeClassName('active'); } }, Next, we write a function to determine the index of the item that is currently selected, shown in Listing 12-43. This is in some ways the opposite of the selectSuggestion() function. It works almost identically, but rather than updating the class name, it checks instead for the presence of the .active class. If no items are currently selected, then -1 is returned. Listing 12-43. Determining the Index of the Selected Suggestion (SearchSuggestor.class.js) getSelectedSuggestionIndex : function() { var items = this.container.getElementsBySelector('li'); for (var i = 0; i < items.size(); i++) { if (items[i].hasClassName('active')) return i; CHAPTER 12 ■ IMPLEMENTING SITE SEARCH 463 9063Ch12CMP3 11/13/07 9:26 PM Page 463 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com } return -1; }, Now we write a function called getSelectedSuggestion(), which is shown in Listing 12-44. This function is identical to getSelectedSuggestionIndex() except that it returns the actual search term that is selected rather than its index in the list. We will use this function when the user hits Enter while a term is selected. Listing 12-44. Determining the Search Suggestion That Is Currently Selected (SearchSuggestor.class.js) getSelectedSuggestion : function() { var items = this.container.getElementsBySelector('li'); for (var i = 0; i < items.size(); i++) { if (items[i].hasClassName('active')) return items[i].innerHTML.strip(); } return ''; } }; The final thing we must do is modify the onQueryChanged() function, which is the event handler we defined that is called whenever a key is pressed in the search input. Currently, all the function does is clear any existing timers and set a new timer for fetching suggestions. We will now add handlers for specific keys to this function (in addition to the timer-handling code). Listing 12-45 shows the code we use to handle the Enter key being pressed. When the user hits Enter, if a suggestion is highlighted, then we want to populate the search input with this term and submit the form. If no term is highlighted, then we submit the form with whatever the user has typed so far. When the search term populates the input, we clear the suggestions, just as we did in the mouse-handling code. Also, note that we leave the call to clearTimeout() in front of the switch() statement. This is because we will be returning from the keys handled in the switch() statement, but we still want to cancel the timer. All normal key presses will travel beyond the switch() statement and trigger the new timer. Listing 12-45. Searching on the Selected Term When the User Hits Enter (SearchSuggestor.class.js) SearchSuggestor = Class.create(); SearchSuggestor.prototype = { // other code CHAPTER 12 ■ IMPLEMENTING SITE SEARCH464 9063Ch12CMP3 11/13/07 9:26 PM Page 464 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com onQueryChanged : function(e) { clearTimeout(this.timer); switch (e.keyCode) { case Event.KEY_RETURN: var term = this.getSelectedSuggestion(); if (term.length > 0) { this.input.value = term; this.clearSuggestions(); } return; Next we handle the Escape key being pressed. This case is fairly simple, because all we need to do is to hide the search suggestions, as shown in Listing 12-46. Listing 12-46. Hiding the Search Suggestions When the User Hits Escape case Event.KEY_ESC: this.clearSuggestions(); return; We now handle the trickier case where the user presses the down arrow key. According to the rules we specified earlier in this section, we want to select the first term if no term is selected; otherwise, we want to select the next term. As another special case, if the last term is selected, then pressing the down arrow should result in no suggestion being selected. Listing 12-47 shows the code we use to determine which suggestion should now be selected as a result of the down arrow being pressed. We make use of the utility functions we just created to help with this. Listing 12-47. Selecting the Next Item When the Down Arrow Is Pressed (SearchSuggestor.class.js) case Event.KEY_DOWN: var total = this.getNumberOfSuggestions(); var selected = this.getSelectedSuggestionIndex(); if (selected == total - 1) // currenty last item so deselect selected = -1; else if (selected < 0) // none selected, select the first selected = 0; else // select the next selected = (selected + 1) % total; this.selectSuggestion(selected); Event.stop(e); return; CHAPTER 12 ■ IMPLEMENTING SITE SEARCH 465 9063Ch12CMP3 11/13/07 9:26 PM Page 465 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com To handle the case where the up arrow is pressed, we basically just do the opposite of the down arrow calculations. Listing 12-48 shows the code for this case. This code also includes the final call of the function to initiate the new timer. Note that this won’t be called for presses of the Enter, Escape, up arrow, and down arrow keys, because we’ve returned from each of them in this function. Listing 12-48. Selecting the Previous Suggestion When the Up Arrow Is Pressed (SearchSuggestor.class.js) case Event.KEY_UP: var total = this.getNumberOfSuggestions(); var selected = this.getSelectedSuggestionIndex(); if (selected == 0) // first item currently selected, so deselect selected = -1; else if (selected < 0) // none selected, select the last item selected = total - 1; else // select the previous selected = (selected - 1) % total; this.selectSuggestion(selected); Event.stop(e); return; } this.timer = setTimeout(this.loadSuggestions.bind(this), this.delay * 1000); }, // other code }; If you now type a search term in the search box (assuming some existing searches have already taken place), you will be shown a list of suggestions for your search, as shown in Figure 12-3. You might want to add some extra functionality to the tool in the future, such as display- ing the number of results that would be returned if the user were to perform the given search. The difficulty in providing features such as this is that they are resource intensive. You need to perform the search of each term in real time (not recommended) to determine how many results the search would return, or you need to cache the result counts so the data can be accessed quickly. In any case, you need to be aware of the implications of adding features like this to your server. Even the suggestion lookup tool as it is results in a new HTTP request and database query each time, so imagine if you had hundreds or thousands of people using the search tool at any one time. CHAPTER 12 ■ IMPLEMENTING SITE SEARCH466 9063Ch12CMP3 11/13/07 9:26 PM Page 466 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Figure 12-3. Search suggestions are now being displayed below the search input. Summary In this chapter, we created a fully functioning search engine for our web application using Zend_Search_Lucene. We achieved this by creating a search index for all of the blog posts in the application. We altered the blog management code so the index is automatically maintained when posts are created, updated, or deleted. Next we added a search form to the website to allow users to find blog posts. The powerful querying syntax of Lucene meant posts could be found based on several criteria, including the title, the body, or its tags. Finally, we improved the search form to behave similarly to Google’s Suggest interface. This provides users with some suggestions on what to search for, based on the tags registered users have applied to their blog posts. In the next chapter, we will be looking closely at Google Maps. We will extend the blog func- tionality so users can add locations to their blog posts and display those maps accordingly. CHAPTER 12 ■ IMPLEMENTING SITE SEARCH 467 9063Ch12CMP3 11/13/07 9:26 PM Page 467 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 9063Ch12CMP3 11/13/07 9:26 PM Page 468 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com [...]... action, we initialize the $ret array, which will hold the return data 4 89 9063Ch13CMP3 11/15/07 8:20 AM Page 490 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 490 CHAPTER 13 ■ INTEGRATING GOOGLE MAPS Listing 13-21 Initializing the Action Handler and Loading the Blog Post (BlogmanagerController .php) < ?php class BlogmanagerController extends CustomControllerAction { // other... the locationsmanageAction() function Listing 13-25 Processing the Move Location Request (BlogmanagerController .php) case 'move': $location_id = $request->getPost('location_id'); 491 90 63Ch13CMP3 11/15/07 8:20 AM Page 492 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 492 CHAPTER 13 ■ INTEGRATING GOOGLE MAPS $location = new DatabaseObject_BlogPostLocation($this->db); if ($location->loadForPost($post->getId(),... post 493 90 63Ch13CMP3 11/15/07 8:20 AM Page 494 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 494 CHAPTER 13 ■ INTEGRATING GOOGLE MAPS • zoomAndCenterMap(): This automatically zooms the map in as far as possible to display all of the locations This will be called when the map is initially loaded and also when a new location is added If there are no locations to work with, .. .90 63Ch13CMP3 11/15/07 8:20 AM Page 4 69 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com CHAPTER 13 Integrating Google Maps A ll of the code we have developed so far in this book has been self-contained with no reliance on any outside services Frequently in your web development endeavors you will need to integrate features... and longitude -1 22.08 1783 would be returned These coordinates can then be used to mark locations on the displayed map Google provides two ways to access its geocoder The first method is to use their JavaScript interface to look up addresses This allows you to look up and add new points on your map from within the client-side web browser 4 69 9063Ch13CMP3 11/15/07 8:20 AM Page 470 Simpo PDF Merge and Split... $this->breadcrumbs->addStep('Manage Locations'); $this->view->post = $post; } } ?> 4 79 9063Ch13CMP3 11/15/07 8:20 AM Page 480 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 480 CHAPTER 13 ■ INTEGRATING GOOGLE MAPS If you were to now view this controller action (assuming you passed in a valid blog post ID in the URL of http://phpweb20/blogmanager/preview?id=PostId), an error would be displayed... locations.tpl, stored in /templates/blogmanager The added or changed lines are highlighted accordingly 90 63Ch13CMP3 11/15/07 8:20 AM Page 493 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com CHAPTER 13 ■ INTEGRATING GOOGLE MAPS Listing 13-26 The Locations Management Template with Add Location Form (locations.tpl) {include file='header.tpl' section='blogmanager' maps=true} . 12 ■ IMPLEMENTING SITE SEARCH 467 90 6 3Ch12CMP3 11/13 /07 9 :26 PM Page 467 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 90 6 3Ch12CMP3 11/13 /07 9 :26 PM Page 468 Simpo PDF. IMPLEMENTING SITE SEARCH4 60 90 6 3Ch12CMP3 11/13 /07 9 :26 PM Page 4 60 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Listing 12- 40. Handling the Mouse Events with the Search Suggestions. Listing 12- 38 fixes this issue. CHAPTER 12 ■ IMPLEMENTING SITE SEARCH 4 59 90 6 3Ch12CMP3 11/13 /07 9 :26 PM Page 4 59 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Listing 12- 38.