Thursday, May 13, 2010

Grails Acegi change locale after user login

In Acegi and Grails application, I would like to set the locale to the users preferred language after they log in. Here comes some steps I made:

In config file: conf/security.groovy. Add the following line:

security {
active = true
loginUserDomainClass = "User"
authorityDomainClass = "Role"
requestMapClass = "Requestmap"
security.defaultRole="ROLE_USER"

useSecurityEventListener = true
onInteractiveAuthenticationSuccessEvent = { e, appCtx ->
// handle AuthenticationSuccessEvent
def autservice = appCtx.authenticateService
def domain = autservice.userDomain()
def request = org.codehaus.groovy.grails.plugins.springsecurity.SecurityRequestHolder.getRequest()
def session = request.getSession(false)

// ok set session information for lang file - tested and ok
if (domain) {
def person = User.get(domain.id)
def userSetting = UserSetting.findByAuthor(person)
def lang = 'en'
if (session && userSetting && userSetting.language != null) {
session.lang = userSetting.language
}
}
}
}

And I have a table called userSetting which hold the user setting information, you need change it as your table information.

Ok, now we need call this function in any controller you want to change locale, maybe you'd better create a new general controller which will be extended by any controller:

// this is for locale change ...
def localeChangeCheck = {
def locale;
if (session.lang != null) {
locale = new Locale(session.lang)
RCU.getLocaleResolver(request).setLocale(request,response,locale)
}
if (locale == null) {
locale = RCU.getLocale(request)
}
}

Now you are on fly. To see running example, you can try @

www.feyasoft.com

Ok, do not forget to change properties file under your i18n folder.

Thursday, April 22, 2010

Quick add multiple language to extjs + grails application

Recently we quick added multiple language to our application. To see demo, you can login @ www.feyasoft.com/main

1: In database, we create a table which hold on the setting for language.

2: In grail MainController file, add the following code:

/**
* Copyright (c) 2005 - 2010 FeyaSoft Inc. All Rights Reserved.
*/
import grails.converters.*

class MainController extends GeneralController {

def index = {
// get login user id
User loginUser = getLoginUser()
if (loginUser == null) {
def json = [failure : "true", errorInfo : "Please login before post"]
render json as JSON
}

def item = UserSetting.findByAuthor(loginUser);
def lang = 'en'
if (item) lang = item.language;

render(view: 'main', model: [lang: lang])
}
}

3: In main.gsp file, the following code are added, really simple:

<~g:javascript library="extjs/src/locale/ext-lang-${lang}" /~>
<~g:javascript library="feyaSoft/lang/${lang}" /~>
And you need create the related lang file now. For example, we have this lang file under my folder: web-app\js\feyaSoft\lang\en.js

/**
* FeyaSoft Online MyActivity
* Copyright(c) 2006-2010, FeyaSoft Inc. All right reserved.
* info@feyasoft.com
*/
Ext.ns("feyaSoft");

feyaSoft.lang = {

'common':{
'accessories': 'Accessories',
'align': 'Align',
'average': 'Average',
'brush': 'Brush',
'calculate': 'Calculate',
....}
...
}

4: Now you can use something like this in your JS file:

feyaSoft.lang.common['average']
And you are on fly now.

Sunday, April 18, 2010

UTF-8 setting in Grails, mysql

Client UTF8----(A)---->
Server UTF8 ----(B)---->
Database UTF8 --- (C)---->
Table UTF8 ---(D)----> Row UTF8

1: Client UTF8

<~meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

2: Server UTF8

In DataSource.groovy

url = "jdbc:mysql://localhost:3306/home?tcpKeepAlive=true&useUnicode=true&characterEncoding=UTF8"
This forces the driver to recognize that all text being sent to the driver is in UTF8. This should conform directly with Java's default UTF8 encoding without any translations occurring.

3: Database UTF8

mysql> show variables like '%character%';

+--------------------------+----------------------------+
| Variable_name | Value |
+--------------------------+----------------------------+
| character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | utf8 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | utf8 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.00 sec)

mysql> set global character_set_server=utf8;
be sure to include the option in your my.cnf to ensure this option is persisted for a mysqld restart.

/etc/mysql/my.conf - default-character-set = utf8

Wednesday, November 04, 2009

Compress all output and speed up download big js files

We can speed up downloads or web page access time with Apache mod_deflate module. The mod_deflate module provides the DEFLATE output filter that allows output from our server to be compressed before being sent to the client over the network.

This decreases the amount of time and data transmitted over the network, resulting in faster web experience or downloads for visitors.

Today we add the following config in our Apache server,

AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/x-javascript application/javascript

And our js file size are huge reduced. The big one reduce as follow:

ext-all.js - from 609 k to 167 k
home_mini.js - from 523 k to 101 k

