You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by hl...@apache.org on 2011/07/19 03:32:54 UTC
svn commit: r1148124 - in /tapestry/tapestry5/trunk/tapestry-core/src:
main/java/org/apache/tapestry5/internal/services/javascript/
main/resources/org/apache/tapestry5/
test/resources/org/apache/tapestry5/integration/app1/pages/
Author: hlship
Date: Tue Jul 19 01:32:53 2011
New Revision: 1148124
URL: http://svn.apache.org/viewvc?rev=1148124&view=rev
Log:
TAP5-999: Add a message topic to allow the underlying framework to remove event handlers on DOM elements
Added:
tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-events.js
- copied, changed from r1148123, tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-prototype.js
Modified:
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/CoreJavaScriptStack.java
tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-dom.js
tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-prototype.js
tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-pubsub.js
tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/js-testing.js
Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/CoreJavaScriptStack.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/CoreJavaScriptStack.java?rev=1148124&r1=1148123&r2=1148124&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/CoreJavaScriptStack.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/CoreJavaScriptStack.java Tue Jul 19 01:32:53 2011
@@ -61,7 +61,8 @@ public class CoreJavaScriptStack impleme
// Uses functions defined by the prior three.
// Order is important, there are some dependencies
- // going on here.
+ // going on here. Switching over to a more managed module system
+ // is starting to look like a really nice idea!
ROOT + "/t5-core.js",
@@ -77,6 +78,8 @@ public class CoreJavaScriptStack impleme
ROOT + "/t5-pubsub.js",
+ ROOT + "/t5-events.js",
+
ROOT + "/t5-dom.js",
ROOT + "/t5-console.js",
Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-dom.js
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-dom.js?rev=1148124&r1=1148123&r2=1148124&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-dom.js (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-dom.js Tue Jul 19 01:32:53 2011
@@ -15,98 +15,107 @@
T5.define("dom", function() {
- /**
- * Locates an element. If element is a string, then
- * document.getElementById() is used to resolve a client element id to a DOM
- * element. If the id does not exist, then null will be returned.
- * <p>
- * If element is not a string, it is presumed to already by a DOM element,
- * and is returned.
- */
- function locate(element) {
- if (typeof element == "string") {
- return document.getElementById(element);
- }
-
- return element; // may be null, otherwise presumed to be a DOM node
- }
-
- /**
- * Tree-walks the children of the element; for each dhild, ensure that all
- * event handlers, listeners and PubSub publishers for the child are
- * removed.
- */
- function purgeChildren(element) {
- var children = element.childNodes;
-
- if (children) {
- var l = children.length, i, child;
-
- for (i = 0; i < l; i++) {
- var child = children[i];
-
- /* Just purge element nodes, not text, etc. */
- if (child.nodeType == 1)
- purge(children[i]);
- }
- }
- }
-
- // Adapted from http://javascript.crockford.com/memory/leak.html
- function purge(element) {
- var attrs = element.attributes;
- if (attrs) {
- var i, name;
- for (i = attrs.length - 1; i >= 0; i--) {
- if (attrs[i]) {
- name = attrs[i].name;
- /* Looking for onclick, etc. */
- if (typeof element[name] == 'function') {
- element[name] = null;
- }
- }
- }
- }
-
- // Get rid of any Prototype event handlers as well.
- // May generalize this to be a published message instead, for
- // cross-library compatibility.
- Event.stopObserving(element);
-
- purgeChildren(element);
-
- if (element.t5pubsub) {
- // TODO: Execute this deferred?
- T5.pubsub.cleanupRemovedElement(element);
- }
- }
-
- /**
- * Removes an element and all of its direct and indirect children. The
- * element is first purged, to ensure that Internet Explorer doesn't leak
- * memory if event handlers associated with the element (or its children)
- * have references back to the element. This also removes all Prototype
- * event handlers, and uses T5.pubsub.cleanupRemovedElement() to delete and
- * publishers or subscribers for any removed elements.
- *
- */
- function remove(element) {
- purge(element);
-
- // Remove the element, and all children, in one go.
- Element.remove(element);
- }
-
- return {
- remove : remove,
- purgeChildren : purgeChildren,
- locate : locate
- };
+ var removeEventHandlers;
+
+ // Necessary to lazy-instantiate femoveEventHandlers publisher function,
+ // due to load order of these namespaces.
+ function doRemoveEventHandlers(element) {
+ if (!removeEventHandlers) {
+ removeEventHandlers = T5.pubsub.createPublisher(T5.events.REMOVE_EVENT_HANDLERS, document);
+ }
+
+ removeEventHandlers(element);
+ }
+
+ /**
+ * Locates an element. If element is a string, then
+ * document.getElementById() is used to resolve a client element id to a DOM
+ * element. If the id does not exist, then null will be returned.
+ * <p>
+ * If element is not a string, it is presumed to already by a DOM element,
+ * and is returned.
+ */
+ function locate(element) {
+ if (typeof element == "string") {
+ return document.getElementById(element);
+ }
+
+ return element; // may be null, otherwise presumed to be a DOM node
+ }
+
+ /**
+ * Tree-walks the children of the element; for each dhild, ensure that all
+ * event handlers, listeners and PubSub publishers for the child are
+ * removed.
+ */
+ function purgeChildren(element) {
+ var children = element.childNodes;
+
+ if (children) {
+ var l = children.length, i, child;
+
+ for (i = 0; i < l; i++) {
+ var child = children[i];
+
+ /* Just purge element nodes, not text, etc. */
+ if (child.nodeType == 1)
+ purge(children[i]);
+ }
+ }
+ }
+
+ // Adapted from http://javascript.crockford.com/memory/leak.html
+ function purge(element) {
+ var attrs = element.attributes;
+ if (attrs) {
+ var i, name;
+ for (i = attrs.length - 1; i >= 0; i--) {
+ if (attrs[i]) {
+ name = attrs[i].name;
+ /* Looking for onclick, etc. */
+ if (typeof element[name] == 'function') {
+ element[name] = null;
+ }
+ }
+ }
+ }
+
+ purgeChildren(element);
+
+ if (element.t5pubsub) {
+ // TODO: Execute this deferred?
+ T5.pubsub.cleanupRemovedElement(element);
+ }
+
+ doRemoveEventHandlers(element);
+ }
+
+ /**
+ * Removes an element and all of its direct and indirect children. The
+ * element is first purged, to ensure that Internet Explorer doesn't leak
+ * memory if event handlers associated with the element (or its children)
+ * have references back to the element. This also removes all Prototype
+ * event handlers, and uses T5.pubsub.cleanupRemovedElement() to delete and
+ * publishers or subscribers for any removed elements.
+ *
+ */
+ function remove(element) {
+ purge(element);
+
+ // Remove the element, and all children, in one go.
+ Element.remove(element);
+ }
+
+ return {
+ remove : remove,
+ purgeChildren : purgeChildren,
+ locate : locate
+ };
});
/**
* Create a T5.$() synonym for T5.dom.locate().
*/
T5.extend(T5, {
- $ : T5.dom.locate
+ $ : T5.dom.locate
});
\ No newline at end of file
Copied: tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-events.js (from r1148123, tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-prototype.js)
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-events.js?p2=tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-events.js&p1=tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-prototype.js&r1=1148123&r2=1148124&rev=1148124&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-prototype.js (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-events.js Tue Jul 19 01:32:53 2011
@@ -14,11 +14,15 @@
*/
/**
- * Adapts Tapestry's SPI (Service Provider Interface) to make use of the
- * Prototype JavaScript library. May also make modifications to Prototype to
- * work with Tapestry.
+ * Defines the names of events used with the publish/subscribe framework.
*/
-T5.extend(T5.spi, function() {
+T5.define("events", {
+
+ /**
+ * Published as an element is being removed from the DOM, to allow framework-specific
+ * approaches to removing any event listeners for the element. This is published on the document object,
+ * and the message is the DOM element for which event handlers should be removed.
+ */
+ REMOVE_EVENT_HANDLERS : "tapestry:remove-event-handlers"
- return {};
});
\ No newline at end of file
Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-prototype.js
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-prototype.js?rev=1148124&r1=1148123&r2=1148124&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-prototype.js (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-prototype.js Tue Jul 19 01:32:53 2011
@@ -20,5 +20,13 @@
*/
T5.extend(T5.spi, function() {
- return {};
-});
\ No newline at end of file
+ document.observe("dom:loaded", function() {
+ T5.sub(T5.events.REMOVE_EVENT_HANDLERS, null, function(element) {
+ Event.stopObserving(element);
+ }
+ );
+ });
+
+ return {};
+});
+
Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-pubsub.js
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-pubsub.js?rev=1148124&r1=1148123&r2=1148124&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-pubsub.js (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-pubsub.js Tue Jul 19 01:32:53 2011
@@ -1,5 +1,4 @@
-T5.define("pubsub", function()
-{
+T5.define("pubsub", function() {
var arrays = T5.arrays;
var first = arrays.first;
@@ -18,36 +17,28 @@ T5.define("pubsub", function()
var publishers = [];
// Necessary since T5.dom depends on T5.pubsub
- function $(element)
- {
+ function $(element) {
return T5.$(element);
}
- function purgePublisherCache(topic)
- {
- each(function(publisher)
- {
- if (publisher.topic === topic)
- {
+ function purgePublisherCache(topic) {
+ each(function(publisher) {
+ if (publisher.topic === topic) {
publisher.listeners = undefined;
}
}, publishers);
}
- function findListeners(topic, element)
- {
- var gross = filter(function(subscriber)
- {
+ function findListeners(topic, element) {
+ var gross = filter(function(subscriber) {
return subscriber.topic === topic;
}, subscribers);
- var primary = filter(function(subscriber)
- {
+ var primary = filter(function(subscriber) {
return subscriber.element === element;
}, gross);
- var secondary = filter(function(subscriber)
- {
+ var secondary = filter(function(subscriber) {
// Match where the element is null or undefined
return !subscriber.element;
}, gross);
@@ -82,8 +73,7 @@ T5.define("pubsub", function()
* events, the document object is used as the element.
* @return a function of no arguments used to unsubscribe the listener
*/
- function subscribe(topic, element, listenerfn)
- {
+ function subscribe(topic, element, listenerfn) {
var subscriber = {
topic : topic,
@@ -101,8 +91,7 @@ T5.define("pubsub", function()
listenerfn = null;
// Return a function to unsubscribe
- return function()
- {
+ return function() {
subscribers = without(subscriber, subscribers);
purgePublisherCache(subscriber.topic);
}
@@ -139,40 +128,33 @@ T5.define("pubsub", function()
* the second parameter.
* @return publisher function used to publish a message
*/
- function createPublisher(topic, element)
- {
+ function createPublisher(topic, element) {
element = $(element);
- if (element == null)
- {
+ if (element == null) {
throw "Element may not be null when creating a publisher.";
}
- var existing = first(function(publisher)
- {
+ var existing = first(function(publisher) {
return publisher.topic === topic && publisher.element === element;
}, publishers);
- if (existing)
- {
+ if (existing) {
return existing.publisherfn;
}
var publisher = {
topic : topic,
element : element,
- publisherfn : function(message)
- {
+ publisherfn : function(message) {
- if (publisher.listeners == undefined)
- {
+ if (publisher.listeners == undefined) {
publisher.listeners = findListeners(publisher.topic,
publisher.element);
}
- return map(function(listenerfn)
- {
+ return map(function(listenerfn) {
return listenerfn(message, publisher.element);
}, publisher.listeners);
}
@@ -201,8 +183,7 @@ T5.define("pubsub", function()
* Creates a publisher and immediately publishes the message, return the
* array of results.
*/
- function publish(topic, element, message)
- {
+ function publish(topic, element, message) {
return createPublisher(topic, element)(message);
}
@@ -210,22 +191,18 @@ T5.define("pubsub", function()
* Invoked whenever an element is about to be removed from the DOM to remove
* any publishers or subscribers for the element.
*/
- function cleanup(element)
- {
- subscribers = remove(function(subscriber)
- {
+ function cleanup(element) {
+ subscribers = remove(function(subscriber) {
return subscriber.element === element
}, subscribers);
// A little evil to modify the publisher object at the same time it is
// being removed.
- publishers = remove(function(publisher)
- {
+ publishers = remove(function(publisher) {
var match = publisher.element === element;
- if (match)
- {
+ if (match) {
publisher.listeners = undefined;
publisher.element = undefined;
}
Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/js-testing.js
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/js-testing.js?rev=1148124&r1=1148123&r2=1148124&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/js-testing.js (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/js-testing.js Tue Jul 19 01:32:53 2011
@@ -1,158 +1,158 @@
var JST = (function() {
- /*
- * Original script title/version: Object.identical.js/1.11 Copyright (c)
- * 2011, Chris O'Brien, prettycode.org
- * http://github.com/prettycode/Object.identical.js
- *
- * LICENSE: Permission is hereby granted for unrestricted use, modification,
- * and redistribution of this script, ONLY under the condition that this
- * code comment is kept wholly complete, appearing above the script's code
- * body--in all original or modified implementations of this script, except
- * those that are minified.
- */
+ /*
+ * Original script title/version: Object.identical.js/1.11 Copyright (c)
+ * 2011, Chris O'Brien, prettycode.org
+ * http://github.com/prettycode/Object.identical.js
+ *
+ * LICENSE: Permission is hereby granted for unrestricted use, modification,
+ * and redistribution of this script, ONLY under the condition that this
+ * code comment is kept wholly complete, appearing above the script's code
+ * body--in all original or modified implementations of this script, except
+ * those that are minified.
+ */
- /*
- * Requires ECMAScript 5 functions: - Array.isArray() - Object.keys() -
- * Array.prototype.forEach() - JSON.stringify()
- */
+ /*
+ * Requires ECMAScript 5 functions: - Array.isArray() - Object.keys() -
+ * Array.prototype.forEach() - JSON.stringify()
+ */
- function identical(a, b, sortArrays) {
+ function identical(a, b, sortArrays) {
- function sort(o) {
+ function sort(o) {
- if (sortArrays === true && Array.isArray(o)) {
- return o.sort();
- } else if (typeof o !== "object" || o === null) {
- return o;
- }
+ if (sortArrays === true && Array.isArray(o)) {
+ return o.sort();
+ } else if (typeof o !== "object" || o === null) {
+ return o;
+ }
- var result = {};
+ var result = {};
- Object.keys(o).sort().forEach(function(key) {
- result[key] = sort(o[key]);
- });
+ Object.keys(o).sort().forEach(function(key) {
+ result[key] = sort(o[key]);
+ });
- return result;
- }
+ return result;
+ }
- return JSON.stringify(sort(a)) === JSON.stringify(sort(b));
- }
+ return JSON.stringify(sort(a)) === JSON.stringify(sort(b));
+ }
- var resultElement;
+ var resultElement;
- var $fail = {};
+ var $fail = {};
- function fail(text) {
- resultElement.insert({
- top : "FAIL - ",
- after : "<hr>" + text
- }).up("div").addClassName("fail").scrollTo();
+ function fail(text) {
+ resultElement.insert({
+ top : "FAIL - ",
+ after : "<hr>" + text
+ }).up("div").addClassName("fail").scrollTo();
- throw $fail;
- }
+ throw $fail;
+ }
- function toString(value) {
+ function toString(value) {
- return Object.toJSON(value);
- }
+ return Object.toJSON(value);
+ }
- function failNotEqual(actual, expected) {
- fail(toString(actual) + " !== " + toString(expected));
- }
+ function failNotEqual(actual, expected) {
+ fail(toString(actual) + " !== " + toString(expected));
+ }
- function assertSame(actual, expected) {
- if (actual !== expected) {
- failNotEqual(actual, expected);
- }
- }
+ function assertSame(actual, expected) {
+ if (actual !== expected) {
+ failNotEqual(actual, expected);
+ }
+ }
- function assertEqual(actual, expected) {
+ function assertEqual(actual, expected) {
- if (!identical(actual, expected)) {
- failNotEqual(actual, expected);
- }
- }
+ if (!identical(actual, expected)) {
+ failNotEqual(actual, expected);
+ }
+ }
- function doRunTests(elementId) {
+ function doRunTests(elementId) {
- var passCount = 0;
- var failCount = 0;
+ var passCount = 0;
+ var failCount = 0;
- $(elementId).addClassName("js-results").select("div:odd").each(
- function(e) {
- e.addClassName("odd");
- });
+ $(elementId).addClassName("js-results").select("div:odd").each(
+ function(e) {
+ e.addClassName("odd");
+ });
- $(elementId).select("div").each(
- function(test) {
+ $(elementId).select("div").each(
+ function(test) {
- test.addClassName("active");
+ test.addClassName("active");
- resultElement = test.down("p");
- resultNoted = false;
+ resultElement = test.down("p");
+ resultNoted = false;
- var testCode = test.down("pre").textContent;
+ var testCode = test.down("pre").textContent;
- try {
- eval(testCode);
+ try {
+ eval(testCode);
- passCount++;
+ passCount++;
- resultElement.insert({
- top : "PASS - "
- });
+ resultElement.insert({
+ top : "PASS - "
+ });
- test.addClassName("pass");
+ test.addClassName("pass");
- } catch (e) {
+ } catch (e) {
- Tapestry.error(e)
+ Tapestry.error(e)
- failCount++;
+ failCount++;
- if (e !== $fail) {
- resultElement.next().insert(
- {
- top : "EXCEPTION - ",
- after : "<hr><div class='exception'>"
- + toString(e) + "</div>"
- });
+ if (e !== $fail) {
+ resultElement.next().insert(
+ {
+ top : "EXCEPTION - ",
+ after : "<hr><div class='exception'>"
+ + toString(e) + "</div>"
+ });
- test.addClassName("fail").scrollTo();
- }
- }
+ test.addClassName("fail").scrollTo();
+ }
+ }
- test.removeClassName("active");
- });
+ test.removeClassName("active");
+ });
- $(elementId)
- .insert(
- {
- top : "<p class='caption #{class}'>Results: #{pass} passed / #{fail} failed</p>"
- .interpolate({
- class : failCount == 0 ? "pass"
- : "fail",
- pass : passCount,
- fail : failCount
- })
- });
+ $(elementId)
+ .insert(
+ {
+ top : "<p class='caption #{class}'>Results: #{pass} passed / #{fail} failed</p>"
+ .interpolate({
+ class : failCount == 0 ? "pass"
+ : "fail",
+ pass : passCount,
+ fail : failCount
+ })
+ });
- if (failCount == 0) {
- $(elementId).down("p").scrollTo();
- }
- }
+ if (failCount == 0) {
+ $(elementId).down("p").scrollTo();
+ }
+ }
- function runTestSuite(elementId) {
- Tapestry.onDOMLoaded(function() {
- doRunTests(elementId);
- });
- }
+ function runTestSuite(elementId) {
+ Tapestry.onDOMLoaded(function() {
+ doRunTests(elementId);
+ });
+ }
- return {
- fail : fail,
- assertEqual : assertEqual,
- assertSame : assertSame,
- runTestSuite : runTestSuite
- };
+ return {
+ fail : fail,
+ assertEqual : assertEqual,
+ assertSame : assertSame,
+ runTestSuite : runTestSuite
+ };
})();
\ No newline at end of file