Using ExtJS with Lift?

49 views
Skip to first unread message

Stefan Scott

unread,
Aug 4, 2009, 9:44:40 AM8/4/09
to Lift
I understand Lift works well with various Ajax frameworks such as YUI
and JQuery, and I really like the "rich internet application" (RIA)
look-and-feel provided by the Ajax framework ExtJS http://extjs.com,
which currently is dual-licensed (commercial or GPL).

I was wondering if it's possible / advisable to use ExtJS with Lift -
and if so, how I would get started using ExtJS with Lift?

Thanks.

marius d.

unread,
Aug 4, 2009, 10:00:34 AM8/4/09
to Lift
To make lift work with ExtJS you need to implement
net.liftweb.http.js.JsArtifacts trait and provide your implementation
in boot such as:

LiftRules.jsArtifacts = YourImplementationOfJsArtifacts

of course on top of this you would likely need to have specific .js
files. take a look on the current implementations for JQuery and YUI
to get an idea and get the ball rolling. Of course if you run into
problems, just post on this list.

Br's,
Marius

On Aug 4, 4:44 pm, Stefan Scott <StefanScottAl...@gmail.com> wrote:
> I understand Lift works well with various Ajax frameworks such as YUI
> and JQuery, and I really like the "rich internet application" (RIA)
> look-and-feel provided by the Ajax framework ExtJShttp://extjs.com,

Timothy Perrett

unread,
Aug 4, 2009, 10:10:53 AM8/4/09
to lif...@googlegroups.com

Stefan,

Chas is doing work with ExtJS, but right now I think because of the GPL
licensing we cant "integrate" it with lift because of some legal stuff.

You might be interested to know that there is a current effort to integrate
with http://cappuccino.org/ which might be of interest (led my myself and
dpp)

Cheers, Tim

Stefan Scott

unread,
Aug 4, 2009, 10:16:36 AM8/4/09
to Lift
Thanks, your instructions sound very straightforward.

Stefan Scott

unread,
Aug 4, 2009, 7:47:49 PM8/4/09
to Lift
Thanks for this info, Marius.

To get started with Lift I'm reading through the excellent book
"Exploring Lift" you wrote with Eric and Tyler (version 27 July 2009),
plus the source for the PocketChange app and liftweb, downloaded from
GitHub.

As I'm still finding my way through the various packages and file
locations, I hope you can bear with me a bit while I think out loud
here to make sure I understand where to put the .js files and the
references for JavaScript resources which aren't integrated into
lift.


***********************************************************
The only real question I have is way down towards the end of this
message, at the end of point (a):

"Where do I put the 'toserve' directory, and how do I make sure that
Maven uses it?"

Feel free to skip most of this lengthy message and just read point (a)
which is the only real question I have. I'm still just getting used to
Maven and how it pulls files from various locations either locally or
online.

***********************************************************


(1) Searching in directory C:\www\work\PocketChange\ (I'm currently
serving lift web apps just locally, running Jetty under Windows), I
see several .js files for JQuery:

date.js
jquery.datePicker.js
jquery.tablesorter.min.js
jquery-ui.min.js

in directory:

C:\www\work\PocketChange\src\main\webapp\scripts\

So it looks like I could put any needed .js files in that directory.


Searching for the string ".js" *inside* all .html files in directory C:
\www\work\PocketChange, I find the file:

C:\www\work\PocketChange\src\main\webapp\index.html

which contains the tag:

<script type="text/javascript" src="/scripts/jquery-ui.min.js"></
script>

referencing the file:

jquery-ui.min.js

in directory C:\www\work\PocketChange\src\main\webapp\scripts\ above.


So again it looks like I can put any needed .js files in that
directory, and reference them in the app's .html file(s) as shown
above.

