tapestry-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From hls...@apache.org
Subject svn commit: r1101721 - in /tapestry/tapestry5/trunk/tapestry-core/src: main/java/org/apache/tapestry5/ main/java/org/apache/tapestry5/internal/services/ main/java/org/apache/tapestry5/internal/services/javascript/ main/java/org/apache/tapestry5/service...
Date Wed, 11 May 2011 01:35:48 GMT
Author: hlship
Date: Wed May 11 01:35:48 2011
New Revision: 1101721

URL: http://svn.apache.org/viewvc?rev=1101721&view=rev
Log:
TAP5-999: Remove ClientInfrastructure and build a new CoreJavaScriptStack
Import new T5 libraries: t5-core, t5-init, t5-arrays, t5-pubsub
Create new page for in-browser unit testing of JavaScript libraries

Added:
    tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-arrays.js
    tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-core.js
    tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-init.js
    tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-pubsub.js
    tapestry/tapestry5/trunk/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/TapestryJavaScriptTests.groovy
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/JavaScriptTests.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/JavaScriptTests.tml
    tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/js-testing.css
    tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/js-testing.js
Removed:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClientInfrastructureImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/EmptyClientInfrastructure.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ClientInfrastructure.java
Modified:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/CoreJavaScriptStack.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptStack.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tapestry.js
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java?rev=1101721&r1=1101720&r2=1101721&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java Wed May 11 01:35:48 2011
@@ -236,14 +236,6 @@ public class SymbolConstants
     public static final String BLACKBIRD = "tapestry.blackbird";
 
     /**
-     * The Asset path of the default javascript (tapestry.js) automatically injected into every rendered HTML page.
-     * 
-     * @since 5.2.0
-     * @deprecated Deprecated in 5.3; not used since 5.1, replaced with the core {@link JavaScriptStack}.
-     */
-    public static final String DEFAULT_JAVASCRIPT = "tapestry.default-javascript";
-
-    /**
      * If "true", then JSON page initialization content is compressed; if "false"
      * then extra white space is added (pretty printing). Defaults to "true" in production mode.
      * 

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=1101721&r1=1101720&r2=1101721&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 Wed May 11 01:35:48 2011
@@ -1,4 +1,4 @@
-// Copyright 2010 The Apache Software Foundation
+// Copyright 2010, 2011 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -16,13 +16,18 @@ package org.apache.tapestry5.internal.se
 
 import java.util.Collections;
 import java.util.List;
+import java.util.Locale;
 
 import org.apache.tapestry5.Asset;
 import org.apache.tapestry5.SymbolConstants;
 import org.apache.tapestry5.func.F;
+import org.apache.tapestry5.func.Flow;
 import org.apache.tapestry5.internal.TapestryInternalUtils;
 import org.apache.tapestry5.ioc.annotations.Symbol;
-import org.apache.tapestry5.services.ClientInfrastructure;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
+import org.apache.tapestry5.ioc.services.SymbolSource;
+import org.apache.tapestry5.ioc.services.ThreadLocale;
+import org.apache.tapestry5.services.AssetSource;
 import org.apache.tapestry5.services.javascript.JavaScriptStack;
 import org.apache.tapestry5.services.javascript.StylesheetLink;
 
@@ -33,17 +38,72 @@ import org.apache.tapestry5.services.jav
  */
 public class CoreJavaScriptStack implements JavaScriptStack
 {
-    private final ClientInfrastructure clientInfrastructure;
-
     private final boolean productionMode;
 
-    public CoreJavaScriptStack(ClientInfrastructure clientInfrastructure,
+    private final SymbolSource symbolSource;
+
+    private final AssetSource assetSource;
+
+    private final ThreadLocale threadLocale;
+
+    private final List<Asset> javaScriptStack, stylesheetStack;
+
+    private final Asset consoleJavaScript, consoleStylesheet;
+
+    private final boolean isBlackbirdEnabled;
 
-    @Symbol(SymbolConstants.PRODUCTION_MODE)
-    boolean productionMode)
+    private static final String ROOT = "org/apache/tapestry5";
+
+    private static final String[] CORE_JAVASCRIPT = new String[]
     {
-        this.clientInfrastructure = clientInfrastructure;
+            // Core scripts added to any page that uses scripting
+
+            "${tapestry.scriptaculous}/prototype.js",
+
+            "${tapestry.scriptaculous}/scriptaculous.js",
+
+            "${tapestry.scriptaculous}/effects.js",
+
+            // Uses functions defined by the prior three
+
+            ROOT + "/t5-core.js",
+
+            ROOT + "/t5-arrays.js",
+            
+            ROOT + "/t5-init.js",
+            
+            ROOT + "/t5-pubsub.js",
+
+            ROOT + "/tapestry.js" };
+
+    // Because of changes to the logic of how stylesheets get incorporated, the default stylesheet
+    // was removed, the logic for it is now in TapestryModule.contributeMarkupRenderer().
+
+    private static final String[] CORE_STYLESHEET = new String[0];
+
+    public CoreJavaScriptStack(@Symbol(SymbolConstants.PRODUCTION_MODE)
+    boolean productionMode,
+
+    SymbolSource symbolSource,
+
+    AssetSource assetSource,
+
+    ThreadLocale threadLocale,
+
+    @Symbol(SymbolConstants.BLACKBIRD_ENABLED)
+    boolean isBlackbirdEnabled)
+    {
+        this.symbolSource = symbolSource;
         this.productionMode = productionMode;
+        this.assetSource = assetSource;
+        this.threadLocale = threadLocale;
+        this.isBlackbirdEnabled = isBlackbirdEnabled;
+
+        javaScriptStack = convertToAssets(CORE_JAVASCRIPT);
+        stylesheetStack = convertToAssets(CORE_STYLESHEET);
+
+        consoleJavaScript = expand("${tapestry.blackbird}/blackbird.js", ROOT + "/tapestry-console.js", null);
+        consoleStylesheet = expand("${tapestry.blackbird}/blackbird.css", ROOT + "/tapestry-console.css", null);
     }
 
     public String getInitialization()
@@ -51,20 +111,52 @@ public class CoreJavaScriptStack impleme
         return productionMode ? null : "Tapestry.DEBUG_ENABLED = true;";
     }
 
