Circle of Confusion

...I once was lost...
                          Limboville 居士

Saturday, March 15, 2008

 

Wicket Flickr à la Ruby On Rails

The Ruby on Rails site has a nice Flickr demo, dress up in Ajax and with spinner busy animation and drop down effect. The whole thing is done with amazingly few lines of code and in only 5 minutes. For comparison sake, I did a Wicket version. It looks and works the same, does Ajax and has the same animation effects. But additionally, it works with Javascript disabled (something not in the RoR version but is made possible automatically with Wicket because all Wicket component degrade gracefully). The thumbnail images are popup links, clicking on it pops up a window showing a larger version of the photo. This is done with a custom component, something that is real easy to do with Wicket.

By comparing the two versions, one can see Wicket has clean separation of logic and presentation: Java for logic and plain HTML for presentation template and the two are fused together by Wicket runtime. Wicket takes care of state management automatically so we can just write plain Java object as page component. Because Wicket templates have no program code but just plain HTML, they can be previewed in browser. By comparison, the RoR template is a mix of HTML and Ruby, it's total babel (well, to none RoR pepole anyway) and cannot be previewed in browser.

Before continuing, let me show a video of how the whole thing come together. It shows template preview in IDE, then how this thing run.

Prerequisite

The Flickrj library is used to do the Flickr part. Download the code and install it. For Maven, this command installs it:
mvn install:install-file -DgroupId=com.aetrion \
-DartifactId=flickr -Dversion=1.0 -Dpackaging=jar \
-Dfile=flickrapi-1.0.jar
Add dependency in pom.xml
<dependency>
<groupId>com.aetrion</groupId>
<artifactId>flickr</artifactId>
<version>1.0</version>
</dependency>

You need a Flickr API key to connect to Flickr. Get it here and replace the string
"xx-get-your-own-key-xx"
in FlickrPage.java with a real key.

For animation effect, the same Scriptaculous Javascript library is used as the RoR version. Download the file from the Scriptaculous site and take the three files: "prototype.js" (in lib dir), "scriptaculous.js", "effects.js" and save them in the webapp folder.

You also need an animated busy spinner.gif file. You can use this: . Save it in the webapp folder.

Now the actual Wicket Flickr: all the files should be in the same package. If you use the Wicket Quickstart maven archetype, put them together with HomePage.java.

First, flickr.css, this file is the same as the RoR version (a couple of rules added for error feedback and thumbnail link decoration):

body {
background-color: #888;
font-family: Lucida Grande;
font-size: 90%;
margin: 25px;
}

form {
margin: 0 0 10px 0;
background-color: #eee;
border: 5px solid #333;
padding: 25px;
}

fieldset {
border: none;
}

#spinner {
float: right;
margin: 5px 15px 0 0;
}

#photos img {
border: 1px solid #000;
width: 75px;
height: 75px;
padding: 2px;
margin: 5px;
}

#photos a {
text-decoration: none;
}

#feedback ul {
list-style-type: square;
background-color: lightPink;
color: red;
padding: 4px 0 4px 20px;
margin: 0 60px 5px 10px;
}

FlickrPage.html

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:wicket="http://wicket.apache.org/" xml:lang="en" lang="en">
<head>
<title>Flickr Demo à la Ruby On Rails</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<link wicket:id="pageCss" href="flickr.css" rel="stylesheet" type="text/css" />
<script language="JavaScript" type="text/javascript" src="prototype.js"></script>
<script language="JavaScript" type="text/javascript" src="scriptaculous.js"></script>
<script language="JavaScript" type="text/javascript" src="effects.js"></script>
</head>
<body>
<form wicket:id="form">
<img id="spinner" src="spinner.gif" style="display:none"/>
<span wicket:id="feedback">Feedback</span>
<fieldset>
<label for='tags'>Tags:</label>
<input type="text" name="tags" size="30" wicket:id="tags"/>
<input type="submit" value="Find" wicket:id="submitButton"/>
</fieldset>

