Showing posts with label JavaScript. Show all posts
Showing posts with label JavaScript. Show all posts
Tuesday, June 25, 2019

Spring with Rails' jQuery UJS

I’ve always wanted to try to see if I could use Rails’ jQuery UJS in a Spring Boot project. The UJS in jquery-ujs stands for unobtrusive JavaScript. I really like how UJS wires event handlers to eligible DOM elements marked with HTML5 data-* attributes. I find myself wanting to see more of this approach being used in Spring Boot web apps. I wonder why there’s very little mentioned on the web about this. Or, may be I’ve been looking at the wrong places.

Anyway, here are some things jQuery UJS can do, and the related source code is on GitHub (albeit using a different example).

Non-GET Links (e.g. DELETE)

When I need to render a link that deletes an item, I would use a <button> wrapped in a <form> with a hidden _method field with a value of delete. The <form> is not visible to the user. But the visible button is used to submit the <form>. Some CSS is used to make the button look like a link.

<form action="/articles/42" method="post">
  <input type="hidden" name="_method" value="delete" />
  <button class="btn btn-link">Delete</button>
</form>

Thanks to Spring’s HiddenHttpMethodFilter (also automatically configured in Spring Boot), when this <form> is submitted, it will be received as a request with a method of DELETE. It maps to @DeleteMapping(path="/articles/{id}") in the related @Controller.

While the above works, there is an easier way with jQuery UJS. All that is needed to render a link to delete an item is this:

<a href="/articles/42" data-method="delete">Delete</a>

jQuery UJS will enhance links that have data-method attribute. When the above example link is clicked, the JavaScript will create a <form>. The action attribute of this <form> is set to the value of link’s href. The method is set to post. A hidden _method field is added to the <form> and set to the value of the link’s data-method. Finally, the <form> is submitted (and the link is not followed).

Confirmation Dialogs

Most often than not, when it comes to deleting anything, it would be good to confirm with the user. This could be implemented as a simple dialog via window.confirm(). If we build from the previous example, the <form> would look like this:

<form action="/articles/42" method="post"
    onsubmit="return confirm('Are you sure?')">
  <input type="hidden" name="_method" value="delete" />
  <button>Delete</button>
</form>

While the above works, jQuery UJS shows us a better way. All that is needed to confirm before deleting is this:

<a href="/articles/42?delete" data-method="delete"
    data-confirm="Are you sure?">Delete</a>

jQuery UJS will enhance links (and <form>s too) that have data-confirm attribute. When the above example link is clicked, the JavaScript will call confirm() to show a dialog containing the text that is the value of the attribute. If the user chooses to cancel, the creation/submission of the <form> (due to data-method) does not take place.

Ajax Links

After deleting an item, the page usually reloads to show that the deleted element has indeed been removed.

Let's say the items are displayed in a table. Each row has a unique id.

<table>
  <tr id="article:18">
    <!-- data cells for /articles/18 -->
    <td><a href="/articles/18?delete"
        data-method="delete" data-confirm="Are you sure?">Delete</a></td>
  </tr>
  <tr id="article:42">
    <!-- data cells for /articles/42 -->
    <td><a href="/articles/42?delete"
        data-method="delete" data-confirm="Are you sure?">Delete</a></td>
  </tr>
  <!-- other rows -->
</table>

Assuming that we can simply remove the HTML element that represented the deleted item, then we can probably send the DELETE request asynchronously and get a response that would remove the related HTML element. jQuery UJS makes this as easy as adding data-remote="true" and some minor changes to the server-side controller.

For the HTML, all we need is data-remote="true".

<a href="/articles/42?delete" data-remote="true"
    data-method="delete"
    data-confirm="Are you sure?">Delete</a>

When the link is clicked, jQuery UJS will again send the DELETE request. But this time, it will be sent asynchronously using Ajax. Doing so enables the browser not to reload the entire page. And depending on the server’s response, can update just a portion of the page. Thus, providing a slightly better user experience.

For the server-side controller, we need to send a different response when the request is expecting text/javascript. We add a handler method that will respond with text/javascript by using the produces element of @RequestMapping. The response will remove the related HTML element(s).

// inside a @Controller
@DeleteMapping(path="/articles/{id}")
String delete(... id) {
    // ... delete article with given identifier
    return "redirect:/articles";
}

@DeleteMapping(path="/articles/{id}",
    produces="text/javascript")
String delete(... id) {
    // ... delete article with given identifier
    return "articles/delete";
}

The view is a JSP that contains text/javascript. This will be executed by jQuery UJS.

<%-- articles/delete.js.jsp --%>
<%@ page contentType="text/javascript" %>
$('#article:${id}').remove();

Partials and Server-generated JavaScript Responses

Now what happens if we wanted to have an edit link to get some HTML content and show it up in a modal (without a page refresh)?

