001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.ajax.attributes;
018
019import java.util.ArrayList;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023
024import org.apache.wicket.ajax.AjaxChannel;
025import org.apache.wicket.util.lang.Args;
026import java.time.Duration;
027
028/**
029 * Attributes of an Ajax Request.
030 * 
031 * @author Matej Knopp
032 */
033public final class AjaxRequestAttributes
034{
035        /**
036         * The method to be used when submitting a form
037         */
038        public enum Method
039        {
040                /** get */
041                GET,
042
043                /** post */
044                POST;
045
046                @Override
047                public String toString()
048                {
049                        return name();
050                }
051        }
052
053        /**
054         * The JavaScript event propagation type
055         */
056        public enum EventPropagation
057        {
058                /**
059                 * Stops the propagation of the JavaScript event to the parent of its target
060                 */
061                STOP,
062
063                /**
064                 * Stops the propagation of the JavaScript event to the parent of its target and all other
065                 * event listeners registered on the same target
066                 */
067                STOP_IMMEDIATE,
068
069                /**
070                 * Do not stop the propagation of the JavaScript event
071                 */
072                BUBBLE
073        }
074
075        public static final String XML_DATA_TYPE = "xml";
076
077        private boolean multipart = false;
078
079        private Method method = Method.GET;
080
081        private Duration requestTimeout;
082
083        private boolean preventDefault = false;
084
085        private EventPropagation eventPropagation = EventPropagation.BUBBLE;
086
087        /**
088         * The names of the events which will trigger the Ajax call
089         */
090        private String[] eventNames = new String[0];
091
092        /**
093         * The id of the for that should be submitted
094         */
095        private String formId;
096
097        /**
098         * The id of the button/link that submitted the form
099         */
100        private String submittingComponentName;
101
102        /**
103         * Indicates whether or not this AjaxBehavior will produce <ajax-response>. By default it will
104         * produce it but some behaviors may need to return their own response which shouldn't be
105         * processed by wicket-ajax.js
106         */
107        private boolean wicketAjaxResponse = true;
108
109        private String dataType = XML_DATA_TYPE;
110
111        private List<IAjaxCallListener> ajaxCallListeners;
112
113        private Map<String, Object> extraParameters;
114
115        private List<CharSequence> dynamicExtraParameters;
116
117        private AjaxChannel channel;
118
119        /**
120         * Whether or not to use asynchronous XMLHttpRequest
121         */
122        private boolean async = true;
123
124        /**
125         * The settings to use if the Ajax call should be throttled. Throttled behaviors only execute
126         * once within the given delay even though they are triggered multiple times.
127         * <p>
128         * For example, this is useful when attaching a behavior to the keypress event. It is not
129         * desirable to have an ajax call made every time the user types so we throttle that call to a
130         * desirable delay, such as once per second. This gives us a near real time ability to provide
131         * feedback without overloading the server with ajax calls.
132         */
133        private ThrottlingSettings throttlingSettings;
134
135        /**
136         * A selector string to filter the descendants of the selected
137         * elements that trigger the event. If the selector is null or empty,
138         * the event is always triggered when it reaches the selected HTML element.
139         *
140         * @see <a href="http://api.jquery.com/on/">jQuery#on's selector</a>
141         */
142        private CharSequence childSelector;
143
144        /**
145         * A flag indicating whether to collect (submit) the name/value pairs for all HTML form elements
146         * children of the HTML element with the JavaScript listener
147         */
148        private boolean serializeRecursively;
149
150        /**
151         * @see #childSelector
152         * @return The selector string that filters the descendants
153         */
154        public CharSequence getChildSelector()
155        {
156                return childSelector;
157        }
158
159        /**
160         * @see #childSelector
161         * @param childSelector
162         *            The selector string that filters the descendants
163         */
164        public void setChildSelector(CharSequence childSelector)
165        {
166                this.childSelector = childSelector;
167        }
168
169        /**
170         * Returns whether the form submit is multipart.
171         * <p>
172         * Note that for multipart AJAX requests a hidden IFRAME will be used and that can have negative
173         * impact on error detection.
174         * 
175         * @return <code>true</code> if the form submit should be multipart, <code>false</code>
176         *         otherwise
177         */
178        public boolean isMultipart()
179        {
180                return multipart;
181        }
182
183        /**
184         * Determines whether the form submit is multipart.
185         * 
186         * <p>
187         * Note that for multipart AJAX requests a hidden IFRAME will be used and that can have negative
188         * impact on error detection.
189         * 
190         * @param multipart
191         * @return this object
192         */
193        public AjaxRequestAttributes setMultipart(boolean multipart)
194        {
195                this.multipart = multipart;
196                return this;
197        }
198
199        /**
200         * Returns the type of the Ajax request: <code>GET</code> or <code>POST</code>.
201         * <p>
202         * For a <code>POST</code>request all URL arguments are submitted as body. This can be useful if
203         * the URL parameters are longer than maximal URL length.
204         * 
205         * @return the type of the Ajax request. Default: {@linkplain Method#GET}
206         */
207        public Method getMethod()
208        {
209                return method;
210        }
211
212        /**
213         * Sets the type of the Ajax request: <code>GET</code> or <code>POST</code>.
214         * <p>
215         * For a <code>POST</code>request all URL arguments are submitted as body. This can be useful if
216         * the URL parameters are longer than maximal URL length.
217         * 
218         * @param method
219         *            the type of the Ajax request
220         * @return {@code this} object for chaining
221         */
222        public AjaxRequestAttributes setMethod(final Method method)
223        {
224                this.method = Args.notNull(method, "method");
225                return this;
226        }
227
228        /**
229         * Returns the timeout in milliseconds for the AJAX request. This only involves the actual
230         * communication and not the processing afterwards. Can be <code>null</code> in which case the
231         * default request timeout will be used.
232         * 
233         * @return request timeout or <code>null<code> for default timeout. Default: no timeout.
234         */
235        public Duration getRequestTimeout()
236        {
237                return requestTimeout;
238        }
239
240        /**
241         * Sets the timeout in milliseconds for the AJAX request. This only involves the actual
242         * communication and not the processing afterwards. Can be <code>null</code> in which case the
243         * default request timeout will be used.
244         * 
245         * @param requestTimeout
246         * @return this object
247         */
248        public AjaxRequestAttributes setRequestTimeout(final Duration requestTimeout)
249        {
250                this.requestTimeout = requestTimeout;
251                return this;
252        }
253
254        /**
255         * @return a list of {@link IAjaxCallListener}s which will be notified during the the execution
256         *         of the Ajax call.
257         */
258        public List<IAjaxCallListener> getAjaxCallListeners()
259        {
260                if (ajaxCallListeners == null)
261                {
262                        ajaxCallListeners = new ArrayList<>();
263                }
264                return ajaxCallListeners;
265        }
266
267        /**
268         * Map that contains additional (static) URL parameters. These will be appended to the request
269         * URL. If you need more than one value for a key then use a java.util.List or an Object[] as a
270         * value of that key.
271         * 
272         * @return a map with additional URL arguments
273         * @see #getDynamicExtraParameters()
274         */
275        public Map<String, Object> getExtraParameters()
276        {
277                if (extraParameters == null)
278                {
279                        extraParameters = new HashMap<>();
280                }
281                return extraParameters;
282        }
283
284        /**
285         * Array of JavaScript functions that produce additional URL arguments.
286         * 
287         * <p>
288         * If there are no multivalued parameters then the function can return a simple JavaScript
289         * object. Example:
290         * 
291         * <pre>
292         *  return {
293         *      'param1': document.body.tagName,
294         *      'param2': calculateParam2()
295         *  }
296         * </pre>
297         * 
298         * </p>
299         * <p>
300         * If there are multivalued parameters then an array of objects may be used. Example:
301         * 
302         * <pre>
303         *  return [
304         *      { name: 'param1', value: document.body.tagName },
305         *      { name: 'param1', value: calculateSecondValueForParam1() },
306         *      { name: 'param2', value: calculateParam2() }
307         *  ]
308         * 
309         * </pre>
310         * 
311         * </p>
312         * 
313         * @return a list of functions that produce additional URL arguments.
314         * @see #getExtraParameters()
315         */
316        public List<CharSequence> getDynamicExtraParameters()
317        {
318                if (dynamicExtraParameters == null)
319                {
320                        dynamicExtraParameters = new ArrayList<>();
321                }
322                return dynamicExtraParameters;
323        }
324
325        /**
326         * Only applies for event behaviors. Returns whether the behavior should prevent the default event
327         * handler to be invoked. For example if the behavior is attached to a link and
328         * isPreventDefault() returns <code>true</code>, the link's
329         * URL will not be followed. If the Ajax behavior is attached to a checkbox or a radio button
330         * then the default behavior should be allowed to actually check the box or radio button, i.e.
331         * this method should return <code>false</code>.
332         * 
333         * @return {@code false} if the default event handler should be invoked
334         */
335        public boolean isPreventDefault()
336        {
337                return preventDefault;
338        }
339
340        /**
341         * Only applies for event behaviors. Determines whether the behavior should prevent the default
342         * event handler to be invoked.
343         *
344         * @see #isPreventDefault()
345         *
346         * @param preventDefault
347         * @return {@code this} object for chaining
348         * @see #isPreventDefault()
349         */
350        public AjaxRequestAttributes setPreventDefault(boolean preventDefault)
351        {
352                this.preventDefault = preventDefault;
353                return this;
354        }
355
356        /**
357         * Only applies for event behaviors. Returns whether the behavior should allow the JavaScript
358         * event to propagate to the parent of its target.
359         */
360        public EventPropagation getEventPropagation()
361        {
362                return eventPropagation;
363        }
364
365        /**
366         * Only applies to event behaviors. Determines whether the behavior should allow the JavaScript
367         * event to propagate to the parent of its target.
368         * 
369         * @param eventPropagation
370         *            the type of the stop
371         * @return {@code this} object, for chaining
372         */
373        public AjaxRequestAttributes setEventPropagation(EventPropagation eventPropagation)
374        {
375                this.eventPropagation = Args.notNull(eventPropagation, "eventPropagation");
376                return this;
377        }
378
379        /**
380         * @param async
381         *            a flag whether to do asynchronous Ajax call or not
382         * @return {@code this} object for chaining
383         */
384        public AjaxRequestAttributes setAsynchronous(final boolean async)
385        {
386                this.async = async;
387                return this;
388        }
389
390        /**
391         * @return whether to do asynchronous Ajax call
392         */
393        public boolean isAsynchronous()
394        {
395                return async;
396        }
397
398        /**
399         * @return the channel to use
400         */
401        public AjaxChannel getChannel()
402        {
403                return channel;
404        }
405
406        /**
407         * @param channel
408         *            the Ajax channel to use. Pass {@code null} to use the default channel with name
409         *            <em>0</em> and queueing type.
410         * @return {@code this} object for chaining
411         */
412        public AjaxRequestAttributes setChannel(final AjaxChannel channel)
413        {
414                this.channel = channel;
415                return this;
416        }
417
418        /**
419         * @return the name(s) of the event(s) which will trigger the Ajax call
420         */
421        public String[] getEventNames()
422        {
423                return eventNames;
424        }
425
426        /**
427         * @param eventNames
428         *            the names of the events which will trigger the Ajax call
429         * @return {@code this} object for chaining
430         */
431        public AjaxRequestAttributes setEventNames(String... eventNames)
432        {
433                Args.notNull(eventNames, "eventNames");
434                this.eventNames = eventNames;
435                return this;
436        }
437
438        /**
439         * @return the id of the for that should be submitted
440         */
441        public String getFormId()
442        {
443                return formId;
444        }
445
446        /**
447         * @param formId
448         *            the id of the for that should be submitted
449         * @return {@code this} object for chaining
450         */
451        public AjaxRequestAttributes setFormId(final String formId)
452        {
453                this.formId = formId;
454                return this;
455        }
456
457        /**
458         * @return the input name of the button/link that submits the form
459         */
460        public String getSubmittingComponentName()
461        {
462                return submittingComponentName;
463        }
464
465        /**
466         * @param submittingComponentName
467         *            the input name of the button/link that submits the form
468         * @return {@code this} object for chaining
469         */
470        public AjaxRequestAttributes setSubmittingComponentName(String submittingComponentName)
471        {
472                this.submittingComponentName = submittingComponentName;
473                return this;
474        }
475
476        /**
477         * @return a flag indicating whether the Ajax response should be processed by Wicket (i.e. to
478         *         replace components, execute scripts, etc.). Default: {@code true}.
479         */
480        public boolean isWicketAjaxResponse()
481        {
482                return wicketAjaxResponse;
483        }
484
485        /**
486         * @param wicketAjaxResponse
487         *            a flag indicating whether the Ajax response should be processed by Wicket (i.e. to
488         *            replace components, execute scripts, etc.).
489         * @return {@code this} object for chaining
490         */
491        public AjaxRequestAttributes setWicketAjaxResponse(final boolean wicketAjaxResponse)
492        {
493                this.wicketAjaxResponse = wicketAjaxResponse;
494                return this;
495        }
496
497        /**
498         * Returns the type of the data in the Ajax response. For example: 'xml', 'json', 'html', etc.
499         * See the documentation of jQuery.ajax() method for more information.
500         * 
501         * @return the type of the data in the Ajax response.
502         */
503        public String getDataType()
504        {
505                return dataType;
506        }
507
508        /**
509         * @param dataType
510         *            the type of the data in the Ajax response.
511         * @return {@code this} object for chaining
512         */
513        public AjaxRequestAttributes setDataType(final String dataType)
514        {
515                this.dataType = Args.notEmpty(dataType, "dataType");
516                return this;
517        }
518
519        /**
520         * @return the settings to use when throttling is needed.
521         */
522        public ThrottlingSettings getThrottlingSettings()
523        {
524                return throttlingSettings;
525        }
526
527        /**
528         * @param throttlingSettings
529         *            the settings to use when throttling is needed. Pass {@code null} to disable
530         *            throttling.
531         * @return {@code this} object for chaining
532         */
533        public AjaxRequestAttributes setThrottlingSettings(ThrottlingSettings throttlingSettings)
534        {
535                this.throttlingSettings = throttlingSettings;
536                return this;
537        }
538
539        /**
540         * @return whether to collect (submit) the name/value pairs for all HTML form elements
541         *      children of the HTML element with the JavaScript listener
542         */
543        public boolean isSerializeRecursively() {
544                return serializeRecursively;
545        }
546
547        /**
548         * @param serializeRecursively
549         *          a flag indicating whether to collect (submit) the name/value pairs for all HTML form elements
550         * children of the HTML element with the JavaScript listener
551         */
552        public AjaxRequestAttributes setSerializeRecursively(final boolean serializeRecursively) {
553                this.serializeRecursively = serializeRecursively;
554                return this;
555        }
556}