It makes our site load really quick and it is really help. You can try it at our site: www.feyasoft.com

Notice

Enabling filters with AddOutputFilterByType may fail partially or completely in some cases. For example, no filters are applied if the MIME-type could not be determined and falls back to the DefaultType setting, even if the DefaultType is the same.

However, if you want to make sure, that the filters will be applied, assign the content type to a resource explicitly, for example with AddType or ForceType. Setting the content type within a (non-nph) CGI script is also safe.

Ok, here are the details steps to create it:

# cd /usr/local/apache/config/
# vi httpd.conf
Add the following line:

LoadModule deflate_module modules/mod_deflate.so
< Location / >
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/x-javascript application/javascript
< /Location >
And now your need restart apache. If you did not have mod_deflate.so in your apache. Ok, now you need config and build one from apache source code. Here is a good link for your:

http://streetsmartingit.blogspot.com/2007/11/how-to-compileinstall-moddeflate-into.html

Thursday, October 29, 2009

Online PowerPoint 1.0.0 Beta is released

After a while, online PowerPoint 1.0.0 Beta is released. And you can insert Text and Image in this version. And you can Drag and Drop the item. Finally you can review it.

See detail demo @ www.feyasoft.com and login as: demo/demo

Tuesday, August 04, 2009

New version of Online Calendar release

We just push another version of our calendar online. Have a look if you shows interesting. It includes the following features:

1: Day/Week/Month view
2: DnD, Resize
3: Support Multiple Calendar

We will continue add more features in the near future.

To see/use it, you can login: http://www.feyasoft.com and sign in as Guest or using: demo/demo

Worked in FF, Chrome, IE.

Month View:Week view:Day view:

Saturday, May 09, 2009

Extjs Code Style

Ext JS as a cross-browser JavaScript library, it is a really flexible language. Two peoples have different code style will generate totally different code. So it is really important for us to follow some standard - especial for the piece of code share between groups memeber. Here list some coding standard shared in our group:



1: File Structure and name

Because UI level can be easily divided as center/south/east/west/noth panel. For each page - especial complex UI, we will always create a folder for each panel, such as: Center, South etc. And we always put the share files into the common folder.

In a words, the file and folder should be easy to connect to the UI level - this will save lots of time for future maintainance.

And namespace will be always used for each folder: Ext.ns('feyaSoft.calendar.dayView')

2: Define the Class

The target of extjs is to achieve complex UI. Obviously this includes part of logic to achieve UI behavior. To make code readable and reusable, we'd better separate the logic code and UI code. For example:


Ext.ns("feyaSoft.main");

feyaSoft.main.MainPanel = function(){
....
// down tab panel
this.downTabPanel = new Ext.TabPanel({
region: 'south',
tabPosition: 'bottom',
activeTab: 0,
deferredRender: false,
layoutOnTabChange:true,
height: 190,
split:true,
resizeTabs:true,
tabWidth:200,
minTabWidth: 120,
plugins: new Ext.ux.TabCloseMenu(),

items:[{
title: 'Our Customer\'s Project',
iconCls: 'application',
closable: false,
autoScroll:true,
layout:'fit',
border: false,
cls:'feyasoft-images-view',
items: [this.customerView]
}]
});

....
feyaSoft.main.MainPanel.superclass.constructor.call(this, {
id: 'main_panel_page',
region: 'center',
layout: 'border',
border: false,
items: [this.upTabPanel, this.downTabPanel]
});
};

Ext.extend(feyaSoft.main.MainPanel, Ext.Panel, {

// click customer image link
onCustomerClickFn : function(dv, index, node, e){
var rd = dv.store.getAt(index);
var data = rd.data;
var name = data['name'];

.....
},

....
});

In this way, we can reuse this logic code as we like. And make code really easy to readable.

3: Try to avoid use "id" in the panel/Grid/field etc.

Because id is binded with dom field, there will be only one place work correct when we reuse the same piece of code with id in another place.

In general, the purpose of id is to make get the reference of item in different place , like: Ext.getCmp('myId'). We can achieve this through pass reference or try use: this.ownerCt.reload() to get other item reference.

Some suggestion:

1: Try to reuse the code - if 2 place have similar code, make it comment and reuse
2: Any function and method should not more than 100 line. Try to have sub function/method if it is too big.
3: Try to use this: if (true == flag) instead of: if (flag == true)

Tuesday, April 28, 2009

Upgrade to Grails 1.1

It is a pain process to update to Grails 1.1 (Your know I do not want to read long release notes)

1: Acegi Security failed - need re-run plugin again. (Plugins are now stored in your USER_HOME directory. You will need to re-install your plugins or run: grails -Dgrails.project.plugins.dir =./plugins run-app)

1.1: Install the Acegi plugin
> grails install-plugin acegi

