Oct 8, 2011

Play!Framework: Use RenderArgs and Excel module effectively

Mr. Basav Nagur has posted a blog demonstrates how easy it is to create Excel reports in Play!Framework. Being the author of Excel module, I really appreciate that. In additional I would like to help make the controller more concise in the sample code. Here is the original code of Application controller:
package controllers;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.commons.lang.RandomStringUtils;
import models.Customer;
import play.Logger;
import play.modules.excel.RenderExcel;
import play.mvc.Controller;
import play.mvc.With;

@With(ExcelControllerHelper.class)
public class Application extends Controller {
    public static void index() {
        render();
    }
    public static void customerphonelist(){
        Logger.info("Generating Customer Phone List Excel report ");
        request.format = "xlsx";
        Date date = new Date();
        String user = "Bob";
        List customers = new ArrayList();
        for (int i = 0 ;i < 10; i++){
         customers.add(new Customer("Mr", RandomStringUtils.randomAlphabetic(15), RandomStringUtils.randomNumeric(10)));
        } 
        renderArgs.put("date", date);
        renderArgs.put("user", user);
        renderArgs.put("customers", customers);
        renderArgs.put(RenderExcel.RA_ASYNC, true);
        renderArgs.put(RenderExcel.RA_FILENAME, "customer_list_report.xlsx");
        render();
        Logger.info("Completed Customer Phone List Excel report ");
    }
} 

The code is correct, however we could make even better in several places:
  1. "@With(ExcelControllerHelper.class)" could be removed. The dependency on that @With has been dropped since Excel v1.2.2
  2. All renderArgs.put(...) for app variables could be removed. You simply pass the variable in the render() directly, and they will get put into renderArgs automatically. 
  3. "renderArgs.put(RenderExcel.RA_ASYNC, true);" could be eliminated if you have "excel.async=true" in your conf/application.conf file.
  4. "renderArgs.put(RenderExcel.RA_FILENAME, "customer_list_report.xlsx");" could be eliminated if your template file name is "customerphonelist.xlsx" which matches the controller name, or if the action method name renamed to "customer_list_report".
All these tiny little things tries to show the beauty of this great framework: being nice to programmer and making programming on the framework a really enjoyable experience. Now the new version of the Application.java:
package controllers;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.commons.lang.RandomStringUtils;
import models.Customer;
import play.Logger;
import play.modules.excel.RenderExcel;
import play.mvc.Controller;
public class Application extends Controller {
    public static void index() {
        render();
    }
    public static void customer_list_report(){
        Logger.info("Generating Customer Phone List Excel report ");
        request.format = "xlsx";
        Date date = new Date();
        String user = "Bob";
        List customers = new ArrayList();
        for (int i = 0 ;i < 10; i++){
         customers.add(new Customer("Mr", RandomStringUtils.randomAlphabetic(15), RandomStringUtils.randomNumeric(10)));
        } 
        render(date, user, customers)
        Logger.info("Completed Customer Phone List Excel report ");
    }
} 

Isn't it more concise and easier for both programming and reading? What do you think?

One additional note about xlsx format of excel template, in order to use that, you will need Excel v1.2.3x version instead of v1.2.3 version mentioned in original post.

BTW, I would be really glad if someone tell me how to format code in the way Mr. Basav did in his post :)

Sep 17, 2011

The Trouble with LESSCSS

GreenScript v1.2.6 start to support LESS. I am pretty excited with this feature and decide to move on to LESS. However very soon I found I am faced with an annoying issue with LESS.

So the story starts with twitter bootstrap, a CSS framework built with LESS which is pretty hyped up by web developers recently. I decide to use it in my view. I've included 'less/bootstrap.less' in my default template. It's all good, unless I want to reference a variable defined in bootstrap library in my own foo.css:
.hero-unit .h2 {
  margin-bottom: .5em;
  font-size: 40px;
  color: @grayLight; // grayLight is defined in less/preboot.less file
  padding: 0 10em;
}


The problem with the above code is when GreenScript tries to compile it, the less compiler complains that the variable @grayLight is not defined. Well, it's a surprise but it's correct. The fix is to add the following line to the beginning of the css file:
@import "bootstrap/preboot.less";

However, the issue with this approach is it duplicates all compiled css code from "bootstrap/preboot.less" in the final css output. And if every css file tries to include this or that less libraries, the css will get bloated very quickly.

