JavaScript Pitfalls: No Block Scope

Programmers used to Java would often assume that block scope in JavaScript behave the same way, but they don’t. Most programmers know that JavaScript has global scope and function scope and that you need to declare variables with “var” within functions in order to make the variables local.

function scope1() {
  test = "ok";    //global scope (without var)
}
scope1();
alert(test);      //displays "ok"

function scope2() {
  var test = "ok";  //local scope (with var)
}
scope2();
alert(test); //displays "undefined" since we are trying to display a global var

Now comes the “no block scope” in JavaScript – variables are all at function scope no matter where the variable is declared within the function. Even when within a try catch clause, for example.

function scope3() {
  try {
    var test = "ok";  // variable declared inside try catch is still function scope
  } catch(err) {
  }
  alert(test);  // displays "ok" since we are still inside function
}
scope3();

Or in a for loop.

function scope4() {
  for(var i=0; i<10; i++) {
  }
  alert(i);  //displays "10", variable i is available outside of for loop!
}
scope4();

The catch exception object, however, is still only available within its own scope – same as Java.

function scope5() {
  try {
    throw new exception("fake error");
  } catch(err) {
    // err is only available within this block
    var test = "ok";
  }
  alert(test); // displays "ok", still function scope
  alert(err);  // displays "undefined", err is only available within catch clause
}

2010 in review

The stats helper monkeys at WordPress.com mulled over how this blog did in 2010, and here’s a high level summary of its overall blog health:

Healthy blog!

The Blog-Health-o-Meter™ reads This blog is doing awesome!.

Crunchy numbers

Featured image

A Boeing 747-400 passenger jet can hold 416 passengers. This blog was viewed about 3,300 times in 2010. That’s about 8 full 747s.

In 2010, there were 12 new posts, growing the total archive of this blog to 18 posts.

The busiest day of the year was December 2nd with 37 views. The most popular post that day was Extracting Links From HTML Using Swing HTMLEditorKit.

Where did they come from?

The top referring sites in 2010 were tips4php.net, blog.augustli.com, google.co.in, facebook.com, and deepumi.wordpress.com.

Some visitors came searching, mostly for jquery yahoo pipes, eclipse + create plugin to modify text in editor, yahoo pipes jquery, jquery multiple feed rss reader, and openlayers marker popup.

Attractions in 2010

These are the posts and pages that got the most views in 2010.

1

Extracting Links From HTML Using Swing HTMLEditorKit August 2010

2

Eclipse Plugin: Adding Custom Editor Context Menu To Process Selected Text Or Whole File August 2010
6 comments

3

Displaying Markers In Open Street Map Using Open Layers August 2010

4

Yahoo Pipes + jQuery = Dynamic Pages (without backend coding) December 2008
4 comments

5

Displaying Markers In Google Maps API August 2010
1 comment

Working With Big Faceless Java Report Generator

The Java Report Generator by Big Faceless Organization (BFO) is a commercial java library that converts XML into PDF documents. I have been using it for several months now and here are some quirks that I have came across so far:

  • XML is not HTML, some characters and tags are not compatible and here are the workarounds I use.
  • HTML XML
    &nbsp; &#160;
    <br> <br/>
    <font face=”Helvetica size=”12pt”>text</font> use <span style=”font: normal 12pt Helvetica;”>text</span>
    bgcolor attribute use CSS background-color
    TABLE border attribute use TABLE cellborder attrbute
    tags can overlap e.g. <b><i>text</b></i> tags can not overlap e.g. <b><i>text</i></b>
  • First element in a DIV must be a tag and not a text.
  • <pdf>
        <body>
             <div>text</div> <!-- this throws an error -->
        </body>
    </pdf>
    

    must be

    <pdf>
        <body>
             <div><span/>text</div> <!-- workaround: place an empty span -->
        </body>
    </pdf>
    

  • If you are using page-break-after:avoid in succession, you need to put something in between the container elements to avoid clustering the containers together.
  • <p style="page-break-after:avoid;">
         <!-- some text here -->
    </p>
    <p style="page-break-after:avoid;">     <!-- the two p tags will be clustered together! -->
         <!-- some text here -->
    </p>
    

    must be

    <p style="page-break-after:avoid;">
         <!-- some text here -->
    </p>
    <span/> <!-- workaround: put empty span to separate the two p tags -->
    <p style="page-break-after:avoid;">
         <!-- some text here -->
    </p>
    

  • BODY tag must not be empty, if you want to output an empty PDF, put in a space using &#160; in the BODY tag.
  • In order for a table to repeat its headers across pages, it must use the thead and tbody tags and the table tag must be first level and not be enclosed in another containing tag.

