1. Trang chủ
  2. » Công Nghệ Thông Tin

Practical Web 2.0 Applications with PHP phần 9 pdf

60 373 1

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Tiêu đề Implementing Site Search
Trường học University of Information Technology
Chuyên ngành Web Development
Thể loại Bài báo
Năm xuất bản 2007
Thành phố Ho Chi Minh City
Định dạng
Số trang 60
Dung lượng 1,41 MB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

We will extend the blog tionality so users can add locations to their blog posts and display those maps accordingly... Specifically, we will allow users to assign one or more locations t

Trang 1

{var json = transport.responseText.evalJSON(true);

this.showSuggestions(json);

},

showSuggestions : function(suggestions) {

this.clearSuggestions();

if (suggestions.size() == 0) return;

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 rently in this function if the search term is empty, then we don’t bother performing this Ajax

Cur-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

Trang 2

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 asearch 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 searchform 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 anew CSS style called active and adding it using the Prototype addClassName() method

• When the mouse moves away from a suggestion, remove the active class usingremoveClassName()

• When a search term is clicked, replace the term currently in the search input with theclicked term and then submit the form

First, we will add the new CSS style We will simply make the active item display with a redbackground 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-40shows the code we add to the showSuggestions() function to observe these events, as well as thesuggestionClicked()function that we call from within the click event

Trang 3

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;

li.observe('mouseout',

function(e) { Event.element(e).removeClassName('active') });

var elt = Event.element(e);

var term = elt.innerHTML.strip();

Trang 4

As you can see, in the suggestionClicked() event handler, the first thing we do is determinewhich suggestion was clicked using the Event.element() function We can then determine whatthe search term is by retrieving the innerHTML property of the element (we also use strip() toclean 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 clearthe 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 gestions Essentially what we want to be able to do is let the user choose a suggestion usingtheir up and down arrow keys The keyboard handling rules we will add are as follows:

sug-• If the user presses the down arrow and no term has been highlighted (that is, set to usethe active class), then select the first term

• If the user presses the down arrow and a suggestion is highlighted, move to the nextsuggestion 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 moreinvolved than adding mouse controls

The first thing we are going to do is to write some utility functions to help us select itemsand 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

Trang 5

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;

Trang 6

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();

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 stillwant to cancel the timer All normal key presses will travel beyond the switch() statement andtrigger 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

Trang 7

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 weneed 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 beselected 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;

Trang 8

To handle the case where the up arrow is pressed, we basically just do the opposite of thedown arrow calculations Listing 12-48 shows the code for this case This code also includesthe 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 ofthem 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;

If you now type a search term in the search box (assuming some existing searches havealready 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 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 toperform the search of each term in real time (not recommended) to determine how manyresults the search would return, or you need to cache the result counts so the data can beaccessed quickly

display-In any case, you need to be aware of the implications of adding features like this to yourserver Even the suggestion lookup tool as it is results in a new HTTP request and databasequery each time, so imagine if you had hundreds or thousands of people using the search tool

at any one time

Trang 9

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 powerfulquerying 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 tionality so users can add locations to their blog posts and display those maps accordingly

Trang 11

func-Integrating Google Maps

All 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 that you don’t necessarily have the resources to provide Or it simply

may be that an outside service provides you with access to data you wouldn’t otherwise be

able to access

In this chapter, we will be integrating Google Maps (http://maps.google.com) into ourweb application as an example of using third-party services Specifically, we will allow users to

assign one or more locations to each of their blog posts and then display a map marking the

location when visitors view the respective blog post

Many other services are available on the Internet that can be used on your web site (oreven desktop applications), such as displaying product information and reviews using Ama-

zon Web Services or processing credit card payments (using PayPal, Google Checkout, or one

of the many other similar options available)

In addition to displaying maps with Google Maps, we will also make use of the geo format, extending what we learned about microformats in Chapter 10

micro-Google Maps Features

The Google Maps API is a well-documented and comprehensive JavaScript API that gives

developers a wide range of options for displaying maps and controlling how their maps

behave Before we begin planning our usage of the maps, let’s take a look at some of the key

features available

Geocoding

Gecoding is the process of converting a street address into geographical coordinates (latitude

and longitude) For example, Google’s address is 1600 Amphitheatre Pkwy, Mountain View,

California If you were to enter this address into the Google Maps geocoder, then the

coordi-nates of latitude 37.423111 and longitude -122.081783 would be returned These coordicoordi-nates

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 theirJavaScript 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

469

C H A P T E R 1 3

Trang 12

The second method to access the geocoder is to query their geocoder web service on theserver side This makes it easy to look up addresses and save the results in your database forfuture use, and it doesn’t therefore rely on the end-user having a JavaScript-enabled web browser.For any given request, the geocoder may return zero, one, or several matches Since anAPI key is required to access Google Maps (which we will create shortly) and each IP addresshas a limitation on the number of geocoder requests daily (15,000), an incorrect key or toomany requests might be the cause for no matches being returned Note that these errors areindicated in the status section of the response.

Note An API key is what Google uses to control access to their services For you (as a web site owner ordeveloper) to use Google Maps on your own web site, you must have an API key When a user tries to load amap from your site, your API key is used in the request

If multiple addresses are found (perhaps you entered an address such as 123 Main St.without specifying the town), it is up to you as the developer to determine which address isthe one you were after The response includes an accuracy rating with each matched address.The rating indicates to what level the response is accurate (such as country, region, city, street,intersection, or an exact match)

We will use the client-side geocoder in this chapter to look up addresses entered by userswhen they try to add locations to their blog posts

Displaying Maps

When displaying a map with Google Maps, you must provide an HTML element on your page

in which to hold the map The map will automatically fill the entire width and height of thespecified element

Additionally, the objects in the following sections can be placed on maps as required.Map Controls

When the map is initially displayed, there will be no controls displayed Controls are buttons

on the map that allow the user to manipulate the display The available controls are as follows:

• Zoom The user can zoom in or zoom out using the appropriate buttons or slider.

• Panning The user can move the map north, south, east, or west using the panning

but-tons

• Map Type The user can choose the type of map displayed, which by default includes a

street map, a satellite map, or a combination of the two (called a hybrid map).

• Mini-map This a small map that sits in the corner of the main map that is zoomed out

further than the main map, allowing users to change the location of the map morequickly for large distances

Trang 13

• Map scale This indicates how many meters, yards, kilometers, or miles the displayed

distance represents

Figure 13-1 shows an example map that includes each of these controls

Figure 13-1.A sample map showing various map controls

The Google Maps API allows you to add any of these controls as required Additionally,you can choose which corner of the map to anchor the control By default, the zoom and pan-

ning buttons are in the top-left, the map type buttons are in the top-right, the mini-map is in

the bottom-right, and the scale is in the bottom-left

Although we will not be doing so in this chapter, the API also allows developers to createcustom controls that can be overlaid on the map For example, if you wanted to use graphical

icons to switch between the map types instead of the text buttons, you could do so by creating

a custom control

Map Overlays

A map overlay is any object (aside from the map controls) that appears on top of the map that

isn’t actually part of the map yet moves with the map as it is panned The use of overlays is

essential to portray any useful custom information to your users The different types of

over-lays available in Google Maps are as follows:

• Markers A marker represents a single point on the map It is possible to use any icon

you desire to display the marker, although the simplest solution is to use the built-inicon (shown in Figure 13-2) A map can have any number of markers, although theremay be scalability issues you need to take into account for a large number of markers

Trang 14

Note The Google Maps API provides a class (google.maps.MarkerManager) that can be used to age a large number of points Since loading a large number of points (hundreds or thousands) can result in

man-a lman-arge man-amount of memory use in the client web browser, then how these points man-are loman-aded man-and displman-ayedneeds to be managed—there’s no sense in loading points that are in Europe when the user is viewing NorthAmerica Since we will not be displaying a large number of markers at any one time in this book, we will not

be using this class However, if you wanted to extend the functionality we add this chapter to display everylocation of every blog post in our database, then you would consider using this class

• Information windows An information window is a callout box you can add to your map

that points to a specified point on the map (which may or may not have a correspondingmarker) Within the information window you can display any HTML content you please(such as text, links, or images) This is commonly used to display information after amarker has been clicked The API also allows you to display tabbed information windows,allowing you to display multiple pages of information in a single information window

• Polylines By specifying a series of points, you can draw lines on the map using the

polyline classes Google uses this on its own maps to display driving directions betweenlocations You can use this feature in many ways, such as if you want to plot the pathand distance of your morning jog (since the distance between two points can be calcu-lated using their latitude and longitude)

Figure 13-2 shows an example of what a marker and information window looks like on

a map

Figure 13-2.Map overlays in Google Maps

Trang 15

We will be using markers to display one or more points on users’ blog posts as required.

We will look at some advanced usage of markers as we will allow users to click and drag

mark-ers to a new location if they please

Controlling Maps

In addition to using the controls that can be added to maps, it is also possible to control maps

programmatically For example, you must choose where to center the map initially; you can

use code to switch between satellite and street map view, and you can open or close

informa-tion windows as required using funcinforma-tion calls

The code we write in this chapter will use a combination of programmed map control aswell as allowing users to control the map as they please For example, when a marker is dragged

to a new location, we will make its information window appear, but we will also allow the users

to close and reopen the window as they please

Planning Integration

Now that we have an idea of the functionality that Google Maps provides, it’s time to plan how

we use the available features As mentioned previously, we are going to allow users to add one

or more locations to each of their blog posts In doing so, we must consider a number of

issues:

• We will use the geocoder to find the coordinates of each location being added by theuser This means we must add a map to the blog manager section of the site and display

a marker when they enter an address

• Since the found location might not be the exact point the user wants to display, we willallow them to drag the marker to any location on the map that they please

• We will save the coordinates and a description for each point in the database

• For each post that has locations assigned to it, we must display the map on the postdetails page, as well as including an information window showing the description forevery marker that is added

Limitations of Google Maps

Although the terms and conditions of Google Maps state there is no limit to the number of

page views for each map, there is a limitation for the number of geocoder requests Each IP

address has a limit of 15,000 geocoder requests per day This means when the geocoder is used

from a user’s web browser, the request counts against their quota (not your server’s) Note,

however, that if you use the server-side geocoder, then each request counts against your

Trang 16

the geocoder response for later use Although we won’t be caching the entire response, we will

be caching the latitude and longitude for each point in the database

Browser Compatibility

Google Maps is compatible with all modern graphical browsers Obviously since it is pletely reliant on JavaScript, users must have JavaScript enabled in their browser In case auser has a browser that does support JavaScript but doesn’t support Google Maps (perhaps it’s an older browser or a browser with an incomplete JavaScript implementation), thegoogle.maps.BrowserIsCompatible()function is available to check whether the browser hasthe capabilities Google Maps requires

com-In addition to using google.maps.BrowserIsCompatible() to ensure Google Maps willwork, we should also provide a non-JavaScript solution for users viewing blog posts that havelocation data To handle this case, we will simply display a list of any saved locations thatbelong to a post (using microformats) rather than displaying a map

Because we have implemented similar solutions in previous chapters, I have chosen not

to include a non-JavaScript implementation in this chapter for the management of blog postlocations However, I have included notes later about how to approach the issue

Documentation and Resources

Since I cannot cover every part of the API in this chapter, it is very much worth your while touse the documentation provided by Google if you want further information about usingGoogle Maps

For an introduction to how Google Maps works (including many examples), it is worthreading http://www.google.com/apis/maps/documentation/index.html

For the complete API reference (that is, documentation of all classes, functions, and sponding arguments), visit http://www.google.com/apis/maps/documentation/reference.html

corre-■ Note We will be using the Google Ajax API loader (http://code.google.com/apis/ajax/

documentation), meaning all classes belong in the google.maps.*namespace This means droppingthe Gfrom the beginning of each class name as it appears in the documentation and adding google.maps.instead For example, to create a new latitude/longitude point, we use the google.maps.LatLngclassrather than the documented GLatLngclass

Creating a Google Maps API Key

To use Google Maps on your own web site, you must create an API key A unique API key must

be created for every domain on which you want to display maps (every subdomain must alsohave its own key)

To create a key, you must visit http://www.google.com/apis/maps/signup.html and enteryour web site domain name It is free to create a key (although you must agree to Google’sterms and conditions, available at the sign-up URL)

Trang 17

Note You will also need a Google account to create a key If you have used any of Google’s other services

(such as Gmail), then you already have an account

Once you’ve created the API key, add it to your application configuration file We will add

it to the settings.ini file using the key google.maps.key Listing 13-1 shows the line we add to

the end of settings.ini

Listing 13-1.Storing the API Key in the Application Settings File (settings.ini)

google.maps.key = "your key here"

Adding Location Storage Capabilities

Before we actually begin integrating Google Maps in our application, we must do what we

have done for other functionality we’ve added to the application: create a new database table

and a DatabaseObject subclass to manage the database data

Creating the Database Table

First let’s create a new database table We will call this table blog_posts_locations, and a

single record will hold one location for one blog post Each post can have any number of

locations associated with it

Listing 13-2 shows the schema for blog_posts_locations, which can be found in theschema-mysql.sqlfile

Listing 13-2.The MySQL Database Table for Storing Blog Post Locations (schema-mysql.sql)

create table blog_posts_locations (

location_id serial not null,post_id bigint unsigned not null,longitude numeric(10, 6) not null,latitude numeric(10, 6) not null,description text not null,primary key (location_id),

foreign key (post_id) references blog_posts (post_id)) type = InnoDB;

As usual, the corresponding PostgreSQL schema can be found in the schema-pgsql.sql file

Creating the DatabaseObject_BlogPostLocation Class

We must also create a new class that extends from DatabaseObject in order to manage the data

in this table Listing 13-3 shows the DatabaseObject_BlogPostLocation class, which we store in

the BlogPostLocation.php file in /include/DatabaseObject

Trang 18

There are no new concepts in this code, because parts of classes covered earlier in thisbook have been combined to create this class The key thing to notice is the inclusion of theGetLocations()method, which allows us to fetch all of the locations for a single blog post easily.

Listing 13-3.Managing Location Data in the blog_posts_locations Table (BlogPostLocation.php)

$post_id = (int) $post_id;

$location_id = (int) $location_id;

if ($post_id <= 0 || $location_id <= 0)return false;

return $this->_load($query);

}

public static function GetLocations($db, $options = array()){

// initialize the options

$defaults = array('post_id' => array());

foreach ($defaults as $k => $v)

$options[$k] = array_key_exists($k, $options) ? $options[$k] : $v;

$select = $db->select();

Trang 19

// turn data into array of DatabaseObject_BlogPostLocation objects

$locations = parent::BuildMultiple($db, CLASS , $data);

return $locations;

}}

?>

Modifying Blog Posts to Load Locations

The next change we make is to the DatabaseObject_BlogPost class We are going to make this

class automatically load all saved locations, just as it does with its profile and any assigned

images Doing so makes it easy for us to include the saved locations when outputting a blog

post

To do so, we call the GetLocations() function we added to DatabaseObject_BlogPostLocation

in the postLoad() function of BlogPost.php, as shown in Listing 13-4 Note that we can reuse the

$optionsarray used for retrieving images

Listing 13-4.Automatically Loading Saved Locations When Loading a Blog Post (BlogPost.php)

$this->images = DatabaseObject_BlogPostImage::GetImages($this->getDb(),

$options);

Trang 20

$this->locations = DatabaseObject_BlogPostLocation::GetLocations(

$this->getDb(),

$options );

}

// other code}

?>

Additionally, we must modify the GetPosts() function in this same class so locations are loaded automatically when blog posts are (meaning if you wanted to you could easily list locations on any of the blog post index pages) To do so, we make the changes shown inListing 13-5

Listing 13-5.Loading Locations Automatically in GetPosts() (BlogPost.php)

?>

We now have the necessary structures in place to load locations when blog posts areloaded, thereby allowing us to easily display the locations (or add them to the map)

Creating Our First Map

The remainder of this chapter will be dedicated to extending the blog post manager to allowusers to add locations to their blog posts and then display them on their public blog accord-ingly We will add a new page to the blog post management area that displays a map while also

Trang 21

allowing the user to enter an address We will then search on this address using the geocoder

and add the found location to the map Once the location has been added, the user will be

able to move or remove the location from the map, or they will be able to add more locations

All of this functionality will be implemented using a combination of the Google Maps API

as well as using Ajax to save location data in our application database We will develop a new

class to manage the map as well as to send location data between the browser and our server

To begin with, we’ll create the most basic map possible to fit within our application, andthen we’ll build on it as we continue through this chapter

Creating a New Blog Manager Controller Action

The first thing to do is to create a new action in the BlogmanagerController.php file This page

will simply be a placeholder to display the Google map and the form to add new locations

Since all functionality will be implemented via Ajax, this action won’t need to do anything

other than loading the blog post that locations are being added to We will create another

action handler shortly to deal with loading, saving, and removing locations from a blog post

$post_id = (int) $request->getQuery('id');

$post = new DatabaseObject_BlogPost($this->db);

if (!$post->loadForUser($this->identity->user_id, $post_id))

$this->_redirect($this->getUrl());

$this->breadcrumbs->addStep(

'Preview Post: ' $post->profile->title,

$this->getUrl('preview') '?id=' $post->getId() );

Trang 22

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

since we haven’t created the corresponding template

Listing 13-7 shows a template we can use for now until we create the map display code.This file is written to locations.tpl in the /templates/blogmanager directory

Listing 13-7.A Starting Template for Managing Blog Post Locations (locations.tpl)

{include file='header.tpl' section='blogmanager' maps=true}

Linking to the locationsAction() Function

Before we complete the template for the newly created action handler, we are going to link to

it from the blog post preview page Similarly to how tags and images are displayed on thispage, we are going to add a block above the blog content that lists all locations that belong tothe post To cut down on page load time, we are not going to display the map on this page.Rather, we will provide a link to locationsAction() (which in turn will display the map).First we display a block in the preview.tpl file in /templates/blogmanager that lists eachexisting location along with a link, as shown in Listing 13-8 We add this between the imagemanagement area and the blog post details

Listing 13-8.Displaying Locations in the Blog Post Preview Page (preview.tpl)

Trang 23

<input type="hidden" name="id" value="{$post->getId()}" />

<input type="submit" value="Manage Locations" />

Displaying Your First Google Map

Now that the basic infrastructure in the blog manager is ready, we can begin our actual Google

Maps implementation To begin, we will look at how to load the Google Maps API as well as

how to initialize and display the map We will do this by creating a new JavaScript class in

which all calls to the API are contained

Loading the Google Maps API

The first thing we are going to do is load the Google Maps JavaScript file Like most of the

other scripts we have loaded in our application, we will load this in the <head> section of our

HTML document To do so, we must load the file from header.tpl

Just like we did with Lightbox in Chapter 11, we want to load the Google Maps API onlywhen we actually display a map As such, we will add a conditional include for loading the

JavaScript file

To load the API, you must load the script at http://www.google.com/jsapi?key=KEY, where KEY is the Google Maps API you created earlier in this chapter and wrote to the application set-

tings file

Trang 24

Note As mentioned previously, we are using the Google Ajax API loader, so if you have used Google Maps

in the past, this URL may be different from what you’re used to using Using this loader allows you to easilyuse different Google APIs in your code while needing to load only one JavaScript file

Since we require the google.maps.key setting we added earlier in this chapter to load theAPI, we require access to this value in the template To make this available, we are going toassign the application settings to the template by default This is not something we haveneeded in the past; however, it may be something you use if you want to output other applica-tion settings directly

To allow this, we must make a minor change to the CustomControllerAction class, which

is used to set up the default template data Listing 13-10 shows the change we make to thisclass, which can be found in the /include/Controllers/CustomControllerAction.php file

Listing 13-10.Assign the Application Settings to the Template (CustomControllerAction.php).

<?php

class CustomControllerAction extends Zend_Controller_Action{

// other codepublic function postDispatch(){

// other code

$this->view->config = Zend_Registry::get('config');

}}

