001    // Copyright 2008, 2009, 2010, 2011 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.corelib.components;
016    
017    import org.apache.tapestry5.*;
018    import org.apache.tapestry5.annotations.Environmental;
019    import org.apache.tapestry5.annotations.Events;
020    import org.apache.tapestry5.annotations.Parameter;
021    import org.apache.tapestry5.annotations.SupportsInformalParameters;
022    import org.apache.tapestry5.corelib.data.InsertPosition;
023    import org.apache.tapestry5.dom.Element;
024    import org.apache.tapestry5.internal.services.RequestConstants;
025    import org.apache.tapestry5.ioc.annotations.Inject;
026    import org.apache.tapestry5.json.JSONObject;
027    import org.apache.tapestry5.services.ClientBehaviorSupport;
028    import org.apache.tapestry5.services.FormSupport;
029    import org.apache.tapestry5.services.ajax.AjaxResponseRenderer;
030    import org.apache.tapestry5.services.ajax.JSONCallback;
031    import org.apache.tapestry5.services.javascript.JavaScriptSupport;
032    
033    import 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)
048    public 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, "id", clientId);
112    
113            resources.renderInformalParameters(writer);
114    
115            // Now work on the JavaScript side of things.
116    
117            Link link = resources.createEventLink(INJECT_EVENT, context);
118    
119            link.addParameter(RequestConstants.FORM_CLIENTID_PARAMETER, formSupport.getClientId());
120            link.addParameter(RequestConstants.FORM_COMPONENTID_PARAMETER, formSupport.getFormComponentId());
121    
122            clientBehaviorSupport.addFormInjector(clientId, link, position, show);
123        }
124    
125        void afterRender(MarkupWriter writer)
126        {
127            writer.end();
128    
129            // Add the class name to the rendered client element. This allows nested elements to locate
130            // the containing FormInjector element.
131    
132            clientElement.addClassName("t-forminjector");
133        }
134    
135        /**
136         * Returns the unique client-side id of the rendered element.
137         */
138        public String getClientId()
139        {
140            return clientId;
141        }
142    
143        /**
144         * Invoked via an Ajax request. Triggers an action event and captures the return value. The return value from the
145         * event notification is what will ultimately render (typically, its a Block).
146         */
147        void onInject(EventContext context) throws IOException
148        {
149            ajaxResponseRenderer.addCallback(new JSONCallback()
150            {
151                public void run(JSONObject reply)
152                {
153                    clientId = javascriptSupport.allocateClientId(resources);
154    
155                    reply.put("elementId", clientId);
156                }
157            });
158    
159            resources.triggerContextEvent(EventConstants.ACTION, context, eventCallback);
160        }
161    }