Eclipse Plugin: Adding Custom Editor Context Menu To Process Selected Text Or Whole File

The current Eclipse plugin I am working on is, of course, much more complex than this, but I thought it would be nice to share how to make a simple Eclipse plugin that processes the selected text or text in the current file in the editor. Do the following steps first before we proceed to the coding portion:

  • Create a new plugin project in Eclipse
  • Open the MANIFEST.MF and click on Overview tab. Update the plugin information on version and put in your name in Provider. Tick the “This plug-in is a singleton” checkbox.
  • Click on Dependencies tab and add in the following Required Plug-ins (if it isn’t there yet). The jar files of these plug-ins will be made available in your project automatically, so if you encounter unresolved imports for Eclipse API, you might have missed an entry here.
    • org.eclipse.ui
    • org.eclipse.core.runtime
    • org.eclipse.jface.text
    • org.eclipse.ui.editors
  • Click on Extensions tab and under All Extensions, click on the Add button. Add “org.eclipse.ui.popupMenus” and save the MANIFEST.MF file. You should see the plugin.xml file being added to the project.
  • Open the plugin.xml file and add the following entries. These entries will add in the context menu for the editor and link the menu item to a class in your plugin. You can refer to the Eclipse API documentation for more details.
  • <?xml version="1.0" encoding="UTF-8"?>
    <?eclipse version="3.4"?>
    <plugin>
        <!-- add to pop menu -->
        <extension point="org.eclipse.ui.popupMenus">
            <!-- add to editor context menu -->
            <objectContribution
                id="Plugin.EditorContribution"
                objectClass="org.eclipse.ui.IEditorInput"
                nameFilter="*.*">
                <!-- add a menu item call "Plugin" -->
                <menu
                    label="Plugin"
                    path="additions"
                    id="Plugin.EditorMenu">
                    <separator
                        name="group">
                    </separator>
                </menu>
                <!-- add a sub menu item called "Do Stuff" -->
                <!-- that calls the class plugin.actions.DoStuff -->
                <action
                    label="Do Stuff"
                    class="plugin.actions.DoStuff"
                    menubarPath="Plugin.EditorMenu/group"
                    enablesFor="1"
                    id="Plugin.DoStuffAction">
                </action>
            </objectContribution>        
        </extension>
    </plugin>
    

Now, create a new package under src named “plugin.actions”, by default, there should be an existing package called “plugin” containing the Activator.java file. Create a new file under “plugin.actions” called “DoStuff.java” implementing the interface “IObjectActionDelegate”. Next, add in the following code.

package plugin.actions;

import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IObjectActionDelegate;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.texteditor.AbstractTextEditor;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.texteditor.ITextEditor;

import plugin.Activator;

/**
 * Sample code template for editor context menu
 * and editor text selection or file text processing.
 * @author augustli
 */
public class DoStuff implements IObjectActionDelegate {
  private static final String TITLE = "Do Stuff";
  private static final int TEXT_REPLACE_ALL = 0;
  private static final int TEXT_REPLACE_SELECTED = 1;
  private static final int TEXT_INSERT = 2;
  private int textOutputMode = 0;
  private String textOutput = null;
  private Shell shell;

  /**
   * Constructor
   */
  public DoStuff() {
    super();
  }

  /**
   * Get the shell object for use in prompting error message.
   * @see IObjectActionDelegate#setActivePart(IAction, IWorkbenchPart)
   */
  @Override
  public void setActivePart(IAction action, IWorkbenchPart targetPart) {
    shell = targetPart.getSite().getShell();
  }

