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
015package org.apache.tapestry5.internal.services.ajax;
016
017import org.apache.tapestry5.MarkupWriter;
018import org.apache.tapestry5.ValidationTracker;
019import org.apache.tapestry5.ValidationTrackerImpl;
020import org.apache.tapestry5.corelib.components.Form;
021import org.apache.tapestry5.corelib.internal.ComponentActionSink;
022import org.apache.tapestry5.corelib.internal.HiddenFieldPositioner;
023import org.apache.tapestry5.corelib.internal.InternalFormSupport;
024import org.apache.tapestry5.internal.util.CaptureResultCallback;
025import org.apache.tapestry5.ioc.ScopeConstants;
026import org.apache.tapestry5.ioc.annotations.Scope;
027import org.apache.tapestry5.ioc.util.IdAllocator;
028import org.apache.tapestry5.runtime.Component;
029import org.apache.tapestry5.services.ClientDataEncoder;
030import org.apache.tapestry5.services.ComponentSource;
031import org.apache.tapestry5.services.Environment;
032import org.apache.tapestry5.services.FormSupport;
033import org.apache.tapestry5.services.Heartbeat;
034import org.apache.tapestry5.services.HiddenFieldLocationRules;
035import org.slf4j.Logger;
036
037@Scope(ScopeConstants.PERTHREAD)
038public 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}