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    }