1.2: CREATE THE USER, ROLE, AND REQUESTMAP DOMAIN CLASSES
> grails create-auth-domains User Role Requestmap

1.3: OPTIONAL - CREATE CONTROLLERS AND GSPS FOR USER, ROLE, AND REQUESTMAP DOMAIN CLASSES
> grails generate-manager

1.4: OPTIONAL - CREATE CONTROLLERS AND GSPS FOR CAPTCHA, REGISTER, AND AN EMAILER SERVICE.
> grails generate-registration

1.5: CREATE A CONTROLLER THAT WILL BE RESTRICTED BY ROLE
> grails create-controller Secure

2: Log4j config change - need read document careful.

// log4j configuration
log4j = {
appenders {
rollingFile name: 'homeLog',
fileName: '/var/log/home/full.log',
maxFileSize: 26214400,
maxBackupIndex: 10,
layout: pattern(conversionPattern: '%d{yyyy-MM-dd HH:mm:ss,SSS} %p %c{2} %m%n')
}
root {
error()
additivity = true
}
error 'org.codehaus.groovy.grails.web.servlet', // controllers
'org.codehaus.groovy.grails.web.pages' // GSP

debug fileLog:'grails.app'
warn 'org.mortbay.log'
}

3: Return Date format

I do have date format issue after I upgrade to Grails 1.1. Ok, this is also new feature. Now grails is locale aware and will change the dateformat according to the user's locale. So you need add this to the config.goovy if you have any issue:

grails.converters.json.date = 'javascript' // default or javascipt

Or do something like this:

import grails.converters.JSON;

class BootStrap {

def init = { servletContext ->
JSON.registerObjectMarshaller(Date) {
return it?.format("dd-MM-yyyy")
}
}
def destroy = {
}
}


4: Acegi issue - I run into the issue related Acegi for reDirect (sometimes)

2009-04-29 10:06:27,875 [6216949@qtp0-1] ERROR errors.GrailsExceptionResolver - java.lang.NullPointerException
org.codehaus.groovy.runtime.InvokerInvocationException: java.lang.NullPointerException
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:92)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:234)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1061)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:910)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:892)
at groovy.lang.Closure.call(Closure.java:279)
at groovy.lang.Closure.call(Closure.java:274)
at org.codehaus.groovy.grails.web.servlet.mvc.SimpleGrailsControllerHelper.handleAction(SimpleGrailsControllerHelper.java:363)
at org.codehaus.groovy.grails.web.servlet.mvc.SimpleGrailsControllerHelper.executeAction(SimpleGrailsControllerHelper.java:243)
at org.codehaus.groovy.grails.web.servlet.mvc.SimpleGrailsControllerHelper.handleURI(SimpleGrailsControllerHelper.java:203)
at org.codehaus.groovy.grails.web.servlet.mvc.SimpleGrailsControllerHelper.handleURI(SimpleGrailsControllerHelper.java:138)
at org.codehaus.groovy.grails.web.servlet.mvc.SimpleGrailsController.handleRequest(SimpleGrailsController.java:88)
at org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter.handle(SimpleControllerHandlerAdapter.java:48)
at org.codehaus.groovy.grails.web.servlet.GrailsDispatcherServlet.doDispatch(GrailsDispatcherServlet.java:264)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:807)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:571)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:501)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:707)


To make sure it is this problem, temporary you can disable Acegi - set "active = false" in SecurityConfig.groovy.

It looks like it's a filter - Grails implements these using one HandlerInterceptor (CompositeInterceptor) that iterates your configured filters:

at org.codehaus.groovy.grails.plugins.web.filters.CompositeInterceptor$-postHandle-closure1.doCall(CompositeInterceptor.groovy:48)

Thursday, April 23, 2009

Extjs based online Spreadsheet/Excel

After a while, extjs based online Spreadsheet/excel is released. You can view the demo @

www.feyasoft.com -> My Document -> Right Click Mouse(New Spreadsheet)

Currently it has the following features available:

1: Increase columns by auto scroll down the mouse
2: Cut/Copy/Paste
3: Undo/Redo
4: Add/edit/delete comments
5: Save As

There are several features on the list for process:

1: Calculate (Summary, Average, Min, Max etc)
2: Export as Excel/CSV

Thursday, March 19, 2009

Build J2EE application with Extjs + Grails

Running open source calendar app: www.cubedrive.com/myCalendar

For J2EE based web application, it is pretty popular to use light weight J2EE loose coupling concepts in business logic tier, and use Structs/Spring MVC/JSF as web framework in presentation tie. However, more and more customers require simple, quick, easy and affordable solution for their business - for example, how to quick build web-based J2EE application similar with Microsoft Outlook. We, J2EE application architecture/developer, need quick move forward to build Ajax-based rich interface J2EE application. Extjs + Grails are the right frameworks for build web application at the right time (2009 is the cut cost year everywhere).

