Added: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/SubstitutionsTest.java URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/SubstitutionsTest.java?rev=603539&view=auto ============================================================================== --- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/SubstitutionsTest.java (added) +++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/SubstitutionsTest.java Wed Dec 12 02:33:35 2007 @@ -0,0 +1,47 @@ +package org.apache.shindig.gadgets; + +import org.apache.shindig.gadgets.Substitutions.Type; + +import junit.framework.TestCase; + +public class SubstitutionsTest extends TestCase { + private Substitutions subst = new Substitutions(); + + public void testMessages() throws Exception { + String msg = "Hello, __MSG_world__!"; + subst.addSubstitution(Type.MESSAGE, "world", "planet"); + assertEquals("Hello, planet!", subst.substitute(msg)); + } + + public void testBidi() throws Exception { + String msg = "Hello, __BIDI_DIR__-world!"; + subst.addSubstitution(Type.BIDI, "DIR", "rtl"); + assertEquals("Hello, rtl-world!", subst.substitute(msg)); + } + + public void testUserPref() throws Exception { + String msg = "__UP_hello__, world!"; + subst.addSubstitution(Type.USER_PREF, "hello", "Greetings"); + assertEquals("Greetings, world!", subst.substitute(msg)); + } + + public void testCorrectOrder() throws Exception { + String msg = "__UP_hello__, __MSG_world__!"; + subst.addSubstitution(Type.MESSAGE, "world", "planet __BIDI_DIR__-__UP_planet__"); + subst.addSubstitution(Type.BIDI, "DIR", "rtl"); + subst.addSubstitution(Type.USER_PREF, "hello", "Greetings"); + subst.addSubstitution(Type.USER_PREF, "planet", "Earth"); + assertEquals("Greetings, planet rtl-Earth!", subst.substitute(msg)); + } + + public void testIncorrectOrder() throws Exception { + String msg = "__UP_hello__, __MSG_world__"; + subst.addSubstitution(Type.MESSAGE, "world", "planet __MSG_earth____UP_punc__"); + subst.addSubstitution(Type.MESSAGE, "earth", "Earth"); + subst.addSubstitution(Type.USER_PREF, "punc", "???"); + subst.addSubstitution(Type.USER_PREF, "hello", "Greetings __MSG_foo____UP_bar__"); + subst.addSubstitution(Type.MESSAGE, "foo", "FOO!!!"); + subst.addSubstitution(Type.USER_PREF, "bar", "BAR!!!"); + assertEquals("Greetings __MSG_foo____UP_bar__, planet __MSG_earth__???", subst.substitute(msg)); + } +}
Added: incubator/shindig/trunk/javascript/README URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/javascript/README?rev=603539&view=auto ============================================================================== --- incubator/shindig/trunk/javascript/README (added) +++ incubator/shindig/trunk/javascript/README Wed Dec 12 02:33:35 2007 @@ -0,0 +1,58 @@ +Using Shindig Gadget Container JavaScript +========================================= + +1) Try out the samples. + These provide examples of Gadget Container using gmodules.com for rendering. + Point your web browser at the following, substituting your Shindig code directory + for <shindig-dir>: + * file:///shindig-dir/javascript/container/sample1.html + * file:///shindig-dir/javascript/container/sample3.html + + Samples #2 and #4 need to be run in the context of a webserver for cookie and + container-gadget communication support. Start up your favorite browser and point + it at the .../shindig/javascript/container directory (here abbreviated <shindig-js-dir>): + * http://yourserver:yourport/shindig-js-dir/sample2.html + * http://yourserver:yourport/shindig-js-dir/sample4.html + +2) Play around with the code. + + A) Create an HTML file including the following <head> boilerplate: + <script type="text/javascript" src="json.js"></script> + <script type="text/javascript" src="http://www.google.com/ig/ifpc.js"></script> + <script type="text/javascript" src="cookies.js"></script> + <script type="text/javascript" src="gadgets.js"></script> + + B) For each Gadget you wish to add to the page: + i) Create it. Example, for Gadget whose spec is at http://foo.com/spec.xml + var gadget = gadgets.container.createGadget({ specUrl: "http://foo.com/spec.xml" }); + + ii) Add it to the container. Example: + gadgets.container.addGadget(gadget); + + iii) Ensure the Gadget's chrome ID is defined. This is the ID of the elements + in which the Gadget is rendered. The way these are specified differs + depending on the LayoutManager being used. Example with default LayoutManager: + gadgets.container.layoutManager.setGadgetChromeIds([ 'gadget-id-1' ]); + + iv) Render it. The chrome element must exist when this call is performed (ie. + this must occur onLoad of the document.body or in inline script). + gadgets.container.renderGadget(gadget); + + You may also render several added Gadgets at once: + gadgets.container.renderGadgets(); + + C) Explore samples 2, 3, and 4 for examples using different LayoutManagers and + supporting UserPrefs storage. + +3) Try it with your own Gadget Server. + A) Set up your own Shindig Gadget Server. See its README for details. + + B) Assume your server is running on http://yourserver:yourport/gadgets/... + Before step 2.B.iv, call the following to point the Gadget at your server: + gadget.setServerBase('http://yourserver:yourport/gadgets'); + +NOTE: In the short term, when rendering Gadgets using gmodules.com certain + functionality tied to inter-frame communication will not work, + such as SetPrefs and dynamically setting IFRAME height. + +For more information, see http://incubator.apache.org/projects/shindig.html Added: incubator/shindig/trunk/javascript/container/cookies.js URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/javascript/container/cookies.js?rev=603539&view=auto ============================================================================== --- incubator/shindig/trunk/javascript/container/cookies.js (added) +++ incubator/shindig/trunk/javascript/container/cookies.js Wed Dec 12 02:33:35 2007 @@ -0,0 +1,258 @@ +// Copyright 2006 Google Inc. +// All Rights Reserved. + +/** + * @fileoverview Functions for setting, getting and deleting cookies + * + * @author [EMAIL PROTECTED] (Erik Arvidsson) [Based on common.js] + */ + + +/** + * Namespace for cookie functions + */ +// goog.provide('goog.net.cookies'); +// TODO: find the official solution for a cookies library +var goog = {net: {cookies: {}}}; + +goog.JsType_ = { + UNDEFINED: 'undefined' +}; + +goog.isDef = function(val) { + return typeof val != goog.JsType_.UNDEFINED; +}; + + +/** + * Sets a cookie. + * The max_age can be -1 to set a session cookie. To remove and expire cookies, + * use remove() instead. + * + * @param {string} name The cookie name. + * @param {string} value The cookie value. + * @param {number} opt_maxAge The max age in seconds (from now). Use -1 to set + * a session cookie. If not provided, the default is + * -1 (i.e. set a session cookie). + * @param {string} opt_path The path of the cookie, or null to not specify a + * path attribute (browser will use the full request + * path). If not provided, the default is '/' (i.e. + * path=/). + * @param {string} opt_domain The domain of the cookie, or null to not specify + * a domain attribute (browser will use the full + * request host name). If not provided, the default + * is null (i.e. let browser use full request host + * name). + */ +goog.net.cookies.set = function(name, value, opt_maxAge, opt_path, opt_domain) { + // we do not allow '=' or ';' in the name + if (/;=/g.test(name)) { + throw new Error('Invalid cookie name "' + name + '"'); + } + // we do not allow ';' in value + if (/;/g.test(value)) { + throw new Error('Invalid cookie value "' + value + '"'); + } + + if (!goog.isDef(opt_maxAge)) { + opt_maxAge = -1; + } + + var domainStr = opt_domain ? ';domain=' + opt_domain : ''; + var pathStr = opt_path ? ';path=' + opt_path : ''; + + var expiresStr; + + // Case 1: Set a session cookie. + if (opt_maxAge < 0) { + expiresStr = ''; + + // Case 2: Expire the cookie. + // Note: We don't tell people about this option in the function doc because + // we prefer people to use ExpireCookie() to expire cookies. + } else if (opt_maxAge == 0) { + // Note: Don't use Jan 1, 1970 for date because NS 4.76 will try to convert + // it to local time, and if the local time is before Jan 1, 1970, then the + // browser will ignore the Expires attribute altogether. + var pastDate = new Date(1970, 1 /*Feb*/, 1); // Feb 1, 1970 + expiresStr = ';expires=' + pastDate.toUTCString(); + + // Case 3: Set a persistent cookie. + } else { + var futureDate = new Date((new Date).getTime() + opt_maxAge * 1000); + expiresStr = ';expires=' + futureDate.toUTCString(); + } + + document.cookie = name + '=' + value + domainStr + pathStr + expiresStr; +}; + + +/** + * Returns the value for the first cookie with the given name + * @param {string} name The name of the cookie to get + * @param {string} opt_default If not found this is returned instead. + * @return {string|undefined} The value of the cookie. If no cookie is set this + * returns opt_default or undefined if opt_default is + * not provided. + */ +goog.net.cookies.get = function(name, opt_default) { + var nameEq = name + "="; + var cookie = String(document.cookie); + for (var pos = -1; (pos = cookie.indexOf(nameEq, pos + 1)) >= 0;) { + var i = pos; + // walk back along string skipping whitespace and looking for a ; before + // the name to make sure that we don't match cookies whose name contains + // the given name as a suffix. + while (--i >= 0) { + var ch = cookie.charAt(i); + if (ch == ';') { + i = -1; // indicate success + break; + } + } + if (i == -1) { // first cookie in the string or we found a ; + var end = cookie.indexOf(';', pos); + if (end < 0) { + end = cookie.length; + } + return cookie.substring(pos + nameEq.length, end); + } + } + return opt_default; +}; + + +/** + * Removes and expires a cookie. + * + * @param {string} name The cookie name. + * @param {string} opt_path The path of the cookie, or null to expire a cookie + * set at the full request path. If not provided, the + * default is '/' (i.e. path=/). + * @param {string} opt_domain The domain of the cookie, or null to expire a + * cookie set at the full request host name. If not + * provided, the default is null (i.e. cookie at + * full request host name). + */ +goog.net.cookies.remove = function(name, opt_path, opt_domain) { + var rv = goog.net.cookies.containsKey(name); + goog.net.cookies.set(name, '', 0, opt_path, opt_domain); + return rv; +}; + + +/** + * Gets the names and values for all the cookies + * @private + * @return {Object} An object with keys and values + */ +goog.net.cookies.getKeyValues_ = function() { + var cookie = String(document.cookie); + var parts = cookie.split(/\s*;\s*/); + var keys = [], values = [], index, part, pair; + for (var i = 0; part = parts[i]; i++) { + index = part.indexOf('='); + + if (index == -1) { // empty name + keys.push(''); + values.push(part); + } else { + keys.push(part.substring(0, index)); + values.push(part.substring(index + 1)); + } + } + return {keys: keys, values: values}; +}; + + +/** + * Gets the names for all the cookies + * @return {Array} An array with the names of the cookies + */ +goog.net.cookies.getKeys = function() { + return goog.net.cookies.getKeyValues_().keys; +}; + + +/** + * Gets the values for all the cookies + * @return {Array} An array with the values of the cookies + */ +goog.net.cookies.getValues = function() { + return goog.net.cookies.getKeyValues_().values; +}; + + +/** + * Whether there are any cookies for this document + * @return {boolean} + */ +goog.net.cookies.isEmpty = function() { + return document.cookie == ''; +}; + + +/** + * Returns the number of cookies for this document + * @return {number} + */ +goog.net.cookies.getCount = function() { + var cookie = String(document.cookie); + if (cookie == '') { + return 0; + } + var parts = cookie.split(/\s*;\s*/); + return parts.length; +}; + + +/** + * Returns whether there is a cookie with the given name + * @param {string} key The name of the cookie to test for + * @return {boolean} + */ +goog.net.cookies.containsKey = function(key) { + var sentinel = {}; + // if get does not find the key it returns the default value. We therefore + // compare the result with an object to ensure we do not get any false + // positives. + return goog.net.cookies.get(key, sentinel) !== sentinel; +}; + + +/** + * Returns whether there is a cookie with the given value. (This is an O(n) + * operation.) + * @param {string} value The value to check for + * @return {boolean} + */ +goog.net.cookies.containsValue = function(value) { + // this O(n) in any case so lets do the trivial thing. + var values = goog.net.cookies.getKeyValues_().values; + for (var i = 0; i < values.length; i++) { + if (values[i] == value) { + return true; + } + } + return false; +}; + + +/** + * Removes all cookies for this document + */ +goog.net.cookies.clear = function() { + var keys = goog.net.cookies.getKeyValues_().keys; + for (var i = keys.length - 1; i >= 0; i--) { + goog.net.cookies.remove(keys[i]); + } +}; + +/** + * Static constant for the size of cookies. Per the spec, there's a 4K limit + * to the size of a cookie. To make sure users can't break this limit, we + * should truncate long cookies at 3950 bytes, to be extra careful with dumb + * browsers/proxies that interpret 4K as 4000 rather than 4096 + * @type number + */ +goog.net.cookies.MAX_COOKIE_LENGTH = 3950; Added: incubator/shindig/trunk/javascript/container/gadgets.css URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/javascript/container/gadgets.css?rev=603539&view=auto ============================================================================== --- incubator/shindig/trunk/javascript/container/gadgets.css (added) +++ incubator/shindig/trunk/javascript/container/gadgets.css Wed Dec 12 02:33:35 2007 @@ -0,0 +1,42 @@ +.gadgets-gadget-chrome { + float: left; + margin: 4px; + border: 1px solid #7aa5d6; +} + +.gadgets-gadget { + border: none; +} + +.gadgets-gadget-title-bar { + padding: 2px 4px; + background-color: #e5ecf9; +} + +.gadgets-gadget-title { + font-weight: bold; + color: #3366cc; +} + +.gadgets-gadget-title-button-bar { + font-size: smaller; +} + +.gadgets-gadget-user-prefs-dialog { + background-color: #e5ecf9; +} + +.gadgets-gadget-user-prefs-dialog-action-bar { + text-align: center; + padding-bottom: 4px; +} + +.gadgets-gadget-title-button { +} + +.gadgets-gadget-content { + padding: 4px; +} + +.gadgets-log-entry { +} Added: incubator/shindig/trunk/javascript/container/gadgets.js URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/javascript/container/gadgets.js?rev=603539&view=auto ============================================================================== --- incubator/shindig/trunk/javascript/container/gadgets.js (added) +++ incubator/shindig/trunk/javascript/container/gadgets.js Wed Dec 12 02:33:35 2007 @@ -0,0 +1,700 @@ +// Copyright 2007 Google Inc. +// All Rights Reserved. + +/** + * @fileoverview Open Gadget Container + * + * @author [EMAIL PROTECTED] (Jun Yang) + * @author [EMAIL PROTECTED] (Zhen Wang) + */ + +// ----- +// Utils + +Function.prototype.inherits = function(parentCtor) { + function tempCtor() {}; + tempCtor.prototype = parentCtor.prototype; + this.superClass_ = parentCtor.prototype; + this.prototype = new tempCtor(); + this.prototype.constructor = this; +}; + + +// ----------- +// gadgets + +var gadgets = {}; + +gadgets.error = {}; +gadgets.error.SUBCLASS_RESPONSIBILITY = 'subclass responsibility'; +gadgets.error.TO_BE_DONE = 'to be done'; + +gadgets.log = function(message) { + if (window.console && console.log) { + console.log(message); + } else { + var logEntry = document.createElement('div'); + logEntry.className = 'gadgets-log-entry'; + logEntry.innerHTML = message; + document.body.appendChild(logEntry); + } +}; + +/** + * Calls an array of asynchronous functions and calls the continuation + * function when all are done. + * @param {Array} functions Array of asynchronous functions, each taking + * one argument that is the continuation function that handles the result + * That is, each function is something like the following: + * function(continuation) { + * // compute result asynchronously + * continuation(result); + * } + * @param {Function} continuation Function to call when all results are in. It + * is pass an array of all results of all functions + * @param {Object} opt_this Optional object used as "this" when calling each + * function + */ +gadgets.callAsyncAndJoin = function(functions, continuation, opt_this) { + var pending = functions.length; + var results = []; + for (var i = 0; i < functions.length; i++) { + // we need a wrapper here because i changes and we need once index + // variable per closure + var wrapper = function(index) { + functions[index].call(opt_this, function(result) { + results[index] = result; + if (--pending == 0) { + continuation(results); + } + }); + }; + wrapper(i); + } +}; + + +// ---------- +// Extensible + +gadgets.Extensible = function() { +}; + +/** + * Sets the dependencies. + * @param {Object} dependencies Object whose properties are set on this + * container as dependencies + */ +gadgets.Extensible.prototype.setDependencies = function(dependencies) { + for (var p in dependencies) { + this[p] = dependencies[p]; + } +}; + +/** + * Returns a dependency given its name. + * @param {String} name Name of dependency + * @return {Object} Dependency with that name or undefined if not found + */ +gadgets.Extensible.prototype.getDependencies = function(name) { + return this[name]; +}; + + + +// ------------- +// UserPrefStore + +/** + * User preference store interface. + * @constructor + */ +gadgets.UserPrefStore = function() { +}; + +/** + * Gets all user preferences of a gadget. + * @param {Object} gadget Gadget object + * @return {Object} All user preference of given gadget + */ +gadgets.UserPrefStore.prototype.getPrefs = function(gadget) { + throw Error(gadgets.error.SUBCLASS_RESPONSIBILITY); +}; + +/** + * Saves user preferences of a gadget in the store. + * @param {Object} gadget Gadget object + * @param {Object} prefs User preferences + */ +gadgets.UserPrefStore.prototype.savePrefs = function(gadget) { + throw Error(gadgets.error.SUBCLASS_RESPONSIBILITY); +}; + + +// ------------------------ +// CookieBasedUserPrefStore + +/** + * Cookie-based user preference store. + * @constructor + */ +gadgets.CookieBasedUserPrefStore = function() { + gadgets.UserPrefStore.call(this); +}; + +gadgets.CookieBasedUserPrefStore.inherits(gadgets.UserPrefStore); + +gadgets.CookieBasedUserPrefStore.prototype.USER_PREFS_PREFIX = + 'gadgetUserPrefs-'; + +gadgets.CookieBasedUserPrefStore.prototype.getPrefs = function(gadget) { + var userPrefs = {}; + var cookieName = this.USER_PREFS_PREFIX + gadget.id; + var cookie = goog.net.cookies.get(cookieName); + if (cookie) { + var pairs = cookie.split('&'); + for (var i = 0; i < pairs.length; i++) { + var nameValue = pairs[i].split('='); + var name = decodeURIComponent(nameValue[0]); + var value = decodeURIComponent(nameValue[1]); + userPrefs[name] = value; + } + } + + return userPrefs; +}; + +gadgets.CookieBasedUserPrefStore.prototype.savePrefs = function(gadget) { + var pairs = []; + for (var name in gadget.getUserPrefs()) { + var value = gadget.getUserPref(name); + var pair = encodeURIComponent(name) + '=' + encodeURIComponent(value); + pairs.push(pair); + } + + var cookieName = this.USER_PREFS_PREFIX + gadget.id; + var cookieValue = pairs.join('&'); + goog.net.cookies.set(cookieName, cookieValue); +}; + + +// ------------- +// GadgetService + +/** + * Interface of service provided to gadgets for resizing gadgets, + * setting title, etc. + * @constructor + */ +gadgets.GadgetService = function() { +}; + +gadgets.GadgetService.prototype.setHeight = function(elementId, height) { + throw Error(gadgets.error.SUBCLASS_RESPONSIBILITY); +}; + +gadgets.GadgetService.prototype.setTitle = function(gadget, title) { + throw Error(gadgets.error.SUBCLASS_RESPONSIBILITY); +}; + +gadgets.GadgetService.prototype.setUserPref = function(id) { + throw Error(gadgets.error.SUBCLASS_RESPONSIBILITY); +}; + + +// ---------------- +// IfrGadgetService + +/** + * Base implementation of GadgetService. + * @constructor + */ +gadgets.IfrGadgetService = function() { + gadgets.GadgetService.call(this); + + _IFPC.registerService('resize_iframe', this.setHeight); + _IFPC.registerService('set_pref', this.setUserPref); +}; + +gadgets.IfrGadgetService.inherits(gadgets.GadgetService); + +gadgets.IfrGadgetService.prototype.setHeight = function(elementId, height) { + var element = document.getElementById(elementId); + if (element) { + element.style.height = height + 'px'; + } +}; + +gadgets.IfrGadgetService.prototype.setTitle = function(gadget, title) { + throw Error(gadgets.error.TO_BE_DONE); +}; + +/** + * Sets one or more user preferences + * @param {String} gadgetFrameId Frame ID of the gadget that initiates the call + * @param {String} dummy + * @param {String} name Name of user preference + * @param {String} value Value of user preference + * More names and values may follow + */ +gadgets.IfrGadgetService.prototype.setUserPref = function(gadgetFrameId) { + // Quick hack to extract the gadget id from module id + var id = parseInt(gadgetFrameId.match(/_([0-9]+)$/)[1], 10); + var gadget = gadgets.container.getGadget(id); + var prefs = gadget.getUserPrefs(); + for (var i = 2; i < arguments.length; i += 2) { + prefs[arguments[i]] = arguments[i + 1]; + } + gadget.setUserPrefs(prefs); +}; + + +// ------------- +// LayoutManager + +/** + * Layout manager interface. + * @constructor + */ +gadgets.LayoutManager = function() { +}; + +/** + * Gets the HTML element that is the chrome of a gadget into which the cotnent + * of the gadget can be rendered. + * @param {Object} gadget Gadget instance + * @return {Object} HTML element that is the chrome for the given gadget + */ +gadgets.LayoutManager.prototype.getGadgetChrome = function(gadget) { + throw Error(gadgets.error.SUBCLASS_RESPONSIBILITY); +}; + +// ------------------- +// StaticLayoutManager + +/** + * Static layout manager where gadget ids have a 1:1 mapping to chrome ids. + * @constructor + */ +gadgets.StaticLayoutManager = function() { + gadgets.LayoutManager.call(this); +}; + +gadgets.StaticLayoutManager.inherits(gadgets.LayoutManager); + +/** + * Sets chrome ids, whose indexes are gadget instance ids (starting from 0). + * @param {Array} gadgetIdToChromeIdMap Gadget id to chrome id map + */ +gadgets.StaticLayoutManager.prototype.setGadgetChromeIds = + function(gadgetChromeIds) { + this.gadgetChromeIds_ = gadgetChromeIds; +}; + +gadgets.StaticLayoutManager.prototype.getGadgetChrome = function(gadget) { + var chromeId = this.gadgetChromeIds_[gadget.id]; + return chromeId ? document.getElementById(chromeId) : null; +}; + + +// ---------------------- +// FloatLeftLayoutManager + +/** + * FloatLeft layout manager where gadget ids have a 1:1 mapping to chrome ids. + * @constructor + * @param {String} layoutRootId Id of the element that is the parent of all + * gadgets. + */ +gadgets.FloatLeftLayoutManager = function(layoutRootId) { + gadgets.LayoutManager.call(this); + this.layoutRootId_ = layoutRootId; +}; + +gadgets.FloatLeftLayoutManager.inherits(gadgets.LayoutManager); + +gadgets.FloatLeftLayoutManager.prototype.getGadgetChrome = + function(gadget) { + var layoutRoot = document.getElementById(this.layoutRootId_); + if (layoutRoot) { + var chrome = document.createElement('div'); + chrome.className = 'gadgets-gadget-chrome'; + chrome.style.float = 'left' + layoutRoot.appendChild(chrome); + return chrome; + } else { + return null; + } +}; + + +// ------ +// Gadget + +/** + * Creates a new instance of gadget. Optoinal parameters are set as instance + * variables. + * @constructor + * @param {Object} params Parameters to set on gadget. Common parameters: + * "specUrl": URL to gadget specification + * "private": Whether gadget spec is accessible only privately, which means + * browser can load it but not gadget server + * "spec": Gadget Specification in XML + */ +gadgets.Gadget = function(params) { + this.userPrefs_ = {}; + + if (params) { + for (var name in params) { + this[name] = params[name]; + } + } +}; + +gadgets.Gadget.prototype.getUserPrefs = function() { + return this.userPrefs_; +}; + +gadgets.Gadget.prototype.setUserPrefs = function(userPrefs) { + this.userPrefs_ = userPrefs; + gadgets.container.userPrefStore.savePrefs(this); +}; + +gadgets.Gadget.prototype.getUserPref = function(name) { + return this.userPrefs_[name]; +}; + +gadgets.Gadget.prototype.setUserPref = function(name, value) { + this.userPrefs_[name] = value; + gadgets.container.userPrefStore.savePrefs(this); +}; + +gadgets.Gadget.prototype.render = function(chrome) { + if (chrome) { + this.getContent(function(content) { + chrome.innerHTML = content; + }); + } +}; + +gadgets.Gadget.prototype.getContent = function(continuation) { + gadgets.callAsyncAndJoin([ + this.getTitleBarContent, this.getUserPrefsDialogContent, + this.getMainContent], function(results) { + continuation(results.join('')); + }, this); +}; + +/** + * Gets title bar content asynchronously or synchronously. + * @param {Function} continutation Function that handles title bar content as + * the one and only argument + */ +gadgets.Gadget.prototype.getTitleBarContent = function(continuation) { + throw Error(gadgets.error.SUBCLASS_RESPONSIBILITY); +}; + +/** + * Gets user preferences dialog content asynchronously or synchronously. + * @param {Function} continutation Function that handles user preferences + * content as the one and only argument + */ +gadgets.Gadget.prototype.getUserPrefsDialogContent = function(continuation) { + throw Error(gadgets.error.SUBCLASS_RESPONSIBILITY); +}; + +/** + * Gets gadget content asynchronously or synchronously. + * @param {Function} continutation Function that handles gadget content as + * the one and only argument + */ +gadgets.Gadget.prototype.getMainContent = function(continuation) { + throw Error(gadgets.error.SUBCLASS_RESPONSIBILITY); +}; + + +// --------- +// IfrGadget + +gadgets.IfrGadget = function(opt_params) { + gadgets.Gadget.call(this, opt_params); + this.serverBase_ = 'http://www.gmodules.com/ig/'; // default server +}; + +gadgets.IfrGadget.inherits(gadgets.Gadget); + +gadgets.IfrGadget.prototype.GADGET_IFRAME_PREFIX_ = 'remote_iframe_'; + +gadgets.IfrGadget.prototype.SYND = 'gadgets'; + +gadgets.IfrGadget.prototype.cssClassGadget = 'gadgets-gadget'; +gadgets.IfrGadget.prototype.cssClassTitleBar = 'gadgets-gadget-title-bar'; +gadgets.IfrGadget.prototype.cssClassTitle = 'gadgets-gadget-title'; +gadgets.IfrGadget.prototype.cssClassTitleButtonBar = + 'gadgets-gadget-title-button-bar'; +gadgets.IfrGadget.prototype.cssClassGadgetUserPrefsDialog = + 'gadgets-gadget-user-prefs-dialog'; +gadgets.IfrGadget.prototype.cssClassGadgetUserPrefsDialogActionBar = + 'gadgets-gadget-user-prefs-dialog-action-bar'; +gadgets.IfrGadget.prototype.cssClassTitleButton = 'gadgets-gadget-title-button'; +gadgets.IfrGadget.prototype.cssClassGadgetContent = 'gadgets-gadget-content'; + +gadgets.IfrGadget.prototype.getTitleBarContent = function(continuation) { + continuation('<div class="' + this.cssClassTitleBar + '"><span class="' + + this.cssClassTitle + '">Title</span> | <span class="' + + this.cssClassTitleButtonBar + + '"><a href="#" onclick="gadgets.container.getGadget(' + this.id + + ').handleOpenUserPrefsDialog()" class="' + this.cssClassTitleButton + + '">settings</a> <a href="#" onclick="gadgets.container.getGadget(' + + this.id + ').handleToggle()" class="' + this.cssClassTitleButton + + '">toggle</a></span></div>'); +}; + +gadgets.IfrGadget.prototype.getUserPrefsDialogContent = function(continuation) { + continuation('<div id="' + this.getUserPrefsDialogId() + '" class="' + + this.cssClassGadgetUserPrefsDialog + '"></div>'); +}; + +// TODO: Move this API to Container rather than Gadget +gadgets.IfrGadget.prototype.setServerBase = function(url) { + this.serverBase_ = url; +}; + +gadgets.IfrGadget.prototype.getServerBase = function() { + return this.serverBase_; +}; + +gadgets.IfrGadget.prototype.getMainContent = function(continuation) { + var iframeId = this.getIframeId(); + continuation('<div class="' + this.cssClassGadgetContent + '"><iframe id="' + + iframeId + '" name="' + iframeId + '" class="' + this.cssClassGadget + + '" src="' + this.getIframeUrl() + + '" frameborder="0" scrolling="no"></iframe></div>'); +}; + +gadgets.IfrGadget.prototype.getIframeId = function() { + return this.GADGET_IFRAME_PREFIX_ + this.id; +}; + +gadgets.IfrGadget.prototype.getUserPrefsDialogId = function() { + return this.getIframeId() + '_userPrefsDialog'; +}; + +gadgets.IfrGadget.prototype.getIframeUrl = function() { + return this.serverBase_ + 'ifr?url=' + + encodeURIComponent(this.specUrl) + '&synd=' + this.SYND + '&mid=' + + this.id + '&parent=' + encodeURIComponent(gadgets.container.parentUrl_) + + '&ogc=' + document.location.host + this.getUserPrefsParams(); +}; + +gadgets.IfrGadget.prototype.getUserPrefsParams = function() { + var params = ''; + if (this.getUserPrefs()) { + for(var name in this.getUserPrefs()) { + var value = this.getUserPref(name); + params += '&up_' + encodeURIComponent(name) + '=' + + encodeURIComponent(value); + } + } + return params; +} + +gadgets.IfrGadget.prototype.handleToggle = function() { + var gadgetIframe = document.getElementById(this.getIframeId()); + if (gadgetIframe) { + var gadgetContent = gadgetIframe.parentNode; + var display = gadgetContent.style.display; + gadgetContent.style.display = display ? '' : 'none'; + } +}; + +gadgets.IfrGadget.prototype.handleOpenUserPrefsDialog = function() { + if (this.userPrefsDialogContentLoaded) { + this.showUserPrefsDialog(); + } else { + var gadget = this; + window['ig_callback_' + this.id] = function(userPrefsDialogContent) { + gadget.userPrefsDialogContentLoaded = true; + gadget.buildUserPrefsDialog(userPrefsDialogContent); + gadget.showUserPrefsDialog(); + }; + + var script = document.createElement('script'); + script.src = 'http://gmodules.com/ig/gadgetsettings?url=' + this.specUrl + + '&mid=' + this.id + '&output=js' + this.getUserPrefsParams(); + document.body.appendChild(script); + } +}; + +gadgets.IfrGadget.prototype.buildUserPrefsDialog = function(content) { + var userPrefsDialog = document.getElementById(this.getUserPrefsDialogId()); + userPrefsDialog.innerHTML = content + + '<div class="' + this.cssClassGadgetUserPrefsDialogActionBar + + '"><input type="button" value="Save" onclick="gadgets.container.getGadget(' + + this.id +').handleSaveUserPrefs()"> <input type="button" value="Cancel" onclick="gadgets.container.getGadget(' + + this.id +').handleCancelUserPrefs()"></div>'; + userPrefsDialog.childNodes[0].style.display = ''; +}; + +gadgets.IfrGadget.prototype.showUserPrefsDialog = function(show) { + var userPrefsDialog = document.getElementById(this.getUserPrefsDialogId()); + userPrefsDialog.style.display = (show || show == undefined) ? '' : 'none'; +} + +gadgets.IfrGadget.prototype.hideUserPrefsDialog = function() { + this.showUserPrefsDialog(false); +}; + +gadgets.IfrGadget.prototype.handleSaveUserPrefs = function() { + this.hideUserPrefsDialog(); + + var prefs = {}; + var numFields = document.getElementById('m_' + this.id + + '_numfields').value; + for (var i = 0; i < numFields; i++) { + var input = document.getElementById('m_' + this.id + '_' + i); + if (input.type != 'hidden') { + var userPrefNamePrefix = 'm_' + this.id + '_up_'; + var userPrefName = input.name.substring(userPrefNamePrefix.length); + var userPrefValue = input.value; + prefs[userPrefName] = userPrefValue; + } + } + + this.setUserPrefs(prefs); + this.refresh(); +}; + +gadgets.IfrGadget.prototype.handleCancelUserPrefs = function() { + this.hideUserPrefsDialog(); +}; + +gadgets.IfrGadget.prototype.refresh = function() { + var iframeId = this.getIframeId(); + document.getElementById(iframeId).src = this.getIframeUrl(); +}; + + +// --------- +// Container + +/** + * Container interface. + * @constructor + */ +gadgets.Container = function() { + this.gadgets_ = {}; + this.parentUrl_ = ''; +}; + +gadgets.Container.inherits(gadgets.Extensible); + +/** + * Known dependencies: + * gadgetClass: constructor to create a new gadget instance + * userPrefStore: instance of a subclass of gadgets.UserPrefStore + * gadgetService: instance of a subclass of gadgets.GadgetService + * layoutManager: instance of a subclass of gadgets.LayoutManager + */ + +gadgets.Container.prototype.gadgetClass = gadgets.Gadget; + +gadgets.Container.prototype.userPrefStore = + new gadgets.CookieBasedUserPrefStore(); + +gadgets.Container.prototype.gadgetService = new gadgets.GadgetService(); + +gadgets.Container.prototype.layoutManager = + new gadgets.StaticLayoutManager(); + +gadgets.Container.prototype.setParentUrl = function(url) { + this.parentUrl_ = url; +}; + +gadgets.Container.prototype.getGadgetKey_ = function(instanceId) { + return 'gadget_' + instanceId; +}; + +gadgets.Container.prototype.getGadget = function(instanceId) { + return this.gadgets_[this.getGadgetKey_(instanceId)]; +}; + +gadgets.Container.prototype.createGadget = function(opt_params) { + return new this.gadgetClass(opt_params); +}; + +gadgets.Container.prototype.addGadget = function(gadget) { + gadget.id = this.getNextGadgetInstanceId(); + gadget.setUserPrefs(this.userPrefStore.getPrefs(gadget)); + this.gadgets_[this.getGadgetKey_(gadget.id)] = gadget; +}; + +gadgets.Container.prototype.addGadgets = function(gadgets) { + for (var i = 0; i < gadgets.length; i++) { + this.addGadget(gadgets[i]); + } +}; + +/** + * Renders all gadgets in the container. + */ +gadgets.Container.prototype.renderGadgets = function() { + for (var key in this.gadgets_) { + this.renderGadget(this.gadgets_[key]); + } +}; + +/** + * Renders a gadget. Gadgets are rendered inside their chrome element. + * @param {Object} gadget Gadget object + */ +gadgets.Container.prototype.renderGadget = function(gadget) { + throw Error(gadgets.error.SUBCLASS_RESPONSIBILITY); +}; + +gadgets.Container.prototype.nextGadgetInstanceId_ = 0; + +gadgets.Container.prototype.getNextGadgetInstanceId = function() { + return this.nextGadgetInstanceId_++; +}; + + +// ------------ +// IfrContainer + +/** + * Container that renders gadget using ifr. + * @constructor + */ +gadgets.IfrContainer = function() { + gadgets.Container.call(this); +}; + +gadgets.IfrContainer.inherits(gadgets.Container); + +gadgets.IfrContainer.prototype.gadgetClass = gadgets.IfrGadget; + +gadgets.IfrContainer.prototype.gadgetService = new gadgets.IfrGadgetService(); + +gadgets.IfrContainer.prototype.setParentUrl = function(url) { + if (!url.match(/^http[s]?:\/\//)) { + url = document.location.href.match(/^[^?#]+\//)[0] + url; + } + + /* Nasty hack to get around the hardcoded /ig/ifpc_relay URL */ + this.parentUrl_ = url + '?'; +}; + +/** + * Renders a gadget using ifr. + * @param {Object} gadget Gadget object + */ +gadgets.IfrContainer.prototype.renderGadget = function(gadget) { + var chrome = this.layoutManager.getGadgetChrome(gadget); + gadget.render(chrome); +}; + +/** + * Default container. + */ +gadgets.container = new gadgets.IfrContainer(); Added: incubator/shindig/trunk/javascript/container/ifpc_relay.html URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/javascript/container/ifpc_relay.html?rev=603539&view=auto ============================================================================== --- incubator/shindig/trunk/javascript/container/ifpc_relay.html (added) +++ incubator/shindig/trunk/javascript/container/ifpc_relay.html Wed Dec 12 02:33:35 2007 @@ -0,0 +1 @@ +<html><head><script src='http://www.google.com/ig/ifpc.js'></script><script>var l=window.location+'';_IFPC.processRequest(l.substring(l.indexOf('#')+1));if(!window.ActiveXObject){window.location='about:blank';}</script></head></html> Added: incubator/shindig/trunk/javascript/container/json.js URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/javascript/container/json.js?rev=603539&view=auto ============================================================================== --- incubator/shindig/trunk/javascript/container/json.js (added) +++ incubator/shindig/trunk/javascript/container/json.js Wed Dec 12 02:33:35 2007 @@ -0,0 +1,141 @@ +/* +Copyright (c) 2005 JSON.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +/* + The global object JSON contains two methods. + + JSON.stringify(value) takes a JavaScript value and produces a JSON text. + The value must not be cyclical. + + JSON.parse(text) takes a JSON text and produces a JavaScript value. It will + return false if there is an error. +*/ +var JSON = function () { + var m = { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }, + s = { + 'boolean': function (x) { + return String(x); + }, + number: function (x) { + return isFinite(x) ? String(x) : 'null'; + }, + string: function (x) { + if (/["\\\x00-\x1f]/.test(x)) { + x = x.replace(/([\x00-\x1f\\"])/g, function(a, b) { + var c = m[b]; + if (c) { + return c; + } + c = b.charCodeAt(); + return '\\u00' + + Math.floor(c / 16).toString(16) + + (c % 16).toString(16); + }); + } + return '"' + x + '"'; + }, + object: function (x) { + if (x) { + var a = [], b, f, i, l, v; + if (x instanceof Array) { + a[0] = '['; + l = x.length; + for (i = 0; i < l; i += 1) { + v = x[i]; + f = s[typeof v]; + if (f) { + v = f(v); + if (typeof v == 'string') { + if (b) { + a[a.length] = ','; + } + a[a.length] = v; + b = true; + } + } + } + a[a.length] = ']'; + } else if (typeof x.hasOwnProperty === 'function') { + a[0] = '{'; + for (i in x) { + if (x.hasOwnProperty(i)) { + v = x[i]; + f = s[typeof v]; + if (f) { + v = f(v); + if (typeof v == 'string') { + if (b) { + a[a.length] = ','; + } + a.push(s.string(i), ':', v); + b = true; + } + } + } + } + a[a.length] = '}'; + } else { + return; + } + return a.join(''); + } + return 'null'; + } + }; + return { + copyright: '(c)2005 JSON.org', + license: 'http://www.JSON.org/license.html', +/* + Stringify a JavaScript value, producing a JSON text. +*/ + stringify: function (v) { + var f = s[typeof v]; + if (f) { + v = f(v); + if (typeof v == 'string') { + return v; + } + } + return null; + }, +/* + Parse a JSON text, producing a JavaScript value. + It returns false if there is a syntax error. +*/ + parse: function (text) { + try { + return !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test( + text.replace(/"(\\.|[^"\\])*"/g, ''))) && + eval('(' + text + ')'); + } catch (e) { + return false; + } + } + }; +}(); Added: incubator/shindig/trunk/javascript/container/sample1.html URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/javascript/container/sample1.html?rev=603539&view=auto ============================================================================== --- incubator/shindig/trunk/javascript/container/sample1.html (added) +++ incubator/shindig/trunk/javascript/container/sample1.html Wed Dec 12 02:33:35 2007 @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html> +<head> +<title>Sample: Simple Container</title> +<!-- default container look and feel --> +<link rel="stylesheet" href="gadgets.css"> +<script type="text/javascript" src="json.js"></script> +<script type="text/javascript" src="http://www.google.com/ig/ifpc.js"></script> +<script type="text/javascript" src="cookies.js"></script> +<script type="text/javascript" src="gadgets.js"></script> +<script type="text/javascript"> +var specUrl0 = 'http://www.google.com/ig/modules/horoscope.xml'; +var specUrl1 = 'http://www.labpixies.com/campaigns/todo/todo.xml'; + +// This container lays out and renders gadgets itself. + +function renderGadgets() { + var gadget0 = gadgets.container.createGadget({specUrl: specUrl0}); + var gadget1 = gadgets.container.createGadget({specUrl: specUrl1}); + + gadgets.container.addGadget(gadget0); + gadgets.container.addGadget(gadget1); + gadgets.container.layoutManager.setGadgetChromeIds( + ['gadget-chrome-x', 'gadget-chrome-y']); + + gadgets.container.renderGadget(gadget0); + gadgets.container.renderGadget(gadget1); +}; +</script> +</head> +<body onLoad="renderGadgets()"> + <h2>Sample: Simple Container</h2> + <div id="gadget-chrome-x" class="gadgets-gadget-chrome"></div> + <div id="gadget-chrome-y" class="gadgets-gadget-chrome"></div> +</body> +</html> Added: incubator/shindig/trunk/javascript/container/sample2.html URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/javascript/container/sample2.html?rev=603539&view=auto ============================================================================== --- incubator/shindig/trunk/javascript/container/sample2.html (added) +++ incubator/shindig/trunk/javascript/container/sample2.html Wed Dec 12 02:33:35 2007 @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<html> +<head> +<title>Sample: Dynamic Height</title> +<!-- default container look and feel --> +<link rel="stylesheet" href="gadgets.css"> +<script type="text/javascript" src="json.js"></script> +<script type="text/javascript" src="http://www.google.com/ig/ifpc.js"></script> +<script type="text/javascript" src="cookies.js"></script> +<script type="text/javascript" src="gadgets.js"></script> +<script type="text/javascript"> +var my = {}; + +my.gadgetSpecUrls = [ + 'http://www.google.com/ig/modules/horoscope.xml', + 'aue07otr.xml', + 'http://www.labpixies.com/campaigns/todo/todo.xml' +]; + +var containerParentUrl = 'ifpc_relay.html'; + +// This container lays out and renders gadgets itself. + +my.LayoutManager = function() { + gadgets.LayoutManager.call(this); +}; + +my.LayoutManager.inherits(gadgets.LayoutManager); + +my.LayoutManager.prototype.getGadgetChrome = function(gadget) { + var chromeId = 'gadget-chrome-' + gadget.id; + return chromeId ? document.getElementById(chromeId) : null; +}; + +my.init = function() { + gadgets.container.setParentUrl(containerParentUrl); + gadgets.container.layoutManager = new my.LayoutManager(); +}; + +my.renderGadgets = function() { + for (var i = 0; i < my.gadgetSpecUrls.length; ++i) { + var gadget = gadgets.container.createGadget( + {specUrl: my.gadgetSpecUrls[i]}); + gadgets.container.addGadget(gadget); + gadgets.container.renderGadget(gadget); + } +}; +</script> +</head> +<body onLoad="my.init();my.renderGadgets()"> + <h2>Sample: Dynamic Height</h2> + <div id="gadget-chrome-0" class="gadgets-gadget-chrome"></div> + <div id="gadget-chrome-1" class="gadgets-gadget-chrome"></div> + <div id="gadget-chrome-2" class="gadgets-gadget-chrome"></div> +</body> +</html> Added: incubator/shindig/trunk/javascript/container/sample3.html URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/javascript/container/sample3.html?rev=603539&view=auto ============================================================================== --- incubator/shindig/trunk/javascript/container/sample3.html (added) +++ incubator/shindig/trunk/javascript/container/sample3.html Wed Dec 12 02:33:35 2007 @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<html> +<head> +<title>Sample: Container with FloatLeft Layout</title> +<!-- default container look and feel --> +<link rel="stylesheet" href="gadgets.css"> +<script type="text/javascript" src="json.js"></script> +<script type="text/javascript" src="http://www.google.com/ig/ifpc.js"></script> +<script type="text/javascript" src="cookies.js"></script> +<script type="text/javascript" src="gadgets.js"></script> +<script type="text/javascript"> +var specUrl0 = 'http://www.google.com/ig/modules/horoscope.xml'; + +function init() { + gadgets.container.layoutManager = + new gadgets.FloatLeftLayoutManager('layout-root'); + + for (var i = 0; i < 13; i++) { + gadgets.container.addGadget( + gadgets.container.createGadget({specUrl: specUrl0})); + } +}; + +function renderGadgets() { + gadgets.container.renderGadgets(); +}; +</script> +</head> +<body onLoad="init();renderGadgets()"> + <h2>Sample: Container with FloatLeft Layout</h2> + <div id="layout-root" class="gadgets-layout-root"></div> +</body> +</html> Added: incubator/shindig/trunk/javascript/container/sample4.html URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/javascript/container/sample4.html?rev=603539&view=auto ============================================================================== --- incubator/shindig/trunk/javascript/container/sample4.html (added) +++ incubator/shindig/trunk/javascript/container/sample4.html Wed Dec 12 02:33:35 2007 @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html> +<head> +<title>Sample: set-pref support</title> +<!-- default container look and feel --> +<link rel="stylesheet" href="gadgets.css"> +<script type="text/javascript" src="json.js"></script> +<script type="text/javascript" src="http://www.google.com/ig/ifpc.js"></script> +<script type="text/javascript" src="cookies.js"></script> +<script type="text/javascript" src="gadgets.js"></script> +<script type="text/javascript"> +var specUrl0 = 'http://www.google.com/ig/modules/test_setprefs_multiple_ifpc.xml'; + +function init() { + gadgets.container.layoutManager = + new gadgets.FloatLeftLayoutManager('gadget-parent'); + + gadgets.container.setParentUrl('ifpc_relay.html'); + gadgets.container.addGadget( + gadgets.container.createGadget({specUrl: specUrl0})); +}; + +function renderGadgets() { + gadgets.container.renderGadgets(); +}; +</script> +</head> +<body onLoad="init();renderGadgets()"> + <h2>Sample: set-pref support</h2> + <div id="gadget-parent" class="gadgets-gadget-parent"></div> +</body> +</html>

