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