CHAPTER |
|
4 |
Advanced JavaScript Topics |
code
, name
, and location
properties) and assign the appropriate value to each property. All the information about a specific airport is stored in a single variable that contains multiple named values such as airport.code
, airport.location
, and so on.LatLng
object or the Marker
object, are implemented in the script you download from Google every time you include this script in your page. JavaScript knows nothing about LatLng
objects, or the Map
object for that matter, and this is why all map related objects are declared with the prefix google.maps
.obj
variable with this statement:[object Object]
. This is a string that JavaScript generates to describe an object. All variables provide a method called toString()
, and every time you attempt to print a variable, JavaScript calls this function internally to convert an object to a string. The generic description of the preceding example says that obj
is a variable of type Object
. This is all the information JavaScript has about the object. If you attempt to print a LatLng
object, you will get back a string with the location’s coordinates like this:LatLng
object is identified by its coordinates, and the toString()
method returns a meaningful string that identifies the object. Most objects do not lend themselves to a simple textual description, so their toString()
method returns a generic string. The Marker
object, for example, can’t be uniquely identified by a simple string; this object has many properties, but not a single property, or combination of properties, that make it unique, so its toString()
method returns a generic string to describe it.toString()
method will return the usual [object Object]
string, but your object has properties, which you can access using the dot notation:LAXAirport
array using the property names as index values:Code
and use its value to evaluate the expression. If no such value exists, then a run-time error will occur. Things will get really bad if a Code
variable exists: JavaScript will use its value to access a property of the LAXAirport
object!LAXAirport
variable is not a common array, but it can be accessed as if it were an array, only instead on an index value you use a string to identify the desired element. You can also add new properties to the LAXAirport
object using the same notation. The following statement adds a property of the LatLng
type, which is the location of the airport:Location
property is itself another object. Any property of an object variable can be another object, or even an array of objects. The Location
property is a nested object: It’s an object contained within another object, the LAXAirport
object, and you can nest properties in any depth.for … in
loop to iterate through the properties of an object:for … in
loop. Enter the following statements in a script and execute the script:Location
property). There’s no specific reason beyond demonstrating multiple methods of adding new properties to custom objects.LatLng
object? If you execute the following statements, you will see that it’s quite possible (and easy):dummy
property! Note that if you attempt to read the value of the dummy
property before it has been added to the location
object, you will get back the value “undefined.” This undefined value means that the property dummy
hasn’t been created yet. To find out whether an object supports a property before requesting its value, use the if
statement to compare the property to undefined
:!==
) be used with undefined
and null
comparisons, but the !=
operator works fine in most situations.LatLng
type have a property named dummy
(only the one variable to which you added the custom property).objInfo
property, which is a place to store custom information. If you want to extend the functionality of the LatLng
object, create a new custom object with the additional information and assign it to the objInfo
property of the original LatLng
object.str
variable:alert()
statements will display the string “NEW Los Angeles Airport”! Objects are structures in memory and the corresponding variables do not contain the object data. Instead, they contain a pointer to the memory, where the structure with the object’s data is stored. These variables are called reference variables because they hold a reference to their values, and not the actual values. The variables that hold simple values are called value variables. When you assign a value variable to another, the original variable’s value is copied. When you assign a reference variable to another, then the reference (memory address) to the object is copied, not the object.google.maps.Map
object, for example, exposes the setCenter()
method. When you call this method, you change the map’s center point, which is equivalent to scrolling the map from within your code. Objects in JavaScript are extremely flexible in that they can store anything, including functions. It’s possible to add a method to an object by adding a function to it, just as you would add a property. The method has a name and a code block that implements it.LAXAirport
object by adding a simple function that returns the airport’s full name (that is, its IATA code followed by its name). Let’s call this method getFullName()
and implement it with a single statement that returns the airport code followed by its name:getFullName()
method of the LAXAirport
variable, use a statement like the following:getFullName()
method is a good candidate for the object’s toString()
method, isn’t it? You can replace the default implementation of the toString()
method with a custom function by adding the toString()
method to your object. Change the name of the getFullName
function to toString
in the definition of the LAXAirport
object and you have a custom object that returns a meaningful representation of itself:toString()
method that returns a meaningful description of the object.Marker
object has two basic properties as you recall from the previous chapter, the title
and position
properties. The marker’s title
property should be set to the same string returned by the custom object’s toString()
method. The position
property of the same marker must be set to the custom object’s Location
property. Here’s the definition of the createMarker()
method:createMarker()
method doesn’t place a marker on the map; it returns a Marker
object that represents the specific airport. To place the marker returned by the createMarker()
method on the map, call the Marker
object’s setMap()
method, passing as an argument a reference to the appropriate Map
object:map
variable represents the map on your page, the last statement will place the marker on the map. Alternatively, you can modify the createMarker()
method so that it accepts a Map
object as an argument and places the marker it creates on the map:createMarker()
method, however, has a major drawback: It will place a new marker on the map, even if one exists already. You must somehow keep track of the marker that corresponds to each airport and not create a new marker, even if the application calls the createMarker()
method multiple times on the same object. The createMarker()
function must be modified as follows:markerExists
property is set to true
once the marker is created, and the comparison at the beginning of the function prevents the creation of a new marker for the specific variable if one exists already. If the current object, represented by the keyword this
, has a property by the name markerExists
already, then the function exists. It the object doesn’t have a property by that name, the script creates it and sets it to true
. When the createMarker()
function is called again, it won’t place another marker on the map.LatLng
type, variables of the Marker
type, and so on. How does one create custom types in JavaScript? Other OOP languages use classes. A class contains the definition of an object, its properties, and its methods, and you can create new variables based on this class. JavaScript doesn’t recognize classes. You can still create custom types with JavaScript using prototypes. In this book, you’re not going to build any prototypes, but a very short introduction to the topic is included for reasons of completeness.this
is a special one. The keyword this
in JavaScript refers to the owner of a function and it’s used frequently as a shorthand notation to the current object. When you create a new object with a function, this
refers to the newly created object. It actually refers to the object you’re about to create. When you use the keyword this
in a method, it represents the owner of the method: the object to which the function is applied as a method. Finally, when you use the same keyword in an event listener, it refers to the object that fired the event. The keyword this
is used in JavaScript very much like it’s used in the English language: It denotes something that is close at hand. At various parts of the program, the same keyword refers to totally different entities, and it’s not always obvious which object it represents. You can safely use it to refer to the custom objects you’re creating in your code and the object you associate with a listener in the addListener()
method. The topic of event listeners is discussed later in this chapter, and you will see shortly how the keyword this
is used to identify the object that fired the event.City
:this
, which represents the object described by the function. Here’s the definition of the City()
function with a few properties:City()
function, you don’t care about the actual property values, and that’s why they’re all set to undefined
. The function does not create a new object; it’s simply the definition of an object. To create a new object of the City
type, use the new
keyword, and then assign values to its properties as shown here:City
type, not a generic object. The City()
function is the object’s constructor and it’s executed every time you create a new object of the City type. You could initialize the properties to specific values rather than setting them to undefined
. These values, however, would be the same for all new objects of the City
type. Use this technique if you want to assign default values to some properties.City
type, it’s convenient to call the constructor passing the city’s data as arguments. Developers using the City
class should be able to pass the necessary parameters to the constructor of the new object, with a statement like this:City
custom type:capitals
object defined with the following statement:capitals
variable may be an object, but it looks and feels just like an associative array: an array whose elements can be accessed by a key value. You can view objects as associative arrays, if you wish, and use them in your code. The fact that JavaScript doesn’t have a special structure for associative arrays is of no importance because objects can be used just like associative arrays.capitals
object (or equivalently to an element of the capitals
associative array) need not be a single value. It can be another object with data about each state. Create an object with many properties, as in the following:states
associative array using the state name as key:majorCities
property could be an array with the state’s largest cities:majorCities
property is an array of custom objects! Do you want to store the major cities in a collection so you can access them by name? Almost trivial. Start by creating a collection with the cities, using city names as the keys:states
collection and add the state of California and the major cities as a property:states
array and through each city in the state. There’s only one state in the collection, but the same loop will work with as many states as it will find in the states
collection.states
variable shown earlier, generates the following output:CitiesAsObjects.html
sample page). Use this code as your starting point to add more properties to the individual objects and more states and cities in each state.onclick
attribute, to which you assign the name of the function that will handle the click
event of the specific button; the text box element provides the textchanged
attribute, to which you assign the name of the function that will handle the text as it’s being edited (by converting it to uppercase, or by rejecting non-numeric keystrokes).Map
object. The Map
object, which represents the map on your page, fires many of its own events as the user interacts with it, but you’ll never handle all the events. You select the ones you’re interested in and associate a listener to these events; all other events raised by the Map
object will go unnoticed. Such events are the click and right-click of the mouse buttons, the dragging of the map, and more.addListener()
method passing as arguments the name of the object that fires the event, the name of the event, and the name of a function that will handle the event. The following statement associates the mapClicked()
function with the click
event of the map:mapClicked()
function is executed automatically.event
argument, which contains information about the event. For the click event, this argument carries information about the location that was clicked. The following is the implementation of the mapClicked()
function that displays the coordinates of the location that was clicked in an alert box:MapClickEvent.html
sample page demonstrates how to register listeners with their events and is shown in Figure 4-2 as a fiddle. Figure 4-3 shows the same page when opened in Google Chrome.Marker
object. As you will see in Chapter 7, you program this event to display a window with additional information about the feature identified by the marker. This window looks like a speech bubble and it’s called InfoWindow. In Chapter 8, you learn about shapes, such as lines, polygons, and circles, that you can place on the map. When the user clicks a shape, an event is raised and you can program this event to display additional information about the selected feature, or perform an action. A common design pattern is to add a marker when the map is clicked and to remove the marker when it’s right-clicked.click
event fire the rightclick
event as well. The rightclick
is extremely important, because this is how you can implement context menus on your maps, a topic that is discussed in detail in Chapter 5. Placing all the commands that apply to the item being clicked on a right-click menu will help you unclutter your interface, as you would normally have to create buttons for these operations. In addition, you would have to change the visibility of these buttons, as not all actions are applicable at all times.removeListener()
method, which accepts an integer as an argument. This integer identifies the event handler and it’s returned by the addListener()
method. If you plan to remove a specific listener down the road, you must store the return value of the addListener()
method to a script variable, and then pass this value to the removeListener()
method.addListener()
method and save its return value:No Closures.html
page, which demonstrates the problem, and Closures 1.html
and Closures 2.html
, with two different solutions to the problem. It’s the same solution actually, just implemented with two different methods.Cities
array, and the script iterates this array and creates a marker for each array element. The following is the definition of the array that holds city data:Marker
objects is shown in Listing 4-1. In addition to creating the markers, it also adds a listener for the click
event. When each marker is clicked, the script displays a string with the selected city’s name.No closures.html
page in your browser and hover the pointer over the three markers, located on three cities in California. The correct title will appear for each marker. Now click a marker. While you’d expect to see an alert box with the name of the city you clicked, nothing will happen. To find out what went wrong, double-click the status bar of Internet Explorer, or open the Developer tools of Google Chrome (open the Chrome menu and select the “Developer tools” command from the Tools submenu). If you’re using Internet Explorer, you will see the error message shown in Figure 4-4.Cities[i]
is undefined and, as a result, the script can’t access the City
property of an undefined object. This is a hard-to-understand error message because the Cities
array has been used to set up the markers, and the markers have been placed successfully on the map. The problem isn’t with the Cities
array, but with the specific element you’re attempting to access. To understand what’s happening, change the listener’s code so that instead of the element Cities[i].City
, it displays the value of the variable i
. This time you will see an alert box showing the value 3. This value corresponds to the fourth element of the array, but there’s no such element in the array!i
. When the listener is executed, the script has gone through the loop and the loop’s counter variable has reached the value 3. This is how the loop ends (it keeps increasing its counter until it exceeds a maximum value). When the listener is executed, the value of i
is 3 and the code fails to access the element Cities[3]
.i
, and store it along with the definition of the function that implements the listener. This is where the closure comes in. The most important feature of a closure is that the inner function has access to variables of the outer function, even after the outer function has returned!Marker
object and an index value as arguments, and associates the Marker
object’s click
event with a listener.fnc()
function to add the listener:fnc()
function will actually be executed with different arguments each time. The arguments will be passed to the event listener, and the script will work as expected. For your convenience, Listing 4-2 shows the entire segment of the script that creates the three markers, places them on the map, and associates a listener with their click
event.Closures 1.html
page in your browser, you will see that it works as expected and each marker reacts to its own click
event. Closures are defined as functions that capture the state of their environment: the values of the variables at the moment they’re declared, and not the moment they’re executed. By forcing some code to be executed while adding the markers, you effectively force the script to evaluate the values of the variables and pass to the listener the actual values, not the variable names.Closures 2.html
page demonstrates a fancy technique of using closures, but this time without an external function. The fnc()
function can be defined in-place, as shown in Listing 4-3:index
and selectedMarker
) need not match the names of the variables with the actual values (variables i
and marker
). At the end of the function, you pass the values as arguments and these values are the index of the current marker and the Marker
object that represents the current marker.marker
variable and at each iteration stores a different Marker
object to this variable. Once the marker has been placed on the map, the variable is no longer needed and can be reused. However, in the click event’s listener of each marker, you must invoke a different alert box. The easy way out is to create an array and store your markers there. Each marker will be assigned its own listener for the click
event and you’re no longer required to handle the listeners of the markers in any special manner. The Without Closures.html
page displays the same three markers without closures because it stores the individual Marker
objects in an array. Here’s the loop that places the markers on the map:Marker
object’s objInfo
property and it’s passed to the alert()
function as an argument. The event listener is associated with an element of the markers array and there’s no interference between the markers because they are stored in different elements of the array.<div>
element definition:initialize()
and addMarkers()
functions, as well as the statements to call the two functions. The addMarkers()
function iterates through the Cities
array and adds a marker to the map for each city.3.135.246.245