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


Reply via email to