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