(One strange minor thing I noticed: if I search inside all .html files
in directory C:\www\work\PocketChange\ for the string "datePicker.js",
it only shows up in lines that have been commented out. So one minor
curiosity I have is: how is the code for datePicker.js being used?
There appears to be a definition of it in jquery-ui.min.js, as well as
a more readable definition of it in datePicker.js as well. I
understand that JavaScript files are "minified" to remove unnecessary
characters - does jquery-ui.min.js include the "minified" version of
datePicker? Is file datePicker.js never sent to the browser?)


(2) The search for the string ".js" inside all .html files also turned
up two occurences in file:

C:\www\work\PocketChange\src\main\webapp\templates-hidden\default.html

in the tags:

<script id="jquery" src="/classpath/jquery.js" type="text/
javascript"></script>
<script id="json" src="/classpath/json.js" type="text/javascript"></
script>

These seem to be referring not to individual JavaScript widgets - but
maybe to entire JavaScript libraries.

I couldn't find any relevant files called "jquery.js" on my machine -
but reading section 7.8 "Resource Server" in the "Exploring Lift" book
(pp. 119-120) I see it says that Maven puts .css (and presumably .js)
resources to be served *inside* the WAR/JAR file, and lift uses the
var LiftRules.resourceServerPath:

var resourceServerPath = "classpath"

to find these resources.


The book goes on to say that in order to find a .css resource in a
subdirectory such as:

<my-project>\src\main\resources\toserve\css\mystyle.css

I would call the following in Boot:

ResourceServer.allow {
case "css" :: _ => true
}


So, examining the lift source code for object ResourceServer in
package net.liftweb.http (in directory ...\liftweb\lift\src\main\scala
\net\liftweb\http\), I think I see how this is all put together:

object ResourceServer {
private var allowedPaths: PartialFunction[List[String], Boolean] = {
case "jquery.js" :: Nil => true
case "yui" :: _ => true
case "liftYUI.js" :: Nil => true
case "json2.js" :: Nil => true
case "json.js" :: Nil => true
case "jlift.js" :: Nil => true
case bp @ ("blueprint" :: _) if bp.last.endsWith(".css") ||
bp.last.endsWith(".png") => true
case "jquery-autocomplete" :: "jquery.autocomplete.js" :: Nil =>
true
case "jquery-autocomplete" :: "jquery.autocomplete.css" :: Nil =>
true
case "jquery-autocomplete" :: "indicator.gif" :: Nil => true
}

//...

var baseResourceLocation = "toserve"

//...

def allow(path: PartialFunction[List[String], Boolean]) {
allowedPaths = path orElse allowedPaths
}

}

Looking at the lift source from GitHub, I see that there are 6
subdirectories and files:

blueprint\
yui\
jlift.js
jquery-1.3.2.js
json2.js
liftYUI.js

(the JavaScript base libraries?), in directory:

...\liftweb\lift\src\main\resources\toserve\

These correspond to the first 6 clauses in the case statement in
object ResourceServer.

(I can see that the wildcard in "yui" :: _ and "blueprint" :: _
represents possible further Strings in the List, indicating that these
are *subdirectories* - versus *files* which are indicated by a one-
element List such as "jquery.js" :: Nil.)


And looking at the source for PocketChangeApp also from GitHub, I
already saw that there are JavaScript files:

date.js
jquery.datePicker.js
jquery.tablesorter.min.js
jquery-ui.min.js

(for individual widgets?) in directory:

...\PocketChange\src\main\webapp\scripts\

so this is where the JavaScript for any individual widgets would go.



So this is my overall plan:


(a) Following the instructions here:

http://extjs.com/learn/Ext_Getting_Started

or on page 20 of the book "Practical Ext JS Projects with Gears" from
Apress by Frank Zammetti, I would reference the additional libraries
by modifying the suggested tags:

<script type="text/javascript" src="extjs/adapter/ext/ext-base.js"></
script>
<script type="text/javascript" src="extjs/ext-all.js"></script> <!--
or my choice of files -->

to read something like:

<script type="text/javascript" src="classpath/ext-base.js"></script>
<script type="text/javascript" src="classpath/ext-all.js"></script>
<!-- or my choice of files -->

and putting these tags in the file:

<my-project>\src\main\webapp\templates-hidden\default.html

and placing the JavaScript libraries themselves in the toserve
directory described on page 120 of "Exploring Lift".

Now here's where I have a question...

************************* QUESTION *************************

Which of 2 possible toserve directories below should I put the .js
files in?

<my-project>\src\main\resources\toserve\
or
...\liftweb\lift\src\main\resources\toserve\

I'm concerned that lift will search only in its own toserve directory:

...\liftweb\lift\src\main\resources\toserve\

Should I put additional files in there? Can I?

On the one hand, I'm not sure if I *should* put any additional files
in the liftweb project - and from my understanding of how Maven works
online (reading dependencies in the project's pom.xml file), I'm not
sure if I even *can*.

As far as I know, Maven running in online mode gets liftweb from
liftweb.net online - so I don't think I even can add a file to the
directory:

...\liftweb\lift\src\main\resources\toserve\

because I don't want to add anything on GitHub (I'm far from being a
committer!) and I don't understand how I could create a local copy of
lift for Maven to use and add things to that local copy.


If I add a JavaScript file to a directory in my *project*:

<my-project>\src\main\resources\toserve\

then will ResourceServer.allow look in there? Somehow I don't think so
- I think it looks in *liftweb's* directory:

...\liftweb\lift\src\main\resources\toserve\


I guess a third possibility might be downloading the liftweb source
from GitHub, adding any additional JavaScript libraries to the local
copy of the liftweb directory which I'm assuming ResourceServer always
looks in:

...\liftweb\lift\src\main\resources\toserve\

(ie, the directory belonging to the liftweb project, not to my
project), and then running Maven in offline mode (so it won't pull
updates from liftweb.net), or maybe modifying the pom.xml for my
project only for the liftweb dependency, to instruct Maven to use the
local, augmented copy of liftweb rather than the online one from
liftweb.net.

But maybe I'm making things more complicated than they need to be here
- so this is the question where I could use some additional help:

>>> Where do I put the toserve directory, and how do I make sure that Maven uses it? <<<

************************************************************

Continuing...

(b) I would reference .js files for individual JavaScript widgets in
specific .html files as needed, say:

<my-project>\src\main\webapp\index.html

and place them in the directory:

<my-project>\src\main\webapp\scripts\