  /**
   * Get the selection text or text for whole file
   * and pass it to process for modification.
   * @see IActionDelegate#run(IAction)
   */
  @Override
  public void run(IAction action) {
    try {
      //get active editor
      IEditorPart editorPart = Activator.getDefault().getWorkbench().getActiveWorkbenchWindow()
                       .getActivePage().getActiveEditor();

      if (editorPart instanceof AbstractTextEditor) {
        //check if there is text selection
        int offset = 0;
        int length = 0;
        String selectedText = null;
        IEditorSite iEditorSite = editorPart.getEditorSite();
        if (iEditorSite != null) {
          ISelectionProvider selectionProvider = iEditorSite.getSelectionProvider();
          if (selectionProvider != null) {
            ISelection iSelection = selectionProvider.getSelection();
            offset = ((ITextSelection) iSelection).getOffset();
            if (!iSelection.isEmpty()) {
              selectedText = ((ITextSelection) iSelection).getText();
              length = ((ITextSelection) iSelection).getLength();
            }
          }
        }

        ITextEditor editor = (ITextEditor) editorPart;
        IDocumentProvider dp = editor.getDocumentProvider();
        IDocument doc = dp.getDocument(editor.getEditorInput());
        //do processing here
        process(selectedText, doc.get());
        //if null is returned, do not replace text in editor
        if (textOutput != null) {
          if (textOutputMode == TEXT_REPLACE_ALL) {
            doc.replace(0, doc.getLength(), textOutput);
          } else if (textOutputMode == TEXT_REPLACE_SELECTED && selectedText != null) {
            doc.replace(offset, length, textOutput);
          } else {
            doc.replace(offset, 0, textOutput);
          }
        }
      }
    } catch (Exception e) {
      MessageDialog.openError(shell, TITLE, e.getMessage());
    }
  }

  /**
   * Selected text or text for the whole file
   * will be pass as parameter for processing.
   * @param selectedText Selected text, if any, otherwise null.
   * @param editorText All text in editor.
   */
  private void process(String selectedText, String editorText) {
    //add your own code here to modify text here before returning it
    //you must set two class variables as output,
    //choose one from the following:

    //To replace selected text, if there is no selection,
    //text will be inserted instead
    textOutputMode = TEXT_REPLACE_SELECTED;
    textOutput = "Replacement text here";

    //To replace all text in editor
    textOutputMode = TEXT_REPLACE_ALL;
    textOutput = "This will replace all text in editor.";

    //To insert text at current cursor position
    textOutputMode = TEXT_INSERT;
    textOutput = "Text to insert";

    //To do nothing, return null for textOutput
    textOutput = null;
  }

  /**
   * Skip implementation
   * @see IActionDelegate#selectionChanged(IAction, ISelection)
   */
  @Override
  public void selectionChanged(IAction action, ISelection selection) {
  }

}

In the code, we get an instance of the editor and check if there is any selected text. If found, then the selected text is passed to the process method along with the text for the whole file. The textOutputMode and textOutput must be set the process method in order to either inserttext in the current position, replace the selected text, all the text in the editor or do nothing.

[August Li] 12-16-2010 Updated code to be able to insert text.

Extracting Links From HTML Using Swing HTMLEditorKit

One way to parse links is to use regular expressions, however, HTML links can come in many forms and combinations of its attributes. Which is why it is easier to use the built-in Swing HTMLEditorKit Parser to parse the HTML and extract the links. It works like a SAX parser, each time the parser encountered the start and end (or the content) of a tag, it calls the appropriate method (which you override) to handle the tag. The variable state keeps track of whether we are inside a link or somewhere else. The link, title and text are then stored in the LinkItem object and then added to the links array list. The code can be easily modified to parse different types of HTML tags.

package com.augustli.html;

import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;

import javax.swing.text.MutableAttributeSet;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.parser.ParserDelegator;

/**
 * Extract links from HTML
 * @author augustli
 */
public class ExtractLinks {
	
	/**
	 * Container object for links
	 */
	class LinkItem {
		private String link;
		private String title;
		private String text;
		
		public String getTitle() {
			return title;
		}

		public void setTitle(String title) {
			this.title = title;
		}

		public String getLink() {
			return link;
		}

		public void setLink(String link) {
			this.link = link;
		}

		public String getText() {
			return text;
		}

		public void setText(String text) {
			this.text = text;
		}
	}

	/**
	 * Swing HTMLEditorKit Parser
	 */
	class Parser extends HTMLEditorKit.ParserCallback {
		private static final int SIZE = 128;
		private static final int INSIDE_LINK = 0;
		private static final int OUTSIDE_LINK = 1;
		private int state = OUTSIDE_LINK;
		private List<LinkItem> links;
		private String href;
		private String title;
		private StringBuilder text;
		
		public Parser(List<LinkItem> links) {
			this.links = links; //pass in our list 
		}

