Author: hlship
Date: Tue Jun 3 12:25:36 2008
New Revision: 662863
URL: http://svn.apache.org/viewvc?rev=662863&view=rev
Log:
TAPESTRY-2380: Add AjaxFormLoop component
Modified:
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/AjaxFormLoop.java
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/FormFragment.java
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/FormInjector.java
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageRenderQueue.java
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageRenderQueueImpl.java
tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/AjaxFormLoop.tml
tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tapestry.js
Modified:
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/AjaxFormLoop.java
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/AjaxFormLoop.java?rev=662863&r1=662862&r2=662863&view=diff
==============================================================================
---
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/AjaxFormLoop.java
(original)
+++
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/AjaxFormLoop.java
Tue Jun 3 12:25:36 2008
@@ -384,7 +384,9 @@
);
- pageRenderQueue.addPartialFilter(new PartialMarkupRendererFilter()
+ renderingInjector = true;
+
+ pageRenderQueue.addPartialMarkupRendererFilter(new
PartialMarkupRendererFilter()
{
public void renderMarkup(MarkupWriter writer, JSONObject reply,
PartialMarkupRenderer renderer)
{
Modified:
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/FormFragment.java
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/FormFragment.java?rev=662863&r1=662862&r2=662863&view=diff
==============================================================================
---
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/FormFragment.java
(original)
+++
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/FormFragment.java
Tue Jun 3 12:25:36 2008
@@ -21,24 +21,27 @@
import org.apache.tapestry5.corelib.internal.ComponentActionSink;
import org.apache.tapestry5.corelib.internal.FormSupportAdapter;
import org.apache.tapestry5.corelib.internal.HiddenFieldPositioner;
-import org.apache.tapestry5.corelib.internal.WrappedComponentAction;
import org.apache.tapestry5.dom.Element;
import org.apache.tapestry5.internal.services.ClientBehaviorSupport;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.services.*;
import org.slf4j.Logger;
-import java.util.List;
-
/**
- * A SubForm is a portion of a Form that may be selectively displayed. Form
elements inside a FormFragment will
+ * A FormFragment is a portion of a Form that may be selectively displayed.
Form elements inside a FormFragment will
* automatically bypass validation when the fragment is invisible. The trick
is to also bypass server-side form
- * processing for such fields when the form is submitted; the fragment uses a
hidden field to track its client-side
- * visibility and will bypass field component submission logic for the
components it encloses.
+ * processing for such fields when the form is submitted; client-side logic
"removes" the [EMAIL PROTECTED]
+ * org.apache.tapestry5.corelib.components.Form#FORM_DATA form data} for the
fragment if it is invisible when the form
+ * is submitted; alternately, client-side logic can simply remove the form
fragment element (including its visible and
+ * hidden fields) to prevent server-side processing.
+ * <p/>
* <p/>
- * In addition, should the client-side element for a Form fragment be removed
before the enclosing form is submitted,
- * then none of the fields inside the fragment will be processed (this can be
considered an extension of the "if not
- * visible, don't process" option above).
+ * The client-side element has a new property, formFragment, added to it. The
formFragment object has new methods to
+ * control the client-side behavior of the fragment: <dl> <dt>hide()</dt>
<dd>Hides the element, using the configured
+ * client-side animation effect.</dd> <dt>hideAndRemove()</dt> <dd>As with
hide(), but the element is removed from the
+ * DOM after being hidden.</dd> <dt>show()</dt> <dd>Makes the element visible,
using the configured client-side
+ * animation effect.</dd> <dt>toggle()</dt> <dd>Invokes hide() or show() as
necessary.</dd> <dt>setVisible()</dt>
+ * <dd>Passed a boolean parameter, invokes hide() or show() as necessary.</dd>
</dl>
*
* @see org.apache.tapestry5.corelib.mixins.TriggerFragment
*/
@@ -74,7 +77,6 @@
@Parameter(defaultPrefix = BindingConstants.LITERAL)
private String element;
-
@Inject
private Environment environment;
@@ -110,22 +112,6 @@
return resources.getElementName("div");
}
- private void handleSubmission(String elementName,
List<WrappedComponentAction> actions)
- {
- String value = request.getParameter(elementName);
-
- boolean visible = Boolean.parseBoolean(value);
-
- if (!visible) return;
-
- // Note that we DON'T update the visible parameter, it is read only.
-
- for (WrappedComponentAction action : actions)
- {
- action.execute(componentSource);
- }
- }
-
/**
* Renders a <div> tag and provides an override of the [EMAIL
PROTECTED] org.apache.tapestry5.services.FormSupport}
* environmental.
@@ -147,7 +133,6 @@
if (!visible)
element.addClassName(CSSClassConstants.INVISIBLE);
-
clientBehaviorSupport.addFormFragment(clientId, show, hide);
componentActions = new ComponentActionSink(logger);
@@ -189,7 +174,6 @@
*/
void afterRender(MarkupWriter writer)
{
-
hiddenFieldPositioner.getElement().attributes(
"type", "hidden",
Modified:
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/FormInjector.java
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/FormInjector.java?rev=662863&r1=662862&r2=662863&view=diff
==============================================================================
---
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/FormInjector.java
(original)
+++
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/FormInjector.java
Tue Jun 3 12:25:36 2008
@@ -28,8 +28,8 @@
import org.apache.tapestry5.internal.services.PageRenderQueue;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.internal.util.IdAllocator;
+import org.apache.tapestry5.json.JSONObject;
import org.apache.tapestry5.runtime.RenderCommand;
-import org.apache.tapestry5.runtime.RenderQueue;
import org.apache.tapestry5.services.*;
import org.slf4j.Logger;
@@ -40,6 +40,10 @@
* 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.
+ * <p/>
+ * On the client side, a new function, trigger(), is added to the element.
Invoking this client-side function will
+ * trigger the FormInjector; a request is sent to the server, new content is
generated, and the new content is placed
+ * before or after (per configuration) the existing FormInjector element.
*/
@SupportsInformalParameters
public class FormInjector implements ClientElement
@@ -165,14 +169,14 @@
* 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
+ void onInject(EventContext context) throws IOException
{
ComponentResultProcessorWrapper callback = new
ComponentResultProcessorWrapper(
componentEventResultProcessor);
resources.triggerContextEvent(EventConstants.ACTION, context,
callback);
- if (!callback.isAborted()) return null;
+ if (!callback.isAborted()) return;
// Here's where it gets very, very tricky.
@@ -182,28 +186,9 @@
final ComponentActionSink actionSink = new ComponentActionSink(logger);
- final RenderCommand cleanup = new RenderCommand()
+ PartialMarkupRendererFilter filter = new PartialMarkupRendererFilter()
{
- public void render(MarkupWriter writer, RenderQueue queue)
- {
- environment.pop(ValidationTracker.class);
-
- FormSupportImpl formSupport = (FormSupportImpl)
environment.pop(FormSupport.class);
-
- formSupport.executeDeferred();
-
- hiddenFieldPositioner.getElement().attributes(
- "type", "hidden",
-
- "name", Form.FORM_DATA,
-
- "value", actionSink.toBase64());
- }
- };
-
- final RenderCommand setup = new RenderCommand()
- {
- public void render(final MarkupWriter writer, RenderQueue queue)
+ public void renderMarkup(MarkupWriter writer, JSONObject reply,
PartialMarkupRenderer renderer)
{
hiddenFieldPositioner = new HiddenFieldPositioner(writer,
rules);
@@ -216,20 +201,32 @@
IdAllocator idAllocator = new IdAllocator(":" + uid);
+ clientId = renderSupport.allocateClientId(resources);
+
+ reply.put("elementId", clientId);
+
FormSupportImpl formSupport = new FormSupportImpl(formId,
actionSink, 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.
+ renderer.renderMarkup(writer, reply);
+
+ formSupport.executeDeferred();
+
+ environment.pop(ValidationTracker.class);
+ environment.pop(FormSupport.class);
+
+ hiddenFieldPositioner.getElement().attributes(
+ "type", "hidden",
- queue.push(cleanup);
- queue.push(rootRenderCommand);
+ "name", Form.FORM_DATA,
+
+ "value", actionSink.toBase64());
}
};
- return setup;
+ pageRenderQueue.addPartialMarkupRendererFilter(filter);
}
}
Modified:
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageRenderQueue.java
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageRenderQueue.java?rev=662863&r1=662862&r2=662863&view=diff
==============================================================================
---
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageRenderQueue.java
(original)
+++
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageRenderQueue.java
Tue Jun 3 12:25:36 2008
@@ -86,5 +86,5 @@
*
* @param filter to add to the pipeline
*/
- void addPartialFilter(PartialMarkupRendererFilter filter);
+ void addPartialMarkupRendererFilter(PartialMarkupRendererFilter filter);
}
Modified:
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageRenderQueueImpl.java
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageRenderQueueImpl.java?rev=662863&r1=662862&r2=662863&view=diff
==============================================================================
---
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageRenderQueueImpl.java
(original)
+++
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageRenderQueueImpl.java
Tue Jun 3 12:25:36 2008
@@ -108,7 +108,7 @@
queue.run(writer);
}
- public void addPartialFilter(PartialMarkupRendererFilter filter)
+ public void addPartialMarkupRendererFilter(PartialMarkupRendererFilter
filter)
{
Defense.notNull(filter, "filter");
Modified:
tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/AjaxFormLoop.tml
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/AjaxFormLoop.tml?rev=662863&r1=662862&r2=662863&view=diff
==============================================================================
---
tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/AjaxFormLoop.tml
(original)
+++
tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/AjaxFormLoop.tml
Tue Jun 3 12:25:36 2008
@@ -1,9 +1,7 @@
<t:container xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
<t:formfragment t:id="fragment" element="prop:element" visible="true">
- <t:delegate to="beforeBody"/>
- <t:body/>
- <t:delegate to="afterBody"/>
+ <t:delegate to="block:ajaxResponse"/>
</t:formfragment>
<t:block id="tail">
Modified:
tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tapestry.js
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tapestry.js?rev=662863&r1=662862&r2=662863&view=diff
==============================================================================
---
tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tapestry.js
(original)
+++
tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tapestry.js
Tue Jun 3 12:25:36 2008
@@ -14,9 +14,16 @@
var Tapestry = {
- FORM_VALIDATE_EVENT : "form:validate",
+ /** Event, triggered on a Form element, to spur fields within the form to
validate thier input. */
+ FORM_VALIDATE_EVENT : "tapestry:formvalidate",
- FORM_PREPARE_FOR_SUBMIT_EVENT : "form:prepareforsubmit",
+ /** Event, triggered on a Form element, to allow callbacks to perpare for
a form submission (this
+ * occurs after validation).
+ */
+ FORM_PREPARE_FOR_SUBMIT_EVENT : "tapestry:formprepareforsubmit",
+
+ /** Event, triggered on the document object, which identifies the current
focus element. */
+ FOCUS_CHANGE_EVENT : "tapestry:focuschange",
DEBUG_ENABLED : false,
@@ -37,10 +44,6 @@
ErrorPopup : Class.create(),
- // An array of Tapestry.ErrorPopup instances that have been created for
fields within the page.
-
- errorPopups : [],
-
// Adds a callback function that will be invoked when the DOM is loaded
(which
// occurs *before* window.onload, which has to wait for images and such to
load
// first. This simply observes the dom:loaded event on the document
object (support for
@@ -76,12 +79,7 @@
{
element.observe("focus", function()
{
- Tapestry.focusedElement = element;
-
- $(Tapestry.errorPopups).each(function(popup)
- {
- popup.handleFocusChange(element);
- });
+ document.fire(Tapestry.FOCUS_CHANGE_EVENT, element);
});
element.isObservingFocusChange = true;
@@ -242,6 +240,27 @@
}
return true;
+ },
+
+ /**
+ * Default function for handling Ajax-related failures.
+ */
+ ajaxFailureHandler : function()
+ {
+ Tapestry.error("Communication with the server failed.");
+ },
+
+ /**
+ * Processes a typical Ajax request for a URL invoking the provided
handler on success.
+ * On failure, error() is invoked to inform the user.
+ *
+ * @param url of Ajax request
+ * @param successHandler to invoke on success
+ * @return the Ajax.Request object
+ */
+ ajaxRequest : function(url, successHandler)
+ {
+ return new Ajax.Request(url, { onSuccess: successHandler, onFailure:
Tapestry.ajaxFailureHandler })
}
};
@@ -283,17 +302,18 @@
{
var effect = Tapestry.ElementEffect.fade(container);
- effect.afterFinish = function()
+ effect.options.afterFinish = function()
{
container.remove();
};
}
}
- new Ajax.Request(spec.url, { onSuccess : successHandler });
+ Tapestry.ajaxRequest(spec.url, successHandler);
});
},
+
/**
* Convert a form or link into a trigger of an Ajax update that
* updates the indicated Zone.
@@ -343,7 +363,7 @@
var handler = function(event)
{
- new Ajax.Request(element.href, { onSuccess : successHandler });
+ Tapestry.ajaxRequest(element.href, successHandler);
return false;
};
@@ -516,7 +536,6 @@
{
this.form = $(form);
this.result = true;
- this.firstError = true;
},
// Invoked by a validator function (which is passed the event) to record
an error
@@ -527,11 +546,8 @@
recordError : function(message)
{
- if (this.firstError)
- {
- this.field.activate();
- this.firstError = false;
- }
+ if (this.focusField == undefined)
+ this.focusField = this.field;
this.field.decorateForValidationError(message);
@@ -544,7 +560,7 @@
BUBBLE_VERT_OFFSET : -34,
- BUBBLE_HORIZONTAL_OFFSET : -5,
+ BUBBLE_HORIZONTAL_OFFSET : -20,
BUBBLE_WIDTH: "auto",
@@ -574,17 +590,31 @@
Event.stop(event); // Should be domevent.stop(), but that fails
under IE
}.bindAsEventListener(this));
- Tapestry.errorPopups.push(this);
-
- this.state = "hidden";
-
this.queue = { position: 'end', scope: this.field.id };
Event.observe(window, "resize", this.repositionBubble.bind(this));
+
+ document.observe(Tapestry.FOCUS_CHANGE_EVENT, function(event)
+ {
+ // Tapestry.debug("Focus change: #{memo} for #{field}", { memo:
event.memo.id, field: this.field.id });
+
+ var focused = event.memo;
+
+ if (focused == this.field)
+ {
+ this.fadeIn();
+ }
+ else
+ {
+ this.fadeOut();
+ }
+ }.bind(this));
},
showMessage : function(message)
{
+ // Tapestry.debug("Show message: #{message} for #{field}", { message:
message, field: this.field.id });
+
this.stopAnimation();
this.innerSpan.update(message);
@@ -607,14 +637,19 @@
fadeIn : function()
{
+ // Tapestry.debug("fadeIn: " + this.field.id);
+
+ if (! this.hasMessage) return;
+
this.repositionBubble();
- if (this.state == "hidden")
- {
- this.state = "visible";
+ if (this.status == "fadeIn") return;
- this.animation = new Effect.Appear(this.outerDiv, { afterFinish :
this.afterFadeIn.bind(this), queue: this.queue });
- }
+ if (this.outerDiv.visible()) return;
+
+ this.animation = new Effect.Appear(this.outerDiv, { queue: this.queue
});
+
+ this.status = "fadeIn";
},
stopAnimation : function()
@@ -622,18 +657,18 @@
if (this.animation) this.animation.cancel();
this.animation = null;
+ this.status = null;
},
fadeOut : function ()
{
- this.stopAnimation();
+ // Tapestry.debug("fadeOut: " + this.field.id);
- if (this.state == "visible")
- {
- this.state = "hidden";
+ if (this.status == "fadeOut") return;
- this.animation = new Effect.Fade(this.outerDiv, { queue :
this.queue });
- }
+ this.animation = new Effect.Fade(this.outerDiv, { queue : this.queue
});
+
+ this.status = "fadeOut";
},
hide : function()
@@ -643,34 +678,6 @@
this.stopAnimation();
this.outerDiv.hide();
-
- this.state = "hidden";
- },
-
- afterFadeIn : function()
- {
- this.animation = null;
-
- if (this.field != Tapestry.focusedElement) this.fadeOut();
- },
-
- handleFocusChange : function(element)
- {
- if (element == this.field)
- {
- if (this.hasMessage) this.fadeIn();
- return;
- }
-
- if (this.animation == null)
- {
- this.fadeOut();
- return;
- }
-
- // Must be fading in, let it finish, then fade it back out.
-
- this.animation = new Effect.Fade(this.outerDiv, { queue : this.queue
});
}
};
@@ -707,9 +714,14 @@
this.form.fire(Tapestry.FORM_VALIDATE_EVENT, event);
-
if (! event.result)
{
+ // Calling focus() does not trigger this event, so we do it
manually.
+ // Defer it long enough for the animations to start.
+
+ event.focusField.activate();
+ // document.fire(Tapestry.FOCUS_CHANGE_EVENT, event.focusField);
+
Event.stop(domevent); // Should be domevent.stop(), but that fails
under IE
}
else
@@ -739,8 +751,8 @@
{
var event = new Tapestry.FormEvent(this.field.form);
- // This prevents the field from taking focus if there is an error.
- event.firstError = false;
+ // This prevents the field from taking focus if there is an error.
+ event.focusField = this.field;
event.field = this.field;
@@ -951,10 +963,10 @@
{
var effect = this.hideFunc(this.element);
- effect.afterFinish = function()
+ effect.options.afterFinish = function()
{
this.element.remove();
- };
+ }.bind(this);
},
show : function()
@@ -1017,6 +1029,8 @@
newElement.update(reply.content);
+ newElement.id = reply.elementId;
+
// Handle any scripting issues.
Tapestry.processScriptInReply(reply);
@@ -1027,7 +1041,7 @@
}.bind(this);
- new Ajax.Request(this.url, { onSuccess : successHandler });
+ Tapestry.ajaxRequest(this.url, successHandler);
return false;
}.bind(this);