(c) As Marius stated above (and as I now see in section 8.2 "JQuery
and other JavaScript frameworks" in "Exploring Lift", page 129), I
would implement the trait net.liftweb.http.js.JsArtifacts in a class
like ExtJsArtifacts and then reference it in Boot.

This trait net.liftweb.http.js.JsArtifacts has method signature:

def toggle(id: String): JsExp
def hide(id: String): JsExp
def show(id: String): JsExp
def showAndFocus(id: String): JsExp
def serialize(id: String): JsExp
def setHtml(id: String, xml: NodeSeq): JsCmd
def onLoad(cmd: JsCmd): JsCmd
def ajax(data: AjaxInfo): String
def comet(data: AjaxInfo): String
def jsonStringify(in: JsExp) : JsExp
def formToJSON(formId: String): JsExp

and it looks straightforward to use
net.liftweb.http.js.jquery.JQueryArtifacts and
net.liftweb.http.js.yui.YUIArtifacts as "models" for implementing
something like net.liftweb.http.js.extjs.ExtJsArtifacts.


(d) In Boot, I would do the following:

import net.liftweb.http.js.extjs.ExtJSArtifacts

class Boot {

def boot = {
//...
LiftRules.jsArtifacts = ExtJSArtifacts
//...
}

//...

ResourceServer.allow {
case "css" :: _ => true
case "extjs" :: _ => true
}
}

as explained on pages 120 and 129 of "Exploring Lift".

The code in ResourceServer.allow above assumes creating a directory
extjs under toserve.

Again, as mentioned in (a) above, I'm not sure if toserve would be
placed in a local copy of liftweb which I can somehow force Maven to
use:

...\liftweb\lift\src\main\resources\toserve\

or if I should create a toserve directory inside my project and tell
lift to look there:

<my-project>\src\main\resources\toserve\


Sorry this has been so long-winded - as I learn more about lift, I'll
feel more comfortable saying less rather than more in these messages.
Right now I'm erring on the side of spelling everything out, to make
sure I'm not getting anything wrong.

Thanks for pointing me in the right direction!

Derek Chen-Becker

unread,
Aug 5, 2009, 6:23:35 AM8/5/09
to lif...@googlegroups.com
Your guess is correct. The jquery-ui-min.js contains the datePicker. We used to use datePicker.js but then we switched to the datePicker that's in jquery UI.

As for your question about where to put the scripts, you don't want to put them in src/main/resources/toserve unless you're writing a widget or some other programmatic user of the scripts. If you'll just be using the scripts directly in your templates you can just put them in the normal src/main/webapp tree (I usually make a scripts subdir to segregate things). Let me know if that doesn't work or if I'm misunderstanding the question.

Derek

Derek Chen-Becker

unread,
Aug 5, 2009, 6:26:35 AM8/5/09
to lif...@googlegroups.com
OK, so on re-read I totally misunderstood the question :P. If you write your own maven project you could put the scripts under src/main/resources/toserve/extjs (per your Boot example setting up ResourceServer). No need to hack on the lift module itself.

Derek

Stefan Scott

unread,
Aug 5, 2009, 10:16:13 AM8/5/09
to Lift
Thanks Derek!

On Aug 5, 7:26 am, Derek Chen-Becker <dchenbec...@gmail.com> wrote:
> OK, so on re-read I totally misunderstood the question :P. If you write your
> own maven project you could put the scripts under
> src/main/resources/toserve/extjs (per your Boot example setting up
> ResourceServer). No need to hack on the lift module itself.
>
> Derek
>
> On Wed, Aug 5, 2009 at 4:23 AM, Derek Chen-Becker <dchenbec...@gmail.com>wrote:
>
> > Your guess is correct. The jquery-ui-min.js contains the datePicker. We
> > used to use datePicker.js but then we switched to the datePicker that's in
> > jquery UI.
>
> > As for your question about where to put the scripts, you don't want to put
> > them in src/main/resources/toserve unless you're writing a widget or some
> > other programmatic user of the scripts. If you'll just be using the scripts
> > directly in your templates you can just put them in the normal
> > src/main/webapp tree (I usually make a scripts subdir to segregate things).
> > Let me know if that doesn't work or if I'm misunderstanding the question.
>
> > Derek
>
> read more »

Dirk Louwers

unread,
Aug 7, 2009, 9:04:45 AM8/7/09
to Lift
Hi,

I am new to this group and have used ExtJS for some projects and have
recently stumbled across Scala and Lift and am very impressed with
both.

Stefan, I would greatly appreciate it if you could supply your
JsArtifacts trait implementation for ExtJS.
Another option that I'm trying now is to use ExtJS's JQuery adapter
which makes all the widget's DOM manipulation "footwork" and possibly
asynch calls done by JQuery. This way you can keep using Lift's full
JQuery support while being able to use Ext. However, the Ext's native
base library is more performant than JQuery's.

Thanks in advance. And nice to meet you.

Dirk Louwers
> net.liftweb.http.js.jquery.JQueryArtifacts and ...
>
> read more »

Stefan Scott

unread,
Aug 7, 2009, 3:16:11 PM8/7/09
to Lift
Hi Dirk -

Nice to meet you. I'm still rather new to lift myself, and completely
new to ExtJS. I'm studying this stuff in my spare time, and I haven't
had a chance to roll up my sleeves and do any work yet on implementing
lift's JsArtifacts trait for the ExtJS JavaScript library. Of course,
if and when I do get something up and running, I will be happy to post
the code here, as well as email you directly if you'd like. :-)

If the two existing implementations of JsArtifacts already included in
lift (in packages net.liftweb.http.js.jquery.JQueryArtifacts and
net.liftweb.http.js.yui.YUIArtifacts) are anything to go by, then I'm
hoping that doing additional implementions of JsArtifacts for other
JavaScript libraries shouldn't be all that difficult, since these
implementations appear to be fairly straightforward, as shown below:

Recall that the trait net.liftweb.http.js.JsArtifacts has method
signature:

def toggle(id: String): JsExp
def hide(id: String): JsExp
def show(id: String): JsExp
def showAndFocus(id: String): JsExp
def serialize(id: String): JsExp
def setHtml(id: String, xml: NodeSeq): JsCmd
def onLoad(cmd: JsCmd): JsCmd
def ajax(data: AjaxInfo): String
def comet(data: AjaxInfo): String
def jsonStringify(in: JsExp) : JsExp
def formToJSON(formId: String): JsExp

So these are the only functions which need to be defined in any
implemenation(s) of JsArtifacts.

The two existing implementations (for JQuery and for YUI) are shown
below:

------------------------------------------------------------------------------------------
JQuery
------------------------------------------------------------------------------------------
package net.liftweb.http.js.jquery
// imports ...

object JQueryArtifacts extends JSArtifacts {

def toggle(id: String) = JqId(id) ~> new JsMethod {
def toJsCmd = "toggle()"
}

def hide(id: String) = JqId(id) ~> new JsMethod {
def toJsCmd = "hide()"
}

def show(id: String) = JqId(id) ~> new JsMethod {
def toJsCmd = "show()"
}

def showAndFocus(id: String) = JqId(id) ~> new JsMethod {
def toJsCmd = "show().each(function(i) {var t = this; setTimeout
(function() { t.focus(); }, 200);})"
}