		@Override
		public void handleStartTag(HTML.Tag tag, MutableAttributeSet attrSet, int pos) {
			if (tag == HTML.Tag.A) {  //check what kind of tag
                                //get the attributes
				href = (String) attrSet.getAttribute(HTML.Attribute.HREF);
				title = (String) attrSet.getAttribute(HTML.Attribute.TITLE);
				text = new StringBuilder(SIZE);
				state = INSIDE_LINK;
			}
		}

		@Override
		public void handleText(char[] data, int pos) {
			if (state == INSIDE_LINK) {
				text.append(data);  
			}
		}

		@Override
		public void handleEndTag(HTML.Tag tag, int pos) {
			if (tag == HTML.Tag.A && state == INSIDE_LINK) {
				if (href != null) { 
					LinkItem itm = new LinkItem();
					itm.setLink(href);
					itm.setTitle(title);
					itm.setText(text.toString());
					links.add(itm);
				}
				state = OUTSIDE_LINK;
			}
		}
	}

	/**
	 * Get list of links from HTML string
	 * @param htmlContent HTML string
	 * @return List<Item>
	 * @throws Exception
	 */
	public List<LinkItem> getLinks(String htmlContent) throws Exception {
		List<LinkItem> links = new ArrayList<LinkItem>();
		Reader reader = new StringReader(htmlContent);
		new ParserDelegator().parse(reader, new Parser(links), true);
		return links;
	}

	/**
	 * Test Program
	 * @param args
	 * @throws Exception
	 */
	public static void main(String... args) throws Exception {
		String html = "<html><body><a id='a' href='a.html' title='Title A'>Test A</a>"+
                "<A TITLE=\"Title B\" HREF=\"b.html\">Test B</A>"+
                "<a class='link' href='c.html'>Test C</a></body></html>";
		ExtractLinks fl = new ExtractLinks();
		for(LinkItem itm : fl.getLinks(html)) {
			System.out.println("Link: " + itm.getLink() +
                        "\ttext: " + itm.text + 
                        "\ttitle: " + itm.getTitle());
		}
	}

}

SocketException Permission Denied In Mac OS X

A Java code I wrote under Windows would throw “Permission denied” when I tried to run it under Mac OS X. I was using localhost port 888 at the time running an instance of Sun’s HTTPServer.

java.net.SocketException: Permission denied
	at sun.nio.ch.Net.bind(Native Method)
	at sun.nio.ch.ServerSocketChannelImpl.bind(ServerSocketChannelImpl.java:119)
	at sun.nio.ch.ServerSocketAdaptor.bind(ServerSocketAdaptor.java:59)
	at sun.net.httpserver.ServerImpl.<init>(ServerImpl.java:70)
	at sun.net.httpserver.HttpServerImpl.<init>(HttpServerImpl.java:32)
	at sun.net.httpserver.DefaultHttpServerProvider.createHttpServer(DefaultHttpServerProvider.java:17)
	at com.sun.net.httpserver.HttpServer.create(HttpServer.java:111)
        ...

It took me a while trying to create my own java.policy file before I realized that the default Mac OS java.policy contained this line and that the issue here is that by default permission is granted only if you are using port number greater than or equal to 1024. So I changed the port to use 8888 and everything is working again.

grant {
         ...
	// allows anyone to listen on un-privileged ports
	permission java.net.SocketPermission "localhost:1024-", "listen";
        ...
}

JavaScript Tips: Replacing Tokens In Strings

Regular expressions are a nifty way to do search and replace operations. The string.replace(regex, value) is often used for quick replacements.

//template text
var text = 'Hello, {name}. Today is {date}.';
//replace values
text = text.replace(/\{name\}/g, 'August');
text = text.replace(/\{date\}/g, 'Sunday');
//output text
document.write(text);

But there is another form of replace that takes a function as a parameter: string.replace(regex, function). This is my preferred method when replacing a lot of tokens because the text is parsed only once by the regex, unlike the method above, the whole text is parsed again and again for each replacement. There is a slight performance gain using the approach below.

//object to hold replacement values
var tokens = { '{name}': 'August', '{date}': 'Sunday' };
//template text
var text = 'Hello, {name}. Today is {date}.';
//replace tokens in template
text = text.replace(/\{(.+?)\}/g, function(token, match, number, txt) {
                                      return tokens[token];
                                  }
                   );
//output text
document.write(text);