Here's what we can do. We send a GET request asynchronously. The response is expected to contain JavaScript that would append the HTML in targeted places in the document, and then trigger the modal to appear.

  <a href="/articles/42?edit" data-remote="true">Edit</a>

When the response is expected to be text/javascript, articles/edit.js.jsp is rendered. Otherwise, the usual articles/edit.jsp is rendered.

// inside a @Controller
@GetMapping(path="/articles/{id}", params={"edit"})
String edit(... id, ...) {
    // ...
    return "articles/edit";
}

@GetMapping(path="/articles/{id}", params={"edit"},
    produces="text/javascript")
String editViaAjax(... id, ...) {
    // ...
    return "articles/edit";
}

The edit.jsp renders the <form> (a partial, not a complete HTML document) which has been refactored to its own JSP to avoid repetition.

<%-- articles/edit.jsp --%>
<!-- -->
  <jsp:include page="_form.jsp" />
<!-- -->

The edit.js.jsp renders the same <form> (a partial, not a complete HTML document) as a string in JS. Then includes it in the modal. It was tricky to render _form.jsp as a string. I had to use <c:import>.

<%-- articles/edit.js.jsp --%>
<%@ page contentType="text/javascript" %>
<c:import var="html" url="…_form.jsp" />
<!-- escape double quotes and remove new lines -->
(function() {
  const $modal = $('#...'); // ID of modal element
  $modal.find('.modal-body').html('${html}');
  if (!$modal.is(':visible')) {
    $modal.modal('show');
  }
})()

For this to work, another InternalResourceViewResolver (IRVR) bean with text/javascript as the contentType is configured. This bean uses the same prefix and a slightly different suffix: .js.jsp. That way, when the request is expecting text/javascript, the CNVR will favor using the IRVR bean with text/javascript and it ends up rendering articles/edit.js.jsp.

Ajax Forms

The data-remote="true" attribute can also be applied to <form>s. With it, jQuery UJS will handle the form submission as an Ajax request. And when the form is being submitted, the buttons can be disabled by adding data-disabled-with. For example,

<form ...>
  <!-- ... -->
  <button data-disable-with="Saving...">Save</button>
</form ...>

When the above form is submitted, jQuery UJS will disable the button and change its content to "Saving...".

Closing Thoughts

I’ve barely touched the surface of Rails’ jQuery UJS. There is so much more that it can offer. I look forward to using it (and similar techniques) in my web apps.

Friday, July 26, 2013

Google Maps and Google Earth Synchronized

Inspired by Google Map Maker's visualization of updates, I created this mashup of Google Earth and Google Maps. Here, if the user pans the map (2D), the earth (3D) follows, and vice-versa.

NOTE: You'll need Google Earth Plug-in installed for this to work.

Below is a demo that is initially showing Manila, Philippines. Go ahead and pan either view and see if it works.

Loading...
Loading...

Synchronizing Earth and Maps

Using Google Earth and Google Maps is straight-forward using their JavaScript APIs. The tricky part is synchronizing them. When a user pans the map (2D), the earth (3D) should follow. Likewise, when the user pans the earth (3D), the map (2D) should follow.

My first attempt was to listen to the earth's viewchangeend event, and the map's center_changed event.

function syncEarthWithMap() {
  …
}

function syncMapWithEarth() {
  …
}
…
google.maps.event.addListener(map, 'center_changed', syncEarthWithMap);
…
google.earth.addEventListener(ge.getView(), 'viewchangeend', syncMapWithEarth);
…

Unfortunately, this resulted in an infinite loop, since the event handlers would update the view, which will trigger more events. To fix this, we'll need to remove the event handlers and restore them at a later time. The idea looks something like this:

function syncEarthWithMap() {
  …
  if (earthEventHandler.isEnabled()) {
    earthEventHandler.disableAndEnableLater();
  }
  …
  // Now that the earth event handler is disabled,
  // we can safely change it without triggering more calls.
  ge.getView().setAbstractView(lookAt);
}

function syncMapWithEarth() {
  …
  if (mapEventHandler.isEnabled()) {
    mapEventHandler.disableAndEnableLater();
  }
  …
  // Now that the map event handler is disabled,
  // we can safely change it without triggering more calls.
  map.setZoom(zoom);
  map.panTo(center);
}

Here's how the event handler would look like:

function createEventHandler(enableHandler, disableHandler, enableLater) {
  var enabled = false;
  return {
    enable: function() {
      enableHandler();
      enabled = true;
    },
    isEnabled: function() {
      return enabled;
    },
    disableAndEnableLater: function() {
      disableHandler();
      enabled = false;
      enableLater(enable);
    }
  };
}

Creating the map event handler looks something like this.

var mapEventHandler;