def serialize(id: String) = JqId(id) ~> new JsMethod {
def toJsCmd = "serialize()"
}

def setHtml(id: String, xml: NodeSeq): JsCmd = JqJsCmds.JqSetHtml
(id, xml)

def onLoad(cmd: JsCmd): JsCmd = JqJsCmds.JqOnLoad(cmd)

def ajax(data: AjaxInfo): String = {
"jQuery.ajax(" + toJson(data, S.contextPath,
prefix =>
JsRaw(S.encodeURL(prefix + "/"
+LiftRules.ajaxPath + "/").encJs))+");"
}

def comet(data: AjaxInfo): String = {
"jQuery.ajax(" + toJson(data, LiftRules.cometServer(),
LiftRules.calcCometPath) + ");"
}

def jsonStringify(in: JsExp) : JsExp = new JsExp {
def toJsCmd = "JSON.stringify(" + in.toJsCmd + ")"
}

def formToJSON(formId: String):JsExp = new JsExp() {
def toJsCmd = "lift$.formToJSON('" + formId + "')";
}

private def toJson(info: AjaxInfo, server: String, path: String =>
JsExp): String =
(("url : liftAjax.addPageName(" + path(server).toJsCmd + ")" ) ::
"data : " + info.data.toJsCmd ::
("type : " + info.action.encJs) ::
("dataType : " + info.dataType.encJs) ::
"timeout : " + info.timeout ::
"cache : " + info.cache :: Nil) ++
info.successFunc.map("success : " + _).toList ++
info.failFunc.map("error : " + _).toList mkString("{ ", ", ", " }")
}

------------------------------------------------------------------------------------------
YUI
------------------------------------------------------------------------------------------
package net.liftweb.http.js.yui
// imports ...

