SlideShare a Scribd company logo
1 of 296
Download to read offline
Pragmatic Ajax
       A Web 2.0 Primer

             Justin Gehtland
                Ben Galbraith
                   Dion Almaer




     The Pragmatic Bookshelf
Raleigh, North Carolina Dallas, Texas
P   r   a       g       m       a       t       i       c




                                   B       o       o       k       s       h       e   l   f




Many of the designations used by manufacturers and sellers to distinguish their products
are claimed as trademarks. Where those designations appear in this book, and The
Pragmatic Programmers, LLC was aware of a trademark claim, the designations have
been printed in initial capital letters or in all capitals. The Pragmatic Starter Kit, The
Pragmatic Programmer, Pragmatic Programming, Pragmatic Bookshelf and the linking g
device are trademarks of The Pragmatic Programmers, LLC.

Every precaution was taken in the preparation of this book. However, the publisher
assumes no responsibility for errors or omissions, or for damages that may result from
the use of information (including program listings) contained herein.

Our Pragmatic courses, workshops, and other products can help you and your team
create better software and have more fun. For more information, as well as the latest
Pragmatic titles, please visit us at

     http://www.pragmaticprogrammer.com




Copyright © 2006 The Pragmatic Programmers LLC.

All rights reserved.

No part of this publication may be reproduced, stored in a retrieval system, or transmit-
ted, in any form, or by any means, electronic, mechanical, photocopying, recording, or
otherwise, without the prior consent of the publisher.

Printed in the United States of America.


ISBN 0-9766940-8-5
Printed on acid-free paper with 85% recycled, 30% post-consumer content.
First printing, March 2006
Version: 2006-4-27
Contents
Acknowledgments                                                                                                   vii

1   Building Rich Internet Applications with Ajax                                                                  1
    1.1 A Tale in Three Acts . . . . . . . . . . . . . .                              .   .   .   .   .   .   .    2
    1.2 Google Maps: The Missing Spark . . . . . .                                    .   .   .   .   .   .   .    4
    1.3 What Is Ajax? . . . . . . . . . . . . . . . . . .                             .   .   .   .   .   .   .    5
    1.4 Whither Now? . . . . . . . . . . . . . . . . . .                              .   .   .   .   .   .   .    8

2   Creating Google Maps                                                                                           9
    2.1 Rocket Scientists? . . .      .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   10
    2.2 Your Own Google Maps          .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   11
    2.3 Creating Ajaxian Maps         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   16
    2.4 Conclusion . . . . . . .      .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   47

3   Ajax   in Action                                                                                              48
    3.1    Ajaxifying a Web Application           .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   48
    3.2    Ajax to the Rescue . . . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   48
    3.3    The Grubby Details . . . . .           .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   56
    3.4    Wrapping Up . . . . . . . . .          .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   59

4   Ajax   Explained                                                                                              60
    4.1    A Review of Client-Side JavaScript .                   .   .   .   .   .   .   .   .   .   .   .   .   61
    4.2    Manipulating the Web Page . . . . .                    .   .   .   .   .   .   .   .   .   .   .   .   67
    4.3    Retrieving Data . . . . . . . . . . . .                .   .   .   .   .   .   .   .   .   .   .   .   73
    4.4    Summary . . . . . . . . . . . . . . .                  .   .   .   .   .   .   .   .   .   .   .   .   76

5   Ajax   Frameworks                                                                                             77
    5.1    Frameworks, Toolkits, and Libraries                        .   .   .   .   .   .   .   .   .   .   .   77
    5.2    Remoting with the Dojo Toolkit . . . .                     .   .   .   .   .   .   .   .   .   .   .   82
    5.3    Remoting with the Prototype Library                        .   .   .   .   .   .   .   .   .   .   .   90
    5.4    Wrapping Up . . . . . . . . . . . . . .                    .   .   .   .   .   .   .   .   .   .   .   92
CONTENTS   v


6   Ajax UI, Part I                                                                                                  93
    6.1 Ajax and JavaScript for the UI . . . . . . . . . . . . . . .                                                 93
    6.2 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . .                                              121

7   Ajax   UI, Part II                                                    122
    7.1    Some Standard Usages . . . . . . . . . . . . . . . . . . . 122
    7.2    It Isn’t All Just Wine and Roses... . . . . . . . . . . . . . 137
    7.3    Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . 146

8   Debugging Ajax Applications                                                                                     147
    8.1 View Source . . . . . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   147
    8.2 DOM Inspectors . . . . .            .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   148
    8.3 JavaScript Debugging . .            .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   160
    8.4 Conclusion . . . . . . . .          .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   169

9   Degradable Ajax                                                   170
    9.1 What Is Degradable Ajax? . . . . . . . . . . . . . . . . . . 170
    9.2 Ensuring Degradable Ajax Applications . . . . . . . . . . 172
    9.3 Wrapping Up . . . . . . . . . . . . . . . . . . . . . . . . . 183

10 JSON and JSON-RPC                                                   184
   10.1 JSON-RPC . . . . . . . . . . . . . . . . . . . . . . . . . . . 187

11 Server-side Framework Integration                                   192
   11.1 Different Strategies for Integration . . . . . . . . . . . . . 193

12 Ajax    with PHP                                                                                                 195
   12.1    The PHP Frameworks       .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   195
   12.2    Working with Sajax .     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   196
   12.3    XOAD . . . . . . . . .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   204
   12.4    Wrapping Up . . . . .    .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   209

13 Ajax    with Rails                                                       210
   13.1    Ruby on Rails . . . . . . . . . . . . . . . . . . . . . . . . . 210
   13.2    Ajax Integration . . . . . . . . . . . . . . . . . . . . . . . . 214
   13.3    The Future of Ajax in Rails . . . . . . . . . . . . . . . . . 227

14 Proxy-Based Ajax with DWR                                            230
   14.1 DWR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231
   14.2 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . 245
CONTENTS   vi


15 ASP.NET and Atlas                                                      246
   15.1 BorgWorX . . . . . . . . . . . . . . . . . . . . . . . . . . . 247
   15.2 Atlas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249
   15.3 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . 258

16 Ajax   in the Future and Beyond                                                                            259
   16.1   Data Manipulation . . . . .     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   259
   16.2   UI Manipulation . . . . . .     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   263
   16.3   Predictions . . . . . . . . .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   275
   16.4   Conclusion . . . . . . . . .    .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   278
Acknowledgments
Writing a book is a lot like (we imagine) flying a spaceship too close
to a black hole. One second you’re thinking “Hey, there’s something
interesting over there” and a picosecond later, everything you know
and love has been sucked inside and crushed.
OK, that’s hyperbole, but the point is that books don’t write themselves.
More to the point, books aren’t even just written by the authors. It takes
the combined efforts of a lot of people to extract information from the
chaos. We’d like to hereby issue the following thanks.
To every single beta purchaser of the book and especially the ones who
sent in all those errata posts. You are a fantastic bunch, and we can’t
thank you enough for your belief in the project and your help in making
it a better book.
To the team at the Pragmatic Programmers (especially you, Dave): you
exhibited endless patience, forbearance, and wisdom during the pro-
cess.
Finally, to the authors of all the wonderful frameworks and tools we
highlight in this book: your work is inspiring and we hope that this
book helps shed just a little more light on the work you’ve done.
From Justin Gehtland
    To my coauthors: thanks for thinking of me.
     My colleagues are an endless font of inspiration and vexation, both
     of which help with the creative process. So, thanks to Stu Hal-
     loway, Glenn Vanderburg, Neal Ford, and Ted Neward, all of whom
     provided various amounts of both.
     I keep telling my family that one day I’ll write a book they’d like
     to read. At least this one has an interesting cover. Lisa, Zoe, and
     Gabe: thanks for putting up with my office hours.
A CKNOWLEDGMENTS   viii


From Ben Galbraith
    Thank you to my family, for all your patience while I spent late
    nights and early mornings working on this project. I love you.
    My sincere gratitude also goes to my publisher Dave Thomas (who
    patiently and gracefully watched this project go from early arrival
    to, well, somewhat less than early arrival) and my fellow authors,
    Justin Gehtland and Dion Almaer, who made many personal sac-
    rifices to get across the finish line.
    Finally, I thank all of my peers and colleagues who have taught
    me throughout the years. The patience and kindness of nearly
    everyone in our industry has always been an inspiration to me.
From Dion Almaer
    Ah, acknowledgments. This is the moment where you feel like you
    are at the podium and don’t want to forget anyone.
    Firstly, I would like to thank my fellow Ajaxians: Ben Galbraith,
    Justin Gehtland, Stu Halloway, Rob Sanheim, Michael Mahemoff,
    and the entire community that visits and contributes to ajax-
    ian.com. This book is really for you, the readers.
    Secondly, I would like to thank all of the great technical folk who I
    have had the pleasure of working with. This includes buddies from
    Adigio, the No Fluff Just Stuff tour, and the general blogosphere.
    You know who you are.
    Finally, I would like to thank my family, especially my wife, Emily,
    who lets me work crazy hours without putting me through guilt
    trips. You are my best friend, Em.
Chapter 1

                         Building Rich Internet
                         Applications with Ajax
This is a book about developing effective web applications. We’re not
going to dance around this issue. Underneath everything else, this
book is about XHTML, JavaScript, CSS, and standards that have been
around for almost a decade now. Not only do we admit this truth, we
embrace it. Just because these standards have been around for a while
doesn’t mean we can’t build something new and exciting out of them.
Technology, like Jello, takes a while to solidify into something tasty and
satisfying.
Ajax (and Web 2.0) represents the maturation of Internet standards into
a viable application development platform. The combination of stable
standards, better understanding, and a unifying vision amount to a
whole that is greater, by far, than the sum of its parts. With Ajax, you’ll
be able to achieve the double Holy Grail: feature-filled user interfaces
and a no-hassle, no-install deployment story.
It wasn’t long ago that Jesse James Garrett coined the term Ajax. When
he first released the term onto the public consciousness, it stood for
Asynchronous JavaScript And XML. It has since, like SOAP before it,
lost its acronym status and is just a word. However, it is an enormously
powerful word. With this single word, Jesse James was able to harness
an industry-wide trend toward richer, install-free web applications and
give it focus.
Naming a thing is powerful. In this case, it’s not powerful enough to
become a movement, though. A spark was still lacking. It was to be
A T ALE IN T HREE A CTS   2


provided by an entirely unlikely entity. What follows is the story of
one development team, that spark, and how it changed the way we
approach web software.


1.1 A Tale in Three Acts
Hector is a project manager for a web application development shop.
With a long history of Perl, CGI, ASP, Servlet, and JSP development
under his belt, Hector’s been around the block. For the last year his
team has been building a CRM application for a large Fortune 500 com-
pany with offices all over the world. The application used to be a green-
screen mainframe application; the company wants to take advantage of
the great reach of the Internet to deploy the application to every office.
Hector and his team focus a lot of their energy on the server side of
the application. They have been using one of the modern MVC frame-
works from the Java community to implement the business logic, a
high-performance persistence framework to access the database, and
messaging-based infrastructure to connect to other existing systems.

Yesterday
On the client side, Hector and his team have become masters of CSS.
The look of the pages bends to their will; when the customer wants
rounded corners, they get rounded corners. Rollover colors? That’s
easy. Multiple color schemes? No problem. In fact, Hector and his team
long ago reached a point where they weren’t really worried about the
user interface. See, the Web operates one way: it essentially distributes
static documents. When users want more data, they incur a complete
interface refresh. It isn’t optimal from an efficiency perspective, but it’s
how the Web works, and users have just learned to live with it.
Then, sometime a couple of weeks ago, Hector’s customer came to a
meeting. The customer was usually a polite, accommodating fellow. He
understood the Web, and he understood the restrictions he had to live
with to get the reach of the Internet. In fact, Hector had never seen him
get really angry. Until this meeting.
As soon as he walked in, the team knew something was up. He had his
laptop with him, and he never carried it. As he stormed into the room,
the team glanced around the table: what have we done? The customer
sat down at the table, fired up the laptop, and hammered away at the
keyboard for a minute. While he pounded the keys, he told the team,
A T ALE IN T HREE A CTS   3


“Last night, my wife and I were invited to a party at the CEO’s house.”
“Uh oh,” thought the team, “this can’t be good.”
“Well, I certainly jumped at the chance,” he continued. “I’ve never been
before. This project got me on his radar.” (“Double uh-oh,” thought
Hector.) “When I couldn’t figure out how to get there with my city map,
I went to the Internet. I found THIS!” He hissed the last word with
venom and scorn. He flipped the laptop around so the table could see
it. There, quietly couched in his browser window, was Google Maps.
“Why,” he said, through clenched teeth, “can’t I have this?”

Today
Since that meeting, Hector and his team have been rethinking the user
interface. Hector went out to learn how Google could have completely
ignored conventional wisdom and generated such a thing. He came
across an article by Jesse James Garrett describing this thing called
Ajax. He has been digging since then, learning everything he can about
this new way of making Internet applications.
The team has begun reimplementing the UI. They’re using JavaScript
and DHTML techniques to provide a more dynamic experience. Most
of all, they’ve begun taking advantage of a useful object available in
modern browsers called XMLHttpRequest (XHR for short). This handy
little guy lets Hector and his team request and receive fresh data from
the server without reloading everything in the page.
In other words, Hector spearheaded a move from Web 1.0 to Web 2.0.
And his customer is happy again.

Tomorrow
So what comes next for Hector? His team is learning a bunch about
JavaScript, XHTML, and even more about CSS than it ever knew before.
The team is really excited about the results: the user experience is just
like any other application now, except the team doesn’t have to manage
an installer as well as the application itself. But they’ve realized that
there’s a downside to all this.
Now, they are writing a ton of code in JavaScript. It turns out that all
this page manipulation and XHR access requires a lot of real, honest-
to-goodness code. And even though JavaScript looks a lot like Java,
they’ve discovered that it really is a different beast. And now they have
two codebases to manage, test, and maintain.
G OOGLE M APS : T HE M ISSING S PARK   4


So Hector is off to find out how to solve these problems. And what
he will see is that most web application development frameworks are
rapidly incorporating Ajax tools into their own suites. Soon, Hector
and his team will be able to leverage Tapestry components, Spring tag
libraries, ASP.NET widgets, Rails helpers, and PHP libraries to take
advantage of Ajax without having to incorporate a second way of work-
ing. The (near) future of Ajax development is total, invisible integration.
And this is exactly what Hector needs.


