Friday May 16, 2008

Server-side image processing with JRuby-on-Rails and the Java 2D API

One of the many advantages of creating Rails applications with JRuby is the access that JRuby gives you to the rich set of Java libraries available in the Java platform.

This blog entry provides step-by-step instructions for creating a simple Rails application that uses the Java 2DTM API to perform server-side image processing. It also shows you how to install and use the GlassFishTM v3 Gem, which contains only the GlassFish v3 kernel, Grizzly, and other utilities, thereby giving you an application server with a smaller size and a faster start-up time.

Although Rails is intended for developing database-backed web applications, this application does not use a database. Many think that it is better to use the file system rather than a database to store binaries. I'll leave it up to you whether you use a database or not for that. In any case, this blog entry focuses on taking advantage of JRuby to access Java platform libraries from a Rails application.

Installing the Software

To get started, go through these steps to install JRuby, Rails, and the GlassFish v3 Gem if you haven't already.
  1. Download jRuby-bin-1.1.zip from jruby.codehaus.org and unpack the zip file.
  2. Add the path to your jRuby installation to your system path.
  3. Install Rails on your JRuby VM by running this command:
    jruby -S gem install rails
    
  4. Install the GlassFish v3 gem on your JRuby VM by running this command:
    jruby -S gem install glassfish
    

Creating the Rails Application