mapEventHandler = createEventHandler(function() {
  /* enable event handler */
  … = google.maps.event.addListener(map, 'center_changed', syncEarthWithMap);
  … = google.maps.event.addListener(map, 'bounds_changed', syncEarthWithMap);
}, function() {
  /* disable event handler */
  google.maps.event.removeListener(…);
  google.maps.event.removeListener(…);
}, function(callback) {
  /* later, enable event handler */
  google.earth.addEventListenerOnce(map, 'idle', function() {
    callback();
  });
});
mapEventHandler.enable();

The earth event handler is similar.

var earthEventHandler;

earthEventHandler = createEventHandler(function() {
  /* enable event handler */
  google.earth.addEventListener(
            ge.getView(), 'viewchangeend', syncMapWithEarth);
}, function() {
  /* disable event handler */
  google.earth.removeEventListener(
            ge.getView(), 'viewchangeend', syncMapWithEarth);
}, function() {
  /* later, enable event handler */
  …
});
earthEventHandler.enable();

Synchronizing both views can be a bit tricky. But thanks to good documentation from Google, synchronizing them can be quite fun!

While developing this, I was looking for interesting places in the Philippines that would look good on Google Earth (largely due to the presence of 3D structures). If you know of some, please let me know (include latitude and longitude if possible) via comments, and I'd add them as links that will reposition the views to those locations. Cheers!

Tuesday, July 9, 2013

Monopoly Deal Cards with CSS3

I've been learning some CSS techniques. And I thought it would be good to continue using CSS on something I can show to others. So, here's a rough attempt to render Monopoly Deal cards with CSS3.

Thanks to the creative team of Edwin and Renee for teaching me and answering some of my questions. Also thanks to Sofia for letting me borrow some Monopoly Deal cards. And the Hong Kong edition looks great too.

What's next? Can we add some JavaScript and make an online game with this?

WARNING: Only tested on Firefox 20+.

My apologies for not explaining the CSS behind this. Perhaps, if I get enough time (and requests), I'll write about it. Besides, I'm sure I'm not the first to attempt this. For now, use your favorite HTML/Browser tool to checkout the CSS behind it.

4
4
4
Boardwalk
4
(No. of properties owned in set) RENT
1
3
1
2
8
FULL SET
New York
Avenue
2
(No. of properties owned in set) RENT
1
1
1
2
3
1
2
3
5
FULL SET
North
Carolina Avenue
4
(No. of properties owned in set) RENT
1
2
1
2
4
1
2
3
7
FULL SET
太平山
Victoria Peak
4
(No. of properties owned in set) 租金 RENT
1
3
1
2
8
全套
FULL SET

DISCLAIMER: I still don't know how to play the game.

Update (Dec 2013): Thanks to Jason for asking. Here's an action card.

3
ACTION CARD
SLY DEAL
Steal a property from
the player of your choice.
(Cannot be part of a full set.)
Play into center to use.
3

Tuesday, April 2, 2013

JavaScript: A Love-Hate Relationship

I’ve been programming in Java for a greater part of my professional career. As web development got more powerful and demanding (and complex), I ended up dealing with client-side JavaScript more often than I wanted. In most of these cases, I’ve come to hate it. To avoid it, I considered alternatives like Adobe’s Flex and GWT.

Until about five (5) years ago, when I sat down and really started learning JavaScript, I started to love it. It felt like one of those new year’s resolutions that I failed to complete. Good thing, I faced projects that relied heavily on jQuery and Google Maps JavaScript API. With that, I was forced encouraged to dive in and learn JavaScript. So far, I’m glad I did.

Variables and Primitive Data Types

How many primitive data types does JavaScript have? Three: string, numeric, and boolean. It only has three primitive data types compared to the eight of Java. Wow! How simpler could it get?!

As it turns out, there are two more primitive data types: null and undefined are also considered primitive data types. Undefined is the values of a variable with no value. Variables can be emptied by setting the value to null.

Note that primitive data types are automatically wrapped as objects (e.g. a string literal is automatically wrapped as a String object). This makes them possess properties and methods.

var text = 'hello'; // or text = new String('hello');
var len = text.length; // returns a number
text.charAt(1); // returns 'e'

Native Objects

The JavaScript language provides some native objects: Array, Function, Date, Error, and Math. Objects can have properties and methods. Objects are key-value pairs. The code snippet below shows an object with five properties. Two of which are functions.

var myObject = {
  prop1: 'hello',
  prop2: false,
  prop3: new Date(),
  function1: function(param1, param2) {. . .},
  function2: function() {. . .}
};

// the above is equivalent to this:
var myObject2 = {
  'prop1': 'hello',
  'prop2': false,
  'prop3': new Date(),
  'function1': function(param1, param2) {. . .},
  'function2': function() {. . .}
};

// accessing the properties is like indexing a map
alert(myObject['prop1'] == myObject.prop1); // true

No Classes, Just Objects

As I dug deeper, I found it confusing that there are no classes, just objects. Coming from a Java background, I wanted to learn how to apply OOP. With that, I looked for creating private member variables and functions. JavaScript is a class-free, prototypal language. Instead of defining classes, and creating objects based on classes, it simply creates objects that inherit from (parent) objects that act as classes.

