blob: 97b55848f933a0a570937f4a17c3f19c045dceb5 [file] [log] [blame]
// Copyright 2008 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.apache.tapestry5.corelib.components;
import org.apache.tapestry5.*;
import org.apache.tapestry5.annotations.Environmental;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.annotations.SupportsInformalParameters;
import org.apache.tapestry5.corelib.data.InsertPosition;
import org.apache.tapestry5.corelib.internal.FormSupportImpl;
import org.apache.tapestry5.internal.services.ClientBehaviorSupport;
import org.apache.tapestry5.internal.services.ComponentResultProcessorWrapper;
import org.apache.tapestry5.internal.services.PageRenderQueue;
import org.apache.tapestry5.internal.util.Base64ObjectOutputStream;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.internal.util.IdAllocator;
import org.apache.tapestry5.runtime.RenderCommand;
import org.apache.tapestry5.runtime.RenderQueue;
import org.apache.tapestry5.services.*;
import java.io.IOException;
import java.util.List;
/**
* A way to add new content to an existing Form. The FormInjector emulates its tag from the template (or uses a
* <div>). When triggered, new content is obtained from the application and is injected before or after the
* element.
*/
@SupportsInformalParameters
public class FormInjector implements ClientElement
{
public static final String INJECT_EVENT = "inject";
public static final String FORMID_PARAMETER = "t:formid";
/**
* The context for the link (optional parameter). This list of values will be converted into strings and included in
* the URI. The strings will be coerced back to whatever their values are and made available to event handler
* methods.
*/
@Parameter
private List<?> context;
@Parameter(defaultPrefix = BindingConstants.LITERAL, value = "above")
private InsertPosition position;
/**
* Name of a function on the client-side Tapestry.ElementEffect object that is invoked to make added content
* visible. Leaving as null uses the default function, "highlight".
*/
@Parameter(defaultPrefix = BindingConstants.LITERAL)
private String show;
/**
* The element name to render, which is normally the element name used to represent the FormInjector component in
* the template, or "div".
*/
@Parameter(defaultPrefix = BindingConstants.LITERAL)
private String element;
@Environmental
private RenderSupport renderSupport;
@Environmental
private FormSupport formSupport;
@Environmental
private ClientBehaviorSupport clientBehaviorSupport;
@Inject
@Ajax
private ComponentEventResultProcessor componentEventResultProcessor;
@Inject
private PageRenderQueue pageRenderQueue;
private String clientId;
@Inject
private ComponentResources resources;
@Inject
private Request request;
@Inject
private Environment environment;
String defaultElement()
{
return resources.getElementName("div");
}
void beginRender(MarkupWriter writer)
{
clientId = renderSupport.allocateClientId(resources);
writer.element(element,
"id", clientId);
resources.renderInformalParameters(writer);
// Now work on the JavaScript side of things.
Link link = resources.createActionLink(INJECT_EVENT, false,
context == null ? new Object[0] : context.toArray());
link.addParameter(FORMID_PARAMETER, formSupport.getClientId());
clientBehaviorSupport.addFormInjector(clientId, link, position, show);
}
void afterRender(MarkupWriter writer)
{
writer.end();
}
/**
* Returns the unique client-side id of the rendered element.
*/
public String getClientId()
{
return clientId;
}
/**
* Invoked via an Ajax request. Triggers an action event and captures the return value. The return value from the
* event notification is what will ultimately render (typically, its a Block). However, we do a <em>lot</em> of
* tricks to provide the desired FormSupport around the what renders.
*/
Object onInject(EventContext context) throws IOException
{
ComponentResultProcessorWrapper callback = new ComponentResultProcessorWrapper(
componentEventResultProcessor);
resources.triggerContextEvent(EventConstants.ACTION, context, callback);
if (!callback.isAborted()) return null;
// Here's where it gets very, very tricky.
final RenderCommand rootRenderCommand = pageRenderQueue.getRootRenderCommand();
final String formId = request.getParameter(FORMID_PARAMETER);
final Base64ObjectOutputStream actions = new Base64ObjectOutputStream();
final RenderCommand cleanup = new RenderCommand()
{
public void render(MarkupWriter writer, RenderQueue queue)
{
try
{
actions.close();
}
catch (IOException ex)
{
throw new RuntimeException(ex);
}
environment.pop(ValidationTracker.class);
FormSupportImpl formSupport = (FormSupportImpl) environment.pop(FormSupport.class);
formSupport.executeDeferred();
writer.element("input",
"type", "hidden",
"name", Form.FORM_DATA,
"value", actions.toBase64());
}
};
final RenderCommand setup = new RenderCommand()
{
public void render(MarkupWriter writer, RenderQueue queue)
{
// Kind of ugly, but the only way to ensure we don't have name collisions on the
// client side is to force a unique id into each name (as well as each id, but that's
// RenderSupport's job). It would be nice if we could agree on the uid, but
// not essential.
String uid = Long.toHexString(System.currentTimeMillis());
IdAllocator idAllocator = new IdAllocator(":" + uid);
FormSupportImpl formSupport = new FormSupportImpl(formId, actions, clientBehaviorSupport, true,
idAllocator);
environment.push(FormSupport.class, formSupport);
environment.push(ValidationTracker.class, new ValidationTrackerImpl());
// Queue up the root render command to execute first, and the cleanup
// to execute after it is done.
queue.push(cleanup);
queue.push(rootRenderCommand);
}
};
return setup;
}
}