Dear Wiki user,

You have subscribed to a wiki page or wiki category on "Tapestry Wiki" for 
change notification.

The following page has been changed by ChrisLewis:
http://wiki.apache.org/tapestry/Tapestry5AndJavaScriptExplained

The comment on the change is:
A thorough explanation of how Tapestry integrates javascript into applications.

New page:
== Introduction ==

!Javascript is a critical element of modern web applications. Historically 
"real" software developers have avoided JS either because they had more 
important things to do, or because they simply didn't want to be bothered with 
front-end details. Well as we know, times have changed and are changing still. 
If you want to have an application that can compete with other modern 
applications, you have to use JS (including AJAX).
Modern frameworks, and perhaps none more aggressively than 
[http://www.rubyonrails.org/ RoR], go a long way in removing the pains of JS 
from developing web applications. Tapestry 5 is no exception and even though it 
is currently in the beta stages, it has come along way in making JS transparent 
to developers (not to mention 3rd party libraries like 
[http://code.google.com/p/tapestry5-components/ tapestry5-components]).

However, there are brave souls who wish to know how it all works. Perhaps 
you're tracing some weird error, developing a sophisticated component with JS, 
or maybe you're just curious. This article is for such people, so we'll have a 
look at how Tapestry 5 elegantly weaves JS into your application by creating a 
mixin. This mixin can be applied to links (that is, !PageLink or !ActionLink 
components) so that the user will be asked to confirm that they would like to 
follow that link, which is ultimately accomplished by a javascript `confirm` 
dialog.

''The full project source is available 
[http://thegodcode.net/tapestry5/jsclarity-project.zip here]''.

== What You Should Already Know ==

This article assumes that you have some basic knowledge of Tapestry 5. You 
don't need intimate knowledge of /how/ it works, just an understanding of how 
to develop applications with it. In addition you should also be acquainted with 
the following concepts:

 * Component 
[http://tapestry.apache.org/tapestry5/tapestry-core/guide/mixins.html mixins].
 * Some javascript knowledge. You should at least have a basic understanding of 
client-side objects and how they are created.
 * Understanding of the [http://json.org/ JSON] format and the 
[http://www.prototypejs.org/ prototype] library would be very helpful, but not 
required. A good starting point for modern JS programming (prototype oriented, 
but still generally useful) is http://www.prototypejs.org/learn.


== Getting Started ==

To start our project we'll use the 
[http://tapestry.apache.org/tapestry5/quickstart/ maven quickstart archetype]. 
Assuming you have maven installed execute the following from a console:

{{{
mvn archetype:create -DarchetypeGroupId=org.apache.tapestry 
-DarchetypeArtifactId=quickstart -DarchetypeVersion=5.0.11 
-DgroupId=net.godcode -DartifactId=jsclarity 
-DpackageName=net.godcode.jsclarity -Dversion=1.0.0-SNAPSHOT
}}}

I use maven 2, eclipse, the maven 2 eclipse plugin, and jetty (5) for my 
development environment. I will make no assumptions about what you use, so when 
we create a class or run the application I will simply say "create this class" 
or "run the application." I will however make note of where certain files 
belong in the context of a maven project.


== Creating the Mixin ==

Let's create our mixin class. Mixin classes live in the `mixins` sub package of 
your application.

''Maven location: src/main/java/net/godcode/jsclarity/mixins/Confirm.java''

{{{
package net.godcode.jsclarity.mixins;

import org.apache.tapestry.ClientElement;
import org.apache.tapestry.PageRenderSupport;
import org.apache.tapestry.TapestryConstants;
import org.apache.tapestry.annotations.AfterRender;
import org.apache.tapestry.annotations.IncludeJavaScriptLibrary;
import org.apache.tapestry.annotations.InjectContainer;
import org.apache.tapestry.annotations.Parameter;
import org.apache.tapestry.ioc.annotations.Inject;

/**
 * A simple mixin for attaching a javascript confirmation box to the onclick
 * event of any component that implements ClientElement.
 * 
 * @author <a href="mailto:[EMAIL PROTECTED]">Chris Lewis</a> Apr 18, 2008
 */
@IncludeJavaScriptLibrary("confirm.js")
public class Confirm {
        
        @Parameter(value = "Are you sure?", defaultPrefix = 
TapestryConstants.LITERAL_BINDING_PREFIX)
        private String message;
        
        @Inject
        private PageRenderSupport renderSupport;
        
        @InjectContainer
        private ClientElement element;
        
        @AfterRender
        public void afterRender() {
                renderSupport.addScript(String.format("new Confirm('%s', 
'%s');",
                        element.getClientId(), this.message));
        }
        
}
}}}

The first thing you may ask is "What does the 
[http://tapestry.apache.org/tapestry5/apidocs/org/apache/tapestry/annotations/IncludeJavaScriptLibrary.html
 @IncludeJavaScriptLibrary] do"? Naturally, it includes a javascript library, 
but we'll have a closer look at it later. For now let's look at the 
`afterRender` method.

{{{
        @AfterRender
        public void afterRender() {
                renderSupport.addScript(String.format("new Confirm('%s', 
'%s');",
                        element.getClientId(), this.message));
        }
}}}

Here we do one thing: insert a little piece of javascript in our page. For now 
don't worry about when/how/where this happens, just understand that this script 
code will be called after the page has properly loaded in the user's browser. 
You'll notice that it's calling a constructor (`new Confirm('%s', '%s');`) to 
create a client-side object of type `Confirm`. This javascript class 
accompanies our mixin and implements the client-side logic to confirm that the 
user would indeed like to proceed with the action.


== Creating the Mixin's Javascript File ==

Now let's create our javascript class to accompany our mixin. Following the 
convention, we'll place this file in the same package as our mixin class, such 
that it becomes a resource on the classpath. This is useful because when we 
pass a relative name to the 
[http://tapestry.apache.org/tapestry5/apidocs/org/apache/tapestry/annotations/IncludeJavaScriptLibrary.html
 @IncludeJavaScriptLibrary], Tapestry will start in the package of the 
annotated class.

''Maven location: src/main/resources/net/godcode/jsclarity/mixins/confirm.js 
(notice this is in `src/main/resources`)''

{{{
// A class that attaches a confirmation box (with logic)  to
// the 'onclick' event of any HTML element.
// @author Chris Lewis Apr 18, 2008 <[EMAIL PROTECTED]>
var Confirm = Class.create();
Confirm.prototype = {
        initialize: function(element, message) {
                this.message = message;
                Event.observe($(element), 'click', 
this.doConfirm.bindAsEventListener(this));
        },
        
        doConfirm: function(e) {
                if(! confirm(this.message))
                        e.stop();
        }
}
}}}

Knowing a little about the prototype library will help you here, but in short, 
this is a javascript class that takes 2 arguments in its constructor: the DOM 
id of the HTML element (`element`) of which we will intercept `onclick` events, 
and the confirmation message (`message`) to display when a click is received.

That's it, we're done writing our mixin. Now let's use it!


== Using our Mixin ==

If you launch the web application and visit the root of the context root 
(`/jsclarity` by default), you'll be brought to the default index page 
(formerly called the Start page). This is a fresh project created via the 
quickstart archetype which created this page for us. All we need is a !PageLink 
or !ActionLink to test our mixin with, and the index page has one. However 
before adding the mixin, launch the project, view the page, and make note of 
the 'Refresh' link. This is a simple !PageLink component that links to this 
same page (hence, 'Refresh'). When you follow the link you'll see the page 
reload and the time update, so we will easily be able to see the effects of 
using our mixin on it. We don't need to mess with any Java code, so let's get 
straight to the template and add our mixin.

''Maven location: src/main/webapp/Index.tml''

{{{
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd";>
    <head>
        <title>jsclarity Start Page</title>
    </head>
    <body>
        <h1>jsclarity Start Page</h1>

        <p> This is the start page for this application, a good place to start 
your modifications.
            Just to prove this is live: </p>

        <p> The current time is: ${currentTime}. </p>


        <p>
            [<a t:type="pagelink" t:mixins="confirm" page="index">Refresh</a>]
        </p>
    </body>
</html>
}}}

Notice the addition of the `t:mixins="confirm"` parameter. Now reload the page 
and click on the link. Did it work? What happens if you click the 'OK' button 
vs the 'Cancel' button?

Suppose you want a different message to be displayed to the user. No problem. 
Remember that `message` parameter in our mixin class?

{{{
        @Parameter(value = "Are you sure?", defaultPrefix = 
TapestryConstants.LITERAL_BINDING_PREFIX)
        private String message;
}}}

This gives you total control over the message. Exploit it in the template by 
adding another parameter to the !PageLink component:

{{{
        <p>
            [<a t:type="pagelink" t:mixins="confirm" t:message="Are you sure 
you want to delete this item?" page="index">Refresh</a>]
        </p>
}}}


== Understanding What's Happening ==

At this point we've created a mixin and paired client-side functionality with 
it by creating a javascript class in an external file. In doing so we've 
learned how to create this pairing, make sure the script file is included in 
the rendered page, and how to create the client-side object from the mixin. But 
how does it all fit together?

Let's start with what we know:

 1. Ultimately a page comes down to HTML and javascript.
 1. Our javascript class is in an external file, so that file must be included.
 1. Our javascript class uses the prototype javascript library, so that must be 
included before we include our javascript file.
 1. Because our javascript class needs the DOM id of an HTML element, that 
element must be loaded before we instantiate an instance of our class.

Now view the source of the rendered page. It will look something like this:

{{{
<html>
    <head><link href="assets/tapestry/default.css" rel="stylesheet" 
type="text/css">
        <title>jsclarity Start Page</title>
    </head>
    <body><script src="assets/scriptaculous/prototype.js" 
type="text/javascript"></script><script 
src="assets/scriptaculous/scriptaculous.js" 
type="text/javascript"></script><script src="assets/scriptaculous/effects.js" 
type="text/javascript"></script><script src="assets/tapestry/tapestry.js" 
type="text/javascript"></script><script 
src="assets/net/godcode/jsclarity/mixins/confirm.js" 
type="text/javascript"></script>
        <h1>jsclarity Start Page</h1>

        <p> This is the start page for this application, a good place to start 
your modifications.
            Just to prove this is live: </p>

        <p> The current time is: Fri Apr 18 21:36:42 CEST 2008. </p>


        <p>
            [<a href="/JSClarity/" id="pagelink">Refresh</a>]
        </p>
    <script type="text/javascript">
<!--
Tapestry.onDOMLoaded(function() {
new Confirm('pagelink', 'Are you sure?');
});
// -->
</script></body>
</html>
}}}

If you scan through it carefully, you'll notice that the prototype library is 
included before our library, and that at the end of the document is our 
javascript initialization script. Also notice that our mixin filled in the 
missing pieces (the DOM id of the link, and the confirmation string):

{{{
<script type="text/javascript">
<!--
Tapestry.onDOMLoaded(function() {
new Confirm('pagelink', 'Are you sure you want to delete this item?');
});
// -->
</script>
}}}

Fantastic! But how did it all happen? Well, it all starts with that annotation 
I said we'd look at later.


== @IncludeJavaScriptLibrary ==

This annotation is a wonderful shortcut. To find where and how Tapestry 
implements the magic behind it you'll need to dig into the T5 source. The 
beauty of course is that you don't need to understand the ''how'' to understand 
what it does.

You can apply this annotation to page, component, or mixin classes. You must 
provide it with either the name of a javascript file as a string, or an array 
of files. These strings represent locations in the classpath, which will be 
searched in a relative manner. Whenever you use this annotation, Tapestry also 
adds the prototype and scriptaculous libraries before the files specified in 
the annotation automatically. It's also smart enough not to add the same file 
twice. To top it off, if your files cannot be found Tapestry will fail fast by 
throwing an exception. To me, this is just great. I don't want my application 
to just suck it up and keep going. The assumption made by Tapestry here is the 
same for any injected asset files: that they are an integral part of the 
application's front end and so the breaking of the application is a logical 
consequence when they are missing.


== PageRenderSupport ==

The last missing piece is the 
[http://tapestry.apache.org/tapestry5/apidocs/org/apache/tapestry/PageRenderSupport.html
 PageRenderSupport] service. Look again at the `afterRender` method of the 
mixin class:

{{{
        @AfterRender
        public void afterRender() {
                renderSupport.addScript(String.format("new Confirm('%s', 
'%s');",
                        element.getClientId(), this.message));
        }
}}}

Now look again at the source of the rendered page in your browser:

{{{
<script type="text/javascript">
<!--
Tapestry.onDOMLoaded(function() {
new Confirm('pagelink', 'Are you sure you want to delete this item?');
});
// -->
</script>
}}}

That basically says it all. Calls to `PageRenderSupport#addScript` result in a 
`<script>` block being generated at the foot of your page. If you want to see 
exactly what happens in the javascript then you'll want to look at the 
`tapestry.js` file, where you can find the source of `Tapestry.onDOMLoaded`. 
You could just take my word and know that the function argument it receives is 
called after the document has loaded, effectively associating the contained 
code with the `onload` event of the document. In our case we can know that the 
element to which we are attaching our javascript object will have loaded. If 
multiple calls are made to `PageRenderSupport#addScript`, then the scripts will 
be accumulated and output in a single call to `Tapestry.onDOMLoaded`.

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to