1.2 Google Maps: The Missing Spark
Google Maps (http://maps.google.com) really ignited the Ajax fire. And
Google was just about the most unlikely candidate to do it. Think
about what made Google an overnight sensation in the first place: bet-
ter search results and the world’s most minimal UI. It was a white page,
with a text box and a button in the middle of it. It doesn’t get any more
minimal than that. If Google had had a soundtrack, it would have been
written by Philip Glass.
When it became obvious that Google was going to enter the online map-
ping space, we all expected something similar: a straightforward, unin-
trusive approach to viewing maps. And this is what we got; just not
the way we expected. Google, through the clever use of XHR callbacks,
provided the first in-page scrollable map. If you wanted to look at the
next grid of map panels, Google went off and retrieved them and just
slid the old ones out of the way. No messy page refresh; no reloading of
a bunch of unchanged text. Particularly, no waiting around for a bunch
of ads to refresh. It was just a map, the way a map ought to work.
Then we clicked on a push pin and got the info bubble. With live text in
it. And a drop shadow. And that was the end of an era. We’ve been told
the same story that you just lived through with Hector again and again.
Somebody’s boss or customer or colleague sees Google Maps and says,
“Why not me?”
As programmers, too, there’s another reaction: “I wish I could work on
that kind of application.” There’s an impression out there that Google
Maps, and applications like it, are rocket science and that it takes a
special kind of team, and a special kind of developer, to make them
happen. This book, if nothing else, will lay to rest that idea. As we’ll
demonstrate in Chapter 2, Creating Google Maps, on page 9, making
web pages sing and dance isn’t all that challenging once you know what
W HAT I S A JAX ?   5


tools are available. It becomes even more impressive once you discover
that Google Maps isn’t really proper Ajax; it doesn’t take advantage of
any of the modern asynchronous callback technology and is really just
dynamic HTML trickery.


1.3 What Is Ajax?
Ajax is a hard beast to distill into a one-liner. The reason it is so hard
is because it has two sides to it:
   • Ajax can be viewed as a set of technologies.
   • Ajax can be viewed as an architecture.

Ajax: Asynchronous JavaScript and XML
The name Ajax came from the bundling of its enabling technologies:
an asynchronous communication channel between the browser and
server, JavaScript, and XML. When it was defined, it was envisioned
as the following:
   • Standards-based presentation using XHTML and CSS
   • Dynamic display and interaction using the browser’s Document
     Object Model (DOM)
   • Data interchange and manipulation using XML and XSLT
   • Asynchronous data retrieval using XMLHttpRequest or XMLHTTP (from
     Microsoft)
   • JavaScript binding everything together
Although it is common to develop using these enabling technologies, it
can quickly become more trouble than reward. As we go through the
book, we will show you how you can do the following:
   • Incorporate Ajaxian techniques that do not use formal XML for
     data transport
   • Bypass the DOM APIs themselves for manipulating the in-memory
     page model
   • Use synchronous calls to the server, which can be powerful but is
     also dangerous
   • Abstract away the complexity of XMLHttpRequest
It is for these reasons that the more important definition for Ajax is...
W HAT I S A JAX ?   6


Ajax: The Architecture
The exciting evolution that is Ajax is in how you architect web applica-
tions. Let’s look first at the conventional web architecture:
  1. Define a page for every event in the application: view items, pur-
     chase items, check out, and so on.
  2. Each event, or action, returns a full page back to the browser.
  3. That page is rendered to the user.
This seems natural to us now. It made sense at the beginning of the
Web, as the Web wasn’t really about applications. The Web started
off as more of a document repository; it was a world in which you
could simply link between documents in an ad hoc way. It was about
document and data sharing, not interactivity in any meaningful sense.
Picture a rich desktop application for a moment. Imagine what you
would think if, on every click, all of the components on the application
screen redrew from scratch. Seems a little nuts, doesn’t it? On the
Web, that was the world we inhabited until Ajax came along.
Ajax is a new architecture. The important parts of this architecture are:
   • Small server-side events: Now components in a web application
     can make small requests back to a server, get some information,
     and tweak the page that is viewed by changing the DOM. No full
     page refresh.
   • Asynchronous: Requests posted back to the server don’t cause the
     browser to block. The user can continue to use other parts of the
     application, and the UI can be updated to alert the user that a
     request is taking place.
   • onAnything: We can interact with the server based on almost any-
     thing the user does. Modern browsers trap most of the same user
     events as the operating system: mouseovers, mouse clicks, key-
     presses, etc. Any user event can cause an asynchronous request.
In Figure 1.1, on the next page, we illustrate the new life cycle of an
Ajax page.
  1. The user makes an initial request against a given URL.
  2. The server returns the original HTML page.
  3. The browser renders the page as in-memory DOM tree.
W HAT I S A JAX ?   7


                                   R       e   n           d                   e           r   e       d               i   n       B       r           o           w       s   e       r                                                           R   u   n   n   i   n   g       o               n                           S                   e           r       v   e   r




                                                                                                   H       T       M           L




                                                                                                                                                                                                                           1                   2




                                                                                                               3
                               A       D                                                                                                                                                   D   Y
                       H   E




                                                                                                                                                                                                                                                                                       s       c           r       i   p                   t   s           /




                                                                                                                                                                                                                                                                               s           e           r       v           l       e           t       s           /




                                                                                                                                                                                                                           4




                                                                       N           K                                                           D                                                       D
   T   I   T   L   E                                   L           I                                                                                       I   V               B   O                           I   V
                                                                                                                                                                                                                                                                                           p           a           g                   e           s




                                                   6




                                                                                                                                               D                                                   S       P   A       N       S   P   A   N
                                                                                                                                                           I   V




                                                                                                                                                                                                                                               5




                                                   S           P           A           N                                               S           P       A           N




                                                                                                                                           Figure 1.1: Ajax Page Lifecycle



  4. Some user activity causes an asynchronous request to another
     URL, leaving the existing DOM tree untouched.
  5. The browser returns data to a callback function inside the existing
     page.
  6. The browser parses the result and updates the in-memory DOM
     with the new data. This is reflected on the screen to the user (the
     page is redrawn but not “refreshed”).
This all sounds great, doesn’t it? With this change we have to be care-
ful, though. One of the greatest things about the Web is that anybody
can use it. Having simple semantics helps that happen. If we go over-
board, we might begin surprising the users with new UI abstractions.
This is a common complaint with Flash UIs, where users are confronted
with new symbols, metaphors, and required actions to achieve useful
results. Usability is an important topic that we will delve into in Chap-
ter 7, Ajax UI, Part II , on page 122.

Ajax: The Future
Where is Ajax going? What is the future going to hold? This is a vital
question, because Ajax is one of those amorphous terms that seems to
change with the context. Ajax itself is a unifying term for describing
a collection of technologies. We believe that the term itself, as unify-
W HITHER N OW ?   8


ing and rallying as it has been, is likely to disappear from the public
consciousness within the next couple of years.
That’s because the technologies you will learn about in this book will
eventually become the substrate of your favorite web application devel-
opment platform. Instead of representing this brave new world of shiny
gadgets and nifty tricks, it will just be how web apps work. Does this
mean that this book is unimportant? Far from it. You need to under-
stand how this works now to get it done, and you’ll need to understand
it in the future to debug your applications. But you probably won’t
think of those apps as Ajax, just as Web apps. And that’s a good thing.


1.4 Whither Now?
The rest of this book will introduce you to the breadth of the Ajax move-
ment. We’ll walk through the conversion of an application to this new
style and provide deep coverage of the enabling technologies behind
Ajax. We’ll introduce you to commonly available toolsets and frame-
works that make seemingly advanced effects as simple as a single line
of code. You’ll get to see what your favorite development platforms are
doing to take advantage of, and integrate with, this new style of devel-
opment.
Most important, we’ll talk a lot about how to use Ajax effectively, prag-
matically, even. That’s because the only thing worse than being left
behind when the train leaves the station is getting on the wrong train.
We intend this book to be a guide through a new and rapidly evolving
landscape. We want to help you find out how, and even if, Ajax can
help your projects. We’re not trying to sell you anything (except this
book). But we believe that Ajax represents a major event, and we want
to be there to help you make the best of it.
But let’s start with the spark that ignited the fire: Google Maps.
Chapter 2

                                Creating Google Maps
For many of us, Google Maps (http://maps.google.com) ignited the Ajax
revolution. While Ajaxian techniques had been creeping into main-
stream websites long before Google Maps, nothing in recent memory
presented commodity browsers with such a visually impressive experi-
ence. Google Maps showed the world that a wide world of potential lay
hidden in the technologies we thought we understood so well.
As we said in Chapter 1, Ajax was initially defined as the intersection
of the XMLHttpRequest object and the usage of XML to update a DOM
tree. However, the current definition of Ajax (and Web 2.0) spans much
more. This chapter demonstrates the underpinnings of Google Maps
and how modern browser-based applications can use nothing but stan-
dard HTML and JavaScript to achieve entirely new kinds of web apps.
The purpose of this chapter is to lay bare the techniques that Google
used to wow us all with Google Maps. What we’ll discover here is fas-
cinating and important; it also might be more than you want to bite off
right now. If so, don’t worry about skipping ahead to the rest of the
book and coming back here later; we won’t mind.
This chapter contains a lot of code. It’s all available online, so you
can download the archives containing all the book’s source.1 Alterna-
tively, if you’re reading the PDF version of this book, just click a link to
get to the file. However, if the file you’re fetching contains HTML, it’ll
probably get rendered by your browser. This is good if you want to see
the running application. If instead you want to see the code, use your
browser’s View Source option.

   1 From http://pragmaticprogrammer.com/titles/ajax/code.html
R OCKET S CIENTISTS ?   10


2.1 Rocket Scientists?
Shortly after Google Maps launched, entrenched commercial interests
who relied upon the staidness of standard HTML-based web interfaces
to make money were quick to claim that mainstream HTML developers
need not attempt to create web interfaces like Google Maps. The CEO
of Macromedia, maker of the popular Flash browser plug-in, stated
in at least one interview that such non-Flash web interfaces required
the skills of “rocket scientists.” (Ironically, when Macromedia finally
produced a clone of Google Maps in Flash four or five months later, it
failed to function on the two Mac laptops we used to try it out—actually
locking up the browser. Google Maps works just fine on both machines.
We’re actually not anti-Flash; we just found it ironic, that’s all.)
Such statements have added to the general impression many develop-
ers have that creating something like Google Maps is just, well, hard.
In fact, some developers have even felt a little fear and intimidation—
fear that someday soon, they’ll be asked to create something like Google
Maps!
Certainly many of us who have been writing HTML for years might like
to believe that it took a team of rocket scientists to produce a litany of
innovations supporting the technologies behind the Google Maps inter-
face, if nothing else to provide an excuse as to why we haven’t been
writing apps like that all this time. However, we believe all this busi-
ness about rocket science and intimidation is a bit exaggerated.
In fact, after spending ten minutes examining Google Maps a bit deeper,
we realized that, far from being the product of rocket scientists, the
Google Maps interface is actually fairly straightforward to implement.
Perhaps, some might say, easy. Not “same-amount-of-effort-as-a-PHP-
web-form” easy, but we were able to implement something a great deal
like it in about two hours. And this wasn’t just any two hours, mind
you; it was two hours of sitting in a crowded convention center during
a technical conference whilst being interrupted by our friends every few
minutes.
So while there’s no doubt Google has recently hired some of the most
visible computer scientists—perhaps the closest examples of rocket
scientist—like brainpower in our industry, such as Adam Bosworth
(famed Microsoft innovator), Joshua Bloch (famed Java innovator at
Sun Microsystems), and Vint Cerf (famed Internet innovator)—we’re
pretty sure they weren’t involved in the creation of the Google Maps
Y OUR O WN G OOGLE M APS   11




    The Real Rocket Science
    OK, OK we admit—it isn’t easy to create something like Google
    Maps. The geocoding features behind the scenes that map
    addresses to locations on a map, that normalize a maps fea-
    tures against satellite imagery to such an amazing degree that
    they can be overlaid on top of each other and look relatively
    accurate, and the plotting of routes from Point A to Point B are
    all incredibly nontrivial.
    However, we maintain that it’s not the geocoding features
    of Google Maps that is particularly innovative or impressive.
    MapQuest and other software packages have been doing this
    kind of work for years. No, what’s impressive about Google
    Maps is the web interface on top of the geocoding engine.
    And it’s that interface that we find easy, not the geocoding
    under the covers.
    As our good friend Glenn Vanderburg says, though: “Techni-
    cally it’s easy, but the conception of this kind of interface is
    the really amazing part, just having the idea and then real-
    izing that it could be done. So many things are simple once
    you’ve seen that they’re possible.” The take-home lesson is that
    Google Maps shows that once you have conceived of your
    next great UI idea, you can take comfort in knowing that the
    technical solution to implementing it might not be so daunting.




interface. (We should say, though, that we stand in awe of Lars Ras-
mussen and his team for being the brains and fingers behind Google
Maps.) The reality is if we can create an interface like Google Maps in
a couple of hours, imagine what a few capable web developers could do
in a few weeks or a month.


2.2 Your Own Google Maps
In fact, we’ll spare you from putting your imagination to the test. Let
us show you firsthand how you can create your own version of Google
Maps. In the next few pages, we’ll walk you through the creation of
Ajaxian Maps, our own derivative of the big GM. We’ll start out by
explaining how the Google Maps user interface works.
Y OUR O WN G OOGLE M APS   12




                       Figure 2.1: Google Maps



Google Maps Deconstructed
We’re going to break down the elements of Google Maps one by one.
Let’s start out with the most dramatic feature: the big scrolling map,
the heart of the application.


The Map

As you know, the map works by allowing you to interactively move the
map by dragging the map using the mouse. We’ve seen mouse dragging
in browsers for years, but the impressive bit is that the scrolling map
is massive in size, can have the zoom level changed and so forth. How
do they do that?
Of course, the browser could never fit such a large map in memory at
once. For example, a street-level map of the entire world would prob-
Y OUR O WN G OOGLE M APS   13




    More Than A Million Pixels
    We say in “The Map” section that a street-level map of the
    world would be about a million square pixels. Actually, that
    number’s a wild underestimate. At Google’s highest level of
    magnification, a square mile consumes about 7,700,000 pixels.
    The Earth is estimated to contain 200,000,000 square miles, but
    only 30% of that is land, so let’s reduce the number to 60,000,000
    square miles.
    Multiplying the number of pixels by the number of square miles
    in the Earth produces the mind boggling number of 462 million
    million pixels, which at 16.7 million colors (the color depth of
    any modern home computer) would consume at least three
    times that amount of memory in bytes. Of course, most image
    viewing programs have some sort of paged memory subsystem
    that views a portion of the image at any one time, but you get
    the idea....




ably be about a million pixels square. How much memory would it
take to display that map? For the sake of conversation, let’s assume
that the map is displayed with just 256 colors, meaning each pixel
would consume just 1 byte of memory. Such a map would require
1,000,000,000,000 bytes of memory, or roughly 1 terabyte (1000 giga-
bytes) of RAM. So, simply displaying an <img> element just isn’t going
to work.
What the Googlers do to work around the paltry amount of memory our
desktop PCs have is split up the map into various tiles. These tiles are
laid out contiguously to form one cohesive image. Figure 2.2, on the
next page, shows an example of these tiles. While the size of these tiles
has changed, the current size is 250 pixels square.
The tiles themselves are all laid out within a single HTML div element,
and this div element is contained within another div; we’ll call these
two divs the inner and outer divs, respectively.
We mentioned just a moment ago that the browser couldn’t fit the entire
map image in memory. Of course, dividing a single map into an arbi-
trary number of tiles and then displaying all those tiles at once would
consume an equal amount of memory as the entire image. To compen-
sate for memory limitations, Google Maps virtualizes the grid of tiles
Y OUR O WN G OOGLE M APS   14




                     Figure 2.2: Google Maps Tiles



in memory and displays only the set of tiles that the user can see, in
addition to a few additional tiles outside of the viewing area to keep the
scrolling smooth.
If this whole grid virtualization mishmash sounds a little complex, don’t
worry; it’s fairly straightforward, though it is the most complicated bit
of the UI.


Zoom Level

Another key feature of Google Maps is the ability to zoom in and out,
enlarging or reducing the size of the map, which lets you get a view
of the entire world at one moment and a view of your street the next.
This is actually the simplest of the features to implement. Changing
the zoom level just changes the size of the tile grid in memory as well
as the URLs of the tile images that are requested.
For example, the URL to one of the tiles in Figure 2.2 is as follows:
http://mt.google.com/mt?v=w2.5&n=404&x=4825&y=6150&zoom=3

By changing the value of the zoom parameter to another value, such as
1, you can retrieve a tile at a different zoom level. In practice, it’s not
quite that simple because the grid coordinates change rather a great
deal with each zoom level and they often become invalid.
Y OUR O WN G OOGLE M APS               15




          Figure 2.3: The Google Maps Push Pin and Dialog



How do they get the zoom level to constantly hover over the map in
a constant position? The zoom level widget is an image embedded in
the outer div, and makes use of transparency to blend in with the map
image.


Push Pins and Dialogs

Other neat-o features are the push pins and dialogs that appear after
a search. Figure 2.3 shows these elements. These are especially cool
because they both include rounded edges and shadows that make them
blend in with the background map in a sophisticated fashion.
We said the zoom level was the easiest feature, and frankly, we were
probably wrong. This is ridiculously easy. The push pins and dialogs
are simply a PNG image. The PNG image format is supported by the
major browsers and supports a nice feature called alpha transparency.       alpha transparency

Alpha transparency allows for more than just the simple transparency
that GIF images support; it allows a pixel to be one of 254 different
values between fully transparent and fully opaque, and it’s this gradient
transparency support that allows the push pins and dialog to use a
shadow that blends in with the map.
C REATING A JAXIAN M APS   16


Showing these features is simply a matter of positioning images in the
inner div at an absolute position.


Feature Review

There are other features, of course. But we’ll stick to the set of features
we’ve enumerated; we think these represent the vast majority of the
“ooh, ahh” factors. In review, they were as follows:
   • The scrolling map: This is implemented as an outer div containing
     an inner div. Mouse listeners allow the inner div to be moved
     within the confines of the outer div. Tiles are displayed as img
     elements inside the inner div, but only those tiles necessary to
     display the viewing area and a buffer area around it are present
     in the inner div.
   • The zoom level: This is an image embedded in the outer div. When
     clicked, it changes the size of the grid representing the tiles and
     changes the URL used to request the tiles.
   • The push pins and dialogs: These are PNG images with alpha
     transparency, placed in absolute positions within the inner div.
Now that we’ve deconstructed Google Maps a bit, let’s set about imple-
menting it.


2.3 Creating Ajaxian Maps
Because Ajaxian Maps won’t bother with all of that geocoding mumbo
jumbo, all of our heavy lifting will be in JavaScript. However, we will
use Java to provide some server features and a few image manipulation
tasks.

IE 6, Firefox 1.x, and Safari 2.x Only
We’ve tested this version of Ajaxian Maps in the three major browsers
but haven’t bothered with older versions and more obscure browsers
(sorry, Opera users). It should work on older platforms, but without
testing, we can’t be sure we’ve caught everything.

Step 1: Create a Map
The first step in displaying a map is, err, creating it. While we could
simply steal the wonderful map that Google Maps uses, Google might
C REATING A JAXIAN M APS   17


          not appreciate that. So, we’ll go ahead and use a map that is explicitly
          open source. The Batik project (http://xml.apache.org/batik), an open-
          source Java-based SVG renderer, comes with an SVG map of Spain.
          We’ll use that.
          Because most browsers don’t provide native support for SVG, we’ll need
          to convert the map to a bitmap-based format. Fortunately, Batik can
          do that for us. One of the nice features of SVG is that it can scale to
          arbitrary sizes, so we could conceivably create a huge image for our
          map. However, creating truly huge images is a little tricky; because
          of memory limitations, we’d have to render portions of the SVG image,
          generate our tiles over the portions, and have some sort of scheme for
          unifying everything together. To keep this chapter simple, we’ll just
          limit our map to 2,000 pixels in width and 1,400 pixels in height. In
          order to implement zooming, we’ll also generate a smaller image that
          represents a view of the map in a zoomed-out mode.
          The following code excerpt shows how to use Batik to convert the map
          of Spain into both a 2000x1400 pixel JPG file and a 1500x1050 pixel
          JPG file:
File 31   package com.ajaxian.amaps;


          import org.apache.batik.apps.rasterizer.DestinationType;
          import org.apache.batik.apps.rasterizer.SVGConverter;


          import java.io.File;


          public class SVGSlicer {
              private static final String BASE_DIR = "resources/";


              public static void main(String[] args) throws Exception {
                  SVGConverter converter = new SVGConverter();


                  // width in pixels; height auto-calculated
                  converter.setWidth(2000);
                  converter.setSources(new String[] { BASE_DIR + "svg/mapSpain.svg" });
                  converter.setDst(new File(BASE_DIR + "tiles/mapSpain.jpg"));
                  converter.setDestinationType(DestinationType.JPEG);
                  converter.execute();


                  converter.setWidth(1500);
                  converter.setDst(new File(BASE_DIR + "tiles/mapSpain-smaller.jpg"));
                  converter.execute();
              }
          }

          To compile the code, you’ll need to put the Batik JARs in your classpath
C REATING A JAXIAN M APS   18




                           Figure 2.4: Batik’s SVG Spain Map



          (everything in BATIK_HOME and BATIK_HOME/lib) and place the source code
          in the following directory hierarchy: com/ajaxian/amaps. Figure 2.4
          shows what either map JPG file should look like. You can also replace
          the value of the BASE_DIR variable with whatever is most convenient for
          you.

          Step 2: Create the Tiles
          Now that we have a map at two different zoom levels, we need to slice
          it up into tiles. This is pretty easy with the nice image manipulation
          libraries available in many programming languages. We’ll demonstrate
          how to do that with Java here:
File 30   package com.ajaxian.amaps;


          import org.apache.batik.apps.rasterizer.DestinationType;
          import org.apache.batik.apps.rasterizer.SVGConverter;


          import javax.imageio.ImageIO;
          import java.io.File;
          import java.awt.*;
C REATING A JAXIAN M APS   19


          import java.awt.image.BufferedImage;


          public class ImageTiler {
                   private static final String BASE_DIR = "resources/";
                   private static final int TILE_WIDTH = 100;
                   private static final int TILE_HEIGHT = 100;


                   public static void main(String[] args) throws Exception {
                        // create the tiles
                        String[][] sources = { { "tiles/mapSpain.jpg", "0" },
                                  {"tiles/mapSpain-smaller.jpg", "1"} };
                        for (int i = 0; i < sources.length; i++) {
                             String[] source = sources[i];
                             BufferedImage bi = ImageIO.read(new File(BASE_DIR + source[0]));
                             int columns = bi.getWidth() / TILE_WIDTH;
                             int rows = bi.getHeight() / TILE_HEIGHT;
                             for (int x = 0; x < columns; x++) {
                                  for (int y = 0; y < rows; y++) {
                                       BufferedImage img = new BufferedImage(TILE_WIDTH, TILE_HEIGHT,
                                              bi.getType());
                                       Graphics2D newGraphics = (Graphics2D) img.getGraphics();
                                       newGraphics.drawImage(bi, 0, 0, TILE_WIDTH, TILE_HEIGHT,
                                              TILE_WIDTH * x, TILE_HEIGHT * y,
                                              TILE_WIDTH * x + TILE_WIDTH,
                                              TILE_HEIGHT * y + TILE_HEIGHT,
                                              null);
                                       ImageIO.write(img, "JPG", new File(BASE_DIR + "tiles/" +
                                              "x" + x + "y" + y + "z" + source[1] + ".jpg"));
                                  }
                             }
                        }
                   }
          }

          Note that to make things interesting, we made our tile size a bit smaller
          than Google Maps: 100 pixels square. We chose x0y0z0.jpg as the nam-
          ing convention for the tiles, where the zeros are replaced with the x
          and y grid coordinates (0-based) and the zoom level (0 or 1; 0 is for the
          bigger of the two maps).

          Step 3: Creating the Inner and Outer Divs
          Now that we have the image tiles, we can start building our map UI.
          We’ll start with a simple web page, shown here:
File 32   Line 1       <html>
               -            <head>
               -                 <title>Ajaxian Maps</title>
               -                 <style type="text/css">
              5                       h1 {
C REATING A JAXIAN M APS   20




                                         Figure 2.5: Humble Beginnings



             -                             font: 20pt sans-serif;
             -                       }
             -                       #outerDiv {
             -                             height: 600px;
            10                             width: 800px;
             -                             border: 1px solid black;
             -                             position: relative;
             -                             overflow: hidden;
             -                       }
            15                 </style>
             -            </head>
             -            <body>
             -                 <p>
             -                       <h1>Ajaxian Maps</h1>
            20                 </p>
             -                 <div id="outerDiv">
             -                 </div>
             -            </body>
             -     </html>

          Figure 2.5 show this page. Pretty simple so far. Let’s get to the good
          stuff. The div on line 21 will become what we’ve called the outer div.
          The outer div is the visible window into the tiles and will be entirely
          contained in the visible space within the browser. The inner div, on
          the other hand, will contain all the tiles and be much larger than the
          available visible space. Let’s start out by giving it an inner div with
          some simple content:
File 33   <html>
                 <head>
                    <title>Ajaxian Maps</title>
                    <style type="text/css">
                           h1 {
                                  font: 20pt sans-serif;
                           }
C REATING A JAXIAN M APS   21


                          #outerDiv {
                                 height: 600px;
                                 width: 800px;
                                 border: 1px solid black;
                                 position: relative;
                                 overflow: hidden;
                          }


                          #innerDiv {
                                 position: relative;
                                 left: 0px;
                                 top: 0px;
                          }
                    </style>
              </head>
              <body>
                    <p>
                          <h1>Ajaxian Maps</h1>
                    </p>
                    <div id="outerDiv">
                          <div id="innerDiv">
                                 The rain in Spain falls mainly in the plains.
                          </div>
                    </div>
              </body>
          </html>

          Now we need to make the inner div large enough to contain all of the
          image tiles. We could just set a style on the inner div to make it some
          arbitrary size, as in <div style="width: 2000px; height: 1400px">, but
          we’ll do this via JavaScript. Why? Well, because we’ll implement the
          ability to change zoom levels a little later, we know we’ll have to change
          the size of the inner div dynamically anyway, so we might as well start
          out that way. We’ll use an onload JavaScript handler to initialize the
          size of the inner div once we load the page. Check out the code:
File 34   <html>
              <head>
                    <title>Ajaxian Maps</title>
                    <style type="text/css">
                          h1 {
                                 font: 20pt sans-serif;
                          }
                          #outerDiv {
                                 height: 600px;
                                 width: 800px;
                                 border: 1px solid black;
                                 position: relative;
                                 overflow: hidden;
                          }
C REATING A JAXIAN M APS   22



                          #innerDiv {
                              position: relative;
                              left: 0px;
                              top: 0px;
                          }
                    </style>
                    <script type="text/javascript">
                          function init() {
                              setInnerDivSize( ' 2000px ' , ' 1400px ' )
                          }


                          function setInnerDivSize(width, height) {
                              var innerDiv = document.getElementById("innerDiv")
                              innerDiv.style.width = width
                              innerDiv.style.height = height
                          }
                    </script>
              </head>
              <body onload="init()">
                    <p>
                          <h1>Ajaxian Maps</h1>
                    </p>
                    <div id="outerDiv">
                          <div id="innerDiv">
                              The rain in Spain falls mainly in the plains.
                          </div>
                    </div>
              </body>
          </html>

          OK, now we’ve got an inner div big enough to display the tiles for the
          largest of our two maps. Now we need to add the dragging functionality.

          Step 4: Dragging the Map
          We’ll implement dragging using three different mouse event listeners.
          When the user clicks the mouse in the map area, we’ll use a listener to
          indicate that a drag operation has started. Now, if the user moves the
          mouse, we’ll use a listener to move the inner div along with the user’s
          mouse movements to create the dragging effect. Finally, we’ll use a
          listener to turn off the dragging operation when the mouse is released.
          The following code demonstrates how we implemented the listeners:
File 35   // used to control moving the map div
          var dragging = false;
          var top;
          var left;
          var dragStartTop;
          var dragStartLeft;
C REATING A JAXIAN M APS   23



function init() {
    // make inner div big enough to display the map
    setInnerDivSize( ' 2000px ' , ' 1400px ' );


    // wire up the mouse listeners to do dragging
    var outerDiv = document.getElementById("outerDiv");
    outerDiv.onmousedown = startMove;
    outerDiv.onmousemove = processMove;
    outerDiv.onmouseup = stopMove;


    // necessary to enable dragging on IE
    outerDiv.ondragstart = function() { return false; }
}


function startMove(event) {
    // necessary for IE
    if (!event) event = window.event;


    dragStartLeft = event.clientX;
    dragStartTop = event.clientY;
    var innerDiv = document.getElementById("innerDiv");
    innerDiv.style.cursor = "-moz-grab";


    top = stripPx(innerDiv.style.top);
    left = stripPx(innerDiv.style.left);


    dragging = true;
    return false;
}


function processMove(event) {
    if (!event) event = window.event;        // for IE
    var innerDiv = document.getElementById("innerDiv");
    if (dragging) {
        innerDiv.style.top = top + (event.clientY - dragStartTop);
        innerDiv.style.left = left + (event.clientX - dragStartLeft);
    }
}


function stopMove() {
    var innerDiv = document.getElementById("innerDiv");
    innerDiv.style.cursor = "";
    dragging = false;
}


function stripPx(value) {
    if (value == "") return 0;
    return parseFloat(value.substring(0, value.length - 2));
}
C REATING A JAXIAN M APS   24


          If you run the code at this point, you’ll now be able to drag that inner
          <div> around.

          Step 5: Displaying the Map Tiles
          The next step requires us to populate our inner div with the map tiles.
          Our approach to this will be fairly simple. The scrolling map effect
          is achieved by moving an inner div inside of an outer div; therefore,
          the tiles we need to display are calculated by determining the current
          position of the inner div relative to the outer div and then working out
          which tiles are visible in the portion of the inner div that is visible. We’ll
          then add those tiles to the inner div.
          It turns out implementing this behavior is not terribly difficult. We’ll
          create the function checkTiles( ) to do all this and call it from within the
          processMove( ) function. processMove( ) is called when the user drags the
          map, so by calling it from within, we’ll be able to load our tiles as the
          map moves. The following code excerpt shows how we’ve added these
          elements to our JavaScript code; for now, checkTiles( ) is just stubbed
          out with comments:
File 39   function processMove(event) {
              if (!event) event = window.event;      // for IE
              var innerDiv = document.getElementById("innerDiv");
              if (dragging) {
                  innerDiv.style.top = top + (event.clientY - dragStartTop);
                  innerDiv.style.left = left + (event.clientX - dragStartLeft);
              }


              checkTiles();
          }


          function checkTiles() {
              // check which tiles should be visible in the inner div


              // add each tile to the inner div, checking first to see
              // if it has already been added
          }

          Now, let’s implement our stubbed-out checkTiles( ) function.


          Calculating the Visible Tiles

          Calculating the set of tiles that the user can see in the inner <div> is
          fairly straightforward. To understand how this works, it will help to
          visualize the inner div as a grid where each grid cell is a placeholder of
          the tiles that we’ll load. Figure 2.6 illustrates this concept.
C REATING A JAXIAN M APS   25




                        Figure 2.6: The Tile Grid



Because we can’t load all the tiles in the grid up front, we’ll need to
calculate which of these grid cells are visible and load the tiles needed
to fit into these cells. As Figure 2.6 shows, this is accomplished by
calculating which grid cells are visible within the viewport created by
the size of the outer div. In the figure, we see that nine cells are visible
across three rows. Note that those cells that are only partially visible
still count as being visible.
Let’s see how to implement all this behavior we just described. To make
things simple, we’ll encapsulate all of the code to figure out which tiles
are visible in a particular method, which we’ll call getVisibleTiles( ). The
first thing we need to figure out in getVisibleTiles( ) is the position of the
inner div relative to the outer div. This is fairly easy:
C REATING A JAXIAN M APS   26


function getVisibleTiles() {
    var innerDiv = document.getElementById("innerDiv");
    var mapX = stripPx(innerDiv.style.left);
    var mapY = stripPx(innerDiv.style.top);
}

The stripPx( ) function, shown earlier, converts the string value returned
by innerDiv.style.left (such as 100px) to a numeric value (say, 100). Now,
we can divide these positions by the size of the tiles to work out the
starting row and column of the tiles. This is just two lines of code:
var startX = Math.abs(Math.floor(mapX / tileSize)) - 1;
var startY = Math.abs(Math.floor(mapY / tileSize)) - 1;

Note that we haven’t yet defined the tileSize variable; we’ll do that glob-
ally (at the top of our JavaScript code), and you’ll see it when we show
the entire page in just a few paragraphs. (Or, you can see it now on
the following page.) The call to Math.floor( ) will round the quotient to an
integer, discarding the remainder (so 1.4 will be rounded down to 1).
This will cause partial tiles to be displayed. Math.abs( ) converts negative
values to a positive number, which in our case is necessary because the
inner div position will nearly always be negative to the outer div, and
because our tile columns/rows are always positive numbers. Finally,
we subtract 1 from the result to make our map load the tiles a touch
early for a smoother effect.
The final bit of calculation is to determine the number of rows and
columns visible in the viewport:
var tilesX = Math.ceil(viewportWidth / tileSize) + 1;
var tilesY = Math.ceil(viewportHeight / tileSize) + 1;

As with tileSize( ), we’ll declare both viewportWidth and viewportHeight as
global variables and show that in just a bit. We use Math.ceil( ), the
opposite of Math.floor( ) (so it rounds the quotient up regardless of the
size of the remainder), to ensure that if any portion of a column or row
is visible, we’ll display it. And, just as we subtracted 1 from the index
of the tiles in the previous lines, we’ll add 1 to the number of columns
and rows to make the scroll effect smooth.
We now have all the data we need to calculate all of the visible tiles
in the viewport plus, as we’ve discussed, a few around the edges that
aren’t immediately visible but will be shortly. Now we’ll build an array
that contains all of the tiles that need to be loaded. To build this array,
we’ll write two for loops, one nested inside the other, that each perform
an iteration for each column and row that is currently visible. Inside
C REATING A JAXIAN M APS   27


          each loop iteration, we’ll add the column and row number of each tile
          to display:
          var visibleTileArray = [];
          var counter = 0;
          for (x = startX; x < (tilesX + startX); x++) {
              for (y = startY; y < (tilesY + startY); y++) {
                  visibleTileArray[counter++] = [x, y];
              }
          }
          return visibleTileArray;

          Note that we’re actually creating a two-dimensional array; the value of
          each item in our array is another array. We did this because we need
          to pass back two values: the column and row index. And now, we’re
          done calculating the tiles that are visible in the inner div, and we can
          move on and work on the code to actually display them. But first, let’s
          review all of the code we’ve written so far:
File 36   function checkTiles() {
              // check which tiles should be visible in the inner div
              var visibleTiles = getVisibleTiles();


              // add each tile to the inner div, checking first to see
              // if it has already been added
          }


          function getVisibleTiles() {
              var innerDiv = document.getElementById("innerDiv");


              var mapX = stripPx(innerDiv.style.left);
              var mapY = stripPx(innerDiv.style.top);


              var startX = Math.abs(Math.floor(mapX / tileSize)) - 1;
              var startY = Math.abs(Math.floor(mapY / tileSize)) - 1;


              var tilesX = Math.ceil(viewportWidth / tileSize) + 1;
              var tilesY = Math.ceil(viewportHeight / tileSize) + 1;


              var visibleTileArray = [];
              var counter = 0;
              for (x = startX; x < (tilesX + startX); x++) {
                  for (y = startY; y < (tilesY + startY); y++) {
                      visibleTileArray[counter++] = [x, y];
                  }
              }
              return visibleTileArray;
          }
C REATING A JAXIAN M APS   28


          Displaying the Visible Tiles

          We’ve now coded half of the checkTiles( ) function, which as you may
          recall is the function responsible for both calculating the visible tiles
          and displaying them. Now, let’s implement the other half of that func-
          tion: displaying the tiles.
          All we need to do here is iterate through each element of the array of
          visible tiles we returned from the getVisibleTiles( ) function and for each
          array element add a tile image to the inner div. Here’s the new code for
          our checkTiles( ) function:
File 37   Line 1   function checkTiles() {
               -       // check which tiles should be visible in the inner div
               -       var visibleTiles = getVisibleTiles();
               -
              5        // add each tile to the inner div, checking first to see
               -       // if it has already been added
               -       var innerDiv = document.getElementById("innerDiv");
               -       var visibleTilesMap = {};
               -       for (i = 0; i < visibleTiles.length; i++) {
             10            var tileArray = visibleTiles[i];
               -           var tileName = "x" + tileArray[0] + "y" + tileArray[1] + "z0";
               -           visibleTilesMap[tileName] = true;
               -           var img = document.getElementById(tileName);
               -           if (!img) {
             15                img = document.createElement("img");
               -               img.src = "resources/tiles/" + tileName + ".jpg";
               -               img.style.position = "absolute";
               -               img.style.left = (tileArray[0] * tileSize) + "px";
               -               img.style.top = (tileArray[1] * tileSize) + "px";
             20                img.setAttribute("id", tileName);
               -               innerDiv.appendChild(img);
               -           }
               -       }
               -   }

          We start out on line 8 by creating an empty map (map in the JavaScript
          sense; a hash that contains key-to-value mappings). We’re going to add
          an entry to this map for each visible image; we’ll discuss why we’re
          doing this a little later.
          On line 9, we start looping through each element in the array we sent
          back from getVisibleTiles( ). For each element, we build the name of the
          image file that will be loaded in. (If you recall, the file-naming conven-
          tion we chose in Step 2 was x0y0z0, where the numbers are replaced
          with the index of the tile in the tile grid.) We also use this name as the
          key in the visibleTilesMap variable, and on lines 13 and 20 you can see
C REATING A JAXIAN M APS   29


          that we also use it as the id attribute for each img element that we add
          to the inner div. This is so on lines 13 and 14, we can check to see
          we’ve already added a given tile to the inner div and, if we have, avoid
          adding it again.
          Finally, in lines 15 through line 21, we create the <img> element and
          add it to the inner div. Note that on line 16 we have to specify the URL
          of the image tile. If you have Java installed and executed the code from
          Steps 1 and 2 to create your own image tiles, great! Reference them
          on line 16, setting the URI to wherever you put them. If not, you can
          reference our tiles online.2
          You can now enjoy a scrolling map of Spain in your browser! We’ve
          placed a copy online at GoogleMaps/step5-3.html. Here’s all the code
          we’ve written so far:
File 37   <html>
              <head>
                    <title>Ajaxian Maps</title>
                    <style type="text/css">
                         h1 {
                                font: 20pt sans-serif;
                         }
                         #outerDiv {
                                height: 600px;
                                width: 800px;
                                border: 1px solid black;
                                position: relative;
                                overflow: hidden;
                         }


                         #innerDiv {
                                position: relative;
                                left: 0px;
                                top: 0px;
                         }
                    </style>
                    <script type="text/javascript">
                         // constants
                         var viewportWidth = 800;
                         var viewportHeight = 600;
                         var tileSize = 100;


                         // used to control moving the map div
                         var dragging = false;



             2 GoogleMaps/resources/tiles/x0y0z0.jpg,   where x0y0z0 should be replaced with the tile you
          want to load
C REATING A JAXIAN M APS   30


var top;
var left;
var dragStartTop;
var dragStartLeft;


function init() {
    // make inner div big enough to display the map
    setInnerDivSize( ' 2000px ' , ' 1400px ' );


    // wire up the mouse listeners to do dragging
    var outerDiv = document.getElementById("outerDiv");
    outerDiv.onmousedown = startMove;
    outerDiv.onmousemove = processMove;
    outerDiv.onmouseup = stopMove;


    // necessary to enable dragging on IE
    outerDiv.ondragstart = function() { return false; }


    checkTiles();
}


function startMove(event) {
    // necessary for IE
    if (!event) event = window.event;


    dragStartLeft = event.clientX;
    dragStartTop = event.clientY;
    var innerDiv = document.getElementById("innerDiv");
    innerDiv.style.cursor = "-moz-grab";


    top = stripPx(innerDiv.style.top);
    left = stripPx(innerDiv.style.left);


    dragging = true;
    return false;
}


function processMove(event) {
    if (!event) event = window.event;        // for IE
    var innerDiv = document.getElementById("innerDiv");
    if (dragging) {
        innerDiv.style.top = top + (event.clientY - dragStartTop);
        innerDiv.style.left = left + (event.clientX - dragStartLeft);
    }


    checkTiles();
}
function checkTiles() {
    // check which tiles should be visible in the inner div
    var visibleTiles = getVisibleTiles();
C REATING A JAXIAN M APS   31


    // add each tile to the inner div, checking first to see
    // if it has already been added
    var innerDiv = document.getElementById("innerDiv");
    var visibleTilesMap = {};
    for (i = 0; i < visibleTiles.length; i++) {
        var tileArray = visibleTiles[i];
        var tileName = "x" + tileArray[0] + "y" + tileArray[1] + "z0";
        visibleTilesMap[tileName] = true;
        var img = document.getElementById(tileName);
        if (!img) {
            img = document.createElement("img");
            img.src = "resources/tiles/" + tileName + ".jpg";
            img.style.position = "absolute";
            img.style.left = (tileArray[0] * tileSize) + "px";
            img.style.top = (tileArray[1] * tileSize) + "px";
            img.setAttribute("id", tileName);
            innerDiv.appendChild(img);
        }
    }
}


function getVisibleTiles() {
    var innerDiv = document.getElementById("innerDiv");


    var mapX = stripPx(innerDiv.style.left);
    var mapY = stripPx(innerDiv.style.top);


    var startX = Math.abs(Math.floor(mapX / tileSize)) - 1;
    var startY = Math.abs(Math.floor(mapY / tileSize)) - 1;


    var tilesX = Math.ceil(viewportWidth / tileSize) + 1;
    var tilesY = Math.ceil(viewportHeight / tileSize) + 1;


    var visibleTileArray = [];
    var counter = 0;
    for (x = startX; x < (tilesX + startX); x++) {
        for (y = startY; y < (tilesY + startY); y++) {
            visibleTileArray[counter++] = [x, y];
        }
    }
    return visibleTileArray;
}



function stopMove() {
    var innerDiv = document.getElementById("innerDiv");
    innerDiv.style.cursor = "";
    dragging = false;
}
C REATING A JAXIAN M APS   32


                          function stripPx(value) {
                              if (value == "") return 0;
                              return parseFloat(value.substring(0, value.length - 2));
                          }


                          function setInnerDivSize(width, height) {
                              var innerDiv = document.getElementById("innerDiv");
                              innerDiv.style.width = width;
                              innerDiv.style.height = height;
                          }
                    </script>
              </head>
              <body onload="init()">
                    <p>
                          <h1>Ajaxian Maps</h1>
                    </p>
                    <div id="outerDiv">
                          <div id="innerDiv">
                              The rain in Spain falls mainly in the plains.
                          </div>
                    </div>
              </body>
          </html>



          Cleaning Up Unused Tiles

          We’ve got some neat scrolling, but this has one glaring inefficiency. We
          add tiles to the inner div on demand, but we never remove the tiles that
          are no longer visible. Fortunately, we’ve already done some of the work
          to accommodate this feature. If you recall, we created a JavaScript map
          named visibleTilesMap in the checkTiles( ) function but never did anything
          with it. Now, we’re going to do something.
          After we add the image tiles to the inner div, we’ll select all of the img
          elements that are present in the inner div, and for each img element,
          we’ll check to see whether its id attribute is present in the visibleTilesMap
          variable. If so, we know that it’s a currently visible tile and should be
          left in the inner div. If not, the <img> is no longer visible and can be
          removed. Here’s the additional code in checkTiles( ) to implement this
          functionality:
File 38   function checkTiles() {
              // check which tiles should be visible in the inner div
              var visibleTiles = getVisibleTiles();


              // add each tile to the inner div, checking first to see
              // if it has already been added
              var innerDiv = document.getElementById("innerDiv");
C REATING A JAXIAN M APS   33


    var visibleTilesMap = {};
    for (i = 0; i < visibleTiles.length; i++) {
        var tileArray = visibleTiles[i];
        var tileName = "x" + tileArray[0] + "y" + tileArray[1] + "z0";
        visibleTilesMap[tileName] = true;
        var img = document.getElementById(tileName);
        if (!img) {
            img = document.createElement("img");
            img.src = "resources/tiles/" + tileName + ".jpg";
            img.style.position = "absolute";
            img.style.left = (tileArray[0] * tileSize) + "px";
            img.style.top = (tileArray[1] * tileSize) + "px";
            img.setAttribute("id", tileName);
            innerDiv.appendChild(img);
        }
    }


    var imgs = innerDiv.getElementsByTagName("img");
    for (i = 0; i < imgs.length; i++) {
        var id = imgs[i].getAttribute("id");
        if (!visibleTilesMap[id]) {
            innerDiv.removeChild(imgs[i]);
            i--;   // compensate for live nodelist
        }
    }
}

Figure 2.7, on the next page, shows what this looks like.

Step 6: Zooming
Zooming is wicked easy; in fact, the hardest bit is just getting a zoom
widget to appear floating above the map. First, we need to create some
kind of image that the user can click on to enable zooming. In Google
Maps, it’s a slider (shown in the margin here); for us, we’ll just create
a simple image that toggles between our two zoom levels. You can use
any image you like; ours is at GoogleMaps/resources/images/zoom.png.
Second, to float the image above the map, we have to properly set the
z-index of our inner div. Browsers support layering elements on top
of each other; the z-index CSS property is used to determine how the
layering occurs. The lower the value, the lower in the layer the element
will appear. Because we want to put our zoom widget above the tile
images, we’ll need to set the z-index of the inner div to 0. The z-index of
the zoom widget then needs to be any value greater than 0 (we use 1).
Now, let’s add the zoom widget. We’ll enclose it in a div, place it inside
the outer div as a peer of the inner div, and we’ll set the z-index proper-
ties appropriately:
C REATING A JAXIAN M APS   34




                                     Figure 2.7: Ajaxian Maps!



File 40   Line 1   <body onload="init()">
               -    <p>
               -      <h1>Ajaxian Maps</h1>
               -    </p>
              5     <div id="outerDiv">
               -      <div style="position: absolute; top: 10px; left: 10px; z-index: 1">
               -           <img src="resources/images/zoom.png"
               -               onclick="toggleZoom()"/>
               -      </div>
             10       <div id="innerDiv" style="z-index: 0">
               -           The rain in Spain falls mainly in the plains.
               -      </div>
               -    </div>
               -   </body>

          That will give us our floating zoom widget; now we need to create the
          toggleZoom( ) function that we referenced on line 8. This will require a
C REATING A JAXIAN M APS   35


          few minor changes to our code. First, we need to create some sort of
          global state that tracks the current zoom level of our map. Second, we
          need to reference this state in the various relevant places in our code
          (just one, actually).
          Let’s start with the global state. We’ll create a variable zoom to track
          the current zoom level and while we’re at it add a constant (in the form
          of a two-dimensional array) for declaring the two different sizes of the
          inner div:
File 40   var zoom = 0;
          var zoomSizes = [ [ "2000px", "1400px" ], [ "1500px", "1050px" ] ];

          Now, in the name of cleanliness, we’ll change the first line of our init
          method from this:
File 38   setInnerDivSize( ' 2000px ' , ' 1400px ' );

          to this:
File 40   setInnerDivSize(zoomSizes[zoom][0], zoomSizes[zoom][1]);

          There’s just one other place we need to wire in the zoom support: our
          checkTiles( ) function, which creates the img elements for the tiles and
          gives them their URL. We need to change this hard-coded zoom-level
          code:
File 38   var tileName = "x" + tileArray[0] + "y" + tileArray[1] + "z0";

          to this:
File 40   var tileName = "x" + tileArray[0] + "y" + tileArray[1] + "z" + zoom;

          All that remains is implementing the toggleZoom( ) function, which we’ve
          done here:
File 40   function toggleZoom() {
              zoom = (zoom == 0) ? 1 : 0;


              var innerDiv = document.getElementById("innerDiv");
              var imgs = innerDiv.getElementsByTagName("img");
              while (imgs.length > 0) innerDiv.removeChild(imgs[0]);


              setInnerDivSize(zoomSizes[zoom][0], zoomSizes[zoom][1]);


              checkTiles();
          }

          Nothing too tricky; we swap the value of the zoom variable from 0 to 1,
          delete all the <img> elements from the inner div, change the size of the
C REATING A JAXIAN M APS   36




                   Figure 2.8: Ajaxian Maps Zoomed Out



inner div based on the zoom level, and invoke checkTiles( ) to rebuild the
map with the new zoom level’s tiles.
And now, we have zooming in our map application! Cool. The code
for this version is on-line if you need it.3 Figure 2.8 shows the zoom
feature in action, with our map zoomed to the smaller size.

Step 7: Push Pins and Dialogs
The final feature is adding push pins with alpha transparency. When
clicked, these show a dialog that also has alpha transparency. The

  3 http://media.pragprog.com/titles/ajax/code/GoogleMaps/step6.html
C REATING A JAXIAN M APS   37


          hardest part is creating the images.4 These images will not render prop-
          erly in IE 6, but see the end of this section for a workaround.
          We’re not going to implement a server back end that does searching,
          and so on, so just as with zooming we implemented a toggle, we’ll imple-
          ment a toggle for our push pin. The graphic for the toggle is available
          at GoogleMaps/resources/images/pushpin.png.
          We’ll place the push pin toggle right next to the zoom toggle by adding
          a new div for it:
File 42   <body onload="init()">
              <p>
                    <h1>Ajaxian Maps</h1>
              </p>
              <div id="outerDiv">
                    <div style="position: absolute; top: 10px; left: 10px; z-index: 1">
                       <img src="resources/images/zoom.png" onclick="toggleZoom()"/>
                    </div>
                    <div style="position: absolute; top: 10px; left: 87px; z-index: 1">
                       <img src="resources/images/pushpin.png" onclick="togglePushPin()"/>
                    </div>
                    <div id="innerDiv" style="z-index: 0">
                       The rain in Spain falls mainly in the plains.
                    </div>
              </div>
          </body>

          Now we need to implement togglePushPin( ), which, frankly, is a piece of
          cake. We’ll just add an absolutely positioned image with a z-index of 1
          to the inner div, add an onclick handler to it, and wire that handler to
          display the dialog at an absolute position just above the push pin:
File 42   function togglePushPin() {
              var pinImage = document.getElementById("pushPin");
              if (pinImage) {
                    pinImage.parentNode.removeChild(pinImage);
                    var dialog = document.getElementById("pinDialog");
                    dialog.parentNode.removeChild(dialog);
                    return;
              }


              var innerDiv = document.getElementById("innerDiv");
              pinImage = document.createElement("img");
              pinImage.src = "resources/images/pin.png";
              pinImage.style.position = "absolute";
              pinImage.style.left = (zoom == 0) ? "850px" : "630px";
              pinImage.style.top = (zoom == 0) ? "570px" : "420px";



            4 GoogleMaps/resources/images/pin.png   and GoogleMaps/resources/images/dialog.png.
C REATING A JAXIAN M APS   38


              pinImage.style.zIndex = 1;
              pinImage.setAttribute("id", "pushPin");
              innerDiv.appendChild(pinImage);


              var dialog = document.createElement("div");
              dialog.style.position = "absolute";
              dialog.style.left = (stripPx(pinImage.style.left) - 90) + "px";
              dialog.style.top = (stripPx(pinImage.style.top) - 210) + "px";
              dialog.style.width = "309px";
              dialog.style.height = "229px";
              dialog.style.backgroundImage = "url(resources/images/dialog.png)";
              dialog.style.zIndex = 2;
              dialog.setAttribute("id", "pinDialog");
              dialog.innerHTML = "<table height= ' 80% '   width= ' 100% ' >" +
                  "<tr><td align= ' center ' >The capital of Spain</td></tr></table>";
              innerDiv.appendChild(dialog);
          }

          There’s just one little problem with this new behavior. Do you remem-
          ber the image remover code in checkTiles( )? It removes any img element
          child of the inner div that has been explicitly added to that function. Of
          course, it will clobber our push pin as well, since it is an img child of
          the inner div, so we need to modify the function to ignore the push pin:
File 42   var imgs = innerDiv.getElementsByTagName("img");
          for (i = 0; i < imgs.length; i++) {
              var id = imgs[i].getAttribute("id");
              if (!visibleTilesMap[id]) {
                  if (id != "pushPin") {
                      innerDiv.removeChild(imgs[i]);
                      i--;   // compensate for live nodelist
                  }
              }
          }

          We’re done! We’ve implemented all of the features we discussed in the
          introduction of this chapter. Let’s wrap up by...err, wait a second.
          While Firefox, Safari, and other browsers provide native support for
          PNGs with alpha transparency, IE 6 does not. If you’ve been using that
          browser to try this sample code, the zoom and push pin buttons as well
          as the push pin and dialog itself have looked really awful.
          Fortunately, this has an easy (but annoying) fix. Despite not supporting
          PNGs out of the box, IE can use some (IE-specific) JavaScript magic
          to parse out the alpha channel from a PNG at runtime and display it
          correctly. A number of websites document this workaround; in order
          to avoid sidetracking our Google Maps story, we’ll just use a JavaScript
C REATING A JAXIAN M APS   39


          library provided by one of these websites, www.alistapart.com,5 to solve
          our problem.
          First, we need to include these new JavaScripts in our webpage, which
          we’ll do at the top:
File 41   <script language="javascript"
                     src="resources/js/browserdetect_lite.js"
                     type="text/javascript">
          </script>
          <script language="javascript"
                     src="resources/js/opacity.js"
                     type="text/javascript">
          </script>

          Then, because this library requires that the PNGs it fixes be back-
          ground images in a div, we need to change our push pin from an img
          element to a div, as well as our two toggle buttons, and then finally use
          this library to fix all of these divs. We’ll change the toggle button images
          to div background images first:
File 41   <body onload="init()">
               <p>
                     <h1>Ajaxian Maps</h1>
               </p>
               <div id="outerDiv">
                     <div id="toggleZoomDiv" onclick="toggleZoom()">
                     </div>
                     <div id="togglePushPinDiv" onclick="togglePushPin()">
                     </div>
                     <div id="innerDiv" style="z-index: 0">
                          The rain in Spain falls mainly in the plains.
                     </div>
               </div>
          </body>

          As part of this change, we moved the style attribute settings on the tog-
          gle divs into the style sheet we defined at the top of the file (something
          we probably should have done anyway):
File 41   #toggleZoomDiv {
               position: absolute;
               top: 10px;
               left: 10px;
               z-index: 1;
               width: 72px;
               height: 30px;
          }



              5 http://www.alistapart.com/articles/pngopacity
C REATING A JAXIAN M APS   40


          #togglePushPinDiv {
              position: absolute;
              top: 10px;
              left: 87px;
              z-index: 1;
              width: 72px;
              height: 30px;
          }

          We now need to add two lines to our init( ) method to use our new IE
          transparency library with the toggle divs:
File 41   // fix the toggle divs to be transparent in IE
          new OpacityObject( ' toggleZoomDiv ' , ' resources/images/zoom ' )
                                     .setBackground();
          new OpacityObject( ' togglePushPinDiv ' , ' resources/images/pushpin ' )
                                     .setBackground();

          And finally, we need to reformat the togglePushPin( ) function to use this
          new technique:
File 41   function togglePushPin() {
              var pinImage = document.getElementById("pushPin");
              if (pinImage) {
                   pinImage.parentNode.removeChild(pinImage);
                   var dialog = document.getElementById("pinDialog");
                   dialog.parentNode.removeChild(dialog);
                   return;
              }


              var innerDiv = document.getElementById("innerDiv");
              pinImage = document.createElement("div");
              pinImage.style.position = "absolute";
              pinImage.style.left = (zoom == 0) ? "850px" : "630px";
              pinImage.style.top = (zoom == 0) ? "570px" : "420px";
              pinImage.style.width = "37px";
              pinImage.style.height = "34px";
              pinImage.style.zIndex = 1;
              pinImage.setAttribute("id", "pushPin");
              innerDiv.appendChild(pinImage);
              new OpacityObject( ' pushPin ' , ' resources/images/pin ' )
                                         .setBackground();


              var dialog = document.createElement("div");
              dialog.style.position = "absolute";
              dialog.style.left = (stripPx(pinImage.style.left) - 90) + "px";
              dialog.style.top = (stripPx(pinImage.style.top) - 210) + "px";
              dialog.style.width = "309px";
              dialog.style.height = "229px";
              dialog.style.zIndex = 2;
              dialog.setAttribute("id", "pinDialog");
              dialog.innerHTML = "<table height= ' 80% '    width= ' 100% ' ><tr>" +
C REATING A JAXIAN M APS   41




        Figure 2.9: Ajaxian Maps Push Pin and Dialog on IE 6



      "<td align= ' center ' >The capital of Spain</td></tr></table>";
    innerDiv.appendChild(dialog);
    new OpacityObject( ' pinDialog ' , ' resources/images/dialog ' )
                               .setBackground();
}

And now, finally, we are done. Up until the image transparency bit,
our code was really quite clean and had very little in the way of cross-
browser hacks. Now, unfortunately, it has had to undergo a bit of an IE
makeover, but the consolation prize is that IE 7 natively supports PNG
so all of this may someday be unnecessary.
For review, let’s take a look at our entire page:
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006
Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006

More Related Content

What's hot

Expert oracle database architecture
Expert oracle database architectureExpert oracle database architecture
Expert oracle database architectureairy6548
 
Getting Started with OpenStack and VMware vSphere
Getting Started with OpenStack and VMware vSphereGetting Started with OpenStack and VMware vSphere
Getting Started with OpenStack and VMware vSphereEMC
 
Information extraction systems aspects and characteristics
Information extraction systems  aspects and characteristicsInformation extraction systems  aspects and characteristics
Information extraction systems aspects and characteristicsGeorge Ang
 
Jasper server ce-install-guide
Jasper server ce-install-guideJasper server ce-install-guide
Jasper server ce-install-guidewoid
 
What's New in VMware Virtual SAN
What's New in VMware Virtual SANWhat's New in VMware Virtual SAN
What's New in VMware Virtual SANEMC
 
Apache Web Server Complete Guide
Apache Web Server Complete GuideApache Web Server Complete Guide
Apache Web Server Complete GuideKazim Soomro
 
Zimbra guide admin_anglais_uniquement
Zimbra guide admin_anglais_uniquementZimbra guide admin_anglais_uniquement
Zimbra guide admin_anglais_uniquementchiensy
 
Wp br v7_a_vmware_architects_favorite_features[1]
Wp br v7_a_vmware_architects_favorite_features[1]Wp br v7_a_vmware_architects_favorite_features[1]
Wp br v7_a_vmware_architects_favorite_features[1]gerdev
 
V mware architecting-v-cloud-wp
V mware architecting-v-cloud-wpV mware architecting-v-cloud-wp
V mware architecting-v-cloud-wpraghav8055
 
Guia definitiva de shodan
Guia definitiva de shodanGuia definitiva de shodan
Guia definitiva de shodannoc_313
 
Whats-New-VMware-vCloud-Director-15-Technical-Whitepaper
Whats-New-VMware-vCloud-Director-15-Technical-WhitepaperWhats-New-VMware-vCloud-Director-15-Technical-Whitepaper
Whats-New-VMware-vCloud-Director-15-Technical-WhitepaperDjbilly Mixe Pour Toi
 
OE Application Server Administratoion
OE Application Server AdministratoionOE Application Server Administratoion
OE Application Server Administratoiontawatchai.psp
 
Documentation - LibraryRandom
Documentation - LibraryRandomDocumentation - LibraryRandom
Documentation - LibraryRandomMichel Alves
 
Php myadmin documentation
Php myadmin documentationPhp myadmin documentation
Php myadmin documentationDavid Raudales
 
Ref arch for ve sg248155
Ref arch for ve sg248155Ref arch for ve sg248155
Ref arch for ve sg248155Accenture
 
Rafal_Malanij_MSc_Dissertation
Rafal_Malanij_MSc_DissertationRafal_Malanij_MSc_Dissertation
Rafal_Malanij_MSc_DissertationRafał Małanij
 

What's hot (20)

Java web programming
Java web programmingJava web programming
Java web programming
 
Expert oracle database architecture
Expert oracle database architectureExpert oracle database architecture
Expert oracle database architecture
 
Getting Started with OpenStack and VMware vSphere
Getting Started with OpenStack and VMware vSphereGetting Started with OpenStack and VMware vSphere
Getting Started with OpenStack and VMware vSphere
 
Information extraction systems aspects and characteristics
Information extraction systems  aspects and characteristicsInformation extraction systems  aspects and characteristics
Information extraction systems aspects and characteristics
 
Jasper server ce-install-guide
Jasper server ce-install-guideJasper server ce-install-guide
Jasper server ce-install-guide
 
What's New in VMware Virtual SAN
What's New in VMware Virtual SANWhat's New in VMware Virtual SAN
What's New in VMware Virtual SAN
 
Apache Web Server Complete Guide
Apache Web Server Complete GuideApache Web Server Complete Guide
Apache Web Server Complete Guide
 
Zimbra guide admin_anglais_uniquement
Zimbra guide admin_anglais_uniquementZimbra guide admin_anglais_uniquement
Zimbra guide admin_anglais_uniquement
 
Apache Maven
Apache MavenApache Maven
Apache Maven
 
Book hudson
Book hudsonBook hudson
Book hudson
 
Wp br v7_a_vmware_architects_favorite_features[1]
Wp br v7_a_vmware_architects_favorite_features[1]Wp br v7_a_vmware_architects_favorite_features[1]
Wp br v7_a_vmware_architects_favorite_features[1]
 
V mware architecting-v-cloud-wp
V mware architecting-v-cloud-wpV mware architecting-v-cloud-wp
V mware architecting-v-cloud-wp
 
Guia definitiva de shodan
Guia definitiva de shodanGuia definitiva de shodan
Guia definitiva de shodan
 
Report-V1.5_with_comments
Report-V1.5_with_commentsReport-V1.5_with_comments
Report-V1.5_with_comments
 
Whats-New-VMware-vCloud-Director-15-Technical-Whitepaper
Whats-New-VMware-vCloud-Director-15-Technical-WhitepaperWhats-New-VMware-vCloud-Director-15-Technical-Whitepaper
Whats-New-VMware-vCloud-Director-15-Technical-Whitepaper
 
OE Application Server Administratoion
OE Application Server AdministratoionOE Application Server Administratoion
OE Application Server Administratoion
 
Documentation - LibraryRandom
Documentation - LibraryRandomDocumentation - LibraryRandom
Documentation - LibraryRandom
 
Php myadmin documentation
Php myadmin documentationPhp myadmin documentation
Php myadmin documentation
 
Ref arch for ve sg248155
Ref arch for ve sg248155Ref arch for ve sg248155
Ref arch for ve sg248155
 
Rafal_Malanij_MSc_Dissertation
Rafal_Malanij_MSc_DissertationRafal_Malanij_MSc_Dissertation
Rafal_Malanij_MSc_Dissertation
 

Viewers also liked

Pragmatic Programmer: Pragmatic Projects
Pragmatic Programmer: Pragmatic ProjectsPragmatic Programmer: Pragmatic Projects
Pragmatic Programmer: Pragmatic ProjectsZach Leatherman
 
Pragmatic Programmer
Pragmatic ProgrammerPragmatic Programmer
Pragmatic ProgrammerBert Añasco
 
The pragmatic programmer
The pragmatic programmerThe pragmatic programmer
The pragmatic programmerJoel Corrêa
 