Now, let's set up the application like you would with any JRuby-on-Rails application.
  1. Go to <JRUBY_INSTALL>/samples.
  2. Generate the application's directory structure:
    jruby -S rails photo
  3. Go to the photo directory you just created.
  4. Generate a controller and a default view:
    jruby script/generate controller home index
  5. Tell Rails that you are not using a database for this application by doing the following:
    1. Open the <JRUBY_INSTALL>/samples/photo/config/environment.rb file in a text editor.
    2. Remove the hash mark (#) from line 21 so that it reads:
      config.frameworks -= [ :active_record, :active_resource, :action_mailer ]

Setting Up the User Interface

This application has a simple UI that displays an image, a combobox, and a button. The user selects an image-filtering effect from the combobox and clicks the button. When the user clicks the button, the application performs an image processing operation on the displayed image to produce the effect and opens a new page that displays the processed image. Here's a screenshot of the first page:

To set up the UI, do the following:

  1. Copy a JPEG image to <JRUBY_INSTALL>/samples/photo/public/images.
  2. Open <JRUBY_INSTALL>/samples/photo/app/views/home/home.html.erb in a text editor.
  3. Replace its contents with the following:
    <html>
    <body>
    <img src="../../images/kids.jpg"/><p>
    <% form_tag :action => 'seeimage' do -%>
    <%= select_tag "operation",
            "<option selected='selected'>Grayscale</option>
            <option>Negative</option>
            <option>Brighten</option>
            <option>Sharpen</option>
    %>
    <<% end -%>
    </body>
    </html>
    
    Here you're using the form tag and select_tag helpers that Rails provides. It's a good idea to use the helpers rather than to write this HTML by hand because the helpers take care of a lot of extra stuff you'd have to add if you wrote the HTML yourself. When you run the application, just view the source of the page and you'll see what HTML the helpers generate for you. Check out Ruby on Rails Manual ActionView::Helpers for more information on the various helpers for views.

    From the combobox on this page, the user can select from four different image effects: Grayscale, Negative, Brighten, and Sharpen. The selected name of the effect is saved into the operation variable and is passed as a request parameter to the seeimage action of the controller. The action will use this request parameter to execute the appropriate image-processing code.

Adding the Image Processing Code to the Controller

This application gives you a good overview of how to access Java libraries from a Rails application using Ruby code. It also includes a nice sampling of the image-processing operations available in the Java 2D API.

While adding the Ruby code that performs the image processing to the controller, you'll learn the following concepts involved in using Java libraries in a Rails application:

  • Giving your controller access to Java libraries
  • Referring to Java classes
  • Performing file input and output using the java.io and javax.imageio packages
  • Assigning Java objects to Ruby objects
  • Calling Java methods and using variables
  • Converting arrays from Java language arrays to Ruby arrays
  • Streaming files to the client
You'll also learn the following concepts associated with performing image processing with Java 2D:
  • Reading an image file into a buffered image so that you can process it.
  • Obtaining a Graphics2D object from the buffered image so that you can draw the processed image.
  • Using the different image operations offered by Java 2D
  • Filtering a buffered image through one of the image operations
  • Writing the processed image to a byte output stream so that you can stream it to the client.
For your convenience, I've included a copy of the controller online here: home_controller.rb.

Giving the Controller Access to Java Libraries

To allow your controller to use Java libraries, perform these steps:
  1. Open <JRUBY_INSTALL>/samples/photo/app/controllers/hello_controller.rb in a text editor
  2. Add the following line right inside the HomeController class declaration:
    include Java
    
This one line is all you need to access Java libraries from your controller.

Referencing Java Classes

The photo example uses constants to reference Java classes it uses frequently.

To add the constants you need for this application, do the following:

  • After the include Java statement, add the following constant declarations in your controller:
      BI = java.awt.image.BufferedImage
      CS = java.awt.color.ColorSpace
      IO = javax.imageio.ImageIO
    
    Now you can use the constant to reference the class later, as shown by this line:
    bi2 = BI.new(w, h, BI::TYPE_INT_RGB)
    
In addition to using constants, you have three ways to reference a Java class from Ruby code:
  • Use the familiar import statement:
    import java.awt.image.BufferedImage
    ...
    bi2 = BufferedImage.new(w, h, BufferedImage::TYPE_INT_RGB)
    
  • Include the class using the include_class statement:
    include_class 'java.awt.image.BufferedImage'
    
  • Reference the fully-qualified name of the class when invoking its methods:
    filename = "#{RAILS_ROOT}/public/images/kids.jpg"
    file = java.io.File.new(filename)
    
  • Creating the Actions Needed in the Controller

    In a typical Rails application, each of your views maps to an action of the same name in your controller. When you access a view in your browser, the corresponding action executes. You have a view named index.html.erb, and so you need an action called index. As I explained in the section on creating the UI, the form submits to the seeimage action in the controller. Normally, you would need a page called seeimage.html.erb to map to this action. But, in this case, the controller will stream the image to the browser, and so you need actions called index and seeimage, but you don't need a view that maps to seeimage.
    1. Inside the HomeController class declaration and after the constants you added in the previous section, add a seeimage action:
      def seeimage
      end
      
    2. After the seeimage action, add an index action:
      def index
      end
      

    Getting Request Parameters

    All request parameters are accessible through the param method, which returns the parameters in a hash.

    The operation request parameter has the value the user selected from the menu on index.html.erb. To get the value of operation, do the following:

    • Inside the seeimage action, read the value of the operation request parameter into a variable called @data:
         @data = params[:operation]
      
    You'll use this variable to select the proper image-processing operation.

    Reading the Image File Into a Buffered Image

    The next step is to input the image file into an in-memory buffered image, represented by a BufferedImage object so that you can perform operations on the image.
    • Inside the seeimage action, right after the assignment of the operation request parameter into the @data variable, add the following code:
      filename = "#{RAILS_ROOT}/public/images/kids.jpg"
      imagefile = java.io.File.new(filename)
      bi = IO.read(imagefile)
      w = bi.getWidth
      h = bi.getHeight
      bi2 = BI.new(w, h, BI::TYPE_INT_RGB)
      big = bi2.getGraphics
      big.drawImage(bi, 0, 0, nil)
      bi = bi2
      
    The preceding code does the following:
    1. Reads the image file into a File object.
    2. Uses the ImageIO class to store the image file into memory as a BufferedImage object so that you can perform operations on it.
    3. Creates a new BufferedImage with the preferred size and bit-depth to facilitate image processing.
    4. Creates a Graphics2D object from the new BufferedImage object so that the graphics context, or drawing surface, has the proper size.
    5. Uses the Graphics2D object to draw the original buffered image to the graphics context.
    6. Saves the new buffered image into the original one.

    As you can see, referencing Java classes and methods from Ruby code is not much different from doing it from Java code. Notable differences are the following:

    • You do not need to declare any types when using Ruby code. Ruby can infer the type based on the return value of the method call or the method's argument list. For example, Ruby can tell that bi is a BufferedImage object because that's what the read method of ImageIO returns.
    • You don't need to add parentheses to a method call when it takes no arguments.
    • You don't add semicolons to the ends of method calls.
    • You use nil instead of null to represent a null value.
    • You use a double colon in between the class name and the field name when referencing static values, such as when referencing the TYPE_INT_RGB field of BufferedImage:
      BI::TYPE_INT_RGB

    Creating a Filter That Can Produce the User's Chosen Effect

    Now that you have the user's chosen image operation saved in @data, you can write a case statement that creates the appropriate filter based on the value of @data. Each condition of the case statement uses a different class from the Java 2D API that can be used to perform a particular image-filtering operation. All of the classes implement BufferedImageOp. For more detail on image filtering in Java 2D, see Using Java 2D's Image Processing Model.

    This section goes into some detail about Java 2D image processing. If you're more interested in using Java libraries with Ruby code rather than the Java 2D API, just look for the Ruby_Info tag delimeters.

    1. After the code to save the image into a buffered image, create a variable to hold the image filter:
      op = nil
      
    2. Add the following case statement:
      case @data
         when "GrayScale"
            colorSpace = CS.getInstance(CS::CS_GRAY)
            op = java.awt.image.ColorConvertOp.new(colorSpace, nil)
         when "Negative"
            lut = Array.new
            for j in 0..255
               lut[j] = 256-j
            end
            jlut = lut.to_java :byte
            blut = java.awt.image.ByteLookupTable.new(0, jlut)
            op = java.awt.image.LookupOp.new(blut, nil)
         when "Brighten"
            op = java.awt.image.RescaleOp.new(1.4, -25, nil)
         when "Sharpen"
            data = [-1, 0, -1, 0, 5, 0, -1, 0, -1]
            dataFloat = data.to_java :float
            sharpen = java.awt.image.Kernel.new(3, 3, dataFloat)
            op = java.awt.image.ConvolveOp.new(sharpen)
      end
      
    <Ruby_Info> The Ruby case statement is similar to the switch statement in the Java programming language, but is more powerful and flexible, partly because it internally tests for multiple conditions at once. For example, one condition of the statement can do a string comparison while another condition can perform regular expression matching, but that's beyond the scope of this blog.</Ruby_Info>

    This case statement has a lot going on. Let's take it one piece at a time.

    Converting the Image to GrayScale

    The first condition of the preceding case statement uses ColorConvertOp to convert the color model of the image to grayscale, essentially making it a black-and-white image instead of a color image:

    The Java 2D API provides a set of color spaces, such as CS_GRAY and CS_CMYK. You just need to create a new ColorConvertOp instance and give it your chosen color space.

    Creating a Negative of the Image

    When the user selects "Negative" from the menu, the application uses the LookupOp class to create a negative of the original image:

    The LookupOp class uses a lookup table to filter the color values of pixels from a source image to a destination image. A pixel's color is made up of three components: red, green, and blue, each of which is represented by a value within the 8-bit range, 0-255.

    To produce the negative of an image, you need to create a lookup table that has the values 0-255 in the reverse order so that each pixel's color will be set to the color's complement, as the following for loop does:

    lut = Array.new
    for j in 0..255
      lut[j] = 256-j
    end
    
    This example uses only one lookup array, which means that it will be used to convert the colors of all three of the color components of each pixel. If you want, you can provide separate arrays for each color component so that each is converted in a different way.

    <Ruby_Info>

    The preceding code uses a Ruby array and a for loop. As with other variables in Ruby, you don't need to declare the type of the array, nor do you need to initialize it to a certain length. Same thing with the for loop: you don't need to initialize the iteration variable. And you don't need to explicitly increment it either. Finally, to indicate the range for the for loop, you just give the starting value and ending value of the iteration variable, separated by two dots.

    After the for loop exits, you have a Ruby array. What you need to do is convert it into a Java array so that you can use the array with Java libraries. To convert the array, you use the to_java function and indicate the type that you want to assign to the array:

    jlut = lut.to_java :byte
    

    </Ruby_Info>

    Now that you have converted the Ruby array to a Java array, you can use it to create a lookup table and pass the lookup table to an instance of LookupOp:

    blut = java.awt.image.ByteLookupTable.new(0, jlut)
    op = java.awt.image.LookupOp.new(blut, nil)
    

    Brightening the Image

    The original image is a little dull and dark. You can use RescaleOp to change the brightness or saturation of the image by applying a multiplier and an offset. The photo example uses RescaleOp to increase the brightness by 40% and shift the color values of each pixel 25 points to the lower part of the range (towards black) to make the image look a little more saturated:
    op = java.awt.image.RescaleOp.new(1.4 -25, nil)
    
    After processing the image with this filter, you'll get the following image:

    Sharpening and Edge-Detection

    The most complicated image filtering operation is convolution. Convolution involves calculating a new color value for a destination pixel by multiplying the color values of the source pixel and its neighboring pixels by a matrix, called a kernel. This operation can produce such effects as sharpening, blurring, or edge-detection, which looks like a line-drawing version of the image.

    The way you perform convolution with the Java 2D API is by creating a Kernel object and then using it to construct a ConvolveOp object. The photo example uses a kernel that causes a sharpening effect:

    To create the filter that will perform this sharpening effect, you would use the following code:

    data = [-1, 0, -1, 0, 5, 0, -1, 0, -1]
    dataFloat = data.to_java :float
    sharpen = java.awt.image.Kernel.new(3, 3, dataFloat)
    op = java.awt.image.ConvolveOp.new(sharpen)
    
    Here again, you need to convert the Ruby array into a Java array before using it to create a Kernel object.

    The following matrix would give you the edge-detection effect:

    [1, 0, 1, 
     0, -4, 0, 
     1, 0, 1]
    
    Here's the result of using this matrix on our example image:

    Filtering the Image

    Once you have your image filter, you can use it to convert your buffered image and draw the filtered image to the graphics context by adding the following code:
    dest = op.filter(bi, nil)
    big.drawImage(dest, 0, 0, nil);
    
    The op variable is the object that represents the image filtering operation from the previous section. The dest variable represents the filtered buffered image.

    Streaming the Image File to the Client

    Just as your used the read method of ImageIO read the image file into a buffered image, you can use the write method to write the filtered buffered image back into a file, or in the case of this example, an output stream, which you can then use to stream the file to the client.

    • Add the following lines to finish up the example by streaming the filtered image to the client:
      os = java.io.ByteArrayOutputStream.new
      IO.write(dest, "jpeg", os)
      string = String.from_java_bytes(os.toByteArray)
      send_data string, :type => "image/jpeg", :disposition => "inline", :filename => "newkids.jpg"
      
    Here, you're writing the data to a byte array output stream. Then, you convert the data to a Ruby string so that you can use send_data to stream it to the browser.

    If you prefer to save the image to a file rather than a stream and save the file to disk, you can use send_file instead of send_data:

    send_file writefilename, :type => 'image/jpg', :disposition => 'inline'
    

    Running the Application

    Running the application is easy using the GlassFish v3 gem:
    1. Deploy the application on the GlassFish v3 GEM:
      jruby -S glassfish_rails photo
      
    2. Run the application by entering the following URL into your browser:
      http://localhost:3000/home/index
      
    3. Select an image filtering operation from the combobox and click Submit.
    4. After the filtered image is displayed in the browser, click the browser's Back button to return to the previous page if you want to filter the image again.

    That's all there is to it. For more information on JRuby, Ruby-on-Rails, and the Java 2D API, visit the following links:

Saturday Sep 29, 2007

Communicating Comboboxes with jMaki, Part 2

For my previous blog entry, I explained how to create a jMaki application that causes a user action on one combobox widget to change the values displayed in another combobox widget. My example allowed a user to select a state in one combobox in order to make the other combobox load the names of a set of cities contained in the chosen state.

In this blog entry, I'll show how you can make this application a little more practical by adding a map widget so that when the user selects a city from the second combobox, the application will plot that city on the map, as this screen shot shows:

So, here's what you do:

  1. Perform the steps outlined in my previous entry, Simple Communicating Comboboxes in a jMaki Application
  2. Drag a Google map widget onto the JSP page, right after the second combobox widget.
  3. Add a publish property to the second combobox, and give it the topic /cities, as shown here:
    <a:widget
            name="dojo.combobox"
            publish="/cities"
            subscribe="/cb"
            value="${StateBean.cities}"  />
    
    Now, when the user selects a city from this combobox, the selected value is published to the /cities topic.
  4. Add the following function to the end of the glue.js file:
     jmaki.subscribe("/cities/onSelect", function(item) {
      var location = item.value;
      var encodedLocation = encodeURIComponent("location=" + location);
      // jmaki.xhp is provided as part of jmaki and maps to the XMLHttpProxy
      var url = jmaki.xhp + "?id=yahoogeocoder&urlparams=" + encodedLocation;
      jmaki.doAjax({url: url, callback : function(req) {
        if (req.responseText.length > 0) {
          // convert the response to an object
          var response = eval("(" + req.responseText + ")");
          var coordinates = response.coordinates;
          v = {results:coordinates};
          jmaki.publish("/jmaki/plotmap", coordinates);
        } else {
          jmaki.log("Failed to get coordinates for " + location );
        }
      }
      });
    });
    
    This function subscribes to the /cities/onSelect topic. In the previous step, you made it so the second combobox widget publishes its value to the /cities topic. Recall from the previous blog entry that a combobox widget always publishes its selected value to the global onSelect topic and to the onSelect sub-topic of a developer-defined parent topic. In this case, you've specified that this combobox widget publish its value to the parent topic, /cities, and therefore, the value is published to /cities/onSelect.

    The subscribe function gets the selected city name from the item variable that is passed to it. The function takes the city name, encodes it, and uses Ajax to pass it to the Yahoo geocoder service by way of the XMLHttpProxy client. The Yahoo geocoder service returns the coordinates of the location to the XMLHttpProxy client, which returns it to the subscribe function.

    When the subscribe function receives the coordinates, it publishes them to the /jmaki/plotMap topic, which is a standard jMaki topic to which all the jMaki map widgets subscribe. Now, the map widget you added to your application displays the city the user selects from the cities combobox.

We're not done yet, however. When you start testing out the application, you'll notice something wrong. If you start from Alaska and work your way down, everything will seem fine until you try to plot Scottsdale. You'll discover that the Yahoo geocoder service sends the coordinates of Scottsdale, Alabama instead of those of Scottsdale, Arizona because you did not specify which Scottsdale you wanted. So, we need to do a couple more things to get this application working correctly:
  1. First, you'll need to add an ID for the combobox that displays the state names because the subscribe function will need to get this widget's value:
    <a:widget id="thisState" name="dojo.combobox" publish="/cb/getState" value="${StateBean.states}" />
    Notice that I've given this widget the ID, thisState
  2. Now, you need to add a couple lines to the subscribe function that get the selected state name and add it to the city name:
    jmaki.subscribe("/cities/onSelect", function(item) { var city = item.value; var state = jmaki.attributes.get('thisState').getValue(); var location = city + ", " + state; // the rest of the function stays the same ... });
    First I added a line that gets the value of the widget called thisState. After that, I added another line that creates a location specifying both the city name and the state name so that the Yahoo geocoder service knows exactly which city I want.
Now when you try to plot Scottsdale, you will see Scottsdale, AZ, not Scottsdale, AL.

Wednesday Sep 26, 2007

Simple Communicating Comboboxes in a jMaki Application

Note: I have updated this blog in response to Greg's comment.

It's been a long time since I've blogged because I was on maternity leave. Now that I'm back and have brushed the cobwebs off my blog, I'm trying to get into the swing of things again, including working with jMaki.

Because it's been so long, I decided to start with something simple. I heard from Greg Murray that, with the new features available with jMaki 1.0, you can now easily get a user action on one combobox widget to change the set of data in another combobox widget.

You've probably seen this use case quite a bit in forms on the web, such as when you need to enter your address. The form asks you to select your state from a list of states in a combobox. When you select a state, another combobox is updated with the list of cities located in that state, as shown in this screenshot:

So, I decided this would be a nice, simple example to help me get to know what's new with jMaki. Here are the steps I took:

  1. Install the NetBeans plugin for jMaki 1.0 into my NetBeans IDE.
  2. Create a new web application and add the jMaki framework to it.
  3. Near the top of the JSP page that the IDE loads into the editor after you create the web application, add the following useBean tag:
            <jsp:useBean id="StateBean" scope="session" class="dualCombobox.StateBean" /> 
    
    Later on in this blog, I'll show you what to put in StateBean.java.
  4. Drag two Dojo combobox widgets onto the JSP page.
  5. Change the first combobox widget tag to look like this:
           <a:widget name="dojo.combobox"
                      publish="/cb/getState"
                      value="${StateBean.states}" />
    
    This combobox widget displays the names of a set of US states, which it fetches from the states property of StateBean in the following JavaScript object literal format:
        [{label: 'Alaska', value: 'AK'},
         {label: 'Arizona', value: 'AZ'},
         {label: 'California', value: 'CA'},
         {label: 'Oregon', value: 'OR'}]
    
    When the user selects a state from this widget, the powerful publish and subscribe mechanism publishes the value of the user's selection to the topic /getState, which is a subtopic of the topic, /cb, as shown in the widget tag's publish property.

    The value that a particular kind of widget is allowed to publish is determined by the widget's data model. See Carla's blog for more information about widget data models.

  6. Change the second combobox widget tag to look like this:
            <a:widget name="dojo.combobox"
                      subscribe="/cb"
                      value="${StateBean.cities}"  />
    
    When the page loads, this combobox widget fetches its initial set of city values from the cities property of StateBean. To update its values when a user chooses a state from the other combobox, the cities combobox subscribes to the same parent topic, /cb to which the states combobox publishes the state code that the user chose, as you can see by looking at the widget's subscribe property. In the next step, you'll see how the cities combobox updates its values.
  7. Add the following script to the end of the glue.js file included in the web application under the Web Pages directory.
    jmaki.subscribe("/cb/getState/\*", function(args) {
         var message = args.value;
         jmaki.doAjax({method: "POST",
            url: "Service?message=" + encodeURIComponent(message),
            callback: function(_req) {
                var tmp = _req.responseText;
                var obj = eval("(" + tmp + ")");
                jmaki.publish('/cb/setValues', obj);
                // handle any errors
            }
        });
    });
    
    The subscribe function subscribes to all subtopics of the /cb/getState/ topic because of the wildcard character at the end of the topic argument of the function. Because the states combobox widget publishes the state code to the /cb/getState topic, the subscribe function receives the state code as the args variable that is passed to it. The subscribe function takes the state code and posts it to the Service servlet, which returns the appropriate set of cities. Finally, the function publishes this set of cities to the /cb/setValues topic.

    By default, all combobox widgets automatically obtain their values in response to a user action by subscribing to the global /setValues topic that jMaki provides unless the developer specifies otherwise. You can also make a combobox widget subscribe to a /setValues subtopic of a developer-defined parent topic, which is what I do in this example. Because the cities combobox subscribes to the parent topic, /cb, it also subscribes to the /setValues subtopic by default and is therefore automatically updated with the new set of cities.

    Normally, if you have only one combobox in a page, you do not have to create a parent topic for the purpose of updating values in the combobox; instead, you can simply publish the new values to the global /setValues topic, and the combobox will be updated with these new values automatically. We can't do that in this case because we have two comboboxes. Because all combobox widgets subscribe to the global /setValues topic automatically, we would end up setting the values of both of our comboboxes to the set of cities returned by the Service servlet if we published to the global /setValues topic.

  8. Create a JavaBeans component called StateBean that builds JSONArray objects for the state names and the city names that correspond to a chosen state:
        ...
        protected String[] states =
                new String[] {"Alaska", "Arizona", "California", "Oregon" };    
        
        protected String[] stateCodes =
                new String[] {"AK", "AZ", "CA", "OR" };
    
        private ResourceBundle cityNames = null;
        
        public StateBean() {
            this.init();
        }
        
        private void init() {
            cityNames = ResourceBundle.getBundle("mapcity.cities");
        }
        ... 
    
        public String getStates() throws JSONException {
            JSONArray statesData = new JSONArray();
            JSONObject stateData = new JSONObject();
    
            for (int loop = 0; loop < states.length; loop++) {
                stateData.put("label", states[loop]);
                stateData.put("value", stateCodes[loop]);
                statesData.put(stateData);
                stateData = new JSONObject();
            }
    
            return jsonArrayToString(statesData, new StringBuffer());
        }
    
        public String getNewCities(String state) throws JSONException {
            JSONObject city = new JSONObject();
            JSONArray cities = new JSONArray();
            String[] names = null;
            
            try {
                names = cityNames.getString(state).split(",");
                // obtaining the cities from a resource bundle keyed by state code, but you
                // can get them however you like
            } catch (Exception e){
                return null;
            }
            for(int i = 0; i < names.length; i++){
                city.put("label", names[i]);
                city.put("value", names[i]);
                cities.put(city);
                city = new JSONObject();
            }
            
            return jsonArrayToString(cities, new StringBuffer());
    
        }
        public String jsonArrayToString(JSONArray ja, StringBuffer buff)  throws JSONException {
            if (buff == null) buff = new StringBuffer("[");
            else buff.append("[");
            
            for (int key=0; (ja != null) && key < ja.length(); key++) {
                String value = null;
                if (ja.optJSONObject(key) != null){
                    jsonToObjectLibertal(ja.optJSONObject(key), buff);
                } else if (ja.optJSONArray(key) != null) {
                    jsonArrayToString(ja.optJSONArray(key), buff);
                } else if (ja.optLong(key, -1) != -1) {
                    value = ja.get(key) + "";
                    buff.append(value);
                } else if (ja.optDouble(key, -1) != -1) {
                    value = ja.get(key) + "";
                    buff.append(value);
                } else if (ja.optBoolean(key)) {
                    value = ja.getBoolean(key) + "";
                    buff.append(value);
                } else if (ja.opt(key) != null) {
                    Object obj = ja.opt(key);
                    if (obj instanceof Boolean) {
                        value = ja.getBoolean(key) + "";
                    } else {
                        value = "'" + ja.get(key) + "'";
                    }
                    buff.append(value);
                }
                if (key < ja.length() -1) buff.append(",");
            }
            buff.append("]");
            return buff.toString();
        }
        
    }
    
    
    The getStates and the getNewCities methods use the org.json APIs to create a JSONArray object representation of the data. The jsonArrayToString method is boilerplate code that you can copy to your bean. What it does is converts the JSON code created by the JSON APIs to JavaScript object literals. Greg says that he might include this code in jMaki 1.1 so that you won't have to copy it.

    In addition to these methods, you'd also need to add a getCities method that returns the initial set of city names for the second combobox.

  9. Create a servlet called Service, and add the code that gets the value from the client, passes it to StateBean, receives the list of cities from StateBean, and returns this list of cities to the client:
        public void doPost(
            HttpServletRequest request,
            HttpServletResponse response) throws IOException, ServletException {
            HttpSession session = request.getSession();
            StateBean stateBean = (StateBean)session.getAttribute("StateBean");
    
       
            String cityData = new String();
            String message = request.getParameter("message");
    
            try{
                cityData = stateBean.getNewCities(message);
            } catch (Exception e){
                System.out.println("could not get city data");
            }
       
            PrintWriter writer = response.getWriter();
            writer.write(cityData);
    
            
            session.setAttribute("stateBean", stateBean);
        }
    
Now, you're done. Next time, I will add a map to this application so that you can plot the chosen city on a map.

As this example has shown, the publish and subscribe mechanism gives you a simple and powerful way to get widgets to interact in jMaki. Thank you, jMaki development team! And thank you, Greg, for enduring all my questions.

For more in-depth information and real-world examples using publish and subscribe, take a look at these blogs:

Tuesday Jan 23, 2007

Dynamic Faces, Part 1: Using fireAjaxTransaction to Ajaxify a JavaServer Faces component

This blog is the first in a series on how to use the basic features of Dynamic Faces to add Ajax capabilities to JavaServer Faces components that you already know and love. The topic of this first blog is how to use the fireAjaxTransaction function provided by Dynamic Faces to perform Ajax updates at the component level.

This series assumes you know the basics of JavaServer Faces Technology.

Before going on, you might want to skim through this article to get an overview of what Dynamic Faces is all about: New Technologies for Ajax and Web Application Development: Project Dynamic Faces.

Throughout this blog series, I'll use a simple example that uses standard JavaServer Faces components along with Dynamic Faces to allow users to do the following:

  • Select a fruit from a radio button group and instantly see both the varieties for that fruit appear in a menu and a description of the currently selected variety display without experiencing a full-page refresh.
  • Select one of the varieties from the menu to see its description, also without experiencing a page refresh.
Here's a screenshot:

If you were using plain JavaServer Faces technology without Dynamic Faces, you would need to add a button to update the varieties menu and the variety description, and you would have to go through a full-page refresh to do the update.

With Dynamic Faces, you can get rid of the button and rely on Dynamic Faces to do the work of updating these components using Ajax.

To see this example in action, download simpleDynamicFaces.war from the samples folder of the download page for the JSF Extensions project and deploy the WAR file.

To create an example like this one, the first thing to do is to download the JSF Extensions project, which includes Dynamic Faces, and perform the simple set-up steps described in Setting up an Application to Use Dynamic Faces in the article mentioned previously. After that, you're ready to start coding.

Behind the scenes, Dynamic Faces performs all the Ajax functionality through the use of a set of JavaScript libraries. One of the JavaScript functions that Dynamic Faces provides is the fireAjaxTransaction function. As its name suggests, it fires an Ajax request in response to a particular event, such as clicking on a component. As such, this function gives you component-level control over what is updated in your page. To use the fireAjaxTransaction function, you do the following:

  • Add a JavaScript event attribute, such as onclick, to a component tag.
  • Set the value of the attribute to the DynaFaces.fireAjaxTransaction function.
  • Pass a set of parameters to the function.
The following piece of the JSP page shows how I used fireAjaxTransaction to update components via Ajax, as shown on lines 10 and 16. Notice on lines 11 and 17 that I have used the standard valueChangeListener tag attributes to register value-change events on the fruit and variety components. I'll get to the importance of this later on.
  1. <f:view>
  2. ...
  3. <h:form prependId="false" id="form">
  4. <h:panelGrid columns="2" cellpadding="4">
  5.  
  6. <h:outputText value="Select a fruit:"/>
  7. <h:outputText value="Select a variety:"/>
  8.  
  9. <h:selectOneRadio id="fruit" value="#{fruitInfoBean.fruit}"
  10. onclick="DynaFaces.fireAjaxTransaction(this, { execute: 'fruit'});"
  11. valueChangeListener="#{fruitInfoBean.changeFruit}">
  12. <f:selectItems value="#{fruitInfoBean.fruits}"/>
  13. </h:selectOneRadio>
  14.  
  15. <h:selectOneMenu id="variety" value="#{fruitInfoBean.variety}"
  16. onchange="DynaFaces.fireAjaxTransaction(this, { execute: 'variety' });"
  17. valueChangeListener="#{fruitInfoBean.updateVariety}">
  18. <f:selectItems value="#{fruitInfoBean.varieties}"/>
  19. </h:selectOneMenu>
  20.  
  21. </h:panelGrid>
  22.  
  23. <h:outputText id="varietyInfo" value="#{fruitInfoBean.varietyInfo}" />
  24.  
  25. </h:form>
  26. </f:view>

The first thing I did in the page is I set the form tag's prependId attribute to false so that I can refer to component IDs without prepending the form's ID. This attribute was added in JavaServer Faces technology 1.2. In Ajax applications, you often have to refer to client IDs. Without the prependId attribute, you'd have to add the form ID to every client ID reference, which adds extra bytes to network transactions and is a pain to type. Remember, with Ajax, you are doing more transactions, so you want each one to be as small as possible.

After setting the prependId attribute, I added an onclick attribute to the fruit selectOneRadio tag and set it to the following call to fireAjaxTransaction:

     "DynaFaces.fireAjaxTransaction(this, { execute: 'fruit'});"
The this parameter is a JavaScript reference that is the DOM element for the markup generated by the fruit selectOneRadio tag.

The other parameter is a kind of JavaScript Object known as an associative array, in which the keys are strings and the values are whatever you want them to be. Each key/value pair represents an option that you pass to the fireAjaxTransaction function.

These options tell Dynamic Faces which parts of the component tree it needs to process and re-render using Ajax. The Dynamic Faces JavaScript Library Reference includes the complete list of acceptable options.

In this case I have only one option, execute: 'fruit', which says that the fruit component (and its children, if it has any) must go through the execute portion of the JavaServer Faces life cycle. This part of the life cycle includes the phases responsible for converting and validating data, updating model values, and handling action events. I did not include the render option because I want all components to be re-rendered as a result of this action, which is the default behavior.

As shown on line 10 of the preceding JSP page, the fireAjaxTransaction function is called when the user clicks on a radio button. When this happens, the fruit component goes through the execute phase of the life cycle, and the value-change event that is registered on it is fired.

The valueChangeListener attribute of the fruit component tag references the changeFruit method, which handles the value-change event (see FruitInfoBean.java). The changeFruit method performs some model updates when the fruit component is activated. Therefore, the fruit component must go through the execute phase of the life cycle so that these model updates will occur.

The changeFruit method updates the model value of the variety menu component with the list of varieties corresponding to the fruit the user chose from the fruit component. For example, if the user selects Pear from the fruit component, the variety component will display the values Aurora, Bartlet, Bosc, Comice, and Delicious.

The changeFruit method also sets the currently selected fruit value to the first variety in the list. Finally, it updates the varietyInfo output component so that the description of a variety corresponds to the currently selected fruit variety. For example, if the user chooses Pear, Aurora is the selected value of the variety component, and the varietyInfo component displays this message:

Aurora: Sweet, juicy, and aromatic. Quality dessert pear ...

When Dynamic Faces re-renders these components using Ajax, the components will display with the new values.

I also used the fireAjaxTransaction function with the variety component:

     "DynaFaces.fireAjaxTransaction(this, { execute: 'variety'});"

This function call works the same way as it does for the fruit component: It causes the variety component to go through the execute phases of the life cycle and re-renders all components via Ajax.

As with the fruit component, the variety component has a value-change event registered on it. Like the changeFruit method, the updateVarety method, which handles this event also updates model values. The updateVariety method updates the varietyInfo component's model value so that the displayed description of a fruit variety corresponds to the variety the user selected from the variety component menu.

That's all there is to it. For this example, you do not need to write any JavaScript code to use Ajax, nor do you need to do anything special in your Java code. And the best part is that you can use the JavaServer Faces components you already know and use without modifying them.

Friday Jan 19, 2007

Still More Fun with jMaki: Loading data into a Dojo table using JSON APIs

OK, I think I've exhausted the "More Fun with jMaki" moniker. But that doesn't mean I won't write more about jMaki. Upcoming blogs include how to wrap a Dojo widget into a jMaki widget. This time, I'll show how to load your own data into a Dojo table. This blog focusses only on how to convert Java object data into JSON format. It doesn't detail getting the data from a database.

The first thing to determine is what format the data for the Dojo table needs to be. To do this, you look at the widget.json file for the table widget. This file is located in the resources/dojo/table directory of the jMaki samples download. As the default value, it shows the following:

"defaultValue":{
"columns": { "title" : "Title", 
             "author":"Author", 
             "isbn": "ISBN #", 
             "description":"Description"},
"rows":[
 ['JavaScript 101', 'Lu Sckrepter','4412', 'Some long description'],
 ['Ajax with Java', 'Jean Bean','4413', 'Some long description']
 ]
}
In JSON-speak, this means that you have a JSON object (denoted by the outer curly braces) that contains another JSON object representing the columns of the table (denoted by the inner set of curly braces) and an array representing the rows of data (denoted by the square brackets). Inside the rows array is a set of other arrays. Each one of those arrays represents a single row of data.

What you need to do is use the JSON APIs to convert Java object data into this JSON format.

First, I created a Book bean to represent a book. If you are using the Java Persistence API, you could make this an entity class. Here is part of the Book class:

public class Book {

    private int bookId;
    private String title;
    private String firstName;
    private String surname;

    /\*\* Creates a new instance of Book \*/
    public Book(int bookId, 
                String title, 
                String firstName, 
                String surname) {
        this.bookId = bookId;
        this.title = title;
        this.firstName = firstName;
        this.surname = surname;
    }

    public int getBookId() {
        return bookId;
    }

    public void setBookId(int bookId) {
        this.bookId = bookId;
    }
    ... // other getter and setter methods for the other properties.
Next, I created a class to convert the data to JSON. In this class, I added a method to create some rows of data. This is where you'd normally do your database access:

    public List createBooks() throws Exception {
        ArrayList books = new ArrayList();
        Book book = 
            new Book(201, 
               "My Early Years: Growing up on \*7", 
               "Duke", "");
        books.add(book);
        book = 
            new Book(202, 
                "Web Servers for Fun and Proft", 
                "Jeeves", "");
        books.add(book);
        book = 
           new Book(203, 
                "Web Components for Web Developers", 
                "Webster", "Masterson");
        books.add(book);
        return books;
    }
Now comes the method that converts this data into JSON format using the JSON APIs:

    public JSONArray getBookData() throws Exception {
             JSONArray books = new JSONArray();
             JSONArray book = new JSONArray();
             ArrayList bookList = 
                     (ArrayList)createBooks();
             Iterator i = bookList.iterator();
             while(i.hasNext()){
                     Book bookData = (Book)i.next();
                     book.put(bookData.getBookId());
                     book.put(bookData.getTitle());
                     book.put(bookData.getFirstName());
                     book.put(bookData.getSurname());
                     books.put(book);
                     book = new JSONArray();
             }
             return books;
    }
This method uses the JSONArray API to create the rows array that contains an array representing each row of data. I'll explain in a minute why I'm not creating the column data here.

Finally, you refer to the getBookData method from the page using an EL expression:

<jsp:useBean id="appBean" 
                class="overviewApp.ApplicationBean" 
                scope="session"/>
<a:ajax name="dojo.table" 
   value="{columns: {'isbn':'ISBN #',
                      'title':'Title',
                      'firstName':'First Name',
                      'surname':'Last Name'},              
           rows:${appBean.bookData}}"/
>
Notice that I had to manually enter the columns data, but I can reference the rows data in the bean. This is because the JSONObject API uses HashMap under the covers. As you might know, HashMap does not ensure insertion order. Our use case requires it so that the column headings match up with the row data in each column. Therefore, I manually entered the column data into the page. If it were not for this issue, we could create the table as a JSON object in the bean so that the page author could reference the entire table with one EL expression.

Monday Jan 08, 2007

More Fun with jMaki: Getting Data From a Bean

My last blog featured a combobox widget that includes a list of European cities. The service attribute of the ajax tag representing the widget in the page points directly to a JavaScript file that includes the data in a format that JavaScript can evaluate:
[
	["Berlin","Berlin"],
	["Helsinki","Helsinki"],
	["London","London"],
	["Madrid","Madrid"],
	["Paris","Paris"],
	["Rome","Rome"]
]
It is a JavaScript array representing the set of values in the combobox. This array consists of a set of other JavaScript arrays. Each of those JavaScript arrays includes the displayed label and the actual value of an item in the combobox.

OK, but what if you want to get the data from a bean using an EL expression, as you can with most other JSP tags? You can, but you need to add a little bit of code that will convert the object representation of your data into the representation shown above so that JavaScript can evaluate it.

This sounds like a lot of work, but it's really not bad. What I did is I created a bean, added the property to set the city name, and added a small method to build the JavaScript array of the data using JSON, which is a library that converts objects into String representations that JavaScript can evaluate. The JSON APIs are included in jMaki. Let's see how we can use JSON with our example.

Instead of using cities, lets use a set of countries and their country codes as our data instead:

[
	["Japan","JP"],
	["Thailand","TH"],
        ["Uganda", "UG"],
	["Ukraine","UK"],
	["United States of America","USA"]
]

In your bean, you need to import the jMaki libraries and the JSON libraries included in jMaki:

import com.sun.jmaki.\*;
import org.json.\*;
Then you create your properties to get and set the data:
    public String getCountry () {
        return country;
    }
    
    public void setCountry(String country) {
        this.country = country;
    }
    
    private String[] countries =
        new String[] {
            "Japan", "Thailand", "Uganda", "Ukraine", 
            "United States of America"
        };
   
   private String[] countryCodes =
        new String[] {
            "JP", "TH", "UG", "UR", "USA"
        };
Now, you need to use JSON to convert that data to a form JavaScript can evaluate:
    public JSONArray getCountryService() {
	JSONArray countriesData = new JSONArray();
        JSONArray countryData = new JSONArray();
        for (int loop=0; loop < countries.length; loop++){
		countryData.put(countries[loop]);
		countryData.put(countryCodes[loop]);
		countriesData.put(countryData);
		countryData = new JSONArray();
	}
	return countriesData;
}
In getCountryService, we're looping through the list of countries and country codes and loading each country and its corresponding country code into a separate array. Then we're adding each array to the countriesData array. What this method returns is the array containing the set of arrays of country data that the combobox widget expects.

The last thing to do is to reference the bean from the ajax tag representing the combobox widget:

<jsp:useBean id="appBean" class="combobox.ApplicationBean" scope="session"/>
<a:ajax id="cb1" name="dojo.combobox" selected="${appBean.country}" 
	value="${appBean.countryService}" />

To use the bean that you created in your page, you need to add the jsp:useBean tag, as shown in the preceding code. Then, you can use EL expressions to access the data from the bean. You can access the selected value of the combobox by referencing the country property from the selected attribute. And, you can get the set of data for the combobox by referencing the getCountryService method.

That's all there is to it. Next time, I'll show how to use JSON to load data from a bean into a dojo etable.

Wednesday Jan 03, 2007

Fun with jMaki: Using the Yahoo Geocoder service with the Dojo Combobox

Those of you who've looked at jMaki have probably seen the document on how to use the XMLHttpProxy to access services from widgets (See https://ajax.dev.java.net/xmlhttpproxy.html). In the document, Greg talks about the Yahoo Geocoder service. He also created a demo that uses the service with a Yahoo map widget.

I wanted to see how easy it would be to use the Geocoder service with the Dojo combobox widget included with jMaki. It turns out that there wasn't much to it.

My example allows a user to select a city from the combobox to see it plotted on the map, as shown in this figure:

Screenshot of application plotting Helsinki

To build this application, I first created my data file, europeCities.js, which contains a list of European cities in a JavaScript file:

[
	["Berlin","Berlin"],
	["Helsinki","Helsinki"],
	["London","London"],
	["Madrid","Madrid"],
	["Paris","Paris"],
	["Rome","Rome"]
]

Next, I created the JSP page that includes the ajax tags that represent the combobox and the map:

<a:ajax id="cb1" name="dojo.combobox" service="europeCities.js" />
<p>
<a:ajax id="map001" 
           name="yahoo.map" 
           args="{zoom:10, 
              centerLat:37.39316, 
              centerLon:-121.947333700, 
              width:350}" />
