001// Copyright 2008, 2009, 2010, 2011, 2012 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.corelib.components;
016
017import org.apache.tapestry5.*;
018import org.apache.tapestry5.annotations.Environmental;
019import org.apache.tapestry5.annotations.Events;
020import org.apache.tapestry5.annotations.Parameter;
021import org.apache.tapestry5.annotations.SupportsInformalParameters;
022import org.apache.tapestry5.corelib.data.InsertPosition;
023import org.apache.tapestry5.dom.Element;
024import org.apache.tapestry5.internal.services.RequestConstants;
025import org.apache.tapestry5.ioc.annotations.Inject;
026import org.apache.tapestry5.json.JSONObject;
027import org.apache.tapestry5.services.ClientBehaviorSupport;
028import org.apache.tapestry5.services.FormSupport;
029import org.apache.tapestry5.services.ajax.AjaxResponseRenderer;
030import org.apache.tapestry5.services.ajax.JSONCallback;
031import org.apache.tapestry5.services.javascript.JavaScriptSupport;
032
033import java.io.IOException;
034
035/**
036 * A way to add new content to an existing Form. The FormInjector emulates its tag from the template (or uses a
037 * <div>). When triggered, new content is obtained from the application and is injected before or after the
038 * element.
039 * <p/>
040 * On the client side, a new function, trigger(), is added to the element. Invoking this client-side function will
041 * trigger the FormInjector; a request is sent to the server, new content is generated, and the new content is placed
042 * before or after (per configuration) the existing FormInjector element.
043 *
044 * @tapestrydoc
045 */
046@SupportsInformalParameters
047@Events(EventConstants.ACTION)
048public class FormInjector implements ClientElement
049{
050    public static final String INJECT_EVENT = "inject";
051
052    /**
053     * The context for the link (optional parameter). This list of values will be converted into strings and included in
054     * the URI. The strings will be coerced back to whatever their values are and made available to event handler
055     * methods.
056     */
057    @Parameter
058    private Object[] context;
059
060    @Parameter(defaultPrefix = BindingConstants.LITERAL,
061            value = BindingConstants.SYMBOL + ":" + ComponentParameterConstants.FORMINJECTOR_INSERT_POSITION)
062    private InsertPosition position;
063
064    /**
065     * Name of a function on the client-side Tapestry.ElementEffect object that is invoked to make added content
066     * visible. The default value is "highlight".
067     */
068    @Parameter(defaultPrefix = BindingConstants.LITERAL,
069            value = BindingConstants.SYMBOL + ":" + ComponentParameterConstants.FORMINJECTOR_SHOW_FUNCTION)
070    private String show;
071
072    /**
073     * The element name to render, which is normally the element name used to represent the FormInjector component in
074     * the template, or "div".
075     */
076    @Parameter(defaultPrefix = BindingConstants.LITERAL)
077    private String element;
078
079    @Environmental
080    private JavaScriptSupport javascriptSupport;
081
082    @Environmental
083    private FormSupport formSupport;
084
085    @Environmental
086    private ClientBehaviorSupport clientBehaviorSupport;
087
088    @SuppressWarnings("unchecked")
089    @Environmental
090    private TrackableComponentEventCallback eventCallback;
091
092    private String clientId;
093
094    @Inject
095    private ComponentResources resources;
096
097    @Inject
098    private AjaxResponseRenderer ajaxResponseRenderer;
099
100    private Element clientElement;
101
102    String defaultElement()
103    {
104        return resources.getElementName("div");
105    }
106
107    void beginRender(MarkupWriter writer)
108    {
109        clientId = javascriptSupport.allocateClientId(resources);
110
111        clientElement = writer.element(element,
112                "id", clientId,
113                "data-container-type", "core/ajaxformloop/fragment");
114
115        resources.renderInformalParameters(writer);
116
117        // Now work on the JavaScript side of things.
118
119        Link link = resources.createEventLink(INJECT_EVENT, context);
120
121        link.addParameter(RequestConstants.FORM_CLIENTID_PARAMETER, formSupport.getClientId());
122        link.addParameter(RequestConstants.FORM_COMPONENTID_PARAMETER, formSupport.getFormComponentId());
123
124        clientBehaviorSupport.addFormInjector(clientId, link, position, show);
125    }
126
127    void afterRender(MarkupWriter writer)
128    {
129        writer.end();
130
131        // Add the class name to the rendered client element. This allows nested elements to locate
132        // the containing FormInjector element.
133
134        clientElement.attribute("class", "t-forminjector");
135    }
136
137    /**
138     * Returns the unique client-side id of the rendered element.
139     */
140    public String getClientId()
141    {
142        return clientId;
143    }
144
145    /**
146     * Invoked via an Ajax request. Triggers an action event and captures the return value. The return value from the
147     * event notification is what will ultimately render (typically, its a Block).
148     */
149    void onInject(EventContext context) throws IOException
150    {
151        ajaxResponseRenderer.addCallback(new JSONCallback()
152        {
153            public void run(JSONObject reply)
154            {
155                clientId = javascriptSupport.allocateClientId(resources);
156
157                reply.put("elementId", clientId);
158            }
159        });
160
161        resources.triggerContextEvent(EventConstants.ACTION, context, eventCallback);
162    }
163}