<div id="photos" wicket:id="photosDiv">
<span wicket:id="photoEntry"><span wicket:id="t">Thumbnail</span></span>
<wicket:remove>
<img width="75" height="75" src="http://farm3.static.flickr.com/2243/2328260234_989578afd3_s.jpg"/>
<img width="75" height="75" src="http://farm3.static.flickr.com/2320/2327441807_58d71c6fd5_s.jpg"/>
<img width="75" height="75" src="http://farm4.static.flickr.com/3061/2328064806_634849bfb3_s.jpg"/>
<img width="75" height="75" src="http://farm4.static.flickr.com/3117/2328064892_970016cfda_s.jpg"/>
<img width="75" height="75" src="http://farm3.static.flickr.com/2061/2325163492_18394afbcf_s.jpg"/>
<img width="75" height="75" src="http://farm4.static.flickr.com/3109/2325163548_6cc733bc84_s.jpg"/>
<img width="75" height="75" src="http://farm3.static.flickr.com/2310/2323708341_46216bc9f6_s.jpg"/>
<img width="75" height="75" src="http://farm4.static.flickr.com/3023/2317136058_e87e607532_s.jpg"/>
<img width="75" height="75" src="http://farm4.static.flickr.com/3027/2315420029_2bab43c6a9_s.jpg"/>
<img width="75" height="75" src="http://farm4.static.flickr.com/3287/2310391100_c98d6961c1_s.jpg"/>
<img width="75" height="75" src="http://farm3.static.flickr.com/2121/2304290885_c6d90fbd48_s.jpg"/>
<img width="75" height="75" src="http://farm3.static.flickr.com/2301/2299313605_9dd239dd97_s.jpg"/>
<img width="75" height="75" src="http://farm4.static.flickr.com/3104/2297363917_f39d1fe4f2_s.jpg"/>
</wicket:remove>
</div>
</form>
</body>
</html>

It's just HTML tags with some wicket:id attributes. The part that show a list of photos from Flickr is this:

    <span wicket:id="photoEntry"><span wicket:id="t">Thumbnail</span></span>
At runtime, this will turn into a list of photos. Since the list is filled at runtime, there is no photo thumbnails in the template to preview. In case like this, Wicket has a "wicket:remove" tag for putting placeholder contents in template for design time preview. These contents are stripped when the template is rendered at runtime. I took a bunch of thumbnail url from Flickr and put a list of img thumbnail in between "wicket:remove" for preview. This is a real strength of Wicket: clean previewable HTML, completely unobtrusive to graphic and css designer.

Now open the html template file in a browser and we can see how the page look (here I am previewing inside Eclipse IDE):

Make change to css and html and see the change without have any running code.

Now the Java code: FlickrPage.java:

package com.hddigitalworks;

import org.apache.wicket.AttributeModifier;
import org.apache.wicket.PageParameters;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.form.AjaxFallbackButton;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.markup.html.resources.StyleSheetReference;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.model.Model;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.aetrion.flickr.Flickr;
import com.aetrion.flickr.REST;
import com.aetrion.flickr.photos.Photo;
import com.aetrion.flickr.photos.PhotoList;
import com.aetrion.flickr.photos.PhotosInterface;
import com.aetrion.flickr.photos.SearchParameters;

