Pages

Showing posts with label Tapestry 5. Show all posts
Showing posts with label Tapestry 5. Show all posts

Thursday, November 4, 2010

Tapestr 5 production mode

I think i made very stupid mistake. When i developed my apps i usually set Tapestry 5 production mode to false in my app module using this line :

        configuration.add(SymbolConstants.PRODUCTION_MODE, "false");

since its programmable, this configuration is carried over when i deploy my apps to the server. What I've should have done is I need to override this configuration in the server so my apps would run on production mode. Adding this context-param in my web.xml hopefully would do the job.


<context-param>
<param-name>tapestry.production-mode</param-name>
<param-value>false</param-value>
</context-param>

It gives a little time for me in facing my server memory problem that has been maxed out.

Thursday, September 2, 2010

Tapestry 5 and Interface

As you see from this Tapestry FAQ, I just realized that my bad habit has cost me some of the advantaged of using Tapestry. I often lazy in creating an Interface when binding a Tapestry Service. It turns out that if you don't give an Interface for the service, Tapestry will create the implementation immediately after injection. While I was expecting it will be instantiated only after the first method of the service is invoked.

Well, i guess its time to dig up some old code :)

Friday, August 13, 2010

Android and T5 Assets Lesson of the month :)

Okay, this is from few days back, just have the time to write it.

I'm serving an android installer (.apk) in my Tapestry 5 website. I used asset to do this, its not architectural choice, but its the only thing i knew at the moment. The problem with using my regular approach, form based/Action link. Is that they generate very particular URL. Some android browser requires that the URL Consist of <url>/<filename>.apk so it will be treated as an installer. Thus why the Asset approach.

Couple of days a go. A friend of mine contacted me that he wasn't able to download my apps through his android phone. He keep getting error message about the installer. After couple of messages, i found out the size of the downloaded apps is not the same with the file on the server. And no matter how many times he refresh it he keeps getting the same file. Even when he tried to download from his laptop. He's using his office Wifi, so I asked him if to use GPRS/3G. Seems to fix the problem.

But since a bit tech savvy, he tried the same method using android emulator. Then he send the error message to me. Its about some error chunk size bla.. bla.. proxy bla.. bla.. Turn out that android still having problem with password protected wifi connection. Lesson No 1.

That problem fixed or at least I know about the root cause, I'm interested in finding out the other one. Why he keep receiving the same corrupted installer though he refresh it many times. So finally i have a proper reading about T5 Assets documentation :)

The things you need to know about Assets, since its an asset it have a very specific behavior.Asset is a static content, usually its a static image or css. So it is assumed that Asset rarely changed and expected to exist in deploy time (not dynamic). So if the .apk file didn't exist when the page is rendered, it will show an error page instead the page you want to access. This is happened although the link is not clicked yet.

Another thing about the Asset, is that is have a very long expiration time. The benefit is that the asset will be cached by the browser (and maybe the proxy server ?) for a long time. Giving some performance boost and reduce load for the server. But this also what makes my friend keep receiving the same corrupted installer. That's Lesson number 2, 3 about T5 Asset.

Since I've got time, I find another way to handle this scenario. Turn out this scenario could be handled pretty easy using T5 event link :) Well, learning bit by bit everyday is not a sin right :D

Friday, May 28, 2010

Tapestry 5 grid paging with hibernate

This is something that i expected to exist in tapestry. Since i "know" T5 from the first release, back then this class isn't available yet. So back then i created my own paging method with 2 additional action link above Tapestry Grid. 

But now its so easy. If you want to create paging for your grid, you just have to return the HibernateDataSource with the session and your class for your grid source parameter. Like this : 

return new HibernateGridDataSource(session, OrderForm.class);

Its something very simple. But when you're in the middle of something, sometimes its too easy to overcomplicate stuff (Not thinking straight, i previously implements my own GridDataSource for my hibernate class). Everything work out of the box (paging, sorting,etc)

Also the grid has a reset() method for reseting the Sort Ordering.


update : 


i found the easiest way to add default column sort for now is by implementing this method. 


@Component(id="orderGrid")
private Grid grid;


@SetupRender
void setDefaultSortOrder() {
if(grid.getSortModel().getSortConstraints().isEmpty()) {
// call it twice to make it desc ordered
grid.getSortModel().updateSort("orderDate");
grid.getSortModel().updateSort("orderDate");
}
} 


don't forget to reset the sort order to see the result.

Wednesday, May 26, 2010

I learn something new today :)