Another approach is to remove all @import clause from LESS file and let GreenScript's dependent management mechanism to manage the import relationships. E.g. in the view file (or in greenscript.conf file), declare the dependency between foo.css and bootstrap/preboot.less:
#{css 'foo < bootstrap/preboot'/}


GreenScript will make sure the content of preboot.less be put in front of foo.css when doing merge of the 2 files. And then call less compiler to compile the merged content. That could solve the result css bloat out problem, but another issue comes up. Because GreenScript run LESS compilation on merged file only, if there is an error in the compilation, it's not possible to give out information that which file caused the trouble, and make it very hard to debug the LESS errors.

The final solution might need a pure Java Less compile engine supporting the session concept, where all the LESS variables, mixins etc be cached through out the session and could used in future compilation process in the same session. So far I don't know there is any implementation for this stuff. I would like to put it in my TO-DO list. But please let me know if you already have that tool.

Aug 5, 2011

Remove the scrollbars from your facebook canvas application

One of my facebook canvas applications always display the vertical and horizontal scrollbars with it, which is very annoying. By looking into the html source code with firebug, I found the app is hosted inside the <ifram> element with "smart_sizing_iframe" class. While another app without the scrollbars is hosted inside the <ifram> element with "canvas_iframe_util" class. I am pretty sure that my app code is the same in terms of canvas size setting.

After visiting my app settings from https://developers.facebook.com I got the tricky: go to the "canvas setting" under "On Facebook", you have two option for IFrame size: "Show Scrollbars" and "Auto-resize". By selecting "Auto-resize" and save, then refresh your application page, then you got it! Yes, the scrollbar get removed.

Jul 28, 2011

Use GreenScript correctly

I've just encountered an mail about greenscript in the playframework google group. Here is the code:

-- main.html --


<html>
<head>
#{get 'title' /}
#{get 'moreStyles' /}

#{script 'jquery-1.6.2.min.js' /}
#{script 'jquery.validate.min.js' /}
#{script 'jquery.form.js' /}
#{get 'moreScripts' /}
</head>
<body>
#{doLayout /}
</body>
</html>

-- index.html --

#{extends 'main.html' /}
#{set title:'title' /}
#{set 'moreStyles'}
#{greenscript.css "main005", output:'all'/}
#{/set}
#{set 'moreScripts'}
#{greenscript.js output:true}
#{/set}
...


So what's wrong in the above code:
1. JavaScripts did not get processed (minimize and compress) at all. The above code is still using #{script} tag to output JavaScripts, which will NOT gain the benefit of greenscript at all
2. Using #{set 'moreScripts'} and #{get 'moreScripts'} is neither needed nor encouraged when you have the power of greenscript.

This code gives me a feeling that you have an electric motor saw but you are using it like a plain saw by not powering it on. The correct way could be much more simpler:

-- main.html --
<html>
<head>
#{get 'title' /}


#{greenscript.js 'jquery.form < jquery.validate.min < jquery-1.6.2', output:'all'/}
#{greenscript.css output:'all'/}
</head>
<body>
#{doLayout /}
</body>
</html>

-- index.html --

#{extends 'main.html' /}
#{set title:'title' /}
#{greenscript.css 'main005' /}

Tips:
  • you can declare multiple resource files in one greenscript tag. E.g. #{greenscript.js 'a b c'/} declares /public/javascripts/a.js, /public/javascripts/b.js and /public/javascripts/c.js using one call to greenscript js tag
  • you can declare dependencies inside greenscript tag, e.g. #{greenscript.js 'jquery.validate.min < jquery-1.6.2' /} will guarantee jquery-1.6.2.js be rendered before jquery.validate.min.js/
  • use [output: 'all'] to render all declared and not rendered resources. E.g. #{greenscript.css output: 'all' /} will output all declared css resource in place. Usually you should use [output: 'all' ] inside <head> block
  • By typing "play greenscript:cp -a ." in your project folder, you copied the needed tags to your project and you can simply the invoking of greenscript tag as: #{js .../}, #{css ... /}

Jun 18, 2011

Play-Excel v1.2.1 released with support to asynchronous Excel rendering

I am very glad to release play-excel-1.2.1 which support asynchronous report rendering. Here are changes compare to v1.1:


Render method call
v1.1: play.modules.excel.Excel.renderExcel(...)
v1.2: play.mvc.Controller.render(...), play.mvc.Controller.renderTemplate(...)


