001// Copyright 2008, 2009, 2010, 2011, 2012 The Apache Software Foundation 002// 003// Licensed under the Apache License, Version 2.0 (the "License"); 004// you may not use this file except in compliance with the License. 005// You may obtain a copy of the License at 006// 007// http://www.apache.org/licenses/LICENSE-2.0 008// 009// Unless required by applicable law or agreed to in writing, software 010// distributed under the License is distributed on an "AS IS" BASIS, 011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012// See the License for the specific language governing permissions and 013// limitations under the License. 014 015package org.apache.tapestry5.corelib.components; 016 017import org.apache.tapestry5.*; 018import org.apache.tapestry5.annotations.*; 019import org.apache.tapestry5.corelib.SubmitMode; 020import org.apache.tapestry5.http.services.Request; 021import org.apache.tapestry5.ioc.annotations.Inject; 022import org.apache.tapestry5.ioc.internal.util.InternalUtils; 023import org.apache.tapestry5.json.JSONArray; 024import org.apache.tapestry5.services.FormSupport; 025import org.apache.tapestry5.services.Heartbeat; 026import org.apache.tapestry5.services.javascript.JavaScriptSupport; 027 028/** 029 * Generates a client-side hyperlink that submits the enclosing form. If the link is clicked in the browser, the 030 * component will trigger an event ({@linkplain EventConstants#SELECTED selected} by default) , just like 031 * {@link Submit}. 032 * 033 * @tapestrydoc 034 */ 035@SupportsInformalParameters 036@Events(EventConstants.SELECTED + " by default, may be overridden") 037@Import(module = "t5/core/forms") 038public class LinkSubmit implements ClientElement 039{ 040 /** 041 * If true, then no link (or accompanying JavaScript) is written (though the body still is). 042 */ 043 @Parameter 044 private boolean disabled; 045 046 /** 047 * The name of the event that will be triggered if this component is the cause of the form submission. The default 048 * is "selected". 049 */ 050 @Parameter(allowNull = false, defaultPrefix = BindingConstants.LITERAL) 051 private String event = EventConstants.SELECTED; 052 053 /** 054 * Defines the mode, or client-side behavior, for the submit. The default is {@link SubmitMode#NORMAL}; clicking the 055 * button submits the form with validation. {@link SubmitMode#CANCEL} indicates the form should be submitted as a cancel, 056 * with no client-side validation. {@link SubmitMode#UNCONDITIONAL} bypasses client-side validation, but does not indicate 057 * that the form was cancelled. 058 * 059 * @see EventConstants#CANCELED 060 * @since 5.2.0 061 */ 062 @Parameter(allowNull = false, defaultPrefix = BindingConstants.LITERAL) 063 private SubmitMode mode = SubmitMode.NORMAL; 064 065 /** 066 * If true (the default), then any notification sent by the component will be deferred until the end of the form 067 * submission (this is usually desirable). In general, this can be left as the default except when the LinkSubmit 068 * component is rendering inside a {@link Loop}, in which case defer should be bound to false (otherwise, the 069 * event context will always be the final value of the Loop). 070 */ 071 @Parameter 072 private boolean defer = true; 073 074 /** 075 * The list of values that will be made available to event handler method of this component when the form is 076 * submitted. 077 * 078 * @since 5.2.0 079 */ 080 @Parameter 081 private Object[] context; 082 083 @Inject 084 private ComponentResources resources; 085 086 @Inject 087 private JavaScriptSupport javascriptSupport; 088 089 @Environmental 090 private FormSupport formSupport; 091 092 @Environmental 093 private Heartbeat heartbeat; 094 095 @Inject 096 private Request request; 097 098 @SuppressWarnings("unchecked") 099 @Environmental 100 private TrackableComponentEventCallback eventCallback; 101 102 private String clientId; 103 104 private static class ProcessSubmission implements ComponentAction<LinkSubmit> 105 { 106 private final String clientId; 107 108 public ProcessSubmission(String clientId) 109 { 110 this.clientId = clientId; 111 } 112 113 public void execute(LinkSubmit component) 114 { 115 component.processSubmission(clientId); 116 } 117 } 118 119 private void processSubmission(String clientId) 120 { 121 this.clientId = clientId; 122 123 String raw = request.getParameter(Form.SUBMITTING_ELEMENT_ID); 124 125 if (InternalUtils.isNonBlank(raw) && new JSONArray(raw).getString(0).equals(clientId)) 126 { 127 Runnable notification = new Runnable() 128 { 129 public void run() 130 { 131 resources.triggerEvent(event, context, eventCallback); 132 } 133 }; 134 135 if (defer) 136 formSupport.defer(notification); 137 else 138 heartbeat.defer(notification); 139 } 140 } 141 142 void beginRender(MarkupWriter writer) 143 { 144 if (!disabled) 145 { 146 clientId = javascriptSupport.allocateClientId(resources); 147 148 formSupport.store(this, new ProcessSubmission(clientId)); 149 150 writer.element("a", 151 152 "href", "#", 153 154 "data-submit-mode", mode.name().toLowerCase(), 155 156 "id", clientId); 157 158 resources.renderInformalParameters(writer); 159 } 160 } 161 162 void afterRender(MarkupWriter writer) 163 { 164 if (!disabled) 165 { 166 writer.end(); 167 } 168 } 169 170 public String getClientId() 171 { 172 return clientId; 173 } 174}