    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}