Thanks to geoff jumpstart, I finally learn how to protect tapestry page in a more 'classier' way. I've been meaning to learn this, but only able to do it now. The guide is here. The concept is very similar with me, only the the implementation differs.  While i'm using basic method of extending base page by their role. Jumpstart uses annotation, way cool :D

So now i'm trying to implement it using annotation. Its very straight forward actually, since the concept is very familiar. Even the autologin concept is very familiar. But of course he's doing it far better and classier than me and i think i will change it too :)

Jumpstart uses system properties to set the autologin properties, while I implement in the code. Since the system properties wouldn't be available when i deploy my apps to the server. I don't have to worry about forgetting to reset the autologin properties when i want to deploy my apps to the server.

To set the system properties in eclipse wtp, you just

  • go to the server window > right click > open 
  • in the bottom of general information > click open launch configuration
  • select the arguments tab, and in the vm argument box add
  • -Dmyapp.autologin="true"
  • restart your server
Cool and Classy :D

~FD

Saturday, May 8, 2010

The mystifying URL Encoding

I just found out about this things. I never really care about the difference between URL and URI. Whatever it is I just figured it will point to a resource somewhere in the internet. For all i know they can be used interchangeably, so far.

It turn out, when you're trying to generate your own URL programmatic-ally , it matters. One thing to know, that it encode space differently. URL will encode space into + as demand by html 4.0 specification. While with URI you can get the usual %20 character.

And what more annoying is this fact.  That you can't simply use URLEncoder to encode and decode URL, but you need to create a URI then execute the toURL() method.

Tapestry URL


Now something specific about tapestry. Since I'm creating an android client that will talk with tapestry server. I need to generate my own URL. In this URL fiasco it turn out that tapestry has its own URL Encoding policy. You can see the change in this jira. Tapestry substitute the '%' character with '$00'.

Luckily the you can get tapestry URLEncoder implementation from tapestry code. So i just use that class in my android app. And one problem solved.

Oh another thing i found out. Tapestry page isn't debug able with eclipse WTP. But the services are.

Cheers

~FD

Tuesday, April 20, 2010

Tapestry 5 and Quartz

Using Quartz with tapestry is pretty easy , thanks to the ChenilleKit project. By following the guide in the chenillekit-quartz project you can use quartz to schedule jobs in your application. But in my case, i need to modify the example a little to make it work.

in the Bundle class :

    private void createBundle() {
        try {
            trigger =  new CronTrigger("myCronTrigger", "CronTriggerGroup", "0 42 7/1 ? * *");
        } catch (ParseException e) {
            e.printStackTrace();
        }
       
        jobDetail = new JobDetail("myJob", null, CrawlingJob.class);
        jobDetail.getJobDataMap().put("crawlingJob", crawlingJob);
        jobDetail.getJobDataMap().put("crawler", crawler);
    }

I'm adding the job (crawlingJob) and the actual services (crawler) into the job data map. So in the Job class :

    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.out.println("Execute job Scheduled Crawler at : " + new Date());
       
        Crawler crawler = (Crawler) context.getJobDetail().getJobDataMap().get("crawler");
        crawler.updateData(CrawlAjax.URL);       
    }
we only need to extract the service class from the context and then execute the real method. As simple as that. And the AFAIK the minimum library you need to include to make it work is :
  • chenillekit-quartz-1.0.2.jar
  • commons-codec-1.4.jar
  • commons-collections-3.1.jar
  • quartz-all-1.6.5.jar

Monday, April 19, 2010

Using block to invoke client Javascript code in Tapestry 5

In my previous post  I've been able to create a self updating tapestry zone.But its only halfway of what i want to do. Although I already give 2 button to start and stop the timer, i want it to be more automatic.Simply said, the server need to send a response that will trigger the stopTimer js function when the crawling process is finished.

To do this I'm using 2 block. The first block contain the regular content that will be returned from the server. While the second block contains the content and an embedded javascript that will invoke the stopTimer function in the client browser. This is the changes I made to my previous post.

The Code

Template Code :
        <t:zone t:id="infoZone" t:update="show">   
        </t:zone><br/>
       
        <t:block id="info">
            Updating Message ....
            <p t:type="OutputRaw" t:value="${message}">
                Text Output
            </p>
        </t:block>

        <t:block id="infoWithScript">
            Update Stopped
            <p t:type="OutputRaw" t:value="${message}">
                Text Output
            </p>
            <script type="text/javascript">
                stopTimer();
            </script>
        </t:block>

    <a t:type="actionlink" t:id="crawl" href="#">Start Crawl</a> &nbsp;

In the template code I'm adding the two block named "info" and "infoWithScript". The block will not be rendered by default. And also I added another actionlink that will change the state in the server and start the crawling process.