Ext JS, a cross-browser JavaScript library for building Rich Internet Applications, owns an extensive library of super-high-quality widgets, an intuitive, extensible component model, and an easy-to-use API to create a full, rock-solid platform for JavaScript-based web apps.

Grails is an open source web application framework which leverages the Groovy programming language (which is in turn based on the Java platform). It is intended to be a high-productivity framework by following the "coding by convention" paradigm, providing a stand-alone development environment and hiding much of the configuration detail from the developer.

High Level Architecture Diagram

Figure 1 shows the basic high level architecture for Extjs/Grails-based RIA J2EE application. [1]

Choice widely adopted 3rd-party software

Open source solutions become widely adopted only if they're reliable and deliver value. Using those valuable open sources will buy us more time and better performance for the project. This is the reason we choice the following the 3rd-party software in this application. See table 1 for detail information [2]

3rd-party Software
Reason to choose
Extjs (www.extjs.com)Extjs Ajax framework acts as a glue between browser and server, it enables us to create a rich and intuitive user interfaces for our application. Extjs provides Layout, Grid, Form, Tree, Menu etc which are widely used in our UI level. Furthermore, extjs provides great document/API.
Grails (www.grails.org)Open source web application framework help us to quick build server side logic, which based on spring framework.
Tomcat (tomcat.apache.org)Free application server.
Apache (www.apache.org)Load balance
MySql (www.mysql.org)Free database server.


Rich UI interface - extjs

By using extjs javascript framework, we can easy achieve nearly all of online ajax-based UI stuff. Compare with DOJO (orthodoxy and stability), extjs has more UI widgets, pretty detail API document which will save lots of developer time. Except that you are fine for extjs commercial license.

Extjs provides following widgets:

  • Grid
  • Layout
  • Window
  • Tree
  • Form
  • Menu
  • and lots of active memeber with really useful plugin

Data Exchange

In tradition way, data exchange from backend to frontend are as following

  • Object in database is transfered to DAO POJO object
  • DAO POJO object transfer data to MVC form object
  • Form object display results in page

In Ajax-based application, data exchange can be something like this:

  • Object in database is transfered to DAO POJO object
  • Render POJO object as JSON data to the UI level.

Grails provides great API to make data exchange between extjs and controller seamless.


Grails Acegi plugin for Authentication and Autherization

Grails based acegi security plugin allows developer to use the powerful of spring-based security, but it also gives us the power to make config changes without having to deal with the complexity of using Spring Security directly.

  • Spring Security filters and Spring beans are registered in web.xml and the application context for you; customization is done in SecurityConfig.groovy
  • Domain classes, Controllers, and CRUD GSPs for User and Role management and persistence
  • Ajax-based login and Logout Controllers






Tuesday, January 20, 2009

Integrate Acegi(0.5.1) with Grails

Recently we try to integrate Acegi(0.5.1) into the grails. We follow the document:

http://docs.codehaus.org/display/GRAILS/AcegiSecurity+Plugin+-+Basic+Tutorial

However, this document is not updated with the latest Acegi. You need pay attention to several issue when you create role, requestmap. Otherwise, it will not work for your.

When you create a role, the role name should include "ROLE" (case sensitive): i.e: ROLE_admin. And this is the quick steps to plugin acegi into your grails project:

1.1: Install the Acegi plugin

> grails install-plugin acegi

1.2: CREATE THE USER, ROLE, AND REQUESTMAP DOMAIN CLASSES

> grails create-auth-domains User Role Requestmap

1.3: OPTIONAL - CREATE CONTROLLERS AND GSPS FOR USER, ROLE, AND REQUESTMAP DOMAIN CLASSES

> grails generate-manager

1.4: OPTIONAL - CREATE CONTROLLERS AND GSPS FOR CAPTCHA, REGISTER, AND AN EMAILER SERVICE.

> grails generate-registration

1.5: CREATE A CONTROLLER THAT WILL BE RESTRICTED BY ROLE

> grails create-controller Secure

2.1: Create role

http://localhost:8080/demo/role
attention - And role name should be start with "ROLE" - i.e: ROLE_ADMIN, ROLE_USER

2.2: Create user

http://localhost:8080/demo/user

2.3: create url request map

http://localhost:8080/demo/requestmap
please add the following url:

/user/** /role/** /adminpage/** /requestmap/**
ROLE_ADMIN

2.4: login to admin page

http://localhost:8080/demo/adminpage

Friday, January 16, 2009

Grails JasperReport plugin

Recently we start to play with Jasper plugin for Grails (version 1.0.4), and we did not have lucky to make it work - we use comment line "grails install-plugin jasper" (version 0.9). We always end with this kind of error - my poor hair:
at net.sf.jasperreports.engine.JRPropertiesMap.readObject(JRPropertiesMap.java:185)
After look around for a while, it seems the grails plugin jasper report library version is too old, and it somehow does not work under this version of Grails.

After update to the latest version of jasperReport and iText

iText-2.1.4.jar
jasperreports-3.1.3.jar


and put it into plugins/japser-0.9/lib, now I can generate the PDF file.

And if you are using the latest iReport, you also need pay attention to the xml format of .jrxml. Please follow the example .jrxml to generated your .jasper file.

Tuesday, December 30, 2008

extjs + AIR installed in Vista SQLError 3122

I have built a small Extjs+AIR+SQLite project and installed it in the Vista system. I got the following error message when do create/Edit/delete.

SQLError: 'Error #3122', details:'', operation:'execute'

This Extjs AIR application makes use of the local database (SQLite) and I was unable to update/create/delete a record; I was repeatedly getting error 3122 during the save operation. After looking up the error description I finally figured out it was because the database file was read-only. And the installed file was in the Vista application directory. (Vista make this - it is not me, right? I think I already complain too much for IE - Vista is really good ??? / compare with IE - your know I did not have enough sleep past 2 days)

AIR applications have privileges to write to any location on the user's hard drive; however, developers are encouraged to use the app-storage:/ path for local storage related to their application. Files written to app-storage:/ from an application are located in a standard location depending on the user's operating system:
  • On Mac OS: the storage directory of an application is //Local Store/ where is the user's “preferences folder,” typically: /Users//Library/Preferences

  • On Windows: the storage directory of an application is \\Local Store\ where is the user's CSIDL_APPDATA “Special Folder,” typically: C:\Documents and Settings\\Application Data

  • On Linux: //Local Store/where is /home//.appdata

You can access the application storage directory via the air.File.applicationStorageDirectory property. You can access its contents using the resolvePath() method of the File class.

Do not installed AIR project under Vista/program file folder if you need touch SQLite DB file.

Hopefully anyone does not hit this stupid error like me.

Tuesday, December 23, 2008

Javascript file Concatenation and Compression

Currently there are several way to do this, you can see this one:

YUI Compressor
[http://www.julienlecomte.net/blog/2008/10/80/]

The YUI Compressor uses a slightly modified version of the parser used in the Rhino JavaScript engine.However, it is Java based tool and you need compile/run etc. Actually it is the best mini tool I found so far.

If you use Apatan/Eclipse, maybe you need think this one.

JSConcatenation
[http://www.rockstarapps.com/pmwiki/pmwiki.php?n=JsLex.JSConcatenation]

Using this tool, will make your easy to mini your file into any output as your like. Rockstarapps.com has an aptana 'feature' that you download via aptanas internal software update engine. It allows for many other things but I was only interested in the JS tools it offers.

It is very straight forward, select files in the resources pane or better yet select your script tags right in an HTML document and "right click--> rockstarapps -->concatenate javascript". It produces an xyz.all.js file (with comments), a xyz.all.min.js file (without comments, linebreaks and spaces) an xyz.all.ycomp.js file (minified and yui compressed), AND it even includes a xyz.all.ycomp.js.gz (Gzipped).

Monday, December 15, 2008

fileupload with Extjs + Grails issues

Recently when I played fileupload with extjs + grails, I have several issues and take a while to identified.

1: In extjs, you need set: fileUpload: true in your formPanel. This will send multipart POST to the server side.

var imagepath = new Ext.form.FileUploadField({
id: 'form-file',
emptyText: 'Select an image',
fieldLabel: 'Photo',
name: 'imagepath',
anchor: '90%',
buttonCfg: {
text: '',
iconCls: 'upload-icon'
}
});
var formPanel = new Ext.form.FormPanel({
id: 'formPanel',
fileUpload: true,
baseCls: 'x-plain',
labelWidth: 120,
url:posurl,
defaultType: 'textfield',
items: [name, imagepath, description]
});

2: You'd better download the latest bug-fixed build (1.0.5) if you see error:

[1884908] errors.GrailsExceptionResolver
java.lang.NullPointerException
at java.net.URLDecoder.decode(URLDecoder.java:119)

There's something fundamentally wrong about how Grails handles multipart POST requests right now (since rev. 7387), but it only happens on this setup:
  • a HTTP POST multipart request is sent to the server;
  • the next request is a GET (or maybe POST also) with query parameters (eg, a query string is present in the URL);
  • Jetty (or the container) binds this next requext to the same thread that the multipart POST was bound to.
Latest build @:
http://bamboo.ci.codehaus.org/download/GRAILS-GRAILS15/artifacts/build-1462/Distributions

This build fixed above issue - 3460 [http://jira.codehaus.org/browse/GRAILS-3640]. Grails-1.1-beta1 does not fix this issue.

3: Render

For fileupload, ExtJs's formpanel is expecting JSON reply, typical HTTP 200 response is not valid and you will get error on the client side Javascript. Therefore, you need to specify the JSON response as normal "text/html" instead of "application/json" type.

Thursday, November 27, 2008

Integrate Grails with Extjs

0/1 -> C -> C++ -> Java -> ? (Assume it is: Grails + extjs)

Extjs acts as front-end, grails works as back-end. Both Grails and Extjs can handle JSON very well. Grails has built-in support for JSON. This is not surprise that JSON will be communication tool between them.

However, in Grails official documentation, there is not enough statement for how to generate JSON. Here comes some of my example to create JSON with Grails (List, Create, Update, Delete), and this JSON data will be feed into extjs (i.e: extjs sexy part - grid).

Feeling: Grails saved me lots of time to write backend code - not need any config and I do not need write hibernate mapping, this is amazing. It really let developer focus on application business logic (Still remember 2000 when EJB come and said this - it really did not). Just Eclipse does not well support grails at this moment, hopefully it come soon.

See open source calendar app: www.cubedrive.com/myCalendar

0: Update with NetBeans IDE6.5 [Update Dec 24]

Recently I played this with NetBeans IDE6.5. Seems this one better than Eclipse, you just download the latest Netbeans package (250 M - huge). And Netbeans already have grails plugin. And you can simple click run button to make grails project run. Worth to have a try - I guess I will stay in Netbeans for a while in grails project.

1: Quick setup


2: list in Grail controller and extjs
/**
* Copyright (c) 2005 - 2008 FeyaSoft Corp. All Rights Reserved.
*/
import grails.converters.*