JavaScript is a prototype-based language.

Prototype-based programming is a style of object-oriented programming in which classes are not present, and behavior reuse (known as inheritance in class-based languages) is accomplished through a process of decorating existing objects which serve as prototypes. This model is also known as class-less, prototype-oriented, or instance-based programming.

JavaScript MDN

This is the part I found confusing. But after a while, it’s becoming clear. Declaring a class in JavaScript is as easy as defining a function.

function Student(name) {
  // This is the constructor
  // We can add properties (instance variables) to "this"
  this.name = name;
}

Since functions are objects in JavaScript, we can add properties and methods. In this case, we use the prototype property of a Function object to add methods.

Student.prototype.greet = function(greeting) {
  alert(greeting + ', my name is '
      + this.name);
};

Student.prototype.anotherMethod = function() {
};

We can create Student objects by calling the constructor with the new keyword.

var student1 = new Student('John Smith');
student1.greet('Hi'); // will display 'Hi, my name is John Smith'
alert(student1.name); // the name property is public

So, how can we provide private members to classes? In JavaScript, functions are used to provide scope and hide variables. For this, let’s change the public name property of the Student class to become private. We also add a public getter method to access the private name property.

function Student(name) {
  // This is the constructor
  // We can add public properties (instance variables) to "this"
  // this.name = name;
  var _name = name;
  this.getName = function() { return _name; };
}

This also changes the public member method in its reference to the private name property.

Student.prototype.greet = function(greeting) {
  alert(greeting + ', my name is '
      + this.getName());
};

Student.prototype.anotherMethod = function() {. . .};

Now that the name property is made private, the usage of Student class will also change.

var student1 = new Student('John Smith');
student1.greet('Hi'); // will display 'Hi, my name is John Smith'
alert(student1.name); // will return undefined
alert(student1.getName()); // will return 'John Smith'

Closures

JavaScript uses closures to provide access to variables that continue to exist even after the variables are out of scope. Let's take the following example from one of Douglas Crockford's talks.

var names = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine'];
var digit_name = function(n) {
  return names[n];
};

In the above example, the names array is declared at the global scope. We can move names as a variable inside the function (and avoid name clashes with other functions). The result looks something like this:

// var names = [];
var digit_name = function(n) {
  var names = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine'];
  return names[n];
};

But the above is slower, since the names array would have to be (re-)initialized every time the function is called. Here's where we can use a closure to allow the function to continue to have access to the names array variable even after it is out of scope.

var digit_name = (function() {
  var names = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine'];

  // return a function
  return function(n) {
    // var names = [];
    return names[n];
  };
})();

Notice that the variable digit_name is assigned the result of a function call. To make it clearer, let’s try re-formatting the code. Hopefully, this makes it clearer.

var digit_name = (function() {...})();

The open-and-close parenthesis immediately calls the function. And that function returns a function.

var digit_name = (function() {... return function(n) {}; })();

Closures are also used to declare private members. In the Student class example, notice how the getName method still had access to the _name variable?

Understanding the Misunderstood

JavaScript is the world’s most misunderstood language largely because people aren’t taking the time to learn it. I hope that developers continue to take programming seriously.

It is the language that most people use without bothering to learn it first.

Programming is a complicated business. It should never be taken in ignorance.

Douglas Crockford - Senior JavaScript Architect, PayPal


Wednesday, November 7, 2012

Google Closure Compiler with Maven Builds

In my previous post, I showed how we used Closure Library with our Maven-based project. More specifically, we used the Jetty Maven plugin to expose the JS code, and make it available in raw form for a fast and convenient code-run-debug cycle. This time, let me show how we used the Closure Compiler to compile and generate the minified JS file, and how we used it in our builds.

Running Closure Compiler

Running the compiler was a bit tricky. Since we didn't have just one single JS file, we needed to provide the compiler a list of JS files in the correct order (where providing classes are declared before the ones that required them). Good thing the library comes with Python scripts that does just that.