The cb1 ajax tag represents the combobox. It gets its data from the europeCities.js file. The map001 ajax tag represents the yahoo map and is set to a default set of coordinates.

To get the city that the user selects to be plotted on the map, I first needed a topic listener that takes the value of that city and uses dojo.io.bind to do the following:

  • Create a URL to the service using the selected value.
  • Publish the coordinates that are returned from the service to the /yahoo/geocoder topic.
Here is the listener code, which is located in the web/resources/glue.js file in the jmaki-core distribution:
jmaki.addGlueListener({
       topic: "/dojo/combobox/updateMap", 
       action: "call", 
       target: {object:"jmaki.listeners.ComboboxGeocoder",
       functionName:"updateMapHandler"}});

jmaki.listeners.ComboboxGeocoder = {
	
     updateMapHandler  : function(args) {
       var location = encodeURIComponent("location="+ args.value);
       dojo.io.bind({
           url: "xhp?key=yahoogeocoder&urlparams=" + location,
           method: "get",
           mimetype: "text/json",
           load : function(type, data) {
               jmaki.publish("/yahoo/geocoder", data.coordinates);
           }
       });
   }
} 

The /yahoo/geocoder topic listener is already included with the jMaki distribution. This listener function plots the coordinates on the map.

To get my new listener to be called when a new value is selected, I needed to add code to dojo combobox widget's component.js file that publishes the selected value and other arguments on the widget tag to the updateMap topic when the combobox experiences a value change:

   this.onChange = function(value){
       if (value == '') return;
       jmaki.publish("/dojo/combobox/updateMap", {wargs: wargs, value: value});
   } 

