You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by kn...@apache.org on 2008/08/19 00:57:39 UTC
svn commit: r686891 -
/wicket/sandbox/knopp/experimental/wicket/src/main/java/org/apache/wicket/ajax/wicket-ajax-ng.js
Author: knopp
Date: Mon Aug 18 15:57:38 2008
New Revision: 686891
URL: http://svn.apache.org/viewvc?rev=686891&view=rev
Log:
just some crazy stuff
Modified:
wicket/sandbox/knopp/experimental/wicket/src/main/java/org/apache/wicket/ajax/wicket-ajax-ng.js
Modified: wicket/sandbox/knopp/experimental/wicket/src/main/java/org/apache/wicket/ajax/wicket-ajax-ng.js
URL: http://svn.apache.org/viewvc/wicket/sandbox/knopp/experimental/wicket/src/main/java/org/apache/wicket/ajax/wicket-ajax-ng.js?rev=686891&r1=686890&r2=686891&view=diff
==============================================================================
--- wicket/sandbox/knopp/experimental/wicket/src/main/java/org/apache/wicket/ajax/wicket-ajax-ng.js (original)
+++ wicket/sandbox/knopp/experimental/wicket/src/main/java/org/apache/wicket/ajax/wicket-ajax-ng.js Mon Aug 18 15:57:38 2008
@@ -1,9 +1,21 @@
(function() {
-
+
+ /*
+ * YUI Shortcuts
+ */
+ var D = YAHOO.util.Dom;
+ var E = YAHOO.util.Event;
+ var L = YAHOO.lang;
+ var UA = YAHOO.env.ua;
+
var oldWicket = window.Wicket;
Wicket = { };
+ /*
+ * Wicket.$, Wicket.$$
+ */
+
Wicket.$ = function(arg)
{
if (arg == null || typeof(arg) == "undefined")
@@ -29,8 +41,10 @@
}
}
- var bind = function(method, object) {
- return function() {
+ var bind = function(method, object)
+ {
+ return function()
+ {
return method.apply(object, arguments);
}
}
@@ -42,37 +56,22 @@
*/
Wicket.$$ = function(element)
{
- if (typeof(element) == "string")
- {
- element = Wicket.$(element);
- }
- if (element == null || typeof(element) == "undefined" ||
- element.tagName == null || typeof(element.tagName) == "undefined")
+ if (L.isString(element))
{
- return true;
+ return Wicket.$(element) != null;
+ }
+ else
+ {
+ return D.inDocument(element);
}
-
- var id = element.getAttribute('id');
- if (typeof(id) == "undefined" || id == null || id == "")
- return element.ownerDocument == document;
- else
- return document.getElementById(id) == element;
- }
-
- var ua = YAHOO.env.ua;
-
- var DummyLogger =
- {
- trace: function() { },
- debug: function() { },
- info: function() { },
- error: function() { }
}
- var prepend = function(element, array)
+ /*
+ * Utility
+ */
+ var copyArray = function(array)
{
var res = new Array();
- res.push(element);
for (var i = 0; i < array.length; ++i)
{
res.push(array[i]);
@@ -80,93 +79,214 @@
return res;
}
+ var iterateArray = function(array, itemFunc)
+ {
+ if (L.isArray(array))
+ {
+ for(var i = 0; i < array.length; ++i)
+ {
+ var res = itemFunc(array[i]);
+ if (res != null)
+ {
+ return res;
+ }
+ }
+ }
+ else if (!L.isUndefined(array) && !L.isNull(array))
+ {
+ return itemFunc(array);
+ }
+ return null;
+ }
+
+ /*
+ * Logging
+ */
+ var DummyLogger =
+ {
+ trace: function() { },
+ debug: function() { },
+ info: function() { },
+ error: function() { }
+ }
+
var FirebugLogger =
{
- trace: function()
+ arg: function(type, args)
{
- console.debug.apply(this, prepend("TRACE:", arguments));
+ var first = args[0];
+ var a = copyArray(args).slice(1);
+ if (args.length > 1)
+ {
+ a.unshift("|");
+ }
+ a.unshift(first);
+ a.unshift("|");
+ a.unshift(type);
+ return a;
+ },
+ trace: function()
+ {
+ console.debug.apply(this, FirebugLogger.arg("TRACE", arguments));
},
debug: function()
{
- console.debug.apply(this, prepend("DEBUG:", arguments));
+ console.debug.apply(this, FirebugLogger.arg("DEBUG", arguments));
},
info: function()
{
- console.info.apply(this, prepend("INFO:", arguments));
+ console.info.apply(this, FirebugLogger.arg("INFO", arguments));
},
error: function()
{
- console.error.apply(this, prepend("ERROR:", arguments));
+ console.error.apply(this, FirebugLogger.arg("ERROR", arguments));
}
}
var logger = DummyLogger;
- if (ua.gecko && typeof(console) !== "undefined" && logger === DummyLogger)
+ if (UA.gecko && typeof(console) !== "undefined" && logger === DummyLogger)
{
logger = FirebugLogger;
}
- var logConfig = { trace: true, debug: true, info: true, error: true };
+ var logConfig = { trace: true, debug: true, info: true, error: true, "trace:GarbageCollector": false };
- var l =
+ Wicket.Log =
{
trace: function()
{
- if (logConfig.trace)
+ if (logConfig.trace && logConfig[arguments[0]] != false && logConfig["trace:" + arguments[0]] != false)
logger.trace.apply(this, arguments);
},
debug: function()
{
- if (logConfig.debug)
+ if (logConfig.debug && logConfig[arguments[0]] != false && logConfig["debug:" + arguments[0]] != false)
logger.debug.apply(this, arguments);
},
info: function()
{
- if (logConfig.info)
+ if (logConfig.info && logConfig[arguments[0]] != false && logConfig["info:" + arguments[0]] != false)
logger.info.apply(this, arguments);
},
error: function()
{
- if (logConfig.error)
+ if (logConfig.error && logConfig[arguments[0]] != false && logConfig["error:" + arguments[0]] != false)
logger.error.apply(this, arguments);
+ },
+ setLogger: function(newLogger)
+ {
+ logger = newLogger;
+ },
+ getLogger: function()
+ {
+ return logger;
+ },
+ isDummyLogger: function()
+ {
+ return logger == DummyLogger;
+ },
+ getLogConfig: function()
+ {
+ return logConfig;
}
};
- Wicket.Log = l;
+ // convenience shortcut
+ var log = Wicket.Log;
+
+ /*
+ * Garbage Collection (for removing event listeners from elements removed from DOM)
+ */
/**
* YAHOO event cleanups the listeners only on page unload. However, if the page lives long
* enough the elements removed from document that have listener attached cause IE GC not free the
* memory. So we manually register each element with listener and then periodically check
* whether the element is still in document. If it's not the element's listeners are removed.
- */
- var elementsWithListeners = new Array();
+ */
+ var GarbageCollector = function(purgeInterval)
+ {
+ this.elementsWithListeners = new Array();
+
+ // temporary array of elements being processed during purge
+ this.beingPurged = null;
+
+ // count of purged elements (debug)
+ this.purgedCount = 0;
+
+ this.purgeInterval = purgeInterval;
+
+ window.setInterval(bind(this.purgeInactiveListeners, this), purgeInterval);
+ };
- var purgeInactiveListeners = function()
+ GarbageCollector.prototype =
{
- var c = 0;
- var a = elementsWithListeners;
- for (var i = 0; i < a.length; ++i)
- {
- var e = a[i];
- if (e != null && Wicket.$$(e) == false)
- {
- l.trace("Events: Purging listeners from element ", e);
- E.purgeElement(e);
- a[i] = null;
- ++c;
+ // periodically called to initiate the purging process
+ purgeInactiveListeners : function()
+ {
+ // if purge is in progress don't do anything
+ if (this.beingPurged != null) {
+ return;
}
- }
- if (c > 0)
+
+ // the the elements
+ this.beingPurged = this.elementsWithListeners;
+ this.elementsWithListeners = new Array();
+
+ log.trace("GarbageCollector", "Purge begin");
+
+ this.purgedCount = 0;
+
+ // start the process
+ this.purge();
+ },
+
+ purge: function()
{
- l.debug("Purged listeners from " + c + " element(s) removed from the document.");
- }
- };
-
- window.setInterval(purgeInactiveListeners, 60000);
+ if (this.beingPurged != null)
+ {
+ var done = 0;
+
+ // it is necessary to limit amount of items being purged in one go otherwise
+ // IE will complain about script being slow
+ var max = 50;
+
+ var a = this.beingPurged;
+ for (var i = 0; i < a.length && done < 50; ++i)
+ {
+ var e = a[i];
+ if (e != null)
+ {
+ ++done;
+ if (!Wicket.$$(e)) {
+ E.purgeElement(e);
+ ++this.purgedCount;
+ } else {
+ // element is still in document, return it
+ this.elementsWithListeners.push(e);
+ }
+ a[i] = null;
+ }
+ }
+
+ if (i == a.length)
+ {
+ // we are done with purging
+ this.beingPurged = null;
+
+ log.trace("GarbageCollector", "Purge End; purged: " + this.purgedCount + ", total: " + this.elementsWithListeners.length);
+ }
+ else
+ {
+ // not yet done, continue after 50ms
+ window.setTimeout(bind(this.purge, this), 50);
+ }
+ }
+ }
+ }
- var E = YAHOO.util.Event;
+ var garbageCollector = new GarbageCollector(5000);
var oldAddListener = E.addListener;
@@ -176,17 +296,20 @@
*/
E.addListener = function(el)
{
- l.trace("Events: Adding event listeners", arguments);
+ log.trace("Events", "Adding event listeners", arguments);
oldAddListener.apply(this, arguments);
if (el !== window && el !== document)
{
- var a = elementsWithListeners;
- var i = a.length;
- a[i] = Wicket.$(el);
+ var a = garbageCollector.elementsWithListeners;
+ a.push(Wicket.$(el));
}
};
- E.addListener(window, "unload", function() { elementsWithListeners = null; } );
+ E.addListener(window, "unload", function() { garbageCollector = null; } );
+
+ /*
+ * Throttler
+ */
var ThrottlerEntry = function(func)
{
@@ -198,17 +321,11 @@
* executed more often that the specified interval. Throttler can be used to reduce number
* of AJAX requests. To match function with previously executed functions a (string) token
* is used.
- *
- * @param 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.
*/
- var Throttler = function(postponeTimerOnUpdate)
+ var Throttler = function()
{
this.entries = { };
this.executionTimes = { };
- this.postponeTimerOnUpdate = postponeTimerOnUpdate || false;
};
Throttler.prototype =
@@ -220,12 +337,20 @@
* @param token - string token to match previously executed function with the one specified now
* @param millis - how much to postpone the function after the last invocation
* @param func - function
+ * @param 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 behavior 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.
+ * This can be useful for throttling events based on keyboard input. The function can be called
+ * for example 2 seconds after the last key stroke.
*/
- throttle: function(token, millis, func)
+ throttle: function(token, millis, func, postponeTimerOnUpdate)
{
+ postponeTimerOnUpdate = postponeTimerOnUpdate || false;
+
// check if throttling is necessary. thottling is always necessary when postponeTimerOnUpdate
// is true
- if (!this.postponeTimerOnUpdate && this.checkLastExecutionTime(token, millis, func))
+ if (!postponeTimerOnUpdate && this.checkLastExecutionTime(token, millis, func))
{
return;
}
@@ -235,20 +360,20 @@
entry = new ThrottlerEntry(func);
entry.timeout = window.setTimeout(bind(function() { this.execute(token) }, this), millis);
this.entries[token] = entry;
- l.trace("Throttler: Setting throttle, token:", token, ", millis:", millis, ", func:", func);
+ log.trace("Throttler", "Setting throttle, token:", token, ", millis:", millis, ", func:", func);
}
else
{
entry.func = func;
- if (this.postponeTimerOnUpdate)
+ if (postponeTimerOnUpdate)
{
window.clearTimeout(entry.timeout);
entry.timeout = window.setTimeout(bind(this.execute, this), millis);
- l.trace("Throttler: Postponing throttle, token:", token, ", millis:", millis, ", func:", func);
+ log.trace("Throttler", "Postponing throttle, token:", token, ", millis:", millis, ", func:", func);
}
else
{
- l.trace("Throttler: Replacing throttle, token:", token, ", millis:", millis, ", func:", func);
+ log.trace("Throttler", "Replacing throttle, token:", token, ", millis:", millis, ", func:", func);
}
}
},
@@ -261,7 +386,7 @@
var now = new Date().getTime();
if (e == null || (e + millis) < now)
{
- l.trace("Throttler: Executing function immediately, token:", token, ", millis:", millis, ", func:", func);
+ log.trace("Throttler", "Executing function immediately, token:", token, ", millis:", millis, ", func:", func);
this.executionTimes[token] = now;
this.entries[token] = null;
func();
@@ -279,7 +404,7 @@
if (entry != null)
{
var f = entry.func;
- l.trace("Invoking throttled function, token:", token, "func:", f);
+ log.trace("Throttler", "Invoking throttled function, token:", token, "func:", f);
this.entries[token] = null;
this.executionTimes[token] = new Date().getTime();
f();
@@ -287,10 +412,19 @@
}
};
- Wicket.Throttler = Throttler;
+ var isNonEmpty = function(string)
+ {
+ return L.isString(string) && string.length > 0;
+ }
+
+ Wicket.Throttler = Throttler;
+
+ /*
+ * AJAX
+ */
/**
- * An item in a RequestQueue. Item constructor get a map of configuration attribute. Each attribute
+ * An item in a RequestQueue. Item constructor get a map of configuration attributes. Each attribute
* can be either specified by a full name or by a shortcut.
*
* Possible attributes:
@@ -302,6 +436,9 @@
* for the component the attribute can contain a string
* (Component#getMarkupId()). The string is then used to find
* the matching component on the server side.
+ *
+ * The element is also used as precondition. The element must
+ * be part of the DOM tree otherwise the item will be ignored.
*
* f, formId - String Form ID if the AJAX request should submit a form or null
* if the request doesn't involve form submission
@@ -333,11 +470,9 @@
* Used to identify previous items (items with same token) that
* will be removed when this item is added and removePrevious
* is true. Also required when throttle attribute is used.
- * Token is also used to invoke global precondition/success/error
- * handlers that are registered for certain token(s).
*
* r, removePrevious - Boolean Optional. If there are previous items with same token in the
- * queue they will be removed if removePrevisious is true. This
+ * queue they will be removed if removePrevious is true. This
* can be useful when the items are added in queue faster
* than they are processed and only the latest request matters.
* An example of this could be periodically updated component.
@@ -350,17 +485,33 @@
* item per n milliseconds where n is the value of throttle
* attribute. Useful to limit the number of AJAX requests that
* are triggered by a user action such as typing into a text
- * field.
+ * field.
+ * Throttle attribute only applies when token is specified.
+ *
+ * thp, throttlePostpone - Boolean Optional. Only applicable when throttle attribute is set.
+ * Defaults to false. Causes the throttle timer reset each time
+ * item with same token is being added to queue.
+ * Example: Event is fired by user typing in a textfield.
+ * Throttle value is 2000 (ms), throttle postpone is true.
+ * The event will be fired 2000ms after user typed the last
+ * character.
+ * If throttle postpone is false, The event is fired immediately
+ * after user starts typing and then every two seconds as long
+ * as user keeps typing.
*
- * pr, precondition - Method(s) Optional. Method or array of methods that is/are invoked
+ * pr, preconditions - Method(s) Optional. Method or array of methods that is/are invoked
* before the request executes. The method(s) will get this
* RequestItem passed as fist argument and have to return
- * a boolean value. If any of these methods return false the
- * request is cancelled.
+ * a boolean value. If any of these methods returns false the
+ * request is canceled.
*
- * s, successHandlers - Method(s) Optional. Method or array of methods that is/are invoked after
- * the request is successfully processed. The method(s) will get
- * this RequestQueueItem passed as fist argument.
+ * be, beforeHandlers - Method(s) Optional. Method or array of methods that is/are invoked
+ * before the actual AJAX request. This invocation only
+ * happens when all precondition methods return true.
+ *
+ * s, successHandlers - Method(s) Optional. Method or array of methods that is/are invoked
+ * after the request is successfully processed. The method(s)
+ * will get this RequestQueueItem passed as fist argument.
*
* e, errorHandlers - Method(s) Optional. Method or array of methods that is/are invoked when
* an error happens during the AJAX request or the processing
@@ -377,28 +528,250 @@
* will be appended to the URL before postprocessing it. This is
* simpler alternative to urlPostProcessor or urlArgumentMethods.
*
- * uam, urlArgumentMethods - Method(s) Optional. Method or array of methods that produce additional URL
- * arguments. Each of the methods will get this RequestQueueItem
- * passed and must return a Map<String, String> (Object).
+ * uam, urlArgumentMethods - Method(s) Optional. Method or array of methods that produce additional
+ * URL arguments. Each of the methods will get this
+ * RequestQueueItem passed and must return a
+ * Map<String, String> (Object).
*/
var RequestQueueItem = function(attributes)
{
- this.attributes = attributes;
+ var a = attributes;
+ var b = function(value)
+ {
+ if (value)
+ return true;
+ else
+ return false;
+ }
+ var createMethodArray = function(methodOrArray)
+ {
+ if (L.isFunction(methodOrArray))
+ return [methodOrArray];
+ else if (L.isArray(methodOrArray))
+ return methodOrArray;
+ else {
+ return [];
+ }
+ }
+ var m = function(m1, m2)
+ {
+ m1 = createMethodArray(m1);
+ m2 = createMethodArray(m2);
+ return m1.concat(m2);
+ }
+ var gs = Wicket.ajax.globalSettings;
+
+ this.attributes =
+ {
+ component: a.component || a.c,
+ formId: a.formId || a.f,
+ multipart: b(a.multipart || a.m),
+ requestTimeout: a.requestTimeout || a.t || gs.defaultRequestTimeout,
+ processingTimeout: a.processingTimeout || a.pt || gs.defaultProcessingTimeout,
+ pageId: a.pageId || a.p || gs.defaultPageId,
+ listenerInterface: a.listenerInterface || a.l || gs.defaultToken,
+ behaviorIndex: a.behaviorIndex || a.b,
+ token: a.token || a.t,
+ removePrevious: b(a.removePrevious || a.r || gs.defaultRemovePrevious),
+ throttle: a.throttle || a.th,
+ throttlePostpone: b(a.throttlePostpone || a.thp),
+ preconditions: m(a.preconditions || a.pr, gs.preconditions),
+ beforeHandlers: m(a.beforeHandlers || a.be, gs.beforeHandlers),
+ successHandlers: m(a.successHandlers || a.s, gs.successHandlers),
+ errorHandlers: m(a.errorHandlers || a.e, gs.errorHandlers),
+ urlPostProcessors: m(a.urlPostProcessors || a.u, gs.urlPostProcessors),
+ urlArguments: a.urlArguments || a.ua,
+ urlArgumentMethods: m(a.urlArgumentMethods || a.uam, gs.urlArgumentMethods)
+ }
+
+ log.trace("RequestQueue", "Creating New Item", this.attributes);
}
+ RequestQueueItem.prototype =
+ {
+ checkPreconditions: function()
+ {
+ var res = iterateArray(this.preconditions, function(precondition)
+ {
+ try
+ {
+ if (precondition(this) == false)
+ {
+ return false;
+ }
+ }
+ catch (exception)
+ {
+ log.error("RequestQueue", "Error evaluating precondition ", precondition, "Exception: ", exception);
+ return false;
+ }
+ });
+ if (res == null)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ },
+
+ execute: function(next)
+ {
+ }
+ };
+
var RequestQueue = function()
{
+ this.queue = new Array();
+ this.throttler = new Throttler();
};
RequestQueue.prototype =
{
+ addInternal: function(item)
+ {
+ var a = item.attributes;
+ if (a.removePrevious)
+ {
+ if (!isNonEmpty(a.token))
+ {
+ log.warn("RequestQueue", "Item ", item, " has removePrevious set but no token specified - ignored");
+ }
+ else
+ {
+ this.removeByToken(a.token);
+ }
+ }
+ this.queue.push(item);
+
+ if (this.currentItem == null)
+ {
+ this.next();
+ }
+ },
+
+ removeByToken: function(token)
+ {
+ if (token != null)
+ {
+ for (var i = 0; i < this.queue.length; ++i)
+ {
+ var item = this.queue[i];
+ if (item.attributes.token == token)
+ {
+ this.queue[i] = null;
+ }
+ }
+ var q = this.queue;
+ this.queue = new Array();
+ for (var i = 0; i < q.length; ++i)
+ {
+ if (q[i] != null)
+ {
+ this.queue.push(q[i]);
+ }
+ }
+ }
+ },
+
+ add: function(item)
+ {
+ if (item == null)
+ {
+ log.error("RequestQueue", "Argument 'item' must not be null.");
+ return;
+ }
+ else if (item.attributes == null)
+ {
+ log.error("RequestQueue", "Item ", item, " must contain attributes.");
+ return;
+ }
+ var a = item.attributes;
+ if (a.throttlePostpone == true && a.throttle == null)
+ {
+ log.warn("RequestQueue", "Item ", item, " has throttlePostpone set but no throttle specified - ignored.");
+ }
+ else if (a.throttle != null && !isNonEmpty(a.token))
+ {
+ log.warn("RequestQueue", "Item ", item, " has throttle set but no token specified - ignored.");
+ }
+ else if (a.throttle != null)
+ {
+ var f = bind(function() { this.addInternal(item);}, this);
+ this.throttler.throttle(a.token, a.throttle, f, a.throttlePostpone);
+ return;
+ }
+ addInternal(item);
+ },
+
+ nextInternal: function()
+ {
+ this.currentItem = null;
+
+ if (this.queue.length > 0)
+ {
+ var i = this.queue.shift();
+ if (i.checkPreconditions())
+ {
+ this.currentItem = i;
+ var s = bind(function() { this.skip(i); }, this);
+ var a = i.attributes;
+ var t = a.requestTimeout + a.processingTimeout;
+
+ // failsafe timeout, in case the request queue item fails to call next()
+ window.setTimeout(s, t);
+
+ var next = bind(this.next, this);
+ i.execute(next);
+ }
+ else
+ {
+ this.next();
+ }
+ }
+ },
+
+ next: function()
+ {
+ window.setTimeout(bind(this.nextInternal, this), 0);
+ },
+
+ skip: function(item)
+ {
+ if (this.currentItem == item)
+ {
+ log.error("RequestQueue", "Timeout exceeded, skipping item", item);
+ this.currentItem = null;
+ this.next();
+ }
+ }
};
+ var globalSettings =
+ {
+ defaultRequestTimeout: 60000,
+ defaultProcessingTimeout: 60000,
+ defaultPageId: -1,
+ defaultToken: null,
+ defaultRemovePrevious: false,
+ beforeHandlers: [],
+ preconditions: [],
+ successHandlers: [],
+ errorHandlers: [],
+ urlPostProcessors: [],
+ urlArgumentMethods: []
+ };
+ var Ajax = function()
+ {
+ this.globalSettings = globalSettings;
+ };
- new f();
- // ===================== REVERT THE OLD WICKET OBJECT =====================
+ Wicket.ajax = new Ajax();
+
+ // ===================== REVERT THE OLD WICKET OBJECT =====================
WicketNG = Wicket;
Wicket = oldWicket;