<plugin>
 <groupId>org.codehaus.mojo</groupId>
 <artifactId>exec-maven-plugin</artifactId>
 <executions>
  <execution>
   <id>calculate-dependencies</id>
   <phase>process-sources</phase>
   <goals>
    <goal>exec</goal>
   </goals>
   <configuration>
    <executable>python</executable>
    <arguments>
     <argument>src/main/js/closure-library/closure/bin/build/depswriter.py</argument>
     <argument>--root_with_prefix</argument>
     <!-- prefix is relative to location of closure-library/closure/goog/base.js -->
     <argument>src/main/js/myapp/ ../../../myapp/</argument>
     <argument>--output_file</argument>
     <argument>src/main/js/myapp/deps.js</argument>
    </arguments>
   </configuration>
  </execution>
  <execution>
   <id>compile-javascript</id>
   <phase>prepare-package</phase>
   <goals>
    <goal>exec</goal>
   </goals>
   <configuration>
    <executable>python</executable>
    <arguments>
     <argument>src/main/js/closure-library/closure/bin/build/closurebuilder.py</argument>
     <argument>--root</argument>
     <argument>src/main/js/closure-library</argument>
     <argument>--root</argument>
     <argument>src/main/js/myapp</argument>
     <argument>--output_mode</argument>
     <argument>compiled</argument>
     <argument>--compiler_jar</argument>
     <argument>src/lib/closure-compiler-20120430.jar</argument>
     <!-- WHITESPACE_ONLY, SIMPLE_OPTIMIZATIONS, ADVANCED_OPTIMIZATIONS -->
     <argument>--compiler_flags=--compilation_level=ADVANCED_OPTIMIZATIONS</argument>
     <!-- QUIET, DEFAULT, VERBOSE -->
     <argument>--compiler_flags=--warning_level=VERBOSE</argument>
     <argument>--namespace</argument>
     <argument>myapp</argument>
     <argument>--output_file</argument>
     <argument>${project.build.directory}/replaced-webapp/scripts/myapp.min.js</argument>
    </arguments>
   </configuration>
  </execution>
 </executions>
</plugin>

I've included the portion that calculates dependencies (from my previous post). Note that use of src/lib/closure-compiler-yyyymmdd.jar. You can download it from here, and extract the compiler.jar file. We just renamed it so we can track which version we're using. And in case of updates, just update the pom.xml.

Note also that we invoke the compiler during the prepare-package phase. This means that when you run jetty:run, the compiler does not get invoked. This makes it faster (and easier to repeat). But when you need to run/debug with the compiled/minified form (with advanced optimization), you run jetty:run-war.

The astute reader would notice that the minified file is currently not being referenced in any web page. This shall be explained in the next section.

Replacing Tags in Web Pages

Since the output of the compiler was one single JS file, we needed to modify our web pages (e.g. JSPs) to refer to it (and not the multiple files). Obviously, we could manually modify the JSP files. But we needed something that would automatically replace them during the build. We decided to use maven-replacer-plugin.

<plugin>
  <groupId>com.google.code.maven-replacer-plugin</groupId>
  <artifactId>maven-replacer-plugin</artifactId>
  <version>1.4.1</version>
  <executions>
    <execution>
      <phase>prepare-package</phase>
      <goals>
        <goal>replace</goal>
      </goals>
    </execution>
  </executions>
  <configuration>
    <ignoreMissingFile>true</ignoreMissingFile>
    <file>src/main/webapp/index.jsp</file>
    <outputFile>${project.build.directory}/replaced-webapp/index.jsp</outputFile>
    <regex>true</regex>
    <regexFlags>
      <regexFlag>MULTILINE</regexFlag>
      <regexFlag>DOTALL</regexFlag>
    </regexFlags>
    <replacements>
      <replacement>
        <token><!-- SCRIPTS.*END SCRIPTS --></token>
        <value><script src="<c:url value='/scripts/myapp.min.js' />" ></script></value>
      </replacement>
    </replacements>
  </configuration>
</plugin>

This effectively replaces the following:

<!-- SCRIPTS -->
<script src="/closure-library/closure/goog/base.js"></script>
<script src="/myapp/deps.js"></script>
<script src="/myapp/myapp.js"></script>
<!-- END SCRIPTS -->
<script>init();</script><!-- our JS code's entry point -->

with

<script src="<c:url value='/scripts/myapp.min.js' />"></script>
<script>init();</script><!-- our JS code's entry point -->

Note that the minified JS file is written to target/replaced-webapp/scripts. But this folder is never used to build the WAR. So, we need to modify our pom.xml to include this folder when building the WAR file.

