001 // Copyright 2010 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.internal.services.ajax;
016
017 import org.apache.tapestry5.MarkupWriter;
018 import org.apache.tapestry5.ValidationTracker;
019 import org.apache.tapestry5.ValidationTrackerImpl;
020 import org.apache.tapestry5.corelib.components.Form;
021 import org.apache.tapestry5.corelib.internal.ComponentActionSink;
022 import org.apache.tapestry5.corelib.internal.HiddenFieldPositioner;
023 import org.apache.tapestry5.corelib.internal.InternalFormSupport;
024 import org.apache.tapestry5.internal.util.CaptureResultCallback;
025 import org.apache.tapestry5.ioc.ScopeConstants;
026 import org.apache.tapestry5.ioc.annotations.Scope;
027 import org.apache.tapestry5.ioc.util.IdAllocator;
028 import org.apache.tapestry5.runtime.Component;
029 import org.apache.tapestry5.services.ClientDataEncoder;
030 import org.apache.tapestry5.services.ComponentSource;
031 import org.apache.tapestry5.services.Environment;
032 import org.apache.tapestry5.services.FormSupport;
033 import org.apache.tapestry5.services.Heartbeat;
034 import org.apache.tapestry5.services.HiddenFieldLocationRules;
035 import org.slf4j.Logger;
036
037 @Scope(ScopeConstants.PERTHREAD)
038 public class AjaxFormUpdateControllerImpl implements AjaxFormUpdateController
039 {
040 private final ComponentSource componentSource;
041
042 private final HiddenFieldLocationRules rules;
043
044 private final Environment environment;
045
046 private final Heartbeat heartbeat;
047
048 private final ClientDataEncoder clientDataEncoder;
049
050 private final Logger logger;
051
052 private String formComponentId;
053
054 private String formClientId;
055
056 private HiddenFieldPositioner hiddenFieldPositioner;
057
058 private ComponentActionSink actionSink;
059
060 private InternalFormSupport formSupport;
061
062 public AjaxFormUpdateControllerImpl(ComponentSource componentSource, HiddenFieldLocationRules rules,
063 Environment environment, Heartbeat heartbeat, ClientDataEncoder clientDataEncoder, Logger logger)
064 {
065 this.componentSource = componentSource;
066 this.rules = rules;
067 this.environment = environment;
068 this.heartbeat = heartbeat;
069 this.clientDataEncoder = clientDataEncoder;
070 this.logger = logger;
071 }
072
073 public void initializeForForm(String formComponentId, String formClientId)
074 {
075 this.formComponentId = formComponentId;
076 this.formClientId = formClientId;
077 }
078
079 public void setupBeforePartialZoneRender(MarkupWriter writer)
080 {
081 if (formComponentId == null)
082 return;
083
084 hiddenFieldPositioner = new HiddenFieldPositioner(writer, rules);
085
086 actionSink = new ComponentActionSink(logger, clientDataEncoder);
087
088 formSupport = createInternalFormSupport(formClientId, formComponentId, actionSink);
089
090 environment.push(FormSupport.class, formSupport);
091 environment.push(ValidationTracker.class, new ValidationTrackerImpl());
092
093 heartbeat.begin();
094 }
095
096 public void cleanupAfterPartialZoneRender()
097 {
098 if (formComponentId == null)
099 return;
100
101 heartbeat.end();
102
103 formSupport.executeDeferred();
104
105 environment.pop(ValidationTracker.class);
106 environment.pop(FormSupport.class);
107
108 // If the Zone didn't actually contain any form control elements, then
109 // nothing will have been written to the action sink. In that case,
110 // get rid of the hidden field, if one was even added.
111
112 if (actionSink.isEmpty())
113 {
114 hiddenFieldPositioner.discard();
115
116 return;
117 }
118
119 // We've collected some hidden data that needs to be placed inside the Zone.
120 // This will raise an exception if the content of the zone didn't provide such a position.
121
122 hiddenFieldPositioner.getElement().attributes("type", "hidden",
123
124 "name", Form.FORM_DATA,
125
126 "value", actionSink.getClientData());
127 }
128
129 private InternalFormSupport createInternalFormSupport(String formClientId, String formComponentId,
130 ComponentActionSink actionSink)
131 {
132 // Kind of ugly, but the only way to ensure we don't have name collisions on the
133 // client side is to force a unique id into each name (as well as each id, but that's
134 // JavaScriptSupport's job). It would be nice if we could agree on the uid, but
135 // not essential.
136
137 String uid = Long.toHexString(System.nanoTime());
138
139 IdAllocator idAllocator = new IdAllocator("_" + uid);
140
141 Component formComponent = componentSource.getComponent(formComponentId);
142
143 CaptureResultCallback<InternalFormSupport> callback = CaptureResultCallback.create();
144
145 // This is a bit of a back-door to access a non-public method!
146
147 formComponent.getComponentResources().triggerEvent("internalCreateRenderTimeFormSupport", new Object[]
148 { formClientId, actionSink, idAllocator }, callback);
149
150 return callback.getResult();
151 }
152 }