+    public List<String> getStacks()
+    {
+        return Collections.emptyList();
+    }
+
+    private List<Asset> convertToAssets(String[] paths)
+    {
+        List<Asset> assets = CollectionFactory.newList();
+
+        for (String path : paths)
+        {
+            assets.add(expand(path, null));
+        }
+
+        return Collections.unmodifiableList(assets);
+    }
+
+    private Asset expand(String path, Locale locale)
+    {
+        String expanded = symbolSource.expandSymbols(path);
+
+        return assetSource.getAsset(null, expanded, locale);
+    }
+
+    private Asset expand(String blackbirdPath, String consolePath, Locale locale)
+    {
+        String path = isBlackbirdEnabled ? blackbirdPath : consolePath;
+
+        return expand(path, locale);
+    }
+
     public List<Asset> getJavaScriptLibraries()
     {
-        return clientInfrastructure.getJavascriptStack();
+        Asset messages = assetSource.getAsset(null, ROOT + "/tapestry-messages.js", threadLocale.getLocale());
+
+        return createStack(javaScriptStack, messages, consoleJavaScript).toList();
     }
 
     public List<StylesheetLink> getStylesheets()
     {
-        return F.flow(clientInfrastructure.getStylesheetStack()).map(TapestryInternalUtils.assetToStylesheetLink)
+        return createStack(stylesheetStack, consoleStylesheet).map(TapestryInternalUtils.assetToStylesheetLink)
                 .toList();
     }
 
-    public List<String> getStacks()
+    private Flow<Asset> createStack(List<Asset> stack, Asset... assets)
     {
-        return Collections.emptyList();
+        return F.flow(stack).append(assets);
     }
-
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java?rev=1101721&r1=1101720&r2=1101721&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java Wed May 11 01:35:48 2011
@@ -484,7 +484,6 @@ public final class TapestryModule
         binder.bind(ClientDataEncoder.class, ClientDataEncoderImpl.class);
         binder.bind(ComponentEventLinkEncoder.class, ComponentEventLinkEncoderImpl.class);
         binder.bind(PageRenderLinkSource.class, PageRenderLinkSourceImpl.class);
-        binder.bind(ClientInfrastructure.class, ClientInfrastructureImpl.class);
         binder.bind(ValidatorMacro.class, ValidatorMacroImpl.class);
         binder.bind(PropertiesFileParser.class, PropertiesFileParserImpl.class);
         binder.bind(PageActivator.class, PageActivatorImpl.class);
@@ -2420,8 +2419,6 @@ public final class TapestryModule
 
         configuration.add(SymbolConstants.START_PAGE_NAME, "start");
 
-        configuration.add(SymbolConstants.DEFAULT_JAVASCRIPT, "classpath:/org/apache/tapestry5/tapestry.js");
-
         configuration.add(SymbolConstants.DEFAULT_STYLESHEET, "classpath:/org/apache/tapestry5/default.css");
         configuration.add("tapestry.spacer-image", "classpath:/org/apache/tapestry5/spacer.gif");
 

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptStack.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptStack.java?rev=1101721&r1=1101720&r2=1101721&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptStack.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptStack.java Wed May 11 01:35:48 2011
@@ -18,10 +18,8 @@ import java.util.List;
 
 import org.apache.tapestry5.Asset;
 import org.apache.tapestry5.SymbolConstants;
-import org.apache.tapestry5.TapestryConstants;
 import org.apache.tapestry5.ioc.services.ThreadLocale;
 import org.apache.tapestry5.services.AssetSource;