<plugin>
 <groupId>org.apache.maven.plugins</groupId>
 <artifactId>maven-war-plugin</artifactId>
 <configuration>
  <webResources>
   <resource>
    <directory>src/main/webapp</directory>
    <filtering>true</filtering>
   </resource>
   <resource>
    <directory>target/replaced-webapp</directory>
    <filtering>true</filtering>
    <includes>
     <include>**/*.jsp</include>
    </includes>
   </resource>
  </webResources>
 </configuration>
</plugin>

The above configuration effectively adds the target/replaced-webapp directory on to the WAR file. This adds a scripts folder with a minified JS file in it. It also contains the modified index.jsp file. When the web page references /scripts/myapp.min.js, it will find it.

Conclusion

It is great that we're able to debug in raw form. This provides an easier way to find which JS file/line is causing the error. We are also able to debug in minified form, and deploy the minified form. I hope this helps.

Sunday, November 4, 2012

Integrating Google Closure Tools with Maven Builds

We wanted to use Google Closure Tools (compiler, plus the library) in our Maven-based project. The current approaches posted here and here were very good. But didn't quite satisfy our needs. As our JavaScript code base grew in size, and in complexity, we needed to run/debug our web pages with the JavaScript code in their raw form, apart from the minified/compiled mode. So, here's how we did it.

Project Structure (Maven's Standard Directory Layout)

Our project structure is as follows:

  • src/main/java
  • src/main/js
  • src/main/js/closure-library
  • src/main/js/myapp
  • src/main/resources
  • src/test/java
  • src/test/resources
  • src/main/webapp

It follows the standard directory layout. We placed our JavaScript (JS) code under src/main/js. We also placed the Closure Library underneath it. This made it easy to expose as additional web app root directories via the Jetty Maven Plugin. As will be explained later, this was crucial in being able to run the our JS code in raw form, which required the inclusion of base.js and our app's deps.js.

In our Subversion code repository, we linked to the Closure Library's code repository. A similar approach can be applied using git-svn mirror when using Git.

$ svn propget svn:externals
src/main/js/closure-library
http://closure-library.googlecode.com/svn/trunk/

This external link provided src/main/js/closure-library/closure/goog/base.js to be part of our project structure.

Running/Debugging with Closure JavaScript in Raw Form

To have quick code-run-debug cycles, we couldn't afford to compile the JS code every time. So, we had to find a way to run our web pages with the following JS script tags.

<html>
<head>...</head>
<body>
<script src="closure-library/closure/goog/base.js"></script>
<script src="myapp/deps.js"></script>
<script src="myapp/main.js"></script>
<script>init();</script><!-- our JS code's entry point -->
</body>
</html>

To make this possible, we needed to expose the JS files on our servlet container. This was made easier by having our JS code and Closure Library under one folder.

<plugin>
  <groupId>org.mortbay.jetty</groupId>
  <artifactId>jetty-maven-plugin</artifactId>
  <configuration>
    <webAppConfig>
      <contextPath>/</contextPath>
      <baseResource 
          implementation="org.eclipse.jetty.util.resource.ResourceCollection">
        <resourcesAsCSV>src/main/webapp,src/main/js</resourcesAsCSV>
      </baseResource>
    </webAppConfig>
  </configuration>
</plugin>

Calculating Dependencies

Our JS code was not just one file. It had more than a dozen files. Just like the ones in the Closure Library, each class was placed in one file. Each had goog.provide and goog.require calls. For our JS classes to work, we needed to generate a dependency file. The Closure Library comes with its pre-generated deps.js file for its classes. We'll need one for our classes. We used the Python script (that comes with the Closure Library), closure/bin/build/depswriter.py. We chose to invoke it during the process-sources phase of the build. This made it easier when running the jetty:run command, as the dependency file will be re-generated before the servlet container is up and running.

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>exec-maven-plugin</artifactId>
  <executions>
    <execution>
      <id>calculate-dependencies</id>
      <phase>process-sources</phase>
      <goals>
        <goal>exec</goal>
      </goals>
      <configuration>
        <executable>python</executable>
        <arguments>
          <argument>src/main/js/closure-library/closure/bin/build/depswriter.py</argument>
          <argument>--root_with_prefix</argument>
          <!-- prefix is relative to location of closure-library/closure/goog/base.js -->
          <argument>src/main/js/myapp/ ../../../myapp/</argument>
          <argument>--output_file</argument>
          <argument>src/main/js/maypp/deps.js</argument>
        </arguments>
      </configuration>
    </execution>
  </executions>
</plugin>

If there are no changes to the dependencies, you can modify your JS code, and just refresh/reload the web page, and the JS code is also reloaded. This provides a very fast and convenient code-run-debug cycle.

The resulting HTML should have loaded the JS files in the correct order. It should look something like this when viewed using your browser's debug mode (e.g. Firebug, or Webkit's Web Inspector).

<script src="/closure-library/closure/goog/base.js"></script>
<script type="text/javascript" src="http://localhost:8080/closure-library/closure/goog/deps.js"></script>
<script src="/myapp/deps.js"></script>
<script src="/myapp/main.js"></script>
<script type="text/javascript"
  src="http://localhost:8080/closure-library/closure/goog/disposable/idisposable.js"></script>
<script type="text/javascript"
  src="http://localhost:8080/closure-library/closure/goog/disposable/disposable.js"></script>
<script type="text/javascript"
  src="http://localhost:8080/closure-library/closure/goog/debug/error.js"></script>
<script type="text/javascript"
  src="http://localhost:8080/closure-library/closure/goog/string/string.js"></script>
<script type="text/javascript"
  src="http://localhost:8080/closure-library/closure/goog/asserts/asserts.js"></script>
<script type="text/javascript"
  src="http://localhost:8080/closure-library/closure/goog/array/array.js"></script>
<script type="text/javascript"
  src="http://localhost:8080/closure-library/closure/goog/../../../myapp/finder.js"></script>
<script type="text/javascript"
  src="http://localhost:8080/closure-library/closure/goog/../../../myapp/model.js"></script>
<script type="text/javascript">init();</script><!-- our JS code's entry point -->

It's interesting to point out how the Closure Library adds its deps.js file, and how it adds the dependencies (e.g. idisposable.js, error.js, string.js). The dependencies are due to the JS code requiring these, and the deps.js file adds in the corresponding JS <script> tags. Also note how our app's JS files are referenced — relative to the location of base.js. Look at lines 17-20. This is why we had to use the --root_with_prefix argument when calling depswriter.py. Otherwise, our JS code wouldn't even be loaded.

I'll post another follow-up on this topic where I'll explain how we compile, minify, and test the minified JS code.

Thursday, May 24, 2012

Rappler.com Map of Corona Assets

After looking at Rappler.com's Map of Chief Justice Corona Assets, I thought I'd use this opportunity to build something using Google Maps and Visualization APIs.

Corona Assets Map (opens page in new window)

Sunday, May 6, 2012

MVCObject using Closure Library

I've recently been spending more time with JavaScript. I thought I'd make something like google.maps.MVCObject (from Google Maps JavaScript API) as a way for me to learn more about JavaScript and the Closure Library. So, here's what I've got, so far.

Understanding MVCObject

First off, here's how MVCObject behaves (at least, this is my understanding).

  1. When a property 'foo' of objA is bound to a property 'foo' of objB, the following happens:
    • Setting value x as property 'foo' on objB, results to objA.get('foo') returning the same value x.
    • Calling objA.get('foo') is equivalent to calling objB.get('foo'). In fact, objA.get('foo') returns the same object returned by objB.get('foo').
    • Setting value x as property 'foo' on objA is actually setting value x as property 'foo' on objB.
    • Calling objB.set('foo', x) notifies objA (and all objects bound to its 'foo' property) about 'foo_changed'. This effectively makes listening to objB's 'foo_changed' event equivalent to listening to objA's 'foo_changed' event.
  2. When a property 'bar' of objA is bound to a property 'foo' of objB, and property 'foo' of objB is bound to a property 'foo' of objC, the following happens:
    • Calling objA.set('bar') is equivalent to calling objB.set('foo'), which, in turn, is equivalent to calling objC.set('foo')
    • Calling objA.get('bar') is equivalent to calling objB.get('foo'), which, in turn, is equivalent to calling objC.get('foo')
    • ...You get the point.

This binding technique can save memory and provide better performance, since the object (or property value) is not duplicated. When bounded, Objects A and B actually point to the same value for their property 'foo'. So, if the object value happens to be a large object with several properties, then there's significant memory to be saved (since they point to the same object, and it is not duplicated).

MVCObject Tests

Second, here's how I started with some unit tests to help guide me in writing the MVCObject class with the Closure Library.

goog.require('goog.events');
goog.require('goog.testing.jsunit');
goog.require('example.MVCObject');

function testSetAndGet() {
  var objA = new example.MVCObject();
  objA.set('foo', 2);
  assertEquals(2, objA.get('foo'));
  assertEquals(2, objA.foo);
}

function testBindObjectAToObjectB() {
  var objA = new example.MVCObject();
  var objB = new example.MVCObject();
  objA.bindTo('foo', objB);
  objB.set('foo', 2);
  assertEquals(2, objA.get('foo'));
  assertEquals(objB.get('foo'), objA.get('foo'));
}

function testDispatchesChangedEventWhenPropertyIsSet() {
  var objA = new example.MVCObject();
  var called = false;
  goog.events.listenOnce(objA, 'foo_changed', function(event) {
    called = true;
  });
  objA.set('foo', 2);
  assertTrue(called);
}

function testUnbind() {
  var objA = new example.MVCObject();
  var objB = new example.MVCObject();
  objA.bindTo('foo', objB);
  objB.set('foo', 2);
  objA.unbind('foo');
  objB.set('foo', 3);
  assertEquals(2, objA.get('foo'));
}

MVCObject Implementation

Lastly, here's my implementation of MVCObject.

goog.provide('example.MVCObject');


goog.require('goog.events');
goog.require('goog.events.EventTarget');


/**
 * Base class implementing KVO (key-value object).
 *
 * @constructor
 * @extends {goog.events.EventTarget}
 */
