You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@shindig.apache.org by lr...@apache.org on 2008/09/11 00:21:55 UTC
svn commit: r694034 [2/2] - in /incubator/shindig/trunk/features: ./
opensocial-templates/
Added: incubator/shindig/trunk/features/opensocial-templates/domutil.js
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/features/opensocial-templates/domutil.js?rev=694034&view=auto
==============================================================================
--- incubator/shindig/trunk/features/opensocial-templates/domutil.js (added)
+++ incubator/shindig/trunk/features/opensocial-templates/domutil.js Wed Sep 10 15:21:55 2008
@@ -0,0 +1,164 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+// A pseudo namespace.
+var domutil = {};
+
+/**
+ * Helper functions adapted from Selenium code.
+ *
+ * Determines if the specified element is visible. An element can be rendered
+ * invisible by setting the CSS "visibility" property to "hidden", or the
+ * "display" property to "none", either for the element itself or one if its
+ * ancestors. This method will fail if the element is not present.
+ * @param {Node} node The node to check.
+ * @return {boolean} Whether the node is visible.
+ */
+domutil.isVisible = function(node) {
+ // This test is necessary because getComputedStyle returns nothing
+ // for WebKit-based browsers if node not in document. See comment below.
+ if (node.style.display == 'none' || node.style.visibility == 'hidden') {
+ return false;
+ }
+ var visibility = this.findEffectiveStyleProperty(node, 'visibility');
+ var display = this.findEffectiveStyleProperty(node, 'display');
+ return visibility != 'hidden' && display != 'none';
+};
+
+
+/**
+ * Returns the value of the effective style specified by {@code property}.
+ * @param {Node} element The node to query.
+ * @param {string} property The name of a style that is of interest.
+ * @return {string} The value of style {@code property}.
+ */
+domutil.findEffectiveStyleProperty = function(element, property) {
+ var effectiveStyle = this.findEffectiveStyle(element);
+ var propertyValue = effectiveStyle[property];
+ if (propertyValue == 'inherit' && element.parentNode.style) {
+ return this.findEffectiveStyleProperty(element.parentNode, property);
+ }
+ return propertyValue;
+};
+
+
+/**
+ * Returns the effective style object.
+ * @param {Node} element The node to query.
+ * @return {CSSStyleDeclaration|undefined} The style object.
+ */
+domutil.findEffectiveStyle = function(element) {
+ if (!element.style) {
+ return undefined; // not a styled element
+ }
+ if (window.getComputedStyle) {
+ // DOM-Level-2-CSS
+ // WebKit-based browsers (Safari included) return nothing if the element
+ // is not a descendent of document ...
+ return window.getComputedStyle(element, null);
+ }
+ if (element.currentStyle) {
+ // non-standard IE alternative
+ return element.currentStyle;
+ // TODO: this won't really work in a general sense, as
+ // currentStyle is not identical to getComputedStyle()
+ // ... but it's good enough for 'visibility'
+ }
+ throw new Error('cannot determine effective stylesheet in this browser');
+};
+
+
+/**
+ * Returns the text content of the current node, without markup and invisible
+ * symbols. New lines are stripped and whitespace is collapsed,
+ * such that each character would be visible.
+ *
+ * @param {Node} node The node from which we are getting content.
+ * @return {string} The text content.
+ */
+domutil.getVisibleText = function(node) {
+ var textContent;
+ // NOTE(kjin): IE innerText is more like Firefox textContent -- visibility
+ // is not concerned. Safari 3 and Chrome innerText is just the visible text.
+ var buf = [];
+ domutil.getVisibleText_(node, buf, true);
+ textContent = buf.join('');
+
+ textContent = textContent.replace(/\xAD/g, '');
+
+ textContent = textContent.replace(/ +/g, ' ');
+ if (textContent != ' ') {
+ textContent = textContent.replace(/^\s*/, '');
+ }
+
+ return textContent;
+};
+
+
+/**
+ * Returns the domutil.getVisibleText without trailing space, if any.
+ *
+ * @param {Node} node The node from which we are getting content.
+ * @return {string} The text content.
+ */
+domutil.getVisibleTextTrim = function(node) {
+ return domutil.getVisibleText(node).replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
+};
+
+
+/**
+ * Recursive support function for text content retrieval.
+ *
+ * @param {Node} node The node from which we are getting content.
+ * @param {Array} buf string buffer.
+ * @param {boolean} normalizeWhitespace Whether to normalize whitespace.
+ * @private
+ */
+domutil.getVisibleText_ = function(node, buf, normalizeWhitespace) {
+ var TAGS_TO_IGNORE_ = {
+ 'SCRIPT': 1,
+ 'STYLE': 1,
+ 'HEAD': 1,
+ 'IFRAME': 1,
+ 'OBJECT': 1
+ };
+ var PREDEFINED_TAG_VALUES_ = {'IMG': ' ', 'BR': '\n'};
+
+ if (node.nodeName in TAGS_TO_IGNORE_) {
+ // ignore certain tags
+ } else if (node.nodeType == 3) {
+ if (normalizeWhitespace) {
+ buf.push(String(node.nodeValue).replace(/(\r\n|\r|\n)/g, ''));
+ } else {
+ buf.push(node.nodeValue);
+ }
+ } else if (!domutil.isVisible(node)) {
+ // ignore invisible node
+ // this has to be after the check for NodeType.TEXT because text node
+ // does not have style.
+ } else if (node.nodeName in PREDEFINED_TAG_VALUES_) {
+ buf.push(PREDEFINED_TAG_VALUES_[node.nodeName]);
+ } else {
+ var child = node.firstChild;
+ while (child) {
+ domutil.getVisibleText_(child, buf, normalizeWhitespace);
+ child = child.nextSibling;
+ }
+ }
+};
+
Added: incubator/shindig/trunk/features/opensocial-templates/feature.xml
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/features/opensocial-templates/feature.xml?rev=694034&view=auto
==============================================================================
--- incubator/shindig/trunk/features/opensocial-templates/feature.xml (added)
+++ incubator/shindig/trunk/features/opensocial-templates/feature.xml Wed Sep 10 15:21:55 2008
@@ -0,0 +1,49 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you 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.
+
+TODO(davidbyttow): In order to use this feature, you must specify
+tell the content-rewrite feature to allow tags. Otherwise,
+<script type="text/os-template"> will be parsed as JS.
+ E.g.,
+ <Optional feature="content-rewrite">
+ <Param name="include-tags"/>
+ </Optional>
+
+TODO(davidbyttow): Implement local versions of jstemplate.
+
+N.B.: Currently the jstemplate library is served directly from googlecode.
+This should not be used in a production setting.
+-->
+<feature>
+ <name>opensocial-templates</name>
+ <gadget>
+ <script src="http://google-jstemplate.googlecode.com/svn/trunk/util.js"></script>
+ <script src="http://google-jstemplate.googlecode.com/svn/trunk/jsevalcontext.js"></script>
+ <script src="http://google-jstemplate.googlecode.com/svn/trunk/jstemplate.js"></script>
+ <script src="jstemplate_debug.js"></script>
+ <script src="base.js"></script>
+ <script src="namespaces.js"></script>
+ <script src="util.js"></script>
+ <script src="template.js"></script>
+ <script src="compiler.js"></script>
+ <script src="loader.js"></script>
+ <script src="container.js"></script>
+ <script src="os.js"></script>
+ <script src="data.js"></script>
+ </gadget>
+</feature>
Added: incubator/shindig/trunk/features/opensocial-templates/jstemplate_debug.js
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/features/opensocial-templates/jstemplate_debug.js?rev=694034&view=auto
==============================================================================
--- incubator/shindig/trunk/features/opensocial-templates/jstemplate_debug.js (added)
+++ incubator/shindig/trunk/features/opensocial-templates/jstemplate_debug.js Wed Sep 10 15:21:55 2008
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+/**
+ * @fileoverview Debug functions to be linked against the JSTemplate library
+ * to provide debugging output.
+ *
+ * TODO(levik):
+ * - Support more output types (currently Firebug only)
+ * - Filter "mundane" errors (such as non-existent variable lookups)
+ */
+
+
+function jsToSource(js) {}
+
+function check(x) {}
+
+function logUrl(url) {}
+function logHtml(url) {}
+function logError(msg, file, line) {}
+function dump(exception) {}
+function rethrow(e) {}
+function getSourceLine(level) { return ""; }
+
+function Profiler() {}
+Profiler.monitor = function(obj, method, trace, opt_l, opt_fmt) {};
+Profiler.monitorAll = function(obj, trace, opt_l) {};
+Profiler.dump = function() {};
Added: incubator/shindig/trunk/features/opensocial-templates/loader.js
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/features/opensocial-templates/loader.js?rev=694034&view=auto
==============================================================================
--- incubator/shindig/trunk/features/opensocial-templates/loader.js (added)
+++ incubator/shindig/trunk/features/opensocial-templates/loader.js Wed Sep 10 15:21:55 2008
@@ -0,0 +1,284 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+/**
+ * @fileoverview OpenSocial Template loader. Can be used to load template
+ * libraries via URL. Supports Javascript and CSS injection.
+ *
+ * Usage:
+ * os.Loader.loadUrl("/path/templatelib.xml", function() { doSomething(); });
+ *
+ * or
+ * os.Loader.loadContent(
+ * "<Templates><Template tag="foo:bar">...</Template></Templates>");
+ *
+ * The Template Library should have the following structure:
+ *
+ * <Templates xmlns:foo="http://foo.com/">
+ * <Namspace prefix="foo" url="http://foo.com/"/>
+ * <Template tag="foo:bar">[Template Markup Here]</Template>
+ * <Style>[CSS for all templates]</Style>
+ * <JavaScript>
+ * function usedByAllTemplates() { ... };
+ * </JavaScript>
+ * <TemplateDef tag="foo:baz">
+ * <Template>[Markup for foo:baz]</Template>
+ * <Style>[CSS for foo:baz]</Style>
+ * <JavaScript>
+ * function usedByFooBaz() { ... };
+ * </JavaScript>
+ * </TemplateDef>
+ * </Templates>
+ *
+ * TODO(levik): Implement dependency support - inject JS and CSS lazily.
+ * TODO(levik): More error handling and reporting of ill-formed XML files.
+ */
+
+os.Loader = {};
+
+/**
+ * A map of URLs which were already loaded.
+ */
+os.Loader.loadedUrls_ = {};
+
+/**
+ * Load a remote URL via XMLHttpRequest or gadgets.io.makeRequest API
+ * when in context of a gadget.
+ * @param {string} url The URL that is to be fetched.
+ * @param {Function} callback Function to call once loaded.
+ */
+os.Loader.loadUrl = function(url, callback) {
+ if (typeof(window['gadgets']) != "undefined") {
+ os.Loader.requestUrlGadgets_(url, callback);
+ } else {
+ os.Loader.requestUrlXHR_(url, callback);
+ }
+};
+
+/**
+ * Loads a Template Library from a URL via XMLHttpRequest. Once the library is
+ * loaded, the callback function is called. A map is kept to prevent loading
+ * the same URL twice.
+ * @param {string} url The URL of the Template Library.
+ * @param {Function} callback Function to call once loaded.
+ */
+os.Loader.requestUrlXHR_ = function(url, callback) {
+ if (os.Loader.loadedUrls_[url]) {
+ window.setTimeout(callback, 0);
+ return;
+ }
+ var req = null;
+ if (typeof(XMLHttpRequest) != "undefined") {
+ req = new XMLHttpRequest();
+ } else {
+ req = new ActiveXObject("MSXML2.XMLHTTP");
+ }
+ req.open("GET", url, true);
+ req.onreadystatechange = function() {
+ if (req.readyState == 4) {
+ os.Loader.loadContent(req.responseText);
+ os.Loader.loadedUrls_[url] = true;
+ callback();
+ }
+ };
+ req.send(null);
+}
+
+/**
+ * Fetch content remotely using the gadgets.io.makeRequest API.
+ * @param {string} url The URL where the content is located.
+ * @param {Function} callback Function to call with the data from the URL
+ * once it is fetched.
+ */
+os.Loader.requestUrlGadgets_ = function(url, callback) {
+ var params = {};
+ var gadgets = window['gadgets'];
+
+ if (os.Loader.loadedUrls_[url]) {
+ window.setTimeout(callback, 0);
+ return;
+ }
+ params[gadgets.io.RequestParameters.CONTENT_TYPE] =
+ gadgets.io.ContentType.TEXT;
+ gadgets.io.makeRequest(url, function(obj) {
+ os.Loader.loadContent(obj.data);
+ os.Loader.loadedUrls_[url] = true;
+ callback();
+ }, params);
+};
+
+/**
+ * Loads a number of Template libraries, specified by an array of URLs. Once
+ * all the libraries have been loaded, the callback is called.
+ * @param {Array.<string>} urls An array of URLs of Template Libraries to load.
+ * @param {Function} callback Function to call once all libraries are loaded.
+ */
+os.Loader.loadUrls = function(urls, callback) {
+ var loadOne = function() {
+ if (urls.length == 0) {
+ callback();
+ } else {
+ os.Loader.loadUrl(urls.pop(), loadOne);
+ }
+ }
+ loadOne();
+};
+
+/**
+ * Processes the XML markup of a Template Library.
+ */
+os.Loader.loadContent = function(xmlString) {
+ var doc = os.parseXML_(xmlString);
+ var templatesNode = doc.firstChild;
+ os.Loader.processTemplatesNode(templatesNode);
+};
+
+/**
+ * Gets the function that should be used for processing a tag.
+ * @param {string} tagName Name of the tag.
+ * @return {Function|null} The function for processing such tags.
+ */
+os.Loader.getProcessorFunction_ = function(tagName) {
+ // TODO(levik): This won't work once compiler does name mangling.
+ return os.Loader['process' + tagName + 'Node'] || null;
+};
+
+/**
+ * Processes the <Templates> node.
+ */
+os.Loader.processTemplatesNode = function(node) {
+ for (var child = node.firstChild; child; child = child.nextSibling) {
+ if (child.nodeType == DOM_ELEMENT_NODE) {
+ var handler = os.Loader.getProcessorFunction_(child.tagName);
+ if (handler) {
+ handler(child);
+ }
+ }
+ }
+};
+
+/**
+ * Processes the <Namespace> node.
+ */
+os.Loader.processNamespaceNode = function(node) {
+ var prefix = node.getAttribute("prefix");
+ var url = node.getAttribute("url");
+ os.createNamespace(prefix, url);
+};
+
+/**
+ * Processes the <TemplateDef> node
+ */
+os.Loader.processTemplateDefNode = function(node) {
+ var tag = node.getAttribute("tag");
+ for (var child = node.firstChild; child; child = child.nextSibling) {
+ if (child.nodeType == DOM_ELEMENT_NODE) {
+ // TODO(levik): This won't work once compiler does name mangling.
+ var handler = os.Loader.getProcessorFunction_(child.tagName);
+ if (handler) {
+ handler(child, tag);
+ }
+ }
+ }
+};
+
+/**
+ * Processes the <Template> node
+ */
+os.Loader.processTemplateNode = function(node, opt_tag) {
+ var tag = opt_tag || node.getAttribute("tag");
+ if (tag) {
+ var tagParts = tag.split(":");
+ if (tagParts.length != 2) {
+ throw "Invalid tag name: " + tag;
+ }
+ var nsObj = os.getNamespace(tagParts[0]);
+ if (!nsObj) {
+ throw "Namespace not registered: " + tagParts[0] +
+ " while trying to define " + tag;
+ }
+ var template = os.compileXMLNode(node);
+ nsObj[tagParts[1]] = os.createTemplateCustomTag(template);
+ }
+};
+
+/**
+ * Processes the <JavaScript> node
+ */
+os.Loader.processJavaScriptNode = function(node, opt_tag) {
+ for (var contentNode = node.firstChild; contentNode;
+ contentNode = contentNode.nextSibling) {
+ // TODO(levik): Skip empty text nodes (with whitespace and newlines)
+ os.Loader.injectJavaScript(contentNode.nodeValue);
+ }
+};
+
+/**
+ * Processes the <Style> node
+ */
+os.Loader.processStyleNode = function(node, opt_tag) {
+ for (var contentNode = node.firstChild; contentNode;
+ contentNode = contentNode.nextSibling) {
+ // TODO(levik): Skip empty text nodes (with whitespace and newlines)
+ os.Loader.injectStyle(contentNode.nodeValue);
+ }
+};
+
+/**
+ * @type {Element} DOM node used for dynamic injection of JavaScript.
+ * @private
+ * TODO(davidbyttow): Only retrieve this once if JavaScript injection was
+ * actually requested.
+ */
+os.Loader.headNode_ = document.getElementsByTagName('head')[0] ||
+ document.getElementsByTagName('*')[0];
+
+/**
+ * Injects and evaluates JavaScript code synchronously in the global scope.
+ */
+os.Loader.injectJavaScript = function(jsCode) {
+ var scriptNode = document.createElement('script');
+ scriptNode.type = 'text/javascript';
+ scriptNode.text = jsCode;
+ os.Loader.headNode_.appendChild(scriptNode);
+};
+
+/**
+ * Injects CSS Style code into the page.
+ */
+os.Loader.injectStyle = function(cssCode) {
+ var sheet;
+ if (document.styleSheets.length == 0) {
+ document.getElementsByTagName("head")[0].appendChild(
+ document.createElement("style"));
+ }
+ sheet = document.styleSheets[0];
+ var rules = cssCode.split("}");
+ for (var i = 0; i < rules.length; i++) {
+ var rule = rules[i].replace(/\n/g, "").replace(/\s+/g, " ");
+ if (rule.length > 2) {
+ if (sheet.insertRule) {
+ rule = rule + "}";
+ sheet.insertRule(rule, sheet.cssRules.length);
+ } else {
+ var ruleParts = rule.split("{");
+ sheet.addRule(ruleParts[0], ruleParts[1]);
+ }
+ }
+ }
+};
Added: incubator/shindig/trunk/features/opensocial-templates/loader_test.js
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/features/opensocial-templates/loader_test.js?rev=694034&view=auto
==============================================================================
--- incubator/shindig/trunk/features/opensocial-templates/loader_test.js (added)
+++ incubator/shindig/trunk/features/opensocial-templates/loader_test.js Wed Sep 10 15:21:55 2008
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+/**
+ * Unit test for injecting JavaScript into the global scope with the
+ * os.Loader.
+ */
+function testInjectJavaScript() {
+ var jsCode = "function testFunction() { return 'foo'; }";
+ os.Loader.injectJavaScript(jsCode);
+ assertTrue(window.testFunction instanceof Function);
+ assertEquals(window.testFunction(), 'foo');
+}
+
+/**
+ * Unit test for injecting CSS through the os.Loader.
+ */
+function testInjectStyle() {
+ var cssCode = '.testCSS { width: 100px; height: 200px; }';
+ os.Loader.injectStyle(cssCode);
+ var rule = getStyleRule('.testCSS');
+ assertNotNull(rule);
+ assertEquals(rule.style.width, '100px');
+ assertEquals(rule.style.height, '200px');
+}
+
+/**
+ * @type {String} Template XML data for testLoadContent.
+ */
+var testContentXML =
+ '<Templates xmlns:test="http://www.google.com/#test">' +
+ ' <Namespace prefix="test" url="http://www.google.com/#test"/>' +
+ ' <Template tag="test:tag">' +
+ ' <div id="tag"></div>' +
+ ' </Template>' +
+ ' <JavaScript>' +
+ ' function testJavaScript() {' +
+ ' return "testJavaScript";' +
+ ' }' +
+ ' </JavaScript>' +
+ ' <Style>' +
+ ' .testStyle {' +
+ ' width: 24px;' +
+ ' }' +
+ ' </Style>' +
+ ' <TemplateDef tag="test:tagDef">' +
+ ' <Template>' +
+ ' <div id="tagDef"></div>' +
+ ' </Template>' +
+ ' <JavaScript>' +
+ ' function testJavaScriptDef() {' +
+ ' return "testJavaScriptDef";' +
+ ' }' +
+ ' </JavaScript>' +
+ ' <Style>' +
+ ' .testStyleDef {' +
+ ' height: 42px;' +
+ ' }' +
+ ' </Style>' +
+ ' </TemplateDef>' +
+ '</Templates>';
+
+/**
+ * System test for os.loadContent functionality. This tests
+ * all functionality except for XHR.
+ */
+function testLoadContent() {
+ os.Loader.loadContent(testContentXML);
+
+ // Verify registered tags.
+ var ns = os.nsmap_['test'];
+ assertNotNull(ns);
+ assertTrue(ns['tag'] instanceof Function);
+ assertTrue(ns['tagDef'] instanceof Function);
+
+ // Verify JavaScript functions.
+ assertTrue(window['testJavaScript'] instanceof Function);
+ assertEquals(window.testJavaScript(), 'testJavaScript');
+ assertTrue(window['testJavaScriptDef'] instanceof Function);
+ assertEquals(window.testJavaScriptDef(), 'testJavaScriptDef');
+
+ // Verify styles.
+ var rule = getStyleRule('.testStyle');
+ assertNotNull(rule);
+ assertEquals(rule.style.width, '24px');
+ var ruleDef = getStyleRule('.testStyleDef');
+ assertNotNull(ruleDef);
+ assertEquals(ruleDef.style.height, '42px');
+}
+
+/**
+ * Utility function for retrieving a style rule by selector text
+ * if its available.
+ * @param {string} name Selector text name.
+ * @return {Object} CSSRule object.
+ */
+function getStyleRule(name) {
+ var sheets = document.styleSheets;
+ for (var i = 0; i < sheets.length; ++i) {
+ var rules = sheets[i].cssRules || sheets[i].rules;
+ if (rules) {
+ for (var j = 0; j < rules.length; ++j) {
+ if (rules[j].selectorText == name) {
+ return rules[j];
+ }
+ }
+ }
+ }
+ return null;
+}
Added: incubator/shindig/trunk/features/opensocial-templates/namespaces.js
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/features/opensocial-templates/namespaces.js?rev=694034&view=auto
==============================================================================
--- incubator/shindig/trunk/features/opensocial-templates/namespaces.js (added)
+++ incubator/shindig/trunk/features/opensocial-templates/namespaces.js Wed Sep 10 15:21:55 2008
@@ -0,0 +1,203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+/**
+ * @fileoverview Implements namespace support for custom tags.
+ *
+ * TODO(davidbyttow): Refactor this.
+ */
+
+/**
+ * Map of namespace collections.
+ *
+ * Each namespace collection is either a map of tag handlers, or an object
+ * that has a getTag(tagName) method that will return a tag handler based on
+ * name.
+ *
+ * A tag handler function should be have the following signature:
+ * function({Element} node, {Object} data, {JSEvalContext} context)
+ * where context is the JSEvalContext used to wrap data.
+ *
+ * For simpler implementations,
+ * function({Element} node, {Object} data)
+ * can be used, omitting the third param.
+ *
+ * Handler functions can return a string, a DOM Element or an Object with
+ * {Element} root and, optionally, {Function} onAttach properties.
+ *
+ * @type {Object}
+ * @private
+ */
+os.nsmap_ = {};
+
+/**
+ * Map of namespace prefixes to full urls.
+ * @type {Object}
+ * @private
+ */
+os.nsurls_ = {};
+
+/***
+ * Registers the given namespace with a specified URL. Throws an error if it
+ * already exists as a different URL.
+ * @param {string} ns Namespace tag.
+ * @param {string} url URL for namespace.
+ * @return {Object} The object map of registered tags.
+ */
+os.createNamespace = function(ns, url) {
+ var tags = os.nsmap_[ns];
+ if (!tags) {
+ tags = {};
+ os.nsmap_[ns] = tags;
+ os.nsurls_[ns] = url;
+ } else if (os.nsurls_[ns] != url) {
+ throw("Namespace " + ns + " already defined with url " + os.nsurls_[ns]);
+ }
+ return tags;
+};
+
+/**
+ * Returns the namespace object for a given prefix.
+ */
+os.getNamespace = function(prefix) {
+ return os.nsmap_[prefix];
+};
+
+os.addNamespace = function(ns, url, nsObj) {
+ if (os.nsmap_[ns]) {
+ throw ("Namespace '" + ns + "' already exists!");
+ }
+ os.nsmap_[ns] = nsObj;
+ os.nsurls_[ns] = url;
+};
+
+os.getCustomTag = function(ns, tag) {
+ var nsObj = os.nsmap_[ns];
+ if (!nsObj) {
+ return null;
+ }
+ if (nsObj.getTag) {
+ return nsObj.getTag(tag);
+ } else {
+ return nsObj[tag];
+ }
+};
+
+/**
+ * Returns the XML namespace declarations that need to be injected into a
+ * particular template to make it valid XML. Uses the defined namespaces to
+ * see which are available, and checks that they are used in the supplied code.
+ * An empty string is returned if no injection is needed.
+ *
+ * @param {string} templateSrc Template source code.
+ * @return {string} A string of xmlns delcarations required for this tempalte.
+ */
+os.getRequiredNamespaces = function(templateSrc) {
+ var codeToInject = "";
+ for (var ns in os.nsurls_) {
+ if (templateSrc.indexOf("<" + ns + ":") >= 0 &&
+ templateSrc.indexOf("xmlns:" + ns + ":") < 0) {
+ codeToInject += " xmlns:" + ns + "=\"" + os.nsurls_[ns] + "\"";
+ }
+ }
+ return codeToInject;
+};
+
+/**
+ * Checks if an XML tag corresponds to a known custom function.
+ * If so, the namespace and tag name are returned in an array.
+ * @param {string} nodeName Name of XML tag.
+ * @return {Array.<string>|null}
+ */
+os.checkCustom_ = function(nodeName) {
+ var index;
+ if ((index = nodeName.indexOf(':')) < 0) {
+ return null;
+ }
+ var ns = nodeName.substring(0, index);
+ var tag = nodeName.substring(index + 1);
+ if (os.getCustomTag(ns, tag)) {
+ return [ns, tag];
+ }
+ return null;
+};
+
+/**
+ * Define 'os:renderAll' and 'os:Html' tags and the @onAttach attribute
+ */
+os.defineBuiltinTags = function() {
+ var osn = os.getNamespace("os") ||
+ os.createNamespace("os", "http://opensocial.com/#template");
+
+ /**
+ * renderAll custom tag renders the specified child nodes of the current
+ * context.
+ */
+ osn.renderAll = function(node, data, context) {
+ var parent = context.getVariable(os.VAR_parentnode);
+ var exp = node.getAttribute("content") || "*";
+ var result = os.getValueFromNode_(parent, exp);
+ if (!result) {
+ return "";
+ } else if (typeof(result) == "string") {
+ var textNode = document.createTextNode(result);
+ result = [];
+ result.push(textNode);
+ } else if (!isArray(result)) {
+ var resultArray = [];
+ for (var i = 0; i < result.childNodes.length; i++) {
+ resultArray.push(result.childNodes[i]);
+ }
+ result = resultArray;
+ } else if (exp != "*" && result.length == 1 &&
+ result[0].nodeType == DOM_ELEMENT_NODE) {
+ // When we call <os:renderAll content="tag"/>, render the inner content
+ // of the tag returned, not the tag itself.
+ var resultArray = [];
+ for (var i = 0; i < result[0].childNodes.length; i++) {
+ resultArray.push(result[0].childNodes[i]);
+ }
+ result = resultArray;
+ }
+
+ return result;
+ }
+ osn.RenderAll = osn.renderAll;
+
+ osn.Html = function(node) {
+ var html = node.code ? "" + node.code : node.getAttribute("code") || "";
+ // TODO(levik): Sanitize the HTML here to avoid script injection issues.
+ // Perhaps use the gadgets sanitizer if available.
+ return html;
+ }
+
+ function createClosure(object, method) {
+ return function() {
+ method.apply(object);
+ }
+ }
+
+ function processOnAttach(node, code, data, context) {
+ var callbacks = context.getVariable(os.VAR_callbacks);
+ var func = new Function(code);
+ callbacks.push(createClosure(node, func));
+ }
+ os.registerAttribute("onAttach", processOnAttach);
+};
+
+os.defineBuiltinTags();
Added: incubator/shindig/trunk/features/opensocial-templates/os.js
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/features/opensocial-templates/os.js?rev=694034&view=auto
==============================================================================
--- incubator/shindig/trunk/features/opensocial-templates/os.js (added)
+++ incubator/shindig/trunk/features/opensocial-templates/os.js Wed Sep 10 15:21:55 2008
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+/**
+ * @fileoverview Implements os:renderAll tag and OpenSocial-specific
+ * identifier resolver.
+ */
+
+/**
+ * Identifier Resolver function for OpenSocial objects.
+ * Checks for:
+ * <ul>
+ * <li>Simple property</li>
+ * <li>JavaBean-style getter</li>
+ * <li>OpenSocial Field</li>
+ * <li>Data result set</li>
+ * </ul>
+ * @param {Object} object The object in the scope of which to get a named
+ * property.
+ * @param {string} name The name of the property to get.
+ * @return {Object?} The property requested.
+ */
+os.resolveOpenSocialIdentifier = function(object, name) {
+ // Simple property from object.
+ if (typeof(object[name]) != "undefined") {
+ return object[name];
+ }
+
+ // JavaBean-style getter method.
+ var functionName = os.getPropertyGetterName(name);
+ if (object[functionName]) {
+ return object[functionName]();
+ }
+
+ // Check OpenSocial field by dictionary mapping
+ if (object.getField) {
+ var fieldData = object.getField(name);
+ if (fieldData) {
+ return fieldData;
+ }
+ }
+
+ // Multi-purpose get() method
+ if (object.get) {
+ var responseItem = object.get(name);
+
+ // ResponseItem is a data set
+ if (responseItem && responseItem.getData) {
+ var data = responseItem.getData();
+ // Return array payload where appropriate
+ return data.array_ || data;
+ }
+ return responseItem;
+ }
+
+ // Return undefined value, to avoid confusing with existing value of "null".
+ var und;
+ return und;
+};
+
+os.setIdentifierResolver(os.resolveOpenSocialIdentifier);
+
+/**
+ * Create methods for an object based upon a field map for OpenSocial.
+ * @param {Object} object Class object to have methods created for.
+ * @param {Object} fields A key-value map object to retrieve fields (keys) and
+ * method names (values) from.
+ * @private
+ */
+os.createOpenSocialGetMethods_ = function(object, fields) {
+ if (object && fields) {
+ for (var key in fields) {
+ var value = fields[key];
+ var getter = os.getPropertyGetterName(value);
+ object.prototype[getter] = function() {
+ this.getField(key);
+ }
+ }
+ }
+};
+
+/**
+ * Automatically register JavaBean-style methods for various OpenSocial objects.
+ * @private
+ */
+os.registerOpenSocialFields_ = function() {
+ var fields = os.resolveOpenSocialIdentifier.FIELDS;
+ if (opensocial) {
+ // TODO: Add more OpenSocial objects.
+ if (opensocial.Person) {
+ //os.createOpenSocialGetMethods_(opensocial.Person, opensocial.Person.Field);
+ }
+ }
+};
+
+os.registerOpenSocialFields_();
Added: incubator/shindig/trunk/features/opensocial-templates/os_test.js
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/features/opensocial-templates/os_test.js?rev=694034&view=auto
==============================================================================
--- incubator/shindig/trunk/features/opensocial-templates/os_test.js (added)
+++ incubator/shindig/trunk/features/opensocial-templates/os_test.js Wed Sep 10 15:21:55 2008
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+/**
+ * Unit test for testing the behavior of the OpenSocial identifier resolver.
+ */
+function testResolveOpenSocialIdentifier() {
+ /**
+ * Sample class with a property, property getter, custom getField and a get()
+ * method.
+ */
+ var TestClass = function() {
+ this.foo = 'fooData';
+ this.bar_ = 'barData';
+ this.thumbnailUrl_ = 'thumbnailUrlData';
+ this.responseItem_ = {};
+ this.responseItem_.getData = function() {
+ return 'responseItemData';
+ };
+ };
+ TestClass.prototype.getBar = function() {
+ return this.bar_;
+ };
+ TestClass.prototype.getField = function(field) {
+ if (field == 'THUMBNAIL_URL') {
+ return this.thumbnailUrl_;
+ }
+ return null;
+ };
+ TestClass.prototype.get = function(field) {
+ if (field == 'responseItem') {
+ return this.responseItem_;
+ }
+ return null;
+ };
+
+ var obj = new TestClass();
+
+ assertEquals('fooData', os.resolveOpenSocialIdentifier(obj, 'foo'));
+ assertEquals('barData', os.resolveOpenSocialIdentifier(obj, 'bar'));
+ assertEquals('thumbnailUrlData',
+ os.resolveOpenSocialIdentifier(obj, 'THUMBNAIL_URL'));
+ assertEquals('responseItemData',
+ os.resolveOpenSocialIdentifier(obj, 'responseItem'));
+}
Added: incubator/shindig/trunk/features/opensocial-templates/ost_test.html
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/features/opensocial-templates/ost_test.html?rev=694034&view=auto
==============================================================================
--- incubator/shindig/trunk/features/opensocial-templates/ost_test.html (added)
+++ incubator/shindig/trunk/features/opensocial-templates/ost_test.html Wed Sep 10 15:21:55 2008
@@ -0,0 +1,127 @@
+<!--
+Copyright 2006 Google Inc.
+
+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.
+-->
+<html>
+ <head>
+ <title>OpenSocial templates JsUnit tests</title>
+ <script src="jsunit/app/jsUnitCore.js"></script>
+ <script src="jsunit/app/jsUnitMockTimeout.js"></script>
+ <!-- the set of js files that make up opensocial-templates feature, as they appear in feature.xml -->
+ <script src="http://google-jstemplate.googlecode.com/svn/trunk/util.js"></script>
+ <script src="http://google-jstemplate.googlecode.com/svn/trunk/jsevalcontext.js"></script>
+ <script src="http://google-jstemplate.googlecode.com/svn/trunk/jstemplate.js"></script>
+ <script src="jstemplate_debug.js"></script>
+ <script src="base.js"></script>
+ <script src="namespaces.js"></script>
+ <script src="util.js"></script>
+ <script src="template.js"></script>
+ <script src="compiler.js"></script>
+ <script src="loader.js"></script>
+ <script src="container.js"></script>
+ <script src="os.js"></script>
+ <script src="data.js"></script>
+ <!-- the JsUnit tests -->
+ <script src="domutil.js"></script>
+ <script type="text/javascript" src="compiler_test.js"></script>
+ <script type="text/javascript" src="data_test.js"></script>
+ <script type="text/javascript" src="container_test.js"></script>
+ <script type="text/javascript" src="loader_test.js"></script>
+ <script type="text/javascript" src="os_test.js"></script>
+ <script type="text/javascript" src="util_test.js"></script>
+ <script type="text/javascript" src="template_test.js"></script>
+ </head>
+ <body>
+ <script type="text/os-template" tag="os:Test">tag template</script>
+ <script type="text/os-template">
+ <div id="test"><os:Test/></div>
+ </script>
+
+ <div style="display: none">
+ <div id="domSource">
+ <ul>
+ <li>one</li>
+ <li>two</li>
+ </ul>
+ <b>bold</b>
+ </div>
+ <div id="domTarget">
+ </div>
+ </div>
+
+ <xmp id="_T_Substitution_text" style="display: none">${title}:${value}</xmp>
+
+ <xmp id="_T_Substitution_text" style="display: none">${title}:${value}</xmp>
+ <xmp id="_T_Substitution_attribute" style="display: none">
+ <button id="${id}" style="color: ${color}" a1="value ${A1}">${text}</button>
+ </xmp>
+ <xmp id="my:user" style="display: none">
+ <a href="${url}">${name}</a> (${$my.foo})
+ </xmp>
+ <xmp id="my:record" style="display: none">
+ <b style="color: ${$my.color}">${title}</b>: <my:user context="user" foo="${$my.foo}"/>
+ </xmp>
+ <xmp id="_T_Substitution_nested" style="display: none">
+ <div repeat="users">
+ <my:record color="${color}" foo="${user.id}"/>
+ </div>
+ </xmp>
+
+ <xmp id="_T_Conditional_Number" style="display: none">
+ <span if="42==42">TRUE</span>
+ <span if="!(42==42)">FALSE</span>
+ </xmp>
+ <xmp id="_T_Conditional_String" style="display: none">
+ <span if="'101'=='101'">TRUE</span>
+ <span if="'101'!='101'">FALSE</span>
+ </xmp>
+ <xmp id="_T_Conditional_Mixed" style="display: none">
+ <span if="'101'>42">TRUE</span>
+ <span if="!('101'>42)">FALSE</span>
+ </xmp>
+
+ <xmp id="_T_Repeat" style="display: none">
+ <div repeat="entries">
+ ${data}
+ </div>
+ </xmp>
+
+ <xmp id="_T_Options" style="display: none">
+ <select id="options">
+ <option repeat="options" value="${value}">${value}</option>
+ </select>
+ </xmp>
+
+ <xmp id="custom:list" style="display: none">
+ <div repeat="$my.item"><os:renderAll content="title"/><os:renderAll content="body"/></div>
+ </xmp>
+
+ <xmp id="_T_List" style="display: none">
+ <custom:list>
+ <item>
+ <title>hello</title>
+ <body>world</body>
+ </item>
+ </custom:list>
+ </xmp>
+
+ <xmp id="_T_Tag_input" style="display: none">
+ <!-- Uses a controller to grab the value ${def} as the default -->
+ <custom:input value="data"/>
+ </xmp>
+ <xmp id="_T_Tag_blink" style="display: none">
+ <custom:blink>blink text</custom:blink>
+ </xmp>
+ </body>
+</html>
Added: incubator/shindig/trunk/features/opensocial-templates/template.js
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/features/opensocial-templates/template.js?rev=694034&view=auto
==============================================================================
--- incubator/shindig/trunk/features/opensocial-templates/template.js (added)
+++ incubator/shindig/trunk/features/opensocial-templates/template.js Wed Sep 10 15:21:55 2008
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+/**
+ * @fileoverview Provides the Template class used to represent a single
+ * compiled template that can be rendered into any DOM node.
+ */
+
+/**
+ * A renderable compiled Template. A template can contain one or more
+ * compiled nodes pre-processed for JST operation.
+ * @constructor
+ */
+os.Template = function(opt_id) {
+ this.templateRoot_ = document.createElement("span");
+ this.id = opt_id || ('template_' + os.Template.idCounter_++);
+};
+
+/**
+ * A global counter for template IDs.
+ * @type {number}
+ * @private
+ */
+os.Template.idCounter_ = 0;
+
+/**
+ * A Map of registered templates by keyed ID.
+ * @type {Object.<string|os.Template>}
+ * @private
+ */
+os.registeredTemplates_ = {};
+
+/**
+ * Registers a compiled template by its ID.
+ * @param {os.Template} template List of template nodes.
+ */
+os.registerTemplate = function(template) {
+ os.registeredTemplates_[template.id] = template;
+};
+
+/**
+ * De-registers a compiled template..
+ * @param {os.Template} template List of template nodes.
+ */
+os.unRegisterTemplate = function(template) {
+ delete os.registeredTemplates_[template.id];
+};
+
+/**
+ * Gets a registered template by ID.
+ * @param {string} templateId The ID of a registered Template.
+ * @return {os.Template} A Template object.
+ */
+os.getTemplate = function(templateId) {
+ return os.registeredTemplates_[templateId];
+};
+
+/**
+ * Sets a single compiled node into this template.
+ * @param node {Element} A compiled node.
+ */
+os.Template.prototype.setCompiledNode_ = function(node) {
+ os.removeChildren(this.templateRoot_);
+ this.templateRoot_.appendChild(node);
+};
+
+/**
+ * Sets a list of compiled nodes into this template.
+ * @param nodes {Array.Element} An array of compiled nodes.
+ */
+os.Template.prototype.setCompiledNodes_ = function(nodes) {
+ os.removeChildren(this.templateRoot_);
+ for (var i = 0; i < nodes.length; i++) {
+ this.templateRoot_.appendChild(nodes[i]);
+ }
+};
+
+/**
+ * Renders the template and returns the result.
+ * Does not fire callbacks.
+ */
+os.Template.prototype.render = function(opt_data, opt_context) {
+ if (!opt_context) {
+ opt_context = os.createContext(opt_data);
+ }
+ return os.renderTemplateNode_(this.templateRoot_, opt_context);
+};
+
+/**
+ * Renders the template and puts the result into the specified element, then
+ * fires callbacks.
+ */
+os.Template.prototype.renderInto = function(root, opt_data, opt_context) {
+ if (!opt_context) {
+ opt_context = os.createContext(opt_data);
+ }
+ var result = this.render(opt_data, opt_context);
+ os.removeChildren(root);
+ os.appendChildren(result, root);
+ os.fireCallbacks(opt_context);
+};
Added: incubator/shindig/trunk/features/opensocial-templates/template_test.js
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/features/opensocial-templates/template_test.js?rev=694034&view=auto
==============================================================================
--- incubator/shindig/trunk/features/opensocial-templates/template_test.js (added)
+++ incubator/shindig/trunk/features/opensocial-templates/template_test.js Wed Sep 10 15:21:55 2008
@@ -0,0 +1,740 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+/**
+ * Helper functions.
+ * @param {string} templateId The id of template definition node.
+ * @param {Object} opt_context The context data.
+ * @return {Element} The rendered HTML element.
+ * @private
+ */
+function compileAndRender_(templateId, opt_context) {
+ var template = os.compileTemplate(document.getElementById(templateId));
+
+ // Process the template and output the result
+ var outputNode = document.createElement("div");
+ template.renderInto(outputNode, opt_context);
+ return outputNode;
+}
+
+/**
+ * Takes a string representing a the markup of single DOM node, and returns
+ * the corresponding DOM node.
+ *
+ * @param {string} markup Markup text of the DOM node
+ * @return {Node} The DOM node
+ */
+function markupToNode(markup) {
+ var node = document.createElement('div');
+ node.innerHTML = markup;
+ return node.firstChild;
+}
+
+/**
+ * Finds if an Attr object is real (specified and not inserted by JST).
+ * The JST attributes start with 'js', but IE also has a special '__jstcache'
+ * attribute.
+ * @param {Node} attr The Attr node object.
+ */
+function isRealAttribute(attr) {
+ return attr.specified &&
+ attr.name.indexOf('js') != 0 &&
+ attr.name != '__jstcache';
+};
+
+/**
+ * Normalize a node to the corresponding markup text, ignoring
+ * JsTemplate artifacts:
+ * - Removes attributes starting with "js"
+ * - Removes the SPAN tag if it has a "customtag" attribute or has no attributes
+ * that don't start with "js". This leaves the contents of the tag
+ * - Removes nodes with style="display: none;", which is what JsTemplate does
+ * to nodes that aren't officially output.
+ *
+ * @param {Node} node The DOM node
+ * @return {string} The normalized markup
+ */
+function nodeToNormalizedMarkup(node) {
+ if (node.nodeType == 3) {
+ return node.nodeValue;
+ } else if (node.nodeType == 1) {
+ var hasRealAttributes = false;
+
+ for (var i = 0; i < node.attributes.length; i++) {
+ if (isRealAttribute(node.attributes[i])) {
+ hasRealAttributes = true;
+ }
+ }
+
+ if (node.getAttribute('customtag') != null) {
+ hasRealAttributes = false;
+ }
+
+ if (node.nodeName == 'SPAN' && !hasRealAttributes) {
+ var text = '';
+ for (var i = 0; i < node.childNodes.length; i++) {
+ text += nodeToNormalizedMarkup(node.childNodes[i]);
+ }
+ return text;
+ }
+ if (node.style.display == 'none') {
+ return '';
+ }
+
+ var text = '<' + node.nodeName;
+ for (var i = 0; i < node.attributes.length; i++) {
+ var att = node.attributes[i];
+ if (isRealAttribute(att)) {
+ text += ' ' + att.name + '="' + att.value + '"';
+ }
+ }
+ text += '>';
+ for (var i = 0; i < node.childNodes.length; i++) {
+ text += nodeToNormalizedMarkup(node.childNodes[i]);
+ }
+ text += '</' + node.nodeName + '>';
+ return text;
+ }
+ return '';
+}
+
+/**
+ * Normalizes a node or text string to normalized text of the DOM node
+ *
+ * @param {Node|string} nodeOrText The DOM node or text of the DOM node
+ * @return {string} The normalized markup text
+ *
+ */
+function normalizeNodeOrMarkup(nodeOrText) {
+ var node = typeof nodeOrText == 'string'
+ ? markupToNode(nodeOrText) : nodeOrText;
+ return nodeToNormalizedMarkup(node);
+}
+
+
+/*
+ * Checks if two DOM node are equal, ingoring template artefacts.
+ *
+ * @param {Node|string} lhs First DOM node or string of markup contents
+ * @param {Node|string} rhs Second DOM node or string of markup contents
+ */
+function assertTemplateDomEquals(lhs, rhs) {
+ lhs = normalizeNodeOrMarkup(lhs);
+ rhs = normalizeNodeOrMarkup(rhs);
+
+ assertEquals('DOM nodes not equal: \n_ ' + lhs + '\n_ ' + rhs, lhs, rhs);
+}
+
+
+/**
+ * Allow testing of templates passed in a strings. Allows for calling
+ * a template, passing in a map of named templates, and passing in the data
+ * context.
+ *
+ * @param {string} templateText The text of the inline template to evaluate
+ * @param {string} output The expected output
+ * @param {Object?} context The data context
+ * @param {Array<String>?} namedTemplates Array of text of namedTemplates
+ */
+function assertTemplateOutput(templateText, output, context,
+ namedTemplates) {
+
+ // Parse and register named templates
+ if (namedTemplates instanceof Array) {
+ for (var i = 0; i < namedTemplates.length; i++) {
+ var text = '<Templates xmlns:os="uri:unused">' + namedTemplates[i] +
+ '</Templates>';
+ var dom = os.parseXML_(text);
+ os.Loader.processTemplatesNode(dom);
+ }
+ }
+
+ var template = os.compileTemplateString(templateText);
+
+ // Process the template and output the result
+ var outputNode = document.createElement("div");
+ template.renderInto(outputNode, context);
+ assertTemplateDomEquals(output, outputNode.firstChild);
+}
+
+/**
+ * Tests Namespace.
+ */
+function testNamespace() {
+ // Create the "custom" namespace
+ var custom = os.createNamespace("custom", "http://google.com/#custom");
+ assertEquals(custom, os.getNamespace("custom"));
+
+ var custom_sameUrl =
+ os.createNamespace("custom", "http://google.com/#custom");
+ assertEquals(custom_sameUrl, os.getNamespace("custom"));
+
+ try {
+ var custom_newUrl =
+ os.createNamespace("custom", "http://google.com/#custom_new");
+ fail("no exception thrown with new URL for the same namespace");
+ }
+ catch (e) {
+ // We expect os to throw an exception for namespace conflict.
+ // But if e is a JsUnitException (thrown from fail), throw it again.
+ if (e.isJsUnitException) {
+ throw e;
+ }
+ }
+}
+
+/**
+ * Tests Substitution.
+ */
+function testSubstitution_text() {
+ var data = {
+ title: "count",
+ value: 0
+ };
+ var outputNode = compileAndRender_("_T_Substitution_text", data);
+
+ assertEquals(data.title + ":" + data.value,
+ domutil.getVisibleText(outputNode));
+}
+
+function testSubstitution_attribute() {
+ var data = {
+ id: "varInAttr",
+ color: "red",
+ A1: 111,
+ text: "click me"
+ };
+ var outputNode = compileAndRender_("_T_Substitution_attribute", data);
+ var contentNode = outputNode.firstChild;
+
+ assertEquals(data.id, contentNode.id);
+ assertEquals(data.color, contentNode.style.color);
+ assertEquals("value " + data.A1, contentNode.getAttribute("a1"));
+ assertEquals(data.text, contentNode.innerHTML);
+}
+
+function testSubstitution_nested() {
+ var data = {
+ title: "Users",
+ users: [
+ { title: "President", color: 'red',
+ user: { name: "Bob", id: "101", url: "http://www.bob.com" }},
+ { title: "Manager", color: 'green',
+ user: { name: "Rob", id: "102", url: "http://www.rob.com" }},
+ { title: "Peon", color: 'blue',
+ user: { name: "Jeb", id: "103", url: "http://www.jeb.com" }}
+ ]
+ };
+
+ os.createNamespace("my", "www.google.com/#my");
+ os.Container.registerTag('my:user');
+ os.Container.registerTag('my:record');
+
+ var outputNode = compileAndRender_("_T_Substitution_nested", data);
+
+ assertEquals(data.users.length, outputNode.childNodes.length);
+ for (var i = 0; i < data.users.length; i++) {
+ var user = data.users[i];
+ var divNode = outputNode.childNodes[i];
+ assertEquals("DIV", divNode.tagName);
+
+ // Find first Element child. FF creates an empty #text node, IE does not,
+ // so we need to look.
+ var spanNode = divNode.firstChild;
+ while (!spanNode.tagName) {
+ spanNode = spanNode.nextSibling;
+ }
+
+ assertEquals(user.color, spanNode.color);
+ assertEquals(user.user.id, spanNode.foo);
+
+ var recordNode = spanNode.childNodes[0];
+ assertEquals(user.color, recordNode.firstChild.style.color);
+ assertEquals(user.title, recordNode.firstChild.innerHTML);
+
+ var userNode = recordNode.lastChild;
+ assertEquals(user.user.id, userNode.foo);
+ var anchorNode = userNode.firstChild.childNodes[0];
+ assertEquals(user.user.name, anchorNode.innerHTML);
+ assertContains(user.user.url, anchorNode.href);
+
+ var fooNode = userNode.firstChild.childNodes[2];
+ assertEquals(user.user.id, fooNode.innerHTML);
+ }
+}
+
+/**
+ * Tests if attribute.
+ */
+function testConditional_Number() {
+ var outputNode = compileAndRender_("_T_Conditional_Number");
+
+ assertEquals("TRUE", domutil.getVisibleText(outputNode));
+}
+
+function testConditional_String() {
+ var outputNode = compileAndRender_("_T_Conditional_String");
+
+ assertEquals("TRUE", domutil.getVisibleText(outputNode));
+}
+
+function testConditional_Mixed() {
+ var outputNode = compileAndRender_("_T_Conditional_Mixed");
+
+ assertEquals("TRUE", domutil.getVisibleText(outputNode));
+}
+
+/**
+ * Tests repeat attribute.
+ */
+function testRepeat() {
+ var data = {
+ entries : [
+ { data: "This" },
+ { data: "is" },
+ { data: "an" },
+ { data: "array" },
+ { data: "of" },
+ { data: "data." }
+ ]
+ };
+ var outputNode = compileAndRender_("_T_Repeat", data);
+
+ assertEquals(data.entries.length, outputNode.childNodes.length);
+ for (var i = 0; i < data.entries.length; i++) {
+ var entry = data.entries[i];
+ assertEquals("DIV", outputNode.childNodes[i].tagName);
+ assertEquals(entry.data,
+ domutil.getVisibleTextTrim(outputNode.childNodes[i]));
+ }
+}
+
+/**
+ * Tests select elements.
+ */
+function testSelect() {
+ var data = {
+ options : [
+ { value: "one" },
+ { value: "two" },
+ { value: "three" }
+ ]
+ };
+ var outputNode = compileAndRender_("_T_Options", data);
+ var selectNode = outputNode.firstChild;
+
+ assertEquals(data.options.length, selectNode.options.length);
+ for (var i = 0; i < data.options.length; i++) {
+ var entry = data.options[i];
+ var optionNode = selectNode.options[i];
+ assertEquals("OPTION", optionNode.tagName);
+ assertEquals(entry.value, optionNode.getAttribute('value'));
+ }
+}
+
+function testList() {
+ os.Container.registerTag('custom:list');
+ var output = compileAndRender_('_T_List');
+ assertEquals('helloworld', domutil.getVisibleText(output));
+}
+
+/**
+ * Tests JS custom tags.
+ */
+function testTag_input() {
+ var custom = os.createNamespace("custom", "http://google.com/#custom");
+ /**
+ * Custom controller that uses the value of any input fields as a key to
+ * replace itself with in the context data.
+ */
+ custom.input = function(node, context) { // return HTML;
+ var inputNode = document.createElement('input');
+
+ // Use the "value" attribute from the tag to index the context data
+ inputNode.value = context[node.getAttribute('value')];
+ return inputNode;
+ };
+
+ var data = {
+ data: "Some default data"
+ };
+ var outputNode = compileAndRender_("_T_Tag_input", data);
+ // contentNode is wrapped by <div><span>
+ var contentNode = outputNode.firstChild.firstChild;
+ assertEquals(contentNode.value, data.data);
+}
+
+function testTag_blink() {
+ var custom = os.createNamespace("custom", "http://google.com/#custom");
+ /**
+ * A controller that reproduces blink behavior.
+ */
+ custom.blink = function(node, context) { // returns HTML
+ var root = document.createElement("span");
+ root.appendChild(node.firstChild);
+ function blink() {
+ var isVisible = root.style["visibility"] == "visible";
+ root.style["visibility"] = isVisible ? "hidden" : "visible";
+ setTimeout(blink, 500);
+ }
+ root.onAttach = function() {
+ blink();
+ }
+ return root;
+ }
+
+ Clock.reset();
+ var outputNode = compileAndRender_("_T_Tag_blink");
+ // contentNode is wrapped by <div><span>
+ var contentNode = outputNode.firstChild.firstChild;
+ assertEquals(contentNode.style.visibility, "visible");
+ Clock.tick(500);
+ assertEquals(contentNode.style.visibility, "hidden");
+ Clock.tick(500);
+ assertEquals(contentNode.style.visibility, "visible");
+}
+
+function testHelloWorld() {
+ assertTemplateOutput(
+ '<div>Hello world!</div>',
+ '<div>Hello world!</div>');
+}
+
+function testSimpleExpression() {
+ assertTemplateOutput(
+ '<div>${HelloWorld}</div>',
+ '<div>Hello world!</div>',
+ {HelloWorld: 'Hello world!'});
+}
+
+function testNamedTemplate() {
+ assertTemplateOutput(
+ '<div><os:HelloWorld/></div>',
+ '<div>Hello world!</div>',
+ null,
+ ['<Template tag="os:HelloWorld">Hello world!</Template>']);
+}
+
+function testParameter() {
+ var tryTemplateContent = function(content) {
+ assertTemplateOutput(
+ '<div><os:HelloWorldWithParam text="Hello world!"/></div>',
+ '<div>Hello world!</div>',
+ null,
+ ['<Template tag="os:HelloWorldWithParam">' + content + '</Template>']);
+ }
+
+ tryTemplateContent('${$my.text}');
+ tryTemplateContent('${My.text}');
+ tryTemplateContent('${my.text}');
+
+ // Not working yet:
+ /*
+ tryTemplateContent('${text}');
+ */
+}
+
+function testContent() {
+ var tryTemplateContent = function(content) {
+ assertTemplateOutput(
+ '<div><os:HelloWorldWithContent>Hello world!' +
+ '</os:HelloWorldWithContent></div>',
+ '<div>Hello world!</div>',
+ null,
+ ['<Template tag="os:HelloWorldWithContent">' + content + '</Template>']);
+ }
+
+ tryTemplateContent('<os:renderAll/>');
+ tryTemplateContent('<os:RenderAll/>');
+}
+
+function testNamedContent() {
+ var tryTemplateContent = function(content) {
+ assertTemplateOutput(
+ '<div>' +
+ '<os:HelloWorldWithNamedContent>' +
+ '<os:DontShowThis>Don\'t show this</os:DontShowThis>' +
+ '<os:Content>Hello <b>world!</b></os:Content>' +
+ '<Content>Hello <b>world!</b></Content>' +
+ '</os:HelloWorldWithNamedContent>' +
+ '</div>',
+
+ '<div>Hello <b>world!</b></div>',
+ null,
+ ['<Template tag="os:HelloWorldWithNamedContent">' + content + '</Template>']);
+ }
+ tryTemplateContent('<os:renderAll content="os:Content"/>');
+ tryTemplateContent('<os:renderAll content="Content"/>');
+
+ // Not working yet:
+ /*
+ tryTemplateContent('<os:renderAll content="${my.os:Content}"/>');
+ tryTemplateContent('<os:renderAll content="my.os:Content"/>');
+ tryTemplateContent('<os:renderAll content="${My.os:Content}"/>');
+ tryTemplateContent('<os:renderAll content="${my.Content}"/>');
+ tryTemplateContent('<os:renderAll content="my.Content"/>');
+ tryTemplateContent('<os:renderAll content="${My.Content}"/>');
+ tryTemplateContent('<os:renderAll content="${Content}"/>');
+ */
+}
+
+function testRepeatedContent() {
+ var tryTemplateContent = function(content) {
+ assertTemplateOutput(
+ '<os:HelloWorldRepeatedContent>' +
+ '<Word>Hello</Word>' +
+ '<Word>world!</Word>' +
+ '<os:Word>Hello</os:Word>' +
+ '<os:Word>world!</os:Word>' +
+ '</os:HelloWorldRepeatedContent>',
+
+ '<div>Helloworld!</div>',
+ null,
+ ['<Template tag="os:HelloWorldRepeatedContent">' + content + '</Template>']);
+ };
+
+ tryTemplateContent('<div><span repeat="$my.Word"><os:renderAll/></span></div>');
+ tryTemplateContent('<div><span repeat="${$my.Word}"><os:renderAll/></span></div>');
+ tryTemplateContent('<div><span repeat="${$my.os:Word}"><os:renderAll/></span></div>');
+ tryTemplateContent('<div><span repeat="$my.os:Word"><os:renderAll/></span></div>');
+ tryTemplateContent('<div><span repeat="${my.os:Word}"><os:renderAll/></span></div>');
+ tryTemplateContent('<div><span repeat="$my.os:Word"><os:renderAll/></span></div>');
+ tryTemplateContent('<div><span repeat="${My.os:Word}"><os:renderAll/></span></div>');
+
+ // Not working yet because $my must be explicit:
+ /*
+ tryTemplateContent('<div><span repeat="${Word}"><os:renderAll/></span></div>');
+ tryTemplateContent('<div><span repeat="${os:Word}"><os:renderAll/></span></div>');
+ tryTemplateContent('<div><span repeat="Word"><os:renderAll/></span></div>');
+ tryTemplateContent('<div><span repeat="os:Word"><os:renderAll/></span></div>');
+ */
+};
+
+/**
+ * Bug when calling a repeat twice - 2nd time fails
+ *
+ * This is because <os:renderAll> moves the child tags out to destination, so
+ * the second loop is empty.
+ */
+function testRepeatedContentTwice() {
+ /*
+ assertTemplateOutput(
+ '<os:HelloWorldRepeatedContent>' +
+ '<os:Word>Hello</os:Word>' +
+ '<os:Word>world!</os:Word>' +
+ '</os:HelloWorldRepeatedContent>',
+
+ '<div><div>Helloworld!</div><div>Helloworld!</div></div>',
+ null,
+ ['<Template tag="os:HelloWorldRepeatedContent">
+ '<div>' +
+ '<div><span repeat="$my.os:Word"><os:renderAll/></span></div>' +
+ '<div><span repeat="$my.os:Word"><os:renderAll/></span></div>' +
+ '</Template>']
+ );
+ */
+};
+
+/**
+ * Currently, expression inside "content" attribute is equiv of no attribute.
+ * Probably should just include no data.
+ *
+ * I.e.
+ * <os:renderAll content="${os:NoMatch}"/> currently has same output as
+ * <os:renderAll/>
+ */
+function testRenderAllBadExprInContent() {
+ /*
+ assertTemplateOutput(
+ '<div>' +
+ '<os:HelloWorldBadExpr>' +
+ '<os:Content>Hello world!</os:Content>' +
+ '</os:HelloWorldBadExpr>' +
+ '</div>',
+
+ '<div></div>',
+ null,
+ ['<Template tag="os:HelloWorldBadExpr">' +
+ '<os:renderAll content="${os:NoMatch}"/>' +
+ '</Template>']);
+ */
+}
+
+
+function testBooleanTrue() {
+ assertTemplateOutput(
+ '<span if="${BooleanTrue}">Hello world!</span>',
+ '<span>Hello world!</span>',
+ {BooleanTrue: true});
+
+ assertTemplateOutput(
+ '<span if="BooleanTrue">Hello world!</span>',
+ '<span>Hello world!</span>',
+ {BooleanTrue: true});
+
+ assertTemplateOutput(
+ '<span if="!BooleanTrue">Hello world!</span>',
+ '<span></span>',
+ {BooleanTrue: true});
+
+ assertTemplateOutput(
+ '<span if="${!BooleanTrue}">Hello world!</span>',
+ '<span></span>',
+ {BooleanTrue: true});
+}
+
+
+function testBooleanFalse() {
+ assertTemplateOutput(
+ '<span if="BooleanFalse">Hello world!</span>',
+ '<span></span>',
+ {BooleanFalse: false});
+
+ assertTemplateOutput(
+ '<span if="!BooleanFalse">Hello world!</span>',
+ '<span>Hello world!</span>',
+ {BooleanFalse: false});
+
+ assertTemplateOutput(
+ '<span if="${!BooleanFalse}">Hello world!</span>',
+ '<span>Hello world!</span>',
+ {BooleanFalse: false});
+
+ assertTemplateOutput(
+ '<span if="${BooleanFalse}">Hello world!</span>',
+ '<span></span>',
+ {BooleanFalse: false});
+}
+
+
+function testRepeatedNode() {
+ var tryTemplateContent = function(content) {
+ assertTemplateOutput(
+ content,
+ '<div>Helloworld!</div>',
+ {
+ Words: ['Hello', 'world!'],
+ WordObjects: [{value: 'Hello'}, {value: 'world!'}]
+ });
+ }
+
+ tryTemplateContent('<div><span repeat="WordObjects">${$cur.value}</span></div>');
+ tryTemplateContent('<div><span repeat="WordObjects">${value}</span></div>');
+ tryTemplateContent('<div><span repeat="WordObjects">${cur.value}</span></div>');
+ tryTemplateContent('<div><span repeat="Words">${cur}</span></div>');
+ tryTemplateContent('<div><span repeat="Words">${$cur}</span></div>');
+ tryTemplateContent('<div><span repeat="Words">${Cur}</span></div>');
+
+ // Do we want to continue to support this?
+ tryTemplateContent('<div><span repeat="Words">${$this}</span></div>');
+};
+
+function testDynamicRepeatedContent() {
+
+ assertTemplateOutput(
+ '<os:DynamicRepeat>' +
+ '<Word repeat="WordObjects">${$cur.value}</Word>' +
+ '</os:DynamicRepeat>',
+ '<div>Helloworld!</div>',
+ {WordObjects: [{value: 'Hello'}, {value: 'world!'}]},
+
+ ['<Template tag="os:DynamicRepeat">' +
+ '<div><span repeat="$my.Word"><os:renderAll/></span></div>' +
+ '</Template>']);
+
+};
+
+function testReplaceTopLevelVars() {
+ function test(src, dest) {
+ assertEquals(dest, os.replaceTopLevelVars_(src));
+ }
+
+ // Basic substitution for each replacement
+ test('my.man', '$my.man');
+ test('my', '$my');
+ test('My.man', '$my.man');
+ test('My', '$my');
+ test('cur.man', '$this.man');
+ test('cur', '$this');
+ test('Cur.man', '$this.man');
+ test('Cur', '$this');
+
+ // Basic no sustitution
+ test('$my.man', '$my.man');
+ test('$my', '$my');
+ test('ns.My', 'ns.My');
+ test('Cur/2', '$this/2');
+ test('Cur*2', '$this*2');
+ test('Cur[My.name]', '$this[$my.name]');
+ test('Cur||\'Nothing\'', '$this||\'Nothing\'');
+
+ // Single operator, both fist and last expression
+ test('My.man+your.man', '$my.man+your.man');
+ test('your.man>My.man', 'your.man>$my.man');
+
+ // Tests a specific operator
+ function testOperator(operator) {
+ test('My.man' + operator + 'your.man',
+ '$my.man' + operator + 'your.man');
+
+ test('your.man' + operator + 'My.man',
+ 'your.man' + operator + '$my.man');
+
+ test('My' + operator + 'My',
+ '$my' + operator + '$my');
+ }
+
+ // All operators
+ testOperator('+');
+ testOperator(' + ');
+ testOperator('-');
+ testOperator('<');
+ testOperator(' lt ');
+ testOperator('>');
+ testOperator(' gt ');
+ testOperator('=');
+ testOperator('!=');
+ testOperator('==');
+ testOperator('&&');
+ testOperator(' and ');
+ testOperator('||');
+ testOperator(' or ');
+ testOperator(' and !');
+ testOperator('/');
+ testOperator('*');
+ testOperator('|');
+ testOperator('(');
+ testOperator('[');
+};
+
+function testHtmlTag() {
+ var template = os.compileTemplateString('<os:Html code="${foo}"/>');
+ var output = template.render({foo: 'Hello <b>world</b>!'});
+ var boldNodes = output.getElementsByTagName("b");
+ assertEquals(1, boldNodes.length);
+};
+
+function testOnAttachAttribute() {
+ /* fails on IE.
+ var template = os.compileTemplateString(
+ '<div onAttach="this.title=\'bar\'"/>');
+ var output = document.createElement('div');
+ template.renderInto(output);
+ assertEquals('bar', output.firstChild.title);
+ */
+};
Added: incubator/shindig/trunk/features/opensocial-templates/util.js
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/features/opensocial-templates/util.js?rev=694034&view=auto
==============================================================================
--- incubator/shindig/trunk/features/opensocial-templates/util.js (added)
+++ incubator/shindig/trunk/features/opensocial-templates/util.js Wed Sep 10 15:21:55 2008
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+/**
+ * @fileoverview Provides various utility functions used throughout the library.
+ */
+
+/**
+ * Checks whether or not a given character is alpha-numeric.
+ * @param {string} ch Character to check.
+ * @return {boolean} This character is alpha-numeric.
+ */
+os.isAlphaNum = function(ch) {
+ return ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') ||
+ (ch >= '0' && ch <= '9') || ch == '_');
+};
+
+/**
+ * Clears the children of a given DOM node.
+ * @param {Node} node DOM node to clear.
+ */
+os.removeChildren = function(node) {
+ while (node.firstChild) {
+ node.removeChild(node.firstChild);
+ }
+};
+
+/**
+ * Clears the children of a given DOM node.
+ * @param {Node} sourceNode DOM node with children to append.
+ * @param {Node} targetNode DOM node to append sourceNode's children to.
+ */
+os.appendChildren = function(sourceNode, targetNode) {
+ while (sourceNode.firstChild) {
+ targetNode.appendChild(sourceNode.firstChild);
+ }
+};
+
+/**
+ * Replaces a specified DOM node with other node(s).
+ * @param {Element} node The node to be replaced
+ * @param {Element|Array.<Element>} replacement Can be a node or an array of
+ * nodes. Elements can be TextNodes.
+ */
+os.replaceNode = function(node, replacement) {
+ var parent = node.parentNode;
+ if (!parent) {
+ throw 'Error in replaceNode() - Node has no parent: ' + node;
+ }
+ if (replacement.nodeType == DOM_ELEMENT_NODE ||
+ replacement.nodeType == DOM_TEXT_NODE) {
+ parent.replaceChild(replacement, node);
+ } else if (isArray(replacement)) {
+ for (var i = 0; i < replacement.length; i++) {
+ parent.insertBefore(replacement[i], node);
+ }
+ parent.removeChild(node);
+ }
+};
+
+/**
+ * Given a property name (e.g. 'foo') will create a JavaBean-style getter
+ * (e.g. 'getFoo').
+ * @param {string} propertyName Name of the property.
+ * @return {string} The name of the getter function.
+ */
+os.getPropertyGetterName = function(propertyName) {
+ var getter = 'get' + propertyName.charAt(0).toUpperCase() +
+ propertyName.substring(1);
+ return getter;
+};
+
+
+/**
+ * Given a constant-style string (e.g., 'FOO_BAR'), will return a camel-cased
+ * string (e.g., fooBar).
+ * @param {string} str String to convert to camelCase.
+ * @return {string} The camel-cased string.
+ */
+os.convertToCamelCase = function(str) {
+ var words = str.toLowerCase().split('_');
+ var out = [];
+ out.push(words[0].toLowerCase());
+ for (var i = 1; i < words.length; ++i) {
+ var piece = words[i].charAt(0).toUpperCase() + words[i].substring(1);
+ out.push(piece);
+ }
+ return out.join('');
+};
Added: incubator/shindig/trunk/features/opensocial-templates/util_test.js
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/features/opensocial-templates/util_test.js?rev=694034&view=auto
==============================================================================
--- incubator/shindig/trunk/features/opensocial-templates/util_test.js (added)
+++ incubator/shindig/trunk/features/opensocial-templates/util_test.js Wed Sep 10 15:21:55 2008
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+/**
+ * Unit test for various DOM utils.
+ */
+function testDomUtils() {
+ var sourceNode = document.getElementById('domSource');
+ var targetNode = document.getElementById('domTarget');
+ var html = sourceNode.innerHTML;
+ targetNode.innerHTML = '';
+
+ // test appendChildren
+ os.appendChildren(sourceNode, targetNode);
+ assertEquals(html, targetNode.innerHTML);
+
+ // test removeChildren
+ os.removeChildren(targetNode);
+ assertEquals(0, targetNode.childNodes.length);
+
+ // test replaceNode
+ var child = document.createElement('p');
+ sourceNode.appendChild(child);
+ os.replaceNode(child, document.createElement('div'));
+ assertEquals('DIV', sourceNode.firstChild.tagName);
+}
+
+/**
+ * Unit test for createPropertyGetter
+ */
+function testGetPropertyGetterName() {
+ assertEquals('getFoo', os.getPropertyGetterName('foo'));
+ assertEquals('getFooBar', os.getPropertyGetterName('fooBar'));
+}
+
+/**
+ * Unit test for convertConstantToCamelCase.
+ */
+function testConvertToCamelCase() {
+ assertEquals('foo', os.convertToCamelCase('FOO'));
+ assertEquals('fooBar', os.convertToCamelCase('FOO_BAR'));
+ assertEquals('fooBarBaz', os.convertToCamelCase('FOO_BAR__BAZ'));
+}