The function has up to four arguments (if the regex used returns a matched string).
The first parameter will be the token (e.g. ‘{name}’), the second is the matched text from the regex (e.g. ‘name’), the third is the index of the matched token (e.g. 7 for ‘{name}’) and fourth is the original string text (e.g. ‘Hello, {name}. Today is {date}.’).

If the regex used does not returns a matched string, there will be only three arguments to your function. Example of this case shown below. The first parameter will be the token (e.g. ‘{name}’), the second is the index of the matched token (e.g. 7 for ‘{name}’) and third is the original string text (e.g. ‘Hello, {name}.’).

var text = 'Hello, {name}.';
text = text.replace(/\{name\}/g, function(token, number, txt) {
                                     return 'August';
                                 }
                   );
document.write(text);

Displaying Markers In Open Street Map Using Open Layers

Open Street Map is open data, licensed under the Creative Commons Attribution-ShareAlike 2.0 licence (CC-BY-SA). Note that while the map data is free, the service isn’t. Please read their license on their website. Open Layers is an open source map api – it could be used to access different map services like Yahoo Maps, Google Maps, and Open Street Map. The API is different from Google Maps. Aside from different API methods and objects, coordinates are in Longitude, Latitude while in Google Maps, the coordinates are in Latitude, Longitude. The map projections are also different, so in the code, you will notice some conversions between projections. The code sample below is the equivalent of the Google Maps implementation in my previous post. Marker is shown for the given location and a popup window will be displayed when the marker is clicked. An HTML div is used for the map display. A map object is created and the controls and layers are added to the map. An icon object is created for the marker, but in open layers you need to clone each icon object per marker. There are several styles of popups in open layers, I choose to use FramedCloud. The bounds object keeps track of multiple coordinates so that the center can be computed and the all the markers in the map can be displayed onscreen. The is only one popup at any time, the script keeps track of the last popup and close it before opening another one.

<html>
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
        <title>Open Street Map</title>
        <style type="text/css">
            body { font: normal 10pt Helvetica, Arial; }
            #map { width: 100%; height: 100%; border: 0px; padding: 0px; }
        </style>
        <script src="http://openlayers.org/dev/lib/OpenLayers.js" type="text/javascript"></script>
        <script type="text/javascript">
            //Sample code by August Li
            var iconSize = new OpenLayers.Size(21, 25);
            var iconOffset = new OpenLayers.Pixel(-(iconSize.w / 2), -iconSize.h);
            var icon = new OpenLayers.Icon("http://www.openstreetmap.org/openlayers/img/marker.png",
                           iconSize, iconOffset);
            var zoom, center, currentPopup, map, lyrMarkers;
            var popupClass = OpenLayers.Class(OpenLayers.Popup.FramedCloud, {
                "autoSize": true,
                "minSize": new OpenLayers.Size(300, 50),
                "maxSize": new OpenLayers.Size(500, 300),
                "keepInMap": true
            });
            var bounds = new OpenLayers.Bounds();
            function addMarker(lng, lat, info) {
                var pt = new OpenLayers.LonLat(lng, lat)
                                       .transform(new OpenLayers.Projection("EPSG:4326"), 
                                       map.getProjectionObject());
                bounds.extend(pt);
                var feature = new OpenLayers.Feature(lyrMarkers, pt);
                feature.closeBox = true;
                feature.popupClass = popupClass;
                feature.data.popupContentHTML = info;
                feature.data.overflow = "auto";
                var marker = new OpenLayers.Marker(pt, icon.clone());
                var markerClick = function(evt) {
                    if (currentPopup != null && currentPopup.visible()) {
                        currentPopup.hide();
                    }
                    if (this.popup == null) {
                        this.popup = this.createPopup(this.closeBox);
                        map.addPopup(this.popup);
                        this.popup.show();
                    } else {
                        this.popup.toggle();
                    }
                    currentPopup = this.popup;
                    OpenLayers.Event.stop(evt);
                };
                marker.events.register("mousedown", feature, markerClick);
                lyrMarkers.addMarker(marker);
            }
            function initMap() {
                var options = {
                    projection: new OpenLayers.Projection("EPSG:900913"),
                    displayProjection: new OpenLayers.Projection("EPSG:4326"),
                    units: "m",
                    numZoomLevels: 19,
                    maxResolution: 156543.0339,
                    maxExtent: new OpenLayers.Bounds(-20037508.34, -20037508.34, 20037508.34, 20037508.34)
                };
                map = new OpenLayers.Map("map", options);
                map.addControl(new OpenLayers.Control.DragPan());
                var lyrOsm = new OpenLayers.Layer.OSM();
                map.addLayer(lyrOsm);
                lyrMarkers = new OpenLayers.Layer.Markers("Markers");
                map.addLayer(lyrMarkers);
                 //add marker on given coordinates
                addMarker(121.06573, 14.65194, '<b>University of the Philippines</b><br/>Philippines');
                addMarker(121.04931, 14.65105, '<b>Quezon Memorial Circle</b><br/>Philippines');
                center = bounds.getCenterLonLat();
                map.setCenter(center, map.getZoomForExtent(bounds) - 1);
                zoom = map.getZoom();
            }
        </script>
    </head>
    <body onload="initMap()" style="margin:0; border:0; padding:0;">
        <div id="map"></div>
    </body>