example.MVCObject = function() {
  // Call super constructor
  goog.events.EventTarget.call(this);
};
goog.inherits(example.MVCObject, goog.events.EventTarget);


/**
 * @type {string}
 * @private
 */
example.MVCObject.EVENT_SUFFIX_ = '_changed';


/**
 * Returns the value of a property.
 *
 * @param {string} key the name of the property.
 * @return {*} the value of a property.
 */
example.MVCObject.prototype.get = function(key) {
  var binding = this.getBindings_()[key];
  if (binding) {
    // Use binding to get bounded object and return its value.
    return binding.target.get(binding.targetKey);
  } else {
    return this[key];
  }
};


/**
 * Sets a property.
 *
 * @param {string} key the name of the property.
 * @param {*} value the new value of the property.
 */
example.MVCObject.prototype.set = function(key, value) {
  var bindings = this.getBindings_();
  if (key in bindings) {
    // Use bindings[key] to get bounded object and set its value.
    var binding = bindings[key];
    binding.target.set(binding.targetKey, value);
  } else {
    this[key] = value;
    this.notify(key);
  }
};


/**
 * Sets a collection of key-value pairs.
 * @param {Object} values a collection of key-value pairs.
 */
example.MVCObject.prototype.setValues = function(values) {
  for (var key in values) {
    this.set(key, values[key]);
  }
};