object YUIArtifacts extends JSArtifacts {

def toggle(id: String) = new JsExp {
def toJsCmd = "YAHOO.lift.toggle(this, " + id.encJs + ");";
}

def hide(id: String) = new JsExp {
def toJsCmd = "YAHOO.util.Dom.setStyle(" + id.encJs + ",
'display', 'none');"
}

def show(id: String) = new JsExp {
def toJsCmd = "YAHOO.util.Dom.setStyle(" + id.encJs + ",
'display', 'block');"
}

def showAndFocus(id: String) = new JsExp {
def toJsCmd = "YAHOO.util.Dom.setStyle(" + id.encJs + ",
'display', 'block');" +
"setTimeout(function() { document.getElementById(" +
id.encJs + ").focus(); }, 200);"
}

def serialize(id: String) = new JsExp {
def toJsCmd = "YAHOO.util.Connect.setForm(" + id.encJs +", false)"
}

def setHtml(uid: String, content: NodeSeq): JsCmd = new JsCmd {
def toJsCmd = "try{document.getElementById(" + uid.encJs +
").innerHTML = " + fixHtml(uid, content)+";} catch (e) {}"
}

def onLoad(cmd: JsCmd): JsCmd = new JsCmd {
def toJsCmd = "YAHOO.util.Event.onDOMReady(function(){" +
cmd.toJsCmd + "})"
}

def ajax(data: AjaxInfo): String = {
val url = S.encodeURL(S.contextPath + "/" + LiftRules.ajaxPath +
"/")

"url = YAHOO.lift.buildURI(liftAjax.addPageName(" + url.encJs +
") , " + data.data.toJsCmd + ");" +
"YAHOO.util.Connect.asyncRequest(" + data.action.encJs + ", url, "
+ toJson(data) + ");"
}

def comet(data: AjaxInfo): String = {
val url = LiftRules.calcCometPath(LiftRules.cometServer())
"url = YAHOO.lift.buildURI(" + url.toJsCmd + ",
YAHOO.lift.simpleJsonToQS(" + data.data.toJsCmd + "));" +
"YAHOO.util.Connect.asyncRequest(" + data.action.encJs + ", url, "
+ toJson(data) + ");";
}

def jsonStringify(in: JsExp) : JsExp = new JsExp {
def toJsCmd = "YAHOO.lang.JSON.stringify(" + in.toJsCmd + ")"
}

def formToJSON(formId: String):JsExp = new JsExp() {
def toJsCmd = "YAHOO.lift.formToJSON('" + formId + "')";
}

private def toJson(info: AjaxInfo): String =
("timeout : " + info.timeout ::
"cache : " + info.cache ::
"success : function(resp) { res = YAHOO.lift.eval(resp);" +
info.successFunc.map(_ + "(res);").openOr("") + "}" ::
"failure : " + info.failFunc.openOr ("function (arg) {YAHOO.log
('Ajax request failed');}") ::
Nil) mkString("{ ", ", ", " }")

}

As you can see, these implementions for JQuery and YUI are fairly
straightforward, and it seems reasonable to expect that the one for
ExtJS would also involve only a minor amount of tweaking.

In fact, if you've already been using ExtJS in other projects, you may
have some familiarity already with the functions and formats it uses,
so you may be able to make some headway adapting the function
definitions in the two implementations above to work with ExtJS.

- Scott
> ...
>
> read more »

Charles F. Munat

unread,
Aug 7, 2009, 3:37:45 PM8/7/09
to lif...@googlegroups.com
I'm using Ext Js 3.0 in a couple of Lift projects (just as soon as I
finish this damn desktop app -- hopefully by Monday). Would be
interested in anything you're doing re Ext Js.

Chas.

marius d.

unread,
Aug 7, 2009, 4:24:01 PM8/7/09
to Lift
Keep in mind that for YUI to work with lift you also need to include
liftYUI.js

Br's,
Marius
> ...
>
> read more »
Reply all
Reply to author
Forward
0 new messages