Friday, May 21, 2010

Mixin to allow any element to submit a form and trigger an event

I modified the builtin LinkSubmit component to be able to use the same thing on any element.

I changed it to a mixin and did some other minor changes.


@MixinAfter
@IncludeJavaScriptLibrary("AnySubmit.js")
public class AnySubmit {

/**
* The name of the event that will be triggered if this component is the cause of the form submission. The default
* is "selected".
*/
@Parameter(allowNull = false, defaultPrefix = BindingConstants.LITERAL)
private String event = EventConstants.SELECTED;

@Parameter(defaultPrefix = BindingConstants.LITERAL)
private String clientEvent = "change";

/**
* The value that will be made available to event handler method of this component when the form is
* submitted.
*/
@Parameter
private Object context;

/**
* If true (the default), then any notification sent by the component will be deferred until the end of the form
* submission (this is usually desirable).
*/
@Parameter
private boolean defer = true;

@InjectContainer
private ClientElement container;

@Inject
private ComponentResources resources;

@Inject
private RenderSupport renderSupport;

@Environmental
private FormSupport formSupport;

@Environmental
private Heartbeat heartbeat;

@Inject
private Request request;

@SuppressWarnings("serial")
private static class ProcessSubmission implements ComponentAction {

private final String clientId;

public ProcessSubmission(String clientId) {
this.clientId = clientId;
}

public void execute(AnySubmit component) {
component.processSubmission(clientId);
}
}

private void processSubmission(String clientId) {

String hiddenFieldName = clientId + ":hidden";

if (request.getParameter(hiddenFieldName) != null) {
final Object[] contextToPublish = getContext(request, clientId);
Runnable notification = new Runnable() {

public void run() {
resources.triggerEvent(event, contextToPublish, null);
}
};

if (defer)
formSupport.defer(notification);
else
heartbeat.defer(notification);
}
}

private Object[] getContext(Request request, String clientId) {
String contextFieldName = clientId + ":context";
String context = request.getParameter(contextFieldName);
if (context != null) {
return new Object[] { context };
}
String value = request.getParameter(clientId);
if (value != null) {
return new Object[] { value };
}
return null;
}

void beginRender() {
formSupport.store(this, new ProcessSubmission(container.getClientId()));
}

void afterRender(MarkupWriter writer) {
renderSupport.addInit("anySubmit", formSupport.getClientId(), container.getClientId(), clientEvent);
if (context != null) {
writer.element("input", "type", "hidden", "value", context, "name", container.getClientId() + ":context");
writer.end();
}
}
}






Tapestry.AnySubmit = Class.create({

initialize: function(formId, clientId, event)
{
this.form = $(formId);
this.element = $(clientId);

this.element.observe(event, this.onEvent.bindAsEventListener(this));
},

createHidden : function()
{
var hidden = new Element("input", { "type":"hidden",
"name": this.element.id + ":hidden",
"value": this.element.id});

this.element.insert({after:hidden});
},

onEvent : function(event)
{
Event.stop(event);

var onsubmit = this.form.onsubmit;

if (onsubmit == undefined || onsubmit.call(window.document, event))
{
this.createHidden();
this.form.submit();
}

return false;
}
});

Tapestry.Initializer.anySubmit = function(formId, clientId, event)
{
new Tapestry.AnySubmit(formId, clientId, event);
}