That's all there is to it.

You can download this application from the jMaki file download page by selecting comboboxGeocoder.war from the list of files.

If you are using a proxy server to server your web requests, you need to specify the proxyHost and proxyPort context parameters in the web.xml file:

  <context-param>
  <param-name>proxyHost</param-name>
  <param-value>myProxy.com</param-value>
  </context-param>
  <context-param>
  <param-name>proxyPort</param-name>
  <param-value>8080</param-value>
  </context-param>

To edit the web.xml file, unpack the WAR file and then re-package it with the jar command after editing the file.

Thanks to Greg Murray for helping me debug the listener code.

Tuesday Oct 17, 2006

Creating an Ajax-enabled Phobos Application

With the Phobos web application framework, you can write your entire web application in JavaScript. If you are developing Ajax applications with Phobos, you have the benefit of the same scripting language on the client and the server.

Adding Ajax capability to a Phobos application is quite easy and the JavaScript to implement it is nearly the same as it is in any other web application.

Check out the new tutorial, How to Create an Ajax-Enabled Phobos Application for a gentle introduction to building a simple Phobos application with AJAX functionality.

Sunday Sep 03, 2006

New jMaki Tutorials

We've posted a couple new tutorials on how to use jMaki widgets. As those of you familiar with jMaki know, you can use a jMaki widget as either a JSP tag handler or a JavaServer Faces component. Therefore, we offer two tutorials, one that shows how to use a jMaki widget as a JSP tag handler and another that describes how to use a jMaki widget as a JavaServer Faces component: Try them out and let us know what you think by writing to dev@ajax.dev.java.net.