/**
 * Lazily initialize bindings map.
 * @return {Object} the bindings map.
 * @private
 */
example.MVCObject.prototype.getBindings_ = function() {
  if (!this.bindings_) {
    this.bindings_ = {};
  }
  return this.bindings_;
};


/**
 * Adds a binding between the given property and a target property.
 *
 * @param {string} key the name of the property.
 * @param {example.MVCObject} target the target object.
 * @param {string} targetKey the name of the property on the target object.
 * @param {boolean=} opt_noNotify optional flag to indicate that no
 *     *_changed call-back shall be called.
 * @private
 */
example.MVCObject.prototype.addBinding_ = function(
    key, target, targetKey, opt_noNotify) {
  this.getBindings_()[key] = {
    target: target,
    targetKey: targetKey
  };
  if (!opt_noNotify) {
    this.notify(key);
  }
};


/**
 * Lazily initialize listeners map.
 * @return {Object} the listeners map.
 * @private
 */
example.MVCObject.prototype.getListeners_ = function() {
  if (!this.listeners_) {
    this.listeners_ = {};
  }
  return this.listeners_;
};


/**
 * Binds the property identified by 'key' to the specified target.
 *
 * @param {string} key the name of the property to be bound.
 * @param {example.MVCObject} target the object to bind to.
 * @param {string=} opt_targetKey the optional name of the property on the
 *     target, if different from the name of the property on the observer.
 * @param {boolean=} opt_noNotify optional flag to indicate that *_changed
 *     call-back shall not be called upon binding.
 */
example.MVCObject.prototype.bindTo = function(
    key, target, opt_targetKey, opt_noNotify) {
  opt_targetKey = opt_targetKey || key;
  this.unbind(key);
  this.getListeners_()[key] =
    goog.events.listen(
        target, opt_targetKey + example.MVCObject.EVENT_SUFFIX_,
        function() { this.notify(key); }, undefined, this);
  this.addBinding_(key, target, opt_targetKey, opt_noNotify);
};


/**
 * Removes a binding of a property from its current target. Un-binding
 * will set the unbound property to the current value. The object will
 * not be notified, as the value has not changed.
 *
 * @param {string} key the name of the property to be un-bound.
 */
example.MVCObject.prototype.unbind = function(key) {
  var listeners = this.getListeners_();
  var listenerKey = listeners[key];
  if (listenerKey) {
    goog.events.unlistenByKey(listenerKey);
    delete listeners[key];
    this[key] = this.get(key);
    delete this.getBindings_()[key];
  }
};


/**
 * Removes all bindings.
 */
example.MVCObject.prototype.unbindAll = function() {
  var listeners = this.getListeners_();
  for (key in listeners) {
    this.unbind(key);
  }
};


/**
 * Notify all observers of a change on this property. This notifies both
 * objects that are bound to the object's property as well as the object
 * that it is bound to.
 *
 * @param {string} key the name of the property that was changed.
 */
example.MVCObject.prototype.notify = function(key) {
  this.changed(key);
  // Dispatch *_changed event to all listeners
  this.dispatchEvent(key + example.MVCObject.EVENT_SUFFIX_);
};


/**
 * Generic handler for state changes. Override this in derived classes to
 * handle arbitrary state changes.
 *
 * @param {string} key the name of the property that changed.
 */
example.MVCObject.prototype.changed = function(key) {
};


/**
 * @override
 */
example.MVCObject.prototype.disposeInternal = function() {
  example.MVCObject.superClass_.disposeInternal.call(this);
  this.unbindAll();
};

I find Closure's way of declaring classes and methods to be very close to Java. It's event management makes user-defined custom events, and DOM/browser events, the same. It also provides a JsUnit-based testing framework. It's very rich in features.

It's making me think (or re-think) if there is value in having all these features under one roof. I mean, when I'm considering building a single-page app, I'd go look at backbone.js, which uses underscore.js. This may also mean using jQuery for DOM manipulation. I find that most (if not all) of the features of these three (3) JavaScript libraries can be found in Google's Closure Library (and possibly more). Will there be significant code savings when using one common JavaScript library?

All in all, I'm liking Closure. I'll spend more time learning it.