I also wanted to add a missing feature, the ability to update a zone simply by calling a javascript function. This is a nice way of "object orienting" different features/sections on a page, by giving different sections their own client API that they can communicate with.
Most important changes:
- Removed support for specifying another listening element than the container
- Cleaner code
- Request parameter instead of encoding parameter into link context.
- Generates a javascript variable to easy zone update through javascript.
I also wanted to add an internal listener that triggers the actual event with context, so the page/component listener doesn't have to inject the Request and do request.getParameter("param"). Didn't succeed so far.
Anyway, here's the modified code. No code formatting this time, but it should be short enough to be readable. Enjoy :)
ZoneUpdater.java
@IncludeJavaScriptLibrary( { "ZoneUpdater.js" })
public class ZoneUpdater {
@Inject
private ComponentResources resources;
@Environmental
private RenderSupport renderSupport;
/**
* The event to listen for on the client. If not specified, zone update can only be triggered manually through calling updateZone on the JS object.
*/
@Parameter(defaultPrefix = BindingConstants.LITERAL)
private String clientEvent;
/**
* The event to listen for in your component class
*/
@Parameter(defaultPrefix = BindingConstants.LITERAL, required = true)
private String event;
@Parameter(defaultPrefix = BindingConstants.LITERAL, value = "default")
private String prefix;
/**
* The element we attach ourselves to
*/
@InjectContainer
private ClientElement element;
@Parameter
private Object[] context;
/**
* The zone to be updated by us.
*/
@Parameter(defaultPrefix = BindingConstants.LITERAL, required = true)
private String zone;
void afterRender() {
String url = resources.createEventLink(event, context).toAbsoluteURI();
String elementId = element.getClientId();
JSONObject spec = new JSONObject();
spec.put("url", url);
spec.put("elementId", elementId);
spec.put("event", clientEvent);
spec.put("zone", zone);
renderSupport.addScript("%sZoneUpdater = new ZoneUpdater(%s)", prefix, spec.toString());
}
}
ZoneUpdater.js
var ZoneUpdater = Class.create({
initialize: function(spec) {
this.element = $(spec.elementId);
this.url = spec.url;
$T(this.element).zoneId = spec.zone;
if (spec.event) {
this.event = spec.event;
this.element.observe(this.event, this.updateZone.bindAsEventListener(this));
}
},
updateZone: function() {
var zoneManager = Tapestry.findZoneManager(this.element);
if ( !zoneManager ) return;
var updatedUrl = this.url;
if (this.element.value) {
var param = this.element.value;
if (param) {
updatedUrl = addParameter('param', param, updatedUrl); // You need to provide your own function for this...
}
}
zoneManager.updateFromURL(updatedUrl);
}
});
Hi Inge,
ReplyDeleteI'm using your original ZoneUpdater implementation, here is my code:
public void onSelectChange(String value) {
System.out.println("AAA");
}
The onSelectChange method gets called successfully, but the problem I'm seeing is that the value (which i'm expecting to be the value of the Select is always null).
I understand Tapestry has implemented something similar to this in the current SNAPSHOT version with the zone parameter on select, but unfortunately our company policy is to not use SNAPSHOTs.
Looking to get this fixed in the next day or so, would appreciate your input.
Thanks,
Ben.
It appears the template was lost, here it is again without the square brackets!
ReplyDeletet:select t:id="selectAddress" t:value="selectedAddressIndex" t:model="addressModel" blankOption="never" t:mixins="zoneUpdater" zone="addressZone" t:event="selectChange" t:clientEvent="change"/
If we use zoneUpdater in a linksubmit component,
ReplyDeletewhat should be the "t:clientEvent", what should be the event handling method signagure in the jave code?
For example
MySubmit
is "submit" the correct event?
What is the method signature for "myEvent"?
thanks,
Sorry the sample code line did not show correctly,
ReplyDeletehere it is:
<t:linksubmit t:id="myId" t:mixins="x/zoneUpdater" zone="myZone" zoneUpdater.event="myEvent" clientEvent="submit">MySubmit</t:linksubmit>
When I use this mixin to my select model, the ajax submit clears all the textfields in my form. To be more specific,the zone encloses the form and the form encloses several textfields and a select with the mixin attached to it. If I type any text into the textfield, it will disappear whenever I make a selection from the drop down menu. Any ideas?
ReplyDeleteThanks
Thank for this ZoneUpdater, it helped me a lot !
ReplyDelete@Alexis
I encountered the same issue. The problem is that Tapestry copy the textfield value in the Java variable only when the form is submitted.
Then, when your zone is updated, the value which is displayed in the textfields is the value contained in Java variables.
There is one easy way to avoid this problem.
On each component (textfield, select, etc.) add a ZoneUpdater mixin which will only update Java value.
Example for a textfield whose value is "text1":
void onText1Updated() {
text1 = request.getParameter("param");
}
The variable takes the new value, and won't be cleared next time you will refresh the zone.
I hope this is clear!
This comment has been removed by the author.
ReplyDeleteThis breaks in Tapestry 5.2, and I'm in the process of trying to fix it. Any chance you can post an update?
ReplyDelete@desikage
ReplyDeleteThe jumpstart project uses an updated version, check it out at http://jumpstart.doublenegative.com.au/jumpstart/examples/javascript/ajaxonevent
Thanks for this great mixin!
ReplyDeleteBut there seems to be an UTF-8 encoding bug. Using this version the mixin replaced all special (non-ASCII) characters with "." or "$".
Regarding to this [http://ecmanaut.blogspot.ch/2006/07/encoding-decoding-utf8-in-javascript.html] I had only to replace one line in ZoneUpdater.js:
updatedUrl = addParameter('param', param, updatedUrl);
to
updatedUrl = addRequestParameter('param', unescape(encodeURIComponent(param)), updatedUrl);
It would be nice if you could update your blog post (and the jumpstart -- if this is possible).
Regards,
René
Hi again, I've had more problems with this mixin. Since the content of the form is sent via GET parameter, there are several difficulties. For example, if you write a '+', it gets replaced with a space ' ', because the '+' (and many other characters, I guess) are not encoded for the url. And also, the maximal length of the input is limited to approximately 1000 tokens (depends on the browser).
ReplyDeleteI don't use this mixin anymore. Instead, I realized the same behaviour with a non-tapestry form, a zone that includes a tapestry-form and some js around. When the user writes text in the non-tapestry form, js copies it to the tapestry-form and clicks on the submit button. The form is ajaxly sent via POST and can be handled by the server in a normal onSuccessFromMyDataSendForm() {} method.
That's probably not as elegant as a mixin would be, but it works for me. And, the most important thing, it all works with common tapestry tools (t:form, t:actionlink, etc) and it sends the data using POST.
Regards,
René
I think, needed to change line from
ReplyDeleteif (this.element.value) {
to
if (this.element.val()) {