You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@shindig.apache.org by jo...@apache.org on 2007/12/12 11:33:38 UTC

svn commit: r603539 [3/3] - in /incubator/shindig/trunk: ./ java/ java/gadgets/ java/gadgets/src/ java/gadgets/src/main/ java/gadgets/src/main/java/ java/gadgets/src/main/java/org/ java/gadgets/src/main/java/org/apache/ java/gadgets/src/main/java/org/a...

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 arv@google.com (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 jyang@google.com (Jun Yang)
+ * @author wangz@google.com (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>