HTML5 Button Component for tapetry 5.4.x

"Luca Arzeni" <[email protected]> Wed, 11 Sep 2019 13:45:44 +0200
Newsgroups gmane.comp.java.tapestry.user
Message-ID <trinity-57668d1b-57ea-4fbb-a89e-74bdc4d952b5-1568202344422@3c-app-mailcom-lxa05>
Hi there,
I googled a little around, but I was not able to find a tapestry component that generates a button.

Here you can find a first attempt to create such component.
It was shameless copied from the Submit component already present in tapestry.

I would be happy if someone more expert than me could revise it and add to the core components.

Regards,
larzeni

package org.apache.tapestry5.corelib.components;

import org.apache.tapestry5.BindingConstants;
import org.apache.tapestry5.ClientElement;
import org.apache.tapestry5.ComponentAction;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.EventConstants;
import org.apache.tapestry5.MarkupWriter;
import org.apache.tapestry5.TrackableComponentEventCallback;
import org.apache.tapestry5.annotations.Environmental;
import org.apache.tapestry5.annotations.Events;
import org.apache.tapestry5.annotations.Import;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.annotations.SupportsInformalParameters;
import org.apache.tapestry5.corelib.SubmitMode;
import org.apache.tapestry5.corelib.components.Form;
import org.apache.tapestry5.corelib.components.Loop;
import org.apache.tapestry5.internal.util.Holder;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.internal.util.InternalUtils;
import org.apache.tapestry5.json.JSONArray;
import org.apache.tapestry5.services.FormSupport;
import org.apache.tapestry5.services.Heartbeat;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.javascript.JavaScriptSupport;

/**
 * Corresponds to &lt;input type="submit"&gt; or &lt;input type="image"&gt;, a client-side element that can force the
 * enclosing form to submit. The submit responsible for the form submission will post a notification that allows the
 * application to know that it was the responsible entity. The notification is named
 * {@linkplain EventConstants#SELECTED selected}, by default, and has no context.
 *
 * @tapestrydoc
 */
@SupportsInformalParameters
@Events(EventConstants.SELECTED + " by default, may be overridden")
@Import(module="t5/core/forms")
public class Html5Button implements ClientElement {

	/**
	 * 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). In general, this can be left as the default except when the Submit
	 * component is rendering inside a {@link Loop}, in which case defer should be bound to false (otherwise, the
	 * event context will always be the final value of the Loop).
	 */
	@Parameter
	private boolean defer = true;

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

	/**
	 * If true, then the field will render out with a disabled attribute
	 * (to turn off client-side behavior). When the form is submitted, the
	 * bound value is evaluated again and, if true, the field's value is
	 * ignored (not even validated) and the component's events are not fired.
	 */
	@Parameter("false")
	private boolean disabled;

	@Parameter(defaultPrefix = BindingConstants.LITERAL)
	private String type;

	/**
	 * The list of values that will be made available to event handler method of this component when the form is
	 * submitted.
	 *
	 * @since 5.1.0.0
	 */
	@Parameter
	private Object[] context;

	/**
	 * Defines the mode, or client-side behavior, for the submit. The default is {@link SubmitMode#NORMAL}; clicking the
	 * button submits the form with validation. {@link SubmitMode#CANCEL} indicates the form should be submitted as a cancel,
	 * with no client-side validation. {@link SubmitMode#UNCONDITIONAL} bypasses client-side validation, but does not indicate
	 * that the form was cancelled.
	 *
	 * @see EventConstants#CANCELED
	 * @since 5.2.0
	 */
	@Parameter(allowNull = false, defaultPrefix = BindingConstants.LITERAL)
	private SubmitMode mode = SubmitMode.NORMAL;

	/**
	 * CSS class for the element.
	 *
	 * @since 5.4
	 */
	@Parameter(name = "class", defaultPrefix = BindingConstants.LITERAL, value = "message:private-core-components.submit.class")
	private String cssClass;

	@Environmental
	private FormSupport formSupport;

	@Environmental
	private Heartbeat heartbeat;

	@Inject
	private ComponentResources resources;

	@Inject
	private Request request;

	@Inject
	private JavaScriptSupport javascriptSupport;

	@SuppressWarnings("rawtypes")
	@Environmental
	private TrackableComponentEventCallback eventCallback;

	private String clientId;

	@SuppressWarnings("serial")
	private static class ProcessSubmission implements ComponentAction<Html5Button>
	{
		private final String clientId, elementName;

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

		public void execute(Html5Button component)
		{
			component.processSubmission(clientId, elementName);
		}
	}

	public Html5Button()
	{
	}

	Html5Button(Request request)
	{
		this.request = request;
	}

	void beginRender(MarkupWriter writer)
	{
		clientId = javascriptSupport.allocateClientId(resources);

		String l_name = formSupport.allocateControlName(resources.getId());

		// Save the element, to see if an id is later requested.

		writer.element("button",

						"type", type,

						"name", l_name,

						"data-submit-mode", mode.name().toLowerCase(),

						"class", cssClass,

						"id", clientId);

		if (disabled)
		{
			writer.attributes("disabled", "disabled");
		}

		formSupport.store(this, new ProcessSubmission(clientId, l_name));

		resources.renderInformalParameters(writer);
	}

	void afterRender(MarkupWriter writer)
	{
		writer.end();
	}

	void processSubmission(String clientId, String elementName)
	{
		if (disabled || !selected(clientId, elementName))
			return;

		// TAP5-1658: copy the context of the current Submit instance so we trigger the event with
		// the correct context later
		final Holder<Object[]> currentContextHolder = Holder.create();
		if (context != null)
		{
			Object[] currentContext = new Object[context.length];
			System.arraycopy(context, 0, currentContext, 0, context.length);
			currentContextHolder.put(currentContext);
		}

		Runnable sendNotification = new Runnable()
		{
			public void run()
			{
				// TAP5-1024: allow for navigation result from the event callback
				resources.triggerEvent(event, currentContextHolder.get(), eventCallback);
			}
		};

		// When not deferred, don't wait, fire the event now (actually, at the end of the current
		// heartbeat). This is most likely because the Submit is inside a Loop and some contextual
		// information will change if we defer.

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

	private boolean selected(String clientId, String elementName)
	{
		// Case #1: via JavaScript, the client id is passed up.

		String raw = request.getParameter(Form.SUBMITTING_ELEMENT_ID);

		if (InternalUtils.isNonBlank(raw) &&
						new JSONArray(raw).getString(0).equals(clientId))
		{
			return true;
		}

		String name = elementName;

		String value = request.getParameter(name);

		return value != null;
	}

	/**
	 * Returns the component's client id. This must be called after the component has rendered.
	 *
	 * @return client id for the component
	 */
	public String getClientId()
	{
		return clientId;
	}
}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]