Monday Aug 14, 2006

Using a jMaki Widget in a Phobos Application

Last week I blogged about how to use the new publish/subscribe mechanism in jMaki to handle an event of the jMaki fisheye widget. To demonstrate this, I created an application that uses the fisheye widget to display bios of some of Sun's engineers.

This week, I'll describe how I used the jMaki fisheye widget to implement the same use case in a Phobos application. You can find this example in the apps/bioFisheyeWidget directory of the Phobos workspace.

The Phobos project is focussed on building a web application framework that allows you to develop your web applications with a scripting language. Just as you can add jMaki widgets to web applications built with JSP technology, you can add jMaki widgets to web applications built with Phobos.

Here again is a screenshot from the web application I described last week:

To implement this web application in Phobos, I performed these steps:

  1. I checked out the jMaki and Phobos projects and built them, as described in Building a Phobos Distribution.
  2. I created my application's directory structure according to the Phobos project's conventions. See Your First Phobos Application for more details. You can also refer to my example, called bioFisheyeWidget, located in the apps directory of your Phobos installation.
  3. I added the images for the fisheye into the bioFisheyeWidget/static directory.
  4. I created a script called index.js in the bioFisheyeWidget/application/script directory. What this script does is it forwards to the controller, which handles the rendering of the view. Here is the index.js file:

    library.httpserver.sendRedirect(library.httpserver.makeUrl("/fisheye"));
    

  5. I created the controller script, called fisheye.js inside the bioFisheyeWidget/application/controller directory. It creates the Fisheye controller object, which renders the view. Here are the contents of fisheye.js:

    library.common.define(controller, "fisheye", function() {
        this.Fisheye = function() {
            this.index = function() {
                library.view.render("fisheye.ejs");
            };
        };
    });
    

  6. Finally, I created the view script, called fisheye.ejs and added it to the bioFisheyeWidget/application/view directory. It is just like1 the JSP page from the application I blogged about last week, except for one thing: Instead of using the custom ajax tag, you need to call the jmaki.insert function provided by Phobos to add the widget to the page:

    <% library.jmaki.insert({component: "dojo.fisheye", args:{items:[
                 {iconSrc:'JayashriVisvanathan.jpg',caption:'Jayashri', index:1},
                      {iconSrc:'chinnici.jpg',caption:'Roberto',index:2},
                      {iconSrc:'blog_murray.jpg',caption:'Greg',index:3}]}}); %>
    </p>
    </div>
    