Changes in java code :
    @Inject
    private Block _info;
   
    @Inject
    private Block _infoWithScript;

    Object onActionFromRefreshZone() {
        if(crawler.getCrawlStatus() == Crawler.CRAWL_STARTED) {
            crawler.setCrawlStatus(Crawler.CRAWL_CRAWLING);
            crawler.updateData(URL);
        }
       
        if(crawler.getCrawlStatus() == Crawler.CRAWL_END) {
            crawler.setCrawlStatus(Crawler.CRAWL_IDLE);
            return _infoWithScript;
        }
        return _info;
    }

    void onActionFromCrawl() {
        crawler.setCrawlStatus(Crawler.CRAWL_STARTED);
    }

    public String getMessage() {
        return ProgressNotifier.getMessage();
    }

In the java code we add the two block that will be return by onActionFromRefrezhZone. This method will check if the crawl status in our services class is changed to started, then it will execute update data (the method that will invoke the real process and add messages to the ProgressNotifier).

At the end of the updateData method, the crawl status will be set to CRAWL_END. So the onActionFromRefreshZone will return the block that contain the embedded js to turn off the timer. And return the status to the original CRAWL_IDLE state.

Nothing changes in the client js script.

~FD

Friday, April 16, 2010

Ajax Zone with Interval in Tapestry 5

I'm planning to create a zone that will keep requesting data from the server at certain interval. Seems simple enough to do in Tapestry 5 right ?

But because of my lack of understanding about Ajax and Javascript concept, I've venture to many place to make it. ZoneUpdate, Progressive Display is a few things that i look a bit deep. Trying to understand their inner work, just to find out in which part could I add this simple code.

After a day struggling, i found out that my original idea work just fine. Yeah when its about web, ajax and javascript, I SUCKS ! BIG TIME !

So here's the code to make it work, so no other newbie would get lost like I Do.


The Code

I'm using the sample code from Zone Component in Tapestry 5.
Here's the template page :

<body>
<h1> Crawl Ajax </h1>
<h2> Timer Zone </h2>
   
    <div style="margin-left: 50px">
        <t:zone t:id="time2zone">
            time2:  ${time2}
        </t:zone><br/>

        <a t:type="actionlink" t:id="refreshZone" href="#"
            t:zone="time2zone">Refresh time2 </a>
            <br/><br/>

    </div>
       
    <input type="button" onclick="startTimer()" value="Start Timer" /> <br/>   
    <input type="button" onclick="stopTimer()" value="Stop Timer" /> <br/>   

</body>

Here's the java class code :

    @InjectComponent
    private Zone _time2Zone;

    // The code
   
    void onActionFromRefreshPage() {
        // Nothing to do - the page will call getTime1() and getTime2() as it renders.
    }

    // Isn't called if the link is clicked before the DOM is fully loaded. See
    // https://issues.apache.org/jira/browse/TAP5-1 .
    Object onActionFromRefreshZone() {
        // Here we can do whatever updates we want, then return the content we want rendered.
        return _time2Zone.getBody();
    }

    public Date getTime2() {
        return new Date();
    }
   
    Object onChangeOfTimerZone() {
        return _time2Zone.getBody();
    }

And finally, Here's the Java Script code :

<script type="text/javascript">

var timerId;
var linkId='refreshZone';

function updateMyZone() {
    alert("Update Zone!" + linkId);
    var actionLink = $(linkId);
    Tapestry.findZoneManager( actionLink ).updateFromURL( actionLink.href );
}

function startTimer() {
    alert('yohoo');
    timerId = window.setInterval('updateMyZone()', 2000);
}

function stopTimer() {
    clearInterval (timerId);
}
</script>

As you can see i only add 2 input button to start and stop the timer. The button will invoke the javascript function that uses setInterval that will invoke updateMyZone every 2 second. The updateMyZone will emulate the actionLink behaviour to update the zone.

Bleah its 10+ line of code and it took me the whole day. *still angry and ashamed with my self*

~FD

It happened again !

Okay, some reminder for the future. I don't know how many simple tapestry project I've made, but definitely more than a few. Yet i keep falling into the same hole. I keep missing some minor detail when creating a new project. And this time I'm facing a weird problem.

My project run fine in my development environment (eclipse WTP, tomcat, windows). But when ever i deploy it to my linux server. The page didn't show , it keep getting exception :

