001: /*
002: * $Id: AbstractDefaultAjaxBehavior.java 4858 2006-03-12 00:26:31 -0800 (Sun, 12
003: * Mar 2006) ivaynberg $ $Revision: 469645 $ $Date: 2006-03-12 00:26:31 -0800
004: * (Sun, 12 Mar 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.ajax;
020:
021: import wicket.Application;
022: import wicket.Page;
023: import wicket.RequestCycle;
024: import wicket.ResourceReference;
025: import wicket.Response;
026: import wicket.behavior.AbstractAjaxBehavior;
027: import wicket.markup.html.resources.CompressedResourceReference;
028: import wicket.settings.IAjaxSettings;
029: import wicket.util.string.AppendingStringBuffer;
030: import wicket.util.string.JavascriptUtils;
031: import wicket.util.string.Strings;
032: import wicket.util.time.Duration;
033:
034: /**
035: * The base class for Wicket's default AJAX implementation.
036: *
037: * @since 1.2
038: *
039: * @author Igor Vaynberg (ivaynberg)
040: *
041: */
042: public abstract class AbstractDefaultAjaxBehavior extends
043: AbstractAjaxBehavior {
044: private static final long serialVersionUID = 1L;
045:
046: /** reference to the default indicator gif file. */
047: public static final ResourceReference INDICATOR = new ResourceReference(
048: AbstractDefaultAjaxBehavior.class, "indicator.gif");
049:
050: /** reference to the default ajax support javascript file. */
051: private static final ResourceReference JAVASCRIPT = new CompressedResourceReference(
052: AbstractDefaultAjaxBehavior.class, "wicket-ajax.js");
053:
054: /** reference to the default ajax debug support javascript file. */
055: private static final ResourceReference JAVASCRIPT_DEBUG_DRAG = new CompressedResourceReference(
056: AbstractDefaultAjaxBehavior.class,
057: "wicket-ajax-debug-drag.js");
058:
059: /** reference to the default ajax debug support javascript file. */
060: private static final ResourceReference JAVASCRIPT_DEBUG = new CompressedResourceReference(
061: AbstractDefaultAjaxBehavior.class, "wicket-ajax-debug.js");
062:
063: /**
064: *
065: * @see wicket.behavior.AbstractAjaxBehavior#getImplementationId()
066: */
067: protected String getImplementationId() {
068: return "wicket-default";
069: }
070:
071: /**
072: * Subclasses should call super.onBind()
073: *
074: * @see wicket.behavior.AbstractAjaxBehavior#onBind()
075: */
076: protected void onBind() {
077: getComponent().setOutputMarkupId(true);
078: }
079:
080: /**
081: *
082: * @see wicket.behavior.AbstractAjaxBehavior#onRenderHeadInitContribution(wicket.Response)
083: */
084: protected void onRenderHeadInitContribution(final Response response) {
085: final IAjaxSettings settings = Application.get()
086: .getAjaxSettings();
087:
088: writeJsReference(response, JAVASCRIPT);
089:
090: if (settings.isAjaxDebugModeEnabled()) {
091: JavascriptUtils.writeJavascript(response,
092: "wicketAjaxDebugEnable=true;",
093: "wicket-ajax-debug-enable");
094: writeJsReference(response, JAVASCRIPT_DEBUG_DRAG);
095: writeJsReference(response, JAVASCRIPT_DEBUG);
096: }
097: }
098:
099: /**
100: * @return ajax call decorator used to decorate the call generated by this
101: * behavior or null for none
102: */
103: protected IAjaxCallDecorator getAjaxCallDecorator() {
104: return null;
105: }
106:
107: /**
108: * @return javascript that will generate an ajax GET request to this
109: * behavior
110: */
111: protected CharSequence getCallbackScript() {
112: return getCallbackScript(true, false);
113: }
114:
115: /**
116: * @return javascript that will generate an ajax GET request to this
117: * behavior *
118: * @param recordPageVersion
119: * if true the url will be encoded to execute on the current page
120: * version, otherwise url will be encoded to execute on the
121: * latest page version
122: * @param onlyTargetActivePage
123: * if true the callback to this behavior will be ignore if the
124: * page is not the last one the user accessed
125: *
126: */
127: protected CharSequence getCallbackScript(boolean recordPageVersion,
128: boolean onlyTargetActivePage) {
129: return getCallbackScript("wicketAjaxGet('"
130: + getCallbackUrl(recordPageVersion,
131: onlyTargetActivePage) + "'", null, null);
132: }
133:
134: /**
135: * Returns javascript that performs an ajax callback to this behavior. The
136: * script is decorated by the ajax callback decorator from
137: * {@link AbstractDefaultAjaxBehavior#getAjaxCallDecorator()}.
138: *
139: * @param partialCall
140: * Javascript of a partial call to the function performing the
141: * actual ajax callback. Must be in format
142: * <code>function(params,</code> with signature
143: * <code>function(params, onSuccessHandler, onFailureHandler</code>.
144: * Example: <code>wicketAjaxGet('callbackurl'</code>
145: * @param onSuccessScript
146: * javascript that will run when the ajax call finishes
147: * successfully
148: * @param onFailureScript
149: * javascript that will run when the ajax call finishes with an
150: * error status
151: *
152: * @return script that peforms ajax callback to this behavior
153: */
154: protected CharSequence getCallbackScript(
155: final CharSequence partialCall,
156: final CharSequence onSuccessScript,
157: final CharSequence onFailureScript) {
158: final IAjaxCallDecorator decorator = getAjaxCallDecorator();
159:
160: String indicatorId = findIndicatorId();
161:
162: CharSequence success = (onSuccessScript == null) ? ""
163: : onSuccessScript;
164: CharSequence failure = (onFailureScript == null) ? ""
165: : onFailureScript;
166:
167: if (decorator != null) {
168: success = decorator.decorateOnSuccessScript(success);
169: }
170:
171: if (!Strings.isEmpty(indicatorId)) {
172: String hide = ";wicketHide('" + indicatorId + "');";
173: success = success + hide;
174: failure = failure + hide;
175: }
176:
177: if (decorator != null) {
178: failure = decorator.decorateOnFailureScript(failure);
179: }
180:
181: AppendingStringBuffer buff = new AppendingStringBuffer(256);
182: buff.append("var ").append(
183: IAjaxCallDecorator.WICKET_CALL_RESULT_VAR).append("=");
184: buff.append(partialCall).append(", function() { ").append(
185: success);
186: buff.append("}, function() { ").append(failure).append("});");
187:
188: CharSequence call = buff;
189:
190: if (!Strings.isEmpty(indicatorId)) {
191: call = new AppendingStringBuffer("wicketShow('").append(
192: indicatorId).append("');").append(call);
193: }
194:
195: if (decorator != null) {
196: call = decorator.decorateScript(call);
197: }
198:
199: return call;
200: }
201:
202: /**
203: *
204: * @return String
205: */
206: private String findIndicatorId() {
207: if (getComponent() instanceof IAjaxIndicatorAware) {
208: return ((IAjaxIndicatorAware) getComponent())
209: .getAjaxIndicatorMarkupId();
210: }
211:
212: if (this instanceof IAjaxIndicatorAware) {
213: return ((IAjaxIndicatorAware) this )
214: .getAjaxIndicatorMarkupId();
215: }
216:
217: return null;
218: }
219:
220: /**
221: * @see wicket.behavior.IBehaviorListener#onRequest()
222: */
223: public final void onRequest() {
224: boolean isPageVersioned = true;
225: Page page = getComponent().getPage();
226: try {
227: isPageVersioned = page.isVersioned();
228: page.setVersioned(false);
229:
230: AjaxRequestTarget target = new AjaxRequestTarget();
231: RequestCycle.get().setRequestTarget(target);
232: respond(target);
233: } finally {
234: page.setVersioned(isPageVersioned);
235: }
236: }
237:
238: /**
239: * @param target
240: * The AJAX target
241: */
242: protected abstract void respond(AjaxRequestTarget target);
243:
244: /**
245: * Wraps the provided javascript with a throttled block. Throttled behaviors
246: * only execute once within the given delay even though they are triggered
247: * multiple times.
248: * <p>
249: * For example, this is useful when attaching an event behavior to the
250: * onkeypress event. It is not desirable to have an ajax call made every
251: * time the user types so we throttle that call to a desirable delay, such
252: * as once per second. This gives us a near real time ability to provide
253: * feedback without overloading the server with ajax calls.
254: *
255: * @param script
256: * javascript to be throttled
257: * @param throttleId
258: * the id of the throttle to be used. Usually this should remain
259: * constant for the same javascript block.
260: * @param throttleDelay
261: * time span within which the javascript block will only execute
262: * once
263: * @return wrapped javascript
264: */
265: public static final CharSequence throttleScript(
266: CharSequence script, String throttleId,
267: Duration throttleDelay) {
268: if (Strings.isEmpty(script)) {
269: throw new IllegalArgumentException("script cannot be empty");
270: }
271:
272: if (Strings.isEmpty(throttleId)) {
273: throw new IllegalArgumentException(
274: "throttleId cannot be empty");
275: }
276:
277: if (throttleDelay == null) {
278: throw new IllegalArgumentException(
279: "throttleDelay cannot be null");
280: }
281:
282: return new AppendingStringBuffer("wicketThrottler.throttle( '")
283: .append(throttleId).append("', ").append(
284: throttleDelay.getMilliseconds()).append(
285: ", function() { ").append(script).append("});");
286: }
287: }
|