That's it! For an introduction to the Phobos architecture, see the document, An Overview of Phobos. See the tutorial for more information on building Phobos applications.

1: There is one other difference between fisheye.ejs and the JSP page I described last week: In fisheye.ejs, I use double quotes around the response text and don't escape the single quotes. After you check out the example code, you can compare the JSP page with the view script.

Monday Aug 07, 2006

Handling jMaki Widget Events Using Publish/Subscribe

One of the many widgets that jMaki offers is the Dojo fisheye widget:



A page author adds this widget to an application by including the following ajax tag in a page:

 <a:ajax
name="dojo.fisheye"
args="{items:[
{iconSrc:'images/icon_browser.png',caption:'You are here!'},
{iconSrc:'images/icon_calendar.png',caption:'test3'},
{iconSrc:'images/icon_update.png',caption:'Update'}
]}">
</a:ajax>

In the preceding tag, each item in the items array represents the properties for the icon in the widget.

This widget resizes the image icons as the user moves the mouse over them. But, what if you want something to happen when the user clicks on an image? One way to do this is to register an event handler onto the widget by hand, as explained in Sang Shin's excellent jMaki lab.

Now there's a much easier way, which is to use the new publish and subscribe mechanism that Greg Murray has just added to jMaki. I thought it would be a good idea to modify the example fisheye widget that is part of jMaki so that it publishes itself as a topic to which individual applications can subscribe.