</html>

Displaying Markers In Google Maps API

The version 3 of Google Maps did away with having to get an API key in order to access the Google Maps API. Here is a simplified version of the code I wrote, it basically allows you to add markers to Google Maps and displays a popup window when the marker is clicked. The map is displayed inside an HTML div (with id = “map”). At the very start of the code, I created an icon object that will be used as our marker and a map object with several parameters to set the controls and display of the map. Coordinates are in latitude and longitude and these are stored in a LatLng object. Then I pass in the LatLng, icon and map objects to the marker constructor. There is only one popup window at any one time and the code tracks which one opens and closes the previous one. Multiple coordinates are passed to LatLngBounds objects so that the center can be computed and used in centering the markers in the map when displayed onscreen.

<html>
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
        <title>Google Maps</title>
        <style type="text/css">
            body { font: normal 10pt Helvetica, Arial; }
            #map { width: 100%; height: 100%; border: 0px; padding: 0px; }
        </style>
        <script src="http://maps.google.com/maps/api/js?v=3&sensor=false" type="text/javascript"></script>
        <script type="text/javascript">
            //Sample code written by August Li 
            var icon = new google.maps.MarkerImage("http://maps.google.com/mapfiles/ms/micons/blue.png", 
                       new google.maps.Size(32, 32), new google.maps.Point(0, 0), 
                       new google.maps.Point(16, 32));
            var center = null;
            var map = null;
            var currentPopup;
            var bounds = new google.maps.LatLngBounds();
            function addMarker(lat, lng, info) {
                var pt = new google.maps.LatLng(lat, lng);
                bounds.extend(pt);
                var marker = new google.maps.Marker({
                    position: pt,
                    icon: icon,
                    map: map
                });
                var popup = new google.maps.InfoWindow({
                    content: info,
                    maxWidth: 300
                });
                google.maps.event.addListener(marker, "click", function() {
                    if (currentPopup != null) {
                        currentPopup.close();
                        currentPopup = null;
                    }
                    popup.open(map, marker);
                    currentPopup = popup;
                });
                google.maps.event.addListener(popup, "closeclick", function() {
                    map.panTo(center);
                    currentPopup = null;
                });
            }            
            function initMap() {
                map = new google.maps.Map(document.getElementById("map"), {
                    center: new google.maps.LatLng(0, 0),
                    zoom: 14,
                    mapTypeId: google.maps.MapTypeId.ROADMAP,
                    mapTypeControl: true,
                    mapTypeControlOptions: {
                        style: google.maps.MapTypeControlStyle.HORIZONTAL_BAR
                    },
                    navigationControl: true,
                    navigationControlOptions: {
                        style: google.maps.NavigationControlStyle.ZOOM_PAN
                    }
                });
                //add marker on given coordinates
                addMarker(14.65194, 121.06573,
                          '<b>University of the Philippines</b><br/>Philippines');
                addMarker(14.65105, 121.04931,
                          '<b>Quezon Memorial Circle</b><br/>Philippines');
                center = bounds.getCenter();
                map.fitBounds(bounds);
                map.setZoom(map.getZoom - 1);
            }
        </script>
    </head>
    <body onload="initMap()" style="margin:0px; border:0px; padding:0px;">
        <div id="map"></div>
    </body>
</html>

JavaScript Pitfalls: Extra Comma In Objects In IE

Firefox and Chrome are more forgiving with errors in JavaScript object literals. In IE8, an extra comma in object literal would throw an error during runtime.

var obj = {
     prop1 : 'value1',
     prop2 : 'value2',      //extra comma here throws runtime error in IE
};

Follow

Get every new post delivered to your Inbox.