?>

Now we can use the settings to load the Google Maps API Listing 13-11 shows the code

we add to header.tpl to load the required JavaScript if the $maps variable is set to true

Listing 13-11.Loading the Google Maps API If the $maps Variable Is True (header.tpl)

</head>

<body>

<! // other code >

Trang 25

Beginning the BlogLocationManager JavaScript Class

We will now begin to write a new JavaScript class called BlogLocationManager, which will be

responsible for loading the map, initiating geocoder requests, and initiating Ajax requests to

load, save, update, and delete markers

Because many features will be going into this class—bringing together the Google MapsAPI with what you learned previously in this book—we will build the class step by step Ini-

tially, we’ll display a hybrid map (combination of satellite and street map) with some basic

controls, centered on the Googleplex—home of the people who brought you Google Maps!

Note You must specify a starting point when displaying a Google map, so we’ll simply use the

coordi-nates returned by a geocoder request of Google’s own address, as described earlier in this chapter Once we

have our own locations to display, we’ll center the map on those locations instead, but for now we’ll use this

location so you can see how to actually use the API

The first thing we must do in this class is to actually load the Google Maps API Eventhough I said we loaded it earlier, in fact all we did is load the generic Google API This API is

used to load a number of different APIs offered by Google To do so, we use the google.load()

