You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by mg...@apache.org on 2019/06/29 15:42:03 UTC

[wicket] 01/01: WICKET-6681 Implement Wicket Ajax with plain JavaScript APIs, i.e. without jQuery

This is an automated email from the ASF dual-hosted git repository.

mgrigorov pushed a commit to branch feature/WICKET-6681-vanilla-wicket-ajax
in repository https://gitbox.apache.org/repos/asf/wicket.git

commit 3680062ba4209525f3bff87724b6534b228428e5
Author: Martin Tzvetanov Grigorov <mg...@apache.org>
AuthorDate: Sat Jun 29 18:39:34 2019 +0300

    WICKET-6681 Implement Wicket Ajax with plain JavaScript APIs, i.e. without jQuery
    
    Copy/paste wicket-aja-jquery.js and remove usage of: #extend(), #hide(), #show(), #on(), #off(), #isWindow(), #isFunction(), #bind()
---
 .../ajax/WicketAjaxVanillaResourceReference.java   |   44 +
 .../wicket/ajax/res/js/wicket-ajax-vanilla.js      | 2586 ++++++++++++++++++++
 .../wicket/devutils/debugbar/wicket-debugbar.js    |   29 +-
 3 files changed, 2645 insertions(+), 14 deletions(-)

diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/WicketAjaxVanillaResourceReference.java b/wicket-core/src/main/java/org/apache/wicket/ajax/WicketAjaxVanillaResourceReference.java
new file mode 100644
index 0000000..703d55a
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/ajax/WicketAjaxVanillaResourceReference.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.wicket.ajax;
+
+import org.apache.wicket.request.resource.JavaScriptResourceReference;
+
+/**
+ * A resource reference to an implementation of Wicket Ajax JavaScript APIs
+ * with vanilla JavaScript APIs
+ */
+public class WicketAjaxVanillaResourceReference extends JavaScriptResourceReference
+{
+	private static final long serialVersionUID = 1L;
+
+	private static final WicketAjaxVanillaResourceReference INSTANCE = new WicketAjaxVanillaResourceReference();
+
+	/**
+	 * @return the singleton INSTANCE
+	 */
+	public static WicketAjaxVanillaResourceReference get()
+	{
+		return INSTANCE;
+	}
+
+	private WicketAjaxVanillaResourceReference()
+	{
+		super(AbstractDefaultAjaxBehavior.class, "res/js/wicket-ajax-vanilla.js");
+	}
+
+}
diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/res/js/wicket-ajax-vanilla.js b/wicket-core/src/main/java/org/apache/wicket/ajax/res/js/wicket-ajax-vanilla.js
new file mode 100644
index 0000000..c25b74c
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/ajax/res/js/wicket-ajax-vanilla.js
@@ -0,0 +1,2586 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+/*global DOMParser: true, console: true */
+
+/*
+ * Wicket Ajax Support
+ *
+ * @author Igor Vaynberg
+ * @author Matej Knopp
+ */
+
+;(function (undefined) {
+
+	'use strict';
+
+	if (typeof(Wicket) === 'undefined') {
+		window.Wicket = {};
+	}
+
+	if (typeof(Wicket.Head) === 'object') {
+		return;
+	}
+
+	var isUndef = function (target) {
+		return (typeof(target) === 'undefined' || target === null);
+	};
+
+	/**
+	 * A safe getter for Wicket's Ajax base URL.
+	 * If the value is not defined or is empty string then
+	 * return '.' (current folder) as base URL.
+	 * Used for request header and parameter
+	 */
+	var getAjaxBaseUrl = function () {
+		var baseUrl = Wicket.Ajax.baseUrl || '.';
+		return baseUrl;
+	};
+
+	/**
+	 * Converts a NodeList to an Array
+	 *
+	 * @param nodeList The NodeList to convert
+	 * @returns {Array} The array with document nodes
+	 */
+	var nodeListToArray = function (nodeList) {
+		var arr = [],
+			nodeId;
+		if (nodeList && nodeList.length) {
+			for (nodeId = 0; nodeId < nodeList.length; nodeId++) {
+				arr.push(nodeList.item(nodeId));
+			}
+		}
+		return arr;
+	};
+
+	/**
+	 * Functions executer takes array of functions and executes them.
+	 * The functions are executed one by one as far as the return value is FunctionsExecuter.DONE.
+	 * If the return value is FunctionsExecuter.ASYNC or undefined then the execution of
+	 * the functions will be resumed once the `notify` callback function is called.
+	 * This is needed because header contributions need to do asynchronous download of JS and/or CSS
+	 * and they have to let next function to run only after the download.
+	 * After the FunctionsExecuter is initialized, the start methods triggers the first function.
+	 *
+	 * @param functions {Array} - an array of functions to execute
+	 */
+	var FunctionsExecuter = function (functions) {
+
+		this.functions = functions;
+
+		/**
+		 * The index of the currently executed function
+		 * @type {number}
+		 */
+		this.current = 0;
+
+		/**
+		 * Tracks the depth of the call stack when `notify` is used for
+		 * asynchronous notification that a function execution has finished.
+		 * Should be reset to 0 when at some point to avoid problems like
+		 * "too much recursion". The reset may break the atomicity by allowing
+		 * another instance of FunctionsExecuter to run its functions
+		 * @type {number}
+		 */
+		this.depth = 0; // we need to limit call stack depth
+
+		this.processNext = function () {
+			if (this.current < this.functions.length) {
+				var f, run;
+
+				f = this.functions[this.current];
+				run = function () {
+					try {
+						var n = this.notify.bind(this);
+						return f(n);
+					}
+					catch (e) {
+						Wicket.Log.error("FunctionsExecuter.processNext:", e);
+						return FunctionsExecuter.FAIL;
+					}
+				};
+				run = run.bind(this);
+				this.current++;
+
+				if (this.depth > FunctionsExecuter.DEPTH_LIMIT) {
+					// to prevent stack overflow (see WICKET-4675)
+					this.depth = 0;
+					window.setTimeout(run, 1);
+				} else {
+					var retValue = run();
+					if (isUndef(retValue) || retValue === FunctionsExecuter.ASYNC) {
+						this.depth++;
+					}
+					return retValue;
+				}
+			}
+		};
+
+		this.start = function () {
+			var retValue = FunctionsExecuter.DONE;
+			while (retValue === FunctionsExecuter.DONE) {
+				retValue = this.processNext();
+			}
+		};
+
+		this.notify = function () {
+			this.start();
+		};
+	};
+
+	var isWindow = function(obj) {
+		return obj && obj.window === window
+	};
+
+	var isFunction = function (item) {
+		if (typeof item === 'function') {
+			return true;
+		}
+		var type = Object.prototype.toString.call(item);
+		return type === '[object Function]' || type === '[object GeneratorFunction]';
+	}
+
+	/**
+	 * Response that should be used by a function when it finishes successfully
+	 * in synchronous manner
+	 * @type {number}
+	 */
+	FunctionsExecuter.DONE = 1;
+
+	/**
+	 * Response that should be used by a function when it finishes abnormally
+	 * in synchronous manner
+	 * @type {number}
+	 */
+	FunctionsExecuter.FAIL = 2;
+
+	/**
+	 * Response that may be used by a function when it executes asynchronous
+	 * code and must wait `notify()` to be executed.
+	 * @type {number}
+	 */
+	FunctionsExecuter.ASYNC = 3;
+
+	/**
+	 * An artificial number used as a limit of the call stack depth to avoid
+	 * problems like "too much recursion" in the browser.
+	 * The depth is not easy to be calculated because the memory used by the
+	 * stack depends on many factors
+	 * @type {number}
+	 */
+	FunctionsExecuter.DEPTH_LIMIT = 1000;
+
+	// Pass in the objects to merge as arguments.
+	// For a deep extend, set the first argument to `true`.
+	var extend = function () {
+
+		// Variables
+		var extended = {};
+		var deep = false;
+		var i = 0;
+		var length = arguments.length;
+
+		// Check if a deep merge
+		if ( Object.prototype.toString.call( arguments[0] ) === '[object Boolean]' ) {
+			deep = arguments[0];
+			i++;
+		}
+
+		// Merge the object into the extended object
+		var merge = function (obj) {
+			for ( var prop in obj ) {
+				if ( Object.prototype.hasOwnProperty.call( obj, prop ) ) {
+					// If deep merge and property is an object, merge properties
+					if ( deep && Object.prototype.toString.call(obj[prop]) === '[object Object]' ) {
+						extended[prop] = extend( true, extended[prop], obj[prop] );
+					} else {
+						extended[prop] = obj[prop];
+					}
+				}
+			}
+		};
+
+		// Loop through each object and conduct a merge
+		for ( ; i < length; i++ ) {
+			var obj = arguments[i];
+			merge(obj);
+		}
+
+		return extended;
+	};
+	
+	// API start
+
+	Wicket.Class = {
+		create: function () {
+			return function () {
+				this.initialize.apply(this, arguments);
+			};
+		}
+	};
+
+	/**
+	 * Logging functionality.
+	 */
+	Wicket.Log = {
+			
+		enabled: false,
+
+		log: function () {
+			if (Wicket.Log.enabled && typeof(console) !== "undefined" && typeof(console.log) === 'function') {
+				console.log.apply(console, arguments);
+			}
+		},
+
+		debug: function () {
+			if (Wicket.Log.enabled && typeof(console) !== "undefined" && typeof(console.debug) === 'function') {
+				console.debug.apply(console, arguments);
+			}
+		},
+
+		info: function () {
+			if (Wicket.Log.enabled && typeof(console) !== "undefined" && typeof(console.info) === 'function') {
+				console.info.apply(console, arguments);
+			}
+		},
+
+		warn: function () {
+			if (Wicket.Log.enabled && typeof(console) !== "undefined" && typeof(console.warn) === 'function') {
+				console.warn.apply(console, arguments);
+			}
+		},
+
+		error: function () {
+			if (Wicket.Log.enabled && typeof(console) !== "undefined" && typeof(console.error) === 'function') {
+				console.error.apply(console, arguments);
+			}
+		}
+	};
+
+	/**
+	 * Channel management
+	 *
+	 * Wicket Ajax requests are organized in channels. A channel maintain the order of
+	 * requests and determines, what should happen when a request is fired while another
+	 * one is being processed. The default behavior (stack) puts the all subsequent requests
+	 * in a queue, while the drop behavior limits queue size to one, so only the most
+	 * recent of subsequent requests is executed.
+	 * The name of channel determines the policy. E.g. channel with name foochannel|s is
+	 * a stack channel, while barchannel|d is a drop channel.
+	 *
+	 * The Channel class is supposed to be used through the ChannelManager.
+	 */
+	Wicket.Channel = Wicket.Class.create();
+
+	Wicket.Channel.prototype = {
+		initialize: function (name) {
+			name = name || '0|s';
+			var res = name.match(/^([^|]+)\|(d|s|a)$/);
+			if (isUndef(res)) {
+				this.name = '0'; // '0' is the default channel name
+				this.type = 's'; // default to stack/queue
+			}
+			else {
+				this.name = res[1];
+				this.type = res[2];
+			}
+			this.callbacks = [];
+			this.busy = false;
+		},
+
+		schedule: function (callback) {
+			if (this.busy === false) {
+				this.busy = true;
+				try {
+					return callback();
+				} catch (exception) {
+					this.busy = false;
+					Wicket.Log.error("An error occurred while executing Ajax request:", exception);
+				}
+			} else {
+				var busyChannel = "Channel '"+ this.name+"' is busy";
+				if (this.type === 's') { // stack/queue
+					Wicket.Log.info("%s - scheduling the callback to be executed when the previous request finish.", busyChannel);
+					this.callbacks.push(callback);
+				}
+				else if (this.type === 'd') { // drop
+					Wicket.Log.info("%s - dropping all previous scheduled callbacks and scheduling a new one to be executed when the current request finish.", busyChannel);
+					this.callbacks = [];
+					this.callbacks.push(callback);
+				} else if (this.type === 'a') { // active
+					Wicket.Log.info("%s - ignoring the Ajax call because there is a running request.", busyChannel);
+				}
+				return null;
+			}
+		},
+
+		done: function () {
+			var callback = null;
+
+			if (this.callbacks.length > 0) {
+				callback = this.callbacks.shift();
+			}
+
+			if (callback !== null && typeof(callback) !== "undefined") {
+				Wicket.Log.info("Calling postponed function...");
+				// we can't call the callback from this call-stack
+				// therefore we set it on timer event
+				window.setTimeout(callback, 1);
+			} else {
+				this.busy = false;
+			}
+		}
+	};
+
+	/**
+	 * Channel manager maintains a map of channels.
+	 */
+	Wicket.ChannelManager = Wicket.Class.create();
+
+	Wicket.ChannelManager.prototype = {
+		initialize: function () {
+			this.channels = {};
+		},
+
+		// Schedules the callback to channel with given name.
+		schedule: function (channel, callback) {
+			var parsed = new Wicket.Channel(channel);
+			var c = this.channels[parsed.name];
+			if (isUndef(c)) {
+				c = parsed;
+				this.channels[c.name] = c;
+			} else {
+				c.type = parsed.type;
+			}
+			return c.schedule(callback);
+		},
+
+		// Tells the ChannelManager that the current callback in channel with given name
+		// has finished processing and another scheduled callback can be executed (if any).
+		done: function (channel) {
+			var parsed = new Wicket.Channel(channel);
+			var c = this.channels[parsed.name];
+			if (!isUndef(c)) {
+				c.done();
+				if (!c.busy) {
+					delete this.channels[parsed.name];
+				}
+			}
+		}
+	};
+
+	Wicket.ChannelManager.FunctionsExecuter = FunctionsExecuter;
+
+	/**
+	 * The Ajax.Request class encapsulates a XmlHttpRequest.
+	 */
+	Wicket.Ajax = {};
+
+	/**
+	 * Ajax call fires a Wicket Ajax request and processes the response.
+	 * The response can contain
+	 *   - javascript that should be invoked
+	 *   - body of components being replaced
+	 *   - header contributions of components
+	 *   - a redirect location
+	 */
+	Wicket.Ajax.Call = Wicket.Class.create();
+
+	Wicket.Ajax.Call.prototype = {
+
+		initialize: function() {},
+
+		/**
+		 * Initializes the default values for Ajax request attributes.
+		 * The defaults are not set at the server side to save some bytes
+		 * for the network transfer
+		 *
+		 * @param attrs {Object} - the ajax request attributes to enrich
+		 * @private
+		 */
+		_initializeDefaults: function (attrs) {
+
+			// (ajax channel)
+			if (typeof(attrs.ch) !== 'string') {
+				attrs.ch = '0|s';
+			}
+
+			// (wicketAjaxResponse) be default the Ajax result should be processed for <ajax-response>
+			if (typeof(attrs.wr) !== 'boolean') {
+				attrs.wr = true;
+			}
+
+			// (dataType) by default we expect XML responses from the Ajax behaviors
+			if (typeof(attrs.dt) !== 'string') {
+				attrs.dt = 'xml';
+			}
+
+			if (typeof(attrs.m) !== 'string') {
+				attrs.m = 'GET';
+			}
+
+			if (attrs.async !== false) {
+				attrs.async = true;
+			}
+
+			if (isNaN(parseFloat(attrs.rt)) || !isFinite(attrs.rt)) {
+				attrs.rt = 0;
+			}
+
+			if (attrs.pd !== true) {
+				attrs.pd = false;
+			}
+
+			if (!attrs.sp) {
+				attrs.sp = "bubble";
+			}
+
+			if (!attrs.sr) {
+				attrs.sr = false;
+			}
+		},
+
+		/**
+		 * Extracts the HTML element that "caused" this Ajax call.
+		 * An Ajax call is usually caused by JavaScript event but maybe be also
+		 * caused by manual usage of the JS API..
+		 *
+		 * @param attrs {Object} - the ajax request attributes
+		 * @return {HTMLElement} - the DOM element
+		 * @private
+		 */
+		_getTarget: function (attrs) {
+			var target;
+			if (attrs.event) {
+				target = attrs.event.target;
+			} else if (!isWindow(attrs.c)) {
+				target = Wicket.$(attrs.c);
+			} else {
+				target = window;
+			}
+			return target;
+		},
+
+		/**
+		 * A helper function that executes an array of handlers (before, success, failure)
+		 *
+		 * @param handlers {Array[Function]} - the handlers to execute
+		 * @private
+		 */
+		_executeHandlers: function (handlers) {
+			if (Array.isArray(handlers)) {
+
+				// cut the handlers argument
+				var args = Array.prototype.slice.call(arguments).slice(1);
+
+				// assumes that the Ajax attributes is always the first argument
+				var attrs = args[0];
+				var that = this._getTarget(attrs);
+
+				for (var i = 0; i < handlers.length; i++) {
+					var handler = handlers[i];
+					if (isFunction(handler)) {
+						handler.apply(that, args);
+					} else {
+						new Function(handler).apply(that, args);
+					}
+				}
+			}
+		},
+
+		/**
+		 * Converts an object (hash) to an array suitable for consumption
+		 * by jQuery.param()
+		 *
+		 * @param {Object} parameters - the object to convert to an array of
+		 *      name -> value pairs.
+		 * @see jQuery.param
+		 * @see jQuery.serializeArray
+		 * @private
+		 */
+		_asParamArray: function(parameters) {
+			var result = [],
+				value,
+				name;
+			if (Array.isArray(parameters)) {
+				result = parameters;
+			}
+			else if (jQuery.isPlainObject(parameters)) {
+				for (name in parameters) {
+					if (name && parameters.hasOwnProperty(name)) {
+						value = parameters[name];
+						result.push({name: name, value: value});
+					}
+				}
+			}
+
+			for (var i = 0; i < result.length; i++) {
+				if (result[i] === null) {
+					result.splice(i, 1);
+					i--;
+				}
+			}
+
+			return result;
+		},
+
+		/**
+		 * Executes all functions to calculate any dynamic extra parameters
+		 *
+		 * @param attrs The Ajax request attributes
+		 * @returns {String} A query string snippet with any calculated request
+		 *  parameters. An empty string if there are no dynamic parameters in attrs
+		 * @private
+		 */
+		_calculateDynamicParameters: function(attrs) {
+			var deps = attrs.dep,
+				params = [];
+
+			for (var i = 0; i < deps.length; i++) {
+				var dep = deps[i],
+					extraParam;
+				if (isFunction(dep)) {
+					extraParam = dep(attrs);
+				} else {
+					extraParam = new Function('attrs', dep)(attrs);
+				}
+				extraParam = this._asParamArray(extraParam);
+				params = params.concat(extraParam);
+			}
+			return params;
+		},
+
+		/**
+		 * Executes or schedules for execution #doAjax()
+		 *
+		 * @param {Object} attrs - the Ajax request attributes configured at the server side
+		 */
+		ajax: function (attrs) {
+			this._initializeDefaults(attrs);
+
+			var res = Wicket.channelManager.schedule(attrs.ch, Wicket.bind(function () {
+				this.doAjax(attrs);
+			}, this));
+			return res !== null ? res: true;
+		},
+
+		/**
+		 * Is an element still present for Ajax requests. 
+		 */
+		_isPresent: function(id) {
+			if (isUndef(id)) {
+				// no id so no check whether present
+				return true;
+			}
+			
+			var element = Wicket.$(id);
+			if (isUndef(element)) {
+				// not present
+				return false;
+			}
+			
+			// present if no attributes at all or not a placeholder
+			return (!element.hasAttribute || !element.hasAttribute('data-wicket-placeholder'));
+		},
+
+		/**
+		 * Handles execution of Ajax calls.
+		 *
+		 * @param {Object} attrs - the Ajax request attributes configured at the server side
+		 */
+		doAjax: function (attrs) {
+
+			var
+				// the headers to use for each Ajax request
+				headers = {
+					'Wicket-Ajax': 'true',
+					'Wicket-Ajax-BaseURL': getAjaxBaseUrl()
+				},
+				
+				url = attrs.u,
+
+				// the request (extra) parameters
+				data = this._asParamArray(attrs.ep),
+
+				self = this,
+
+				// the precondition to use if there are no explicit ones
+				defaultPrecondition = [ function (attributes) {
+					return self._isPresent(attributes.c) && self._isPresent(attributes.f); 
+				}],
+
+				// a context that brings the common data for the success/fialure/complete handlers
+				context = {
+					attrs: attrs,
+
+					// initialize the array for steps (closures that execute each action)
+					steps: []
+				},
+				we = Wicket.Event,
+				topic = we.Topic;
+
+			if (Wicket.Focus.lastFocusId) {
+				// WICKET-6568 might contain non-ASCII
+				headers["Wicket-FocusedElementId"] = Wicket.Form.encode(Wicket.Focus.lastFocusId);
+			}
+
+			self._executeHandlers(attrs.bh, attrs);
+			we.publish(topic.AJAX_CALL_BEFORE, attrs);
+
+			var preconditions = attrs.pre || [];
+			preconditions = defaultPrecondition.concat(preconditions);
+			if (Array.isArray(preconditions)) {
+
+				var that = this._getTarget(attrs);
+
+				for (var p = 0; p < preconditions.length; p++) {
+
+					var precondition = preconditions[p];
+					var result;
+					if (isFunction(precondition)) {
+						result = precondition.call(that, attrs);
+					} else {
+						result = new Function(precondition).call(that, attrs);
+					}
+					if (result === false) {
+						Wicket.Log.info("Ajax request stopped because of precondition check, url: %s", attrs.u);
+						self.done(attrs);
+						return false;
+					}
+				}
+			}
+
+			we.publish(topic.AJAX_CALL_PRECONDITION, attrs);
+
+			if (attrs.f) {
+				// serialize the form with id == attrs.f
+				var form = Wicket.$(attrs.f);
+				data = data.concat(Wicket.Form.serializeForm(form));
+
+				// set the submitting component input name
+				if (attrs.sc) {
+					var scName = attrs.sc;
+					data = data.concat({name: scName, value: 1});
+				}
+			} else if (attrs.c && !isWindow(attrs.c)) {
+				// serialize just the form component with id == attrs.c
+				var el = Wicket.$(attrs.c);
+				data = data.concat(Wicket.Form.serializeElement(el, attrs.sr));
+			}
+			
+			// collect the dynamic extra parameters
+			if (Array.isArray(attrs.dep)) {
+				var dynamicData = this._calculateDynamicParameters(attrs);
+				if (attrs.m.toLowerCase() === 'post') {
+					data = data.concat(dynamicData);
+				} else {
+					var separator = url.indexOf('?') > -1 ? '&' : '?';
+					url = url + separator + jQuery.param(dynamicData);
+				}
+			}
+
+			var wwwFormUrlEncoded; // undefined is jQuery's default
+			if (attrs.mp) {
+				try {
+					var formData = new FormData();
+					for (var i = 0; i < data.length; i++) {
+						formData.append(data[i].name, data[i].value || "");
+					}
+					
+					data = formData;
+					wwwFormUrlEncoded = false;
+				} catch (exception) {
+					Wicket.Log.error("Ajax multipart not supported:", exception);
+				}
+			}
+
+			Wicket.Log.info("Executing Ajax request");
+			Wicket.Log.debug(attrs);
+
+			// execute the request
+			var jqXHR = jQuery.ajax({
+				url: url,
+				type: attrs.m,
+				context: self,
+				processData: wwwFormUrlEncoded,
+				contentType: wwwFormUrlEncoded,
+				
+				beforeSend: function (jqXHR, settings) {
+					self._executeHandlers(attrs.bsh, attrs, jqXHR, settings);
+					we.publish(topic.AJAX_CALL_BEFORE_SEND, attrs, jqXHR, settings);
+
+					if (attrs.i) {
+						// show the indicator
+						Wicket.DOM.showIncrementally(attrs.i);
+					}
+				},
+				data: data,
+				dataType: attrs.dt,
+				async: attrs.async,
+				timeout: attrs.rt,
+				cache: false,
+				headers: headers,
+				success: function(data, textStatus, jqXHR) {
+					if (attrs.wr) {
+						self.processAjaxResponse(data, textStatus, jqXHR, context);
+					} else {
+						self._executeHandlers(attrs.sh, attrs, jqXHR, data, textStatus);
+						we.publish(topic.AJAX_CALL_SUCCESS, attrs, jqXHR, data, textStatus);
+					}
+				},
+				error: function(jqXHR, textStatus, errorMessage) {
+					if (jqXHR.status === 301 && jqXHR.getResponseHeader('Ajax-Location')) {
+						self.processAjaxResponse(data, textStatus, jqXHR, context);
+					} else {
+						self.failure(context, jqXHR, errorMessage, textStatus);
+					}
+				},
+				complete: function (jqXHR, textStatus) {
+
+					context.steps.push(function (notify) {
+						if (attrs.i && context.isRedirecting !== true) {
+							Wicket.DOM.hideIncrementally(attrs.i);
+						}
+
+						self._executeHandlers(attrs.coh, attrs, jqXHR, textStatus);
+						we.publish(topic.AJAX_CALL_COMPLETE, attrs, jqXHR, textStatus);
+
+						self.done(attrs);
+						return FunctionsExecuter.DONE;
+					}.bind(self));
+
+					var executer = new FunctionsExecuter(context.steps);
+					executer.start();
+				}
+			});
+
+			// execute after handlers right after the Ajax request is fired
+			self._executeHandlers(attrs.ah, attrs);
+			we.publish(topic.AJAX_CALL_AFTER, attrs);
+
+			return jqXHR;
+		},
+
+		/**
+		 * Method that processes a manually supplied <ajax-response>.
+		 *
+		 * @param data {XmlDocument} - the <ajax-response> XML document
+		 */
+		process: function(data) {
+			var context =  {
+					attrs: {},
+					steps: []
+				};
+			var xmlDocument = Wicket.Xml.parse(data);
+			this.loadedCallback(xmlDocument, context);
+			var executer = new FunctionsExecuter(context.steps);
+			executer.start();
+		},
+
+		/**
+		 * Method that processes the <ajax-response> in the context of an XMLHttpRequest.
+		 *
+		 * @param data {XmlDocument} - the <ajax-response> XML document
+		 * @param textStatus {String} - the response status as text (e.g. 'success', 'parsererror', etc.)
+		 * @param jqXHR {Object} - the jQuery wrapper around XMLHttpRequest
+		 * @param context {Object} - the request context with the Ajax request attributes and the FunctionExecuter's steps
+		 */
+		processAjaxResponse: function (data, textStatus, jqXHR, context) {
+
+			if (jqXHR.readyState === 4) {
+
+				// first try to get the redirect header
+				var redirectUrl;
+				try {
+					redirectUrl = jqXHR.getResponseHeader('Ajax-Location');
+				} catch (ignore) { // might happen in older mozilla
+				}
+
+				// the redirect header was set, go to new url
+				if (typeof(redirectUrl) !== "undefined" && redirectUrl !== null && redirectUrl !== "") {
+
+					// In case the page isn't really redirected. For example say the redirect is to an octet-stream.
+					// A file download popup will appear but the page in the browser won't change.
+					this.success(context);
+
+					var withScheme  = /^[a-z][a-z0-9+.-]*:\/\//;  // checks whether the string starts with a scheme
+
+					// support/check for non-relative redirectUrl like as provided and needed in a portlet context
+					if (redirectUrl.charAt(0) === '/' || withScheme.test(redirectUrl)) {
+						context.isRedirecting = true;
+						Wicket.Ajax.redirect(redirectUrl);
+					}
+					else {
+						var urlDepth = 0;
+						while (redirectUrl.substring(0, 3) === "../") {
+							urlDepth++;
+							redirectUrl = redirectUrl.substring(3);
+						}
+						// Make this a string.
+						var calculatedRedirect = window.location.pathname;
+						while (urlDepth > -1) {
+							urlDepth--;
+							var i = calculatedRedirect.lastIndexOf("/");
+							if (i > -1) {
+								calculatedRedirect = calculatedRedirect.substring(0, i);
+							}
+						}
+						calculatedRedirect += "/" + redirectUrl;
+
+						context.isRedirecting = true;
+						Wicket.Ajax.redirect(calculatedRedirect);
+					}
+				}
+				else {
+					// no redirect, just regular response
+					Wicket.Log.info("Received ajax response (%s characters)", jqXHR.responseText.length);
+					Wicket.Log.debug(jqXHR.responseXML);
+
+					// invoke the loaded callback with an xml document
+					return this.loadedCallback(data, context);
+				}
+			}
+		},
+
+		// Processes the response
+		loadedCallback: function (envelope, context) {
+			// To process the response, we go through the xml document and add a function for every action (step).
+			// After this is done, a FunctionExecuter object asynchronously executes these functions.
+			// The asynchronous execution is necessary, because some steps might involve loading external javascript,
+			// which must be asynchronous, so that it doesn't block the browser, but we also have to maintain
+			// the order in which scripts are loaded and we have to delay the next steps until the script is
+			// loaded.
+			try {
+				var root = envelope.getElementsByTagName("ajax-response")[0];
+
+				// the root element must be <ajax-response
+				if (isUndef(root) || root.tagName !== "ajax-response") {
+					this.failure(context, null, "Could not find root <ajax-response> element", null);
+					return;
+				}
+
+				var steps = context.steps;
+
+				// go through the ajax response and execute all priority-invocations first
+				for (var i = 0; i < root.childNodes.length; ++i) {
+					var childNode = root.childNodes[i];
+					if (childNode.tagName === "header-contribution") {
+						this.processHeaderContribution(context, childNode);
+					} else if (childNode.tagName === "priority-evaluate") {
+						this.processEvaluation(context, childNode);
+					}
+				}
+
+				// go through the ajax response and for every action (component, js evaluation, header contribution)
+				// ad the proper closure to steps
+				var stepIndexOfLastReplacedComponent = -1;
+				for (var c = 0; c < root.childNodes.length; ++c) {
+					var node = root.childNodes[c];
+
+					if (node.tagName === "component") {
+						if (stepIndexOfLastReplacedComponent === -1) {
+							this.processFocusedComponentMark(context);
+						}
+						stepIndexOfLastReplacedComponent = steps.length;
+						this.processComponent(context, node);
+					} else if (node.tagName === "evaluate") {
+						this.processEvaluation(context, node);
+					} else if (node.tagName === "redirect") {
+						this.processRedirect(context, node);
+					}
+
+				}
+				if (stepIndexOfLastReplacedComponent !== -1) {
+					this.processFocusedComponentReplaceCheck(steps, stepIndexOfLastReplacedComponent);
+				}
+
+				// add the last step, which should trigger the success call the done method on request
+				this.success(context);
+
+			} catch (exception) {
+				this.failure(context, null, exception, null);
+			}
+		},
+
+		// Adds a closure to steps that should be invoked after all other steps have been successfully executed
+		success: function (context) {
+			context.steps.push(function (notify) {
+				Wicket.Log.info("Response processed successfully.");
+
+				var attrs = context.attrs;
+				this._executeHandlers(attrs.sh, attrs, null, null, 'success');
+				Wicket.Event.publish(Wicket.Event.Topic.AJAX_CALL_SUCCESS, attrs, null, null, 'success');
+
+				Wicket.Focus.requestFocus();
+
+				// continue to next step (which should make the processing stop, as success should be the final step)
+				return FunctionsExecuter.DONE;
+			}.bind(this));
+		},
+
+		// On ajax request failure
+		failure: function (context, jqXHR, errorMessage, textStatus) {
+			context.steps.push(function (notify) {
+				if (errorMessage) {
+					Wicket.Log.error("Wicket.Ajax.Call.failure: Error while parsing response: %s", errorMessage);
+				}
+				var attrs = context.attrs;
+				this._executeHandlers(attrs.fh, attrs, jqXHR, errorMessage, textStatus);
+				Wicket.Event.publish(Wicket.Event.Topic.AJAX_CALL_FAILURE, attrs, jqXHR, errorMessage, textStatus);
+
+				return FunctionsExecuter.DONE;
+			}.bind(this));
+		},
+
+		done: function (attrs) {
+			this._executeHandlers(attrs.dh, attrs);
+			Wicket.Event.publish(Wicket.Event.Topic.AJAX_CALL_DONE, attrs);
+
+			Wicket.channelManager.done(attrs.ch);
+		},
+
+		// Adds a closure that replaces a component
+		processComponent: function (context, node) {
+			context.steps.push(function (notify) {
+				// get the component id
+				var compId = node.getAttribute("id");
+
+				// get existing component
+				var element = Wicket.$(compId);
+
+				if (isUndef(element)) {
+					Wicket.Log.error("Wicket.Ajax.Call.processComponent: Component with id '%s' was not found while trying to perform markup update. " +
+						"Make sure you called component.setOutputMarkupId(true) on the component whose markup you are trying to update.", compId);
+				} else {
+					var text = Wicket.DOM.text(node);
+
+					// replace the component
+					Wicket.DOM.replace(element, text);
+				}
+				// continue to next step
+				return FunctionsExecuter.DONE;
+			});
+		},
+
+		/**
+		 * Adds a closure that evaluates javascript code.
+		 * @param context {Object} - the object that brings the executer's steps and the attributes
+		 * @param node {XmlElement} - the <[priority-]evaluate> element with the script to evaluate
+		 */
+		processEvaluation: function (context, node) {
+
+			// used to match evaluation scripts which manually call FunctionsExecuter's notify() when ready
+			var scriptWithIdentifierR = new RegExp("\\(function\\(\\)\\{([a-zA-Z_]\\w*)\\|((.|\\n)*)?\\}\\)\\(\\);$");
+
+			/**
+			 * A regex used to split the text in (priority-)evaluate elements in the Ajax response
+			 * when there are scripts which require manual call of 'FunctionExecutor#notify()'
+			 * @type {RegExp}
+			 */
+			var scriptSplitterR = new RegExp("\\(function\\(\\)\\{[\\s\\S]*?}\\)\\(\\);", 'gi');
+
+			// get the javascript body
+			var text = Wicket.DOM.text(node);
+
+			// aliases to improve performance
+			var steps = context.steps;
+			var log = Wicket.Log;
+
+			var evaluateWithManualNotify = function (parameters, body) {
+				return function(notify) {
+					var toExecute = "(function(" + parameters + ") {" + body + "})";
+
+					try {
+						// do the evaluation in global scope
+						var f = window.eval(toExecute);
+						f(notify);
+					} catch (exception) {
+						log.error("Wicket.Ajax.Call.processEvaluation: Exception evaluating javascript: %s", text, exception);
+					}
+					return FunctionsExecuter.ASYNC;
+				};
+			};
+
+			var evaluate = function (script) {
+				return function(notify) {
+					// just evaluate the javascript
+					try {
+						// do the evaluation in global scope
+						window.eval(script);
+					} catch (exception) {
+						log.error("Ajax.Call.processEvaluation: Exception evaluating javascript: %s", text, exception);
+					}
+					// continue to next step
+					return FunctionsExecuter.DONE;
+				};
+			};
+
+			// test if the javascript is in form of identifier|code
+			// if it is, we allow for letting the javascript decide when the rest of processing will continue
+			// by invoking identifier();. This allows usage of some asynchronous/deferred logic before the next script
+			// See WICKET-5039
+			if (scriptWithIdentifierR.test(text)) {
+				var scripts = [];
+				var scr;
+				while ( (scr = scriptSplitterR.exec(text) ) !== null ) {
+					scripts.push(scr[0]);
+				}
+
+				for (var s = 0; s < scripts.length; s++) {
+					var script = scripts[s];
+					if (script) {
+						var scriptWithIdentifier = script.match(scriptWithIdentifierR);
+						if (scriptWithIdentifier) {
+							steps.push(evaluateWithManualNotify(scriptWithIdentifier[1], scriptWithIdentifier[2]));
+						}
+						else {
+							steps.push(evaluate(script));
+						}
+					}
+				}
+			} else {
+				steps.push(evaluate(text));
+			}
+		},
+
+		// Adds a closure that processes a header contribution
+		processHeaderContribution: function (context, node) {
+			var c = Wicket.Head.Contributor;
+			c.processContribution(context, node);
+		},
+
+		// Adds a closure that processes a redirect
+		processRedirect: function (context, node) {
+			var text = Wicket.DOM.text(node);
+			Wicket.Log.info("Redirecting to: %s", text);
+			context.isRedirecting = true;
+			Wicket.Ajax.redirect(text);
+		},
+
+		// mark the focused component so that we know if it has been replaced by response
+		processFocusedComponentMark: function (context) {
+			context.steps.push(function (notify) {
+				Wicket.Focus.markFocusedComponent();
+
+				// continue to next step
+				return FunctionsExecuter.DONE;
+			});
+		},
+
+		// detect if the focused component was replaced
+		processFocusedComponentReplaceCheck: function (steps, lastReplaceComponentStep) {
+			// add this step imediately after all components have been replaced
+			steps.splice(lastReplaceComponentStep + 1, 0, function (notify) {
+				Wicket.Focus.checkFocusedComponentReplaced();
+
+				// continue to next step
+				return FunctionsExecuter.DONE;
+			});
+		}
+	};
+
+
+	/**
+	 * Throttler's purpose is to make sure that ajax requests wont be fired too often.
+	 */
+	Wicket.ThrottlerEntry = Wicket.Class.create();
+
+	Wicket.ThrottlerEntry.prototype = {
+		initialize: function (func) {
+			this.func = func;
+			this.timestamp = new Date().getTime();
+			this.timeoutVar = undefined;
+		},
+
+		getTimestamp: function () {
+			return this.timestamp;
+		},
+
+		getFunc: function () {
+			return this.func;
+		},
+
+		setFunc: function (func) {
+			this.func = func;
+		},
+
+		getTimeoutVar: function () {
+			return this.timeoutVar;
+		},
+
+		setTimeoutVar: function (timeoutVar) {
+			this.timeoutVar = timeoutVar;
+		}
+	};
+
+	Wicket.Throttler = Wicket.Class.create();
+
+	// declare it as static so that it can be shared between Throttler instances
+	Wicket.Throttler.entries = [];
+
+	Wicket.Throttler.prototype = {
+
+		/* "postponeTimerOnUpdate" is an optional parameter. If it is set to true, then the timer is
+		   reset each time the throttle function gets called. Use this behaviour if you want something
+		   to happen at X milliseconds after the *last* call to throttle.
+		   If the parameter is not set, or set to false, then the timer is not reset. */
+		initialize: function (postponeTimerOnUpdate) {
+			this.postponeTimerOnUpdate = postponeTimerOnUpdate;
+		},
+
+		throttle: function (id, millis, func) {
+			var entries = Wicket.Throttler.entries;
+			var entry = entries[id];
+			var me = this;
+			if (typeof(entry) === 'undefined') {
+				entry = new Wicket.ThrottlerEntry(func);
+				entry.setTimeoutVar(window.setTimeout(function() { me.execute(id); }, millis));
+				entries[id] = entry;
+			} else {
+				entry.setFunc(func);
+				if (this.postponeTimerOnUpdate)
+				{
+					window.clearTimeout(entry.getTimeoutVar());
+					entry.setTimeoutVar(window.setTimeout(function() { me.execute(id); }, millis));
+				}
+			}
+		},
+
+		execute: function (id) {
+			var entries = Wicket.Throttler.entries;
+			var entry = entries[id];
+			if (typeof(entry) !== 'undefined') {
+				var func = entry.getFunc();
+				entries[id] = undefined;
+				return func();
+			}
+		}
+	};
+
+	Wicket = extend(true, Wicket, {
+
+		channelManager: new Wicket.ChannelManager(),
+
+		throttler: new Wicket.Throttler(),
+
+		$: function (arg) {
+			return Wicket.DOM.get(arg);
+		},
+
+		/**
+		 * returns if the element belongs to current document
+		 * if the argument is not element, function returns true
+		 */
+		$$: function (element) {
+			return Wicket.DOM.inDoc(element);
+		},
+
+		/**
+		 * Merges two objects. Values of the second will overwrite values of the first.
+		 *
+		 * @param {Object} object1 - the first object to merge
+		 * @param {Object} object2 - the second object to merge
+		 * @return {Object} a new object with the values of object1 and object2
+		 */
+		merge: function(object1, object2) {
+			return extend({}, object1, object2);
+		},
+
+		/**
+		 * Takes a function and returns a new one that will always have a particular context, i.e. 'this' will be the passed context.
+		 *
+		 * @param {Function} fn - the function which context will be set
+		 * @param {Object} context - the new context for the function
+		 * @return {Function} the original function with the changed context
+		 */
+		bind: function(fn, context) {
+			return fn.bind(context);
+		},
+
+		Xml: {
+			parse: function (text) {
+				var parser = new DOMParser();
+
+				var xmlDocument = parser.parseFromString(text, "text/xml");
+
+				return xmlDocument;
+			}
+		},
+
+		/**
+		 * Form serialization
+		 *
+		 * To post a form using Ajax Wicket first needs to serialize it, which means composing a string
+		 * from form elments names and values. The string will then be set as body of POST request.
+		 */
+
+		Form: {
+			encode: function (text) {
+				if (window.encodeURIComponent) {
+					return window.encodeURIComponent(text);
+				} else {
+					return window.escape(text);
+				}
+			},
+
+			/**
+			 * Serializes HTMLFormSelectElement to URL encoded key=value string.
+			 *
+			 * @param select {HTMLFormSelectElement} - the form element to serialize
+			 * @return an object of key -> value pair where 'value' can be an array of Strings if the select is .multiple,
+			 *		or empty object if the form element is disabled.
+			 */
+			serializeSelect: function (select){
+				var result = [];
+				if (select) {
+					var $select = jQuery(select);
+					if ($select.length > 0 && $select.prop('disabled') === false) {
+						var name = $select.prop('name');
+						var values = $select.val();
+						if (Array.isArray(values)) {
+							for (var v = 0; v < values.length; v++) {
+								var value = values[v];
+								result.push( { name: name, value: value } );
+							}
+						} else {
+							result.push( { name: name, value: values } );
+						}
+					}
+				}
+				return result;
+			},
+
+			/**
+			 * Serializes a form element to an array with a single element - an object
+			 * with two keys - <em>name</em> and <em>value</em>.
+			 *
+			 * Example: [{"name": "searchTerm", "value": "abc"}].
+			 *
+			 * Note: this function intentionally ignores image and submit inputs.
+			 *
+			 * @param input {HtmlFormElement} - the form element to serialize
+			 * @return the URL encoded key=value pair or empty string if the form element is disabled.
+			 */
+			serializeInput: function (input) {
+				var result = [];
+				if (input && input.type) {
+					var $input = jQuery(input);
+					
+					if (input.type === 'file') {
+						for (var f = 0; f < input.files.length; f++) {
+							result.push({"name" : input.name, "value" : input.files[f]});
+						}
+					} else if (!(input.type === 'image' || input.type === 'submit')) {
+						result = $input.serializeArray();
+					}
+				}
+				return result;
+			},
+
+			/**
+			 * A hash of HTML form element to exclude from serialization
+			 * As key the element's id is being used.
+			 * As value - the string "true".
+			 */
+			excludeFromAjaxSerialization: {
+			},
+
+			/**
+			 * Serializes a form element by checking its type and delegating the work to
+			 * a more specific function.
+			 *
+			 * The form element will be ignored if it is registered as excluded in
+			 * <em>Wicket.Form.excludeFromAjaxSerialization</em>
+			 *
+			 * @param element {HTMLFormElement} - the form element to serialize. E.g. HTMLInputElement
+			 * @param serializeRecursively {Boolean} - a flag indicating whether to collect (submit) the
+			 * 			name/value pairs for all HTML form elements children of the HTML element with
+			 * 			the JavaScript listener
+			 * @return An array with a single element - an object with two keys - <em>name</em> and <em>value</em>.
+			 */
+			serializeElement: function(element, serializeRecursively) {
+
+				if (!element) {
+					return [];
+				}
+				else if (typeof(element) === 'string') {
+					element = Wicket.$(element);
+				}
+
+				if (Wicket.Form.excludeFromAjaxSerialization && element.id && Wicket.Form.excludeFromAjaxSerialization[element.id] === "true") {
+					return [];
+				}
+
+				var tag = element.tagName.toLowerCase();
+				if (tag === "select") {
+					return Wicket.Form.serializeSelect(element);
+				} else if (tag === "input" || tag === "textarea") {
+					return Wicket.Form.serializeInput(element);
+				} else {
+					var result = [];
+					if (serializeRecursively) {
+						var elements = nodeListToArray(element.getElementsByTagName("input"));
+						elements = elements.concat(nodeListToArray(element.getElementsByTagName("select")));
+						elements = elements.concat(nodeListToArray(element.getElementsByTagName("textarea")));
+
+						for (var i = 0; i < elements.length; ++i) {
+							var el = elements[i];
+							if (el.name && el.name !== "") {
+								result = result.concat(Wicket.Form.serializeElement(el, serializeRecursively));
+							}
+						}
+					}
+					return result;
+				}
+			},
+
+			serializeForm: function (form) {
+				var result = [],
+					elements;
+
+				if (form) {
+					if (form.tagName.toLowerCase() === 'form') {
+						elements = form.elements;
+					} else {
+						do {
+							form = form.parentNode;
+						} while (form.tagName.toLowerCase() !== "form" && form.tagName.toLowerCase() !== "body");
+
+						elements = nodeListToArray(form.getElementsByTagName("input"));
+						elements = elements.concat(nodeListToArray(form.getElementsByTagName("select")));
+						elements = elements.concat(nodeListToArray(form.getElementsByTagName("textarea")));
+					}
+				}
+
+				for (var i = 0; i < elements.length; ++i) {
+					var el = elements[i];
+					if (el.name && el.name !== "") {
+						result = result.concat(Wicket.Form.serializeElement(el, false));
+					}
+				}
+				return result;
+			},
+
+			serialize: function (element, dontTryToFindRootForm) {
+				if (typeof(element) === 'string') {
+					element = Wicket.$(element);
+				}
+
+				if (element.tagName.toLowerCase() === "form") {
+					return Wicket.Form.serializeForm(element);
+				} else {
+					// try to find a form in DOM parents
+					var elementBck = element;
+
+					if (dontTryToFindRootForm !== true) {
+						do {
+							element = element.parentNode;
+						} while(element.tagName.toLowerCase() !== "form" && element.tagName.toLowerCase() !== "body");
+					}
+
+					if (element.tagName.toLowerCase() === "form"){
+						return Wicket.Form.serializeForm(element);
+					} else {
+						// there is not form in dom hierarchy
+						// simulate it
+						var form = document.createElement("form");
+						var parent = elementBck.parentNode;
+
+						parent.replaceChild(form, elementBck);
+						form.appendChild(elementBck);
+						var result = Wicket.Form.serializeForm(form);
+						parent.replaceChild(elementBck, form);
+
+						return result;
+					}
+				}
+			}
+		},
+
+		/**
+		 * DOM nodes serialization functionality
+		 *
+		 * The purpose of these methods is to return a string representation
+		 * of the DOM tree.
+		 */
+		DOM: {
+
+			/**
+			 * Shows an element
+			 * @param {HTMLElement | String} e   The HTML element (or its id) to show
+			 * @param {String} display  The value of CSS display property to use,
+			 *      e.g. 'block', 'inline'. Optional
+			 */
+			show: function (e, display) {
+				e = Wicket.$(e);
+				if (e !== null) {
+					if (isUndef(display)) {
+						// no explicit 'display' value is requested so
+						// use jQuery. It has special logic to decide which is the
+						// best value for an HTMLElement
+						jQuery(e).show();
+					} else {
+						e.style.display = display;
+					}
+				}
+			},
+
+			/** hides an element */
+			hide: function (e) {
+				e = Wicket.$(e);
+				if (e !== null) {
+					jQuery(e).hide();
+				}
+			},
+
+			/** call-counting implementation of Wicket.DOM.show() */
+			showIncrementally: function (e) {
+				e = Wicket.$(e);
+				if (e === null) {
+					return;
+				}
+				var count = e.getAttribute("showIncrementallyCount");
+				count = parseInt(isUndef(count) ? 0 : count, 10);
+				if (count >= 0) {
+					Wicket.DOM.show(e);
+				}
+				e.setAttribute("showIncrementallyCount", count + 1);
+			},
+
+			/** call-counting implementation of Wicket.DOM.hide() */
+			hideIncrementally: function(e) {
+				e = Wicket.$(e);
+				if (e === null) {
+					return;
+				}
+				var count = e.getAttribute("showIncrementallyCount");
+				count = parseInt(isUndef(count) ? 0 : count - 1, 10);
+				if (count <= 0) {
+					Wicket.DOM.hide(e);
+				}
+				e.setAttribute("showIncrementallyCount", count);
+			},
+
+			get: function (arg) {
+				if (isUndef(arg)) {
+					return null;
+				}
+				if (arguments.length > 1) {
+					var e = [];
+					for (var i = 0; i < arguments.length; i++) {
+						e.push(Wicket.DOM.get(arguments[i]));
+					}
+					return e;
+				} else if (typeof arg === 'string') {
+					return document.getElementById(arg);
+				} else {
+					return arg;
+				}
+			},
+
+			/**
+			 * returns if the element belongs to current document
+			 * if the argument is not element, function returns true
+			 */
+			inDoc: function (element) {
+				if (isWindow(element)) {
+					return true;
+				}
+				if (typeof(element) === "string") {
+					element = Wicket.$(element);
+				}
+				if (isUndef(element) || isUndef(element.tagName)) {
+					return false;
+				}
+
+				var id = element.getAttribute('id');
+				if (isUndef(id) || id === "") {
+					return element.ownerDocument === document;
+				}
+				else {
+					return document.getElementById(id) === element;
+				}
+			},
+
+			/**
+			 * A cross-browser method that replaces the markup of an element. The behavior
+			 * is similar to calling element.outerHtml=text in internet explorer. However
+			 * this method also takes care of executing javascripts within the markup on
+			 * browsers that don't do that automatically.
+			 * Also this method takes care of replacing table elements (tbody, tr, td, thead)
+			 * on browser where it's not supported when using outerHTML (IE).
+			 *
+			 * This method sends notifications to all subscribers for channels with names
+			 * '/dom/node/removing' with the element that is going to be replaced and
+			 * '/dom/node/added' with the newly created element (the replacement).
+			 *
+			 * Note: the 'to be replaced' element must have an 'id' attribute
+			 */
+			replace: function (element, text) {
+
+				var we = Wicket.Event;
+				var topic = we.Topic;
+
+				we.publish(topic.DOM_NODE_REMOVING, element);
+
+				if (element.tagName.toLowerCase() === "title") {
+					// match the text between the tags
+					var titleText = />(.*?)</.exec(text)[1];
+					document.title = titleText;
+					return;
+				} else {
+					// jQuery 1.9+ expects '<' as the very first character in text
+					var cleanedText = jQuery.trim(text);
+
+					var $newElement = jQuery(cleanedText);
+					jQuery(element).replaceWith($newElement);
+				}
+
+				var newElement = Wicket.$(element.id);
+				if (newElement) {
+					we.publish(topic.DOM_NODE_ADDED, newElement);
+				}
+			},
+
+			// Method for serializing DOM nodes to string
+			// original taken from Tacos (http://tacoscomponents.jot.com)
+			serializeNodeChildren: function (node) {
+				if (isUndef(node)) {
+					return "";
+				}
+				var result = [];
+
+				if (node.childNodes.length > 0) {
+					for (var i = 0; i < node.childNodes.length; i++) {
+						var thisNode = node.childNodes[i];
+						switch (thisNode.nodeType) {
+							case 1: // ELEMENT_NODE
+							case 5: // ENTITY_REFERENCE_NODE
+								result.push(this.serializeNode(thisNode));
+								break;
+							case 8: // COMMENT
+								result.push("<!--");
+								result.push(thisNode.nodeValue);
+								result.push("-->");
+								break;
+							case 4: // CDATA_SECTION_NODE
+								result.push("<![CDATA[");
+								result.push(thisNode.nodeValue);
+								result.push("]]>");
+								break;
+							case 3: // TEXT_NODE
+							case 2: // ATTRIBUTE_NODE
+								result.push(thisNode.nodeValue);
+								break;
+							default:
+								break;
+						}
+					}
+				} else {
+					result.push(node.textContent || node.text);
+				}
+				return result.join("");
+			},
+
+			serializeNode: function (node){
+				if (isUndef(node)) {
+					return "";
+				}
+				var result = [];
+				result.push("<");
+				result.push(node.nodeName);
+
+				if (node.attributes && node.attributes.length > 0) {
+
+					for (var i = 0; i < node.attributes.length; i++) {
+						// serialize the attribute only if it has meaningful value that is not inherited
+						if (node.attributes[i].nodeValue && node.attributes[i].specified) {
+							result.push(" ");
+							result.push(node.attributes[i].name);
+							result.push("=\"");
+							result.push(node.attributes[i].value);
+							result.push("\"");
+						}
+					}
+				}
+
+				result.push(">");
+				result.push(Wicket.DOM.serializeNodeChildren(node));
+				result.push("</");
+				result.push(node.nodeName);
+				result.push(">");
+				return result.join("");
+			},
+
+			// Utility function that determines whether given element is part of the current document
+			containsElement: function (element) {
+				var id = element.getAttribute("id");
+				if (id) {
+					return Wicket.$(id) !== null;
+				}
+				else {
+					return false;
+				}
+			},
+
+			/**
+			 * Reads the text from the node's children nodes.
+			 * Used instead of jQuery.text() because it is very slow in IE10/11.
+			 * WICKET-5132, WICKET-5510
+			 * @param node {DOMElement} the root node
+			 */
+			text: function (node) {
+				if (isUndef(node)) {
+					return "";
+				}
+
+				var result = [];
+
+				if (node.childNodes.length > 0) {
+					for (var i = 0; i < node.childNodes.length; i++) {
+						var thisNode = node.childNodes[i];
+						switch (thisNode.nodeType) {
+							case 1: // ELEMENT_NODE
+							case 5: // ENTITY_REFERENCE_NODE
+								result.push(this.text(thisNode));
+								break;
+							case 3: // TEXT_NODE
+							case 4: // CDATA_SECTION_NODE
+								result.push(thisNode.nodeValue);
+								break;
+							default:
+								break;
+						}
+					}
+				} else {
+					result.push(node.textContent || node.text);
+				}
+
+				return result.join("");
+			}
+		},
+
+		/**
+		 * The Ajax class handles low level details of creating XmlHttpRequest objects,
+		 * as well as registering and execution of pre-call, post-call and failure handlers.
+		 */
+		 Ajax: {
+
+			Call: Wicket.Ajax.Call,
+
+			/**
+			 * Aborts the default event if attributes request it
+			 *
+			 * @param {Object} attrs - the Ajax request attributes configured at the server side
+			 */
+			_handleEventCancelation: function(attrs) {
+				var evt = attrs.event;
+				if (evt) {
+					if (attrs.pd) {
+						try {
+							evt.preventDefault();
+						} catch (ignore) {
+							// WICKET-4986
+							// jquery fails 'member not found' with calls on busy channel
+						}
+					}
+
+					if (attrs.sp === "stop") {
+						Wicket.Event.stop(evt);
+					} else if (attrs.sp === "stopImmediate") {
+						Wicket.Event.stop(evt, true);
+					}
+				}
+			},
+
+			get: function (attrs) {
+
+				attrs.m = 'GET';
+
+				return Wicket.Ajax.ajax(attrs);
+			},
+
+			post: function (attrs) {
+
+				attrs.m = 'POST';
+
+				return Wicket.Ajax.ajax(attrs);
+			},
+
+			ajax: function(attrs) {
+
+				attrs.c = attrs.c || window;
+				attrs.e = attrs.e || [ 'domready' ];
+
+				if (!Array.isArray(attrs.e)) {
+					attrs.e = [ attrs.e ];
+				}
+
+				Array.prototype.forEach.call(attrs.e, function (evt) {
+					Wicket.Event.add(attrs.c, evt, function (jqEvent, data) {
+						var call = new Wicket.Ajax.Call();
+						var attributes = extend({}, attrs);
+
+						if (evt !== "domready") {
+							attributes.event = Wicket.Event.fix(jqEvent);
+							if (data) {
+								attributes.event.extraData = data;
+							}
+						}
+
+						call._executeHandlers(attributes.ih, attributes);
+						Wicket.Event.publish(Wicket.Event.Topic.AJAX_CALL_INIT, attributes);
+
+						var throttlingSettings = attributes.tr;
+						if (throttlingSettings) {
+							var postponeTimerOnUpdate = throttlingSettings.p || false;
+							var throttler = new Wicket.Throttler(postponeTimerOnUpdate);
+							throttler.throttle(throttlingSettings.id, throttlingSettings.d,
+								Wicket.bind(function () {
+									call.ajax(attributes);
+								}, this));
+						}
+						else {
+							call.ajax(attributes);
+						}
+						if (evt !== "domready") {
+							Wicket.Ajax._handleEventCancelation(attributes);
+						}
+					}, null, attrs.sel);
+				});
+			},
+			
+			process: function(data) {
+				var call = new Wicket.Ajax.Call();
+				call.process(data);
+			},
+
+			/**
+			 * An abstraction over native window.location.replace() to be able to suppress it for unit tests
+			 *
+			 * @param url The url to redirect to
+			 */
+			redirect: function(url) {
+				window.location = url;
+			}
+		},
+
+		/**
+		 * Header contribution allows component to include custom javascript and stylesheet.
+		 *
+		 * Header contributor takes the code component would render to page head and
+		 * interprets it just as browser would when loading a page.
+		 * That means loading external javascripts and stylesheets, executing inline
+		 * javascript and aplying inline styles.
+		 *
+		 * Header contributor also filters duplicate entries, so that it doesn't load/process
+		 * resources that have been loaded.
+		 * For inline styles and javascript, element id is used to filter out duplicate entries.
+		 * For stylesheet and javascript references, url is used for filtering.
+		 */
+		Head: {
+			Contributor: {
+
+				// Parses the header contribution element (returns a DOM tree with the contribution)
+				parse: function (headerNode) {
+					// the header contribution is stored as CDATA section in the header-contribution element,
+					// we need to parse it since each header contribution needs to be treated separately
+					
+					// get the header contribution text and unescape it if necessary
+					var text = Wicket.DOM.text(headerNode);
+
+					// build a DOM tree of the contribution
+					var xmldoc = Wicket.Xml.parse(text);
+					return xmldoc;
+				},
+
+				// checks whether the passed node is the special "parsererror"
+				// created by DOMParser if there is a error in XML parsing
+				// TODO: move out of the API section
+				_checkParserError: function (node) {
+					var result = false;
+
+					if (!isUndef(node.tagName) && node.tagName.toLowerCase() === "parsererror") {
+						Wicket.Log.error("Error in parsing: %s", node.textContent);
+						result = true;
+					}
+					return result;
+				},
+
+				// Processes the parsed header contribution
+				processContribution: function (context, headerNode) {
+					var xmldoc = this.parse(headerNode);
+					var rootNode = xmldoc.documentElement;
+
+					// Firefox and Opera reports the error in the documentElement
+					if (this._checkParserError(rootNode)) {
+						return;
+					}
+
+					// go through the individual elements and process them according to their type
+					for (var i = 0; i < rootNode.childNodes.length; i++) {
+						var node = rootNode.childNodes[i];
+
+						// Chromium reports the error as a child node
+						if (this._checkParserError(node)) {
+							return;
+						}
+
+						if (!isUndef(node.tagName)) {
+							var name = node.tagName.toLowerCase();
+
+							// it is possible that a reference is surrounded by a <wicket:link
+							// in that case, we need to find the inner element
+							if (name === "wicket:link") {
+								for (var j = 0; j < node.childNodes.length; ++j) {
+									var childNode = node.childNodes[j];
+									// try to find a regular node inside wicket:link
+									if (childNode.nodeType === 1) {
+										node = childNode;
+										name = node.tagName.toLowerCase();
+										break;
+									}
+								}
+							}
+
+							// process the element
+							if (name === "link") {
+								this.processLink(context, node);
+							} else if (name === "script") {
+								this.processScript(context, node);
+							} else if (name === "style") {
+								this.processStyle(context, node);
+							} else if (name === "meta") {
+								this.processMeta(context, node);
+							}
+						} else if (node.nodeType === 8) { // comment type
+							this.processComment(context, node);
+						}
+					}
+				},
+
+				// Process an external stylesheet element
+				processLink: function (context, node) {
+					context.steps.push(function (notify) {
+						var res = Wicket.Head.containsElement(node, "href");
+						var oldNode = res.oldNode;
+						if (res.contains) {
+							// an element with same href attribute is in document, skip it
+							return FunctionsExecuter.DONE;
+						} else if (oldNode) {
+							// remove another external element with the same id but different href
+							oldNode.parentNode.removeChild(oldNode);
+						}
+
+						// create link element
+						var css = Wicket.Head.createElement("link");
+
+						// copy supplied attributes only.
+						var attributes = jQuery(node).prop("attributes");
+						var $css = jQuery(css);
+						Array.prototype.forEach.call(attributes, function(attr) {
+							$css.attr(attr.name, attr.value);
+						});
+
+						// add element to head
+						Wicket.Head.addElement(css);
+
+						// cross browser way to check when the css is loaded
+						// taken from http://www.backalleycoder.com/2011/03/20/link-tag-css-stylesheet-load-event/
+						// this makes a second GET request to the css but it gets it either from the cache or
+						// downloads just the first several bytes and realizes that the MIME is wrong and ignores the rest
+						var img = document.createElement('img');
+						var notifyCalled = false;
+						img.onerror = function () {
+							if (!notifyCalled) {
+								notifyCalled = true;
+								notify();
+							}
+						};
+						img.src = css.href;
+						if (img.complete) {
+						  if (!notifyCalled) {
+							notifyCalled = true;
+							notify();
+						  }
+						}
+
+						return FunctionsExecuter.ASYNC;
+					});
+				},
+
+				// Process an inline style element
+				processStyle: function (context, node) {
+					context.steps.push(function (notify) {
+						// if element with same id is already in document, skip it
+						if (Wicket.DOM.containsElement(node)) {
+							return FunctionsExecuter.DONE;
+						}
+						// serialize the style to string
+						var content = Wicket.DOM.serializeNodeChildren(node);
+
+						// create style element
+						var style = Wicket.Head.createElement("style");
+
+						// copy id attribute
+						style.id = node.getAttribute("id");
+
+						var textNode = document.createTextNode(content);
+						style.appendChild(textNode);
+
+						Wicket.Head.addElement(style);
+
+						// continue to next step
+						return FunctionsExecuter.DONE;
+					});
+				},
+
+				// Process a script element (both inline and external)
+				processScript: function (context, node) {
+					context.steps.push(function (notify) {
+
+						if (!node.getAttribute("src") && Wicket.DOM.containsElement(node)) {
+							// if an inline element with same id is already in document, skip it
+							return FunctionsExecuter.DONE;
+						} else {
+							var res = Wicket.Head.containsElement(node, "src");
+							var oldNode = res.oldNode;
+							if (res.contains) {
+								// an element with same src attribute is in document, skip it
+								return FunctionsExecuter.DONE;
+							} else if (oldNode) {
+								// remove another external element with the same id but different src
+								oldNode.parentNode.removeChild(oldNode);
+							}
+						}
+
+						// determine whether it is external javascript (has src attribute set)
+						var src = node.getAttribute("src");
+
+						if (src !== null && src !== "") {
+
+							// convert the XML node to DOM node
+							var scriptDomNode = document.createElement("script");
+
+							var attrs = node.attributes;
+							for (var a = 0; a < attrs.length; a++) {
+								var attr = attrs[a];
+								scriptDomNode[attr.name] = attr.value;
+							}
+
+							var onScriptReady = function () {
+								notify();
+							};
+
+							// first check for feature support
+							if (typeof(scriptDomNode.onload) !== 'undefined') {
+								scriptDomNode.onload = onScriptReady;
+							} else if (typeof(scriptDomNode.onreadystatechange) !== 'undefined') {
+								scriptDomNode.onreadystatechange = function () {
+									if (scriptDomNode.readyState === 'loaded' || scriptDomNode.readyState === 'complete') {
+										onScriptReady();
+									}
+								};
+							} else {
+								// as a final resort notify after the current function execution
+								window.setTimeout(onScriptReady, 10);
+							}
+
+							Wicket.Head.addElement(scriptDomNode);
+
+							return FunctionsExecuter.ASYNC;
+						} else {
+							// serialize the element content to string
+							var text = Wicket.DOM.serializeNodeChildren(node);
+							// get rid of prefix and suffix, they are not eval-d correctly
+							text = text.replace(/^\n\/\*<!\[CDATA\[\*\/\n/, "");
+							text = text.replace(/\n\/\*\]\]>\*\/\n$/, "");
+
+							var id = node.getAttribute("id");
+							var type = node.getAttribute("type");
+
+							if (typeof(id) === "string" && id.length > 0) {
+								// add javascript to document head
+								Wicket.Head.addJavascript(text, id, "", type);
+							} else {
+								try {
+									// do the evaluation in global scope
+									window.eval(text);
+								} catch (e) {
+									Wicket.Log.error("Wicket.Head.Contributor.processScript: %s", text, e);
+								}
+							}
+
+							// continue to next step
+							return FunctionsExecuter.DONE;
+						}
+					});
+				},
+
+				processMeta: function (context, node) {
+					context.steps.push(function (notify) {
+						var meta = Wicket.Head.createElement("meta"),
+							$meta = jQuery(meta),
+							attrs = jQuery(node).prop("attributes"),
+							name = node.getAttribute("name");
+
+						if(name) {
+							jQuery('meta[name="' + name + '"]').remove();
+						}
+						Array.prototype.forEach.call(attrs, function(attr) {
+							$meta.attr(attr.name, attr.value);
+						});
+
+						Wicket.Head.addElement(meta);
+
+						return FunctionsExecuter.DONE;
+					});
+				},
+
+				// process (conditional) comments
+				processComment: function (context, node) {
+					context.steps.push(function (notify) {
+						var comment = document.createComment(node.nodeValue);
+						Wicket.Head.addElement(comment);
+						return FunctionsExecuter.DONE;
+					});
+				}
+			},
+
+			// Creates an element in document
+			createElement: function (name) {
+				if (isUndef(name) || name === '') {
+					Wicket.Log.error('Cannot create an element without a name');
+					return;
+				}
+				return document.createElement(name);
+			},
+
+			// Adds the element to page head
+			addElement: function (element) {
+				var headItems = document.querySelector('head meta[name="wicket.header.items"]');
+				if (headItems) {
+					headItems.parentNode.insertBefore(element, headItems);
+				} else {
+					var head = document.querySelector("head");
+
+					if (head) {
+						head.appendChild(element);
+					}
+				}
+			},
+
+			// Returns true, if the page head contains element that has attribute with
+			// name mandatoryAttribute same as the given element and their names match.
+			//
+			// e.g. Wicket.Head.containsElement(myElement, "src") return true, if there
+			// is an element in head that is of same type as myElement, and whose src
+			// attribute is same as myElement.src.
+			containsElement: function (element, mandatoryAttribute) {
+				var attr = element.getAttribute(mandatoryAttribute);
+				if (isUndef(attr) || attr === "") {
+					return {
+						contains: false
+					};
+				}
+
+				var elementTagName = element.tagName.toLowerCase();
+				var elementId = element.getAttribute("id");
+				var head = document.getElementsByTagName("head")[0];
+
+				if (elementTagName === "script") {
+					head = document;
+				}
+
+				var nodes = head.getElementsByTagName(elementTagName);
+
+				for (var i = 0; i < nodes.length; ++i) {
+					var node = nodes[i];
+
+					// check node names and mandatory attribute values
+					// we also have to check for attribute name that is suffixed by "_".
+					// this is necessary for filtering script references
+					if (node.tagName.toLowerCase() === elementTagName) {
+
+						var loadedUrl = node.getAttribute(mandatoryAttribute);
+						var loadedUrl_ = node.getAttribute(mandatoryAttribute+"_");
+						if (loadedUrl === attr || loadedUrl_ === attr) {
+							return {
+								contains: true
+							};
+						} else if (elementId && elementId === node.getAttribute("id")) {
+							return {
+								contains: false,
+								oldNode: node
+							};
+						}
+					}
+				}
+				return {
+					contains: false
+				};
+			},
+
+			// Adds a javascript element to page header.
+			// The fakeSrc attribute is used to filter out duplicate javascript references.
+			// External javascripts are loaded using xmlhttprequest. Then a javascript element is created and the
+			// javascript body is used as text for the element. For javascript references, wicket uses the src
+			// attribute to filter out duplicates. However, since we set the body of the element, we can't assign
+			// also a src value. Therefore we put the url to the src_ (notice the underscore)  attribute.
+			// Wicket.Head.containsElement is aware of that and takes also the underscored attributes into account.
+			addJavascript: function (content, id, fakeSrc, type) {
+				var script = Wicket.Head.createElement("script");
+				if (id) {
+					script.id = id;
+				}
+
+				// WICKET-5047: encloses the content with a try...catch... block if the content is javascript
+				// content is considered javascript if mime-type is empty (html5's default) or is 'text/javascript'
+				if (!type || type.toLowerCase() === "text/javascript") {
+					type = "text/javascript";
+					content = 'try{'+content+'}catch(e){Wicket.Log.error(e);}';
+				}
+
+				script.setAttribute("src_", fakeSrc);
+				script.setAttribute("type", type);
+
+				// set the javascript as element content
+				if (null === script.canHaveChildren || script.canHaveChildren) {
+					var textNode = document.createTextNode(content);
+					script.appendChild(textNode);
+				} else {
+					script.text = content;
+				}
+				Wicket.Head.addElement(script);
+			},
+
+			// Goes through all script elements contained by the element and add them to head
+			addJavascripts: function (element, contentFilter) {
+				function add(element) {
+					var src = element.getAttribute("src");
+					var type = element.getAttribute("type");
+
+					// if it is a reference, just add it to head
+					if (src !== null && src.length > 0) {
+						var e = document.createElement("script");
+						if (type) {
+							e.setAttribute("type",type);
+						}
+						e.setAttribute("src", src);
+						Wicket.Head.addElement(e);
+					} else {
+						var content = Wicket.DOM.serializeNodeChildren(element);
+						if (isUndef(content) || content === "") {
+							content = element.text;
+						}
+
+						if (typeof(contentFilter) === "function") {
+							content = contentFilter(content);
+						}
+
+						Wicket.Head.addJavascript(content, element.id, "", type);
+					}
+				}
+				if (typeof(element) !== "undefined" &&
+					typeof(element.tagName) !== "undefined" &&
+					element.tagName.toLowerCase() === "script") {
+					add(element);
+				} else {
+					// we need to check if there are any children, because Safari
+					// aborts when the element is a text node
+					if (element.childNodes.length > 0) {
+						var scripts = element.getElementsByTagName("script");
+						for (var i = 0; i < scripts.length; ++i) {
+							add(scripts[i]);
+						}
+					}
+				}
+			}
+		},
+
+		// FOCUS FUNCTIONS
+
+		Focus: {
+			lastFocusId : "",
+			refocusLastFocusedComponentAfterResponse : false,
+			focusSetFromServer : false,
+
+			focusin: function (event) {
+				event = Wicket.Event.fix(event);
+
+				var target = event.target;
+				if (target) {
+					var WF = Wicket.Focus;
+					WF.refocusLastFocusedComponentAfterResponse = false;
+					var id = target.id;
+					WF.lastFocusId = id;
+					Wicket.Log.info("focus set on '%s'", id);
+				}
+			},
+
+			focusout: function (event) {
+				event = Wicket.Event.fix(event);
+
+				var target = event.target;
+				var WF = Wicket.Focus;
+				if (target && WF.lastFocusId === target.id) {
+					var id = target.id;
+					if (WF.refocusLastFocusedComponentAfterResponse) {
+						// replaced components seem to blur when replaced only on Safari - so do not modify lastFocusId so it gets refocused
+						Wicket.Log.info("focus removed from '%s' but ignored because of component replacement", id);
+					} else {
+						WF.lastFocusId = null;
+						Wicket.Log.info("focus removed from '%s'", id);
+					}
+				}
+			},
+
+			getFocusedElement: function () {
+				var lastFocusId = Wicket.Focus.lastFocusId;
+				if (lastFocusId) {
+					var focusedElement = Wicket.$(lastFocusId);
+					Wicket.Log.info("returned focused element:", focusedElement);
+					return  focusedElement;
+				}
+			},
+
+			setFocusOnId: function (id) {
+				var WF = Wicket.Focus;
+				if (id) {
+					WF.refocusLastFocusedComponentAfterResponse = true;
+					WF.focusSetFromServer = true;
+					WF.lastFocusId = id;
+					Wicket.Log.info("focus set on '%s' from server side", id);
+				} else {
+					WF.refocusLastFocusedComponentAfterResponse = false;
+					Wicket.Log.info("refocus focused component after request stopped from server side");
+				}
+			},
+
+			// mark the focused component so that we know if it has been replaced or not by response
+			markFocusedComponent: function () {
+				var WF = Wicket.Focus;
+				var focusedElement = WF.getFocusedElement();
+				if (focusedElement) {
+					// create a property of the focused element that would not remain there if component is replaced
+					focusedElement.wasFocusedBeforeComponentReplacements = true;
+					WF.refocusLastFocusedComponentAfterResponse = true;
+					WF.focusSetFromServer = false;
+				} else {
+					WF.refocusLastFocusedComponentAfterResponse = false;
+				}
+			},
+
+			// detect if the focused component was replaced
+			checkFocusedComponentReplaced: function () {
+				var WF = Wicket.Focus;
+				if (WF.refocusLastFocusedComponentAfterResponse) {
+					var focusedElement = WF.getFocusedElement();
+					if (focusedElement) {
+						if (typeof(focusedElement.wasFocusedBeforeComponentReplacements) !== "undefined") {
+							// focus component was not replaced - no need to refocus it
+							WF.refocusLastFocusedComponentAfterResponse = false;
+						}
+					} else {
+						// focused component dissapeared completely - no use to try to refocus it
+						WF.refocusLastFocusedComponentAfterResponse = false;
+						WF.lastFocusId = "";
+					}
+				}
+			},
+
+			requestFocus: function() {
+				// if the focused component is replaced by the ajax response, a re-focus might be needed
+				// (if focus was not changed from server) but if not, and the focus component should
+				// remain the same, do not re-focus - fixes problem on IE6 for combos that have
+				// the popup open (refocusing closes popup)
+				var WF = Wicket.Focus;
+				if (WF.refocusLastFocusedComponentAfterResponse && WF.lastFocusId) {
+					var toFocus = Wicket.$(WF.lastFocusId);
+
+					if (toFocus) {
+						Wicket.Log.info("Calling focus on '%s'", WF.lastFocusId);
+
+						var safeFocus = function() {
+							try {
+								toFocus.focus();
+							} catch (ignore) {
+								// WICKET-6209 IE fails if toFocus is disabled
+							}
+						};
+
+						if (WF.focusSetFromServer) {
+							// WICKET-5858
+							window.setTimeout(safeFocus, 0);
+						} else {
+							// avoid loops like - onfocus triggering an event the modifies the tag => refocus => the event is triggered again
+							var temp = toFocus.onfocus;
+							toFocus.onfocus = null;
+
+							// IE needs setTimeout (it seems not to call onfocus sync. when focus() is called
+							window.setTimeout(function () { safeFocus(); toFocus.onfocus = temp; }, 0);
+						}
+					} else {
+						WF.lastFocusId = "";
+						Wicket.Log.info("Couldn't set focus on element with id '%s' because it is not in the page anymore", WF.lastFocusId);
+					}
+				} else if (WF.refocusLastFocusedComponentAfterResponse) {
+					Wicket.Log.info("last focus id was not set");
+				} else {
+					Wicket.Log.info("refocus last focused component not needed/allowed");
+				}
+				Wicket.Focus.refocusLastFocusedComponentAfterResponse = false;
+			}
+		},
+
+		/**
+		 * Manages the functionality needed by AbstractAjaxTimerBehavior and its subclasses
+		 */
+		Timer: {
+			/**
+			 * Schedules a timer
+			 * @param {string} timerId - the identifier for the timer
+			 * @param {function} f - the JavaScript function to execute after the timeout
+			 * @param {number} delay - the timeout
+			 */
+			'set': function(timerId, f, delay) {
+				if (typeof(Wicket.TimerHandles) === 'undefined') {
+					Wicket.TimerHandles = {};
+				}
+
+				Wicket.Timer.clear(timerId);
+				Wicket.TimerHandles[timerId] = setTimeout(function() {
+					Wicket.Timer.clear(timerId);
+					f();
+				}, delay);
+			},
+
+			/**
+			 * Clears a timer by its id
+			 * @param {string} timerId - the identifier of the timer
+			 */
+			clear: function(timerId) {
+				if (Wicket.TimerHandles && Wicket.TimerHandles[timerId]) {
+					clearTimeout(Wicket.TimerHandles[timerId]);
+					delete Wicket.TimerHandles[timerId];
+				}
+			},
+			
+			/**
+			 * Clear all remaining timers.
+			 */
+			clearAll: function() {
+				var WTH = Wicket.TimerHandles;
+				if (WTH) {
+					for (var th in WTH) {
+						if (WTH.hasOwnProperty(th)) {
+							Wicket.Timer.clear(th);
+						}
+					}
+				}
+			}
+		},
+		
+		/**
+		 * Events related code
+		 * Based on code from Mootools (http://mootools.net)
+		 */
+		Event: {
+			idCounter: 0,
+
+			getId: function (element) {
+				var $el = jQuery(element),
+					id = $el.prop("id");
+
+				if (typeof(id) === "string" && id.length > 0) {
+					return id;
+				} else {
+					id = "wicket-generated-id-" + Wicket.Event.idCounter++;
+					$el.prop("id", id);
+					return id;
+				}
+			},
+
+			keyCode: function (evt) {
+				return Wicket.Event.fix(evt).keyCode;
+			},
+
+			/**
+			 * Prevent event from bubbling up in the element hierarchy.
+			 * @param evt {Event} - the event to stop
+			 * @param immediate {Boolean} - true if the event should not be handled by other listeners registered
+			 *      on the same HTML element. Optional
+			 */
+			stop: function (evt, immediate) {
+				evt = Wicket.Event.fix(evt);
+				if (immediate) {
+					evt.stopImmediatePropagation();
+				} else {
+					evt.stopPropagation();
+				}
+				return evt;
+			},
+
+			/**
+			 * If no event is given as argument (IE), window.event is returned.
+			 */
+			fix: function (evt) {
+				return jQuery.event.fix(evt || window.event);
+			},
+
+			fire: function (element, event) {
+				jQuery(element).trigger(event);
+			},
+
+			/**
+			 * Binds an event listener for an element
+			 *
+			 * Also supports the special 'domready' event on window.
+			 * 'domready' is event fired when the DOM is complete, but
+			 * before loading external resources (images, scripts, ...)
+			 *
+			 * @param element {HTMLElement | Window} The host HTML element
+			 * @param type {String} The type of the DOM event
+			 * @param fn {Function} The event handler to unbind
+			 * @param data {Object} Extra data for the event
+			 * @param selector {String} A selector string to filter the descendants of the selected
+			 *      elements that trigger the event. If the selector is null or omitted,
+			 *      the event is always triggered when it reaches the selected element.
+			 */
+			add: function (element, type, fn, data, selector) {
+				if (type === 'domready') {
+					if (document.readyState !== 'loading') {
+						fn();
+					} else {
+						document.addEventListener('DOMContentLoaded', fn);
+					}
+				} else if (type === 'load' && isWindow(element)) {
+					if (document.readyState !== 'loading') {
+						fn();
+					} else {
+						document.addEventListener('load', fn);
+					}
+				} else {
+					var el = element;
+					if (typeof(element) === 'string') {
+						el = Wicket.$(element);
+					}
+
+					if (!el && Wicket.Log) {
+						Wicket.Log.error("Cannot bind a listener for event '%s' because the element is not in the DOM", type, element);
+					}
+
+					// FIXME: how to pass sub-selector and data to the native impl ?
+					Wicket.$(el).addEventListener(type, fn, selector, data);
+				}
+				return element;
+			},
+
+			/**
+			 * Unbinds an event listener for an element
+			 *
+			 * @param element {HTMLElement} The host HTML element
+			 * @param type {String} The type of the DOM event
+			 * @param fn {Function} The event handler to unbind
+			 */
+			remove: function (element, type, fn) {
+				Wicket.$(element).removeEventListener(type, fn);
+			},
+
+			/**
+			* Adds a subscriber for the passed topic.
+			*
+			* @param topic {String} - the channel name for which this subscriber will be notified
+			*        If '*' then it will be notified for all topics
+			* @param subscriber {Function} - the callback to call when an event with this type is published
+			*/
+			subscribe: function (topic, subscriber) {
+				if (topic) {
+					document.addEventListener(topic, subscriber);
+				}
+			},
+
+			/**
+			 * Un-subscribes a subscriber from a topic.
+			 * @param topic {String} - the topic name.
+			 * @param subscriber {Function} - the handler to un-subscribe.
+			 */
+			unsubscribe: function(topic, subscriber) {
+				if (topic) {
+					if (subscriber) {
+						document.removeEventListener(topic, subscriber);
+					} else {
+						document.removeEventListener(topic);
+					}
+				}
+			},
+
+			/**
+			* Sends a notification to all subscribers for the given topic.
+			* Subscribers for topic '*' receive the actual topic as first parameter,
+			* otherwise the topic is not passed to subscribers which listen for specific
+			* event types.
+			*
+			* @param topic {String} - the channel name for which all subscribers will be notified.
+			*/
+			publish: function (topic) {
+				if (topic) {
+					// cut the topic argument
+					var args = Array.prototype.slice.call(arguments).slice(1);
+
+					document.dispatchEvent(new CustomEvent(topic, {detail: args}));
+					document.dispatchEvent(new CustomEvent('*', {detail: args}));
+				}
+			},
+
+			/**
+			 * The names of the topics on which Wicket notifies
+			 */
+			Topic: {
+				DOM_NODE_REMOVING      : '/dom/node/removing',
+				DOM_NODE_ADDED         : '/dom/node/added',
+				AJAX_CALL_INIT         : '/ajax/call/init',
+				AJAX_CALL_BEFORE       : '/ajax/call/before',
+				AJAX_CALL_PRECONDITION : '/ajax/call/precondition',
+				AJAX_CALL_BEFORE_SEND  : '/ajax/call/beforeSend',
+				AJAX_CALL_SUCCESS      : '/ajax/call/success',
+				AJAX_CALL_COMPLETE     : '/ajax/call/complete',
+				AJAX_CALL_AFTER        : '/ajax/call/after',
+				AJAX_CALL_FAILURE      : '/ajax/call/failure',
+				AJAX_CALL_DONE         : '/ajax/call/done',
+				AJAX_HANDLERS_BOUND    : '/ajax/handlers/bound'
+			}
+		}
+	});
+
+	// MISC FUNCTIONS
+
+	/**
+	 * Track focused element.
+	 */
+	Wicket.Event.add(window, 'focusin', Wicket.Focus.focusin);
+	Wicket.Event.add(window, 'focusout', Wicket.Focus.focusout);
+
+	/**
+	 * Clear any scheduled Ajax timers when leaving the current page
+	 */
+	Wicket.Event.add(window, "unload", function() {
+		Wicket.Timer.clearAll();
+	});
+
+})();
diff --git a/wicket-devutils/src/main/java/org/apache/wicket/devutils/debugbar/wicket-debugbar.js b/wicket-devutils/src/main/java/org/apache/wicket/devutils/debugbar/wicket-debugbar.js
index 4d70320..da65e0f 100644
--- a/wicket-devutils/src/main/java/org/apache/wicket/devutils/debugbar/wicket-debugbar.js
+++ b/wicket-devutils/src/main/java/org/apache/wicket/devutils/debugbar/wicket-debugbar.js
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-;(function (jQuery, undefined) {
+;(function (undefined) {
 
 	'use strict';
 
@@ -51,32 +51,33 @@
 			}
 		}
 
-		jQuery('#wicketDebugBarCollapse').on("click", function() {
-			var content = jQuery('#wicketDebugBarContents');
+		Wicket.Event.add('wicketDebugBarCollapse', 'click', function() {
+			var content = Wicket.$('wicketDebugBarContents');
 			setExpandedCookie(!content.is(':visible'));
 			content.toggle(400);
 		});
 
-		jQuery('#wicketDebugBarRemove').on("click", function() {
-			var bar = jQuery('#wicketDebugBar');
+		Wicket.Event.add('wicketDebugBarRemove', 'click', function() {
+			var bar = Wicket.$('wicketDebugBar');
 			setExpandedCookie(!bar.is(':visible'));
-			bar.hide();
+			Wicket.DOM.hide(bar);
 		});
 
 	    // determine state and set it
 		if (getExpandedCookie() === 'false') {
-			jQuery('#wicketDebugBarContents').hide();
+			Wicket.DOM.hide(Wicket.$('wicketDebugBarContents'));
 		}
 		
 		var original = Wicket.Log.error;
 		Wicket.Log.error = function() {
 			original.apply(Wicket.Log, arguments);
-			
-			jQuery('#wicketDebugBar')
-				.addClass('wicketDebugBarError')
-				.one('animationend', function() {
-					jQuery(this).removeClass('wicketDebugBarError');
-				});
+
+			var el = Wicket.$('wicketDebugBar');
+			el.classList.add('wicketDebugBarError')
+			el.addEventListener('animationend', function() {
+				el.removeEventListener('animationend');
+				el.classList.remove('wicketDebugBarError');
+			});
 		};
 	};
-})(jQuery);
+})();