See detail demo @ www.feyasoft.com and login as: demo/demo
We are Web2.0 application consultant/developer group based on Ottawa. Currently our interesting parts are Ajax(ExtJS), Grail(Spring, hibernate), Adobe AIR etc. And hopefully we can keep up with the latest 'interesting' stuff.

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'];
.....
},
....
});
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
// 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'
}
import grails.converters.JSON;
class BootStrap {
def init = { servletContext ->
JSON.registerObjectMarshaller(Date) {
return it?.format("dd-MM-yyyy")
}
}
def destroy = {
}
}
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)

| 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. |
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
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.
On Mac OS: the storage directory of an application is
On Windows: the storage directory of an application is
On Linux:
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]
});
/**Here is the extjs side code:
* 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
}
}
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}});
}
});
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"
}
}
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(");");
}
}
}
public static JSONObject fromMap(Map model) {
JSONObject jsonObject = new JSONObject();
try {
Iteratorite = 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;
}
OR
Mapmap = new HashMap ();
map.put("success", "true");
map.put("info", "Succeed create this audit object");
return new ModelAndView(new JSONView(), map);
BaseSearchResult searchResult = auditService.getList(listAuditInfo);
AuditUtil auditUtil = new AuditUtil(searchResult);
return new ModelAndView(new JSONView(auditUtil.toJSONObject()));
/**Third, I can call following public functions from any JS class.
* Copyright(c) 2006-2007 ...
*/
package("FEYA.util");
/**
* This JS is used to define a bunch of util class
*
* @author fzhuang
* @Date July 26, 2007
*/
FEYA.util.common = function(){
// private variable/function. Not accessible from outside
var count = 0;
var increaseCount = function(){count++;};
// Priviledged method. Can be called from outside
return {
formatDate: function(value){
return value ? value.dateFormat('D, M d Y') : '';
}
}
}();
FEYA.util.common.formatDate

{"totalCount":2,The big different about JSON and XML is that JSON is a subset of JavaScript. I can use JavaScript's own compiler to do just that by calling eval. Parsing JSON is a one-liner! Moreover, navigating an object synthesized from JSON is identical to navigating any JavaScript object. It's far easier than navigating through the DOM tree.
"results":[{"bh":"100","sm":"hello"},{"bh":"101","sm":"me" }]}
public JSONObject toJSONObject() throws Exception {To parse JSON data from JS in serve side, please see the following example:
JSONObject json = new JSONObject();
json.put("totalCount", totalCount);
JSONArray jsonItems = new JSONArray();
for (Iteratoriter = results.iterator(); iter.hasNext();) {
jsonItems.put(iter.next().toJSONObject());
}
json.put("results", jsonItems);
return json;
}JSONArray jsonArray = JsonUtil.getJsonArray(jsonString);
// JsonUtil.getJsonArray(jsonString) just do this ->
// jsonArray = new JSONArray(jsonString);
// loop through - get from json and update
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
String id = jsonObject.getString("id");
}
postMsg : function(url, actId) {
var myAjax = new Ajax.Request(url,{
method: 'post',
parameters: 'actId='+escape(actId),
onComplete:handlerResult
});
},
dojo.io.bind({
url: url,
method: "post",
content: {actId: "123456"},
load: function(type, data, evt){/* callback code */ },
error: function(type, error){/* error handling callback */ },
mimetype: "text/plain"
});
public class PhoneService {
public String getCallerName(int callerNumber){...}
}
..script.. type="text/javascript" src="SVProvider/dwr/interface/PhoneService.js ..script..
PhoneService.getCallerName(18003456700, processPBXResponse)
var requestFromObject = YAHOO.util.Connect.asynRequest('post', uri ,
callback , postData);
var callback = {
success: handleSuccess
failure: handleFailure
argument: {callerName: "N/A"}
};
Ext JS is another very popular Ajax framework. It also provides a XHR wrapper allowing quickly and efficiently perform AJAX requests.Ext.Ajax.request({
url : '../listActivity.do' ,
params : { action : 'loadData' },
method: 'GET',
success: function ( result, request ) {
Ext.MessageBox.alert('Success', 'Data return from the server: '+ result.responseText);
},
failure: function ( result, request) {
Ext.MessageBox.alert('Failed', 'Successfully posted form: '+action.date);
}
});

DWR consists of two main parts:
YAHOO.example.calendar.cal1.selectEvent.subscribe(mySelectDate, YAHOO.example.calendar.cal1, true);
YAHOO.example.calendar.cal1.addRenderer(txtDate1.value, YAHOO.example.calendar.cal1.renderCellStyleHighlight2);
YAHOO.example.calendar.cal1.render();
In my application, I need send new request to the server and get result for this specific date. It works well in Firefox, however, it does not always work in IE6. After dig for a while, there exist this code in YUI calendar.js:YAHOO.widget.Calendar.prototype.renderCellDefault = function(workingDate, cell)IE does not pass javascript:void(null) in href.
Exist: javascript:void(null) in href
href="javascript: ", onclick="javascript:" There is no such thing as a JavaScript protocol on the web. Links use protocols to connect documents.



protected BindException bindObject(HttpServletRequest request,
Object command, Validator validator) throws Exception {
preBind(request, command);
ServletRequestDataBinder binder = createBinder(request, command);
binder.bind(request);
BindException errors = new BindException(command,
getCommandName(command));
if (validator.supports(command.getClass())) {
ValidationUtils.invokeValidator(validator, command, errors);
}
return errors;
}
public ModelAndView save(HttpServletRequest request,
HttpServletResponse response, PhoneInfo command) throws Exception {
ModelAndView addPhoneView = new ModelAndView(LIST_VIEW, "phones",
phones);
addPhoneView.addObject("phoneInfo", command);
// add validator and call bindobject to get the result
BindException errors = super.bindObject(request, command, new PhoneInfoValidator());
if (errors.hasErrors()) {
addPhoneView.addAllObjects(errors.getModel());
return addPhoneView;
}
// otherwise --- save this object...
return addPhoneView;
}