method The first argument is the name of the API we want to load (in this case it is maps),

while the second argument is the version of the API

Tip Being able to specify the version number allows you to run any version of the Google Maps API you

please For example, if an upgrade was made by Google that broke an existing application of yours, you

could temporarily force your application to use the older version until you make your application compatible

with the newest version

For our purposes, we simply specify 2 as the API version, which uses the latest version ofthe Google Maps 2 code As such, we need to call google.load('maps', '2') to load the

Google Maps API We do this before declaring the class so the API is ready to be used when the

class is instantiated

Listing 13-12 shows the initialization of the class Inside the constructor (initialize()),

we observe the onload event on the page The Google documentation recommends that you

display the map only after the page has completed loading We will look at the loadMap()

function shortly As we have done previously for classes we have written, we bind the call to

loadMap()to this so we have the correct context when inside the function

Note that we store this code in a file called BlogLocationManager.class.js in the./htdocs/jsdirectory

Trang 26

Listing 13-12.Initializing the BlogLocationManager Class (BlogLocationManager.class.js)

this.container = $(container);

Event.observe(window, 'load', this.loadMap.bind(this));

},Next we implement the loadMap() method, which is responsible for creating the map, aswell as adding all of the controls and markers We must perform some basic tasks related tomanaging maps correctly in our browsers The first thing to do is call the

