HTML5 Button Component for tapetry 5.4.x

classic Classic list List threaded Threaded
5 messages Options
Reply | Threaded
Open this post in threaded view
|

HTML5 Button Component for tapetry 5.4.x

larzeni
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 <input type="submit"> or <input type="image">, 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: [hidden email]
For additional commands, e-mail: [hidden email]

Reply | Threaded
Open this post in threaded view
|

Re: HTML5 Button Component for tapetry 5.4.x

Chris Poulsen
We usually just style the various Tapestry link components to get buttons
with the desired behavior.

--
Chris

On Wed, Sep 11, 2019 at 1:45 PM Luca Arzeni <[hidden email]> wrote:

> 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: [hidden email]
> For additional commands, e-mail: [hidden email]
>
>
Reply | Threaded
Open this post in threaded view
|

Re: HTML5 Button Component for tapetry 5.4.x

Thiago H de Paula Figueiredo
In reply to this post by larzeni
On Wed, Sep 11, 2019 at 8:45 AM Luca Arzeni <[hidden email]> wrote:

> Hi there,
>

Hello!


> I googled a little around, but I was not able to find a tapestry component
> that generates a button.
>

What's exactly the use case you're thinking here? I cannot remember the
last time I used one and I'm not exactly an HTML expert nor a designer, so
I'm curious what you used it for. :)

For submitting forms, if you have a single button, you don't even need to
use the Submit component. An ordinary <input type="submit"> suffices for
the From component. Submit's main reason to exist is when you have more
than one button, so it triggers an event so you know which one was used.

Welcome to the Tapestry users mailing list!


>
> 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: [hidden email]
> For additional commands, e-mail: [hidden email]
>
>

--
Thiago
Reply | Threaded
Open this post in threaded view
|

Re: HTML5 Button Component for tapetry 5.4.x

larzeni
Hi Thiago,

I was trying to use html5 client validation.
Tapestry docs says that you can enable it in AppModule, adding:

configuration.add(SymbolConstants.ENABLE_HTML5_SUPPORT, "true");

And this works fine, but then, at least in Chromium and Firefox, you need a <input type="submit"/> in the form to trigger it.

You can't use a LinkSubmit (which generates an "<a>" tag), since the LinkSubmit calls only javascript validation, and not the Html5 form validation.

But the <input type="submit"/> is severely limited when it comes to customization, as it cannot contain internal element, so you cannot use bootstrap glyphs to style it, for example. You could hide the <input type="submit"/> and workaround it's restrictions using some js, but this is too ugly to be a "real" solution.

So I need to place a <button type="submit"> inside the form. The button can be styled at your wish using bootstrap, and, as side effect, you can place more than one button inside the form, so I can have a "save" and "save and new", which is one of my requirements.

Thanks for your job,
Luca


> Sent: Thursday, September 12, 2019 at 3:05 AM
> From: "Thiago H. de Paula Figueiredo" <[hidden email]>
> To: "Tapestry users" <[hidden email]>
> Subject: Re: HTML5 Button Component for tapetry 5.4.x
>
> On Wed, Sep 11, 2019 at 8:45 AM Luca Arzeni <[hidden email]> wrote:
>
> > Hi there,
> >
>
> Hello!
>
>
> > I googled a little around, but I was not able to find a tapestry component
> > that generates a button.
> >
>
> What's exactly the use case you're thinking here? I cannot remember the
> last time I used one and I'm not exactly an HTML expert nor a designer, so
> I'm curious what you used it for. :)
>
> For submitting forms, if you have a single button, you don't even need to
> use the Submit component. An ordinary <input type="submit"> suffices for
> the From component. Submit's main reason to exist is when you have more
> than one button, so it triggers an event so you know which one was used.
>
> Welcome to the Tapestry users mailing list!
>
>
> >
> > 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 <input type="submit"> or <input
> > type="image">, 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: [hidden email]
> > For additional commands, e-mail: [hidden email]
> >
> >
>
> --
> Thiago
>

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

Reply | Threaded
Open this post in threaded view
|

Re: HTML5 Button Component for tapetry 5.4.x

larzeni
In reply to this post by Chris Poulsen
Hi Chris,
see the reply to Thiago for my use case.
Thanks,
Luca

> Sent: Wednesday, September 11, 2019 at 2:58 PM
> From: "Chris Poulsen" <[hidden email]>
> To: "Tapestry users" <[hidden email]>
> Subject: Re: HTML5 Button Component for tapetry 5.4.x
>
> We usually just style the various Tapestry link components to get buttons
> with the desired behavior.
>
> --
> Chris
>
> On Wed, Sep 11, 2019 at 1:45 PM Luca Arzeni <[hidden email]> wrote:
>
> > 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 <input type="submit"> or <input
> > type="image">, 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: [hidden email]
> > For additional commands, e-mail: [hidden email]
> >
> >
>

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