public class FlickrPage extends WebPage {

private static final long serialVersionUID = 1L;
private static final Logger log = LoggerFactory.getLogger(FlickrPage.class);

private String tags; // flickr search tags, received from form field
WebMarkupContainer photosDiv; // something for Ajax target to add to for update

public FlickrPage(final PageParameters parameters) {
add(new StyleSheetReference("pageCss", getClass(), "flickr.css"));
Form form = new Form("form", new CompoundPropertyModel(this));
add(form);
FeedbackPanel fp = new FeedbackPanel("feedback");
fp.setOutputMarkupPlaceholderTag(true);
fp.setMarkupId("feedback");
form.add(fp);
form.add(new TextField("tags").setRequired(true));
AjaxFallbackButton submitButton = new AjaxFallbackButton("submitButton", form) {
private static final long serialVersionUID = 1L;

@Override protected void onSubmit(AjaxRequestTarget target, Form f) {
renderPhotos(tags);
if (target != null) {
target.addComponent(f.get("feedback")); // clear error feedback if any
target.addComponent(photosDiv); // update our photos display
target.appendJavascript("new Effect.BlindDown('photos', {duration:3});"
+ "Element.hide('spinner');");
}
}

@Override protected void onError(AjaxRequestTarget target, Form f) {
target.addComponent(f.get("feedback")); // show updated error feedback
target.appendJavascript("Element.hide('spinner');");
// target.addComponent(photosDiv); // keep to show existing photo display on error
}
};
// add effects for when submit button onclick
String onClickJavascript = "Element.show('spinner'); new Effect.BlindUp('photos');";
submitButton.add(new AttributeModifier("onclick", true, new Model(onClickJavascript)) {
private static final long serialVersionUID = 1L;
@Override protected String newValue(String oldValue, String newValue) {
return newValue + oldValue;
}
});
form.add(submitButton);
photosDiv = new WebMarkupContainer("photosDiv");
photosDiv.setOutputMarkupId(true);
photosDiv.setMarkupId("photos"); // use our own id instead of let Wicket generate
form.add(photosDiv);
// Initially there is no photo to display
// so add a temporary place holder component to make Wicket happy
photosDiv.add(new WebMarkupContainer("photoEntry").setVisible(false));

}


private void renderPhotos(final String tags) {
IModel list = new LoadableDetachableModel() {
private static final long serialVersionUID = 1L;
@Override protected Object load() {
return searchFlickr(tags, 24, 1);
}
};
ListView photoList = new ListView("photoEntry", list) {
private static final long serialVersionUID = 1L;
@Override protected void populateItem(ListItem item) {
Photo photo = (Photo) item.getModelObject();
item.add(new Thumbnail("t", photo));
}
};
photosDiv.replace(photoList);
}


private PhotoList searchFlickr(String tags, int perPage, int page) {
PhotoList result = null;
try {
Flickr f = new Flickr("xx-get-your-own-key-xx", new REST());
PhotosInterface photos = f.getPhotosInterface();
SearchParameters params = new SearchParameters();
params.setTags(new String[] { tags });
result = photos.search(params, perPage, page); // page start from 1?
} catch(Exception e) {
log.error("Flicker communication error [" + e.getMessage() + "]", e);
throw new RuntimeException(e);
}

return result;

}
}

Below is the Thumbnail image link component. The image is the thumbnail, clicking it show larger photo in a popup window.

Thumbnail.java:

package com.hddigitalworks;

import org.apache.wicket.behavior.SimpleAttributeModifier;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.link.ExternalLink;
import org.apache.wicket.markup.html.link.PopupSettings;
import org.apache.wicket.markup.html.panel.Panel;

import com.aetrion.flickr.photos.Photo;

public class Thumbnail extends Panel {
private static final long serialVersionUID = 1L;
private static PopupSettings popupSettings =
new PopupSettings(PopupSettings.SCROLLBARS | PopupSettings.RESIZABLE)
.setHeight(550)
.setWidth(550);

public Thumbnail(String id, Photo photo) {
super(id);
ExternalLink link = new ExternalLink("link", photo.getMediumUrl());
link.setPopupSettings(popupSettings);
add(link);
WebMarkupContainer img = new WebMarkupContainer("thumbnail");
img.add(new SimpleAttributeModifier("src", photo.getSmallSquareUrl()));
link.add(img);
}
}

Thumbnail.html

<html>
<wicket:panel>
<a wicket:id="link">
<img wicket:id="thumbnail" width="75" height="75" src="http://farm3.static.flickr.com/2025/2330755480_6a00d6005b_s.jpg"/>
</a>
</wicket:panel>
</html>

Again, simple previewable HTML.

This completes the Wicket Flickr demo.

Comments:
You could consider uploading the video to vimeo.com. It supports the quality you need.
 
Post a Comment

Subscribe to Post Comments [Atom]





<< Home

Archives

August 2005   September 2005   October 2005   December 2005   January 2006   February 2006   March 2006   April 2006   December 2006   January 2008   February 2008   March 2008   October 2009  

This page is powered by Blogger. Isn't yours?

Subscribe to Comments [Atom]