001 // Copyright 2008, 2009, 2010, 2011 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 015 package org.apache.tapestry5.corelib.components; 016 017 import org.apache.tapestry5.*; 018 import org.apache.tapestry5.annotations.Environmental; 019 import org.apache.tapestry5.annotations.Events; 020 import org.apache.tapestry5.annotations.Parameter; 021 import org.apache.tapestry5.annotations.SupportsInformalParameters; 022 import org.apache.tapestry5.corelib.SubmitMode; 023 import org.apache.tapestry5.ioc.annotations.Inject; 024 import org.apache.tapestry5.json.JSONArray; 025 import org.apache.tapestry5.json.JSONObject; 026 import org.apache.tapestry5.services.FormSupport; 027 import org.apache.tapestry5.services.Heartbeat; 028 import org.apache.tapestry5.services.Request; 029 import org.apache.tapestry5.services.javascript.InitializationPriority; 030 import org.apache.tapestry5.services.javascript.JavaScriptSupport; 031 032 /** 033 * Generates a client-side hyperlink that submits the enclosing form. If the link is clicked in the browser, the 034 * component will trigger an event ({@linkplain EventConstants#SELECTED selected} by default) , just like {@link Submit} 035 * . 036 * 037 * @tapestrydoc 038 */ 039 @SupportsInformalParameters 040 @Events(EventConstants.SELECTED + " by default, may be overridden") 041 public class LinkSubmit implements ClientElement 042 { 043 /** 044 * If true, then no link (or accompanying JavaScript) is written (though the body still is). 045 */ 046 @Parameter 047 private boolean disabled; 048 049 /** 050 * The name of the event that will be triggered if this component is the cause of the form submission. The default 051 * is "selected". 052 */ 053 @Parameter(allowNull = false, defaultPrefix = BindingConstants.LITERAL) 054 private String event = EventConstants.SELECTED; 055 056 /** 057 * Defines the mode, or client-side behavior, for the submit. The default is {@link SubmitMode#NORMAL}; clicking the 058 * button submits the form with validation. {@link SubmitMode#CANCEL} indicates the client-side validation 059 * should be omitted (though server-side validation still occurs). 060 * 061 * @since 5.2.0 062 */ 063 @Parameter(allowNull = false, defaultPrefix = BindingConstants.LITERAL) 064 private SubmitMode mode = SubmitMode.NORMAL; 065 066 /** 067 * If true (the default), then any notification sent by the component will be deferred until the end of the form 068 * submission (this is usually desirable). In general, this can be left as the default except when the LinkSubmit 069 * component is rendering inside a {@link Loop}, in which case defer should be bound to false (otherwise, the 070 * event context will always be the final value of the Loop). 071 */ 072 @Parameter 073 private boolean defer = true; 074 075 /** 076 * The list of values that will be made available to event handler method of this component when the form is 077 * submitted. 078 * 079 * @since 5.2.0 080 */ 081 @Parameter 082 private Object[] context; 083 084 @Inject 085 private ComponentResources resources; 086 087 @Inject 088 private JavaScriptSupport javascriptSupport; 089 090 @Environmental 091 private FormSupport formSupport; 092 093 @Environmental 094 private Heartbeat heartbeat; 095 096 @Inject 097 private Request request; 098 099 @SuppressWarnings("unchecked") 100 @Environmental 101 private TrackableComponentEventCallback eventCallback; 102 103 private String clientId; 104 105 private static class ProcessSubmission implements ComponentAction<LinkSubmit> 106 { 107 private final String clientId; 108 109 public ProcessSubmission(String clientId) 110 { 111 this.clientId = clientId; 112 } 113 114 public void execute(LinkSubmit component) 115 { 116 component.processSubmission(clientId); 117 } 118 } 119 120 private void processSubmission(String clientId) 121 { 122 this.clientId = clientId; 123 124 String raw = request.getParameter(Form.SUBMITTING_ELEMENT_ID); 125 126 if (raw != null && new JSONArray(raw).getString(0).equals(clientId)) 127 { 128 Runnable notification = new Runnable() 129 { 130 public void run() 131 { 132 resources.triggerEvent(event, context, eventCallback); 133 } 134 }; 135 136 if (defer) 137 formSupport.defer(notification); 138 else 139 heartbeat.defer(notification); 140 } 141 } 142 143 void beginRender(MarkupWriter writer) 144 { 145 if (!disabled) 146 { 147 clientId = javascriptSupport.allocateClientId(resources); 148 149 formSupport.store(this, new ProcessSubmission(clientId)); 150 151 writer.element("span", 152 153 "id", clientId); 154 155 resources.renderInformalParameters(writer); 156 } 157 } 158 159 void afterRender(MarkupWriter writer) 160 { 161 if (!disabled) 162 { 163 writer.end(); 164 165 JSONObject spec = new JSONObject("form", formSupport.getClientId(), "clientId", clientId); 166 167 spec.put("validate", mode == SubmitMode.NORMAL); 168 169 if (mode == SubmitMode.CANCEL) 170 { 171 spec.put("cancel", true); 172 } 173 174 javascriptSupport.addInitializerCall(InitializationPriority.EARLY, "linkSubmit", spec); 175 } 176 } 177 178 public String getClientId() 179 { 180 return clientId; 181 } 182 }