Modified: incubator/shindig/trunk/features/opensocial-samplecontainer/feature.xml URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/features/opensocial-samplecontainer/feature.xml?rev=611352&r1=611351&r2=611352&view=diff ============================================================================== --- incubator/shindig/trunk/features/opensocial-samplecontainer/feature.xml (original) +++ incubator/shindig/trunk/features/opensocial-samplecontainer/feature.xml Fri Jan 11 18:12:13 2008 @@ -21,22 +21,77 @@ <dependency>opensocial-reference</dependency> <dependency>caja</dependency> <gadget> - <script src="samplecontainer.js"/> + <script src="samplecontainer.js"></script> + <script src="caja-compatible-jquery.js"></script> + <script src="statefileparser.js"></script> <script> - opensocial.Container.setContainer(new opensocial.SampleContainer()); - opensocial.Container.get().enableCaja(); + opensocial.Container.setContainer(new opensocial.SampleContainer()); + opensocial.Container.get().enableCaja(); + + var stateUrl = "/gadgets/samplecontainer/state-basicfriendlist.xml"; + <!--TODO(doll): The cookies.js file needs to be moved into a feature so we can use it here--> + <!--var cookieUrl = decodeURIComponent(goog.net.cookies.get('sampleContainerStateUrl'));--> + <!--if (cookieUrl && cookieUrl != "undefined") {--> + <!--stateUrl = cookieUrl;--> + <!--}--> + + var messageDiv = {"innerHTML" : "ignoring for now"}; + + function toggleStateType() { + if (document.getElementById("stateTypeUrl").checked) { + document.getElementById("stateUrl").style.display = "block"; + document.getElementById("stateXml").style.display = "none"; + } else { + document.getElementById("stateUrl").style.display = "none"; + document.getElementById("stateXml").style.display = "block"; + } + } + + function showXmlState() { + document.getElementById("stateTypeUrl").checked = false; + document.getElementById("stateTypeXml").checked = true; + toggleStateType(); + } + + function changeState() { + if (document.getElementById("stateTypeUrl").checked) { + changeStateUrl(); + } else { + changeStateXml(); + } + } + + function changeStateUrl() { + stateUrl = document.getElementById("stateUrl").value; + StateFileParser.refreshState(stateUrl, messageDiv, + opensocial.Container.get(), function() {gadgets.util.runOnLoadHandlers(); dumpState();}); + } + + function changeStateXml() { + var stateXml = document.getElementById("stateXml").value; + var stateXmlObject = (new DOMParser()).parseFromString(stateXml, "text/xml"); + StateFileParser.onLoadState(stateXmlObject, null, messageDiv, + opensocial.Container.get(), function() {window.console.log(messageDiv.innerHTML); gadgets.util.runOnLoadHandlers();}); + } + + function dumpState() { + StateFileParser.dumpState(opensocial.Container.get(), document.getElementById("stateXml")); + } + + var stateHtml = "<div style=\"padding: 5px; margin-bottom: 20px; background-color: #FAFAD2;" + + "color: #DAA520; font-size: smaller; font-weight:bold\">Displaying state:" + + "<input type=\"radio\" id=\"stateTypeUrl\" name=\"stateType\" value=\"url\" onclick=\"toggleStateType()\" checked=\"checked\" >use url</input>" + + "<input type=\"radio\" id=\"stateTypeXml\" name=\"stateType\" value=\"xml\" onclick=\"toggleStateType()\">use textbox</input>" + + "<input type=\"button\" value=\"load\" onclick=\"changeState();\"/>" + + "<input type=\"button\" value=\"dump\" onclick=\"dumpState(); showXmlState();\"/>" + + "<input type=\"text\" size=\"75\" id=\"stateUrl\" style=\"display:block;\"/>" + + "<textarea id=\"stateXml\" rows=\"15\" cols=\"120\" style=\"display:none;\" ></textarea>" + + "</div>"; + + document.write(stateHtml); + document.getElementById("stateUrl").value = stateUrl; + changeStateUrl(); - // remove when have state parsing or server side impl - var container = opensocial.Container.get(); - var viewer = container.newPerson({'id' : 1, 'name' : 'viewer guy'}, - false, true); - var owner = container.newPerson({'id' : 2, 'name' : 'owner guy'}, - true, false); - var viewerFriend1 = container.newPerson({'id' : 3, - 'name' : 'viewer friend 1!'}); - - container.resetData(viewer, owner, - container.newCollection([viewerFriend1])); </script> </gadget> </feature>
Modified: incubator/shindig/trunk/features/opensocial-samplecontainer/samplecontainer.js URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/features/opensocial-samplecontainer/samplecontainer.js?rev=611352&r1=611351&r2=611352&view=diff ============================================================================== --- incubator/shindig/trunk/features/opensocial-samplecontainer/samplecontainer.js (original) +++ incubator/shindig/trunk/features/opensocial-samplecontainer/samplecontainer.js Fri Jan 11 18:12:13 2008 @@ -47,6 +47,7 @@ */ opensocial.SampleContainer = function() { opensocial.Container.call(this); + this.resetData(this.newPerson()); }; opensocial.SampleContainer.inherits(opensocial.Container); Added: incubator/shindig/trunk/features/opensocial-samplecontainer/statefileparser.js URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/features/opensocial-samplecontainer/statefileparser.js?rev=611352&view=auto ============================================================================== --- incubator/shindig/trunk/features/opensocial-samplecontainer/statefileparser.js (added) +++ incubator/shindig/trunk/features/opensocial-samplecontainer/statefileparser.js Fri Jan 11 18:12:13 2008 @@ -0,0 +1,375 @@ +/** + * Copyright 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @fileoverview This file parses state files for the in memory container. + */ + + +/** + * State file parsing class for the sample container + * + * @constructor + */ +var StateFileParser = function() {}; + + +/** + * Activity fields that make up the state file's stream object + */ +StateFileParser.STREAM_FIELDS = ['userId', 'streamTitle', 'streamUrl', + 'streamFaviconUrl', 'streamSourceUrl']; + + +/** + * Make an ajax request to fetch the XML file. + */ +StateFileParser.refreshState = function(stateUrl, gadgetMessageDiv, + container, callback) { + // Make sure we have a valid state URL + if (!stateUrl) { + gadgetMessageDiv.innerHTML = 'Please enter a container state url. '; + callback(); + return; + } + + // Fetch the container state XML + gadgetMessageDiv.innerHTML = + 'Loading container state from ' + stateUrl + '<br>' + + 'If the state does not load make sure your URLs are in the same ' + + 'domain as this page.'; + + var me = this; + + + $.ajax({type: "GET", url: stateUrl, dataType: "xml", timeout: 5000, + error: function() { + gadgetMessageDiv.innerHTML + = 'Cannot fetch container state from ' + stateUrl; + callback(); + }, + success: function(xmlState) { + StateFileParser.onLoadState(xmlState, stateUrl, gadgetMessageDiv, + container, callback); + } + }); // ajax request +}; + + +/** + * This function will get called after successful download of the + * container state XML. + * @private + */ +StateFileParser.onLoadState = function(xmlState, stateUrl, gadgetMessageDiv, + container, callback) { + window.console.log(xmlState); + // Get the high level container node + var containerNode = $(xmlState).find('container')[0]; + if (!containerNode) { + gadgetMessageDiv.innerHTML + = 'Invalid container state XML at ' + stateUrl; + callback(); + return; + } + + // Get the viewer node + var viewer; + var viewerNode = $(containerNode).find('viewer')[0]; + if (viewerNode) { + viewer = StateFileParser.loadPerson(container, + $(viewerNode).find('person')[0], true); + } + + // Get the owner node + var owner; + var ownerNode = $(containerNode).find('owner')[0]; + if (ownerNode) { + owner = StateFileParser.loadPerson(container, + $(ownerNode).find('person')[0], false, true); + } + + // If the id of the owner is the same as the viewer, then set the viewer + // as the primary source of truth + if (!owner || (viewer && owner.getId() == viewer.getId())) { + owner = viewer; + owner.isViewer = true; + owner.isOwner = true; + } + + // Build the friends list + var me = this; + var viewerFriends = new Array(); + var friendsNode = $(containerNode).find('viewerFriends')[0]; + $(friendsNode).find('person').each(function() { + viewerFriends.push(StateFileParser.loadPerson(container, $(this))); + }); + var ownerFriends = new Array(); + friendsNode = $(containerNode).find('ownerFriends')[0]; + $(friendsNode).find('person').each(function() { + ownerFriends.push(StateFileParser.loadPerson(container, $(this))); + }); + + // Build the App data + var globalAppData = {}; + var globalDataNode = $(containerNode).find('globalAppData')[0]; + if (globalDataNode) { + $(globalDataNode).find('data').each(function() { + globalAppData[$(this).attr('field')] = $(this).text(); + }); + } + + var instanceAppData = {}; + var instanceDataNode = $(containerNode).find('instanceAppData')[0]; + if (instanceDataNode) { + $(instanceDataNode).find('data').each(function() { + instanceAppData[$(this).attr('field')] = $(this).text(); + }); + } + + var personAppData = {}; + var personDataNode = $(containerNode).find('personAppData')[0]; + if (personDataNode) { + $(personDataNode).find('data').each(function() { + if (personAppData[$(this).attr('person')] == null) { + personAppData[$(this).attr('person')] = {}; + } + personAppData[$(this).attr('person')][$(this).attr('field')] + = $(this).text(); + }); + } + + + // Build the activities list + var appIdNode = $(containerNode).find('appId')[0]; + var appId = appIdNode ? $(appIdNode).text() : 'sampleContainerAppId'; + + var activities = {}; + var activitiesNode = $(containerNode).find('activities')[0]; + $(activitiesNode).find('stream').each(function() { + var userId = $(this).attr('userId'); + var streamTitle = $(this).attr('title'); + var streamUrl = $(this).attr('url'); + var streamSourceUrl = $(this).attr('sourceUrl'); + var streamFaviconUrl = $(this).attr('faviconUrl'); + + activities[userId] = []; + + $(this).find('activity').each(function() { + var mediaItems = []; + $(this).find('mediaItem').each(function() { + mediaItems.push(container.newActivityMediaItem( + $(this).attr('mimeType'), + $(this).attr('url'), + {'type' : $(this).attr('type')})); + }); + activities[userId].push(container.newActivity( + $(this).attr('title'), + {'id' : $(this).attr('id'), + 'externalId' : $(this).attr('externalId'), + 'body' : $(this).attr('body'), + 'appId' : appId, + 'userId' : userId, + 'streamTitle' : streamTitle, + 'streamUrl' : streamUrl, + 'streamSourceUrl' : streamSourceUrl, + 'streamFaviconUrl' : streamFaviconUrl, + 'url' : $(this).attr('url'), + 'postedTime' : $(this).attr('postedTime'), + 'mediaItems' : mediaItems})); + }); + }); + + // Initialize the sample container with the state that has been read + container.resetData(viewer, owner, + container.newCollection(viewerFriends), + container.newCollection(ownerFriends), globalAppData, + instanceAppData, personAppData, activities, appId); + callback(); +}; + + +/** + * load a person related info from the XML node. Person could be + * viewer, owner or friend. Return value is the person object. + * @private + */ +StateFileParser.loadPerson = function(container, xmlNode, isViewer, isOwner) { + var fields = { + 'id' : $(xmlNode).attr(opensocial.Person.Field.ID), + 'name' : $(xmlNode).attr(opensocial.Person.Field.NAME), + 'thumbnailUrl' : $(xmlNode).attr(opensocial.Person.Field.THUMBNAIL_URL), + 'profileUrl' : $(xmlNode).attr(opensocial.Person.Field.PROFILE_URL)}; + return container.newPerson(fields, isViewer, isOwner); +}; + + +/** + * Dumps the current state of the container in XML. + */ +StateFileParser.dumpState = function(container, stateDiv) { + window.console.log("dumping state"); + var xmlText = '<container>\n'; + + xmlText += ' <viewer>\n'; + xmlText += StateFileParser.dumpPerson(container.viewer); + xmlText += ' </viewer>\n'; + + xmlText += ' <owner>\n'; + xmlText += StateFileParser.dumpPerson(container.owner); + xmlText += ' </owner>\n'; + + xmlText += ' <viewerFriends>\n'; + container.viewerFriends.each(function(friend) { + xmlText += StateFileParser.dumpPerson(friend); + }); + xmlText += ' </viewerFriends>\n'; + + xmlText += ' <ownerFriends>\n'; + container.ownerFriends.each(function(friend) { + xmlText += StateFileParser.dumpPerson(friend); + }); + xmlText += ' </ownerFriends>\n'; + + // Dump App Data + xmlText += ' <globalAppData>\n'; + for (var field in container.globalAppData) { + if (___.canInnocentEnum(container.globalAppData, field)) { + xmlText += ' <data field="' + field + '">'; + xmlText += container.globalAppData[field]; + xmlText += '</data>\n'; + } + } + xmlText += ' </globalAppData>\n'; + + xmlText += ' <instanceAppData>\n'; + for (var field in container.instanceAppData) { + if (___.canInnocentEnum(container.instanceAppData, field)) { + xmlText += ' <data field="' + field + '">'; + xmlText += container.instanceAppData[field]; + xmlText += '</data>\n'; + } + } + xmlText += ' </instanceAppData>\n'; + + xmlText += ' <personAppData>\n'; + for (var person in container.personAppData) { + if (___.canInnocentEnum(container.personAppData, person)) { + for (var field in container.personAppData[person]) { + if (___.canInnocentEnum(container.personAppData[person], field)) { + xmlText += ' <data person="' + person + '" '; + xmlText += 'field="' + field + '">'; + xmlText += container.personAppData[person][field]; + xmlText += '</data>\n'; + } + } + } + } + xmlText += ' </personAppData>\n'; + + // Dump the activities. Since only 1 stream is supported, use the first one + xmlText += ' <activities>\n'; + var streamWritten = false; + for (var id in container.activities) { + if (___.canInnocentEnum(container.activities, id)) { + var activity = container.activities[id]; + if (!streamWritten) { + var streamTitle = activity.getField('streamTitle'); + if (!streamTitle) { + continue; + } + xmlText += ' <stream'; + for (var field in StateFileParser.STREAM_FIELDS) { + if (___.canInnocentEnum(StateFileParser.STREAM_FIELDS, field)) { + var value = activity.getField(field); + if (value == null) { + continue; + } + xmlText += ' ' + field + '="' + value + '"'; + } + } + xmlText += '>\n'; + streamWritten = true; + } + + xmlText += ' <activity'; + for (var field in activity.fields_) { + if (___.canInnocentEnum(activity.fields_, field)) { + var value = activity.getField(field); + if (value == null || field == 'mediaItems' + || field in StateFileParser.STREAM_FIELDS) { + continue; + } + xmlText += ' ' + field + '="' + value + '"'; + } + } + xmlText += '>'; + var mediaItems = activity.mediaItems; + for (var i = 0; mediaItems && i < mediaItems.length; i++) { + var mediaItem = mediaItem[i]; + xmlText += ' <mediaItem '; + if (mediaItem.mimeType) { + xmlText += ' mimeType="' + mediaItem.mimeType + '"'; + } + if (mediaItem.url) { + xmlText += ' url="' + mediaItem.url + '"'; + } + if (mediaItem.opt_params && mediaItem.opt_params.type) { + xmlText += ' type="' + mediaItem.opt_params.type + '"'; + } + xmlText += '/>\n'; + } + xmlText += '</activity>\n'; + } + } + if (streamWritten) { + xmlText += ' </stream>\n'; + } + xmlText += ' </activities>\n'; + + xmlText += '</container>'; + window.console.log("got to the end with this " + xmlText); + stateDiv.value = xmlText; +}; + + +/** + * @private + */ +StateFileParser.dumpPersonField = function(personObj, name) { + var field = personObj.getField(name); + if (field) { + return ' ' + name + '="' + field + '"'; + } + return ''; +}; + + +/** + * Dump the state of a person object. + * @private + */ +StateFileParser.dumpPerson = function(personObj) { + var xmlText = ' <person'; + xmlText += StateFileParser.dumpPersonField(personObj, + opensocial.Person.Field.ID); + xmlText += StateFileParser.dumpPersonField(personObj, + opensocial.Person.Field.NAME); + xmlText += StateFileParser.dumpPersonField(personObj, + opensocial.Person.Field.THUMBNAIL_URL); + xmlText += StateFileParser.dumpPersonField(personObj, + opensocial.Person.Field.PROFILE_URL); + xmlText += '></person>\n'; + return xmlText; +}; Modified: incubator/shindig/trunk/javascript/README URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/javascript/README?rev=611352&r1=611351&r2=611352&view=diff ============================================================================== --- incubator/shindig/trunk/javascript/README (original) +++ incubator/shindig/trunk/javascript/README Fri Jan 11 18:12:13 2008 @@ -9,7 +9,7 @@ * 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 + 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 @@ -50,6 +50,25 @@ 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/'); + +4) Run the opensocial sample container + A) Set up your own Shindig Gadget Server. See its README for details. + + B) From within the java/gadgets directory run the following commands + i) Copy the sample container code into the webapp directory so that the + shindig server will serve it for you. + cp -r ../../javascript/container/ src/main/webapp/ + cp -r ../../javascript/samplecontainer/ src/main/webapp/ + + ii) Restart shindig + mvn jetty:run + + C) Hit the sample container at + http://yourserver:yourport/gadgets/samplecontainer/samplecontainer.html + + D) See the sample container help document for more details on how it works: + http://yourserver:yourport/gadgets/samplecontainer/getting-started.html + NOTE: In the short term, when rendering Gadgets using gmodules.com certain functionality tied to inter-frame communication will not work, Added: incubator/shindig/trunk/javascript/samplecontainer/getting-started.html URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/javascript/samplecontainer/getting-started.html?rev=611352&view=auto ============================================================================== --- incubator/shindig/trunk/javascript/samplecontainer/getting-started.html (added) +++ incubator/shindig/trunk/javascript/samplecontainer/getting-started.html Fri Jan 11 18:12:13 2008 @@ -0,0 +1,63 @@ +<html> +<head> + <title>OpenSocial Container Sample</title> +</head> + +<body> + <h1>OpenSocial Container Sample - Getting started</h1> + + <ul> + <li><a href="#Purpose">Purpose</a> + <li><a href="#Theory">Theory</a> + <li><a href="#Tips">Tips</a> + </ul> + + <h2 id="Purpose">Purpose</h2> + This sample serves two primary purposes: + <ol> + <li>To demonstrate how a container can be implemented using a simple + example. + <li>To create an environment for easy gadget testing. OpenSocial is all + about social APIs, which means that gadget testing usually involves + multiple user accounts. This container makes testing easy by letting + gadgets specify arbitrary state for any number of users. + </ol> + + <h2 id="Theory">How it works? (Theory)</h2> + As the gadget developer you need to specify two pieces of information: + <ol type="a"> + <li>URL to the gadget definition; and</li> + <li>initial state of the container in the form of a URL to a state file.</li> + </ol> + + You can find the DTD for the state definition in the docs/state.dtd folder. + + The state definition file allows you to specify the viewer of the gadget, + the owner, the friends and the activities of those users. Once the gadget and + its state are loaded you can use the gadget in the same way as any other + container. At any point you can also dump a snap shot of the state of the + environment to an XML file (with the same format as the state definition + file). + + <h2 id="Tips">Tips/caveats</h2> + <ul> + <li>Due to browser security restrictions, your gadget definition + file and system state file must be on the same server as the + container if you specify a url. The above step by step procedure runs the test container from the local + file system. You can also copy the files to a web server and run off it + instead. + <li>The gadget definition URL is stored in a cookie. + You can set the values to empty or clear your cookies to clear existing + values for those fields. + <li>For easier debugging of your gadget script in Firebug, include the + gadget script using a script tag in the gadget definition file, instead + of inlining the script. Sometimes Firebug still cannot display the gadget + javascript if it is on the local file system. To avoid this, you can untar + the package to a web server and access it through an <code>http</code> URL + to that server. + <li>You always need to specify a state file for the container. At a minimum + it must include the viewer name. All other fields are optional. + </ul> + +</body> +</html> Added: incubator/shindig/trunk/javascript/samplecontainer/samplecontainer.html URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/javascript/samplecontainer/samplecontainer.html?rev=611352&view=auto ============================================================================== --- incubator/shindig/trunk/javascript/samplecontainer/samplecontainer.html (added) +++ incubator/shindig/trunk/javascript/samplecontainer/samplecontainer.html Fri Jan 11 18:12:13 2008 @@ -0,0 +1,78 @@ +<html> +<head> +<title>Gadget testing container</title> +<link rel="stylesheet" href="../container/gadgets.css"> +<style type="text/css"> + body { + font-family: arial, sans-serif; + } + + .gadgets-gadget { + height: 100%; + width: 100%; + } + + #headerDiv { + padding: 10px; + margin-bottom: 20px; + background-color: #e5ecf9; + color: #3366cc; + font-size: larger; + font-weight: bold; + } + + .subTitle { + font-size: smaller; + float: right; + } + +</style> +<script type="text/javascript" src="../container/json.js"></script> +<script type="text/javascript" src="../container/ifpc.js"></script> +<script type="text/javascript" src="../container/cookies.js"></script> +<script type="text/javascript" src="../container/gadgets.js"></script> +<script type="text/javascript"> + +var specUrl = 'http://hosting.gmodules.com/ig/gadgets/file/117247905274371511495/caja-clickme.xml'; +var gadget; + +function initGadget() { + var cookieUrl = decodeURIComponent(goog.net.cookies.get('sampleContainerGadgetUrl')); + if (cookieUrl && cookieUrl != "undefined") { + specUrl = cookieUrl; + } + + document.getElementById("gadgetUrl").value = specUrl; + + gadget = gadgets.container.createGadget({'specUrl': specUrl});; + gadget.setServerBase('http://localhost:8080/gadgets/'); + + gadgets.container.addGadget(gadget); + gadgets.container.layoutManager.setGadgetChromeIds(['gadget-chrome']); + gadgets.container.renderGadgets(); +}; + +function changeGadgetUrl() { + specUrl = document.getElementById("gadgetUrl").value; + gadget.specUrl = specUrl; + goog.net.cookies.set('sampleContainerGadgetUrl', encodeURIComponent(specUrl)); + + gadgets.container.renderGadgets(); +}; + +</script> +</head> +<body onLoad="initGadget();"> + <div id="headerDiv"> + <div style="float:left">Gadget testing container</div> + <div class="subTitle"> + Displaying gadget: + <input type="text" size="75" id="gadgetUrl"/> + <input type="button" value="reset" onclick="changeGadgetUrl();"/> + </div> + <div style="clear:both; height: 1px;"> </div> + </div> + + <div id="gadget-chrome" class="gadgets-gadget-chrome" style="width:80%; height:100%"></div> +</body> +</html> Added: incubator/shindig/trunk/javascript/samplecontainer/state-basicfriendlist.xml URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/javascript/samplecontainer/state-basicfriendlist.xml?rev=611352&view=auto ============================================================================== --- incubator/shindig/trunk/javascript/samplecontainer/state-basicfriendlist.xml (added) +++ incubator/shindig/trunk/javascript/samplecontainer/state-basicfriendlist.xml Fri Jan 11 18:12:13 2008 @@ -0,0 +1,13 @@ +<container> + <viewer> + <person id="[EMAIL PROTECTED]" name="John Doe"></person> + </viewer> + + <viewerFriends> + <person id="[EMAIL PROTECTED]" name="Jane Doe"></person> + <person id="[EMAIL PROTECTED]" name="George Doe"></person> + <person id="[EMAIL PROTECTED]" name="Peter Doe"></person> + <person id="[EMAIL PROTECTED]" name="Susan Doe"></person> + </viewerFriends> + +</container> Added: incubator/shindig/trunk/javascript/samplecontainer/state.dtd URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/javascript/samplecontainer/state.dtd?rev=611352&view=auto ============================================================================== --- incubator/shindig/trunk/javascript/samplecontainer/state.dtd (added) +++ incubator/shindig/trunk/javascript/samplecontainer/state.dtd Fri Jan 11 18:12:13 2008 @@ -0,0 +1,39 @@ +<!ELEMENT container (viewer, owner?, viewerFriends?, ownerFriends?, appId?, + globalAppData?, instanceAppData?, personAppData?, activities?)> + +<!ELEMENT viewer (person)> +<!ELEMENT owner (person)> +<!ELEMENT viewerFriends (person*)> +<!ELEMENT ownerFriends (person*)> +<!ELEMENT appId CDATA #REQUIRED> + +<!ELEMENT person> +<!ATTLIST person id CDATA #REQUIRED + name CDATA #IMPLIED + thumbnailUrl CDATA #IMPLIED + profileUrl CDATA #IMPLIED> + +<!ELEMENT globalAppData (data*)> +<!ELEMENT instanceAppData (data*)> +<!ELEMENT personAppData (data*)> + +<!ELEMENT data (#PCDATA)> +<!ATTLIST data field CDATA #REQUIRED person CDATA #IMPLIED> + +<!ELEMENT activities (stream*)> +<!ELEMENT stream (activity*)> +<!ATTLIST stream title CDATA #REQUIRED + url CDATA #IMPLIED + userId CDATA #IMPLIED + sourceUrl CDATA #IMPLIED + faviconUrl CDATA #IMPLIED> +<!ELEMENT activity (mediaItem*) (#PCDATA)> +<!ATTLIST activity title CDATA #REQUIRED + id CDATA #REQUIRED + externalId CDATA #IMPLIED + body CDATA #IMPLIED + url CDATA #IMPLIED + postedTime CDATA #IMPLIED> +<!ATTLIST mediaItem mimeType CDATA #REQUIRED + url CDATA #REQUIRED + type CDATA #IMPLIED>