Page xxx did not generate any markup when rendered. This could be because its template file could not be located, or because a render phase method in the page prevented rendering.
 First I thought it was Case Sensitive problem. The name of the template (.tml) didn't match up to the class page. Although tapestry is case insensitive in many other things unfortunately this is something that T5 couldn't control. To bad that wasn't the problem :(

And then when i try to rebuild my project, clean, rebuild the class, and refreshing the project tree on eclipse explorer. You need to refresh the tree to make sure new/old/deleted file in the project will be the same with the wtp tomcat temp folder. The temp folder usually located in :
<your_workspace>/.metadata/.plugins/org.eclipse.wst.server.core/tmpX
 After rebuilding I found out that my local project also has the same problem, so it isn't about case sensitive. It turn out the source of my problem is this :

<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
while the correct one is :

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
Tapestry 5 could be very strict about this stuff, and no IDE support for Tapestry 5 certainly didn't help :( There are times when i build the html page from scratch just to test simple case, i forgot to ade the xmlns and Tapestry would just sprout some error.

It's a great framework no doubt. Years in front of its competitor. But it has its own perks *meh* Hope this can be a gentle reminder for me in the future.

~FD

Saturday, January 16, 2010

Javascript in Tapestry5

Okay, lets start from the beginning.

This is the basic thing that i need to know :
1. how to transfer data between tapestry class and my javascript library
a. from javascript to tapestry page class
b. from page class to javascritp

2. How to invoke
a. javascript from tapestry class
b. tapestry class from javascript.

well so far the answers is :
1a. The easiest way was to create a hidden variable that could be accessed from the class.
1b. Is by returning a JSON object that being invoked by javascript. Something more or less like this,

public JSONArray getArea() {
System.out.println("=== Getting area in javascript ");

List areaMarkers = areaEngine.getAllAreaMarkers();
return jsonProcessor.marshal(areaMarkers);

}


Well for the other two is blank for me :(, I'll update this if i have the answer.

~FD

Sunday, December 20, 2009

Tapestry 5 and Javascript

Ok, I'm not a web designer. The only web framework that I've ever learn was tapestry. So when i try to make something and that something is related to web scripting, javascript or css, I'm lost. But i can't let that happened all the time, that is a necessary skill for me to complete my side project. Because of that reasone I'm spending this long weekend to know javascript in Tapestry 5 a little better.

Since my side project is building something with google maps, i find that there are 3 kind of javascript that i need to handel in my page. First external javascript, my google maps library that reside in another webserver. Second my javascript library, javascript code that will be used in many pages. And the third is the javascript for my current page.

Isn't the first and second type can be consider as one ? Unfortunately no, from the perspective of the page they both are external javascript. But currently there are no way to treat the external javascript from google like javascript library in Tapestry 5.

My Story


Okay, since I'm a bit slow picking up new things. So I created my page the old way. I put everything in the template (.tml) file. Luckily it worked, the map from google shows and I've managed to create some polygon above it. Doing this required me to have :

a link to google maps javascript library
<script src="http://maps.google.com/maps?file=api&v=2&sensor=false&key=xxxxx" type="text/javascript"></script>

my own link
<script type="text/javascript" src="${asset:context:js/mapeditor.js}" charset="utf-8"></script>

and my own javascript for current page
<script type="text/javascript">
// tapestry aware vars
var latLng = ${getArea()};
<!-- //
// used for only initialization and invoke method in js lib,
// while other js lib will be added using addScript command in the java file
// lets keep the template clean
var map = null;
var gmarkers = [];
var other_polygon = [];

function initialize() {
if (GBrowserIsCompatible()) {
map = new GMap2(document.getElementById("map_canvas"));
map.setCenter(new GLatLng(-2.1088986592431254, 117.158203125), 5);
showPolygon(latLng);
}
}

// -->
</script>

This worked !

The Other way
Now I'm trying to do it the right way, in this case the Tapestry 5 way. As i understand you can add external library so it will render in the top of your page by using renderSupport addScriptLink method. But unfortunately this method cannot add the external Javascript from google, so i leave the google javascript library in the template file.

While i've managed to move my library to the java file, so the template would look clean. I understand moving the current page to the render support is also possible, but composing the javascript into a string using StringBuffer is a pain :(. And i don't see the real benefit anyway. Because as long as you keep the page javascript clean, only for initialization, its okay to leave it on the template page.

Anyway one of tapestry strong point is to separate coding and UI design. And sometimes the UI designer uncomfortable when they doesn't know the behaviour of the page. If you move all of the script to the java class. And that would break their design tool WYSWYG and also force them to understand the java class.

PS : I understand one more things writing this blog. Writing code in blogger is pain, because you need to < , > sign to the appropriate mark-up your self.