001: /*
002: * $Id: AbstractAjaxBehavior.java 5440 2006-04-17 12:07:07 -0700 (Mon, 17 Apr
003: * 2006) jdonnerstag $ $Revision: 462329 $ $Date: 2006-04-17 12:07:07 -0700 (Mon,
004: * 17 Apr 2006) $
005: *
006: * ==============================================================================
007: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
008: * use this file except in compliance with the License. You may obtain a copy of
009: * the License at
010: *
011: * http://www.apache.org/licenses/LICENSE-2.0
012: *
013: * Unless required by applicable law or agreed to in writing, software
014: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
015: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
016: * License for the specific language governing permissions and limitations under
017: * the License.
018: */
019: package wicket.behavior;
020:
021: import java.util.HashSet;
022: import java.util.Set;
023:
024: import wicket.Component;
025: import wicket.RequestCycle;
026: import wicket.RequestListenerInterface;
027: import wicket.ResourceReference;
028: import wicket.Response;
029: import wicket.markup.ComponentTag;
030: import wicket.markup.html.IHeaderContributor;
031: import wicket.markup.html.PackageResourceReference;
032: import wicket.protocol.http.request.WebRequestCodingStrategy;
033: import wicket.util.string.AppendingStringBuffer;
034: import wicket.util.string.JavascriptUtils;
035:
036: /**
037: * Abstract class for handling Ajax roundtrips. This class serves as a base for
038: * javascript specific implementations, like ones based on Dojo or
039: * Scriptaculous, or Wicket's default.
040: *
041: * @author Eelco Hillenius
042: * @author Ralf Ebert
043: * @author Igor Vaynberg
044: */
045: public abstract class AbstractAjaxBehavior extends AbstractBehavior
046: implements IBehaviorListener, IHeaderContributor {
047: /** thread local for head contributions. */
048: private static final ThreadLocal headContribHolder = new ThreadLocal();
049:
050: /** the component that this handler is bound to. */
051: private Component component;
052:
053: /**
054: * Construct.
055: */
056: public AbstractAjaxBehavior() {
057: }
058:
059: /**
060: * Bind this handler to the given component.
061: *
062: * @param hostComponent
063: * the component to bind to
064: */
065: public final void bind(final Component hostComponent) {
066: if (hostComponent == null) {
067: throw new IllegalArgumentException(
068: "Argument hostComponent must be not null");
069: }
070:
071: if (this .component != null) {
072: throw new IllegalStateException(
073: "this kind of handler cannot be attached to "
074: + "multiple components; it is already attached to component "
075: + this .component + ", but component "
076: + hostComponent
077: + " wants to be attached too");
078:
079: }
080:
081: this .component = hostComponent;
082:
083: // call the calback
084: onBind();
085: }
086:
087: /**
088: * Gets the url that references this handler.
089: *
090: * @return the url that references this handler
091: */
092: public CharSequence getCallbackUrl() {
093: return getCallbackUrl(false, true);
094: }
095:
096: /**
097: * Gets the url that references this handler.
098: *
099: * @param recordPageVersion
100: * if true the url will be encoded to execute on the current page
101: * version, otherwise url will be encoded to execute on the
102: * latest page version
103: * @param onlyTargetActivePage
104: * if true the callback to this behavior will be ignore if the
105: * page is not the last one the user accessed
106: *
107: * @return the url that references this handler
108: */
109: public final CharSequence getCallbackUrl(
110: final boolean recordPageVersion,
111: final boolean onlyTargetActivePage) {
112: if (getComponent() == null) {
113: throw new IllegalArgumentException(
114: "Behavior must be bound to a component to create the URL");
115: }
116:
117: int index = getComponent().getBehaviors().indexOf(this );
118: if (index == -1) {
119: throw new IllegalArgumentException("Behavior " + this
120: + " was not registered with this component: "
121: + getComponent().toString());
122: }
123:
124: final RequestListenerInterface rli;
125: if (recordPageVersion) {
126: rli = IBehaviorListener.INTERFACE;
127: } else {
128: rli = IUnversionedBehaviorListener.INTERFACE;
129: }
130:
131: // TODO Post 1.2: URL encoding strategies are not applied
132: // And you can not simply call getResponse().encodeUrl() as the URL
133: // might
134: // already be encoded.
135: AppendingStringBuffer url = new AppendingStringBuffer(
136: getComponent().urlFor(rli)).append('&').append(
137: WebRequestCodingStrategy.BEHAVIOR_ID_PARAMETER_NAME)
138: .append('=').append(index);
139:
140: if (onlyTargetActivePage) {
141: url
142: .append("&")
143: .append(
144: WebRequestCodingStrategy.IGNORE_IF_NOT_ACTIVE_PARAMETER_NAME)
145: .append("=true");
146: }
147:
148: return url;
149: }
150:
151: /**
152: * @see wicket.behavior.IBehavior#onComponentTag(wicket.Component,
153: * wicket.markup.ComponentTag)
154: */
155: public final void onComponentTag(final Component component,
156: final ComponentTag tag) {
157: onComponentTag(tag);
158: }
159:
160: /**
161: * @see wicket.behavior.AbstractBehavior#onRendered(wicket.Component)
162: */
163: public final void onRendered(final Component hostComponent) {
164: onComponentRendered();
165: }
166:
167: /**
168: * @see wicket.behavior.AbstractBehavior#cleanup()
169: */
170: public void cleanup() {
171: headContribHolder.set(null);
172: }
173:
174: /**
175: * @see wicket.markup.html.IHeaderContributor#renderHead(wicket.Response)
176: */
177: public final void renderHead(final Response response) {
178: Set contributors = (Set) headContribHolder.get();
179:
180: // were any contributors set?
181: if (contributors == null) {
182: contributors = new HashSet(1);
183: headContribHolder.set(contributors);
184: }
185:
186: // get the id of the implementation; we need this trick to be
187: // able to support multiple implementations
188: String implementationId = getImplementationId();
189:
190: // was a contribution for this specific implementation done yet?
191: if (!contributors.contains(implementationId)) {
192: onRenderHeadInitContribution(response);
193: contributors.add(implementationId);
194: }
195:
196: onRenderHeadContribution(response);
197: }
198:
199: /**
200: * Convenience method to add a javascript reference.
201: *
202: * @param response
203: *
204: * @param ref
205: * reference to add
206: * @deprecated use {@link #writeJsReference(Response, ResourceReference)} instead
207: */
208: protected void writeJsReference(final Response response,
209: final PackageResourceReference ref) {
210: CharSequence url = RequestCycle.get().urlFor(ref);
211: JavascriptUtils.writeJavascriptUrl(response, url);
212: }
213:
214: /**
215: * Convenience method to add a javascript reference.
216: *
217: * @param response
218: *
219: * @param ref
220: * reference to add
221: */
222: protected void writeJsReference(final Response response,
223: final ResourceReference ref) {
224: CharSequence url = RequestCycle.get().urlFor(ref);
225: JavascriptUtils.writeJavascriptUrl(response, url);
226: }
227:
228: /**
229: * Gets the component that this handler is bound to.
230: *
231: * @return the component that this handler is bound to
232: */
233: protected final Component getComponent() {
234: return component;
235: }
236:
237: /**
238: * Gets the unique id of an ajax implementation. This should be implemented
239: * by base classes only - like the dojo or scriptaculous implementation - to
240: * provide a means to differentiate between implementations while not going
241: * to the level of concrete implementations. It is used to ensure 'static'
242: * header contributions are done only once per implementation.
243: *
244: * @return unique id of an ajax implementation
245: *
246: * @deprecated The mechanism will be changed for Wicket 2.0, where doubles
247: * are filtered in (future) class IHeaderResponse.
248: */
249: protected abstract String getImplementationId();
250:
251: /**
252: * Called any time a component that has this handler registered is rendering
253: * the component tag. Use this method e.g. to bind to javascript event
254: * handlers of the tag
255: *
256: * @param tag
257: * the tag that is rendered
258: */
259: protected void onComponentTag(final ComponentTag tag) {
260: }
261:
262: /**
263: * Called when the component was bound to it's host component. You can get
264: * the bound host component by calling getComponent.
265: */
266: protected void onBind() {
267: }
268:
269: /**
270: * Called to indicate that the component that has this handler registered
271: * has been rendered. Use this method to do any cleaning up of temporary
272: * state
273: */
274: protected void onComponentRendered() {
275: }
276:
277: /**
278: * Let this handler print out the needed header contributions. This
279: * implementation does nothing.
280: *
281: * @param response
282: * head container
283: */
284: protected void onRenderHeadContribution(final Response response) {
285: }
286:
287: /**
288: * Do a one time (per page) header contribution that is the same for all
289: * ajax variant implementations (e.g. Dojo, Scriptaculous). This
290: * implementation does nothing.
291: *
292: * @param response
293: * head container
294: */
295: protected void onRenderHeadInitContribution(final Response response) {
296: }
297: }
|