import com.feyasoft.myActivity.domain.account.*

/**
* @author Fenqiang Zhuang
* @Dec 1, 2008
*
* This file is used to define the account controller action
* This will provides the service for UI level.
* Most important actions are list, save, update, delete
*/
class AccountController {

// the delete, save and update actions only accept POST requests - maybe not necessary
def allowedMethods = [delete:'POST', update:'POST', create:'POST']

/**
* Mapping url: http://localhost:8080/fileMgr/account/list
*
* This will lost of accounts in the system based on search result.
* Return result will be in JSON format.
*
*/
def list = {
// log information
log.info(params: params)
if(!params.max) params.max = 20

// get a list of result based on params.
def accounts = Account.list(params)

// return a bunch of json data with metadata.
def json = [
metaData: [
totalProperty: 'totalCount',
root: 'results',
id: 'id',
fields: [
[name: "id", type: "int"],
[name: "firstname"],
[name: "lastname"],
[name: "username"],
[name: "email"]
]
],
totalCount: accounts.size,
results: accounts
]

render json as JSON
}

/**
* * Mapping url: http://localhost:8080/fileMgr/account/save
*
* create account method - get passed parameter from extjs and call save() function
* If everything ok, it will return success message for form.
*/
def create = {
def account = new Account(params)

// default error msg
def json = [failure : "true", errorInfo : "Internal Error, please try again"]

// check whether something wrong in save
if(!account.hasErrors()) {
// check username already exist or not
if (Account.findAllByUsername(account.username).size()>0) {
json = [failure : "true", errorInfo: "This username already existed, please choose another one"]
} else if (account.save()) {
json = [success : "true", info : "You have success creat this account"]
} else {
def errors = "Error is: "
account.errors.allErrors.each {
errors = errors + it
}
json = [failure : "true", errorInfo: errors]
}
}

// return json data
render json as JSON
}

/**
* This will load existing account information based on id from UI.
* Return back a json string.
*
* JSON like this:
*
* {"success":"true","data":[{"id":4,"class":"Account","createDate":new Date(1227909694000),
* "email":"","firstname":"jj","lastModifiedDate":new Date(1227909694000),"lastname":"j",
* "password":"kkkk","username":"llkl"}]}
*/
def load = {
def account = Account.get(params.id)

if(account) {
def json = [success: "true", data: [account]]
render json as JSON
} else {
// default error msg
def json = [failure : "true", errorInfo : "Internal Error, please try again"]
render json as JSON
}
}

/**
* This will update the passed parameter from UI level
* Called by the Edit.js file.
*/
def update = {
def account = Account.get( params.id )

// default error msg
def json = [failure : "true", errorInfo : "Internal Error, please try again"]

if(account) {
account.properties = params
if(!account.hasErrors()) {
if (account.save()){
json = [success : "true", info : "You have success update this account"]
} else {
def errors = "Error is: "
account.errors.allErrors.each {
errors = errors + it
}
json = [failure : "true", errorInfo: errors]
}
}
}

// return json data
render json as JSON
}

/**
* Delete this related account by id - passed by
*/
def delete = {
def json = [failure : "true", errorInfo : "Internal Error, please try again"]

// check whether del parameter is passed - if yes, delete first
if (params.delData != null) {
def jsonArray = JSON.parse(params.delData)
jsonArray.each {
log.info("start to delete account with id: ${it.id}")
def delAccount = Account.get(new Long("${it.id}"))
if (delAccount) {
delAccount.delete()
json = [success : "true", info : "You have success delete this account"]
}
}
}

render json as JSON
}
}
Here is the extjs side code:
    feyaSoft.account.List = function() {

Ext.QuickTips.init();

// define default rowsPerPage = 10;
var myPageSize = 20;

var accountCM = new Ext.grid.ColumnModel([
new Ext.grid.RowNumberer(),
{id: 'id', header: "Identify", dataIndex: 'id', width: 150, hidden: true},
{header: "First Name", dataIndex: 'firstname',width: 150},
{header: "Last Name", dataIndex: 'lastname',width: 150},
{header: "Username", dataIndex: 'username',width: 150},
{header: "Email", dataIndex: 'email',width: 150}
]);
accountCM.defaultSortable = false;

/************************************************************
* connect to backend - grid - core part
* create the Data Store
* connect with backend and list the result in page
* through JSON format
************************************************************/
this.dataStore = new Ext.data.JsonStore({
url: 'account/list',
remoteSort: true,
fields: [] // initialized from json metadata
});
this.dataStore.setDefaultSort('lastname', 'ASC');

/************************************************************
* Define menubar now in here
* add and delete functions
************************************************************/
var menubar = [{
text:'Add New Account',
tooltip:'Add a new account information',
iconCls:'addItem',
handler: function(){
// add new user now - call another JS class
new feyaSoft.account.Create();
}
},'-',{
text:'Archive',
tooltip:'Archive the selected items',
iconCls:'remove',
handler: function(){
//new feyaSoft.util.DeleteItem({panel: 'listAccount-panel'});
}
}];

var pagingbar = new Ext.ux.PagingToolbar({
pageSize: myPageSize,
store: this.dataStore,
displayInfo: true,
displayMsg: 'Displaying items {0} - {1} of {2}',
emptyMsg: "not result to display"
});

/************************************************************
* define grid panel now
* For more detail parameters, you can find from extjs API
************************************************************/
feyaSoft.account.List.superclass.constructor.call(this, {
id: 'list-account-panel',
ds: this.dataStore,
cm: accountCM,
viewConfig: {forceFit:true},
tbar: menubar,
bbar: pagingbar,
height:400,
width:800,
frame: true,
autoScroll:true
});

// trigger the data store load
this.dataStore.load({params:{start:0, limit:myPageSize}});

};