google.maps.BrowserIsCompatible()function to ensure the user’s browser can display maps

If it can’t, we simply return from the function, thereby not making any calls to the maps API.The other thing we do—which is extremely important—is to observe the window unloadevent This means when the browser closes or the user navigates to a new page, we call theunloadMap()function This allows us to perform any map shutdown code required, which wewill soon see is important when we cover unloadMap()

Listing 13-13 shows the code we use to check for compatibility and to unload the mapscorrectly

Listing 13-13.Ensuring Browser Compatibility and Destructing the Map Correctly

(BlogLocationManager.class.js)

loadMap : function(){

if (!google.maps.BrowserIsCompatible())return;

Event.observe(window, 'unload', this.unloadMap.bind(this));

We are now free to create the map by instantiating the google.maps.Map2 class This classtakes the container in which the map will be displayed as its first argument (additionally youcan specify further options to customize the map in the second argument; however, we willnot be using this)

Once the map has been created, we make the map display by setting the center of the display using setCenter() This function takes an instance of google.maps.LatLng as its firstargument, the zoom level as its second argument (with 20 being the maximum zoom level),and the type of map as the third argument (optional) The API documentation states that thismethod must be called before adding any controls or overlays to the map

Trang 27

To make a hybrid map appear, we use G_HYBRID_MAP as the map type The default value forthe map type is G_NORMAL_MAP, while a satellite map can be specified using G_SATELLITE_MAP.

Note that you can also use the setMapType() method to change the map type

Next we can add controls to the map using the addControl() method By default, six different controls are available to be added, although it is possible to create custom controls

We will add MapTypeControl (allows you to switch between map, satellite, and hybrid),

LargeMapControl(a control with buttons to pan and zoom), ScaleControl (displays the map

scale), and OverviewMapControl (displays a mini-map in the corner at a lower zoom level) The

other available controls are SmallMapControl (the same as LargeMapControl but without zoom

slider) and SmallZoomControl (zoom buttons only)

Listing 13-14 shows the remainder of the loadMap() function, which creates the maps,adds controls, and finally centers on the Googleplex

Listing 13-14.Initializing the Map and Centering on the Googleplex

(BlogLocationManager.class.js)

this.map = new google.maps.Map2(this.container);

this.map.setCenter(new google.maps.LatLng(37.423111, -122.081783),

16, // zoom levelG_HYBRID_MAP); // map typethis.map.addControl(new google.maps.MapTypeControl());

this.map.addControl(new google.maps.ScaleControl());

this.map.addControl(new google.maps.LargeMapControl());

this.map.addControl(new google.maps.OverviewMapControl());

},Finally, we must create the unloadMap() function, which is called when the window unloadevent is fired To unload the map, we simply need to make a call to google.maps.Unload(), a

Google API function that cleans up internal data structures to release memory If this function

is not called, then browser memory leaks may occur (depending on the browser)

Listing 13-15 shows the code for unloadMap() as well as closing this initial version ofBlogLocationManager

Listing 13-15.Correctly Unloading Google Maps (BlogLocationManager.class.js)

unloadMap : function(){

google.maps.Unload();

}};

