Thursday, May 21, 2009

Simple OnEvent mixin

This mixin is heavily based on Chenillekit OnEvent. I found that CK’s mixin had a different and too specific focus to fit my needs. I wanted to write an event mixin that does nothing but trigger an event. This is what I came up with, I believe the result is quite easy to understand, and should work as a nice foundation for other similar components and mixins.

Usage:

<input type=”button” value=”Answer yes” t:type=”any” t:id=”yesButton” t:mixins=”onEvent” t:event=”click” callback=”updateStatus”/>

Object onClickFromYesButton() {

return “Positive”; // Arguments for javascript function “updateStatus”

}

OnEvent.java

@IncludeJavaScriptLibrary("OnEvent.js")
public class OnEvent {
  @Parameter(required = true, defaultPrefix = BindingConstants.LITERAL)
  private String event;
  @Parameter
  private Object[] context;
  @Environmental
  private RenderSupport renderSupport;
  @InjectContainer
  private ClientElement container;
  @Parameter(defaultPrefix = BindingConstants.LITERAL)
  private String callback;
  @Inject
  private ComponentResources componentResources;
  void afterRender() {
    Link link = componentResources.createEventLink(event, context);
    String script = "new OnEvent('%s', '%s', '%s', '%s')";
    renderSupport.addScript(script, container.getClientId(), event, link.toRedirectURI(), callback);
  }
}


OnEvent.js



var OnEvent = Class.create({
    initialize: function(elementId, event, url, callback)
    {
        if (!$(elementId))
            throw(elementId + " doesn't exist!");
        this.element = $(elementId);
        this.callback = callback;
        this.url = url;
        this.element.observe(event, this.eventHandler.bindAsEventListener(this));
    },
    eventHandler: function(event)
    {
        new Ajax.Request(this.url, {
            method: 'get',
			onFailure: function(t)
            {
                alert('Error communication with the server: ' + t.responseText.stripTags());
            },
            onException: function(t, exception)
            {
                alert('Error communication with the server: ' + exception.message);
            },
            onSuccess: function(t)
            {
                if (this.callback)
				{
					var funcToEval = this.callback + "(" + t.responseText + ")";
					eval(funcToEval);
				}
			}.bind(this)
        });
    }
});

Friday, May 15, 2009

Update a zone on any client side event

 

Usage:

<input type=”text” t:type=”textField” value=”myValue” t:mixins=”zoneUpdater” zone=”searchResultZone” event=”performSearch” clientEvent=”onkeyup”/>

Then all you have to do is providing a listener method for the performSearch-event in your component/page class.

There is some room for improvement here in terms of more flexibility on event link context and other things. But it works.

There might be improvements in the Tapestry js library to make it easier to obtain and trigger zones. This code was developed with T 5.0.18.

 

ZoneUpdater.java

@IncludeJavaScriptLibrary("ZoneUpdater.js")
public class ZoneUpdater {
  public static final String PLACEHOLDER = "XXX";
  @Inject
  private ComponentResources resources;
  @Environmental
  private RenderSupport renderSupport;
  @Parameter(defaultPrefix = BindingConstants.LITERAL)
  private String clientEvent;
  @Parameter(defaultPrefix = BindingConstants.LITERAL, required = true)
  private String event;
  @InjectContainer
  private ClientElement element;
  @Parameter
  private Object[] context;
  @Parameter(defaultPrefix = BindingConstants.LITERAL)
  // To enable popups to fire events on this document, enter "document" here.
  private String listeningElement;
  @Parameter(defaultPrefix = BindingConstants.LITERAL, required = true)
  private String zone;
  protected Link createLink(Object[] context) {
    if (context == null) {
      context = new Object[1];
    }
    context = ArrayUtils.add(context, PLACEHOLDER); // To be replaced by javascript
    return resources.createEventLink(event, context);
  }
  void afterRender() {
    String link = createLink(context).toAbsoluteURI();
    String elementId = element.getClientId();
    if (clientEvent == null) {
      clientEvent = event;
    }
    if (listeningElement == null) {
      listeningElement = "$('" + elementId + "')";
    }
    renderSupport.addScript("new ZoneUpdater('%s', %s, '%s', '%s', '%s', '%s')", elementId, listeningElement, clientEvent, link, zone, PLACEHOLDER);
  }
}

 





ZoneUpdater.js





