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()));

Thursday, February 28, 2008

FeyaSoft MyActivity

After awhile, FeyaSoft MyActivity 1.0 is released now. It includes the following features:
  • My Calendar
  • My Documents
  • My TimeSheet
  • My Task (1.1 release)
The main purpose for this application is try to make your daily activity smooth and easily. And it is pretty easy plugin to your existing system.

To view it, please go to the following link self-register and login:

http://www.feyasoft.com/myActivity

The whole system is built with:

EXTJS + Acegi Security + Spring MVC(JSON View) + Spring + Hibernate(Annotation) + mysql

Simple, qualify and affordable. ENJOY