THe Pragmatic Programmer - Basic tools
THe Pragmatic Programmer - Basic toolsTHe Pragmatic Programmer - Basic tools
THe Pragmatic Programmer - Basic toolsRuiyun Zhou
 
Teoria das janelas quebradas - Pragmatic Programmer
Teoria das janelas quebradas - Pragmatic ProgrammerTeoria das janelas quebradas - Pragmatic Programmer
Teoria das janelas quebradas - Pragmatic Programmerleopoa
 
Pragmatic Programmer
Pragmatic ProgrammerPragmatic Programmer
Pragmatic Programmerguy_davis
 
1ST TECH TALK: Pragmatic Programmer Practical Tips by Bert Añasco
1ST TECH TALK: Pragmatic Programmer Practical Tips by Bert Añasco1ST TECH TALK: Pragmatic Programmer Practical Tips by Bert Añasco
1ST TECH TALK: Pragmatic Programmer Practical Tips by Bert AñascoBicol IT.org
 
Scala : Monads for the Pragmatic Programmer , Composition with Stackable Traits
Scala : Monads for the Pragmatic Programmer , Composition with Stackable TraitsScala : Monads for the Pragmatic Programmer , Composition with Stackable Traits
Scala : Monads for the Pragmatic Programmer , Composition with Stackable TraitsAndreas Neumann
 
Functional Programming Patterns for the Pragmatic Programmer
Functional Programming Patterns for the Pragmatic ProgrammerFunctional Programming Patterns for the Pragmatic Programmer
Functional Programming Patterns for the Pragmatic ProgrammerRaúl Raja Martínez
 
Curs 1 consimtamantul
Curs 1 consimtamantulCurs 1 consimtamantul
Curs 1 consimtamantulvd88
 
Training for CMS Clients
Training for CMS ClientsTraining for CMS Clients
Training for CMS Clientsalledia
 
Qué le pido a 2012 para mi español
Qué le pido a 2012 para mi españolQué le pido a 2012 para mi español
Qué le pido a 2012 para mi españolcircusromanus
 
sdpssngo Annual report 2010 -2011
 sdpssngo Annual report 2010 -2011 sdpssngo Annual report 2010 -2011
sdpssngo Annual report 2010 -2011sdpss Ngo
 
Kelsey muir presentazione stanford abu tortura
Kelsey muir presentazione stanford abu torturaKelsey muir presentazione stanford abu tortura
Kelsey muir presentazione stanford abu torturajacckino
 
PROBLEMS
PROBLEMSPROBLEMS
PROBLEMSholbeau
 
оюунзул хэрэглэгдэхүүн ъ
оюунзул хэрэглэгдэхүүн ъоюунзул хэрэглэгдэхүүн ъ
оюунзул хэрэглэгдэхүүн ъoyunzul
 

Viewers also liked (20)

Pragmatic Programmer: Pragmatic Projects
Pragmatic Programmer: Pragmatic ProjectsPragmatic Programmer: Pragmatic Projects
Pragmatic Programmer: Pragmatic Projects
 
Some principles from The Pragmatic Programmer
Some principles from The Pragmatic ProgrammerSome principles from The Pragmatic Programmer
Some principles from The Pragmatic Programmer
 
Pragmatic Programmer
Pragmatic ProgrammerPragmatic Programmer
Pragmatic Programmer
 
The pragmatic programmer
The pragmatic programmerThe pragmatic programmer
The pragmatic programmer
 
THe Pragmatic Programmer - Basic tools
THe Pragmatic Programmer - Basic toolsTHe Pragmatic Programmer - Basic tools
THe Pragmatic Programmer - Basic tools
 
Pragmatic programmer 2
Pragmatic programmer 2Pragmatic programmer 2
Pragmatic programmer 2
 
Teoria das janelas quebradas - Pragmatic Programmer
Teoria das janelas quebradas - Pragmatic ProgrammerTeoria das janelas quebradas - Pragmatic Programmer
Teoria das janelas quebradas - Pragmatic Programmer
 
Prag progppt
Prag progpptPrag progppt
Prag progppt
 
Pragmatic Programmer
Pragmatic ProgrammerPragmatic Programmer
Pragmatic Programmer
 
1ST TECH TALK: Pragmatic Programmer Practical Tips by Bert Añasco
1ST TECH TALK: Pragmatic Programmer Practical Tips by Bert Añasco1ST TECH TALK: Pragmatic Programmer Practical Tips by Bert Añasco
1ST TECH TALK: Pragmatic Programmer Practical Tips by Bert Añasco
 
Scala : Monads for the Pragmatic Programmer , Composition with Stackable Traits
Scala : Monads for the Pragmatic Programmer , Composition with Stackable TraitsScala : Monads for the Pragmatic Programmer , Composition with Stackable Traits
Scala : Monads for the Pragmatic Programmer , Composition with Stackable Traits
 
Functional Programming Patterns for the Pragmatic Programmer
Functional Programming Patterns for the Pragmatic ProgrammerFunctional Programming Patterns for the Pragmatic Programmer
Functional Programming Patterns for the Pragmatic Programmer
 
Curs 1 consimtamantul
Curs 1 consimtamantulCurs 1 consimtamantul
Curs 1 consimtamantul
 
Training for CMS Clients
Training for CMS ClientsTraining for CMS Clients
Training for CMS Clients
 
Actividad 3
Actividad 3Actividad 3
Actividad 3
 
Qué le pido a 2012 para mi español
Qué le pido a 2012 para mi españolQué le pido a 2012 para mi español
Qué le pido a 2012 para mi español
 
sdpssngo Annual report 2010 -2011
 sdpssngo Annual report 2010 -2011 sdpssngo Annual report 2010 -2011
sdpssngo Annual report 2010 -2011
 
Kelsey muir presentazione stanford abu tortura
Kelsey muir presentazione stanford abu torturaKelsey muir presentazione stanford abu tortura
Kelsey muir presentazione stanford abu tortura
 
PROBLEMS
PROBLEMSPROBLEMS
PROBLEMS
 
оюунзул хэрэглэгдэхүүн ъ
оюунзул хэрэглэгдэхүүн ъоюунзул хэрэглэгдэхүүн ъ
оюунзул хэрэглэгдэхүүн ъ
 

Similar to Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006

java web_programming
java web_programmingjava web_programming
java web_programmingbachector
 
Advanced Java Script.pdf
Advanced Java Script.pdfAdvanced Java Script.pdf
Advanced Java Script.pdfSophia Diaz
 
Pragmatic Agile Web Development With Rails.3rd Edition.2009
Pragmatic   Agile Web Development With Rails.3rd Edition.2009Pragmatic   Agile Web Development With Rails.3rd Edition.2009
Pragmatic Agile Web Development With Rails.3rd Edition.2009SHC
 
Docker containerization cookbook
Docker containerization cookbookDocker containerization cookbook
Docker containerization cookbookPascal Louis
 
RAC Attack 12c Installation Instruction
RAC Attack 12c Installation InstructionRAC Attack 12c Installation Instruction
RAC Attack 12c Installation InstructionYury Velikanov
 
Shariar Rostami - Master Thesis
Shariar Rostami - Master ThesisShariar Rostami - Master Thesis
Shariar Rostami - Master Thesisshahriar-ro
 
Ibm total storage nas backup and recovery solutions sg246831
Ibm total storage nas backup and recovery solutions sg246831Ibm total storage nas backup and recovery solutions sg246831
Ibm total storage nas backup and recovery solutions sg246831Banking at Ho Chi Minh city
 
Ibm total storage nas backup and recovery solutions sg246831
Ibm total storage nas backup and recovery solutions sg246831Ibm total storage nas backup and recovery solutions sg246831
Ibm total storage nas backup and recovery solutions sg246831Banking at Ho Chi Minh city
 
Android_Programming_Cookbook.pdf
Android_Programming_Cookbook.pdfAndroid_Programming_Cookbook.pdf
Android_Programming_Cookbook.pdfBasemMohammad2
 
Yii blog-1.1.9
Yii blog-1.1.9Yii blog-1.1.9
Yii blog-1.1.9Netechsrl
 
www.webre24h.com - [Wordware] advanced javascript, 3rd ed. - [easttom]
www.webre24h.com - [Wordware]   advanced javascript, 3rd ed. - [easttom]www.webre24h.com - [Wordware]   advanced javascript, 3rd ed. - [easttom]
www.webre24h.com - [Wordware] advanced javascript, 3rd ed. - [easttom]webre24h
 
VBoxUserManual.pdf
VBoxUserManual.pdfVBoxUserManual.pdf
VBoxUserManual.pdfvladvah77
 
Thesis: Slicing of Java Programs using the Soot Framework (2006)
Thesis:  Slicing of Java Programs using the Soot Framework (2006) Thesis:  Slicing of Java Programs using the Soot Framework (2006)
Thesis: Slicing of Java Programs using the Soot Framework (2006) Arvind Devaraj
 
Ibm tivoli intelligent think dynamic orchestrator pre proof of-concept cookbo...
Ibm tivoli intelligent think dynamic orchestrator pre proof of-concept cookbo...Ibm tivoli intelligent think dynamic orchestrator pre proof of-concept cookbo...
Ibm tivoli intelligent think dynamic orchestrator pre proof of-concept cookbo...Banking at Ho Chi Minh city
 

Similar to Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006 (20)

java web_programming
java web_programmingjava web_programming
java web_programming
 
Advanced Java Script.pdf
Advanced Java Script.pdfAdvanced Java Script.pdf
Advanced Java Script.pdf
 
Pragmatic Agile Web Development With Rails.3rd Edition.2009
Pragmatic   Agile Web Development With Rails.3rd Edition.2009Pragmatic   Agile Web Development With Rails.3rd Edition.2009
Pragmatic Agile Web Development With Rails.3rd Edition.2009
 
Docker containerization cookbook
Docker containerization cookbookDocker containerization cookbook
Docker containerization cookbook
 
RAC Attack 12c Installation Instruction
RAC Attack 12c Installation InstructionRAC Attack 12c Installation Instruction
RAC Attack 12c Installation Instruction
 
IBM Streams - Redbook
IBM Streams - RedbookIBM Streams - Redbook
IBM Streams - Redbook
 
AIX 5L Differences Guide Version 5.3 Edition
AIX 5L Differences Guide Version 5.3 EditionAIX 5L Differences Guide Version 5.3 Edition
AIX 5L Differences Guide Version 5.3 Edition
 
Flask docs
Flask docsFlask docs
Flask docs
 
Shariar Rostami - Master Thesis
Shariar Rostami - Master ThesisShariar Rostami - Master Thesis
Shariar Rostami - Master Thesis
 
Ibm total storage nas backup and recovery solutions sg246831
Ibm total storage nas backup and recovery solutions sg246831Ibm total storage nas backup and recovery solutions sg246831
Ibm total storage nas backup and recovery solutions sg246831
 
Ibm total storage nas backup and recovery solutions sg246831
Ibm total storage nas backup and recovery solutions sg246831Ibm total storage nas backup and recovery solutions sg246831
Ibm total storage nas backup and recovery solutions sg246831
 
Knowledge base
Knowledge baseKnowledge base
Knowledge base
 
Android_Programming_Cookbook.pdf
Android_Programming_Cookbook.pdfAndroid_Programming_Cookbook.pdf
Android_Programming_Cookbook.pdf
 
Yii blog-1.1.9
Yii blog-1.1.9Yii blog-1.1.9
Yii blog-1.1.9
 
Jdbc
JdbcJdbc
Jdbc
 
www.webre24h.com - [Wordware] advanced javascript, 3rd ed. - [easttom]
www.webre24h.com - [Wordware]   advanced javascript, 3rd ed. - [easttom]www.webre24h.com - [Wordware]   advanced javascript, 3rd ed. - [easttom]
www.webre24h.com - [Wordware] advanced javascript, 3rd ed. - [easttom]
 
VBoxUserManual.pdf
VBoxUserManual.pdfVBoxUserManual.pdf
VBoxUserManual.pdf
 
IBM PowerVC Introduction and Configuration
IBM PowerVC Introduction and ConfigurationIBM PowerVC Introduction and Configuration
IBM PowerVC Introduction and Configuration
 
Thesis: Slicing of Java Programs using the Soot Framework (2006)
Thesis:  Slicing of Java Programs using the Soot Framework (2006) Thesis:  Slicing of Java Programs using the Soot Framework (2006)
Thesis: Slicing of Java Programs using the Soot Framework (2006)
 
Ibm tivoli intelligent think dynamic orchestrator pre proof of-concept cookbo...
Ibm tivoli intelligent think dynamic orchestrator pre proof of-concept cookbo...Ibm tivoli intelligent think dynamic orchestrator pre proof of-concept cookbo...
Ibm tivoli intelligent think dynamic orchestrator pre proof of-concept cookbo...
 