Loading BlogLocationManager

To use this class, we must now load and instantiate on the locationsAction() template We do

this by loading the class in the /templates/header.tpl file, as well as instantiating the class in

the /htdocs/js/scripts.js file

Listing 13-16 shows the changes we make to header.tpl This code assumes that if we’vechosen to load the maps (as we did by including maps=true when including header.tpl from

Trang 28

./templates/blogmanager/locations.tpl) and we’re in the blogmanager section, then we loadthe BlogLocationManager class.

Listing 13-16.Loading the BlogLocationManager JavaScript Class (header.tpl)

Listing 13-17.Instantiating the BlogLocationManager Class (locations.tpl)

{include file='header.tpl' section='blogmanager' maps=true}

Listing 13-18.Setting the Height of the Map Container (styles.css)

#location-manager { height : 400px; }

If you now log in to the web application and try to manage locations for an existing blogpost, you should see a map on your page, similar to that in Figure 13-3

Trang 29

Figure 13-3.Our first map being displayed using Google Maps

Managing Locations on the Map

The next step is to extend the location management page and JavaScript class to allow users

to enter the address they want to add to their map We will then perform a geocoder request to

find the coordinates for the entered address and add it to the map Additionally, we will use

Ajax in the background to save the location to the database for the active blog post

Once an address is displayed on the map, the user will have the option of dragging it to anew location (which will result in the new coordinates being saved via Ajax) or deleting it alto-