in other words, now you don't even aware that you are rendering a Excel report or normal HTML report. Everything is determined by request.format. Once your request.format set to 'xls' or 'xlsx', Excel module will take over the rendering process automatically. There are 2 ways to set request.format:
1. explicitly set request.format in controller methods
a. Set request.format in Action methods
b. Set request format in @Before methods based on http params for example
2. set request.format in route config file. Refer to http://www.playframework.org/documentation/1.2.1/routes#content-types for detail


Template location
v1.1 Excel template can not be put inside views folder, instead you will need to put the templates outside of views folder and set the template root in application.conf file, e.g. excel.template.root=app/excel_tmpl
v1.2 Just put Excel template in views folder along with your normal groovy template files. And of course "excel.template.root" shall not be used anymore


File name
v1.1 set download file name by set "fileName" in renderArgs
v1.2 use play.modules.excel.RenderExcel.RA_FILENAME instead of "fileName".


Async rendering (v1.2.1 only)
v1.1 does not support asynchronous rendering
v1.2.1 support transparent asynchronous rendering. You don't have to deal with Future, Promise, await, request.new etc and you get the power for nearly free (Yes, this is really cool! and thanks to the excellent play framework again!). The only place you need to touch is
1. add "excel.async=true" in application.conf, which enable async rendering globally
2. do renderArgs.set(play.modules.excel.RenderExcel.RA_ASYNC, true) in your action controller to enable async rendering for this controller action only


For those who don't yet see the power of async rendering, please refer to http://www.playframework.org/documentation/1.2.1/asynchronous, or think about the fact that a large Excel table rendering might take as long as 500ms and blocking request handling thread for half second is NOT acceptable in a high performance web server like play which use only limited thread (N+1) to handle web requests.



- Green

Apr 3, 2011

greenscript-1.2d released

There are a couple of new features released on this version (v1.2d):

1. Resource and dependency management in modules
Now you are able to manage your javascript and css files in modules. Meaning you could be able to create a "greenscript.conf" file in your ${module.root}/conf folder, put javascript and css files in ${module.root}/public/(javascripts|stylesheets} folders and reuse them with any play applications.

2. Inline dependency declaration in tags
There is a long time complaint about greenscript that the sequence of declared resources in tag does not get kept when those resources are output to html page. Now with inline dependency declaration feature, the inconvenience is largely removed. E.g.

#{greenscript.js 'myapp < mylib < jquery-1.4.4.min' /}

A tag declaration like above automatically set the dependence graph between myapp.js, mylib.js and jquery-1.4.4.min.js. I.e. myapp.js relies on mylib.js, which in turn relies on jquery-1.4.4.min.js. You don't need to be forced to create conf/greenscript.conf to declare the dependencies.

This feature is very useful for those application with relatively simple resource dependencies.

3. Reverse dependence declaration in greenscript.conf file
This is actually a feature added since v1.2c. Usually you declare dependency relationship in greenscript.conf file as follows:

js.myapp=mylib1,mylib2

which means myapp.js relies on both mylib1.js and mylib2.js. However with reverse dependence declaration, you switch the dependency relationship arrow:

js.mylib1-=myapp1,myapp2

meaning both myapp1.js and myapp2.js relies on mylib1.js. This feature makes it convenient to declare dependency relationships associated with things like jquery:

js.jquery-1.4.4.min-=jquery-ui-1.8.9.min,jquery-validate.min,jquery-tmpl.min

Note to declare reverse dependence relationship, you put a 'hyphen' right left to 'equal' sign in the statement.

4. support '.bundle' suffix
The '.bundle' suffix could be used in greenscript.conf dependence relationship declarations. E.g.

js.common.bundle=mylib1,mylib2,mylib3...
js.common.bundle-=myapp1,myapp2...
...

In the above example, 'common.bundle.js' is not a real javascript file. It's merely a symbol represent a group of resources (mylib1.js, mylib2.js, mylib3.js ...).

This feature makes it convenient to group your resources. It could also be used to create alias for resources that might be updated due to new version, e.g.

js.jq.bundle=jquery-1.4.4.min
js.mylib1=jq.bundle,...

In the above example, you create an alias 'jq.bundle' for jquery-1.4.4.min, which could be easily updated to jquery-1.5.2.min in your greenscript.conf file.

In summary, the new version of greenscript continue to move ahead in the way to make developer's life more easier and more interesting.