What we did to the fisheye widget's component.js file is we replaced the alert message inside the onClick function with a publish statement:

dojo.require("dojo.widget.FisheyeList");
// create the top level widget var fishEye = dojo.widget.createWidget(widget.uuid);
// programtically add FisheyeListItem children to the widget var counter = 0;
while (true) {
var i = widget.args.items[counter++];
if (i == null) break;
var icon = dojo.widget.createWidget("FisheyeListItem", i);

    icon.onClick = function (){
 jmaki.publish("/fisheye", this);
 }

   fishEye.addChild(icon);
}

As shown in the preceding code, for each image in the fisheye, we create a new icon widget. The i variable in the call to create the icon widget is the set of properties we pass from our widget.  (Recall the items array in the ajax tag.)

Inside the onclick function, we call the publish method, passing the topic, which is "/fisheye" and the icon widget corresponding to the icon that was clicked.  The icon, which includes all the properties passed from the tag is what is published.

Now what we need is an application to subscribe to the topic. To do this, we modified the fisheye.jsp page in the dojo-test application included in jMaki.  What the application does now is it displays images of some of Sun's movers and shakers. When a user clicks on an image, the bio matching the image displays on the same page.  Here is the JSP page that does this:

    <a:ajax type="dojo" name="dojo.fisheye"