gether from the map

Handling Location Management Ajax Requests

We’ll first create a new action handler (once again in BlogmanagerController) to handle each

of the different possible Ajax requests, each of which will return JSON data to the requesting

script The four different actions we are going to handle are as follows:

• Get We use this action to return each of the locations saved in the database for the

cur-rent blog post Initially there will be no locations to return

Trang 30

• Add This is called to save a new location to the database We will write a new

form-processing class to aid with this Once a new location has successfully been saved to the database, we will return its ID as well as the coordinates and description When thelocation data is returned, we will add it to the Google map

• Delete This action is called to remove a location from the database We must also tell

the Google map to remove the location from its display in real time

• Move This action is used to update the coordinates of an existing location It will be

ini-tiated after the user drags and drops a location to a new point on the map

The New Location Form Processor

As mentioned earlier, to add new locations to the database, we must write a new form sor This class is almost identical to other form-processing classes we have written throughoutthis book, so it is just shown in Listing 13-19 We store it in a file called BlogPostLocation.php

proces-in the /proces-include/FormProcessor directory

Listing 13-19.Processing New Locations and Saving Them Accordingly (BlogPostLocation.php)

// set up the initial values for the new location

$this->location = new DatabaseObject_BlogPostLocation($post->getDb());

Ngày đăng: 12/08/2014, 13:21

TỪ KHÓA LIÊN QUAN