Ext.extend(feyaSoft.account.List, Ext.grid.GridPanel, {

// reload datasource
reload : function () {
this.dataStore.load();
},

remove : function (row) {
this.dataStore.remove(row);
},

deleteData : function (jsonData) {
this.dataStore.load({params:{start:0, delData:jsonData}});
}
});

Some hints maybe help for your to use Grails:

1: Get latest version Grail 1.0.4 and setup GRAIL_HOME in your ENV: http://grails.org/

2: Create a grails project through Eclipse.

3: Update

You do not need create tables - using dbCreate="create" for first time and grails will create DB table for us (you do need create database name). Go to:
grails-app/conf/DataSource.groovy file, you need update:
 dataSource {
pooled = true
driverClassName = "com.mysql.jdbc.Driver"
username = "root"
password = "XXXXXX"
}

development {
dataSource {
dbCreate = "update" // one of 'create', 'create-drop', 'update' - first time, please use 'create'
url = "jdbc:mysql://localhost:3306/demo?tcpKeepAlive=true&useUnicode=true&characterEncoding=UTF8"
}
}

4: Run

come to the project folder: cd yourProjectFolder // using cmd

> grails clean <--- sometimes compiled code are not cleaned and behavior weird - clean will help
> grails run-app

You can also run this through ant in Eclipse. However, I do not know how to stop Jetty under Eclipse (Finally I kill it through Ctrl + Alt + del and find task manager - kill all of Java running thread.).

Wednesday, November 05, 2008

Extjs based Calendar 1.0 release

After a while (6 month later), we finially have some time and move our calendar forward again. In this release 1.0, you will see the following feature:
  • Fully integrated in Ext
  • different views (day, week, month)
  • create/delete/change entries -- contextMenu and doubleClick
  • drag and drop to shift entries
  • Event overlaps
  • event south resizeable
  • edit entries in a window
  • colorable event
The project structure is still:

Extjs + spring MVC Json View + Spring + hibernate + Mysql

To view demo, please visit: www.feyasoft.com

Monday, October 20, 2008

Adobe AIR + extjs for desktop application