-import org.apache.tapestry5.services.ClientInfrastructure;
 
 /**
  * A high level description of a group of related JavaScript libraries and stylesheets. The built-in "core"
@@ -30,9 +28,6 @@ import org.apache.tapestry5.services.Cli
  * define additional stacks for related sets of resources, for example, to bundle together some portion
  * of the ExtJS or YUI libraries.
  * <p>
- * A JavaScriptStack can be thought of as a generalization of Tapestry 5.1's {@link ClientInfrastructure}, which exists
- * now to define the "core" JavaScript stack.
- * <p>
  * A JavaScript assets of a stack may (when {@linkplain SymbolConstants#COMBINE_SCRIPTS enabled}) be exposed to the
  * client as a single URL (identifying the stack by name). The individual assets are combined into a single virtual
  * asset, which is then streamed to the client.

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-arrays.js
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-arrays.js?rev=1101721&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-arrays.js (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-arrays.js Wed May 11 01:35:48 2011
@@ -0,0 +1,143 @@
+/* Copyright 2011 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Extends T5 with new utility functions.
+ */
+T5.extend(T5, function() {
+
+	function isNonEmpty(array) {
+		if (array === null || array === undefined)
+			return false;
+
+		return array.length > 0;
+	}
+
+	/**
+	 * Iterates over an array, invoking a function for each array element.
+	 * 
+	 * @param array
+	 *            to iterate over (possibly null or undefined)
+	 * @param fn
+	 *            passed each element in array as first parameter, element index
+	 *            as second parameter
+	 */
+	function each(array, fn) {
+		if (isNonEmpty(array)) {
+			for ( var index = 0; index < array.length; index++) {
+				fn(array[index], index);
+			}
+		}
+	}
+
+	/**
+	 * Maps over a JavaScript array, passing each value to the mapper function.
+	 * Returns the array of return values from the mapper.
+	 * 
+	 * @param array
+	 *            object to iterate over (may be null or undefined)
+	 * @param mapperfn
+	 *            function passed each object from the array, and the index for
+	 *            each object from the array
+	 * @returns result array (possibly empty)
+	 */
+	function map(array, mapperfn) {
+		var result = [];
+
+		each(array, function(element, index) {
+			result[index] = mapperfn(element, index);
+		});
+
+		return result;
+	}
+
+	/**
+	 * Reduces an array by passing the initial value and the first element to
+	 * the reducer function. The result (the accumulator) is passed to the
+	 * reducer function with the second element, and so on. The final result is
+	 * the accumulator after all elements have been passed.
+	 * 
+	 * @param array
+	 *            array (may be null or undefined)
+	 * @param initial
+	 *            the initial value for the accumulator
+	 * @param reducerfn
+	 *            passed the accumulator, an element, and an index and returns
+	 *            the new accumulator
+	 * @returns the accumulator
+	 */
+	function reduce(array, initial, reducerfn) {
+		var accumulator = initial;
+
+		each(array, function(element, index) {
+			accumulator = reducerfn(accumulator, element, index);
+		});
+
+		return accumulator;
+	}
+
+	var concat = Array.prototype.concat;
+	
+	/**
+	 * A variation of map, where the mapperfn is expected to return an array of
+	 * values (not a single value). The result arrays are concatenated, to
+	 * return a single flattened result.
+	 * 
+	 * @param array
+	 *            to iterate over
+	 * @param mapperfn
+	 *            passed each element and index, returns an array of results
+	 * @returns the concatination of the result arrays
+	 */
+	function mapcat(array, mapperfn) {
+		var results = map(array, mapperfn);
+
+		return concat.apply([], results);
+	}
+
+	/**
+	 * Removes an element from an array, returning a modified version of the
+	 * array with all instances of the element eliminated. Uses === for
+	 * comparison. May return the original array unchanged if the element is not
+	 * present.
+	 * 
+	 * @param array
+	 *            a non-null array
+	 * @param element
+	 *            to remove from array
+	 * @returns the array, or the array with any references to element removed
+	 */
+	function without(array, element) {
+		var index;
+		for (index = array.length - 1; index >= 0; index--) {
+			if (array[index] === element) {
+				// TODO: This could be made more efficient when the element is
+				// the first or last index in the array.
+
+				array = array.slice(0, index).concat(array.slice(index + 1));
+			}
+		}
+
+		return array;
+	}
+
+	return {
+		each : each,
+		map : map,
+		mapcat : mapcat,
+		reduce : reduce,
+		without : without
+	};
+});
\ No newline at end of file

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-core.js
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-core.js?rev=1101721&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-core.js (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-core.js Wed May 11 01:35:48 2011
@@ -0,0 +1,40 @@
+/* Copyright  2011 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * The master T5 namespace.
+ * 
+ * @since 5.3.0
+ */
+var T5 = {
+	/**
+	 * Extends an object using a source. In the simple case, the source object's
+	 * properties are overlaid on top of the destination object. In the typical
+	 * case, the source parameter is a function that returns the source object
+	 * ... this is to facilitate modularity and encapsulation.
+	 * 
+	 * @param destination
+	 *            object to receive new or updated properties
+	 * @param source
+	 *            source object for properties, or function returning source
+	 *            object
+	 */
+	extend : function(destination, source) {
+		if (Object.isFunction(source))
+			source = source();
+
+		Object.extend(destination, source);
+	}
+}
\ No newline at end of file

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-init.js
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-init.js?rev=1101721&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-init.js (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-init.js Wed May 11 01:35:48 2011
@@ -0,0 +1,36 @@
+/* Copyright  2011 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/** Extends T5 with new functions related to page initialization. */
+T5.extend(T5, function() {
+
+	return {
+		/**
+		 * The T5.Initializer namespace, which contains functions used to
+		 * perform page load initializations.
+		 */
+		Initializer : {},
+
+		/**
+		 * A convienience method for extending the T5.Initializer namespace.
+		 * 
+		 * @param source
+		 *            object or function used to extend T5.Initializer
+		 */
+		extendInitializer : function(source) {
+			T5.extend(T5.Initializer, source);
+		}
+	};
+});
\ No newline at end of file

Added: 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=1101721&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-pubsub.js (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/t5-pubsub.js Wed May 11 01:35:48 2011
@@ -0,0 +1,159 @@
+/* Copyright 2011 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+T5.extend(T5, function() {
+
+	var map = T5.map;
+	var mapcat = T5.mapcat;
+	var each = T5.each;
+
+	var subscribersVersion = 0;
+
+	var subscribers = {};
+	var publishers = {};
+
+	/**
+	 * Expands a selector into a array of strings representing the containers of
+	 * the selector (by stripping off successive terms, breaking at slashes).
+	 */
+	function expandSelector(selector) {
+		var result = [];
+		var current = selector;
+
+		while (true) {
+			result.push(current);
+			var slashx = current.lastIndexOf('/');
+
+			if (slashx < 0)
+				break;
+
+			current = current.substring(0, slashx);
+		}
+
+		return result;
+	}
+
+	function doPublish(listeners, message) {
+
+		return map(listeners, function(fn) {
+			fn(message);
+		});
+	}
+
+	/**
+	 * Creates a publisher for a selector. The selector is a string consisting
+	 * of individual terms separated by slashes. A publisher sends a message to
+	 * listener functions. Publishers are cached internally.
+	 * 
+	 * <p>
+	 * The returned publisher function is used to publish a message. It takes a
+	 * single argument, the message object. The message object is passed to all
+	 * listener functions matching the selector. The return value from the
+	 * publisher function is all the return values from the listener functions.
+	 * 
+	 * @return publisher function
+	 */
+	function createPublisher(selector) {
+
+		var publisher = publishers[selector];
+
+		if (publisher === undefined) {
+			var selectors = expandSelector(selector);
+
+			var listeners = null;
+
+			var subscribersVersionSnapshot = -1;
+
+			var publisher = function(message) {
+
+				// Recalculate the listeners whenever the subscribers map
+				// has changed.
+
+				if (subscribersVersionSnapshot !== subscribersVersion) {
+					listeners = mapcat(selectors, function(selector) {
+						return subscribers[selector] || [];
+					});
+
+					subscribersVersionSnapshot = subscribersVersion;
+				}
+
+				return doPublish(listeners, message);
+			};
+
+			publishers[selector] = publisher;
+		}
+
+		return publisher;
+	}
+
+	/**
+	 * Creates a publisher for the selector (or uses a previously cached
+	 * publisher) and publishes the message, returning the combined results of
+	 * all the listener functions.
+	 */
+	function publish(selector, message) {
+
+		return createPublisher(selector)(message);
+	}
+
+	function unsubscribe(selector, listenerfn) {
+		var listeners = subscribers[selector];
+
+		var editted = T5.without(listeners, listenerfn);
+
+		if (editted !== listeners) {
+			subscribers[selector] = editted;
+
+			subscribersVersion++;
+		}
+	}
+
+	/**
+	 * Subscribes a listener function to a selector. The selector is a string
+	 * consisting of individual terms separated by slashes. A publisher will
+	 * send a message object to a selector; matching listener functions are
+	 * invoked, and are passed the message object.
+	 * <p>
+	 * The return value is a function, of no parameters, used to unsubscribe the
+	 * listener function.
+	 * 
+	 */
+	function subscribe(selector, listenerfn) {
+
+		var listeners = subscribers[selector];
+
+		if (listeners === undefined) {
+			listeners = [];
+			subscribers[selector] = listeners;
+		}
+
+		listeners.push(listenerfn);
+
+		// Indicate that subscribers has changed, so publishers need to
+		// recalculate their listeners.
+
+		subscribersVersion++;
+
+		return function() {
+			unsubscribe(selector, listenerfn);
+		}
+	}
+
+	return {
+		createPublisher : createPublisher,
+		pub : publish,
+		sub : subscribe
+	};
+});
\ No newline at end of file

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tapestry.js
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tapestry.js?rev=1101721&r1=1101720&r2=1101721&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tapestry.js (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tapestry.js Wed May 11 01:35:48 2011
@@ -922,389 +922,414 @@ Element.addMethods([ 'INPUT', 'SELECT', 
 	}
 });
 
-/** Container of functions that may be invoked by the Tapestry.init() function. */
-Tapestry.Initializer = {
+/** Compatibility: set Tapestry.Initializer equal to T5.Initializer. */
 
-	/** Make the given field the active field (focus on the field). */
-	activate : function(id) {
-		$(id).activate();
-	},
+Tapestry.Initializer = T5.Initializer;
 
-	/**
-	 * evalScript is a synonym for the JavaScript eval function. It is used in
-	 * Ajax requests to handle any setup code that does not fit into a standard
-	 * Tapestry.Initializer call.
-	 */
-	evalScript : eval,
+/** Container of functions that may be invoked by the Tapestry.init() function. */
+T5
+		.extendInitializer({
 
-	ajaxFormLoop : function(spec) {
-		var rowInjector = $(spec.rowInjector);
+			/** Make the given field the active field (focus on the field). */
+			activate : function(id) {
+				$(id).activate();
+			},
+
+			/**
+			 * evalScript is a synonym for the JavaScript eval function. It is
+			 * used in Ajax requests to handle any setup code that does not fit
+			 * into a standard Tapestry.Initializer call.
+			 */
+			evalScript : eval,
 
-		$(spec.addRowTriggers).each(function(triggerId) {
-			$(triggerId).observeAction("click", function(event) {
-				$(rowInjector).trigger();
-			});
-		});
-	},
+			ajaxFormLoop : function(spec) {
+				var rowInjector = $(spec.rowInjector);
 
-	formLoopRemoveLink : function(spec) {
-		var link = $(spec.link);
-		var fragmentId = spec.fragment;
+				$(spec.addRowTriggers).each(function(triggerId) {
+					$(triggerId).observeAction("click", function(event) {
+						$(rowInjector).trigger();
+					});
+				});
+			},
 
-		link.observeAction("click", function(event) {
-			var successHandler = function(transport) {
-				var container = $(fragmentId);
+			formLoopRemoveLink : function(spec) {
+				var link = $(spec.link);
+				var fragmentId = spec.fragment;
 
-				var effect = Tapestry.ElementEffect.fade(container);
+				link.observeAction("click", function(event) {
+					var successHandler = function(transport) {
+						var container = $(fragmentId);
 
-				effect.options.afterFinish = function() {
-					Tapestry.remove(container);
-				}
-			};
+						var effect = Tapestry.ElementEffect.fade(container);
 
-			Tapestry.ajaxRequest(spec.url, successHandler);
-		});
-	},
+						effect.options.afterFinish = function() {
+							Tapestry.remove(container);
+						}
+					};
 
-	/**
-	 * Convert a form or link into a trigger of an Ajax update that updates the
-	 * indicated Zone.
-	 * 
-	 * @param spec.linkId
-	 *            id or instance of &lt;form&gt; or &lt;a&gt; element
-	 * @param spec.zoneId
-	 *            id of the element to update when link clicked or form
-	 *            submitted
-	 * @param spec.url
-	 *            absolute component event request URL
-	 */
-	linkZone : function(spec) {
-		Tapestry.Initializer.updateZoneOnEvent("click", spec.linkId,
-				spec.zoneId, spec.url);
-	},
+					Tapestry.ajaxRequest(spec.url, successHandler);
+				});
+			},
 
-	/**
-	 * Converts a link into an Ajax update of a Zone. The url includes the
-	 * information to reconnect with the server-side Form.
-	 * 
-	 * @param spec.selectId
-	 *            id or instance of &lt;select&gt;
-	 * @param spec.zoneId
-	 *            id of element to update when select is changed
-	 * @param spec.url
-	 *            component event request URL
-	 */
-	linkSelectToZone : function(spec) {
-		Tapestry.Initializer.updateZoneOnEvent("change", spec.selectId,
-				spec.zoneId, spec.url);
-	},
+			/**
+			 * Convert a form or link into a trigger of an Ajax update that
+			 * updates the indicated Zone.
+			 * 
+			 * @param spec.linkId
+			 *            id or instance of &lt;form&gt; or &lt;a&gt; element
+			 * @param spec.zoneId
+			 *            id of the element to update when link clicked or form
+			 *            submitted
+			 * @param spec.url
+			 *            absolute component event request URL
+			 */
+			linkZone : function(spec) {
+				Tapestry.Initializer.updateZoneOnEvent("click", spec.linkId,
+						spec.zoneId, spec.url);
+			},
+
+			/**
+			 * Converts a link into an Ajax update of a Zone. The url includes
+			 * the information to reconnect with the server-side Form.
+			 * 
+			 * @param spec.selectId
+			 *            id or instance of &lt;select&gt;
+			 * @param spec.zoneId
+			 *            id of element to update when select is changed
+			 * @param spec.url
+			 *            component event request URL
+			 */
+			linkSelectToZone : function(spec) {
+				Tapestry.Initializer.updateZoneOnEvent("change", spec.selectId,
+						spec.zoneId, spec.url);
+			},
 
-	linkSubmit : function(spec) {
+			linkSubmit : function(spec) {
 
-		Tapestry.replaceElementTagName(spec.clientId, "A");
+				Tapestry.replaceElementTagName(spec.clientId, "A");
 
-		$(spec.clientId).writeAttribute("href", "#");
+				$(spec.clientId).writeAttribute("href", "#");
 
-		$(spec.clientId).observeAction("click", function(event) {
+				$(spec.clientId).observeAction("click", function(event) {
 
-			var form = $(spec.form);
+					var form = $(spec.form);
 
-			if (!spec.validate)
-				form.skipValidation();
+					if (!spec.validate)
+						form.skipValidation();
 
-			form.setSubmittingElement(this);
+					form.setSubmittingElement(this);
 
-			form.performSubmit(event);
-		});
-	},
+					form.performSubmit(event);
+				});
+			},
 
-	/**
-	 * Used by other initializers to connect an element (either a link or a
-	 * form) to a zone.
-	 * 
-	 * @param eventName
-	 *            the event on the element to observe
-	 * @param element
-	 *            the element to observe for events
-	 * @param zoneId
-	 *            identified a Zone by its clientId. Alternately, the special
-	 *            value '^' indicates that the Zone is a container of the
-	 *            element (the first container with the 't-zone' CSS class).
-	 * @param url
-	 *            The request URL to be triggered when the event is observed.
-	 *            Ultimately, a partial page update JSON response will be passed
-	 *            to the Zone's ZoneManager.
-	 */
-	updateZoneOnEvent : function(eventName, element, zoneId, url) {
-		element = $(element);
+			/**
+			 * Used by other initializers to connect an element (either a link
+			 * or a form) to a zone.
+			 * 
+			 * @param eventName
+			 *            the event on the element to observe
+			 * @param element
+			 *            the element to observe for events
+			 * @param zoneId
+			 *            identified a Zone by its clientId. Alternately, the
+			 *            special value '^' indicates that the Zone is a
+			 *            container of the element (the first container with the
+			 *            't-zone' CSS class).
+			 * @param url
+			 *            The request URL to be triggered when the event is
+			 *            observed. Ultimately, a partial page update JSON
+			 *            response will be passed to the Zone's ZoneManager.
+			 */
+			updateZoneOnEvent : function(eventName, element, zoneId, url) {
+				element = $(element);
 
-		$T(element).zoneUpdater = true;
+				$T(element).zoneUpdater = true;
 
-		var zoneElement = zoneId == '^' ? $(element).up('.t-zone') : $(zoneId);
+				var zoneElement = zoneId == '^' ? $(element).up('.t-zone')
+						: $(zoneId);
 
-		if (!zoneElement) {
-			Tapestry
-					.error(
-							"Could not find zone element '#{zoneId}' to update on #{eventName} of element '#{elementId}",
-							{
-								zoneId : zoneId,
-								eventName : eventName,
-								elementId : element.id
-							});
-			return;
-		}
+				if (!zoneElement) {
+					Tapestry
+							.error(
+									"Could not find zone element '#{zoneId}' to update on #{eventName} of element '#{elementId}",
+									{
+										zoneId : zoneId,
+										eventName : eventName,
+										elementId : element.id
+									});
+					return;
+				}
 
-		/*
-		 * Update the element with the id of zone div. This may be changed
-		 * dynamically on the client side.
-		 */
+				/*
+				 * Update the element with the id of zone div. This may be
+				 * changed dynamically on the client side.
+				 */
 
-		$T(element).zoneId = zoneElement.id;
+				$T(element).zoneId = zoneElement.id;
 
-		if (element.tagName == "FORM") {
+				if (element.tagName == "FORM") {
 
-			// Create the FEM if necessary.
-			element.addClassName(Tapestry.PREVENT_SUBMISSION);
+					// Create the FEM if necessary.
+					element.addClassName(Tapestry.PREVENT_SUBMISSION);
 
-			/*
-			 * After the form is validated and prepared, this code will process
-			 * the form submission via an Ajax call. The original submit event
-			 * will have been cancelled.
-			 */
+					/*
+					 * After the form is validated and prepared, this code will
+					 * process the form submission via an Ajax call. The
+					 * original submit event will have been cancelled.
+					 */
 
-			element.observe(Tapestry.FORM_PROCESS_SUBMIT_EVENT, function() {
-				var zoneManager = Tapestry.findZoneManager(element);
+					element
+							.observe(
+									Tapestry.FORM_PROCESS_SUBMIT_EVENT,
+									function() {
+										var zoneManager = Tapestry
+												.findZoneManager(element);
+
+										if (!zoneManager)
+											return;
+
+										var successHandler = function(transport) {
+											zoneManager
+													.processReply(transport.responseJSON);
+										};
+
+										element.sendAjaxRequest(url, {
+											parameters : {
+												"t:zoneid" : zoneId
+											},
+											onSuccess : successHandler
+										});
+									});
 
-				if (!zoneManager)
 					return;
+				}
 
-				var successHandler = function(transport) {
-					zoneManager.processReply(transport.responseJSON);
-				};
-
-				element.sendAjaxRequest(url, {
-					parameters : {
-						"t:zoneid" : zoneId
-					},
-					onSuccess : successHandler
-				});
-			});
-
-			return;
-		}
-
-		/* Otherwise, assume it's just an ordinary link or input field. */
-
-		element.observeAction(eventName, function(event) {
-			element.fire(Tapestry.TRIGGER_ZONE_UPDATE_EVENT);
-		});
+				/* Otherwise, assume it's just an ordinary link or input field. */
 
-		element.observe(Tapestry.TRIGGER_ZONE_UPDATE_EVENT, function() {
+				element.observeAction(eventName, function(event) {
+					element.fire(Tapestry.TRIGGER_ZONE_UPDATE_EVENT);
+				});
 
-			var zoneObject = Tapestry.findZoneManager(element);
+				element.observe(Tapestry.TRIGGER_ZONE_UPDATE_EVENT, function() {
 
-			if (!zoneObject)
-				return;
+					var zoneObject = Tapestry.findZoneManager(element);
 
-			/*
-			 * A hack related to allowing a Select to perform an Ajax update of
-			 * the page.
-			 */
+					if (!zoneObject)
+						return;
 
-			var parameters = {};
+					/*
+					 * A hack related to allowing a Select to perform an Ajax
+					 * update of the page.
+					 */
 
-			if (element.tagName == "SELECT" && element.value) {
-				parameters["t:selectvalue"] = element.value;
-			}
+					var parameters = {};
 
-			zoneObject.updateFromURL(url, parameters);
-		});
-	},
+					if (element.tagName == "SELECT" && element.value) {
+						parameters["t:selectvalue"] = element.value;
+					}
 
-	/**
-	 * Sets up a Tapestry.FormEventManager for the form, and enables events for
-	 * validations. This is executed with InitializationPriority.EARLY, to
-	 * ensure that the FormEventManager exists vefore any validations are added
-	 * for fields within the Form.
-	 * 
-	 * @since 5.2.2
-	 */
-	formEventManager : function(spec) {
-		$T(spec.formId).formEventManager = new Tapestry.FormEventManager(spec);
-	},
+					zoneObject.updateFromURL(url, parameters);
+				});
+			},
 
-	/**
-	 * Keys in the masterSpec are ids of field control elements. Value is a list
-	 * of validation specs. Each validation spec is a 2 or 3 element array.
-	 */
-	validate : function(masterSpec) {
-		$H(masterSpec)
-				.each(
-						function(pair) {
-
-							var field = $(pair.key);
-
-							/*
-							 * Force the creation of the field event manager.
-							 */
-
-							$(field).getFieldEventManager();
-
-							$A(pair.value)
-									.each(
-											function(spec) {
-												/*
-												 * Each pair value is an array
-												 * of specs, each spec is a 2 or
-												 * 3 element array. validator
-												 * function name, message,
-												 * optional constraint
-												 */
-
-												var name = spec[0];
-												var message = spec[1];
-												var constraint = spec[2];
-
-												var vfunc = Tapestry.Validator[name];
-
-												if (vfunc == undefined) {
-													Tapestry
-															.error(
-																	Tapestry.Messages.missingValidator,
-																	{
-																		name : name,
-																		fieldName : field.id
-																	});
-													return;
-												}
-
-												/*
-												 * Pass the extended field, the
-												 * provided message, and the
-												 * constraint object to the
-												 * Tapestry.Validator function,
-												 * so that it can, typically,
-												 * invoke field.addValidator().
-												 */
-												try {
-													vfunc
-															.call(this, field,
+			/**
+			 * Sets up a Tapestry.FormEventManager for the form, and enables
+			 * events for validations. This is executed with
+			 * InitializationPriority.EARLY, to ensure that the FormEventManager
+			 * exists vefore any validations are added for fields within the
+			 * Form.
+			 * 
+			 * @since 5.2.2
+			 */
+			formEventManager : function(spec) {
+				$T(spec.formId).formEventManager = new Tapestry.FormEventManager(
+						spec);
+			},
+
+			/**
+			 * Keys in the masterSpec are ids of field control elements. Value
+			 * is a list of validation specs. Each validation spec is a 2 or 3
+			 * element array.
+			 */
+			validate : function(masterSpec) {
+				$H(masterSpec)
+						.each(
+								function(pair) {
+
+									var field = $(pair.key);
+
+									/*
+									 * Force the creation of the field event
+									 * manager.
+									 */
+
+									$(field).getFieldEventManager();
+
+									$A(pair.value)
+											.each(
+													function(spec) {
+														/*
+														 * Each pair value is an
+														 * array of specs, each
+														 * spec is a 2 or 3
+														 * element array.
+														 * validator function
+														 * name, message,
+														 * optional constraint
+														 */
+
+														var name = spec[0];
+														var message = spec[1];
+														var constraint = spec[2];
+
+														var vfunc = Tapestry.Validator[name];
+
+														if (vfunc == undefined) {
+															Tapestry
+																	.error(
+																			Tapestry.Messages.missingValidator,
+																			{
+																				name : name,
+																				fieldName : field.id
+																			});
+															return;
+														}
+
+														/*
+														 * Pass the extended
+														 * field, the provided
+														 * message, and the
+														 * constraint object to
+														 * the
+														 * Tapestry.Validator
+														 * function, so that it
+														 * can, typically,
+														 * invoke
+														 * field.addValidator().
+														 */
+														try {
+															vfunc.call(this,
+																	field,
 																	message,
 																	constraint);
-												} catch (e) {
-													Tapestry
-															.error(
-																	Tapestry.Messages.invocationException,
-																	{
-																		fname : "Tapestry.Validator."
-																				+ functionName,
-																		params : Object
-																				.toJSON([
-																						field.id,
-																						message,
-																						constraint ]),
-																		exception : e
-																	});
-												}
-											});
-						});
-	},
-
-	zone : function(spec) {
-		new Tapestry.ZoneManager(spec);
-	},
-
-	formFragment : function(spec) {
-
-		var element = $(spec.element);
-
-		var hidden = $(spec.element + "-hidden");
-		var form = $(hidden.form);
-
-		function runAnimation(makeVisible) {
-			var effect = makeVisible ? Tapestry.ElementEffect[spec.show]
-					|| Tapestry.ElementEffect.slidedown
-					: Tapestry.ElementEffect[spec.hide]
-							|| Tapestry.ElementEffect.slideup;
-			return effect(element);
-		}
-
-		element.observe(Tapestry.CHANGE_VISIBILITY_EVENT, function(event) {
-			var makeVisible = event.memo.visible;
+														} catch (e) {
+															Tapestry
+																	.error(
+																			Tapestry.Messages.invocationException,
+																			{
+																				fname : "Tapestry.Validator."
+																						+ functionName,
+																				params : Object
+																						.toJSON([
+																								field.id,
+																								message,
+																								constraint ]),
+																				exception : e
+																			});
+														}
+													});
+								});
+			},
+
+			zone : function(spec) {
+				new Tapestry.ZoneManager(spec);
+			},
+
+			formFragment : function(spec) {
+
+				var element = $(spec.element);
+
+				var hidden = $(spec.element + "-hidden");
+				var form = $(hidden.form);
+
+				function runAnimation(makeVisible) {
+					var effect = makeVisible ? Tapestry.ElementEffect[spec.show]
+							|| Tapestry.ElementEffect.slidedown
+							: Tapestry.ElementEffect[spec.hide]
+									|| Tapestry.ElementEffect.slideup;
+					return effect(element);
+				}
 
-			if (makeVisible == element.visible())
-				return;
+				element.observe(Tapestry.CHANGE_VISIBILITY_EVENT, function(
+						event) {
+					var makeVisible = event.memo.visible;
 
-			runAnimation(makeVisible);
-		});
+					if (makeVisible == element.visible())
+						return;
 
-		element.observe(Tapestry.HIDE_AND_REMOVE_EVENT, function() {
-			var effect = runAnimation(false);
+					runAnimation(makeVisible);
+				});
 
-			effect.options.afterFinish = function() {
-				Tapestry.remove(element);
-			};
-		});
+				element.observe(Tapestry.HIDE_AND_REMOVE_EVENT, function() {
+					var effect = runAnimation(false);
 
-		if (!spec.alwaysSubmit) {
-			form.observe(Tapestry.FORM_PREPARE_FOR_SUBMIT_EVENT, function() {
+					effect.options.afterFinish = function() {
+						Tapestry.remove(element);
+					};
+				});
 
-				/*
-				 * On a submission, if the fragment is not visible, then
-				 * disabled its form submission data, so that no processing or
-				 * validation occurs on the server.
-				 */
-				hidden.disabled = !element.isDeepVisible();
-			});
-		}
-	},
+				if (!spec.alwaysSubmit) {
+					form.observe(Tapestry.FORM_PREPARE_FOR_SUBMIT_EVENT,
+							function() {
+
+								/*
+								 * On a submission, if the fragment is not
+								 * visible, then disabled its form submission
+								 * data, so that no processing or validation
+								 * occurs on the server.
+								 */
+								hidden.disabled = !element.isDeepVisible();
+							});
+				}
+			},
 
-	formInjector : function(spec) {
-		new Tapestry.FormInjector(spec);
-	},
+			formInjector : function(spec) {
+				new Tapestry.FormInjector(spec);
+			},
 
-	/*
-	 * Links a FormFragment to a trigger (a radio or a checkbox), such that
-	 * changing the trigger will hide or show the FormFragment. Care should be
-	 * taken to render the page with the checkbox and the FormFragment's
-	 * visibility in agreement.
-	 */
-	linkTriggerToFormFragment : function(spec) {
-		var trigger = $(spec.triggerId);
+			/*
+			 * Links a FormFragment to a trigger (a radio or a checkbox), such
+			 * that changing the trigger will hide or show the FormFragment.
+			 * Care should be taken to render the page with the checkbox and the
+			 * FormFragment's visibility in agreement.
+			 */
+			linkTriggerToFormFragment : function(spec) {
+				var trigger = $(spec.triggerId);
 
-		var update = function() {
-			var checked = trigger.checked;
-			var makeVisible = checked == !spec.invert;
-
-			$(spec.fragmentId).fire(Tapestry.CHANGE_VISIBILITY_EVENT, {
-				visible : makeVisible
-			}, true);
-		}
+				var update = function() {
+					var checked = trigger.checked;
+					var makeVisible = checked == !spec.invert;
+
+					$(spec.fragmentId).fire(Tapestry.CHANGE_VISIBILITY_EVENT, {
+						visible : makeVisible
+					}, true);
+				}
 
-		/* Let the event bubble up to the form level. */
-		if (trigger.type == "radio") {
-			$(trigger.form).observe("click", update);
-			return;
-		}
+				/* Let the event bubble up to the form level. */
+				if (trigger.type == "radio") {
+					$(trigger.form).observe("click", update);
+					return;
+				}
 
-		/* Normal trigger is a checkbox; listen just to it. */
-		trigger.observe("click", update);
+				/* Normal trigger is a checkbox; listen just to it. */
+				trigger.observe("click", update);
 
-	},
+			},
 
-	cancelButton : function(clientId) {
+			cancelButton : function(clientId) {
 
-		/*
-		 * Set the form's skipValidation property and allow the event to
-		 * continue, which will ultimately submit the form.
-		 */
-		$(clientId).observeAction("click", function(event) {
-			$(this.form).skipValidation();
-			$(this.form).setSubmittingElement(clientId);
-			$(this.form).performSubmit(event);
+				/*
+				 * Set the form's skipValidation property and allow the event to
+				 * continue, which will ultimately submit the form.
+				 */
+				$(clientId).observeAction("click", function(event) {
+					$(this.form).skipValidation();
+					$(this.form).setSubmittingElement(clientId);
+					$(this.form).performSubmit(event);
+				});
+			}
 		});
-	}
-};
 
 /*
  * Collection of field based functions related to validation. Each function

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/TapestryJavaScriptTests.groovy
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/TapestryJavaScriptTests.groovy?rev=1101721&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/TapestryJavaScriptTests.groovy (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/TapestryJavaScriptTests.groovy Wed May 11 01:35:48 2011
@@ -0,0 +1,18 @@
+package org.apache.tapestry5.integration.app1
+
+import org.apache.tapestry5.integration.TapestryCoreTestCase
+import org.testng.annotations.Test
+
+class TapestryJavaScriptTests extends TapestryCoreTestCase {
+
+    @Test
+    void basic_javascript_tests() {
+        openLinks "JavaScript Unit Tests"
+
+        def resultClass = getAttribute("//table[@class='js-results']/caption/@class")
+        
+        if (resultClass == 'failures') {
+            fail "Some JavaScript unit tests failed"
+        }
+    }
+}
\ No newline at end of file

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java?rev=1101721&r1=1101720&r2=1101721&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java Wed May 11 01:35:48 2011
@@ -72,6 +72,9 @@ public class Index
     private static final List<Item> ITEMS = CollectionFactory
             .newList(
 
+                    new Item("JavaScriptTests", "JavaScript Unit Tests",
+                            "Unit tests for client-side JavaScript"),
+                            
                     new Item("PACAnnotationDemo", "PageActivationContext Demo",
                             "Shows that @PageActivationContext fields are set before calls to the activate event handler."),
 

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/JavaScriptTests.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/JavaScriptTests.java?rev=1101721&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/JavaScriptTests.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/JavaScriptTests.java Wed May 11 01:35:48 2011
@@ -0,0 +1,9 @@
+package org.apache.tapestry5.integration.app1.pages;
+
+import org.apache.tapestry5.annotations.Import;
+
+@Import(library = "js-testing.js", stylesheet = "js-testing.css")
+public class JavaScriptTests
+{
+
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/JavaScriptTests.tml
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/JavaScriptTests.tml?rev=1101721&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/JavaScriptTests.tml (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/JavaScriptTests.tml Wed May 11 01:35:48 2011
@@ -0,0 +1,225 @@
+<html t:type="Border" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
+
+  <h1>JavaScript Tests</h1>
+
+  <p>
+    This page executes a number of JavaScript tests.
+  </p>
+
+  <table id="results-table">
+    <tr>
+      <td>
+        assertEqual(map(null, null), [])
+    </td>
+      <td>
+        Map() on null returns an empty array
+    </td>
+    </tr>
+
+    <tr>
+      <td>
+        assertEqual(map(undefined, null), [])
+    </td>
+      <td>
+        Map() on undefined returns an empty array
+    </td>
+    </tr>
+
+    <tr>
+      <td>
+        assertEqual(map([], null), [])
+    </td>
+      <td>Map() on empty array returns an empty array</td>
+    </tr>
+
+    <tr>
+      <td>
+        assertEqual(map([1, 2, 3], function(e) { return e + 100; }), [101, 102, 103])
+        </td>
+      <td>
+        The normal use of map().
+        </td>
+    </tr>
+
+    <tr>
+      <td>
+        <pre>
+          assertEqual(map([1, 2, 3],
+          function(e, index) { return (e + 100) + "@" + index; }),
+          ["101@0", "102@1",
+          "103@2"])            
+        </pre>
+      </td>
+      <td>
+        The normal use of map().
+        </td>
+    </tr>
+
+    <tr>
+      <td>
+        <pre>
+          var initial = { };
+          assertSame(reduce([], initial, function() { }), initial);
+        </pre>
+      </td>
+      <td>
+        The initial value is returned, as is, when no reducing occurs due to empty/null/undefined list.
+        </td>
+    </tr>
+
+    <tr>
+
+      <td>
+        <pre>
+          assertEqual(reduce(["fred", "barney", "wilma"], {},
+          function(o, value, index) {
+          o[value] = index;
+
+          return o;
+          }),
+          { fred: 0, barney: 1, wilma: 2 })
+        </pre>
+      </td>
+      <td>
+        Show that the index of each element is passed to the reducer function.</td>
+    </tr>
+
+    <tr>
+      <td>
+        <pre>
+          var initial = [1, 2, 3]
+          assertSame(without(initial, 9), initial)
+        </pre>
+      </td>
+      <td>
+        Removing a value that does not exist in the array returns the array unchanged
+        </td>
+    </tr>
+
+    <tr>
+      <td>
+        assertEqual(without([1, 2, 2, 3, 4, 2, 5], 2), [1, 3, 4, 5])
+        </td>
+      <td>
+        Multiple matches should all be removed
+        </td>
+    </tr>
+
+    <tr>
+      <td>
+        <pre>
+          var initial = [1, 2, 3]
+          without(initial, 2)
+          assertEqual(initial, [1, 2, 3])
+          </pre>
+      </td>
+      <td>
+        The array should not be changed even if values are removed from the result
+        </td>
+    </tr>
+
+    <tr>
+      <td>assertEqual(mapcat(null,null), [])</td>
+      <td>mapcat() on empty/null/undefined returns empty list</td>
+    </tr>
+
+    <tr>
+      <td>
+        <pre>
+          var message = { }
+          var rcvd;
+
+          sub("foo", function(m) { rcvd = m; })
+
+          pub("foo", message)
+
+          assertSame(rcvd,
+          message)        
+        </pre>
+      </td>
+      <td>
+        Ensure that the message object passed to pub() is delivered to the listener.</td>
+    </tr>
+
+    <tr>
+      <td>
+        <pre>
+          var count = 0;
+
+          pub("foo"); assertEqual(count, 0)
+
+          var unsub = sub("foo", function() { count++; } )
+
+          pub("foo");
+          assertEqual(count, 1)
+
+          pub("foo"); assertEqual(count, 2)
+
+          pub("bar"); assertEqual(count, 2)
+
+          unsub()
+
+          pub("foo"); assertEqual(count, 2)
+          </pre>
+      </td>
+      <td>
+        Check that the function returned from sub() can be used to cancel message reciept</td>
+    </tr>
+
+    <tr>
+      <td>
+        <pre>
+          var pubs = []
+
+          sub("foo/bar", function() { pubs.push("foo/bar"); })
+          sub("foo", function() {
+          pubs.push("foo"); })
+
+          pub("foo/bar")
+
+          assertEqual(pubs, ["foo/bar", "foo"])                
+        </pre>
+      </td>
+      <td>
+        Check that messages are published to less specific selectors after more specific.
+        </td>
+    </tr>
+
+    <tr>
+      <td>
+        <pre>
+          var pubs = []
+
+          sub("foo/bar", function() { pubs.push("foo/bar"); })
+          sub("foo", function() {
+          pubs.push("foo"); })
+
+          pub("foo")
+
+          assertEqual(pubs, ["foo"])                
+        </pre>
+      </td>
+      <td>
+        Check that messages to less specific selectors do not invoke listeners of more specific selectors.        </td>
+    </tr>
+  </table>
+
+  <script>
+<![CDATA[
+    map = T5.map
+    mapcat = T5.mapcat
+    reduce = T5.reduce
+    without = T5.without
+
+    pub = T5.pub
+    sub = T5.sub
+
+
+    assertEqual = JST.assertEqual
+    assertSame = JST.assertSame
+
+    JST.runTestSuite("results-table");
+    ]]>
+  </script>
+</html>
+

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/js-testing.css
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/js-testing.css?rev=1101721&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/js-testing.css (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/js-testing.css Wed May 11 01:35:48 2011
@@ -0,0 +1,36 @@
+
+TABLE.js-results { width: 100%; }
+
+TABLE.js-results THEAD TR
+{
+    font-weight: bold;
+    background-color: black;
+    color: white;
+}
+
+TABLE.js-results TR.odd
+{
+  background-color: #e5e5e5;
+}
+
+TABLE.js-results TR.active
+{
+  font-weight: bold;
+  background-color: #ffff00;
+}
+
+TABLE.js-results TR.pass {
+    color: green;    
+}
+
+TABLE.js-results CAPTION {
+  font-weight: bold;
+}
+
+TABLE.js-results CAPTION.success { color: green; }
+
+TABLE.js-results CAPTION.failures { color: red; }
+
+TABLE.js-results TR.fail { color: red; }
+
+TABLE.js-results TR.exception { color: red; font-family: italic; }
\ No newline at end of file

Added: 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=1101721&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/js-testing.js (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/js-testing.js Wed May 11 01:35:48 2011
@@ -0,0 +1,148 @@
+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.
+	 */
+
+	/*
+	 * Requires ECMAScript 5 functions: - Array.isArray() - Object.keys() -
+	 * Array.prototype.forEach() - JSON.stringify()
+	 */
+
+	function identical(a, b, sortArrays) {
+
+		function sort(o) {
+
+			if (sortArrays === true && Array.isArray(o)) {
+				return o.sort();
+			} else if (typeof o !== "object" || o === null) {
+				return o;
+			}
+
+			var result = {};
+
+			Object.keys(o).sort().forEach(function(key) {
+				result[key] = sort(o[key]);
+			});
+
+			return result;
+		}
+
+		return JSON.stringify(sort(a)) === JSON.stringify(sort(b));
+	}
+
+	var resultElement;
+
+	var $fail = {};
+
+	function fail(text) {
+		resultElement.next().insert({
+			top : "FAIL - ",
+			bottom : "<hr>" + text
+		}).up().addClassName("fail");
+
+		throw $fail;
+	}
+
+	function toString(value) {
+
+		return Object.toJSON(value);
+	}
+
+	function failNotEqual(actual, expected) {
+		fail(toString(actual) + " !== " + toString(expected));
+	}
+
+	function assertSame(actual, expected) {
+		if (actual !== expected) {
+			failNotEqual(actual, expected);
+		}
+	}
+
+	function assertEqual(actual, expected) {
+
+		if (!identical(actual, expected)) {
+			failNotEqual(actual, expected);
+		}
+	}
+
+	function doRunTests(elementId) {
+
+		var passCount = 0;
+		var failCount = 0;
+
+		$(elementId).insert({
+			top : "<thead><th>Test</th><th>Description</th></tr></thead>"
+		});
+
+		$(elementId).addClassName("js-results").select("tbody tr:odd").each(
+				function(e) {
+					e.addClassName("odd");
+				});
+
+		$(elementId).select("tbody tr").each(function(row) {
+
+			row.addClassName("active");
+
+			row.scrollTo();
+
+			resultElement = $(row).select("td")[0];
+			resultNoted = false;
+
+			try {
+				eval(resultElement.textContent);
+
+				passCount++;
+
+				resultElement.next().insert({
+					top : "PASS - "
+				}).up().addClassName("pass");
+
+			} catch (e) {
+				failCount++;
+
+				if (e !== $fail) {
+					resultElement.next().insert({
+						top : "EXCEPTION - ",
+						bottom : "<hr>" + toString(e)
+					}).up().addClassName("exception");
+				}
+			}
+
+			row.removeClassName("active");
+		});
+
+		$(elementId)
+				.insert(
+						{
+							bottom : "<caption class='#{class}'>Results: #{pass} pass / #{fail} fail</caption>"
+									.interpolate({
+										class : failCount == 0 ? "success"
+												: "failures",
+										pass : passCount,
+										fail : failCount
+									})
+						});
+	}
+
+	function runTestSuite(elementId) {
+		Tapestry.onDOMLoaded(function() {
+			doRunTests(elementId);
+		});
+	}
+
+	return {
+		fail : fail,
+		assertEqual : assertEqual,
+		assertSame : assertSame,
+		runTestSuite : runTestSuite
+	};
+})();
\ No newline at end of file



Mime
View raw message