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}