args="{items:[
{iconSrc:'images/JayashriVisvanathan.jpg',caption:'Jayashri', index:1},
{iconSrc:'images/chinnici.jpg',caption:'Roberto',index:2},
{iconSrc:'images/blog_murray.jpg',caption:'Greg',index:3}
]}"/>


<script>
function fisheyeListener(item) {
var targetDiv = document.getElementById("newpage");
var responseText = "";
var index = Number(item.index);
switch(index){
case 1: // Set responseText equal to Jayashri's bio
break;
case 2: // Set responseText equal to Roberto's bio
break;
case 3: // Set responseText equal to Greg's bio
break;
default: responseText += 'Click on one of the photos above';
break;
}
targetDiv.innerHTML = responseText;
}
jmaki.subscribe("/fisheye", fisheyeListener);
</script>
<p>
<h3><div id="newpage"></div></h3>

Notice that there is a function that subscribes to the "/fisheye" topic. This function passes the reference to the topic into the JavaScript function, fisheyeListener, which is called if a message is published to the topic.

Let's go through what happens when this application runs. First, the fisheye widget renders itself. When the user clicks an icon in the fisheye widget, all the properties for that clicked icon are published to the topic. The JSP page then checks the icon's index property to see which icon was clicked and then replaces the div tag with the appropriate bio:





To see the new fisheye, you need to check out a new jMaki workspace or update your current workspace and then build it. Alternatively, you can browse the code.

To see an example of two jMaki widgets interacting using the publish and subscribe mechanism, download the latest jMaki application and take a look at the Yahoo Map with Geocoder example. With this example, you can enter a location into the GeoCoder widget and a tooltip that points to the location is added on top of the map widget.

One of the great things about this mechanism is that once you publish a topic for a widget, you can subscribe to the topic from any application and respond to a widget event however you like. You don't need to hardcode specific behavior into the widget.

Wednesday Jul 26, 2006

Rapid Web Application Development, brought to you by the Phobos Project

Phobos is a lightweight web application framework that runs on the Java platform but allows you to develop your entire application using a scripting language. You can therefore take advantage of the many benefits offered by scripting languages but still leverage the power of the Java platform. By being scripting-friendly, Phobos provides a programming environment that fosters rapid development.

Check out the new tutorial, Your First Phobos Application, which uses a simple calculator example to show you how quickly and easily you can create web applications with Phobos.

For an overview of the basic features of the Phobos architecture, see the new document, Overview of Phobos.

The Phobos Project is still being developed. To tap into what's happening with the Phobos project, go to phobos.dev.java.net and join the project's aliases.

Wednesday Jun 21, 2006

Get started quickly with the Java EE 5 platform

We have released the beta version of a new tutorial called Your First Cup: An Introduction to the Java EE Platform to help you get up and running quickly with the new Java EE 5 platform.

Here is the HTML version:

http://java.sun.com/javaee/5/docs/firstcup/doc/toc.html

And here is the PDF:

http://java.sun.com/javaee/5/docs/firstcup/doc/firstcup.pdf

It starts off with a short introduction to the Java EE 5 platform and then gives you step-by-step instructions for building your first Java EE 5 application with the NetBeans IDE and the Java EE 5 SDK. This application demonstrates using enterprise Java Beans, web services, and JavaServer Faces technology for the web client.

Let us know what you think of it.

About

jenniferb

Search

Categories
Archives
« July 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
  
       
Today