Contents
Apache Click is an excellent lightweight web application framework, especially for primarily stateless web projects. For the developer, it pushes a great deal of HTML and Javasript coding into Java, which I believe makes the resulting code more maintainable and reusable.
I started working with Click about three weeks ago. I mention this to make clear I'm not a Click expert, so there may be a better way to accomplish what I'm trying to do.
I wanted to have a single page element react to an action, and have this reaction affect one or more other page elements. For example, user clicks a link says "Close all Panels" and every open panel on the current page would disappear. Of course, in plain old HTML/Javascript, this is quite easy. But, to stay consistent with Apache Click, I wanted to push as much of this setup into Java itself.
Here's what I came up with. Note I'm not using it currently in any real projects (only on demonstrations). Also note, the solution as written is dependent on jquery to trigger the DOM events, but it could be rewritten without.
First, a custom
ActionResult
class is used to trigger an event on the body element of the page. In Click,
ActionResult
s are generally used to return partial messages back to the page. This subtype,
DomEventActionResult
, will just trigger an event and nothing more.
import org.apache.click.ActionResult; public class DomEventActionResult extends ActionResult { public DomEventActionResult() { super(); setContentType(JAVASCRIPT); } public DomEventActionResult(String name) { super(name, JAVASCRIPT); this.setContent("jQuery('body').trigger('" + name + "');"); } public void setEvent(String name) { setContentType(JAVASCRIPT); this.setContent("jQuery('body').trigger('" + name + "');"); } }Next, a behavior is made that will take action based on this dom event. I created a simple
JavascriptOnDomEventBehavior
that will simply execute some given javascript whenever the event is heard. It overrides the default
preRenderHeadElements
to bind a listener for a specific event type. When the listener hears its event type, it will execute whatever
javascript it has been given. It's not terribly helpful in itelf, but serves as a base for more interesting
behaviors.
import org.apache.click.Behavior; import org.apache.click.Control; import org.apache.click.element.JsScript; public class JavascriptOnDomEventBehavior implements Behavior { /** An event type to listen for */ protected String event_type = "ajax_event"; /** The javascript to be executed when event is heard */ protected String js = "void();"; public JavascriptOnDomEventBehavior(String event_type) { super(); this.event_type = event_type; } public JavascriptOnDomEventBehavior(String event_type, String js) { super(); this.event_type = event_type; this.js = js; } @Override public void preRenderHeadElements(Control source) { String jsText = "$('body').bind('" + this.event_type + "',function(event){" +this.js + "});"; JsScript jsScript = new JsScript(jsText); jsScript.setExecuteOnDomReady(true); source.getHeadElements().add(jsScript); } }So if you tie a
DomEventActionResult
with one or more
JavascriptOnDomEventBehavior
with the same event type, then you can execute arbitrary javascript when your Click actions are made. An example
fragment (a more complete one will be listed later) of a Click Page doing this is:
addControl(linkA); addControl(panelA); addControl(panelB); linkA.setId("turn-off-link-id"); linkA.addBehavior(new DefaultAjaxBehavior() { public ActionResult onAction(Control source) { DomEventActionResult as = new DomEventActionResult(turnOffPanelEventName); return as; } }); panelA.addBehavior(new JavascriptOnDomEventBehavior(turnOffPanelEventName, "$('#"+panelA.getId()+"').hide();")); panelB.addBehavior(new JavascriptOnDomEventBehavior(turnOffPanelEventName, "$('#"+panelB.getId()+"').hide();"));And you'd have a link, when pressed, would cause two panels to disappear. But it's not very elegent, because we have to write the target ID for each behavior. It would be much nicer if the javascript was encapsulated in the behavior itself. See next section.
JavascriptOnDomEventBehavior
to provide some more useful behaviors. Here is a behavior that will toggle (or just hide/show) an element when
an event is heard.
import java.util.Map; import org.apache.click.Control; import org.apache.click.element.JsScript; import org.apache.jasper.compiler.JavacErrorDetail; import org.json.simple.JSONValue; public class ToggleVisibilityOnEventBehavior extends JavascriptOnDomEventBehavior { /** The type of toggle: toggle, hide, or show */ protected int toggle_type = 0; protected int speed = 500; protected String effect_type = "fade"; protected Map<String , Object> options; public ToggleVisibilityOnEventBehavior(String event_type, String effect_type, int toggle_type) { super(event_type); this.effect_type = effect_type; this.toggle_type = toggle_type; } public ToggleVisibilityOnEventBehavior(String event_type, String effect_type, int toggle_type, int speed) { super(event_type); this.effect_type = effect_type; this.toggle_type = toggle_type; this.speed = speed; } public ToggleVisibilityOnEventBehavior(String event_type, String effect_type, int toggle_type, int speed, Map<String , Object> options) { super(event_type); this.effect_type = effect_type; this.toggle_type = toggle_type; this.speed = speed; this.options = options; } @Override public void preRenderHeadElements(Control source) { String id = source.getId(); String fxOptionString = JSONValue.toJSONString(this.options); String func = "toggle"; if (this.toggle_type > 0) func = "show"; if (this.toggle_type < 0) func = "hide"; this.js = "$('#" + id + "')." + func + "('" + this.effect_type + "'," + fxOptionString + "," + this.speed + ");"; super.preRenderHeadElements(source); } } </String></String>Now, in this behavior's
preRenderHeadElements
the source control is specifically targeted , so we can rewrite the example Page above as:
public class AjaxDomEventPage extends StandardBorder { private final ActionLink linkA = new ActionLink("link", "Turn off Panels"); private Panel panelA = new Panel("panelA", "panels/panel-example.htm"); private Panel panelB = new Panel("panelB", "panels/panel-example.htm"); private Panel panelC = new Panel("panelC", "panels/panel-example.htm"); private final String turnOffPanelEventName = "turn_off_panels"; public AjaxDomEventPage() { addControl(linkA); addControl(panelA); addControl(panelB); addControl(panelC); linkA.setId("turn-off-link-id"); linkA.addBehavior(new DefaultAjaxBehavior() { public ActionResult onAction(Control source) { DomEventActionResult as = new DomEventActionResult(turnOffPanelEventName); return as; } }); //keep panel A the old way panelA.addBehavior(new JavascriptOnDomEventBehavior(turnOffPanelEventName, "$('#"+panelA.getId()+"').hide();")); //but for panel b and c, create a single toggle behavior and apply it to two different controls MapThe behaviors and action results are tied to the same event name,fxOptions = new HashMap (); ToggleVisibilityOnEventBehavior toggleBehavior = new ToggleVisibilityOnEventBehavior(turnOffPanelEventName, "slide", -1, 5000, fxOptions); panelB.addBehavior(toggleBehavior); panelC.addBehavior(toggleBehavior); } }
turnOffPanelEventName
. A single behavior object
toggleBehavior
is used to make two panels disappear. Of course, other behaviors which do different things can be written. For
completeness, here's the content of panels/panel-example.htm
<div id="$this.id" style="border:solid 1px black; margin: 4px; float:left; width:50px;"> Hi, I'm a panel. <div>and of the velocity template associated with
AjaxDomEventPage
, which initates the original Ajax call just as described in the Apache Click ajax example:
<p>This page demonstrates an ajax request resulting in effect behaviors on multiple panels. Clicking this link ($link) will send an Ajax request to the server. The server will respond, triggering an event, causing each panel to disappear. </p> $panelA $panelB $panelC <script type="text/javascript"> jQuery(document).ready(function() { jQuery("#turn-off-link-id").click(function(event){ makeRequest(); return false; }); }); function makeRequest() { var link = jQuery('#turn-off-link-id'); var extraData = link.attr('id') + '=1'; var url = link.attr('href'); jQuery.ajax( { url: url, dataType:'script', data: extraData } }); } <script>
MultiActionResult
class (among many other useful things). I chose not to use this approach as I wished to keep things abstracted
into Java. The Ajax4Click differs in its approach. To quote their documentation:
Ajax4Click is not a framework or abstraction layer. You will be coding Ajax functions in JavaScript. Some Java developers prefer to code against Java abstraction layers instead of JavaScript itself. Others prefer to write their own JavaScript code and templates to ensure direct control over the underlying technology and to not introduce another black box into their stacks. Ajax4Click is targeted at these developers.If you are one of those developers, then I'd certainly recommend using Ajax4Click over my proposed solution.