Pragmatic.bookshelf.pragmatic.ajax.a.web.2.0.primer.apr.2006

  • 1.
  • 2. Pragmatic Ajax A Web 2.0 Primer Justin Gehtland Ben Galbraith Dion Almaer The Pragmatic Bookshelf Raleigh, North Carolina Dallas, Texas
  • 3. P r a g m a t i c B o o k s h e l f Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in this book, and The Pragmatic Programmers, LLC was aware of a trademark claim, the designations have been printed in initial capital letters or in all capitals. The Pragmatic Starter Kit, The Pragmatic Programmer, Pragmatic Programming, Pragmatic Bookshelf and the linking g device are trademarks of The Pragmatic Programmers, LLC. Every precaution was taken in the preparation of this book. However, the publisher assumes no responsibility for errors or omissions, or for damages that may result from the use of information (including program listings) contained herein. Our Pragmatic courses, workshops, and other products can help you and your team create better software and have more fun. For more information, as well as the latest Pragmatic titles, please visit us at http://www.pragmaticprogrammer.com Copyright © 2006 The Pragmatic Programmers LLC. All rights reserved. No part of this publication may be reproduced, stored in a retrieval system, or transmit- ted, in any form, or by any means, electronic, mechanical, photocopying, recording, or otherwise, without the prior consent of the publisher. Printed in the United States of America. ISBN 0-9766940-8-5 Printed on acid-free paper with 85% recycled, 30% post-consumer content. First printing, March 2006 Version: 2006-4-27
  • 4. Contents Acknowledgments vii 1 Building Rich Internet Applications with Ajax 1 1.1 A Tale in Three Acts . . . . . . . . . . . . . . . . . . . . . 2 1.2 Google Maps: The Missing Spark . . . . . . . . . . . . . 4 1.3 What Is Ajax? . . . . . . . . . . . . . . . . . . . . . . . . . 5 1.4 Whither Now? . . . . . . . . . . . . . . . . . . . . . . . . . 8 2 Creating Google Maps 9 2.1 Rocket Scientists? . . . . . . . . . . . . . . . . . . . . . . 10 2.2 Your Own Google Maps . . . . . . . . . . . . . . . . . . . 11 2.3 Creating Ajaxian Maps . . . . . . . . . . . . . . . . . . . 16 2.4 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . 47 3 Ajax in Action 48 3.1 Ajaxifying a Web Application . . . . . . . . . . . . . . . . 48 3.2 Ajax to the Rescue . . . . . . . . . . . . . . . . . . . . . . 48 3.3 The Grubby Details . . . . . . . . . . . . . . . . . . . . . 56 3.4 Wrapping Up . . . . . . . . . . . . . . . . . . . . . . . . . 59 4 Ajax Explained 60 4.1 A Review of Client-Side JavaScript . . . . . . . . . . . . . 61 4.2 Manipulating the Web Page . . . . . . . . . . . . . . . . . 67 4.3 Retrieving Data . . . . . . . . . . . . . . . . . . . . . . . . 73 4.4 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . 76 5 Ajax Frameworks 77 5.1 Frameworks, Toolkits, and Libraries . . . . . . . . . . . 77 5.2 Remoting with the Dojo Toolkit . . . . . . . . . . . . . . . 82 5.3 Remoting with the Prototype Library . . . . . . . . . . . 90 5.4 Wrapping Up . . . . . . . . . . . . . . . . . . . . . . . . . 92
  • 5. CONTENTS v 6 Ajax UI, Part I 93 6.1 Ajax and JavaScript for the UI . . . . . . . . . . . . . . . 93 6.2 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . 121 7 Ajax UI, Part II 122 7.1 Some Standard Usages . . . . . . . . . . . . . . . . . . . 122 7.2 It Isn’t All Just Wine and Roses... . . . . . . . . . . . . . 137 7.3 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . 146 8 Debugging Ajax Applications 147 8.1 View Source . . . . . . . . . . . . . . . . . . . . . . . . . . 147 8.2 DOM Inspectors . . . . . . . . . . . . . . . . . . . . . . . 148 8.3 JavaScript Debugging . . . . . . . . . . . . . . . . . . . . 160 8.4 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . 169 9 Degradable Ajax 170 9.1 What Is Degradable Ajax? . . . . . . . . . . . . . . . . . . 170 9.2 Ensuring Degradable Ajax Applications . . . . . . . . . . 172 9.3 Wrapping Up . . . . . . . . . . . . . . . . . . . . . . . . . 183 10 JSON and JSON-RPC 184 10.1 JSON-RPC . . . . . . . . . . . . . . . . . . . . . . . . . . . 187 11 Server-side Framework Integration 192 11.1 Different Strategies for Integration . . . . . . . . . . . . . 193 12 Ajax with PHP 195 12.1 The PHP Frameworks . . . . . . . . . . . . . . . . . . . . 195 12.2 Working with Sajax . . . . . . . . . . . . . . . . . . . . . 196 12.3 XOAD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204 12.4 Wrapping Up . . . . . . . . . . . . . . . . . . . . . . . . . 209 13 Ajax with Rails 210 13.1 Ruby on Rails . . . . . . . . . . . . . . . . . . . . . . . . . 210 13.2 Ajax Integration . . . . . . . . . . . . . . . . . . . . . . . . 214 13.3 The Future of Ajax in Rails . . . . . . . . . . . . . . . . . 227 14 Proxy-Based Ajax with DWR 230 14.1 DWR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231 14.2 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . 245
  • 6. CONTENTS vi 15 ASP.NET and Atlas 246 15.1 BorgWorX . . . . . . . . . . . . . . . . . . . . . . . . . . . 247 15.2 Atlas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249 15.3 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . 258 16 Ajax in the Future and Beyond 259 16.1 Data Manipulation . . . . . . . . . . . . . . . . . . . . . . 259 16.2 UI Manipulation . . . . . . . . . . . . . . . . . . . . . . . 263 16.3 Predictions . . . . . . . . . . . . . . . . . . . . . . . . . . 275 16.4 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . 278
  • 7. Acknowledgments Writing a book is a lot like (we imagine) flying a spaceship too close to a black hole. One second you’re thinking “Hey, there’s something interesting over there” and a picosecond later, everything you know and love has been sucked inside and crushed. OK, that’s hyperbole, but the point is that books don’t write themselves. More to the point, books aren’t even just written by the authors. It takes the combined efforts of a lot of people to extract information from the chaos. We’d like to hereby issue the following thanks. To every single beta purchaser of the book and especially the ones who sent in all those errata posts. You are a fantastic bunch, and we can’t thank you enough for your belief in the project and your help in making it a better book. To the team at the Pragmatic Programmers (especially you, Dave): you exhibited endless patience, forbearance, and wisdom during the pro- cess. Finally, to the authors of all the wonderful frameworks and tools we highlight in this book: your work is inspiring and we hope that this book helps shed just a little more light on the work you’ve done. From Justin Gehtland To my coauthors: thanks for thinking of me. My colleagues are an endless font of inspiration and vexation, both of which help with the creative process. So, thanks to Stu Hal- loway, Glenn Vanderburg, Neal Ford, and Ted Neward, all of whom provided various amounts of both. I keep telling my family that one day I’ll write a book they’d like to read. At least this one has an interesting cover. Lisa, Zoe, and Gabe: thanks for putting up with my office hours.
  • 8. A CKNOWLEDGMENTS viii From Ben Galbraith Thank you to my family, for all your patience while I spent late nights and early mornings working on this project. I love you. My sincere gratitude also goes to my publisher Dave Thomas (who patiently and gracefully watched this project go from early arrival to, well, somewhat less than early arrival) and my fellow authors, Justin Gehtland and Dion Almaer, who made many personal sac- rifices to get across the finish line. Finally, I thank all of my peers and colleagues who have taught me throughout the years. The patience and kindness of nearly everyone in our industry has always been an inspiration to me. From Dion Almaer Ah, acknowledgments. This is the moment where you feel like you are at the podium and don’t want to forget anyone. Firstly, I would like to thank my fellow Ajaxians: Ben Galbraith, Justin Gehtland, Stu Halloway, Rob Sanheim, Michael Mahemoff, and the entire community that visits and contributes to ajax- ian.com. This book is really for you, the readers. Secondly, I would like to thank all of the great technical folk who I have had the pleasure of working with. This includes buddies from Adigio, the No Fluff Just Stuff tour, and the general blogosphere. You know who you are. Finally, I would like to thank my family, especially my wife, Emily, who lets me work crazy hours without putting me through guilt trips. You are my best friend, Em.
  • 9. Chapter 1 Building Rich Internet Applications with Ajax This is a book about developing effective web applications. We’re not going to dance around this issue. Underneath everything else, this book is about XHTML, JavaScript, CSS, and standards that have been around for almost a decade now. Not only do we admit this truth, we embrace it. Just because these standards have been around for a while doesn’t mean we can’t build something new and exciting out of them. Technology, like Jello, takes a while to solidify into something tasty and satisfying. Ajax (and Web 2.0) represents the maturation of Internet standards into a viable application development platform. The combination of stable standards, better understanding, and a unifying vision amount to a whole that is greater, by far, than the sum of its parts. With Ajax, you’ll be able to achieve the double Holy Grail: feature-filled user interfaces and a no-hassle, no-install deployment story. It wasn’t long ago that Jesse James Garrett coined the term Ajax. When he first released the term onto the public consciousness, it stood for Asynchronous JavaScript And XML. It has since, like SOAP before it, lost its acronym status and is just a word. However, it is an enormously powerful word. With this single word, Jesse James was able to harness an industry-wide trend toward richer, install-free web applications and give it focus. Naming a thing is powerful. In this case, it’s not powerful enough to become a movement, though. A spark was still lacking. It was to be
  • 10. A T ALE IN T HREE A CTS 2 provided by an entirely unlikely entity. What follows is the story of one development team, that spark, and how it changed the way we approach web software. 1.1 A Tale in Three Acts Hector is a project manager for a web application development shop. With a long history of Perl, CGI, ASP, Servlet, and JSP development under his belt, Hector’s been around the block. For the last year his team has been building a CRM application for a large Fortune 500 com- pany with offices all over the world. The application used to be a green- screen mainframe application; the company wants to take advantage of the great reach of the Internet to deploy the application to every office. Hector and his team focus a lot of their energy on the server side of the application. They have been using one of the modern MVC frame- works from the Java community to implement the business logic, a high-performance persistence framework to access the database, and messaging-based infrastructure to connect to other existing systems. Yesterday On the client side, Hector and his team have become masters of CSS. The look of the pages bends to their will; when the customer wants rounded corners, they get rounded corners. Rollover colors? That’s easy. Multiple color schemes? No problem. In fact, Hector and his team long ago reached a point where they weren’t really worried about the user interface. See, the Web operates one way: it essentially distributes static documents. When users want more data, they incur a complete interface refresh. It isn’t optimal from an efficiency perspective, but it’s how the Web works, and users have just learned to live with it. Then, sometime a couple of weeks ago, Hector’s customer came to a meeting. The customer was usually a polite, accommodating fellow. He understood the Web, and he understood the restrictions he had to live with to get the reach of the Internet. In fact, Hector had never seen him get really angry. Until this meeting. As soon as he walked in, the team knew something was up. He had his laptop with him, and he never carried it. As he stormed into the room, the team glanced around the table: what have we done? The customer sat down at the table, fired up the laptop, and hammered away at the keyboard for a minute. While he pounded the keys, he told the team,
  • 11. A T ALE IN T HREE A CTS 3 “Last night, my wife and I were invited to a party at the CEO’s house.” “Uh oh,” thought the team, “this can’t be good.” “Well, I certainly jumped at the chance,” he continued. “I’ve never been before. This project got me on his radar.” (“Double uh-oh,” thought Hector.) “When I couldn’t figure out how to get there with my city map, I went to the Internet. I found THIS!” He hissed the last word with venom and scorn. He flipped the laptop around so the table could see it. There, quietly couched in his browser window, was Google Maps. “Why,” he said, through clenched teeth, “can’t I have this?” Today Since that meeting, Hector and his team have been rethinking the user interface. Hector went out to learn how Google could have completely ignored conventional wisdom and generated such a thing. He came across an article by Jesse James Garrett describing this thing called Ajax. He has been digging since then, learning everything he can about this new way of making Internet applications. The team has begun reimplementing the UI. They’re using JavaScript and DHTML techniques to provide a more dynamic experience. Most of all, they’ve begun taking advantage of a useful object available in modern browsers called XMLHttpRequest (XHR for short). This handy little guy lets Hector and his team request and receive fresh data from the server without reloading everything in the page. In other words, Hector spearheaded a move from Web 1.0 to Web 2.0. And his customer is happy again. Tomorrow So what comes next for Hector? His team is learning a bunch about JavaScript, XHTML, and even more about CSS than it ever knew before. The team is really excited about the results: the user experience is just like any other application now, except the team doesn’t have to manage an installer as well as the application itself. But they’ve realized that there’s a downside to all this. Now, they are writing a ton of code in JavaScript. It turns out that all this page manipulation and XHR access requires a lot of real, honest- to-goodness code. And even though JavaScript looks a lot like Java, they’ve discovered that it really is a different beast. And now they have two codebases to manage, test, and maintain.
  • 12. G OOGLE M APS : T HE M ISSING S PARK 4 So Hector is off to find out how to solve these problems. And what he will see is that most web application development frameworks are rapidly incorporating Ajax tools into their own suites. Soon, Hector and his team will be able to leverage Tapestry components, Spring tag libraries, ASP.NET widgets, Rails helpers, and PHP libraries to take advantage of Ajax without having to incorporate a second way of work- ing. The (near) future of Ajax development is total, invisible integration. And this is exactly what Hector needs. 1.2 Google Maps: The Missing Spark Google Maps (http://maps.google.com) really ignited the Ajax fire. And Google was just about the most unlikely candidate to do it. Think about what made Google an overnight sensation in the first place: bet- ter search results and the world’s most minimal UI. It was a white page, with a text box and a button in the middle of it. It doesn’t get any more minimal than that. If Google had had a soundtrack, it would have been written by Philip Glass. When it became obvious that Google was going to enter the online map- ping space, we all expected something similar: a straightforward, unin- trusive approach to viewing maps. And this is what we got; just not the way we expected. Google, through the clever use of XHR callbacks, provided the first in-page scrollable map. If you wanted to look at the next grid of map panels, Google went off and retrieved them and just slid the old ones out of the way. No messy page refresh; no reloading of a bunch of unchanged text. Particularly, no waiting around for a bunch of ads to refresh. It was just a map, the way a map ought to work. Then we clicked on a push pin and got the info bubble. With live text in it. And a drop shadow. And that was the end of an era. We’ve been told the same story that you just lived through with Hector again and again. Somebody’s boss or customer or colleague sees Google Maps and says, “Why not me?” As programmers, too, there’s another reaction: “I wish I could work on that kind of application.” There’s an impression out there that Google Maps, and applications like it, are rocket science and that it takes a special kind of team, and a special kind of developer, to make them happen. This book, if nothing else, will lay to rest that idea. As we’ll demonstrate in Chapter 2, Creating Google Maps, on page 9, making web pages sing and dance isn’t all that challenging once you know what
  • 13. W HAT I S A JAX ? 5 tools are available. It becomes even more impressive once you discover that Google Maps isn’t really proper Ajax; it doesn’t take advantage of any of the modern asynchronous callback technology and is really just dynamic HTML trickery. 1.3 What Is Ajax? Ajax is a hard beast to distill into a one-liner. The reason it is so hard is because it has two sides to it: • Ajax can be viewed as a set of technologies. • Ajax can be viewed as an architecture. Ajax: Asynchronous JavaScript and XML The name Ajax came from the bundling of its enabling technologies: an asynchronous communication channel between the browser and server, JavaScript, and XML. When it was defined, it was envisioned as the following: • Standards-based presentation using XHTML and CSS • Dynamic display and interaction using the browser’s Document Object Model (DOM) • Data interchange and manipulation using XML and XSLT • Asynchronous data retrieval using XMLHttpRequest or XMLHTTP (from Microsoft) • JavaScript binding everything together Although it is common to develop using these enabling technologies, it can quickly become more trouble than reward. As we go through the book, we will show you how you can do the following: • Incorporate Ajaxian techniques that do not use formal XML for data transport • Bypass the DOM APIs themselves for manipulating the in-memory page model • Use synchronous calls to the server, which can be powerful but is also dangerous • Abstract away the complexity of XMLHttpRequest It is for these reasons that the more important definition for Ajax is...
  • 14. W HAT I S A JAX ? 6 Ajax: The Architecture The exciting evolution that is Ajax is in how you architect web applica- tions. Let’s look first at the conventional web architecture: 1. Define a page for every event in the application: view items, pur- chase items, check out, and so on. 2. Each event, or action, returns a full page back to the browser. 3. That page is rendered to the user. This seems natural to us now. It made sense at the beginning of the Web, as the Web wasn’t really about applications. The Web started off as more of a document repository; it was a world in which you could simply link between documents in an ad hoc way. It was about document and data sharing, not interactivity in any meaningful sense. Picture a rich desktop application for a moment. Imagine what you would think if, on every click, all of the components on the application screen redrew from scratch. Seems a little nuts, doesn’t it? On the Web, that was the world we inhabited until Ajax came along. Ajax is a new architecture. The important parts of this architecture are: • Small server-side events: Now components in a web application can make small requests back to a server, get some information, and tweak the page that is viewed by changing the DOM. No full page refresh. • Asynchronous: Requests posted back to the server don’t cause the browser to block. The user can continue to use other parts of the application, and the UI can be updated to alert the user that a request is taking place. • onAnything: We can interact with the server based on almost any- thing the user does. Modern browsers trap most of the same user events as the operating system: mouseovers, mouse clicks, key- presses, etc. Any user event can cause an asynchronous request. In Figure 1.1, on the next page, we illustrate the new life cycle of an Ajax page. 1. The user makes an initial request against a given URL. 2. The server returns the original HTML page. 3. The browser renders the page as in-memory DOM tree.
  • 15. W HAT I S A JAX ? 7 R e n d e r e d i n B r o w s e r R u n n i n g o n S e r v e r H T M L 1 2 3 A D D Y H E s c r i p t s / s e r v l e t s / 4 N K D D T I T L E L I I V B O I V p a g e s 6 D S P A N S P A N I V 5 S P A N S P A N Figure 1.1: Ajax Page Lifecycle 4. Some user activity causes an asynchronous request to another URL, leaving the existing DOM tree untouched. 5. The browser returns data to a callback function inside the existing page. 6. The browser parses the result and updates the in-memory DOM with the new data. This is reflected on the screen to the user (the page is redrawn but not “refreshed”). This all sounds great, doesn’t it? With this change we have to be care- ful, though. One of the greatest things about the Web is that anybody can use it. Having simple semantics helps that happen. If we go over- board, we might begin surprising the users with new UI abstractions. This is a common complaint with Flash UIs, where users are confronted with new symbols, metaphors, and required actions to achieve useful results. Usability is an important topic that we will delve into in Chap- ter 7, Ajax UI, Part II , on page 122. Ajax: The Future Where is Ajax going? What is the future going to hold? This is a vital question, because Ajax is one of those amorphous terms that seems to change with the context. Ajax itself is a unifying term for describing a collection of technologies. We believe that the term itself, as unify-
  • 16. W HITHER N OW ? 8 ing and rallying as it has been, is likely to disappear from the public consciousness within the next couple of years. That’s because the technologies you will learn about in this book will eventually become the substrate of your favorite web application devel- opment platform. Instead of representing this brave new world of shiny gadgets and nifty tricks, it will just be how web apps work. Does this mean that this book is unimportant? Far from it. You need to under- stand how this works now to get it done, and you’ll need to understand it in the future to debug your applications. But you probably won’t think of those apps as Ajax, just as Web apps. And that’s a good thing. 1.4 Whither Now? The rest of this book will introduce you to the breadth of the Ajax move- ment. We’ll walk through the conversion of an application to this new style and provide deep coverage of the enabling technologies behind Ajax. We’ll introduce you to commonly available toolsets and frame- works that make seemingly advanced effects as simple as a single line of code. You’ll get to see what your favorite development platforms are doing to take advantage of, and integrate with, this new style of devel- opment. Most important, we’ll talk a lot about how to use Ajax effectively, prag- matically, even. That’s because the only thing worse than being left behind when the train leaves the station is getting on the wrong train. We intend this book to be a guide through a new and rapidly evolving landscape. We want to help you find out how, and even if, Ajax can help your projects. We’re not trying to sell you anything (except this book). But we believe that Ajax represents a major event, and we want to be there to help you make the best of it. But let’s start with the spark that ignited the fire: Google Maps.
  • 17. Chapter 2 Creating Google Maps For many of us, Google Maps (http://maps.google.com) ignited the Ajax revolution. While Ajaxian techniques had been creeping into main- stream websites long before Google Maps, nothing in recent memory presented commodity browsers with such a visually impressive experi- ence. Google Maps showed the world that a wide world of potential lay hidden in the technologies we thought we understood so well. As we said in Chapter 1, Ajax was initially defined as the intersection of the XMLHttpRequest object and the usage of XML to update a DOM tree. However, the current definition of Ajax (and Web 2.0) spans much more. This chapter demonstrates the underpinnings of Google Maps and how modern browser-based applications can use nothing but stan- dard HTML and JavaScript to achieve entirely new kinds of web apps. The purpose of this chapter is to lay bare the techniques that Google used to wow us all with Google Maps. What we’ll discover here is fas- cinating and important; it also might be more than you want to bite off right now. If so, don’t worry about skipping ahead to the rest of the book and coming back here later; we won’t mind. This chapter contains a lot of code. It’s all available online, so you can download the archives containing all the book’s source.1 Alterna- tively, if you’re reading the PDF version of this book, just click a link to get to the file. However, if the file you’re fetching contains HTML, it’ll probably get rendered by your browser. This is good if you want to see the running application. If instead you want to see the code, use your browser’s View Source option. 1 From http://pragmaticprogrammer.com/titles/ajax/code.html
  • 18. R OCKET S CIENTISTS ? 10 2.1 Rocket Scientists? Shortly after Google Maps launched, entrenched commercial interests who relied upon the staidness of standard HTML-based web interfaces to make money were quick to claim that mainstream HTML developers need not attempt to create web interfaces like Google Maps. The CEO of Macromedia, maker of the popular Flash browser plug-in, stated in at least one interview that such non-Flash web interfaces required the skills of “rocket scientists.” (Ironically, when Macromedia finally produced a clone of Google Maps in Flash four or five months later, it failed to function on the two Mac laptops we used to try it out—actually locking up the browser. Google Maps works just fine on both machines. We’re actually not anti-Flash; we just found it ironic, that’s all.) Such statements have added to the general impression many develop- ers have that creating something like Google Maps is just, well, hard. In fact, some developers have even felt a little fear and intimidation— fear that someday soon, they’ll be asked to create something like Google Maps! Certainly many of us who have been writing HTML for years might like to believe that it took a team of rocket scientists to produce a litany of innovations supporting the technologies behind the Google Maps inter- face, if nothing else to provide an excuse as to why we haven’t been writing apps like that all this time. However, we believe all this busi- ness about rocket science and intimidation is a bit exaggerated. In fact, after spending ten minutes examining Google Maps a bit deeper, we realized that, far from being the product of rocket scientists, the Google Maps interface is actually fairly straightforward to implement. Perhaps, some might say, easy. Not “same-amount-of-effort-as-a-PHP- web-form” easy, but we were able to implement something a great deal like it in about two hours. And this wasn’t just any two hours, mind you; it was two hours of sitting in a crowded convention center during a technical conference whilst being interrupted by our friends every few minutes. So while there’s no doubt Google has recently hired some of the most visible computer scientists—perhaps the closest examples of rocket scientist—like brainpower in our industry, such as Adam Bosworth (famed Microsoft innovator), Joshua Bloch (famed Java innovator at Sun Microsystems), and Vint Cerf (famed Internet innovator)—we’re pretty sure they weren’t involved in the creation of the Google Maps
  • 19. Y OUR O WN G OOGLE M APS 11 The Real Rocket Science OK, OK we admit—it isn’t easy to create something like Google Maps. The geocoding features behind the scenes that map addresses to locations on a map, that normalize a maps fea- tures against satellite imagery to such an amazing degree that they can be overlaid on top of each other and look relatively accurate, and the plotting of routes from Point A to Point B are all incredibly nontrivial. However, we maintain that it’s not the geocoding features of Google Maps that is particularly innovative or impressive. MapQuest and other software packages have been doing this kind of work for years. No, what’s impressive about Google Maps is the web interface on top of the geocoding engine. And it’s that interface that we find easy, not the geocoding under the covers. As our good friend Glenn Vanderburg says, though: “Techni- cally it’s easy, but the conception of this kind of interface is the really amazing part, just having the idea and then real- izing that it could be done. So many things are simple once you’ve seen that they’re possible.” The take-home lesson is that Google Maps shows that once you have conceived of your next great UI idea, you can take comfort in knowing that the technical solution to implementing it might not be so daunting. interface. (We should say, though, that we stand in awe of Lars Ras- mussen and his team for being the brains and fingers behind Google Maps.) The reality is if we can create an interface like Google Maps in a couple of hours, imagine what a few capable web developers could do in a few weeks or a month. 2.2 Your Own Google Maps In fact, we’ll spare you from putting your imagination to the test. Let us show you firsthand how you can create your own version of Google Maps. In the next few pages, we’ll walk you through the creation of Ajaxian Maps, our own derivative of the big GM. We’ll start out by explaining how the Google Maps user interface works.
  • 20. Y OUR O WN G OOGLE M APS 12 Figure 2.1: Google Maps Google Maps Deconstructed We’re going to break down the elements of Google Maps one by one. Let’s start out with the most dramatic feature: the big scrolling map, the heart of the application. The Map As you know, the map works by allowing you to interactively move the map by dragging the map using the mouse. We’ve seen mouse dragging in browsers for years, but the impressive bit is that the scrolling map is massive in size, can have the zoom level changed and so forth. How do they do that? Of course, the browser could never fit such a large map in memory at once. For example, a street-level map of the entire world would prob-
  • 21. Y OUR O WN G OOGLE M APS 13 More Than A Million Pixels We say in “The Map” section that a street-level map of the world would be about a million square pixels. Actually, that number’s a wild underestimate. At Google’s highest level of magnification, a square mile consumes about 7,700,000 pixels. The Earth is estimated to contain 200,000,000 square miles, but only 30% of that is land, so let’s reduce the number to 60,000,000 square miles. Multiplying the number of pixels by the number of square miles in the Earth produces the mind boggling number of 462 million million pixels, which at 16.7 million colors (the color depth of any modern home computer) would consume at least three times that amount of memory in bytes. Of course, most image viewing programs have some sort of paged memory subsystem that views a portion of the image at any one time, but you get the idea.... ably be about a million pixels square. How much memory would it take to display that map? For the sake of conversation, let’s assume that the map is displayed with just 256 colors, meaning each pixel would consume just 1 byte of memory. Such a map would require 1,000,000,000,000 bytes of memory, or roughly 1 terabyte (1000 giga- bytes) of RAM. So, simply displaying an <img> element just isn’t going to work. What the Googlers do to work around the paltry amount of memory our desktop PCs have is split up the map into various tiles. These tiles are laid out contiguously to form one cohesive image. Figure 2.2, on the next page, shows an example of these tiles. While the size of these tiles has changed, the current size is 250 pixels square. The tiles themselves are all laid out within a single HTML div element, and this div element is contained within another div; we’ll call these two divs the inner and outer divs, respectively. We mentioned just a moment ago that the browser couldn’t fit the entire map image in memory. Of course, dividing a single map into an arbi- trary number of tiles and then displaying all those tiles at once would consume an equal amount of memory as the entire image. To compen- sate for memory limitations, Google Maps virtualizes the grid of tiles
  • 22. Y OUR O WN G OOGLE M APS 14 Figure 2.2: Google Maps Tiles in memory and displays only the set of tiles that the user can see, in addition to a few additional tiles outside of the viewing area to keep the scrolling smooth. If this whole grid virtualization mishmash sounds a little complex, don’t worry; it’s fairly straightforward, though it is the most complicated bit of the UI. Zoom Level Another key feature of Google Maps is the ability to zoom in and out, enlarging or reducing the size of the map, which lets you get a view of the entire world at one moment and a view of your street the next. This is actually the simplest of the features to implement. Changing the zoom level just changes the size of the tile grid in memory as well as the URLs of the tile images that are requested. For example, the URL to one of the tiles in Figure 2.2 is as follows: http://mt.google.com/mt?v=w2.5&n=404&x=4825&y=6150&zoom=3 By changing the value of the zoom parameter to another value, such as 1, you can retrieve a tile at a different zoom level. In practice, it’s not quite that simple because the grid coordinates change rather a great deal with each zoom level and they often become invalid.
  • 23. Y OUR O WN G OOGLE M APS 15 Figure 2.3: The Google Maps Push Pin and Dialog How do they get the zoom level to constantly hover over the map in a constant position? The zoom level widget is an image embedded in the outer div, and makes use of transparency to blend in with the map image. Push Pins and Dialogs Other neat-o features are the push pins and dialogs that appear after a search. Figure 2.3 shows these elements. These are especially cool because they both include rounded edges and shadows that make them blend in with the background map in a sophisticated fashion. We said the zoom level was the easiest feature, and frankly, we were probably wrong. This is ridiculously easy. The push pins and dialogs are simply a PNG image. The PNG image format is supported by the major browsers and supports a nice feature called alpha transparency. alpha transparency Alpha transparency allows for more than just the simple transparency that GIF images support; it allows a pixel to be one of 254 different values between fully transparent and fully opaque, and it’s this gradient transparency support that allows the push pins and dialog to use a shadow that blends in with the map.
  • 24. C REATING A JAXIAN M APS 16 Showing these features is simply a matter of positioning images in the inner div at an absolute position. Feature Review There are other features, of course. But we’ll stick to the set of features we’ve enumerated; we think these represent the vast majority of the “ooh, ahh” factors. In review, they were as follows: • The scrolling map: This is implemented as an outer div containing an inner div. Mouse listeners allow the inner div to be moved within the confines of the outer div. Tiles are displayed as img elements inside the inner div, but only those tiles necessary to display the viewing area and a buffer area around it are present in the inner div. • The zoom level: This is an image embedded in the outer div. When clicked, it changes the size of the grid representing the tiles and changes the URL used to request the tiles. • The push pins and dialogs: These are PNG images with alpha transparency, placed in absolute positions within the inner div. Now that we’ve deconstructed Google Maps a bit, let’s set about imple- menting it. 2.3 Creating Ajaxian Maps Because Ajaxian Maps won’t bother with all of that geocoding mumbo jumbo, all of our heavy lifting will be in JavaScript. However, we will use Java to provide some server features and a few image manipulation tasks. IE 6, Firefox 1.x, and Safari 2.x Only We’ve tested this version of Ajaxian Maps in the three major browsers but haven’t bothered with older versions and more obscure browsers (sorry, Opera users). It should work on older platforms, but without testing, we can’t be sure we’ve caught everything. Step 1: Create a Map The first step in displaying a map is, err, creating it. While we could simply steal the wonderful map that Google Maps uses, Google might
  • 25. C REATING A JAXIAN M APS 17 not appreciate that. So, we’ll go ahead and use a map that is explicitly open source. The Batik project (http://xml.apache.org/batik), an open- source Java-based SVG renderer, comes with an SVG map of Spain. We’ll use that. Because most browsers don’t provide native support for SVG, we’ll need to convert the map to a bitmap-based format. Fortunately, Batik can do that for us. One of the nice features of SVG is that it can scale to arbitrary sizes, so we could conceivably create a huge image for our map. However, creating truly huge images is a little tricky; because of memory limitations, we’d have to render portions of the SVG image, generate our tiles over the portions, and have some sort of scheme for unifying everything together. To keep this chapter simple, we’ll just limit our map to 2,000 pixels in width and 1,400 pixels in height. In order to implement zooming, we’ll also generate a smaller image that represents a view of the map in a zoomed-out mode. The following code excerpt shows how to use Batik to convert the map of Spain into both a 2000x1400 pixel JPG file and a 1500x1050 pixel JPG file: File 31 package com.ajaxian.amaps; import org.apache.batik.apps.rasterizer.DestinationType; import org.apache.batik.apps.rasterizer.SVGConverter; import java.io.File; public class SVGSlicer { private static final String BASE_DIR = "resources/"; public static void main(String[] args) throws Exception { SVGConverter converter = new SVGConverter(); // width in pixels; height auto-calculated converter.setWidth(2000); converter.setSources(new String[] { BASE_DIR + "svg/mapSpain.svg" }); converter.setDst(new File(BASE_DIR + "tiles/mapSpain.jpg")); converter.setDestinationType(DestinationType.JPEG); converter.execute(); converter.setWidth(1500); converter.setDst(new File(BASE_DIR + "tiles/mapSpain-smaller.jpg")); converter.execute(); } } To compile the code, you’ll need to put the Batik JARs in your classpath
  • 26. C REATING A JAXIAN M APS 18 Figure 2.4: Batik’s SVG Spain Map (everything in BATIK_HOME and BATIK_HOME/lib) and place the source code in the following directory hierarchy: com/ajaxian/amaps. Figure 2.4 shows what either map JPG file should look like. You can also replace the value of the BASE_DIR variable with whatever is most convenient for you. Step 2: Create the Tiles Now that we have a map at two different zoom levels, we need to slice it up into tiles. This is pretty easy with the nice image manipulation libraries available in many programming languages. We’ll demonstrate how to do that with Java here: File 30 package com.ajaxian.amaps; import org.apache.batik.apps.rasterizer.DestinationType; import org.apache.batik.apps.rasterizer.SVGConverter; import javax.imageio.ImageIO; import java.io.File; import java.awt.*;
  • 27. C REATING A JAXIAN M APS 19 import java.awt.image.BufferedImage; public class ImageTiler { private static final String BASE_DIR = "resources/"; private static final int TILE_WIDTH = 100; private static final int TILE_HEIGHT = 100; public static void main(String[] args) throws Exception { // create the tiles String[][] sources = { { "tiles/mapSpain.jpg", "0" }, {"tiles/mapSpain-smaller.jpg", "1"} }; for (int i = 0; i < sources.length; i++) { String[] source = sources[i]; BufferedImage bi = ImageIO.read(new File(BASE_DIR + source[0])); int columns = bi.getWidth() / TILE_WIDTH; int rows = bi.getHeight() / TILE_HEIGHT; for (int x = 0; x < columns; x++) { for (int y = 0; y < rows; y++) { BufferedImage img = new BufferedImage(TILE_WIDTH, TILE_HEIGHT, bi.getType()); Graphics2D newGraphics = (Graphics2D) img.getGraphics(); newGraphics.drawImage(bi, 0, 0, TILE_WIDTH, TILE_HEIGHT, TILE_WIDTH * x, TILE_HEIGHT * y, TILE_WIDTH * x + TILE_WIDTH, TILE_HEIGHT * y + TILE_HEIGHT, null); ImageIO.write(img, "JPG", new File(BASE_DIR + "tiles/" + "x" + x + "y" + y + "z" + source[1] + ".jpg")); } } } } } Note that to make things interesting, we made our tile size a bit smaller than Google Maps: 100 pixels square. We chose x0y0z0.jpg as the nam- ing convention for the tiles, where the zeros are replaced with the x and y grid coordinates (0-based) and the zoom level (0 or 1; 0 is for the bigger of the two maps). Step 3: Creating the Inner and Outer Divs Now that we have the image tiles, we can start building our map UI. We’ll start with a simple web page, shown here: File 32 Line 1 <html> - <head> - <title>Ajaxian Maps</title> - <style type="text/css"> 5 h1 {
  • 28. C REATING A JAXIAN M APS 20 Figure 2.5: Humble Beginnings - font: 20pt sans-serif; - } - #outerDiv { - height: 600px; 10 width: 800px; - border: 1px solid black; - position: relative; - overflow: hidden; - } 15 </style> - </head> - <body> - <p> - <h1>Ajaxian Maps</h1> 20 </p> - <div id="outerDiv"> - </div> - </body> - </html> Figure 2.5 show this page. Pretty simple so far. Let’s get to the good stuff. The div on line 21 will become what we’ve called the outer div. The outer div is the visible window into the tiles and will be entirely contained in the visible space within the browser. The inner div, on the other hand, will contain all the tiles and be much larger than the available visible space. Let’s start out by giving it an inner div with some simple content: File 33 <html> <head> <title>Ajaxian Maps</title> <style type="text/css"> h1 { font: 20pt sans-serif; }
  • 29. C REATING A JAXIAN M APS 21 #outerDiv { height: 600px; width: 800px; border: 1px solid black; position: relative; overflow: hidden; } #innerDiv { position: relative; left: 0px; top: 0px; } </style> </head> <body> <p> <h1>Ajaxian Maps</h1> </p> <div id="outerDiv"> <div id="innerDiv"> The rain in Spain falls mainly in the plains. </div> </div> </body> </html> Now we need to make the inner div large enough to contain all of the image tiles. We could just set a style on the inner div to make it some arbitrary size, as in <div style="width: 2000px; height: 1400px">, but we’ll do this via JavaScript. Why? Well, because we’ll implement the ability to change zoom levels a little later, we know we’ll have to change the size of the inner div dynamically anyway, so we might as well start out that way. We’ll use an onload JavaScript handler to initialize the size of the inner div once we load the page. Check out the code: File 34 <html> <head> <title>Ajaxian Maps</title> <style type="text/css"> h1 { font: 20pt sans-serif; } #outerDiv { height: 600px; width: 800px; border: 1px solid black; position: relative; overflow: hidden; }
  • 30. C REATING A JAXIAN M APS 22 #innerDiv { position: relative; left: 0px; top: 0px; } </style> <script type="text/javascript"> function init() { setInnerDivSize( ' 2000px ' , ' 1400px ' ) } function setInnerDivSize(width, height) { var innerDiv = document.getElementById("innerDiv") innerDiv.style.width = width innerDiv.style.height = height } </script> </head> <body onload="init()"> <p> <h1>Ajaxian Maps</h1> </p> <div id="outerDiv"> <div id="innerDiv"> The rain in Spain falls mainly in the plains. </div> </div> </body> </html> OK, now we’ve got an inner div big enough to display the tiles for the largest of our two maps. Now we need to add the dragging functionality. Step 4: Dragging the Map We’ll implement dragging using three different mouse event listeners. When the user clicks the mouse in the map area, we’ll use a listener to indicate that a drag operation has started. Now, if the user moves the mouse, we’ll use a listener to move the inner div along with the user’s mouse movements to create the dragging effect. Finally, we’ll use a listener to turn off the dragging operation when the mouse is released. The following code demonstrates how we implemented the listeners: File 35 // used to control moving the map div var dragging = false; var top; var left; var dragStartTop; var dragStartLeft;
  • 31. C REATING A JAXIAN M APS 23 function init() { // make inner div big enough to display the map setInnerDivSize( ' 2000px ' , ' 1400px ' ); // wire up the mouse listeners to do dragging var outerDiv = document.getElementById("outerDiv"); outerDiv.onmousedown = startMove; outerDiv.onmousemove = processMove; outerDiv.onmouseup = stopMove; // necessary to enable dragging on IE outerDiv.ondragstart = function() { return false; } } function startMove(event) { // necessary for IE if (!event) event = window.event; dragStartLeft = event.clientX; dragStartTop = event.clientY; var innerDiv = document.getElementById("innerDiv"); innerDiv.style.cursor = "-moz-grab"; top = stripPx(innerDiv.style.top); left = stripPx(innerDiv.style.left); dragging = true; return false; } function processMove(event) { if (!event) event = window.event; // for IE var innerDiv = document.getElementById("innerDiv"); if (dragging) { innerDiv.style.top = top + (event.clientY - dragStartTop); innerDiv.style.left = left + (event.clientX - dragStartLeft); } } function stopMove() { var innerDiv = document.getElementById("innerDiv"); innerDiv.style.cursor = ""; dragging = false; } function stripPx(value) { if (value == "") return 0; return parseFloat(value.substring(0, value.length - 2)); }
  • 32. C REATING A JAXIAN M APS 24 If you run the code at this point, you’ll now be able to drag that inner <div> around. Step 5: Displaying the Map Tiles The next step requires us to populate our inner div with the map tiles. Our approach to this will be fairly simple. The scrolling map effect is achieved by moving an inner div inside of an outer div; therefore, the tiles we need to display are calculated by determining the current position of the inner div relative to the outer div and then working out which tiles are visible in the portion of the inner div that is visible. We’ll then add those tiles to the inner div. It turns out implementing this behavior is not terribly difficult. We’ll create the function checkTiles( ) to do all this and call it from within the processMove( ) function. processMove( ) is called when the user drags the map, so by calling it from within, we’ll be able to load our tiles as the map moves. The following code excerpt shows how we’ve added these elements to our JavaScript code; for now, checkTiles( ) is just stubbed out with comments: File 39 function processMove(event) { if (!event) event = window.event; // for IE var innerDiv = document.getElementById("innerDiv"); if (dragging) { innerDiv.style.top = top + (event.clientY - dragStartTop); innerDiv.style.left = left + (event.clientX - dragStartLeft); } checkTiles(); } function checkTiles() { // check which tiles should be visible in the inner div // add each tile to the inner div, checking first to see // if it has already been added } Now, let’s implement our stubbed-out checkTiles( ) function. Calculating the Visible Tiles Calculating the set of tiles that the user can see in the inner <div> is fairly straightforward. To understand how this works, it will help to visualize the inner div as a grid where each grid cell is a placeholder of the tiles that we’ll load. Figure 2.6 illustrates this concept.
  • 33. C REATING A JAXIAN M APS 25 Figure 2.6: The Tile Grid Because we can’t load all the tiles in the grid up front, we’ll need to calculate which of these grid cells are visible and load the tiles needed to fit into these cells. As Figure 2.6 shows, this is accomplished by calculating which grid cells are visible within the viewport created by the size of the outer div. In the figure, we see that nine cells are visible across three rows. Note that those cells that are only partially visible still count as being visible. Let’s see how to implement all this behavior we just described. To make things simple, we’ll encapsulate all of the code to figure out which tiles are visible in a particular method, which we’ll call getVisibleTiles( ). The first thing we need to figure out in getVisibleTiles( ) is the position of the inner div relative to the outer div. This is fairly easy:
  • 34. C REATING A JAXIAN M APS 26 function getVisibleTiles() { var innerDiv = document.getElementById("innerDiv"); var mapX = stripPx(innerDiv.style.left); var mapY = stripPx(innerDiv.style.top); } The stripPx( ) function, shown earlier, converts the string value returned by innerDiv.style.left (such as 100px) to a numeric value (say, 100). Now, we can divide these positions by the size of the tiles to work out the starting row and column of the tiles. This is just two lines of code: var startX = Math.abs(Math.floor(mapX / tileSize)) - 1; var startY = Math.abs(Math.floor(mapY / tileSize)) - 1; Note that we haven’t yet defined the tileSize variable; we’ll do that glob- ally (at the top of our JavaScript code), and you’ll see it when we show the entire page in just a few paragraphs. (Or, you can see it now on the following page.) The call to Math.floor( ) will round the quotient to an integer, discarding the remainder (so 1.4 will be rounded down to 1). This will cause partial tiles to be displayed. Math.abs( ) converts negative values to a positive number, which in our case is necessary because the inner div position will nearly always be negative to the outer div, and because our tile columns/rows are always positive numbers. Finally, we subtract 1 from the result to make our map load the tiles a touch early for a smoother effect. The final bit of calculation is to determine the number of rows and columns visible in the viewport: var tilesX = Math.ceil(viewportWidth / tileSize) + 1; var tilesY = Math.ceil(viewportHeight / tileSize) + 1; As with tileSize( ), we’ll declare both viewportWidth and viewportHeight as global variables and show that in just a bit. We use Math.ceil( ), the opposite of Math.floor( ) (so it rounds the quotient up regardless of the size of the remainder), to ensure that if any portion of a column or row is visible, we’ll display it. And, just as we subtracted 1 from the index of the tiles in the previous lines, we’ll add 1 to the number of columns and rows to make the scroll effect smooth. We now have all the data we need to calculate all of the visible tiles in the viewport plus, as we’ve discussed, a few around the edges that aren’t immediately visible but will be shortly. Now we’ll build an array that contains all of the tiles that need to be loaded. To build this array, we’ll write two for loops, one nested inside the other, that each perform an iteration for each column and row that is currently visible. Inside
  • 35. C REATING A JAXIAN M APS 27 each loop iteration, we’ll add the column and row number of each tile to display: var visibleTileArray = []; var counter = 0; for (x = startX; x < (tilesX + startX); x++) { for (y = startY; y < (tilesY + startY); y++) { visibleTileArray[counter++] = [x, y]; } } return visibleTileArray; Note that we’re actually creating a two-dimensional array; the value of each item in our array is another array. We did this because we need to pass back two values: the column and row index. And now, we’re done calculating the tiles that are visible in the inner div, and we can move on and work on the code to actually display them. But first, let’s review all of the code we’ve written so far: File 36 function checkTiles() { // check which tiles should be visible in the inner div var visibleTiles = getVisibleTiles(); // add each tile to the inner div, checking first to see // if it has already been added } function getVisibleTiles() { var innerDiv = document.getElementById("innerDiv"); var mapX = stripPx(innerDiv.style.left); var mapY = stripPx(innerDiv.style.top); var startX = Math.abs(Math.floor(mapX / tileSize)) - 1; var startY = Math.abs(Math.floor(mapY / tileSize)) - 1; var tilesX = Math.ceil(viewportWidth / tileSize) + 1; var tilesY = Math.ceil(viewportHeight / tileSize) + 1; var visibleTileArray = []; var counter = 0; for (x = startX; x < (tilesX + startX); x++) { for (y = startY; y < (tilesY + startY); y++) { visibleTileArray[counter++] = [x, y]; } } return visibleTileArray; }
  • 36. C REATING A JAXIAN M APS 28 Displaying the Visible Tiles We’ve now coded half of the checkTiles( ) function, which as you may recall is the function responsible for both calculating the visible tiles and displaying them. Now, let’s implement the other half of that func- tion: displaying the tiles. All we need to do here is iterate through each element of the array of visible tiles we returned from the getVisibleTiles( ) function and for each array element add a tile image to the inner div. Here’s the new code for our checkTiles( ) function: File 37 Line 1 function checkTiles() { - // check which tiles should be visible in the inner div - var visibleTiles = getVisibleTiles(); - 5 // add each tile to the inner div, checking first to see - // if it has already been added - var innerDiv = document.getElementById("innerDiv"); - var visibleTilesMap = {}; - for (i = 0; i < visibleTiles.length; i++) { 10 var tileArray = visibleTiles[i]; - var tileName = "x" + tileArray[0] + "y" + tileArray[1] + "z0"; - visibleTilesMap[tileName] = true; - var img = document.getElementById(tileName); - if (!img) { 15 img = document.createElement("img"); - img.src = "resources/tiles/" + tileName + ".jpg"; - img.style.position = "absolute"; - img.style.left = (tileArray[0] * tileSize) + "px"; - img.style.top = (tileArray[1] * tileSize) + "px"; 20 img.setAttribute("id", tileName); - innerDiv.appendChild(img); - } - } - } We start out on line 8 by creating an empty map (map in the JavaScript sense; a hash that contains key-to-value mappings). We’re going to add an entry to this map for each visible image; we’ll discuss why we’re doing this a little later. On line 9, we start looping through each element in the array we sent back from getVisibleTiles( ). For each element, we build the name of the image file that will be loaded in. (If you recall, the file-naming conven- tion we chose in Step 2 was x0y0z0, where the numbers are replaced with the index of the tile in the tile grid.) We also use this name as the key in the visibleTilesMap variable, and on lines 13 and 20 you can see
  • 37. C REATING A JAXIAN M APS 29 that we also use it as the id attribute for each img element that we add to the inner div. This is so on lines 13 and 14, we can check to see we’ve already added a given tile to the inner div and, if we have, avoid adding it again. Finally, in lines 15 through line 21, we create the <img> element and add it to the inner div. Note that on line 16 we have to specify the URL of the image tile. If you have Java installed and executed the code from Steps 1 and 2 to create your own image tiles, great! Reference them on line 16, setting the URI to wherever you put them. If not, you can reference our tiles online.2 You can now enjoy a scrolling map of Spain in your browser! We’ve placed a copy online at GoogleMaps/step5-3.html. Here’s all the code we’ve written so far: File 37 <html> <head> <title>Ajaxian Maps</title> <style type="text/css"> h1 { font: 20pt sans-serif; } #outerDiv { height: 600px; width: 800px; border: 1px solid black; position: relative; overflow: hidden; } #innerDiv { position: relative; left: 0px; top: 0px; } </style> <script type="text/javascript"> // constants var viewportWidth = 800; var viewportHeight = 600; var tileSize = 100; // used to control moving the map div var dragging = false; 2 GoogleMaps/resources/tiles/x0y0z0.jpg, where x0y0z0 should be replaced with the tile you want to load
  • 38. C REATING A JAXIAN M APS 30 var top; var left; var dragStartTop; var dragStartLeft; function init() { // make inner div big enough to display the map setInnerDivSize( ' 2000px ' , ' 1400px ' ); // wire up the mouse listeners to do dragging var outerDiv = document.getElementById("outerDiv"); outerDiv.onmousedown = startMove; outerDiv.onmousemove = processMove; outerDiv.onmouseup = stopMove; // necessary to enable dragging on IE outerDiv.ondragstart = function() { return false; } checkTiles(); } function startMove(event) { // necessary for IE if (!event) event = window.event; dragStartLeft = event.clientX; dragStartTop = event.clientY; var innerDiv = document.getElementById("innerDiv"); innerDiv.style.cursor = "-moz-grab"; top = stripPx(innerDiv.style.top); left = stripPx(innerDiv.style.left); dragging = true; return false; } function processMove(event) { if (!event) event = window.event; // for IE var innerDiv = document.getElementById("innerDiv"); if (dragging) { innerDiv.style.top = top + (event.clientY - dragStartTop); innerDiv.style.left = left + (event.clientX - dragStartLeft); } checkTiles(); } function checkTiles() { // check which tiles should be visible in the inner div var visibleTiles = getVisibleTiles();
  • 39. C REATING A JAXIAN M APS 31 // add each tile to the inner div, checking first to see // if it has already been added var innerDiv = document.getElementById("innerDiv"); var visibleTilesMap = {}; for (i = 0; i < visibleTiles.length; i++) { var tileArray = visibleTiles[i]; var tileName = "x" + tileArray[0] + "y" + tileArray[1] + "z0"; visibleTilesMap[tileName] = true; var img = document.getElementById(tileName); if (!img) { img = document.createElement("img"); img.src = "resources/tiles/" + tileName + ".jpg"; img.style.position = "absolute"; img.style.left = (tileArray[0] * tileSize) + "px"; img.style.top = (tileArray[1] * tileSize) + "px"; img.setAttribute("id", tileName); innerDiv.appendChild(img); } } } function getVisibleTiles() { var innerDiv = document.getElementById("innerDiv"); var mapX = stripPx(innerDiv.style.left); var mapY = stripPx(innerDiv.style.top); var startX = Math.abs(Math.floor(mapX / tileSize)) - 1; var startY = Math.abs(Math.floor(mapY / tileSize)) - 1; var tilesX = Math.ceil(viewportWidth / tileSize) + 1; var tilesY = Math.ceil(viewportHeight / tileSize) + 1; var visibleTileArray = []; var counter = 0; for (x = startX; x < (tilesX + startX); x++) { for (y = startY; y < (tilesY + startY); y++) { visibleTileArray[counter++] = [x, y]; } } return visibleTileArray; } function stopMove() { var innerDiv = document.getElementById("innerDiv"); innerDiv.style.cursor = ""; dragging = false; }
  • 40. C REATING A JAXIAN M APS 32 function stripPx(value) { if (value == "") return 0; return parseFloat(value.substring(0, value.length - 2)); } function setInnerDivSize(width, height) { var innerDiv = document.getElementById("innerDiv"); innerDiv.style.width = width; innerDiv.style.height = height; } </script> </head> <body onload="init()"> <p> <h1>Ajaxian Maps</h1> </p> <div id="outerDiv"> <div id="innerDiv"> The rain in Spain falls mainly in the plains. </div> </div> </body> </html> Cleaning Up Unused Tiles We’ve got some neat scrolling, but this has one glaring inefficiency. We add tiles to the inner div on demand, but we never remove the tiles that are no longer visible. Fortunately, we’ve already done some of the work to accommodate this feature. If you recall, we created a JavaScript map named visibleTilesMap in the checkTiles( ) function but never did anything with it. Now, we’re going to do something. After we add the image tiles to the inner div, we’ll select all of the img elements that are present in the inner div, and for each img element, we’ll check to see whether its id attribute is present in the visibleTilesMap variable. If so, we know that it’s a currently visible tile and should be left in the inner div. If not, the <img> is no longer visible and can be removed. Here’s the additional code in checkTiles( ) to implement this functionality: File 38 function checkTiles() { // check which tiles should be visible in the inner div var visibleTiles = getVisibleTiles(); // add each tile to the inner div, checking first to see // if it has already been added var innerDiv = document.getElementById("innerDiv");
  • 41. C REATING A JAXIAN M APS 33 var visibleTilesMap = {}; for (i = 0; i < visibleTiles.length; i++) { var tileArray = visibleTiles[i]; var tileName = "x" + tileArray[0] + "y" + tileArray[1] + "z0"; visibleTilesMap[tileName] = true; var img = document.getElementById(tileName); if (!img) { img = document.createElement("img"); img.src = "resources/tiles/" + tileName + ".jpg"; img.style.position = "absolute"; img.style.left = (tileArray[0] * tileSize) + "px"; img.style.top = (tileArray[1] * tileSize) + "px"; img.setAttribute("id", tileName); innerDiv.appendChild(img); } } var imgs = innerDiv.getElementsByTagName("img"); for (i = 0; i < imgs.length; i++) { var id = imgs[i].getAttribute("id"); if (!visibleTilesMap[id]) { innerDiv.removeChild(imgs[i]); i--; // compensate for live nodelist } } } Figure 2.7, on the next page, shows what this looks like. Step 6: Zooming Zooming is wicked easy; in fact, the hardest bit is just getting a zoom widget to appear floating above the map. First, we need to create some kind of image that the user can click on to enable zooming. In Google Maps, it’s a slider (shown in the margin here); for us, we’ll just create a simple image that toggles between our two zoom levels. You can use any image you like; ours is at GoogleMaps/resources/images/zoom.png. Second, to float the image above the map, we have to properly set the z-index of our inner div. Browsers support layering elements on top of each other; the z-index CSS property is used to determine how the layering occurs. The lower the value, the lower in the layer the element will appear. Because we want to put our zoom widget above the tile images, we’ll need to set the z-index of the inner div to 0. The z-index of the zoom widget then needs to be any value greater than 0 (we use 1). Now, let’s add the zoom widget. We’ll enclose it in a div, place it inside the outer div as a peer of the inner div, and we’ll set the z-index proper- ties appropriately:
  • 42. C REATING A JAXIAN M APS 34 Figure 2.7: Ajaxian Maps! File 40 Line 1 <body onload="init()"> - <p> - <h1>Ajaxian Maps</h1> - </p> 5 <div id="outerDiv"> - <div style="position: absolute; top: 10px; left: 10px; z-index: 1"> - <img src="resources/images/zoom.png" - onclick="toggleZoom()"/> - </div> 10 <div id="innerDiv" style="z-index: 0"> - The rain in Spain falls mainly in the plains. - </div> - </div> - </body> That will give us our floating zoom widget; now we need to create the toggleZoom( ) function that we referenced on line 8. This will require a
  • 43. C REATING A JAXIAN M APS 35 few minor changes to our code. First, we need to create some sort of global state that tracks the current zoom level of our map. Second, we need to reference this state in the various relevant places in our code (just one, actually). Let’s start with the global state. We’ll create a variable zoom to track the current zoom level and while we’re at it add a constant (in the form of a two-dimensional array) for declaring the two different sizes of the inner div: File 40 var zoom = 0; var zoomSizes = [ [ "2000px", "1400px" ], [ "1500px", "1050px" ] ]; Now, in the name of cleanliness, we’ll change the first line of our init method from this: File 38 setInnerDivSize( ' 2000px ' , ' 1400px ' ); to this: File 40 setInnerDivSize(zoomSizes[zoom][0], zoomSizes[zoom][1]); There’s just one other place we need to wire in the zoom support: our checkTiles( ) function, which creates the img elements for the tiles and gives them their URL. We need to change this hard-coded zoom-level code: File 38 var tileName = "x" + tileArray[0] + "y" + tileArray[1] + "z0"; to this: File 40 var tileName = "x" + tileArray[0] + "y" + tileArray[1] + "z" + zoom; All that remains is implementing the toggleZoom( ) function, which we’ve done here: File 40 function toggleZoom() { zoom = (zoom == 0) ? 1 : 0; var innerDiv = document.getElementById("innerDiv"); var imgs = innerDiv.getElementsByTagName("img"); while (imgs.length > 0) innerDiv.removeChild(imgs[0]); setInnerDivSize(zoomSizes[zoom][0], zoomSizes[zoom][1]); checkTiles(); } Nothing too tricky; we swap the value of the zoom variable from 0 to 1, delete all the <img> elements from the inner div, change the size of the
  • 44. C REATING A JAXIAN M APS 36 Figure 2.8: Ajaxian Maps Zoomed Out inner div based on the zoom level, and invoke checkTiles( ) to rebuild the map with the new zoom level’s tiles. And now, we have zooming in our map application! Cool. The code for this version is on-line if you need it.3 Figure 2.8 shows the zoom feature in action, with our map zoomed to the smaller size. Step 7: Push Pins and Dialogs The final feature is adding push pins with alpha transparency. When clicked, these show a dialog that also has alpha transparency. The 3 http://media.pragprog.com/titles/ajax/code/GoogleMaps/step6.html
  • 45. C REATING A JAXIAN M APS 37 hardest part is creating the images.4 These images will not render prop- erly in IE 6, but see the end of this section for a workaround. We’re not going to implement a server back end that does searching, and so on, so just as with zooming we implemented a toggle, we’ll imple- ment a toggle for our push pin. The graphic for the toggle is available at GoogleMaps/resources/images/pushpin.png. We’ll place the push pin toggle right next to the zoom toggle by adding a new div for it: File 42 <body onload="init()"> <p> <h1>Ajaxian Maps</h1> </p> <div id="outerDiv"> <div style="position: absolute; top: 10px; left: 10px; z-index: 1"> <img src="resources/images/zoom.png" onclick="toggleZoom()"/> </div> <div style="position: absolute; top: 10px; left: 87px; z-index: 1"> <img src="resources/images/pushpin.png" onclick="togglePushPin()"/> </div> <div id="innerDiv" style="z-index: 0"> The rain in Spain falls mainly in the plains. </div> </div> </body> Now we need to implement togglePushPin( ), which, frankly, is a piece of cake. We’ll just add an absolutely positioned image with a z-index of 1 to the inner div, add an onclick handler to it, and wire that handler to display the dialog at an absolute position just above the push pin: File 42 function togglePushPin() { var pinImage = document.getElementById("pushPin"); if (pinImage) { pinImage.parentNode.removeChild(pinImage); var dialog = document.getElementById("pinDialog"); dialog.parentNode.removeChild(dialog); return; } var innerDiv = document.getElementById("innerDiv"); pinImage = document.createElement("img"); pinImage.src = "resources/images/pin.png"; pinImage.style.position = "absolute"; pinImage.style.left = (zoom == 0) ? "850px" : "630px"; pinImage.style.top = (zoom == 0) ? "570px" : "420px"; 4 GoogleMaps/resources/images/pin.png and GoogleMaps/resources/images/dialog.png.
  • 46. C REATING A JAXIAN M APS 38 pinImage.style.zIndex = 1; pinImage.setAttribute("id", "pushPin"); innerDiv.appendChild(pinImage); var dialog = document.createElement("div"); dialog.style.position = "absolute"; dialog.style.left = (stripPx(pinImage.style.left) - 90) + "px"; dialog.style.top = (stripPx(pinImage.style.top) - 210) + "px"; dialog.style.width = "309px"; dialog.style.height = "229px"; dialog.style.backgroundImage = "url(resources/images/dialog.png)"; dialog.style.zIndex = 2; dialog.setAttribute("id", "pinDialog"); dialog.innerHTML = "<table height= ' 80% ' width= ' 100% ' >" + "<tr><td align= ' center ' >The capital of Spain</td></tr></table>"; innerDiv.appendChild(dialog); } There’s just one little problem with this new behavior. Do you remem- ber the image remover code in checkTiles( )? It removes any img element child of the inner div that has been explicitly added to that function. Of course, it will clobber our push pin as well, since it is an img child of the inner div, so we need to modify the function to ignore the push pin: File 42 var imgs = innerDiv.getElementsByTagName("img"); for (i = 0; i < imgs.length; i++) { var id = imgs[i].getAttribute("id"); if (!visibleTilesMap[id]) { if (id != "pushPin") { innerDiv.removeChild(imgs[i]); i--; // compensate for live nodelist } } } We’re done! We’ve implemented all of the features we discussed in the introduction of this chapter. Let’s wrap up by...err, wait a second. While Firefox, Safari, and other browsers provide native support for PNGs with alpha transparency, IE 6 does not. If you’ve been using that browser to try this sample code, the zoom and push pin buttons as well as the push pin and dialog itself have looked really awful. Fortunately, this has an easy (but annoying) fix. Despite not supporting PNGs out of the box, IE can use some (IE-specific) JavaScript magic to parse out the alpha channel from a PNG at runtime and display it correctly. A number of websites document this workaround; in order to avoid sidetracking our Google Maps story, we’ll just use a JavaScript
  • 47. C REATING A JAXIAN M APS 39 library provided by one of these websites, www.alistapart.com,5 to solve our problem. First, we need to include these new JavaScripts in our webpage, which we’ll do at the top: File 41 <script language="javascript" src="resources/js/browserdetect_lite.js" type="text/javascript"> </script> <script language="javascript" src="resources/js/opacity.js" type="text/javascript"> </script> Then, because this library requires that the PNGs it fixes be back- ground images in a div, we need to change our push pin from an img element to a div, as well as our two toggle buttons, and then finally use this library to fix all of these divs. We’ll change the toggle button images to div background images first: File 41 <body onload="init()"> <p> <h1>Ajaxian Maps</h1> </p> <div id="outerDiv"> <div id="toggleZoomDiv" onclick="toggleZoom()"> </div> <div id="togglePushPinDiv" onclick="togglePushPin()"> </div> <div id="innerDiv" style="z-index: 0"> The rain in Spain falls mainly in the plains. </div> </div> </body> As part of this change, we moved the style attribute settings on the tog- gle divs into the style sheet we defined at the top of the file (something we probably should have done anyway): File 41 #toggleZoomDiv { position: absolute; top: 10px; left: 10px; z-index: 1; width: 72px; height: 30px; } 5 http://www.alistapart.com/articles/pngopacity
  • 48. C REATING A JAXIAN M APS 40 #togglePushPinDiv { position: absolute; top: 10px; left: 87px; z-index: 1; width: 72px; height: 30px; } We now need to add two lines to our init( ) method to use our new IE transparency library with the toggle divs: File 41 // fix the toggle divs to be transparent in IE new OpacityObject( ' toggleZoomDiv ' , ' resources/images/zoom ' ) .setBackground(); new OpacityObject( ' togglePushPinDiv ' , ' resources/images/pushpin ' ) .setBackground(); And finally, we need to reformat the togglePushPin( ) function to use this new technique: File 41 function togglePushPin() { var pinImage = document.getElementById("pushPin"); if (pinImage) { pinImage.parentNode.removeChild(pinImage); var dialog = document.getElementById("pinDialog"); dialog.parentNode.removeChild(dialog); return; } var innerDiv = document.getElementById("innerDiv"); pinImage = document.createElement("div"); pinImage.style.position = "absolute"; pinImage.style.left = (zoom == 0) ? "850px" : "630px"; pinImage.style.top = (zoom == 0) ? "570px" : "420px"; pinImage.style.width = "37px"; pinImage.style.height = "34px"; pinImage.style.zIndex = 1; pinImage.setAttribute("id", "pushPin"); innerDiv.appendChild(pinImage); new OpacityObject( ' pushPin ' , ' resources/images/pin ' ) .setBackground(); var dialog = document.createElement("div"); dialog.style.position = "absolute"; dialog.style.left = (stripPx(pinImage.style.left) - 90) + "px"; dialog.style.top = (stripPx(pinImage.style.top) - 210) + "px"; dialog.style.width = "309px"; dialog.style.height = "229px"; dialog.style.zIndex = 2; dialog.setAttribute("id", "pinDialog"); dialog.innerHTML = "<table height= ' 80% ' width= ' 100% ' ><tr>" +
  • 49. C REATING A JAXIAN M APS 41 Figure 2.9: Ajaxian Maps Push Pin and Dialog on IE 6 "<td align= ' center ' >The capital of Spain</td></tr></table>"; innerDiv.appendChild(dialog); new OpacityObject( ' pinDialog ' , ' resources/images/dialog ' ) .setBackground(); } And now, finally, we are done. Up until the image transparency bit, our code was really quite clean and had very little in the way of cross- browser hacks. Now, unfortunately, it has had to undergo a bit of an IE makeover, but the consolation prize is that IE 7 natively supports PNG so all of this may someday be unnecessary. For review, let’s take a look at our entire page: