News Flash

Great article by @NeutronUK on how to create a print stylesheet using Firebug and the Web Developer Toolbar - http://cot.ag/bOQiVM

Tagged: AJAX

2 July 2007

Any sufficiently advanced technology is indistinguishable from magic.
- Arthur C. Clarke

When Google first unveiled GMail, then GMaps, a firestorm of interest and activity was generated — not just in those Google applications, but the technologies that powered them.

These web programming techniques (now, of course, known collectively as AJAX) have become incredibly popular, especially among Web 2.0 startups and their early adopters.

The developers of Ruby on Rails recognized early on that allowing RoR developers to easily AJAX-ify their webapps would be a great addition to the framework.

With the addition of RJS Templates to Rails core, the AJAX bar was lowered even further.

Simple AJAX requests like incrementing the number of diggs a story has received, or splicing a comment into a blog, are remarkably fast, not to mention user friendly.

What we’ll be addressing today, though, is optimizing ever more complex AJAX requests that might involve a multitude of SQL calls and JavaScript rendering techniques.

Not all of the techniques are strictly JavaScript/browser-oriented. The list was actually derived from the steps that we took to optimize Mailroom, the first app in the Sproutit suite for small businesses.

Mailroom is built for use with small teams; it can be used to manage shared email accounts such as support@ and sales@. It combines an ajax-rich front end with features like tagging, saved replies (for later reuse) and shared internal conversation notes.

When we first launched Mailroom, the service was reasonably fast. As we grew and our server got busier, however, some customers started to complain about how long it took for some of our AJAX functions to complete.

The following optimization techniques, though not complete by any means, were just a few of the methods we used over the course of several weeks to bring Mailroom, literally, up to speed.

Following the performance boost, Mailroom’s response times for a Load Conversation call (via AJAX), went from 2-8 seconds to averaging less than half a second. Once we added pre-caching (technique #5) on the client side via JavaScript, clicking on a message resulted in a near-instantaneous load; sometimes faster than Outlook or Apple Mail.

Possible Sources of Latency

When building out your AJAX application, it’s a good idea to keep in mind the different sources of latency that can slow down the user experience. These include:

  1. The Database (both in memory and filesystem swap)
  2. App Server Execution & Rendering
  3. The Webserver
  4. The Network
  5. The Browser (only in really complex AJAX apps)

There are others, but we hope this broadly covers them all. We’ll only be taking a look at a few of these sources, but it’s useful to keep them all in mind as you build out your app.

5 Ways You Can Optimize Your Ruby on Rails/AJAX Application

  1. Optimal Database Indexing
  2. Eliminate Redundant SQL Queries
  3. Fragment Caching
  4. Response Text Compression/Minimization
  5. Pre-rendering and Client-side JavaScript Caching

1. Optimal Database Indexing

For many applications, database indexing will be the biggest performance booster of them all.

Sample output from a Rails log file:


  Completed in 8.29523 (0 reqs/sec) | Rendering: 2.68176 (32%) | DB: 5.38202 (64%) | 200 OK [http://railsapp.org/action]

This action would be an ideal candidate for database optimization. If, however, you see something like DB: 0.05 (5%), the action is already spending most of it’s time rendering and there’s probably not a lot you can do in the way of database indexing.

It used to be somewhat difficult to pinpoint the exact function call in your Rails app that was generating an SQL query. But with last year’s release of Nathaniel Talbot’s QueryTrace plugin for Rails, this query back tracing process got a whole lot simpler.

After you install the QueryTrace plugin your Rails app (in the ‘development’ environment) will give you output like this in your log/development.log:


 Conversation Load (0.001538)   SELECT * FROM conversations WHERE (conversations.id = 23453) LIMIT 1
    app/models/feed_observer.rb:14:in `after_create'
    app/controllers/conversation_controller.rb:162:in `send_to'
    script/server:3

As you can see, it’s now trivial to find the exact call that’s generating the SQL database hit.

Adding Indexes via Migrations

The following is a migration’s self.up method to both create an articles table and add a few indices:


def self.up     # brings db schema to the next version
  create_table :articles do |t|
    t.column :title,      :string
    t.column :author_id,  :integer
    t.column :body,       :text
    t.column :is_live,    :boolean, :default => false
  end
  add_index :articles, :author_id
  add_index :articles, [ :author_id, :is_live ], :name => 'author_live_idx'
end

This would create both a single-column index on the author_id, as well as a multi-column index on author_id and is_live.

When in doubt, use a tool like railsbench or the Ruby Performance Validator to see where the bottlenecks are in your Rails app.

2. Eliminate Redundant SQL Queries

Note: this one is more of a “gotcha” applicable to those of us who are new to Ruby on Rails. (Since I’ve fallen victim to this mistake several times, I thought it was worth mentioning here.)

It’s always a good idea to keep a ‘tail -f log/development.log’ open when you are developing your Rails apps. (Win32 users can get tail, etc. here or use Cygwin )

You might be surprised at what you find if you are not in the habit of checking your query logs.

Ever written a quick hack like this?


  class ApplicationController < ActionController::Base
    def current_member
      return Member.find(@session[:member])
    end
    helper_method :current_member
  end

Only to discover that you’re actually calling current_member more than once in a particular controller/view combination? (thus generating multiple SQL calls for the same unchanged ActiveRecord object)

Here’s how to cache the variable in a per-request instance variable so that the Member.find(session[:member_id]) database query only gets called once per request:


  class ApplicationController < ActionController::Base
    protected
      def current_member

        @current_member ||= Member.find(session[:member_id])
      end

      helper_method :current_member
    end
  end

3. Response Text Compression/Minimization

Before attempting the below technique, the simplest way to reduce the number of bits sent across the wire is to enable gzip compression in your web server.

Some links on gzip compress: mod_deflate (apache 2.2), thread on config’ing lighty with gzip, apache 2.2 mod_deflate tutorial.

In the initial version of Mailroom, we were loading the tag conversation HTML module via AJAX for every load conversation request.

There was quite a bit of superfluous HTML that was being returned, when all we really needed was the specific tags that this particular conversation had been tagged with.

By the way, this kind of optimization is helpful when you have one master page load (where it’s OK to take 1-2 seconds longer), followed by many successive AJAX requests that you want to be rendered as quickly as possible.

We’re also targeting the size of the AJAX response text sent back by the server. A 4k response text is of course more preferable to a 16k one.

Initially, this was the HTML snippet being returned for every load conversation AJAX call:


Choose from the tags below:

… lots of tags …
or enter some new tags:

Instead of returning this whole chunk of HTML each call, we are going to load a static, hidden HTML div once on the initial page load, and return (via AJAX, each call) just the bare essential tag data needed to render this static HTML into a per-conversation tag editor.

First we create a simple Tags object in javascript (with a little help from Prototype):


  Tags = Class.create();
  Tags.prototype =  {
    initialize: function(conversation_id, tag_str, tag_ids) {
      this.conversation_id  = conversation_id;
      this.tag_str          = tag_str;
      this.tag_ids          = tag_ids;
    }
  }

In your main page load, we render a static version of the tag editor template that stays in a hidden div.

The static HTML in a hidden div:




When a conversation is loaded, the contents of the hidden div will be read in via JavaScript. Regular expressions are performed against it on the fly to replace elements like #TAGS# with actual per-conversation data returned via the AJAX request.

Here we create a simple JavaScript function that pulls in the contents of the hidden div, performs regex replacements on it, and completes with a Prototype Insertion call of the newly rendered HTML.


  loadTagEditor: function(tags) {
    var tags_static = $('tag-editor-static').innerHTML;
    var regex = /-static/g;
    tags_static = tags_static.replace(regex, '');  # Strip out the "-static" portion of the ids

    var tags_regex = /#TAGS#/;
    tags_static = tags_static.replace(tags_regex, tags.tag_str);
    ...
    new Insertion.After('the-widget-before-tag-editor', tags_static);
  }

Now whenever a conversation is selected, we just need to make sure the “loadTagEditor(tags)” method is called.

There are a few gotchas you’ll want to watch out for with this technique, such as not ending up with duplicate ids in your HTML.

In the hidden, static divs, one way to avoid duplicate ids is to append all your id names with ”-static” and then strip out the ”-static” when you load in your hidden div HTML. (that’s what we do above with the Regular Expression)

Now, our original chunk of HTML (which can really add up if you have 20 – 50 tags) that was previously being returned back with each Load Conversation AJAX request—is now replaced with a few simple lines of JavaScript:


  tags = new Tags(460857, 'home Marketing Development', new Array(1251, 1252, 1460));
  loadTagEditor(tags);

This is the Rails RJS (inside show_conversation.rjs, perhaps) that would be used to generate the above JavaScript:


  page << "tags = new Tags(#{@conversation.id}, '#{@conversation_tags * ' '}', new
Array(#{@conversation_tag_ids.inspect_raw}));"
  page << "ch.loadTagEditor(tags);"

4. Fragment Caching

Here we’ll use the ever so Web 2.0 example here of the tag cloud. These little beauties can be expensive to calculate in SQL. Once you’re site has been TechCrunched, you’ll want to make sure that your tag cloud (especially miniature versions loaded on heavily-trafficked pages, or via AJAX) gets cached as a Rails HTML fragment.


  class TagController < ApplicationController

    def tag_cloud
      fragment = read_fragment("myrailsapp.com/tagcloud")
      if not fragment
        setup_tag_cloud
        fragment = render :template => “tag/tag_cloud”
        write_fragment(”myrailsapp.com/tagcloud”, fragment)
      else
        #logger.info “Fragment cache read: #{fragment}”
        render :text => fragment and return
      end
    end

    private
      def setup_tag_cloud
        # Expensive SQL queries here …
      end
  end

Uncomment the logger.info call to verify that your fragment really is being read from the cache!

Admittedly, fragment caching in Rails can make your code less maintainable. It’s a technique that should really only be used for expensive-to-compute, fairly static portions of your site.

There are several helpful rails caching plugins on the Plugins page on the RoR wiki.

One simple but effective plugin is the Timed File Store which allows you to set an expiration (e.g. 15 minutes) on your cached fragments.

5. Client-side JavaScript Caching and Pre-rendering

If you really want to wow your users, pre-cache commonly called AJAX components into hidden divs so that the only time necessary to load them is the time it takes their browser to execute (eval) the pre-rendered JavaScript.

In the following example, we’ll cache conversations into hidden divs so that whenever a user clicks on a conversation, it’ll load almost instantaneously.

The pre-caching functions will all access a single global JavaScript variable that holds an array. That array will be populated on the first page load with the conversation IDs that should be cached.

Here’s the inline JavaScript (placed at the bottom of the main rendered HTML page):


  

Here are the JavaScript functions that do the preloading/caching:


  // Uses JavaScript's "setTimeout" function to call the "loadFirst" method (once per id).
  //    This method handles the logic of delaying the first load (to give the parent HTML page
  //    time to load) and putting a delay between each AJAX load so as to not overwhelm
  //    the browser & server.
  function preLoader() {
    var cnt = 0;
    var offset = 250;              # Milliseconds to delay the initial load
    var delay_per_load = 500;      # Milliseconds between each load
    ids.each(
      function (id) {
        setTimeout("loadFirst()", (offset   (cnt * delay_per_load)));
        if (id > 0) {
          cnt  = 1;
        }
      }
    );
  }
  // Pops off the first element in the 'load_ids' array and loads it
  function loadFirst() {
    var id = load_ids.pop();
    if (id > 0) {
      preloadConversation(id);
    }
  }
  // Preloads the conversation specified by creating a new AJAX request -- Note the use of Prototype's
  //   "onComplete" method to specify that "cacheConversation" should be called when the AJAX request
  //   has been completed and its data returned.
  preloadConversation = function(conversation_id) {
    new Ajax.Request('/mailroom/conversation/'   conversation_id   '?preload=true', {asynchronous:true, evalScripts:false, onComplete: cacheConversation});
  }
  cacheConversation = function(originalRequest) {
    // ... omitting some "clever" hacks used to get the conversation id ('conv_id' variable) ...
    var hidden_div_id = 'pre-'   conv_id;
    // Set the Inner HTML of a hidden div to the JavaScript passed back that was generated by Rails RJS templates:
    $(hidden_div_id).innerHTML = encode(originalRequest.responseText);
  }

The following is the JavaScript function used to either A) load the cached conversation, or B) if the cache is not present, load the conversation directly off the server:


  loadConversation: function(id) {
    if ($('pre-'   id).innerHTML != '') {
      var decoded = decode($('pre-'   id).innerHTML);
      eval(decoded);
    } else {
      // Load the conversation via a new Ajax call (eval'ing the result by setting "evalScripts:true",
      //    then caching it via the onComplete parameter).
      new Ajax.Request('/mailroom/conversation/'   id, {asynchronous:true, evalScripts:true, onComplete: cacheConversation});
    }
  }

Phew. Well, there you have it. JavaScript, long the domain of cut n’ paste HTML jockeys, has proven in recent years to be a formidable challenger to Flash (and Applets, remember those), once hailed as the future of interactive web applications. Of course, each has their place and own unique advantages and disadvantages.

While most applications wouldn’t need the kind of firepower described in this technique, hopefully you’ve learned a little bit about JavaScript, Rails and RJS along the way.

Footnote

The inspect_raw method used in technique #5 is a custom method we added to the Array class. Go here to learn more about this method and how we implemented it.

Continue reading 1
CSS3 Online Conference March 22nd 2010 banner ad

24 April 2007

Ajax has been a hot topic for quite a while now, and there seems to be a new book with the eponymous Greek warrior in its title pretty much every week. Coming in at just 207 pages, Jeremy Keith’s new book, Bulletproof Ajax, is the latest to join the fray. With only 200 odd pages how does he plan to cover such a complex subject you may be asking? Well, here’s the secret – Ajax is really quite simple when you get down to it.

Jeremy’s previous book, DOM Scripting, stands as a fantastic introduction to JavaScript in general and DOM Scripting in particular and Bulletproof Ajax follows on from that title to some degree. Although you get a short introduction to the JavaScript language it’s just enough to understand the examples (I’d recommend you have at least a passing knowledge of JavaScript before reading this book). Also if you are coming to Ajax as a server side programmer then this book is probably not what you’re looking for, but worth reading at a later date. Given the client-server nature of Ajax there are server-side code examples (in PHP) but these are generally brief and serve only to support the examples rather than look at real world usage. Again, it would be useful to have at least a passing familiarity with a server side language and to be able to know if you already have a web server handy to experiment with.

The book features plenty of sample projects – for instance a simple address book is built up, showing off how to use XML, JSON and HTML as data sources and introducing the central XMLHTTPRequest object. There’s lots of focus, as you would expect, on making these examples bulletproof, in this case making sure they work even if JavaScript is unavailable using a method called Hijax.

To go from simple inline event handlers through to completely unobtrusive, object-based code and discussions of closures in JavaScript would be pretty good going for a book twice the size. It’s testament to the clear, no-nonsense and eminently readable writing style that this never bogs down the examples. Some people are bound to complain about the use of the proprietary innerHTML property and the minimal coverage of XML and JSON in the larger examples. This seems to be a facet of the scope of the book and a pragmatic approach to the problem rather than an unintentional oversight. If you’re looking for an A-Z of building an enterprise Ajax application then you’re probably looking for another book (but you should read this one first anyway!).

An entire chapter is dedicated to Accessibility and Ajax, a hugely important subject and one I’ve not seen mentioned anywhere else to date. Although the chapter lacks equivocal conclusions (mainly because no one seems to have formed any yet) it raises all the important issues for discussion and debate and provides a solid set of references for further reading. Throughout the book everything is anchored on the importance of user experience, rather than simply using technology for technology’s sake; this makes Bulletproof Ajax stand out from the crowd of more technology-focused tomes on the subject.

As a standards savvy developer if you want to get up to speed quickly with the hows and whys of modern Ajax but don’t have the time to wade through an awful lot of blog posts then Bulletproof Ajax is worthwhile reading. If you already know what you’re up to then it’s a perfect book to recommend to your unenlightened colleagues. A perfectly digestible read for one of those long train journeys!

Book Name: Bulletproof Ajax
Publisher: New Riders
Author: Jeremy Keith
URL: http://bulletproofajax.com
Price: $34.99 USD Buy Bulletproof Ajax at Amazon and save 34% off the cover price!
Rating out of 5: 3.5

Continue reading 12

30 October 2006

The GSmapSearchControl Solution is a simple to use application of the Google AJAX Search API that is designed to let you easily add a searchable map to your pages, sites and blogs. The rest of this page discusses this topic in detail – but if you just want to cut and paste some code, the “hello world” of the Map Search Control solution is available at the end of this article.

This basic sample demonstrates the following features:

  • A search control that allows you to look up addresses and business listings, plotting the results on a map.
  • The search control has a programmable center point, and at this center point you can specify a custom title and url.
  • The search control takes a small amount of verticle space while inactive, the inactive view is a small map with a white center point.
  • Once a search completes, the map expands and the search result cursors allow you to scroll through the set of search results.
  • Each search result contains a link to a details page for the result as well as a link to get driving directions from the center point of the map to the current search result.
  • The clear button in the center of the cursor controls clears the current set of results and sets the control into the idle state.

The solution is designed for extreme ease of use. As a site designer you are able to control the center point location as well as a custom title and url. In addition, you can easily program a collection of “search links” that you can place on your page that when clicked, will drive the search control.

New Functionality Update

If you are already familiar with this solution, you might find the following new functionality helpful and useful.

Instructions for adding this to your site

Adding the solution is a simple three step process.

Step 1 – Load AJAX Search API, the Google Maps API, and the Map Search Solution

First you need to load the Google AJAX Search API, the Google Maps API, and the Map Search solution into your application. If you don’t already have a Google AJAX Search API key, your first step is to sign up for a key. With your key in hand, add the following five lines of code to your page or blog template.

<!-- maps api, ajax search api, map search solution code -->
<script src="http://maps.google.com/maps?file=api&v=2&key=YOUR-KEY"
    type="text/javascript"></script>

<script src="http://www.google.com/uds/api?file=uds.js&v=1.0&key=YOUR-KEY"
    type="text/javascript"></script>
<script src="http://www.google.com/uds/solutions/mapsearch/gsmapsearch.js"
    type="text/javascript"></script>

<!-- ajax search stylesheet, map search stylesheet -->
<link href="http://www.google.com/uds/css/gsearch.css" rel="stylesheet"
    type="text/css"/>
<link href="http://www.google.com/uds/solutions/mapsearch/gsmapsearch.css"
    rel="stylesheet" type="text/css"/>
      

Step 2 – Define a location on your page for the Map Search Control

The next step is to define a place on your page for the search control (note: you can have more than one). This is typically done by defining a named <div> element as we have shown below:

<div id="mapsearch">Loading...</div>
    

You might want to set some styling attributes on this element to constrain the width of the control, set a border or margin, etc. For example, a style rule like this might be useful.

#mapsearch {
  width : 400px;
  margin : 10px;
  padding : 4px;
  border : 1px solid #f9f9f9;
}
    

In addition to this base style, the height of the idle state map and the active state map is easily modified.

/* set height of idle state map */
#mapsearch .gsmsc-idleMapDiv { height : 200px; }

/* set height of active state map */
#mapsearch .gsmsc-mapDiv { height : 300px; }
    
Step 3 – Create a GSmapSearchControl and bind it to your page

The final step involves creating and configuring the control. The control is very powerful and offers several options. In this section, we will describe the simplest case of adding a control to your page or blog followed several examples that demonstrate more advanced concepts.

The first step is to actually get your code to run. The easiest way to do this is to either extend an existing body onload handler, or create a new one. Find the <body> element on your page and add an onload handler.

<body onload="OnLoad()">
    

Now write your onload handler by adding some code to an existing <script> block, or by creating a new one. Find the <body> element on your page and add an onload handler. The code snippet below demonstrates how to create a new map search control that will appear on your page inside the element named “mapsearch”. The map will center itself on Google corporate headquarters located at “1600 Amphitheatre Parkway, Mountain View, CA.”

<script type="text/javascript">
function OnLoad() {
  new GSmapSearchControl(
        document.getElementById("mapsearch"),             // container
        "1600 Amphitheatre Parkway, Mountain View, CA",   // center point
        null                                              // options
        );
}
</script>
    

Options

Notice in the above example, the value null is passed as the options argument, a Javascript object which is typically specified in object literal form. The following sections describe the possible values for options. These values support the following features:

  • Setting a special title and url for the map center point.
  • Specifying a set of hot spots on your page that will trigger searches
Setting a special title and url for the map center point

A special title and url can be programmed into the control and bound to the white center point marker of an active map. A typical use case for this feature might be to specify a conference name and conference url, or a company name and url. In order to request this functionality, the title and url properties of the options argument must be set. The following code snippet demonstrates this.

<script type="text/javascript">
function OnLoad() {

  // set title to the Googleplex and the link to
  // the Google corporate information page
  var options =
    {
      title : "Googleplex",
      url : "http://www.google.com/corporate/index.html"
    }

  // create the map search control
  new GSmapSearchControl(
        document.getElementById("mapsearch"),
        "1600 Amphitheatre Parkway, Mountain View, CA",
        options
        );
}
</script>
    
Setting up hot spots

A common use case for this control is program hot spots on to your page thatwhen clicked will drive the search control. These hot spots might take the form of a list of recommendations, hotels, restaurants, etc. The search control supports this by allowing you to program a list of HTML elements and associated queries. When your users click on the element, the search control is activated with the specified query.

The options argument contains an optional hotspot property. This property is an array of objects, where each object contains an element property which is a reference to an HTML element on your page, as well as a query property which specifies a query to execute when the element is clicked.

The following code fragment demonstrates the use of this feature including the CSS declarations,HTML fragments, and options programming. If you prefer programming this on your own, perhaps by using <a> tags with javascript: urls, that’s a perfectly reasonable way to proceed. We include this capability in the solution because for some of our users, this is a simpler mechanism that requires less of their time to develop. In the snippet that follows we are going to show sample CSS, HTML, and search control initialization logic.

/* define the CSS used to style the hotspots */
h4.hotspot {
  font-size : 100%;
  font-weight : normal;
  color : rgb(9, 122, 182);
  margin-left : 8px;
  margin-top : 0px;
  margin-bottom : 2px;
  font-style : normal;
  cursor : pointer;
}

h4.hotspot:hover {
  color : rgb(237, 92, 11);
  text-decoration : underline;
}

<!--
Define the HTML that places the hotspots on your page.
Note, you can use any HTML element you like including li, div, etc.
-->

<div id="mapsearch">Loading...</div>
<h3 class="hotspotheader">Recommendations</h3>
<h4 id="hs01" class="hotspot">Caffeine</h4>
<h4 id="hs02" class="hotspot">Thai Food</h4>
<h4 id="hs03" class="hotspot">Pizza</h4>

<h4 id="hs04" class="hotspot">Gym</h4>
<h4 id="hs05" class="hotspot">Hotel Avante</h4>
<h4 id="hs06" class="hotspot">Residence Inn</h4>
<h4 id="hs07" class="hotspot">The Four Seasons</h4>
<h4 id="hs08" class="hotspot">The Westin, Palo Alto</h4>

<script type="text/javascript">
function OnLoad() {

  // Create an array of hotspots. Each entry contains and html element
  // from your page, and the query to execute when that element is clicked
  var hotspotsList = [
      { element : document.getElementById("hs01"), query : "Starbucks" },
      { element : document.getElementById("hs02"), query : "Amarin Thai" },
      { element : document.getElementById("hs03"), query : "Frankie Johnnie & Luigi" },
      { element : document.getElementById("hs04"), query : "Hotel Avante" },
      { element : document.getElementById("hs05"), query : "Residence Inn" },
      { element : document.getElementById("hs06"), query : "Four Seasons Palo Alto" },
      { element : document.getElementById("hs07"), query : "Westin Palo Alto" }
  ];

  // set title to the Googleplex and the link to
  // the Google corporate information page
  // set the hotspot list to the list above
  var options =
    { title : "Googleplex",
      url : "http://www.google.com/corporate/index.html",
      hotspots : hotspotsList
    }

  // create the map search control
  new GSmapSearchControl(
        document.getElementById("mapsearch"),
        "1600 Amphitheatre Parkway, Mountain View, CA",
        options
        );
}
</script>
    
New! Setting Custom Center Icon

Applications can either accept the default center location icon, or they can supply their own GIcon and the system will use that Icon to mark the center point of the map.

The options argument contains an optional centerIcon property. When supplied, property must specify a valid GIcon. When specified, this icon is used as the center point marker for both the idle and active map.

The following code fragment demonstrates the use of this feature. It demonstrates the use of both a hand built icon, as well as the G_DEFAULT_ICON two calls:

// use the ridefinder, small yellow icon as the center point marker
var ci = new GIcon();
ci.image = "http://labs.google.com/ridefinder/images/mm_20_yellow.png";
... (rest of initialization omitted)

new GSmapSearchControl(container,
                       "1600 Amphitheatre Parkway, Mountain View, CA",
                       { title : "Googleplex",
                         url : "http://www.google.com/press/factorytour.html",
                         centerIcon : ci
                       }
                       );

// use the G_DEFAULT_ICON as the center point marker
new GSmapSearchControl(container,
                       "1600 Amphitheatre Parkway, Mountain View, CA",
                       { title : "Googleplex",
                         url : "http://www.google.com/press/factorytour.html",
                         centerIcon : G_DEFAULT_ICON
                       }
                       );
    
New! Setting Custom Selected and Unselected Icons

Applications are able to specify the icons for the currently selected search result as well as for all unselected search results using a similar model.

The options argument contains an optional selectedIcon property. When supplied, property must specify a valid GIcon. When specified, this icon is used as the marker for the selected search result.

The options argument contains an optional unselectedIcon property. When supplied, property must specify a valid GIcon. When specified, this icon is used as the marker for all other search results.

The following code fragment demonstrates the use of this feature:

// use the G_DEFAULT_ICON as the for selected and unselected search results
new GSmapSearchControl(container,
                       "1600 Amphitheatre Parkway, Mountain View, CA",
                       { title : "Googleplex",
                         url : "http://www.google.com/press/factorytour.html",
                         selectedIcon : G_DEFAULT_ICON,
                         unselectedIcon : G_DEFAULT_ICON
                       }
                       );
    
Controlling the Zoom Level

Applications can control the Zoom Level for the idle map (displayed when no searches are active) as well as for the active map (displayed when a map is showing results). The system defines two constants that are helpful when configuring this aspect of the control:

  • GSmapSearchControl.IDLE_MAP_ZOOM – the default zoom level for the idle map
  • GSmapSearchControl.ACTIVE_MAP_ZOOM – the default zoom level for the active map

The options argument contains an optional idleMapZoom property. When supplied, this property specifies the zoom level for the idle map. A common use of this property is to have the idle map’s zoom level match the default zoom level for the active map. In order to do this, use the constants described above to set idleMapZoom to GSmapSearchControl.ACTIVE_MAP_ZOOM.

The zoom level for the active map is controlled in a similar manner. The options argument contains an optional activeMapZoom property. When supplied, this property specifies the zoom level for the active map. Note: Applications can use these properties independently of each other.

The following code fragment demonstrates the use of this feature.

// set the idle map zoom so that it matches the
// default active map zoom level
new GSmapSearchControl(container,
                       "1600 Amphitheatre Parkway, Mountain View, CA",
                       { title : "Googleplex",
                         url : "http://www.google.com/press/factorytour.html",
                         idleMapZoom : GSmapSearchControl.ACTIVE_MAP_ZOOM
                       }
                       );

// set the active map zoom level up by 1
new GSmapSearchControl(container,
                       "1600 Amphitheatre Parkway, Mountain View, CA",
                       { title : "Googleplex",
                         url : "http://www.google.com/press/factorytour.html",
                         activeMapZoom : GSmapSearchControl.ACTIVE_MAP_ZOOM+1
                       }
                       );
    
New! Enabling the Map Type Control

The default behavior of the maps created by this control is for them to not contain the GMapTypeControl, the control that allows switching between map, hybrid and satellite mode. This behavior is programmable allowing applications to cause the control to build maps with the GMapTypeControl available on all maps, or just on the active map.

The options argument contains an optional mapTypeControl property. When supplied, this property specifies which maps should be programmed to include the GMapTypeControl.

  • GSmapSearchControl.MAP_TYPE_ENABLE_ACTIVE – When this value is specified for the mapTypeControlproperty, the active map will contain a GMapTypeControl
  • GSmapSearchControl.MAP_TYPE_ENABLE_ALL – When this value is specified for the mapTypeControl property, both the active map and the idle map will contain a GMapTypeControl

The following code fragment demonstrates the use of this feature.

// enable a GMapTypeControl on the active map
new GSmapSearchControl(container,
                       "1600 Amphitheatre Parkway, Mountain View, CA",
                       { title : "Googleplex",
                         url : "http://www.google.com/press/factorytour.html",
                         mapTypeControl : GSmapSearchControl.MAP_TYPE_ENABLE_ACTIVE
                       }
                       );

// enable a GMapTypeControl on the all maps
new GSmapSearchControl(container,
                       "1600 Amphitheatre Parkway, Mountain View, CA",
                       { title : "Googleplex",
                         url : "http://www.google.com/press/factorytour.html",
                         mapTypeControl : GSmapSearchControl.MAP_TYPE_ENABLE_ALL
                       }
                       );

The “Hello World” of GSmapSearchControl

The following page demonstrates a complete page which uses the GSmapSearchControl solution. You can start with this simple page, change internal to the value of your key and be up and running in seconds.

<html>
<head>

  <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  <title>GSmapSearchControl Sample</title>

  <!-- Note:
    Make sure to replace the &key=internal with &key=YOUR-KEY
    in both the maps API script load and in the Ajax Search API script load statements
  -->
  <!-- maps api, ajax search api, map search solution code -->

  <script src="http://maps.google.com/maps?file=api&v=2&key=internal"
    type="text/javascript"></script>
  <script src="http://www.google.com/uds/api?file=uds.js&v=1.0&key=internal"
    type="text/javascript"></script>
  <script src="http://www.google.com/uds/solutions/mapsearch/gsmapsearch.js"
    type="text/javascript"></script>

  <!-- ajax search stylesheet, map search stylesheet -->
  <link href="http://www.google.com/uds/css/gsearch.css" rel="stylesheet"
    type="text/css"/>
  <link href="http://www.google.com/uds/solutions/mapsearch/gsmapsearch.css"
    rel="stylesheet" type="text/css"/>
  <style type="text/css">
    body, table, p{
      background-color: white;
      font-family: Arial, sans-serif;
      font-size: 13px;
    }

    #mapsearch {
      width : 400px;
      margin-left: 10px;
      padding: 4px;
      border : 1px solid #f9f9f9;
    }
  </style>
  <script type="text/javascript">

    function OnLoad() {

      var options = {
            title : "Googleplex",
            url : "http://www.google.com/corporate/index.html"
            }

      new GSmapSearchControl(
            document.getElementById("mapsearch"),
            "1600 Amphitheatre Parkway, Mountain View, CA",
            options
            );
    }
  </script>
</head>
<body onload="OnLoad()">
  <div id="mapsearch">Loading...</div>
</body>
</html>

Examples

digg.com logo Like this article? Digg it!

Continue reading 11

Sign Up to our Newsletter

Enter your e-mail address below to receive regular updates on web design, web development and web business. Subscribe today and receive a free 44 page PDF "Designing Web User Interfaces" by Ryan Singer of 37signals.

Subscribe to the Think Vitamin articles RSS feed

HTML5 Online Conference April 12 2010

News

Twitter

Follow us on Twitter

Subscribe

Article Subscribers

Feedburner blog subscriber indicator

News Subscribers

Feedburner blog subscriber indicator

Subscribe by Email

You can receive Think Vitamin updates via email. Just pop your email address in the box below and click the arrows.

Subscribe by RSS

You can also receive new Think Vitamin posts via your RSS feed reader

Subscribe RSS Think Vitamin is a proud member of the Smashing Network

Ads Via The Deck