For building desktop application, there have .Net and Java Swing solution. However, there seems not that elegant for me. Recently we are trying to use following structure to build own desktop application:

Adobe AIR (SQLite buildin) + extjs

This is the general steps we process to build extjs + AIR project:

1: Download develop tool which will use to edit/package adobe air project (Adobe air and Aptana studio)


2: Start Aptana and install plugin

Click the icon (4th or 5th icon in second line): "Open Plugin View", you will find:
  • Adobe Air support
  • extjs support
Install those 2 plugin.

3: Now start Aptana - create new project

File -> New -> project -> Adobe Air Project

You can create some example project - such as: Tasks. And you will find 2 important files: tasks.html and application.xml

4: Hit: Run -> Run ...

You will see your example running ...

5: Export/deploy air project.

You need setup certificate. Every AIR application requires certificate signing in order to be built and deployed.See this link for more information:

http://www.adobe.com/devnet/air/ajax/articles/building_on_air_in_aptana_05.html

Some problem ...

* If you see "invocation forwarded to primary instance"
You need go to your task manager and kill manually ADL.EXE

Wednesday, June 04, 2008

DWR or Spring MVC + JSONView

To build extjs based application. At first glance, you will find that DWR is pretty neat. You do not need MVC level code and client side can talk direct to service layer. Seems this will save lots of time and code.

Wait.... Is this really true? (perfect solution = time cosuming ?)

After fight with DWR problem for a while, I decide move back to Spring MVC JsonView.

Extjs have a pretty good interface to hanlde JSON data. By using DWR, you need decide whether you still use JSON data, or pass trhough object (maybe parameter). You need write your own reader to understand passed object. At least you need write: DWRTreeProxy, DWRListProxy, DWRComboProxy. True, you can copy those proxy from forum, there are lots of examples. However, those example does not have quality guarantee, and it drive me crazy to debug line by line.

I try to save time when I move to DWR, finally I found I spent more time that using Spring MVC + JsonView.

Suggestion: if you know really well for extjs and DWR, you can try using DWR. Jack said that he had a really success porject with Extjs + DWR. However, Jack is Jack. Otherwise, keep Extjs + JsonView. This will save your lots of time. (honestly, Extjs + JsonView already really beautiful, DWR seems a perfect solution, however it will take your long time if you are not as smart as Jack - oh ya, I am only 10% smart as Jack)

Friday, March 28, 2008

Spring MVC JSON view

For Ajax-based web application, one of the most popular ways for message transfer is JSON (JavaScript Object Notation). JSON provides a pretty name/value pair data format which is easy to generate and parse.

With the popular using Spring framework. It would be really useful to integrate Spring MVC view with JSON. Spring already provides several view resolvers, which enable us to render models in a browser, such as: JSPs, Velocity templates and XSLT views.

To add JSON view to the Spring MVC, we need create a class: JSONView

public class JSONView implements View {
private static final String DEFAULT_JSON_CONTENT_TYPE = "application/json";
private static final String DEFAULT_JAVASCRIPT_TYPE = "text/javascript";
private JSONObject jsonObject;

public JSONView(JSONObject jsonObject) {
super();
this.jsonObject = jsonObject;
}

public JSONView() {
super();
this.jsonObject = null;
}

public String getContentType() {
return DEFAULT_JSON_CONTENT_TYPE;
}

public void render(Map model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
if (this.jsonObject == null) {
this.jsonObject = JsonUtil.fromMap(model);
}

boolean scriptTag = false;
String cb = request.getParameter("callback");
if (cb != null) {
scriptTag = true;
response.setContentType(DEFAULT_JAVASCRIPT_TYPE);
} else {
response.setContentType(DEFAULT_JSON_CONTENT_TYPE);
}

PrintWriter out = response.getWriter();
if (scriptTag) {
out.write(cb + "(");
}
out.write(this.jsonObject.toString());
if (scriptTag) {
out.write(");");
}
}
}

And this is fromMap function in JsonUtil

public static JSONObject fromMap(Map model) {
JSONObject jsonObject = new JSONObject();

try {
Iterator ite = model.keySet().iterator();
while (ite.hasNext()) {
String key = ite.next();
jsonObject.put(key, model.get(key));
}
} catch (Exception e) {
log.error("call fromMap failed ");
}

return jsonObject;
}

And in the controller side, we can pass JOSNObject to create a JSONView. Or we can also pass MAP to create JSONView.

Map map = new HashMap();
map.put("success", "true");
map.put("info", "Succeed create this audit object");
return new ModelAndView(new JSONView(), map);
OR

BaseSearchResult searchResult = auditService.getList(listAuditInfo);
AuditUtil auditUtil = new AuditUtil(searchResult);
return new ModelAndView(new JSONView(auditUtil.toJSONObject()));