var ZoneUpdater = Class.create();
ZoneUpdater.prototype = {
	initialize: function(zoneElementId, listeningElement, event, link, zone, placeholder) {
		this.zoneElement = $(zoneElementId);
		this.event = event;
		this.link = link;
		this.placeholder = placeholder;		
		$T(this.zoneElement).zoneId = zone;			
		listeningElement.observe(this.event, this.updateZone.bindAsEventListener(this));
	},	
	updateZone: function(event) {
	    var zoneObject = Tapestry.findZoneManager(this.zoneElement);
	    if ( !zoneObject ) return;
	    var param;
	    if (this.zoneElement.value) {
	    	param = this.zoneElement.value;
	    }
	    if (!param) param = ' ';
	    param = this.encodeForUrl(param);
	    var updatedLink = this.link.gsub(this.placeholder, param);
	    zoneObject.updateFromURL(updatedLink);		
	},
	encodeForUrl: function(string) {
		/**
		 * See equanda.js for updated version of this
		 */
		string = string.replace(/\r\n/g,"\n");
	    var res = "";
	    for (var n = 0; n < string.length; n++)
	    {
	        var c = string.charCodeAt( n );
	        if ( '$' == string.charAt( n ) )
	        {
	            res += '$$';
	        }
	        else if ( this.inRange( c, "AZ" ) || this.inRange( c, "az" ) || this.inRange( c, "09" ) || this.inRange( c, ".." ) )
	        {
	            res += string.charAt( n )
	        }
	        else
	        {
	            var tmp = c.toString(16);
	            while ( tmp.length < 4 ) tmp = "0" + tmp;
	            res += '$' + tmp;
	        }
	    }
	    return res;
	},
	inRange: function(code, range) {
		return code >= range.charCodeAt( 0 ) &&  code <= range.charCodeAt( 1 );
	}
}

Wednesday, April 15, 2009

Open a page in a popup window

This mixin can be applied to any element that has an onclick event. It opens the specified page in a new window. There is room for more parameters here for styling the window, and possibly for using other events than onclick as a trigger.

Usage:

<input type=”button” value=”View shopping cart” t:mixins=”popupPageLink” page=”cart/view” context=”cartId”/>

Java:

import org.apache.tapestry5.BindingConstants;
import org.apache.tapestry5.ClientElement;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.Link;
import org.apache.tapestry5.RenderSupport;
import org.apache.tapestry5.annotations.Environmental;
import org.apache.tapestry5.annotations.IncludeJavaScriptLibrary;
import org.apache.tapestry5.annotations.InjectContainer;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.ioc.annotations.Inject;
@IncludeJavaScriptLibrary("PopupPageLink.js")
public class PopupPageLink {
  @Inject
  private ComponentResources resources;
  @Environmental
  private RenderSupport renderSupport;
  @InjectContainer
  private ClientElement container;
  @Parameter(required = true, defaultPrefix = BindingConstants.LITERAL)
  private String page;
  
  @Parameter(defaultPrefix = BindingConstants.LITERAL, value="800")
  private String width;
  
  @Parameter(defaultPrefix = BindingConstants.LITERAL, value="600")
  private String height;
  @Parameter
  private Object[] context;
  void afterRender() {
    Link link = resources.createPageLink(page, true, context);
    renderSupport.addScript("new PopupPageLink('%s', '%s', %s, %s);", container.getClientId(), link, width, height);
  }
}


Javascript:



var PopupPageLink = Class.create();
PopupPageLink.prototype = {
	initialize: function(id, link, width, height) {
		this.element = $(id);
		this.link = link;
		this.width = width;
		this.height = height;
		Event.observe(this.element, 'click', this.onclick.bindAsEventListener(this));
	},
	onclick: function() {
		var	name = 'dialogWindow';
		var win = window.open(this.link,name,'width=' + this.width + ',height=' + this.height + ',resizable=yes,scrollbars=yes,menubar=no,screenX=0,screenY=0,left=0,top=0' );
	    win.focus();
	}
}

Enforce max length on textareas and textfields

This is a mixin that can be used to enforce the maximum content length of any textarea or text input. It uses javascript to chop off any character that exceeds the given maxlength.

Usage:

<textarea t:type="textarea" t:mixins="maxlength" max="100"></textarea>

MaxLength.java

/**
 * Enforces maxlength on a textfield or a textarea
 * 
 * @author Inge
 *
 */
@IncludeJavaScriptLibrary("MaxLength.js")
public class MaxLength {
  
  @InjectContainer
  private ClientElement container;
  
  @Parameter(required=true)
  private int max;
  
  @Parameter("true")
  private boolean displayCounter;
  
  @Environmental
  private RenderSupport renderSupport;
  
  void afterRender(MarkupWriter writer) {
    String id = container.getClientId();
    String counterId = id + "-counter";
    writer.element("div", "id", counterId + "-container");
    if (!displayCounter) {
      writer.attributes("style", "display: none");
    }
    writer.element("input", "type", "text", "id", counterId, "disabled", "disabled", "size", "3");    
    writer.end();
    writer.end();
    renderSupport.addScript("new MaxLength('%s', '%s', %s)", id, counterId, max);
  }
}


 MaxLenght.js





var MaxLength = Class.create();
MaxLength.prototype = {
	initialize: function(id, counterId, max) {
		this.max = max;
		this.element = $(id);
		this.element.observe('keyup', this.keyup.bindAsEventListener(this));
		this.counterElement = $(counterId);
		this.updateCounter();		
	},
	keyup: function(event) {
		if (this.element.value.length > this.max) {
			this.element.value = this.element.value.substring(0, this.max);
		}
		this.updateCounter();
	},
	updateCounter: function() {
		var currentLength = this.element.value.length;
		this.counterElement.value = (this.max - currentLength);
	}
}