Commit 072a2004 authored by Sebastien Robin's avatar Sebastien Robin

Merge remote-tracking branch 'remotes/trac/master'

parents b115e436 209534e7
/UNGProject/dojo/
/UNGProject/xinha/ /UNGProject/xinha/
/UNGProject/nbproject/ /UNGProject/nbproject/
/UNGProject/jquery_sheet_editor/ /UNGProject/jquery_sheet_editor/
...@@ -17,3 +18,6 @@ apache2.conf ...@@ -17,3 +18,6 @@ apache2.conf
/.installed.cfg /.installed.cfg
/.mr.developer.cfg /.mr.developer.cfg
/parts/ /parts/
#vi
*.swp
...@@ -13,9 +13,9 @@ div#pad-navigation-wrapper { ...@@ -13,9 +13,9 @@ div#pad-navigation-wrapper {
} }
#pad-navigation-wrapper { #pad-navigation-wrapper {
border-bottom: 1px solid #3D6474; border-bottom: 1px solid #3D6474;
height: 25px; height: 38px;
margin-top: 10px; margin-top: 10px;
width: 100%; width: 96%;
} }
/* tabs */ /* tabs */
#tabs_switcher { #tabs_switcher {
......
[{"name":"Color Palette","url":"http:\/\/localhost\/gadgets\/sample\/colorpalette_oam.xml"},
{"name":"Date Listener","url":"http:\/\/localhost\/gadgets\/sample\/dateListener.oam.xml"}]
.gadgetContainer {
position:absolute;
}
\ No newline at end of file
function AbsoluteLayout(params)
{
for (var obj in params) {
this[obj] = params[obj];
}
this._bindCallbacks();
this.registerLayout();
this._top = 0;
}
AbsoluteLayout.prototype._keyPress = function(event)
{
event = event ? event : window.event;
var target = event.target ? event.target : event.srcElement;
if ((this._isDraggingGadget || this._isDraggingPaletteItem) && event.keyCode == 27) {
Browser.removeEventListener('mousemove', window.document, this._boundMouseMove);
this._hijackFrames(true);
if (this._isDraggingGadget) {
Browser.setAlpha(this._container, '1.0');
this._container.style.left = this._gadgetLeft + "px";
this._container.style.top = this._gadgetTop + "px";
this._isDraggingGadget = false;
this._container = null;
} else {
Browser.removeEventListener('click', window.document, this._boundClick);
this._isDraggingPaletteItem = false;
if (this._dragDIV) {
this._dragDIV = null;
delete this._dragDIVLeft;
delete this._dragDIVTop;
}
if (this._dragDoneCallback) {
this._dragDoneCallback(false, null);
this._dragDoneCallback = null;
}
}
}
}
AbsoluteLayout.prototype.getGadgets = function(element)
{
var tables = element.getElementsByTagName('TABLE');
var inputs = new Array();
for (var i =0; i < tables.length; i++) {
var table = tables.item(i);
if (table.className == 'gadgetContainer') {
inputs.push(table);
}
}
var result = new Array();
for (var i = 0; i < inputs.length; i++) {
var id = inputs[i].getAttribute('_widgetid');
var gadget = mashupMaker.getWidgetModelFromID(id);
if (gadget) {
var site = gadget._site;
if (site && site.getContainer() != inputs[i]) {
site = new GadgetSite(inputs[i], id/*,
mashupMaker.models[id].views*/); // XXX JHP TODO handle views
}
result.push({gadget:gadget, site: site });
}
}
return(result);
}
AbsoluteLayout.prototype._bindCallbacks = function()
{
this._boundMouseDown = Browser.bind(this._mouseDown, this);
this._boundMouseUp = Browser.bind(this._mouseUp, this);
this._boundMouseMove = Browser.bind(this._mouseMove, this);
this._boundKeyPress = Browser.bind(this._keyPress, this);
this._boundClick = Browser.bind(this._click, this);
}
AbsoluteLayout.prototype._mouseDown = function(event)
{
if (this._isDraggingPaletteItem) {
// since we use sticky drag for palette items, we don't want to
// do anything on mousedown if we are in the middle of one.
return(Browser.cancelEvent(event));
}
event = event ? event : window.event;
var target = event.target ? event.target : event.srcElement;
switch (target.className) {
case 'gadgetHeader':
case 'gadgetTitle':
case 'gadgetFooter':
this._dragGadget(this._getGadgetContainer(target), event.clientX, event.clientY);
return(Browser.cancelEvent(event));
case 'gadgetBoxFooter':
case 'absSizerImg':
this._sizeGadget(this._getGadgetContainer(target), event.clientX, event.clientY);
return(Browser.cancelEvent(event));
}
}
AbsoluteLayout.prototype._mouseUp = function(event)
{
if (this._isDraggingPaletteItem) {
// since we use sticky drag for palette items, we don't want to
// do anything on mousedown if we are in the middle of one.
return(Browser.cancelEvent(event));
}
event = event ? event : window.event;
var target = event.target ? event.target : event.srcElement;
switch (target.className) {
case 'absDeleteImg':
var container = this._getGadgetContainer(target);
if ( container.getAttribute("_widgetid") != "" ) {
mashupMaker.deleteGadget( container.getAttribute("_widgetid") );
} else {
container.parentNode.removeChild(container);
delete container;
}
return(Browser.cancelEvent(event));
case 'absPropEditImg':
return(false);
}
if (this._isSizing || this._isDraggingGadget) {
Browser.removeEventListener('mousemove', window.document, this._boundMouseMove);
if ( this._isSizing ) {
// We cannot use "target" in order to get the widget container,
// since the target may not be related to a part of the widget
// DOM structure (for example, if the widget is sized outside of the
// window, then target may come back as the top level "document").
// Instead, we save the container when sizing starts and use it here.
var container = this._sizingContainer;
this._isSizing = false;
this._sizingContainer = null;
var id = container.getAttribute("_widgetid");
if ( id != "" ) {
mashupMaker.handleWidgetResize( id );
}
}
if (this._saveIndex) {
if (this._container) {
this._container.style.zIndex = this._saveIndex;
this._saveIndex = null;
}
}
this._hijackFrames(true);
if (this._isDraggingGadget) {
if (this._container) {
Browser.setAlpha(this._container, "1.0");
}
this._isDraggingGadget = false;
}
this._container = null;
return(Browser.cancelEvent(event));
}
}
AbsoluteLayout.prototype._mouseMove = function(event) {
event = event ? event : window.event;
if (this._isSizing) {
var dx = event.clientX - this._lastX;
var dy = event.clientY - this._lastY;
var gadgetBody = this._site.getBody();
var pos = dojo.coords(gadgetBody);
var newWidth = event.clientX - pos.x + this._offsetX;
var newHeight = event.clientY - pos.y + this._offsetY;
this._site.resize( newWidth, newHeight );
if (this._overlay) {
this._overlay.style.width = newWidth + "px";
this._overlay.style.height = newHeight + "px";
}
this._lastX = event.clientX;
this._lastY = event.clientY;
return(Browser.cancelEvent(event));
} else {
if (this._dragDIV) {
var dragX, dragY;
if (event.pageX || event.pageY) {
dragX = event.pageX - this._offsetX;
dragY = event.pageY - this._offsetY;
} else {
dragX = event.clientX + window.document.body.scrollLeft - window.document.body.clientLeft - this._offsetX;
dragY = event.clientY + window.document.body.scrollTop - window.document.body.clientTop - this._offsetY;
}
this._dragDIV.style.left = dragX + "px";
this._dragDIV.style.top = dragY + "px";
} else {
this._container.style.left = event.clientX - this._offsetX + "px";
this._container.style.top = event.clientY - this._offsetY + "px";
if (this._overlay) {
this._overlay.style.left = Browser.pixelValue(this._container.style.left) - this._overlayOffset.x + "px";
this._overlay.style.top = Browser.pixelValue(this._container.style.top) - this._overlayOffset.y + "px";
}
}
return(Browser.cancelEvent(event));
}
}
AbsoluteLayout.prototype._getGadgetContainer = function(div)
{
while (div && div.className != 'gadgetContainer') {
div = div.parentNode;
}
return(div);
}
AbsoluteLayout.prototype._sizeGadget = function(container, startX, startY)
{
this._container = container;
var id = container.getAttribute('_widgetid');
this._site = new GadgetSite(container, id/*, mashupMaker.models[id].views*/); // XXX JHP TODO handle views
var pos = dojo.coords(this._site.getBody());
this._offsetX = pos.x + pos.w - startX;
this._offsetY = pos.y + pos.h - startY;
this._lastX = startX;
this._lastY = startY;
this._isSizing = true;
this._sizingContainer = container;
this._hijackFrames();
if (this._frames) {
this._overlay = this._frames[id];
} else {
this._overlay = null;
}
Browser.addEventListener('mousemove', window.document, this._boundMouseMove);
}
AbsoluteLayout.prototype._hijackFrames = function(destroy)
{
if (destroy) {
if (this._frames) {
for (var frameID in this._frames) {
var div = this._frames[frameID];
div.parentNode.removeChild(div);
delete div;
}
}
} else {
var iframes = document.getElementsByTagName('IFRAME');
this._frames = new Array();
for (var i = 0; i < iframes.length; i++) {
var frame = iframes.item(i);
var container = this._getGadgetContainer(frame);
if (!container) {
// it is possible that dojo inserts iframes
continue;
}
var id = container.getAttribute('_widgetid');
if (! id) {
continue;
}
var div = document.createElement('DIV');
div.style.position = 'absolute';
var pos = dojo.coords(frame, true);
div.style.left = pos.x + "px";
div.style.top = pos.y + "px";
div.style.width = pos.w + "px";
div.style.height = pos.h + "px";
div.style.zIndex = 10000;
div.style.background = 'white';
div.innerHTML = '&nbsp;';
Browser.setAlpha(div, "0");
this._frames[id] = div;
document.body.appendChild(div);
}
}
}
AbsoluteLayout.prototype._dragGadget = function(container, startX, startY)
{
this._container = container;
this._offsetX = startX - container.offsetLeft;
this._offsetY = startY - container.offsetTop;
this._saveIndex = ++this._top;
this._container.style.zIndex = 10001;
this._isDraggingGadget = true;
this._hijackFrames();
if (this._frames) {
this._overlay = this._frames[this._container.getAttribute('_widgetid')];
if (this._overlay) {
var pos = dojo.coords(container, true);
this._overlayOffset = {x: pos.x - Browser.pixelValue(this._overlay.style.left),
y: pos.y - Browser.pixelValue(this._overlay.style.top)};
this._overlay.style.zIndex = Number(this._container.style.zIndex) + 1;
}
} else {
this._overlay = null;
}
Browser.setAlpha(container, "0.4");
this._gadgetLeft = container.offsetLeft;
this._gadgetTop = container.offsetTop;
Browser.addEventListener('mousemove', window.document, this._boundMouseMove);
}
AbsoluteLayout.prototype.putOnTop = function(container)
{
while (container && container.className != 'gadgetContainer') {
container = container.parentNode;
}
if (container) {
container.style.zIndex = ++this._top;
return true;
}
return false;
}
AbsoluteLayout.prototype.unregisterLayout = function() {
Browser.removeEventListener('mousedown', window.document, this._boundMouseDown);
Browser.removeEventListener('mouseup', window.document, this._boundMouseUp);
Browser.removeEventListener('keypress', window.document, this._boundKeyPress);
}
AbsoluteLayout.prototype.registerLayout = function() {
Browser.addEventListener('mousedown', window.document, this._boundMouseDown);
Browser.addEventListener('mouseup', window.document, this._boundMouseUp);
Browser.addEventListener('keypress', window.document, this._boundKeyPress);
}
AbsoluteLayout.prototype.setLayoutStyles = function(container) {
container.style.position = 'absolute';
}
AbsoluteLayout.prototype.removeLayoutStyles = function(container) {
container.style.position = '';
}
AbsoluteLayout.prototype.dragPaletteItem = function(dragDIV, startX, startY, callback)
{
this._dragDoneCallback = callback;
this._dragDIV = dragDIV;
this._offsetX = startX - dragDIV.offsetLeft;
this._offsetY = startY - dragDIV.offsetTop;
this._saveIndex = ++this._top;
this._dragDIV.style.zIndex = 10001;
this._isDraggingPaletteItem = true;
this._hijackFrames();
this._overlay = null;
this._dragDIVLeft = dragDIV.offsetLeft;
this._dragDIVTop = dragDIV.offsetTop;
Browser.addEventListener('mousemove', window.document, this._boundMouseMove);
Browser.addEventListener('click', window.document, this._boundClick);
}
AbsoluteLayout.prototype._click = function(event)
{
if (!this._isDraggingPaletteItem) {
return;
}
this._isDraggingPaletteItem = false;
if (this._dragDIV) {
this._dragDIV = null;
delete this._dragDIVLeft;
delete this._dragDIVTop;
}
Browser.removeEventListener('click', window.document, this._boundClick);
Browser.removeEventListener('mousemove', window.document, this._boundMouseMove);
this._saveIndex = null;
this._hijackFrames(true);
if (this._dragDoneCallback) {
var dropX, dropY;
if (event.pageX || event.pageY) {
dropX = event.pageX;
dropY = event.pageY;
} else {
dropX = event.clientX + window.document.body.scrollLeft - window.document.body.clientLeft;
dropY = event.clientY + window.document.body.scrollTop - window.document.body.clientTop;
}
this._dragDoneCallback(true, {layout: 'absolutelayout', x: dropX, y: dropY});
this._dragDoneCallback = null;
}
}
if (typeof Node == 'undefined') {
Node = {};
Node.ELEMENT_NODE = 1;
Node.TEXT_NODE = 3;
}
function gel(id)
{
return(document.getElementById(id));
}
Browser = {};
Browser.isIE = (navigator.userAgent.indexOf('MSIE') != -1);
Browser.isFirefox = (navigator.userAgent.indexOf('Firefox') != -1);
Browser.isSafari = (navigator.userAgent.indexOf('Safari') != -1);
Browser.isOpera = (navigator.userAgent.indexOf('Opera') != -1);
Browser.addEventListener = function(eventType, onWhom, callback) {
if (onWhom.addEventListener) {
onWhom.addEventListener(eventType, callback, false);
} else {
onWhom.attachEvent('on' + eventType, callback);
}
}
Browser.removeEventListener = function(eventType, onWhom, callback)
{
if (onWhom.removeEventListener) {
onWhom.removeEventListener(eventType, callback, false);
} else {
onWhom.detachEvent('on' + eventType, callback);
}
}
Browser.bind = function(callback, toWom)
{
var __method = callback;
return function() {
return __method.apply(toWom, arguments);
}
}
Browser.setAlpha = function(element, alpha)
{
if (Browser.isIE) {
element.style.filter="Alpha(Opacity=" + (parseFloat(alpha) * 100) + ")";
} else {
element.style.opacity = alpha;
}
}
Browser.cancelEvent = function(e)
{
if (e.stopPropagation) {
e.stopPropagation();
}
if (e.preventDefault) {
e.preventDefault();
}
e.cancelBubble = true;
e.cancel = true;
e.returnValue = false;
return(false);
}
Browser.fetchPreviousSibling = function(element, tagName)
{
if (! tagName) {
tagName = element.tagName;
}
element = element.previousSibling;
while (element && element.tagName != tagName) {
element = element.previousSibling;
}
return(element);
}
Browser.fetchNextSibling = function(element, tagName)
{
if (! tagName) {
tagName = element.tagName;
}
element = element.nextSibling;
while (element && element.tagName != tagName) {
element = element.nextSibling;
}
return(element);
}
Browser.fetchFirst = function(element, tagName)
{
if (element.childNodes) {
tagName = tagName.toUpperCase()
for (var i = 0; i < element.childNodes.length; i++) {
var child = element.childNodes.item(i);
if ((child.tagName && child.tagName.toUpperCase() == tagName) || (!tagName && (child.nodeType == Node.ELEMENT_NODE))) {
return(child);
}
}
}
return(null);
}
Browser.fetchChildren = function(element, tagName)
{
var result = new Array();
tagName = tagName.toUpperCase();
result.item = function(index) {
if (index >= 0 && index < this.length) {
return(this[index]);
}
throw 'Index out of bounds';
}
if (element.childNodes) {
for (var i = 0; i < element.childNodes.length; i++) {
var child = element.childNodes.item(i);
if (child.tagName && child.tagName.toUpperCase() == tagName) {
result.push(child);
}
}
}
return(result);
}
Browser.elementFromPoint = function(parent, x, y) {
if (document.elementFromPoint) {
var element = document.elementFromPoint(x, y);
if (element) {
return(element);
}
} else {
var startPos = dojo.coords(parent);
if ((x >= startPos.x && x <= (startPos.x + parent.offsetWidth)) &&
(y >= startPos.y && y < (startPos.y + parent.offsetHeight))) {
for (var i = 0; i < parent.childNodes.length; i++) {
var child = parent.childNodes.item(i);
if (child.nodeType == Node.ELEMENT_NODE) {
var result = this.elementFromPoint(child, x, y);
if (result) {
return(result);
}
}
}
return(parent);
}
}
return(false);
}
Browser.getScrollTop = function(){
return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
}
Browser.getScrollLeft = function(){
return window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0;
}
Browser.sumAncestorProperties = function(node, prop) {
if (!node) {
return 0;
} // FIXME: throw an error?
var retVal = 0;
while(node){
var val = node[prop];
if(val){
retVal += val - 0;
if(node==document.body){ break; }// opera and khtml #body & #html has the same values, we only need one value
}
node = node.parentNode;
}
return retVal;
}
Browser.getStyle = function(el, prop) {
if (document.defaultView && document.defaultView.getComputedStyle) {
return document.defaultView.getComputedStyle(el, null)[prop];
} else if (el.currentStyle) {
var value = el.currentStyle[prop];
if (typeof value == 'undefined') {
value = el.style[prop];
}
return(value);
} else {
return el.style[prop];
}
}
Browser.pixelValue = function(str)
{
if (typeof str == 'number') {
return(str);
}
if (!str) {
return(0);
}
var match = str.match(/(.*)(px|\%)?/);
if (match && match.length == 3)
return(parseFloat(match[1]));
return(0);
}
Browser.createElement = function(tagName, style, parent) {
var result = document.createElement(tagName);
if (style) {
result.className = style;
}
if (parent) {
parent.appendChild(result);
}
return(result);
}
Browser.evalScriptGlobal = function(script) {
// dojo.eval doesn't execute script in global scope on IE. Be aware that
// window.execScript doesn't return anything so really best just for
// declaring things like functions, etc
if (window.execScript) {
window.execScript(script);
return null;
}
return dojo.eval(script);
}
function _gel(id) {
return(document.getElementById(id));
}
/* styles for the banner and toolbar */
.banner {
background-image: url('../../images/OAAright.jpg');
background-repeat: repeat-x;
margin: 0;
height: 47px;
width: 100%;
}
.banner-left {
float: left;
height: 47px;
width: 320px;
}
.banner-right {
float: right;
height: 47px;
width: 1px;
}
.nomadToolbarIcon {
background-repeat: no-repeat;
width: 24px;
height: 24px;
text-align: center;
cursor: pointer;
}
.nomadIconHelp {
background-image: url('../../images/help.gif');
}
.nomadIconView {
background-image: url('../../images/view.gif');
}
.nomadIconImport {
background-image: url('../../images/plus-circle.png');
}
.nomadIconRefresh {
background-image: url('../../images/refreshResults.gif');
}
.nomadIconSearchResults {
background-image: url('../../images/plus-circle.png');
}
.nomadCheckedMenuItemIcon {
background-image: url('../../images/checkmark.gif');
}
.nomadIconNoBinding {
background-image: url('../../images/noBind.gif');
}
.nomadIconSingleBinding {
background-image: url('../../images/singleBind.gif');
}
.nomadIconMultipleBinding {
background-image: url('../../images/multipleBind.gif');
}
.nomadIconPreferences {
background-image: url('../../images/gear_small.gif');
}
.preEdit input {
font-style: italic;
font-weight: lighter;
color: #999;
}
input.postEdit {
font-style: normal;
font-weight: normal;
color: #000;
}
/* styles for palette*/
.nomadIconWidget {
background-image: url('../../images/component_18x18.gif');
}
.paletteItemIconRow {
display: none;
}
.dijitMenuItemHover .paletteItemIconRow {
display: table-row;
}
.dijitMenuItemHoverIE .paletteItemIconRow {
display: block;
}
.dijitMenuItemHover .nomadIconInfo {
background-image: url('../../images/info.png');
}
.dijitMenuItemHover .nomadIconRun {
background-image: url('../../images/run.gif');
}
.dijitMenuItemHover .nomadIconBookmark {
background-image: url('../../images/folder.gif');
}
.nomadPaletteItem {
cursor: default;
}
.dijitMenu .nomadInnerMenu {
border: 0;
}
.paletteScrollButton {
width: 100%;
height: 18px;
}
.nomadIconScrollUp {
background-image: url('../../images/uparrow.gif');
}
.nomadIconScrollDown {
background-image: url('../../images/downarrow.gif');
}
.dijitTitlePane.paletteNorgie {
min-width: 200px;
}
.dijitMenu .paletteNorgie .dijitTitlePaneTitle {
background-image: none;
background-color: #e0d138;
}
.nomadWiringOverlayCurrent {
background-color: purple;
}
.nomadWiringOverlayRecommended {
background-color: yellow;
}
.nomadWiringOverlayPossible {
/* background-color: gray;*/
}
.dijitTooltip .dijitTooltipContainer {
width: 200px;
}
#propertyDialog .gadgetBody {
border: none;
padding: 0;
height: auto;
}
.nomadErrorMsg {
color: red;
font-style: italic;
}
.tundra .nomadMultiSelect select, .tundra .nomadMultiSelect input {
/* use same layout styles for both major components of the multiselect control */
/*border: 1px solid #B3B3B3;*/
margin: 0em 0.1em;
}
/* dojo 1.1 doesn't necessarily gray text anymore due to '.dijitReset' rule it introduced */
input[disabled], textarea[disabled], option[disabled], optgroup[disabled], select[disabled] {
color: graytext;
}
/* dojo 1.1 is causing things in buttons to be vertical-align: middle which
* is bothering the arrows on our WiringDropDownButtons
*/
.wiringButton .dijitButtonNode * {
display: -moz-inline-box;
display: inline-block;
}
.wiringButton .dijitA11yDownArrow {
margin-left: 0.8em;
font-size: 0.75em;
color: #848484;
}
/* had to specify directly, IE wasn't inheriting well */
/*
#searchOptions.dijitMenuItemHover {
background-color: #f7f7f7;
color: #000;
}
*/
html body {
background:#DADADA;
margin:0px;
}
.properties {
display:none;
}
.gadgetBody {
position:relative;
padding:4px;
height:100%;
overflow:hidden;
background:white;
border-right: solid 1px #dfdfdf;
border-bottom: solid 1px #dfdfdf;
border-left: solid 1px #dfdfdf;
}
.gadgetFrame {
width:100%;
height:100%;
margin:0;
padding:0;
border:none;
}
.gadgetDragAvatar {
position:absolute;
display:none;
background:url(smallish_widget.png) center left no-repeat;
height:50px;
padding-left:54px;
overflow:hidden;
}
.gadgetDragAvatar div {
overflow:hidden;
background:#DADADA;
height:100%;
background:#fef49c;
border:1px solid #BCA902;
padding:2px;
font-size:14px;
}
.gadgetDropTarget {
position:absolute;
background:darkgray;
width:4px;
}
.gadgetContainer .gadgetBoxHeader {
height:4px;
background:#DAE6F6; /* url(corner_dg_TR.gif) 100% 0 no-repeat;*/
}
.gadgetContainer .gadgetBoxHeader div {
width:4px;
height:4px;
background:#DAE6F6; /* url(corner_dg_TL.gif) 0 0 no-repeat;*/
}
.gadgetContainer .gadgetBoxContent {
background:#DAE6F6;
padding:5px 9px 0px 9px;
}
.gadgetContainer .gadgetBoxFooter {
height:16px;
background:#DAE6F6; /* url(corner_db_BR16.png) 100% 0 no-repeat;*/
cursor:se-resize;
}
.gadgetContainer .gadgetBoxFooter div {
height:16px;
width:16px;
background:#DAE6F6; /* url(corner_dg_BL16.png) 0 0 no-repeat;*/
}
.gadgetHeader {
position:relative;
height:20px;
cursor:move;
background:#DADADA;
}
.absDeleteImg {
margin-top:3px;
margin-right:4px;
cursor:pointer;
float:right;
}
.absPropEditImg {
margin-top:2px;
margin-right:4px;
margin-left:4px;
cursor:pointer;
float:left;
}
.absSizerImg {
float:right;
cursor:se-resize;
}
.gadgetTitle {
overflow:hidden;
font-size:16px;
padding-top:2px;
white-space:nowrap;
}
dojo.declare("GadgetSite", null, {
constants : {
GADGET_CLASSNAMES: ['gadgetHeader', 'gadgetBody', 'gadgetTitle']
},
constructor: function(container, id, views) {
this.gadgetContainer = container;
this.widgetId = id;
this.views = views;
if (container) {
this.adopt(container);
}
if ( ! dijit.byId(id + "_propMenu") ) {
// Due to a bug in IE 6/7, we cannot call this method directly.
// This constructor is called from inline JavaScript and tries
// to manipulate the DOM of the containing element. On IE 6/7,
// this leads to a crash of the JS engine. In order to avoid
// that, we create the menu after the DOM has loaded.
dojo.addOnLoad(this, "createEditMenu");
}
},
/**
* create the edit menu for the widget attaching it to left click on
* the widget edit image in the widget header
*
*/
createEditMenu: function() {
dojo.require('dijit.Menu');
var targetImgNode = dojo.byId(this.widgetId+"_propMenuTarget");
if ( ! targetImgNode ) { // nothing to bind to...
return;
}
var widgetId = this.widgetId;
pMenu = new dijit.Menu(
{
targetNodeIds:[this.widgetId+"_propMenuTarget"],
id: this.widgetId + "_propMenu",
leftClickToOpen: true
});
pMenu.addChild(new dijit.MenuItem(
{
label: "Edit widget properties",
onClick: //dojo.hitch( window,
function(evt) {
mashupMaker.editGadget( widgetId );
}
//)
}));
pMenu.addChild(new dijit.MenuItem(
{
label: "Share widget",
onClick:
function(evt) {
mashupMaker.shareGadget( widgetId );
}
}));
// menu items for custom widget views go here
if ( typeof this.views == "object" ) {
var addedSeparator = false;
for ( var i = 0; i < this.views.length; i++ ) {
var viewName = this.views[i];
if ( viewName == 'default' ||
viewName == 'edit' ||
viewName == 'help' ) {
continue;
}
// Add an extra separator if there are any custom views
if ( ! addedSeparator ) {
pMenu.addChild(new dijit.MenuSeparator());
addedSeparator = true;
}
// Custom view names are QNames, which may consist of a
// prefix and a local part separated by a ':'. For the
// menu, we'll only display the local part.
var localPart = viewName.split(":").pop();
pMenu.addChild(new dijit.MenuItem(
{
label: localPart.charAt(0).toUpperCase() + localPart.substr(1),
onClick:
function(evt) {
var menuItem = dijit.getEnclosingWidget(evt.currentTarget);
mashupMaker.openGadgetView( widgetId, menuItem.fullQName );
},
fullQName: viewName
}));
}
}
pMenu.addChild(new dijit.MenuSeparator());
if ( this.views && dojo.indexOf(this.views, 'help') != -1 ) { // XXX this.views should exist; shouldn't need to check for it
pMenu.addChild(new dijit.MenuItem(
{
label: "Help",
onClick:
function(evt) {
mashupMaker.openGadgetView( widgetId, 'help' );
}
}));
} else {
pMenu.addChild(new dijit.MenuItem({label: "Help", disabled: true}));
}
pMenu.startup();
},
/**
* Return the body html element for this site. The body element actually contains the widget
*
* @return HTML Element
* @type {DOM}
*/
getBody: function() {
return(this.gadgetBody);
},
/**
* Return the containing html element for this site.
*
* @return HTML Element
* @type {DOM}
*/
getContainer: function() {
return(this.gadgetContainer);
},
/**
* Set the title of the gadget within the site.
*
* @param {String} title The title to be displayed
*/
setTitle : function(title) {
this._titleText = title;
var titleTarget = this.getTitleElement();
if (titleTarget) {
titleTarget.innerHTML = title;
}
},
/**
* Get the title of the gadget within the site.
*
* @return The title to be displayed
* @type {String}
*/
getTitle : function() {
var titleTarget = this.getTitleElement();
if (! this._titleText && titleTarget) {
this._titleText = titleTarget.innerHTML;
}
return(this._titleText);
},
getTitleElement : function() {
return(this.gadgetTitle);
},
/**
* @param {DOM} containerDOM The DOM element to adopt as the container
*/
adopt: function(containerDOM) {
if (containerDOM.tagName != 'TABLE' || containerDOM.className != 'gadgetContainer') {
return(false);
}
this.gadgetContainer = containerDOM;
dojo.forEach(this.constants.GADGET_CLASSNAMES,
dojo.hitch(this,
function(className) {
var gadgetPart = dojo.query('.' + className, containerDOM);
if (gadgetPart && gadgetPart.length) {
this[className] = gadgetPart[0];
}
})
);
},
getDimensions : function() {
var dimensions = {};
var style = dojo.getComputedStyle( this.gadgetBody );
dimensions.width = parseInt( style.width );
dimensions.height = parseInt( style.height );
return dimensions;
},
getPosition : function() {
var coords = dojo.coords( this.gadgetContainer );
return { x: coords.x, y: coords.y };
},
resize : function( width, height ) {
this.gadgetBody.style.width = width + "px";
this.gadgetBody.style.height = height + "px";
// XXX 'gadgetHeader' sizing should be done in theme specific file
if ( this.gadgetHeader ) {
this.gadgetHeader.style.width = dojo.coords( this.gadgetBody ).w + "px";
}
}
});
This source diff could not be displayed because it is too large. You can view the blob instead.
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2006-2008 OpenAjax Alliance
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.
-->
<widget name="ColorPalette" id="http://openajax.org/samples/widgets/colorpalette"
spec="1.0" width="208" height="148"
xmlns="http://openajax.org/metadata">
<requires>
<library name="dojo" version="1.3" src="http://ajax.googleapis.com/ajax/libs/dojo/1.3/">
<preload>
djConfig = { isDebug: false, parseOnLoad: false, afterOnLoad: true };
</preload>
<require type="javascript" src="dojo/dojo.xd.js"/>
<require type="css" src="dojo/resources/dojo.css"/>
<require type="css" src="dijit/themes/dijit.css"/>
<require type="css" src="dijit/themes/dijit_rtl.css"/>
<require type="css" src="dijit/themes/tundra/ColorPalette.css"/>
</library>
</requires>
<properties>
<property name='color' datatype='String' defaultValue="#ffffff" sharedAs='color'/>
<!-- publish='true' -->
</properties>
<javascript location='afterContent'>
dojo.require("dijit.ColorPalette");
dojo.addOnLoad(function(){
new dijit.ColorPalette(
{ "class": "tundra",
onChange: function( color ) {
OpenAjax.widget.byId('__WID__').OpenAjax.setPropertyValue( 'color', color );
}
},
"__WID__palette"
);
});
</javascript>
<content>
<![CDATA[
<div class="tundra" style="background-color:#f5f5f5" >
<span ID='__WID__palette' dojoType="dijit.ColorPalette">
</span>
</div>
]]>
</content>
</widget>
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2006-2008 OpenAjax Alliance
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.
-->
<widget name="DateListener" id="http://openajax.org/samples/widgets/DateListener"
spec="1.0" version='0.9' width="300" height="24" sandbox="true"
xmlns="http://openajax.org/metadata">
<description>This is a test widget that tests the UI representation of various parts of the OAA spec</description>
<properties>
<property name="date" datatype="Date" defaultValue="" sharedAs="date"></property>
<!--
<property name="subscribedDate" datatype="Date" defaultValue="" readonly="false" sharedAs="date">
// subscribe="true"
<description>last date published by another gadget</description>
</property>
<property name="publishedDate" datatype="String" defaultValue="" readonly="false" hidden="false" sharedAs="datestring">
// publish="true"
<description>when this value is changed, this date will be published to other gadgets</description>
</property>
<property name="hiddenDate" datatype="String" defaultValue="" readonly="false" hidden="true" sharedAs="datestring">
// publish="true"
<description>when this value is changed, this date will be published to other gadgets</description>
</property>
-->
</properties>
<content>
<![CDATA[
<script>
var w__WID__ = OpenAjax.widget.byId("__WID__");
w__WID__.onLoad = function() {
document.getElementById("__WID__date").value = this.OpenAjax.getPropertyValue( "date" );
};
w__WID__.handleClick = function() {
// alert("click!");
};
w__WID__.handleChange = function() {
this.OpenAjax.setPropertyValue( "date", document.getElementById("__WID__date").value );
};
w__WID__.onChangeDate = function( event ) {
document.getElementById("__WID__date").value = event.newValue;
};
</script>
<label id="__WID__date_label" for="__WID__date" style="border: 1px solid red;">date last broadcast: </label>
<input id="__WID__date" name="__WID__date" onclick="OpenAjax.widget.byId('__WID__').handleClick();" onChange="OpenAjax.widget.byId('__WID__').handleChange();"/>
]]>
</content>
</widget>
/*
Copyright 2006-2008 OpenAjax Alliance
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.
*/
var toolbarInit;
var retrieveMyStuff;
var searchRepositories;
(function(){
toolbarInit = function()
{
dojo.require("dojo.parser");
dojo.require("dojo.data.ItemFileReadStore");
dojo.require("dijit.form.Button");
dojo.require("dijit.form.FilteringSelect");
dojo.require("dijit.form.TextBox");
dojo.require("dijit.form.ValidationTextBox");
dojo.require("dijit.Menu");
dojo.require("dijit.Toolbar");
dojo.require("dijit.TitlePane");
// dojo.registerModulePath("nomad", "../../../nomad");
dojo.require("nomad.widget.PaletteMenu");
dojo.require("nomad.widget.CheckmarkMenuItem");
dojo.addOnLoad(function(){
var toolbar = dojo.byId('mashupToolbarContainer');
dojo.parser.parse(toolbar);
// toolbarOffsetY = toolbar.offsetTop + toolbar.offsetHeight + 10;
// disable the "Save" and "Share" menu items if we didn't load from a page
if ( mashupMaker.pageName == '' ) {
dijit.byId( 'save' ).setDisabled( true );
dijit.byId( 'share' ).setDisabled( true );
}
var searchDropDown = dojo.byId( 'searchResults' );
dojo.connect(searchDropDown, 'onclick', null, populateDropDown);
});
}
var __firstSearch = true;
function populateDropDown() {
if ( __firstSearch ) {
window.retrieveMyStuff();
__firstSearch = false;
}
}
retrieveMyStuff = function() {
var resourceUri = 'gadgets.html';
var bindArgs = {
handleAs: 'json',
url: resourceUri,
sync: false,
load: function(response) {
buildPaletteFromSearchResults(response);
},
error: function(error, request) {
alert('Search Error: \n'+error);
}
};
dojo.xhrGet(bindArgs);
}
searchRepositories = function(event) {
if (event.keyCode != 13 && event.type != 'click') {
return;
}
var searchTerms = dojo.byId('widgetSearchTerms').value;
if ( searchTerms == "Enter widget search terms" ) {
searchTerms = "";
}
var resourceUri = 'gadgets.html?terms='+searchTerms;
var advancedSearch = dijit.byId('searchResultsMenu_searchOptions');
var selectedRepositories = advancedSearch.getSelectedRepositories();
for (var i = 0; i < selectedRepositories.length; i++) {
resourceUri += "&repos[]=" + encodeURIComponent(selectedRepositories[i]);
}
var bindArgs = {
handleAs: 'json',
url: resourceUri,
sync: true,
load: function(response) {
buildPaletteFromSearchResults(response);
},
error: function(error, request) {
alert('Search Error: \n'+error);
}
};
dojo.xhrGet(bindArgs);
}
function buildPaletteFromSearchResults(response) {
var temp = '';
var searchResults = dojo.byId('searchResultsMenu');
var searchResultsWidget = dijit.byId('searchResultsMenu');
// remove all of the previous search results
dojo.query('*[id^="searchResults_foundWidget"]').forEach(
function(itemFromMenu) {
var widget = dijit.byId(itemFromMenu.id);
if (widget) {
searchResultsWidget.removeChild(widget);
widget.destroyRecursive();
}
}
);
dojo.query('*[id^="searchResults_menuSeparator"]').forEach(
function(itemFromMenu) {
var widget = dijit.byId(itemFromMenu.id);
if (widget) {
searchResultsWidget.removeChild(widget);
widget.destroyRecursive();
}
}
);
var tooltipConnectIds = [];
for (var i = 0; i < response.length; i++) {
var opt = response[i];
var labelstr, url;
// insert the menu separator first
var menuItemWidget = new dijit.MenuSeparator({
id: 'searchResults_menuSeparator'+i
});
searchResultsWidget.addChild(menuItemWidget);
url = opt.url;
labelstr = opt.name;
// insert the menuitem for the gadget
/* XXX probably need to be able to insert the widget's icon
* if it has one rather than using nomadIconWidget iconclass
* I put a method called setImage on the PaletteMenuItem class
* to handle this once we know how to get the image src path
* out of the repository. Similar thing should be done for
* the description, too.
*/
menuItemWidget = new nomad.widget.PaletteItem({
id: 'searchResults_foundWidget'+i,
label: labelstr,
iconClass: "nomadToolbarIcon nomadIconWidget",
runIconClass: "nomadToolbarIcon nomadIconRun",
bookmarkIconClass: "nomadToolbarIcon nomadIconBookmark",
infoIconClass: "nomadToolbarIcon nomadIconInfo",
itemUrl: url,
onClick: function() {
mashupMaker.createGadget(this.itemUrl, null);
// XXX JHP TODO
// var gadgetCoords = dojo.coords(mashupMaker._creatingGadget.frame, true);
// window.scrollTo(gadgetCoords.x, gadgetCoords.y);
}
});
searchResultsWidget.addChild(menuItemWidget);
var tooltip = new dijit.Tooltip({
label: "test info message",
connectId: [menuItemWidget.id+"_infoIcon"]
});
// we are going to connect a tooltip to the 'more info' icons
// on each palette item so build a list of the ids to use
tooltipConnectIds.push(menuItemWidget.id+"_infoIcon");
}
var tooltip = new nomad.widget.PaletteTooltip({
label: "",
connectId: tooltipConnectIds
});
searchResultsWidget.setTooltip(tooltip);
searchResultsWidget.resizePalette();
if (searchResultsWidget.domNode.style.visibility == 'hidden') {
// menu tries to blur its focused child but since we may have just
// destroyed it in our clean up of menu items, we should null it out
// to be safe
dijit.byId('searchResults')._toggleDropDown();
} else {
searchResultsWidget.focusFirstItem();
}
}
})(); // end closure
openMashup = function() {
var dialogDiv = dojo.byId('__OAA_openDialog_container');
dojo.parser.parse(dialogDiv);
openDialog = dijit.byId('__OAA_open_dialog');
openDialog.show();
}
doOpen = function(mashupname) {
var url = OpenAjax.widget.baseURI + 'newmashup.php?pageName=' + mashupname;
window.document.location = url;
}
saveMashup = function() {
doSave(mashupMaker.pageName);
}
saveMashupAs = function() {
var dialogDiv = dojo.byId('__OAA_saveAsDialog_container');
dojo.parser.parse(dialogDiv);
saveAsDialog = dijit.byId('__OAA_saveAs_dialog');
saveAsDialog.show();
}
doSave = function(pagename) {
var resourceUri = 'doSave.php';
var serializedPage = mashupMaker.exportElement().innerHTML;
var bindArgs = {
handleAs: 'text',
url: resourceUri,
sync: true,
content: { page : pagename, data : serializedPage },
load: function(response) {
var select = dijit.byId( "openname" );
if ( select ) {
// Since 'clearOnClose' is set to true on the data store,
// calling close() will cause the data store to refetch
// from the server next time.
select.store.close();
}
},
error: function(error, request) {
alert('Save error: \n'+error);
}
};
dojo.xhrPost(bindArgs);
}
shareMashup = function() {
var script = "&lt;script src=\"" + OpenAjax.widget.baseURI + "embedWidget.php?pageName=" + mashupMaker.pageName + "\"&gt;&lt;/script&gt;";
var dialogDiv = dojo.byId('__OAA_shareDialog_container');
dojo.parser.parse(dialogDiv);
dojo.byId("__OAA_shareDialog_script_tag").innerHTML = script;
dijit.byId("__OAA_share_dialog").show();
return;
}
function resetTextfield() {
var textbox = dijit.byId('widgetSearchTerms');
var textboxNode = dojo.byId('widgetSearchTerms');
textbox.setValue("");
textboxNode.className = 'postEdit';
}
function launchAboutRefimpl( event ) {
window.open( OpenAjax.widget.baseURI + 'README.html', 'aboutRefimpl',
'width=800,height=570,resizable=yes,scrollbars=yes,toolbar=no,menubar=yes' );
dojo.stopEvent( event );
return false;
}
function launchGeneralHelp( event ) {
window.open( OpenAjax.widget.baseURI + 'HELP.html', 'generalHelp',
'width=900,height=800,resizable=yes,scrollbars=yes,toolbar=no,menubar=yes' );
dojo.stopEvent( event );
return false;
}
function launchGeneralHelp2( event ) {
window.open( OpenAjax.widget.baseURI + 'HELP2.html', 'generalHelp2',
'width=900,height=800,resizable=yes,scrollbars=yes,toolbar=no,menubar=yes' );
dojo.stopEvent( event );
return false;
}
function launchParticipationHelp( event ) {
window.open( OpenAjax.widget.baseURI + 'PARTICIPATE.html', 'participationHelp',
'width=900,height=800,resizable=yes,scrollbars=yes,toolbar=no,menubar=yes' );
dojo.stopEvent( event );
return false;
}
function launchMetadataIntro( event ) {
window.open( OpenAjax.widget.baseURI + 'METADATAINTRO.html', 'metadataIntroHelp',
'width=900,height=800,resizable=yes,scrollbars=yes,toolbar=no,menubar=yes' );
dojo.stopEvent( event );
return false;
}
function launchHubIntro( event ) {
window.open( OpenAjax.widget.baseURI + 'HUBINTRO.html', 'hubIntroHelp',
'width=900,height=800,resizable=yes,scrollbars=yes,toolbar=no,menubar=yes' );
dojo.stopEvent( event );
return false;
}
function launchOpenBug( event ) {
window.open( 'http://sourceforge.net/tracker/?atid=874168&group_id=175671', 'openBug',
'width=900,height=800,resizable=yes,scrollbars=yes,toolbar=no' );
dojo.stopEvent( event );
return false;
}
function launchRepository( event ) {
var sarray = OpenAjax.widget.baseURI.split("/");
var current = sarray.pop();
while ( current != "gadgets" ) {
current = sarray.pop();
}
var baseURL = sarray.join("/");
window.open( baseURL + '/repository/', 'launchRepository',
'resizable=yes,scrollbars=yes,toolbar=no' );
dojo.stopEvent( event );
return false;
}
function newMashup( event ) {
window.document.location = OpenAjax.widget.baseURI + "newmashup.php";
dojo.stopEvent( event );
return false;
}
// var toolbarOffsetY = 0;
toolbarInit();
/*
Copyright 2006-2009 OpenAjax Alliance
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.
*/
// XXX
if ( typeof console === "undefined" ) {
console = {};
}
if ( typeof console.log === "undefined" ) {
console.log = function() {};
}
if ( typeof OpenAjax !== "undefined" ) {
OpenAjax.hub.registerLibrary("OpenAjax.widget", "http://openajax.org/widget", "0.3", {});
// XXX make this a part of Loader params?
/*=====
oaaLoaderConfig = {
// proxy: String
// URL of proxy which allows cross-domain calls. The proxy must take
// a URL parameter "oawu", which is the requested URL.
proxy: undefined
}
=====*/
if ( ! OpenAjax.widget ) {
/**
* @namespace
*/
OpenAjax.widget = {};
}
(function() {
/**
* @class
* <p> Widget loader. </p>
*
* @description
* Creates a new Loader instance.
*
* @param {Object} args
* Parameters used to instantiate the Loader. This object may contain the
* following properties:
* @param {Object} args.ManagedHub
* Parameters for creating a ManagedHub instance.
*/
OpenAjax.widget.Loader = function( args )
{
var onsub = args.ManagedHub.onSubscribe;
var onpub = args.ManagedHub.onPublish;
var onunsub = args.ManagedHub.onUnsubscribe;
var scope = args.scope || window;
function _onSubscribe( topic, container )
{
return onsub.apply( scope, arguments );
}
function _onPublish( topic, data, pcont, scont )
{
return onpub.apply( scope, arguments );
}
function _onUnsubscribe( topic, container )
{
if ( onunsub ) {
return onunsub.apply( scope, arguments );
}
}
this.hub = new OpenAjax.hub.ManagedHub({
onPublish: _onPublish,
onSubscribe: _onSubscribe,
onUnsubscribe: _onUnsubscribe,
scope: scope,
log: args.logs
});
_hub = this.hub;
_metadataCache = {};
// Find location of OpenAjax Hub files, so that we can generate 'tunnel.html'
// location.
_tunnelURI = null;
_loaderRoot = null;
var scripts = document.getElementsByTagName("script");
// match "OpenAjax-mashup.js", "OpenAjaxManagedHub-std.js", "OpenAjaxManagedHub-core.js", or "OpenAjaxManagedHub-all.js"
var reHub = /openajax(?:ManagedHub-.+|-mashup)\.js$/i;
var reLoader = /loader\.js$/i;
for ( var i = 0; (_tunnelURI === null || _loaderRoot === null) && i < scripts.length; i++ ) {
var src = scripts[i].src;
if ( src ) {
var m;
if ( _tunnelURI === null ) {
if ( reHub.test( src ) ) {
// make URL absolute
src = /^\w+:/.test( src ) ? src : scripts[i].getAttribute('src', -1);
_hubBaseJS = src;
m = src.match( reHub );
var hubRoot = src.substring( 0, m.index );
if ( /openajax-mashup\.js/i.test( m[0] ) ) {
_tunnelURI = hubRoot + "containers/iframe/tunnel.html";
} else {
_tunnelURI = hubRoot + "tunnel.html";
}
}
}
if ( _loaderRoot === null ) {
if ( reLoader.test( src ) ) {
// make URL absolute
src = /^\w+:/.test( src ) ? src : scripts[i].getAttribute('src', -1);
m = src.match( reLoader );
_loaderRoot = src.substring( 0, m.index );
}
}
}
}
// parse oaaLoaderConfig object
if ( typeof oaaLoaderConfig !== "undefined" ) {
// Save proxy URL, if specified
if ( oaaLoaderConfig.proxy ) {
_proxyURL = oaaLoaderConfig.proxy;
if ( _proxyURL.charAt(0) !== "/" && ! /^\w+:/.test( _proxyURL ) ) {
// make absolute
_proxyURL = window.location.protocol + "//" + window.location.host +
/^(\/(.*\/)*)/.exec( window.location.pathname )[1] + _proxyURL;
}
}
}
_metadata_plurals = {
authors: "author",
categories: "category",
configs: "config",
contents: "content",
enums: "enum",
examples: "example",
icons: "icon",
javascripts: "javascript",
libraries: "library",
options: "option",
properties: "property",
references: "reference",
requires: "require",
topics: "topic"
};
};
/**
* Load and parse a widget specification.
*
* @param {Object} args
* Parameters used to load widget metadata. This object may contain the
* following properties:
* @param {String} args.url
* The URL of the widget specification.
* @param {String} [args.locale]
* The locale to be used when doing localization substitutions. If not
* specified, defaults to the browser locale.
* @param {Object} [args.properties]
* Initial widget properties. This is an object of name-value pairs, where
* the name matches a property name as defined in the widget metadata. This
* object is used when doing property value substitutions. If this
* parameter is null, or if a given property name is not specified, then
* the substitution code will use the property's default value.
* @param {Function} args.onComplete
* Callback which is invoked if loading the URL was successful,
* onSuccess(metadata).
* @param {Function} args.onError
* Callback which is invoked if an error occurs, onError(error).
*
* @returns {Object}
* An object whose properties represent the widget elements.
*/
OpenAjax.widget.Loader.prototype.loadMetadata = function( args )
{
// XXX TODO properly handle plural elements
// make sure the URL is absolute
// XXX need a better method of doing this
var a = document.createElement('a');
a.href = args.url;
var url = a.href;
var cache = _metadataCache;
var locale = null;
// Return the attributes of the given DOM element as an object of
// name-value pairs.
var _getAttrs = function(elem) {
var map = {}, domAttrs = elem.attributes;
for(var i = 0; i < domAttrs.length; i++) {
map[domAttrs[i].name] = domAttrs[i].value;
}
return map;
};
// Return the text content of the given DOM element.
var _innerText = function( node ) {
var text = node.innerText || node.textContent;
if ( typeof text === "undefined" ) {
text = "";
var children = node.childNodes;
if (children) {
for (var i = 0; i < children.length; i++) {
var n = children.item(i);
if (n.nodeType == 3 || n.nodeType == 4) { // text or CDATA
text += n.nodeValue;
}
}
}
}
return text;
};
// Compute the appropriate text content of the give DOM element, doing
// any necessary substitutions.
var _getContentText = function( node, item ) {
function escapequotes( text ) {
return text.replace(/'/g, "\\'").replace(/"/g, '\\"');
}
function entityencode( text ) {
return text.replace(/&/gm, "&amp;").replace(/</gm, "&lt;")
.replace(/>/gm, "&gt;").replace(/"/gm, "&quot;")
.replace(/'/gm, "&#39;");
}
var text = null;
if ( item.locid && locale ) {
text = locale.getMsg( item.locid );
}
if ( ! text ) {
text = _innerText( node );
}
if ( text ) {
item._content_ = text.replace( /##(\w+)(?:\((\w+)\))?##/g,
function( str, p1, p2 ) {
var key = p2 || p1;
var newText = locale.getMsg( key );
if ( newText ) {
if ( p2 ) {
switch ( p1 ) {
case "escapequotes":
newText = escapequotes( newText );
break;
case "entityencode":
newText = entityencode( newText );
break;
}
}
return newText;
}
return str;
}
).replace( /__BIDI_\w+__/g,
function( str ) {
switch ( str ) {
case "__BIDI_START_EDGE__":
if ( locale.language_direction === "ltr" ) {
return "left";
}
return "right";
case "__BIDI_END_EDGE__":
if ( locale.language_direction === "ltr" ) {
return "right";
}
return "left";
case "__BIDI_DIR__":
return locale.language_direction;
case "__BIDI_REVERSE_DIR__":
if ( locale.language_direction === "ltr" ) {
return "rtl";
}
return "ltr";
}
return str;
}
);
}
};
// Find which of the widget's message bundles to use for widget
// localization. Generates the "locale" object.
var _getLocale = function( metadata, onComplete ) {
// find the client's locale
var userLocale = (args.locale ? args.locale :
(navigator.language ? navigator.language : navigator.userLanguage)).toLowerCase();
// find the closest widget locale match to the client's locale
var localeNode = null;
do {
if ( metadata.locale[ userLocale ] ) {
localeNode = metadata.locale[ userLocale ];
break;
}
var idx = userLocale.lastIndexOf( "-" );
if ( idx === -1 ) {
break;
}
userLocale = userLocale.slice( 0, idx );
} while(1);
// no appropriate message bundle was found for the user's locale -- try
// the fallback message bundle
if ( ! localeNode ) {
if ( metadata.locale.ALL ) {
localeNode = metadata.locale.ALL;
} else {
// no appropriate message bundle found; don't create 'locale' object
onComplete();
return;
}
}
// retrieve the message bundle file contents
var u = _getProxyUrl( _resolveURI( localeNode.messages, metadata._src_ ) );
OpenAjax.widget._xhrGet( u, true,
/* onComplete */
function( dom ) {
var messages = {};
var bundle = dom.getElementsByTagName( "messagebundle" ).item(0);
var msg = bundle.getElementsByTagName( "msg" );
for ( var j = 0; j < msg.length; j++ ) {
var name = msg[j].getAttribute( "name" );
var text = _innerText( msg[j] );
messages[ name ] = text;
}
// create locale object, in context of loadMetadata()
locale = {
lang: localeNode.lang,
language_direction: localeNode.language_direction ? localeNode.language_direction : "ltr",
_messages: messages,
getMsg: function( name ) {
return this._messages[ name ];
}
};
onComplete();
},
/* onError */
function( error ) {
args.onError( "Failed to retrieve message bundle file -- " +
"url: " + u + " error: " + error );
}
);
};
// Parse the given DOM element's children and add data to the "parentObj"
// object.
var _parseElement = function( element, parentObj, grandparentObj ) {
if ( element.childNodes.length === 0 ) {
return true; // return true in order to circumvent _innerText() call
}
var elemName = element.tagName.toLowerCase();
var hasNonTextContent = false;
for ( var i = 0; i < element.childNodes.length; i++ ) {
var node = element.childNodes.item( i );
// for text nodes, "node.tagName" is undefined
if ( ! node.tagName ) {
continue;
}
var tagName = node.tagName.toLowerCase();
if ( tagName in _metadata_plurals ) {
// Ignore plural elements, but loop over their child nodes.
_parseElement( node, parentObj, parentObj );
} else {
// If the parent of this element is a plural element, then we
// only want to deal with the singular version of that parent,
// and ignore all other elements. For example, for the parent
// <properties>, we only want to handle any child <property>
// elements, while ignoring others such as <description>.
// The only exception is the <library> element, which can be a
// child of <requires>.
if ( (elemName in _metadata_plurals) &&
tagName !== _metadata_plurals[ elemName ] &&
tagName !== "library" )
{
continue;
}
// <locale> and <property> have already been handled
if ( tagName === "locale" || tagName === "property" ) {
continue;
}
hasNonTextContent = true;
// get the attributes for this element
var item = _getAttrs( node );
// handle any special cases
var attrs,
obj = parentObj;
switch ( tagName ) {
case "option":
// If <option> is a child of <options>, save the
// 'multiple' and 'unconstrained' attributes if they
// exist on the parent.
if ( elemName === "options" ) {
attrs = _getAttrs( node.parentNode );
if ( attrs.multiple ) {
item._multiple_ = attrs.multiple;
}
if ( attrs.unconstrained ) {
item._unconstrained_ = attrs.unconstrained;
}
}
break;
case "require":
// If a <require> element is a child of a <library>
// element, then we save the name of the library to
// which that require belongs.
if ( elemName === "library" ) {
obj = grandparentObj;
item._library_ = _getAttrs( node.parentNode ).name;
}
break;
}
// Add this element info to the object. If this element has a
// 'name' attribute, then we index using that.
var name = item.name;
if ( name ) {
if ( ! obj[ tagName ] ) {
obj[ tagName ] = {};
}
delete item.name;
obj[ tagName ][ name ] = item;
} else {
if ( ! obj[ tagName ] ) {
obj[ tagName ] = [];
}
obj[ tagName ].push( item );
}
// See if this element has any child nodes that need to be
// handled. If not, then see if it has any text content.
if ( ! _parseElement( node, item, parentObj ) ) {
_getContentText( node, item );
}
}
}
return hasNonTextContent;
};
// Convert the widget's DOM into a JavaScript object.
var _transformXML = function( dom ) {
var widget = dom.getElementsByTagName('widget').item(0);
var metadata = _getAttrs(widget);
// save location of widget metadata file
metadata._src_ = url;
// In order to handle substitutions, we need to first parse the 'locale'
// and 'property' elements.
var locales = widget.getElementsByTagName( "locale" );
if ( locales.length > 0 ) {
metadata.locale = {};
for ( var i = 0; i < locales.length; i++ ) {
var item = _getAttrs( locales[i] );
var lang = item.lang ? item.lang.toLowerCase() : null;
if ( ! lang ) {
// If <locale> has no "lang" attribute, then this is the
// fallback message bundle. We only allow one, so just
// take the first one.
if ( ! metadata.locale.ALL ) {
metadata.locale.ALL = item;
}
} else {
delete item.lang;
metadata.locale[ lang ] = item;
}
}
// given these locale elements, find the user's locale
_getLocale( metadata, finish );
} else {
finish();
}
function finish() {
// Now that we've handled <locale>, we move on to <property>
var properties = widget.getElementsByTagName( "property" );
if ( properties.length > 0 ) {
metadata.property = {};
for ( var i = 0; i < properties.length; i++ ) {
var item = _getAttrs( properties[i] );
var name = item.name;
delete item.name;
metadata.property[ name ] = item;
// If <property> is a child of <properties>, save the
// 'name' and 'managed' attributes if they exist on
// the parent.
if ( properties[i].parentNode.tagName.toLowerCase() === "properties" ) {
attrs = _getAttrs( properties[i].parentNode );
if ( attrs.name ) {
item._group_ = attrs.name;
}
if ( attrs.managed ) {
item._managed_ = attrs.managed;
}
}
// see if it has any child elements
_parseElement( properties[i], item, null );
}
}
// parse the rest of the widget DOM
_parseElement( widget, metadata, metadata );
if ( cache ) {
cache[url] = metadata;
}
args.onComplete( metadata );
}
};
if (cache && cache[url]) {
args.onComplete(cache[url]);
} else {
// If a proxy has been defined, then we use it in order to get widget
// definitions that live on other domains.
var u = _getProxyUrl( url );
OpenAjax.widget._xhrGet( u, true, _transformXML, args.onError);
}
};
/**
* Creates an instance of a widget and adds it to the page, loading resources as
* needed.
*
* @param {Object} args
* Parameters used to create a widget. This object may contain the
* following properties:
* @param {Object | String} args.spec
* A widget spec object (as returned by
* {@link OpenAjax.widget.Loader#loadMetadata}) or a URL pointing to the
* widget specification.
* @param {HTMLElement | String} args.target
* HTML DOM element or ID of element in which this widget is rendered.
* @param {String} [args.id]
* A string identifier for this widget. If null, one is generated.
* @param {String} [args.mode]
* Initial mode of rendered widget. If not specified, defaults to "view".
* @param {String} [args.locale]
* The locale to be used when doing localization substitutions. If not
* specified, defaults to the browser locale.
* @param {Object} [args.properties]
* Initial widget properties. This is an object of name-value pairs, where
* the name matches a property name as defined in the widget metadata.
* @param {Object} [args.availableDimensions]
* The maximum size to which a widget can be sized inside of this mashup
* framework. This object has "width" and "height" properties. If either
* is omitted, then that dimension is considered unconstrained.
* @param {Function} [args.adjustDimensions]
* A callback which gets invoked whenever a widget requests a resize. This
* function takes two parameters: a widget ID and a dimensions object, with
* "width" and "height" properties. If the callback returns null, the
* resize request is ignored. Otherwise, the callback should return a
* dimensions object with dimensions less than or equal to those passed in,
* in order to size the widget to those dimensions.
* @param {Boolean} [args.sandbox]
* If true, will load the widget as sandboxed (in an iframe), regardless
* of what the "sandbox" attribute is set to in the widget spec. If false
* or unspecified, then we honor the "sandbox" attribute as specified in
* the widget spec.
* @param {Function} args.onComplete
* Callback which is invoked if loading the URL was successful,
* onSuccess(metadata).
* @param {Function} args.onError
* Callback which is invoked if an error occurs, onError(error).
*
* @returns {Object}
* Widget instance object.
*/
OpenAjax.widget.Loader.prototype.create = function(args)
{
var spec = args.spec;
if(typeof spec == "string") {
var that = this;
return this.loadMetadata({
url: spec,
locale: args.locale,
properties: args.properties,
onComplete: function(metadata) {
args.spec = metadata;
that.create(args);
},
onError: onError
});
}
var target = args.target,
view = args.view,
properties = args.properties || {},
availDimensions = {},
onComplete = args.onComplete,
onError = args.onError || function(e) { console.error(e); };
if ( typeof target == "string" ) {
target = document.getElementById( target );
}
// XXX widget ID should be randomized
var wid = args.id;
if ( ! wid ) {
while ((wid = "OAA" + ++_uniqueId) && document.getElementById(wid));
}
if ( args.availableDimensions ) {
if ( args.availableDimensions.width ) {
availDimensions.width = args.availableDimensions.width;
}
if ( args.availableDimensions.height ) {
availDimensions.height = args.availableDimensions.height;
}
}
var oaa;
if ( spec.sandbox || args.sandbox ) {
oaa = new ProxyWidget();
} else {
oaa = new BaseWidget();
}
oaa._init({
spec: spec,
id: wid,
props: properties,
root: target,
availDimensions: availDimensions,
adjustDimensions: args.adjustDimensions
});
oaa._render( target, view, onComplete );
// XXX How does the mashup developer get access to the widget object (or even
// the widget ID)? create() doesn't return anything. But there is the
// byId() method. So either have create() return the generated widget ID,
// so that the dev can get the widget object by calling byId(), or have
// create() return the widget object itself.
return oaa._widget;
};
/**
* Updates the available dimensions for the given widget.
*
* @param {Object | String} widget
* Widget instance object or ID.
* @param {Object} availDimensions
* The new available dimensions. The object may have properties of "width"
* and "height". If either property is undefined, then that dimensions is
* considered to be unconstrained.
*/
OpenAjax.widget.Loader.prototype.setAvailableDimensions = function( widget, availDimensions )
{
if ( typeof widget == "String" ) {
widget = OpenAjax.widget.byId( widget );
}
widget.OpenAjax._setAvailableDimensions( availDimensions );
};
/**
* Returns a widget instance for the given ID.
*
* @param {Object} id
* Requested widget ID.
*/
OpenAjax.widget.byId = function( id )
{
return _widgetTable[id];
};
//*************** private functions and variables ***************//
// ManagedHub instance
var _hub;
// optional cache object. hide in closure? expose remove?
var _metadataCache;
// shared counter to be used for __WID__ substitution
var _uniqueId = 0;
// lookup table for widgets
var _widgetTable = {};
// hash to keep track of loaded requires
//var _requiresCache = {};
// absolute URL of main OpenAjax Hub JS file
var _hubBaseJS;
// absolute URL of OpenAjax Hub tunnel.html
var _tunnelURI;
// base URL for this JS file
var _loaderRoot;
// arrays of element names; used by loadMetadata() to parse widget XML file
var _metadata_plurals;
// URL of proxy, if specified
var _proxyURL = null;
/**
* Returns the proxied version of the given URL. If a proxy was never
* specified, or if the given URL is in the same origin, then the URL is
* returned unchanged.
*
* @param {String} url
* @returns {String}
* Proxied version of URL
*/
function _getProxyUrl( url ) {
if ( _proxyURL ) {
if ( url.match( /^(\w+:\/\/[^\/?#]+).*/ )[1] ===
(window.location.protocol + "//" + window.location.host) ) {
// no need to use proxy if 'url' is in same origin
return url;
}
return _proxyURL + "?oawu=" + encodeURIComponent( url );
}
return url;
}
/**
* Returns an absolute URI version of the passed-in uri. URI is resolved
* against the widget metadata location.
*
* @param {Object} uri
* URI to resolve
* @param {Object} specURI
* URI of widget metadata file
*/
function _resolveURI( uri, specURI )
{
// if absolute URI, return
// XXX save off RegExp object, rather than creating anew every time?
if ( /^\w+:\/\/.+/.exec( uri ) !== null ) {
return uri;
}
if ( uri.charAt(0) !== "/" ) {
return specURI.slice( 0, specURI.lastIndexOf("/") + 1 ) + uri;
}
return (/^(\w+:\/\/[^\/]+).*/.exec( specURI ))[1] + uri;
}
var _head = document.getElementsByTagName('HEAD').item(0);
if (!_head) {
_head = document.createElement('HEAD');
document.body.insertBefore(_head, document.body.childNodes.item(0));
}
////////////////////////////////////////////////////////////////////////////
// BaseWidget
////////////////////////////////////////////////////////////////////////////
var BaseWidget = function() {};
BaseWidget.prototype =
{
//*** OpenAjax Metadata Widget APIs ***//
getId: function() {
return this._id;
},
getAvailableDimensions: function() {
return this._availableDimensions;
},
getDimensions: function() {
return {
width: parseInt( this._rootElement.style.width, 10 ),
height: parseInt( this._rootElement.style.height, 10 )
};
},
adjustDimensions: function( dimensions ) {
// ask the app if this widget is allowed to resize to the
// requested dimensions
if ( this._adjustDimensionsCB ) {
// XXX scope for callback?
dimensions = this._adjustDimensionsCB( this._widget, dimensions );
}
if ( dimensions ) {
this._rootElement.style.width = dimensions.width + "px";
this._rootElement.style.height = dimensions.height + "px";
return dimensions;
}
return null;
},
getMode: function() {
// XXX TODO
alert( "BaseWidget.getMode not implemented" );
},
requestMode: function( mode ) {
// XXX TODO
alert( "BaseWidget.requestMode not implemented" );
},
getPropertyValue: function(name) {
return this._properties[name] || this._spec.property[name].defaultValue;
},
setPropertyValue: function( name, value )
{
if ( this._setPropertyValue( name, value, true, true ) ) {
var prop = this._spec.property[ name ];
if ( prop.sharedAs ) {
this._hubClient.publish( prop.sharedAs,
this._encodePropValue( name, value ) );
}
}
},
getPropertyNames: function() {
var names = [];
for (var name in this._spec.property) {
names.push(name);
}
return names;
},
getMsg: function( key )
{
// XXX TODO
alert( "BaseWidget.getMsg not implemented" );
},
rewriteURI: function( url )
{
return _getProxyUrl( _resolveURI( url, this._spec._src_ ) );
},
// XXX old code
// getSupportedViews: function() {
// var views = {};
// for (var i=0; i < this.spec.contents; i++) {
// var list = (this.spec.contents[i].view || "default").split(",");
// for (var j=0; j < list.length; j++) {
// //TODO: this method is supposed to construct a hash of View objects. What are View objects?
// views[list[j] = undefined];
// }
// }
// return views;
// },
// requestNavigateTo: function() {
// //TODO
// },
//*** private functions ***//
_init: function ( args )
{
this._id = args.id;
this._spec = args.spec;
this._properties = args.props || {};
this._rootElement = args.root;
this._availableDimensions = args.availDimensions;
this._adjustDimensionsCB = args.adjustDimensions;
this._connectToHub();
this._createHubSubObject();
this._handlePropSubscriptions();
// XXX should we directly set size on rootElement, or create a DIV
// inside and change its dimensions?
this._rootElement.style.width = this._spec.width + "px";
this._rootElement.style.height = this._spec.height + "px";
},
_connectToHub: function()
{
// XXX add error checks
this._container = new OpenAjax.hub.InlineContainer( _hub, this._id,
{ Container: {
onSecurityAlert: function( source, alertType ) {
console.log( "onSecurityAlert: s=" + source.getClientID() + " a=" + alertType );
},
scope: this,
log: function( msg ) { console.log( msg ); }
}
}
);
this._hubClient = new OpenAjax.hub.InlineHubClient({
HubClient: {
onSecurityAlert: function( source, alertType ) {
console.log( "onSecurityAlert: s=" + source.getClientID() + " a=" + alertType );
},
scope: this,
log: function(msg) { console.log( msg ); }
},
InlineHubClient: {
container: this._container
}
});
this._hubClient.connect();
},
_createHubSubObject: function()
{
var that = this;
this.hub = new function() {
this.subscribe = function() {
that._hubClient.subscribe.apply( that._hubClient, arguments );
};
this.publish = function() {
that._hubClient.publish.apply( that._hubClient, arguments );
};
this.unsubscribe = function() {
that._hubClient.unsubscribe.apply( that._hubClient, arguments );
};
this.isConnected = function() {
that._hubClient.isConnected.apply( that._hubClient, arguments );
};
this.getScope = function() {
that._hubClient.getScope.apply( that._hubClient, arguments );
};
this.getSubscriberData = function() {
that._hubClient.getSubscriberData.apply( that._hubClient, arguments );
};
this.getSubscriberScope = function() {
that._hubClient.getSubscriberScope.apply( that._hubClient, arguments );
};
// do no expose this function
this.getParameters = function() {};
};
},
_handlePropSubscriptions: function()
{
var that = this;
function createSubscription( name, prop ) {
return that._hubClient.subscribe( prop.sharedAs,
function( topic, data ) {
that._setPropertyValue( name, data, true, false );
},
that
);
}
this._subscriptions = [];
for ( var name in this._spec.property ) {
var prop = this._spec.property[ name ];
if ( prop.sharedAs ) {
var sid = createSubscription( name, prop );
this._subscriptions.push( sid );
}
}
},
_setPropertyValue: function( name, newValue, notify, self )
{
var oldValue = this.getPropertyValue( name );
newValue = this._decodePropValue( name, newValue );
if ( this._equals( name, oldValue, newValue )) {
return false;
}
this._properties[ name ] = newValue;
// call onChange* functions
if ( notify ) {
var event = {
property: name,
oldValue: oldValue,
newValue: newValue,
self: self
};
if ( this._widget.onChange ) {
this._widget.onChange.call( this._widget, event );
}
var cb = "onChange" + name.charAt(0).toUpperCase() + name.substring(1);
if ( this._widget[ cb ] ) {
this._widget[ cb ].call( this._widget, event );
}
}
return true;
},
_equals: function( propName, value1, value2 )
{
var prop = this._spec.property[ propName ];
switch( prop.datatype ) {
case "Date":
return value1.valueOf() == value2.valueOf();
default:
return value1 == value2;
}
},
_encodePropValue: function( name, value )
{
var prop = this._spec.property[ name ];
switch ( prop.datatype ) {
// XXX for type 'Date', we transmit the value as milliseconds
case "Date":
if ( typeof value === "object" ) {
value = value.getTime();
}
break;
}
return value;
},
_decodePropValue: function( name, value )
{
var prop = this._spec.property[ name ];
switch ( prop.datatype ) {
case "Date":
if ( typeof value !== "object" ) {
if ( value === "" ) {
// XXX assume empty string for type 'Date' signifies "now"
value = new Date();
} else {
value = new Date( value );
}
}
break;
}
return value;
},
_render: function( target, view, onComplete )
{
var that = this;
this._widgetContext = new function() {
// Keep track of the widget's content. The scripts are separated
// from each other, depending on when they should run.
this.body = {
jsBefore: [],
content: null,
jsContent: [],
jsAfter: [],
jsEnd: []
};
this.loadedCallbacks = 0;
this.runDeferredScripts = function() {
if (--this.loadedCallbacks) {
return;
}
var _getObject = function(clazz, context) {
var obj = context || window,
path = clazz.split(".");
for (var i=0; i < path.length; i++) {
obj = obj[path[i]];
}
return obj;
};
// create widget instance
var widget;
if ( that._spec.jsClass ) {
widget = new (_getObject( that._spec.jsClass ))();
} else {
widget = {};
}
widget.OpenAjax = that;
that._widget = widget;
// XXX How do we handle _widgetTable in the sandbox case? Should it only be used
// on the app side? Or is it also necessary inside an iframe? Where does
// the developer get the widget ID from?
_widgetTable[ that._id ] = widget;
// evaluate widget content -- render content and run scripts
// in order
var s = [ "jsBefore", "jsContent", "jsAfter", "jsEnd" ];
for ( i = 0; i < s.length; i++ ) {
if ( s[i] == "jsContent" ) {
// add content to page, before running the scripts we
// had previously extracted from the content
target.innerHTML = this.body.content;
}
var scripts = this.body[ s[i] ];
this.body[ s[i] ] = [];
for ( var j = 0; j < scripts.length; j++ ) {
OpenAjax.widget._runScript( scripts[j]/*, onError*/ ); // XXX handle error
}
}
// let the widget code know that the widget has been fully
// loaded
if ( widget.onLoad ) {
widget.onLoad( {} );
}
if ( onComplete ) {
onComplete( widget );
}
};
};
this._widgetContext.loadedCallbacks++;
if ( this._spec.require ) {
this._loadAndEvalRequires();
}
this._loadContent( target, view );
this._widgetContext.runDeferredScripts();
},
_loadAndEvalRequires: function()
{
var scripts = [];
var prepost = {};
for (var i = 0; i < this._spec.require.length; i++) {
var req = this._spec.require[i];
// // check for duplicates
// // XXX This is for use by inline widgets, where multiple widgets
// // exist on the same page and may have similar requires. The
// // problem with this approach is that the first instance of a
// // given library of file is used. Rather, we should probably
// // use the one with the greatest version.
// var requireInfo;
// if (req.src in _requiresCache && (requireInfo = _requiresCache[req.src]).library == req.library) {
// if (requireInfo.inFlight) {
// this._widgetContext.loadedCallbacks++;
// requireInfo.callbacks.push(function(uri){
// that._widgetContext.runDeferredScripts();
// });
// }
//
// // already loaded on page
// continue;
// }
//
// _requiresCache[req.src] = {
// library: req.library
// };
//
// var library = req.library && this._spec.libraries[req.library];
//
// if (library && library.preload) {
// this._runScript(library.preload);
// }
//
// this._insertResource(document, req, library ? library.src : "");
//
// if (library && library.postload) {
// this._runScript(library.postload);
// }
var library = req._library_ && this._spec.library[ req._library_ ];
if ( library && library.preload && ! (req._library_ in prepost) ) {
scripts.push( { text: library.preload[0]._content_ } );
}
var prefix = library ? library.src : "";
// relative URLs are assumed to be relative to widget XML file
var uri = req.src ? _resolveURI( prefix + req.src, this._spec._src_ ) : null;
switch ( req.type ) {
case "javascript":
scripts.push( uri ?
{ src: uri } :
{ text: req._content_ }
);
break;
case "css":
// XXX Directly loading the CSS require could result in it
// being loaded out of order as compared to the JS
// requires as defined in the widget XML.
this._loadCSS( req._content_, uri );
break;
default:
// XXX TODO
}
if ( library && library.postload && ! (req._library_ in prepost) ) {
scripts.push( { text: library.postload[0]._content_ } );
}
// keep track that we have already handled preload/postload for library
if ( library ) {
prepost[ req._library_ ] = true;
}
}
this._widgetContext.loadedCallbacks++;
var that = this;
OpenAjax.widget._loadScripts( scripts, true,
function( scripts, success, error ) { // onComplete
if ( success ) {
that._widgetContext.runDeferredScripts();
return;
}
// XXX handle error
console.error( error );
}
);
},
// load widget content (<content> & <javascript>)
_loadContent: function( target, mode )
{
var that = this;
var content;
for (var i = 0; i < this._spec.content.length; i++) {
var c = this._spec.content[i],
list = ","+(c.mode||"view")+",";
if (list.indexOf(","+(mode||"view")+",") != -1) {
content = c;
break;
}
if (!content && list.indexOf(",view,") != -1) {
content = c;
}
}
if ( ! content ) {
// XXX handle error
return;
}
var _propReplace = function(match, transform, name)
{
name = name || transform; // if no transform, name will be in the first position
if (transform) {
switch (transform) {
case "escapequotes":
name = name.replace(/'/g, "\\'").replace(/"/g, '\\"');
break;
case "entityencode":
name = name.replace(/&/gm, "&amp;").replace(/</gm, "&lt;")
.replace(/>/gm, "&gt;").replace(/"/gm, "&quot;")
.replace(/'/gm, "&#39;");
// break;
// default:
//TODO: error handling?
}
}
return that._properties[name] || that._spec.property[name].defaultValue;
};
if ( content.src ) {
// relative URLs are assumed to be relative to widget XML file
var uri = _resolveURI( content.src, this._spec._src_ );
this._widgetContext.loadedCallbacks++;
OpenAjax.widget._xhrGet( uri, false,
function(text){
text = text.replace(/__WID__/g, that._id).replace(/@@(\w+)(?:\((\w+)\))?@@/g, _propReplace);
that._widgetContext.body.content = that._extractScripts(text);
that._widgetContext.runDeferredScripts();
},
function(err){
target.innerHTML = "<b>An error occurred</b>"; // XXX TODO
console.error(err);
return;
}
);
} else {
var text = content._content_.replace(/__WID__/g, this._id).replace(/@@(\w+)(?:\((\w+)\))?@@/g, _propReplace);
that._widgetContext.body.content = this._extractScripts(text);
}
if ( this._spec.javascript ) {
this._widgetContext.loadedCallbacks++;
var scripts = [];
for ( i = 0; i < this._spec.javascript.length; i++ ) {
scripts.push( this._spec.javascript[i] );
var s = scripts[i];
if ( s .src ) {
// relative URLs are assumed to be relative to widget XML file
s.src = _resolveURI( s.src, this._spec._src_ );
} else {
s.text = s._content_;
}
}
// load <javascript> elements, but don't evaluate yet
OpenAjax.widget._loadScripts( scripts, false,
function( scripts, success, error ) {
if ( success ) {
var jsBefore = 0, jsAfter = 0, jsEnd = 0;
for ( var i = 0; i < scripts.length; i++ ) {
var text = scripts[i].text.replace(/__WID__/g, that._id).replace(/@@(\w+)(?:\((\w+)\))?@@/g, _propReplace);
switch ( scripts[i].location ) {
case "beforeContent":
that._widgetContext.body.jsBefore[ jsBefore++ ] = text;
break;
case "atEnd":
that._widgetContext.body.jsEnd[ jsEnd++ ] = text;
break;
default:
//case "afterContent":
that._widgetContext.body.jsAfter[ jsAfter++ ] = text;
break;
}
}
that._widgetContext.runDeferredScripts();
return;
}
// XXX handle error
console.error( error );
},
false // doEval
);
}
},
_loadCSS: function( data, uri )
{
if (uri) {
var link = document.createElement('LINK');
link.setAttribute('rel', 'stylesheet');
link.setAttribute('type', 'text/css');
link.setAttribute('href', uri);
_head.appendChild(link);
} else {
var style = document.createElement('STYLE');
if (!window.attachEvent) { //XXX FIXME: odd test
style.text = data;
} else {
document.styleSheets[document.styleSheets.length - 1].cssText = data;
}
_head.appendChild(style);
}
},
_extractScripts: function( text )
{
//XXX TODO: avoid SCRIPT in <!-- -->
// XXX See old version of createGadget() in minimashup.js, where we create a DOM
// node (DIV), inject the HTML code and use DOM APIs to acquire and remove
// the script elements. This should get around the issue of accidentally
// matching <script> in comments.
var that = this;
return text.replace(/<script\s*(?:[^>]*?(?:src=(['"]?)([^>]*?)\1[^>]*)?)*>([\s\S]*?)<\/script>/gi, function(ignore, delim, src, code){
if (src) {
OpenAjax.widget._xhrGet( src, false,
function(text){
that._widgetContext.body.jsContent.push( text );
},
function(err) {
console.error(err); // XXX TODO
}
);
} else {
that._widgetContext.body.jsContent.push(code);
}
return "";
});
},
_setAvailableDimensions: function( availDimensions )
{
if ( availDimensions.width ) {
this._availableDimensions.width = availDimensions.width;
} else {
delete this._availableDimensions.width;
}
if ( availDimensions.height ) {
this._availableDimensions.height = availDimensions.height;
} else {
delete this._availableDimensions.height;
}
},
_unload: function( callback ) {
if ( this._widget.onUnload ) {
try {
this._widget.onUnload( {} );
} catch(e) {}
}
_hub.removeContainer( this._container );
delete _widgetTable[ this._id ];
callback( this._id );
}
};
////////////////////////////////////////////////////////////////////////////
// ProxyWidget
////////////////////////////////////////////////////////////////////////////
var ProxyWidget = function() {};
ProxyWidget.prototype = new BaseWidget();
//*** OpenAjax Metadata Widget APIs ***//
ProxyWidget.prototype.adjustDimensions = function( dimensions )
{
dimensions = BaseWidget.prototype.adjustDimensions.apply( this, arguments );
if ( dimensions ) {
_hub.publish( "openajax.widget." + this._id + "._sizeChanged", dimensions );
}
};
ProxyWidget.prototype.setPropertyValue = function(name, value)
{
if ( this._setPropertyValue( name, value, false, false ) ) {
_hub.publish( "openajax.widget." + this._id + "._propValueChange.proxy",
{ p: name, v: this._encodePropValue( name, value ) } );
}
};
//*** private functions ***//
ProxyWidget.prototype._init = function( args )
{
BaseWidget.prototype._init.apply( this, arguments );
// create a dummy widget instance
var widget = {
OpenAjax: this
};
this._widget = widget;
_widgetTable[ this._id ] = widget;
};
ProxyWidget.prototype._connectToHub = function()
{
var widgetBaseURI = encodeURIComponent( this._spec._src_.slice( 0, this._spec._src_.lastIndexOf("/") + 1 ));
//AP var stubURI = _loaderRoot + "widget.html?oawb=" + widgetBaseURI + "&oawh=" + encodeURIComponent( _hubBaseJS );
var stubURI = "http://localhost/js/widget.html?oawb=" + widgetBaseURI + "&oawh=" + encodeURIComponent( _hubBaseJS );
this._container = new OpenAjax.hub.IframeContainer( _hub, this._id,
{ Container: {
onSecurityAlert: function( source, alertType ) {
console.log( "onSecurityAlert: s=" + source.getClientID() + " a=" + alertType );
},
scope: this,
log: function( msg ) { console.log( msg ); }
},
IframeContainer: {
uri: stubURI,
tunnelURI: _tunnelURI,
iframeAttrs: {
frameBorder: "0",
scrolling: "no",
style: { width: "100%", height: "100%" }
},
parent: this._rootElement
}
}
);
this._listenForEvents();
};
ProxyWidget.prototype._createHubSubObject = function() {};
ProxyWidget.prototype._handlePropSubscriptions = function() {};
ProxyWidget.prototype._listenForEvents = function()
{
this._subs = [];
var prefix = "openajax.widget." + this._id;
var sid = _hub.subscribe( prefix + "._instantiated",
function( topic, data ) {
_hub.publish( prefix + "._init",
{ _spec: this._spec,
_properties: this._properties,
_sentinel: this._sentinel,
_subscriptions: this._subscriptions,
_availableDimensions: this._availableDimensions,
_proxy: _proxyURL
}
);
_hub.unsubscribe( sid );
},
this
);
this._subs.push( _hub.subscribe( prefix + ".api.*", this._apiCall, this ) );
this._subs.push( _hub.subscribe( prefix + "._propValueChange.remote",
function( topic, data ) {
this._properties[ data.p ] = this._decodePropValue( data.p, data.v );
},
this
));
};
ProxyWidget.prototype._render = function( target, view, onComplete )
{
// Call onComplete when the remote widget has finished loading
if ( onComplete ) {
var sid = _hub.subscribe(
"openajax.widget." + this._id + "._loaded",
function( topic, data ) {
onComplete( this._widget );
_hub.unsubscribe( sid );
},
this
);
}
};
ProxyWidget.prototype._apiCall = function( topic, data )
{
var api = topic.slice( topic.lastIndexOf(".") + 1 );
this[ api ].call( this, data );
};
ProxyWidget.prototype._setAvailableDimensions = function( availDimensions )
{
BaseWidget.prototype._setAvailableDimensions.apply( this, arguments );
_hub.publish( "openajax.widget." + this._id + "._availDimensions",
this._availableDimensions );
};
ProxyWidget.prototype._unload = function( callback )
{
var that = this;
function finishUnload() {
_hub.removeContainer( that._container );
for ( var i = 0; i < that._subs.length; i++ ) {
_hub.unsubscribe( that._subs[i] );
}
delete _widgetTable[ that._id ];
callback( that._id );
}
var prefix = "openajax.widget." + this._id;
// XXX set a timeout in case RemoteWidget never responds?
this._subs.push( _hub.subscribe( prefix + "._unloaded",
function( topic, data ) { // onData
// RemoteWidget finished unloading
finishUnload();
},
this,
function( item, success, errCode ) { // onComplete
if ( ! success ) {
// something went wrong -- log error and invoke callback
// XXX TODO log error
finishUnload();
}
}
));
_hub.publish( prefix + "._unload", null );
};
////////////////////////////////////////////////////////////////////////////
// RemoteWidget
////////////////////////////////////////////////////////////////////////////
var RemoteWidget = function( target )
{
this._rootElement = target;
this._proxyPropChange = false;
this._hubClient = new OpenAjax.hub.IframeHubClient({
HubClient: {
onSecurityAlert: function( source, alertType ) {
console.log( "onSecurityAlert: s=" + source.getClientID() + " a=" + alertType );
},
scope: this,
log: function(msg) { console.log( msg ); }
}
});
this._id = this._hubClient.getClientID();
this._hubClient.connect(
function( item, success, errorCode ) {
// XXX handle error
// first, subscribe to "_init" msg from parent
var topicPrefix = "openajax.widget." + this._id;
this._initSub = this._hubClient.subscribe(
topicPrefix + "._init",
this._init,
this
);
// then, let parent know that we are ready
this._hubClient.publish( topicPrefix + "._instantiated", null );
},
this
);
};
RemoteWidget.prototype = new BaseWidget();
//*** OpenAjax Metadata Widget APIs ***//
RemoteWidget.prototype.adjustDimensions = function( dimensions )
{
this._hubClient.publish( "openajax.widget." + this._id + ".api.adjustDimensions", dimensions );
};
//*** private functions ***//
RemoteWidget.prototype._init = function( topic, data )
{
this._hubClient.unsubscribe( this._initSub );
delete this._initSub;
// mixin received object with 'this'
for ( var prop in data ) {
if ( ! (prop in this) ) {
this[ prop ] = data[ prop ];
}
}
_proxyURL = this._proxy;
delete this._proxy; // XXX handle this better
this._createHubSubObject();
this._handlePropSubscriptions();
this._listenForEvents();
// Add widget to page
// _render() calls the widget objects "onLoad" method.
var that = this;
this._render( this._rootElement, /*view*/ null,
function() { // onComplete
that._hubClient.publish( "openajax.widget." + that._id + "._loaded", null );
}
);
// set widget size
this._rootElement.style.width = this._spec.width + "px";
this._rootElement.style.height = this._spec.height + "px";
};
// already connected to hub in constructor - nothing to do here
RemoteWidget.prototype._connectToHub = function() {};
RemoteWidget.prototype._listenForEvents = function()
{
var topicPrefix = "openajax.widget." + this._id;
// widget has been resized
this._hubClient.subscribe( topicPrefix + "._sizeChanged", this._sizeChanged, this);
// update to the available (max) dimensions for this widget
this._hubClient.subscribe( topicPrefix + "._availDimensions",
function( topic, data ) {
this._availableDimensions = data;
},
this
);
// ProxyWidget has updated property value
this._hubClient.subscribe( topicPrefix + "._propValueChange.proxy",
function( topic, data ) {
this._proxyPropChange = true;
this.setPropertyValue( data.p, this._decodePropValue( data.p, data.v ) );
this._proxyPropChange = false;
},
this
);
this._hubClient.subscribe( topicPrefix + "._unload", this._unload, this );
};
RemoteWidget.prototype._setPropertyValue = function( name, value, notify, self )
{
var changed = BaseWidget.prototype._setPropertyValue.apply( this, arguments );
if ( changed && ! this._proxyPropChange ) {
this._hubClient.publish( "openajax.widget." + this._id + "._propValueChange.remote",
{ p: name, v: this._encodePropValue( name, value ) }
);
}
return changed;
};
RemoteWidget.prototype._sizeChanged = function( topic, data )
{
// If the widget object has a 'sizeChanged' method, then we need to save
// the current dimensions, before changing them.
if ( this._widget.onSizeChanged ) {
var oldDimensions = this.getDimensions();
}
// change dimensions based on data from parent
BaseWidget.prototype.adjustDimensions.call( this, data );
if ( this._widget.onSizeChanged ) {
var newDimensions = this.getDimensions();
this._widget.onSizeChanged.call(
this._widget,
{ oldWidth: oldDimensions.width,
oldHeight: oldDimensions.height,
newWidth: newDimensions.width,
newHeight: newDimensions.height
}
);
}
};
RemoteWidget.prototype._unload = function()
{
if ( this._widget.onUnload ) {
try {
this._widget.onUnload( {} );
} catch(e) {}
}
this._hubClient.publish( "openajax.widget." + this._id + "._unloaded",
null );
};
OpenAjax.widget._createRemoteWidget = function( target )
{
return new RemoteWidget( target );
};
})();
// remove object that may have been created previously (in case of widget.html)
if ( typeof window.__openajax_widget__ !== "undefined" ) {
try {
window.__openajax_widget__ = null;
delete window.__openajax_widget__;
} catch(e) {}
}
} // OpenAjax !== "undefined"
var oaw;
if ( typeof OpenAjax === "undefined" ) {
oaw = window.__openajax_widget__ = {};
} else {
oaw = OpenAjax.widget;
}
////////////////////////////////////////////////////////////////////////////
// _xhrGet
////////////////////////////////////////////////////////////////////////////
/**
* @memberOf OpenAjax.widget
*
* @param {String} url
* @param {Boolean} forceXml
* @param {Function} onComplete
* @param {Function} onError
*/
oaw._xhrGet = function(url, forceXml, onComplete, onError) {
var activeX = "ActiveXObject" in window;
//TODO: what happens to namespaces on various browsers?
var toXml = function(xhr){
var result = xhr.responseXML;
if (activeX && (!result || !result.documentElement)){
var dom = new ActiveXObject("Microsoft.XMLDOM");
dom.async = false;
dom.loadXML(xhr.responseText);
result = dom;
}
if (result && result.documentElement.tagName == "parsererror") { // Annoying Mozilla behavior
throw result.documentElement.firstChild.nodeValue;
}
if (!result) {
throw "Parser error";
}
return result;
};
var xhr = ((typeof XMLHttpRequest == "undefined" || !location.href.indexOf("file:")) && activeX) ?
new ActiveXObject("Msxml2.XMLHTTP") : new XMLHttpRequest();
onError = onError || function(e) { console.error(e); };
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if (!xhr.status || xhr.status == 200) {
try {
onComplete(forceXml ? toXml(xhr) : xhr.responseText);
} catch(e) {
onError(e);
}
} else {
onError(new Error("Unable to load " + url + " status:" + xhr.status));
}
}
};
if ( forceXml && xhr.overrideMimeType ) {
xhr.overrideMimeType("text/xml");
}
xhr.open("GET", url, true);
try {
xhr.send(null);
} catch(e) {
onError(e);
}
};
////////////////////////////////////////////////////////////////////////////
// _loadScripts
////////////////////////////////////////////////////////////////////////////
/**
* Load all of the given scripts, making sure they are evaluated in order.
*
* @memberOf OpenAjax.widget
*
* @param {Object[]} scripts
* An array of scripts. Each array element is an object which must have
* either a "text" property (the full script text) or a "src"
* property (URL of script).
* @param {Boolean} [xdomain="false"]
* If true, signifies that the given scripts array may contain cross-domain
* URLs. If false, it is assumed that all script URLs are within the
* current domain.
* @param {Function} [onComplete]
* Callback function invoked on successful completion or error. The first
* parameter is the original 'scripts' array; each script now has a valid
* 'text' property (when 'xdomain' is true, this may not be correct).
* Syntax is as follows:
* onComplete( //Object[]// scripts, //Boolean// success, //Error|String// error )
* @param {Boolean} [doEval="true"]
* By default, the scripts are evaluated after being loaded. Specify "false"
* to prevent evaluation.
*/
oaw._loadScripts = function( scripts, xdomain, onComplete, doEval )
{
// For scripts that may be cross-domain, load them one-by-one -- this
// maintains the proper order.
if ( xdomain ) {
var head = document.getElementsByTagName('HEAD').item(0);
function addScript( scripts, idx ) {
if ( idx == scripts.length ) {
if ( onComplete ) {
onComplete( scripts, true );
}
return;
}
var script = document.createElement( 'SCRIPT' );
script.setAttribute( 'type', 'text/javascript' );
if ( scripts[idx].text ) {
script.text = scripts[idx].text;
head.appendChild(script);
addScript( scripts, idx + 1 );
} else {
script.setAttribute( 'src', scripts[idx].src );
script.onload = script.onreadystatechange = function(e) {
if ((e && e.type == "load") || /complete|loaded/.test(script.readyState)) {
addScript( scripts, idx + 1 );
}
};
head.appendChild(script);
}
}
addScript( scripts, 0 );
}
// If there are no cross-domain scripts, then use async XHR to load the
// scripts, but make sure that they are evaluated in the proper order.
else {
doEval = (typeof doEval !== "undefined") ? doEval : true;
var nextIdx = 0;
function evalNextScript( idx ) {
if ( idx === nextIdx ) {
while ( nextIdx < scripts.length && scripts[ nextIdx ].text ) {
if ( doEval ) {
oaw._runScript( scripts[ nextIdx ].text,
function( errorMsg ) {
onComplete( scripts, false, errorMsg );
}
);
}
nextIdx++;
}
if ( onComplete && nextIdx == scripts.length ) {
onComplete( scripts, true );
}
}
}
function getAndEval( idx ) {
var s = scripts[idx];
oaw._xhrGet( s.src, false,
function( text ) { // onComplete
s.text = text;
evalNextScript( idx );
},
function( err ) { // onError
if ( onComplete ) {
onComplete( scripts, false,
"Failed to retrieve script, with error: " + err.toString() +
"\nscript src = " + s.src );
}
}
);
}
for ( var i = 0; i < scripts.length; i++ ) {
var s = scripts[i];
if ( s.text ) {
evalNextScript( i );
} else {
getAndEval( i );
}
}
}
};
////////////////////////////////////////////////////////////////////////////
// _runScript
////////////////////////////////////////////////////////////////////////////
oaw._runScript = (function(){
// Test window.eval first, since this is the standards compliant way of
// running scripts.
if ( window.eval ) {
window.eval( "function __openajax_widget_fn__() {};" );
if ( window.__openajax_widget_fn__ ) {
delete window.__openajax_widget_fn__;
return function( text ) {
window.eval( text );
};
}
}
// fallbacks
// Next, see if the browser provides the execScript function (IE).
if ( window.execScript ) {
return function( text ) {
window.execScript( text );
};
}
// Lastly, if all else fails, dynamically add script to HEAD
// (i.e. Safari 3.2).
var head = document.getElementsByTagName('HEAD').item(0);
return function( text ) {
var script = document.createElement( 'SCRIPT' );
script.setAttribute( 'type', 'text/javascript' );
script.text = text;
head.appendChild( script );
};
})();
/*
Copyright 2006-2009 OpenAjax Alliance
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* The Wiring Overlay class does the work to overlay a gadget with a
* semi-transparent DIV. Through a context menu on the DIV, the user can
* express a desire to wire one or more gadgets to the currently selected
* property on the Property Dialog of a gadget. Any wiring changes will be
* committed once the user selects 'save' from the Property Dialog
*
* @class WiringOverlay
*/
function WiringOverlay(/* Gadget */gadget, topic, /* boolean */publisher)
{
// Create a DIV overlay that covers the given gadget. We are overlaying the
// gadget because we are overlaying gadgets that publish (if publisher is
// true) or listen (if publisher is false) the given topic.
if (!gadget) {
return;
}
var container = gadget.getSite().getContainer();
var gadgetCoords = dojo.coords(container, true);
var overlay = document.createElement('div');
overlay.id = gadget.getId()+'_overlay';
overlay.style.left = gadgetCoords.x;
overlay.style.top = gadgetCoords.y;
overlay.style.width = gadgetCoords.w;
overlay.style.height = gadgetCoords.h;
var zIndex = container.style.zIndex;
if (zIndex) {
overlay.style.zIndex = zIndex+1;
} else {
overlay.style.zIndex = 1;
}
overlay.style.position = 'absolute';
Browser.setAlpha(overlay, 0.6);
dojo.body().appendChild(overlay);
this.gadget = gadget;
this.topic = topic;
this.publish = publisher;
this.element = overlay;
// If we got to the point of creating an overlay, then we know that this
// gadget is a possible match for wiring at least. The type may be further
// improved as we inspect the gadget's properties while building the context
// menu.
this.setType(WiringOverlay.POSSIBLE);
}
WiringOverlay.CURRENT = 1;
WiringOverlay.RECOMMENDED = 2;
WiringOverlay.POSSIBLE = 3;
WiringOverlay.WIRING_NONE = 0;
WiringOverlay.WIRING_SINGLE = 1;
WiringOverlay.WIRING_MULTI = 2;
WiringOverlay.prototype.setType = function(/* int */type)
{
switch(type)
{
case WiringOverlay.CURRENT:
this.element.className = 'nomadWiringOverlayCurrent';
this.type = type;
break;
case WiringOverlay.RECOMMENDED:
this.element.className = 'nomadWiringOverlayRecommended';
this.type = type;
break;
case WiringOverlay.POSSIBLE:
this.element.className = 'nomadWiringOverlayPossible';
this.type = type;
break;
}
}
WiringOverlay.prototype.destroy = function()
{
if (this.popupMenu) {
dojo.forEach(this.popupMenu.getChildren(),
function(menuItem){
if (menuItem.popup) {
menuItem.popup.destroyRecursive();
}
}
);
this.popupMenu.destroyRecursive();
this.popupMenu = null;
}
if (this.tooltip) {
this.tooltip.destroy();
this.tooltip = null;
}
dojo.body().removeChild(this.element);
this.gadget = null;
this.element = null;
}
WiringOverlay.prototype.enable = function(/* int */zIndex)
{
// Get the overlay ready so that the user can interact with it. If zIndex
// is specified, then this is the level to which we need to raise the overlay
// so that it isn't hidden by some other window
if (zIndex) {
this.element.style.zIndex = zIndex;
}
if (!this.publish) {
this.popupMenu = this.createListenerContextMenu();
} else {
this.popupMenu = this.createPublisherContextMenu();
}
this.tooltip = this.createTooltip();
}
WiringOverlay.prototype.createTooltip = function()
{
var label = null;
switch (this.type) {
case WiringOverlay.CURRENT:
if (this.publish) {
label = 'This gadget publishes the topic '+this.topic+' which is currently bound to the selected property.'
} else {
label = 'This gadget currently listens to the topic '+this.topic+' which is currently published by the selected property.'
}
break;
case WiringOverlay.RECOMMENDED:
if (this.publish) {
label = 'This gadget publishes the topic '+mashupMaker.getWiringManager().wiringProperty.defaultTopic()+' which is recommended for binding to the selected property.'
} else {
label = 'This gadget by default listens to the topic '+this.topic+' which is currently published by the selected property. However, it is not currently listening for this topic.'
}
break;
case WiringOverlay.POSSIBLE:
if (this.publish) {
label = 'This gadget publishes other topics that may be bound to the selected property.'
} else {
label = 'This gadget is able to listen to the topic '+this.topic+' but it does not currently do so.'
}
break;
}
if (!label) {
return;
}
if (this.publish) {
label += ' Caution should be used when selecting a non-recommended topic for binding.'
} else if (this.type == WiringOverlay.POSSIBLE) {
label += ' Caution should be used when asking a gadget to listen for a non-recommended topic.'
}
label += ' Bring up a context menu for further options.'
var tooltip = new dijit.Tooltip({
label: label,
connectId: [this.element.id]
});
tooltip.domNode.id = "overlayTooltip";
return tooltip;
}
WiringOverlay.prototype.createPublisherContextMenu = function()
{
// this function builds the context menu for the overlay and also initializes
// the type for the overlay
var topic = this.topic;
if (topic === null) {
return null;
}
var menu = new dijit.Menu({
id: 'wiring_publishing_menu'+this.gadget.getId(),
targetNodeIds: [this.element.id]
});
if (!menu) {
return null;
}
var properties = this.gadget.OpenAjax._spec.property;
var wiringManager = mashupMaker.getWiringManager();
var boundGadget = null;
var oldTopic = null;
if (wiringManager.wiringProperty) {
boundGadget = mashupMaker.getWidgetModelFromID(getPropertyInfo(wiringManager.wiringProperty, "singleBoundGadget"));
oldTopic = getPropertyInfo(wiringManager.wiringProperty, "topic");
}
var newType = this.type,
currType = this.type;
// use topicList to make sure that even if a topic is published more than
// once by the gadget, that it still only appears once on the list
var topicList = {};
for ( var name in properties ) {
var property = properties[ name ];
var state = null;
// if (property.publish()) {
// var publishedTopic = property.defaultTopic();
if ( property.sharedAs ) {
var publishedTopic = property.sharedAs;
if (publishedTopic === null || topicList[publishedTopic]) {
continue;
}
// assign menuitem.initChecked prior to menu.startup()
var menuitem = null;
menuitem = new nomad.widget.CheckmarkMenuItem({
label: 'Publishes topic '+publishedTopic,
onClick: dojo.hitch(wiringManager, "registerWiringChange", wiringManager.wiringProperty, publishedTopic, oldTopic, this.gadget)
});
topicList[publishedTopic] = menuitem;
var checkable = publishedTopic == this.topic;
// see if the overlay's type can be improved
if (checkable) {
if (!boundGadget) {
newType = WiringOverlay.CURRENT;
} else if (boundGadget == this.gadget) {
newType = WiringOverlay.CURRENT;
menuitem.initChecked = true;
} else if (publishedTopic == wiringManager.wiringProperty.defaultTopic()) {
newType = WiringOverlay.RECOMMENDED;
}
} else if (publishedTopic == wiringManager.wiringProperty.defaultTopic()) {
newType = WiringOverlay.RECOMMENDED;
}
if (newType < currType) {
currType = newType;
}
menu.addChild(menuitem);
}
}
if (currType < this.type) {
this.setType(currType);
}
menu.startup();
return menu;
}
WiringOverlay.prototype.createListenerContextMenu = function()
{
var topic = this.topic;
if (!topic || topic.length == 0) {
return;
}
var menu = new dijit.Menu({
id: 'wiring_listener_menu'+this.gadget.getId(),
targetNodeIds: [this.element.id]
});
if (!menu) {
return null;
}
var properties = this.gadget._spec.property;
var wiringProperty = mashupMaker.getWiringManager().wiringProperty;
var newType = this.type,
currType = this.type;
for ( var name in properties ) {
var property = properties[ name ];
var state = null;
// if (property.listen() && !property.hidden()) {
if ( property.sharedAs && !property.hidden ) {
if (getPropertyInfo(property, "topic") == topic) {
if (getPropertyInfo(property, "singleBoundGadget")) {
if (getPropertyInfo(property, "singleBoundGadget") == wiringProperty.getGadget().getId()) {
state = WiringOverlay.WIRING_SINGLE;
newType = WiringOverlay.CURRENT;
// } else if (property.defaultTopic() == topic) {
} else if (property.sharedAs == topic) {
state = WiringOverlay.WIRING_NONE;
newType = WiringOverlay.RECOMMENDED;
}
} else {
state = WiringOverlay.WIRING_MULTI;
newType = WiringOverlay.CURRENT;
}
// } else if (property.defaultTopic() == topic) {
} else if (property.sharedAs == topic) {
state = WiringOverlay.WIRING_NONE;
newType = WiringOverlay.RECOMMENDED;
} else {
// XXX should we allow a publisher to publish a non-recommended topic
// to any ol' listener? For now we'll allow it
state = WiringOverlay.WIRING_NONE;
}
if (state != null) {
var popupmenu = this.createListenerSubmenu(topic, property, getPropertyInfo(property, "topic"), state);
var menuitem = new dijit.PopupMenuItem({
label: 'binding for property '+property.name,
popup: popupmenu
});
// see if the overlay's type can be improved
if (newType < currType) {
currType = newType;
}
menu.addChild(menuitem);
}
}
}
if (currType < this.type) {
this.setType(currType);
}
menu.startup();
return menu;
}
WiringOverlay.prototype.createListenerSubmenu = function(topic, property, originalTopic, /* int */checkedState)
{
var menu = new dijit.Menu({
id: 'wiring_listener_popup_'+this.gadget.getId()+'_'+property.name
});
if (!menu) {
return null;
}
var wiringManager = mashupMaker.getWiringManager();
// assign menuitem.initChecked prior to menu.startup()
var menuitem = null;
menuitem = new nomad.widget.CheckmarkMenuItem({
label: 'Listen for all '+topic+' topics',
onClick: dojo.hitch(wiringManager, "registerWiringChange", property, topic, originalTopic, null)
});
menuitem.initChecked = checkedState == WiringOverlay.WIRING_MULTI;
menu.addChild(menuitem);
menuitem = new nomad.widget.CheckmarkMenuItem({
label: 'Listen for no '+topic+' topics',
onClick: dojo.hitch(wiringManager, "registerWiringChange", property, "", originalTopic, null)
});
menuitem.initChecked = checkedState == WiringOverlay.WIRING_NONE;
menu.addChild(menuitem);
menuitem = new nomad.widget.CheckmarkMenuItem({
label: 'Bind to source gadget only',
handleEveryClick: true,
onClick: dojo.hitch(wiringManager, "registerWiringChange", property, topic, originalTopic, wiringManager.wiringProperty.getGadget())
});
menuitem.initChecked = checkedState == WiringOverlay.WIRING_SINGLE;
menu.addChild(menuitem);
// need to call startup here as startup won't filter down through a
// dijit.PopupMenuItem to its submenu
menu.startup();
return menu;
}
getPropertyInfo = function(property, /* string */key)
{
// In order to make the overlays look and work correctly, we'll sometimes need
// to know about any pending changes to the property that may not have taken
// affect, yet. This is especially true when trying to get the overlay
// type correct and to have the context menus initialize with the currently
// selected item correctly identified.
if (!property || !key) {
return;
}
var change = mashupMaker.getWiringManager().getWiringChange(property);
switch (key) {
case "topic":
if (change) {
return change.newTopic;
}
// return property.topic();
return property.sharedAs;
case "singleBoundGadget":
if (change) {
return change.publishingGadget.getId();
}
return property.getSingleBoundGadget(); // XXX JHP TODO
default:
return null;
}
}
/**
* The Wiring Manager class encapsulates the work that needs to happen each time
* the property dialog rolls up so that the user can select gadgets to wire
* to the current gadget.
*
* @class WiringManager
*/
function WiringManager()
{
this._wireableGadgets = new Array();
this._subscribedTopics = null;
this._publishedTopics = null;
this._changeArray = null;
}
WiringManager.prototype.addGadget = function(gadget)
{
if (!gadget) {
return;
}
// if the gadget publishes or subscribes to any topics, find out which and
// remember them
// var properties = gadget.getProperties();
// if ( properties ) {
// for (var i = 0; i < properties.length; i++) {
// var property = properties[i];
// if (property.listen() || property.publish()) {
// this._wireableGadgets[gadget.gID] = gadget;
// break;
// }
// }
// }
for ( var name in gadget.OpenAjax._spec.property ) {
var prop = gadget.OpenAjax._spec.property[ name ];
if ( prop.sharedAs ) {
this._wireableGadgets[ gadget.OpenAjax.getId() ] = gadget;
break;
}
}
}
WiringManager.prototype.buildTopicsLists = function()
{
if (this._publishedTopics || this._subscribedTopics) {
return;
}
this._publishedTopics = new Array();
this._subscribedTopics = new Array();
for (var gID in this._wireableGadgets) {
var gadget = this._wireableGadgets[gID];
var props = gadget.OpenAjax._spec.property;
for ( var name in props ) {
var topic = props[ name ].sharedAs;
if ( topic ) {
if ( ! this._publishedTopics[ topic ] ) {
this._publishedTopics[ topic ] = {};
}
// make sure that a gadget only appears once on the list
if ( ! this._publishedTopics[ topic ][ gID ] ) {
this._publishedTopics[ topic ][ gID ] = gadget;
}
if ( ! this._subscribedTopics[ topic ] ) {
this._subscribedTopics[ topic ] = {};
}
// make sure that a gadget only appears once on the list
if ( ! this._subscribedTopics[ topic ][ gID ] ) {
this._subscribedTopics[ topic ][ gID ] = gadget;
}
}
}
}
// // if the gadget publishes or subscribes to any topics, find out which and
// // remember them
// var topicInfo = gadget.getTopicInfo();
// if (topicInfo) {
// // topicInfo is an object that contains two arrays, topics that the gadget
// // listens for and topics that the gadget publishes
// var topicsArray = topicInfo.publishes;
// if (topicsArray && topicsArray.length > 0) {
// var topicsLength = topicsArray.length;
// for (var j = 0; j < topicsLength; j++) {
// var topic = topicInfo.publishes[j];
// if (!this._publishedTopics[topic]) {
// this._publishedTopics[topic] = {};
// }
// // make sure that a gadget only appears once on the list
// if (!this._publishedTopics[topic][gadget.getId()]) {
// this._publishedTopics[topic][gadget.getId()] = gadget;
// }
// }
// }
//
// topicsArray = topicInfo.subscribes;
// if (topicsArray && topicsArray.length > 0) {
// topicsLength = topicsArray.length;
// for (var j = 0; j < topicsLength; j++) {
// var topic = topicInfo.subscribes[j];
// if (!this._subscribedTopics[topic]) {
// this._subscribedTopics[topic] = {}
// }
// // make sure that a gadget only appears once on the list
// if (!this._subscribedTopics[topic][gadget.gID]) {
// this._subscribedTopics[topic][gadget.gID] = gadget;
// }
// }
// }
// }
// }
}
WiringManager.prototype.removeGadget = function(gadget)
{
if (!gadget) {
return;
}
// if the gadget publishes or subscribes to any topics, find out which and
if (this._wireableGadgets[gadget.gID]) {
delete this._wireableGadgets[gadget.gID];
}
}
WiringManager.prototype.findGadgets = function(/* string */topic, /* boolean */publish)
{
// If topic is specified, only gadgets related to that topic will be returned.
// If no topic, then all gadgets meeting the publish/listen requirement will
// be returned.
var results = new Array();
var topicArray = null;
var searchArray = null;
if (topic) {
searchArray = publish ? this._publishedTopics[topic] : this._subscribedTopics[topic];
} else {
topicArray = publish ? this._publishedTopics : this._subscribedTopics;
}
if (topicArray) {
for (var topic in topicArray) {
searchArray = topicArray[topic];
for (var gadgetId in searchArray) {
// since the same gadget can publish/subscribe to multiple topics,
// make sure that they are on the result list only once
if (!results[gadgetId]) {
results[gadgetId] = searchArray[gadgetId];
}
}
}
} else if (searchArray) {
for (var gadgetId in searchArray) {
if (searchArray[gadgetId]) {
results.push(searchArray[gadgetId]);
}
}
}
return results;
}
WiringManager.prototype.highlightTopic = function(/* string */topic, /* boolean */publishers, /* gadget */ignoreGadget)
{
// overlay all of the gadgets that publish/subscribe the given topic and
// give them a type of WiringOverlay.CURRENT
if (!topic || topic.length == 0) {
return;
}
this._overlayGadgets(this.findGadgets(topic, publishers), topic, publishers, ignoreGadget);
if (this.overlays && this.overlays.length) {
var numOverlays = this.overlays.length;
for (var i = 0; i < numOverlays; i++) {
var overlay = this.overlays[i];
overlay.setType(WiringOverlay.CURRENT);
}
}
}
WiringManager.prototype.highlightGadgets = function(/* array */gadgets, /* string */topic, /* boolean */publishers)
{
if (!gadgets) {
return;
}
this._overlayGadgets(gadgets, topic, publishers, null);
if (this.overlays && this.overlays.length) {
var numOverlays = this.overlays.length;
for (var i = 0; i < numOverlays; i++) {
var overlay = this.overlays[i];
overlay.setType(WiringOverlay.CURRENT);
}
}
}
WiringManager.prototype._overlayPotentialPublishers = function()
{
// create overlays for all gadgets that could publish to the current
// wiring property, ignoring the gadget that contains the current wiring
// property
this._overlayGadgets(this.findGadgets(null, true), getPropertyInfo(this.wiringProperty, "topic"), true, this.wiringProperty.getGadget());
}
WiringManager.prototype._overlayPotentialListeners = function()
{
// create overlays for all gadgets that could publish to the current
// wiring property, ignoring the gadget that contains the current wiring
// property
this._overlayGadgets(this.findGadgets(null, false), this.wiringProperty.defaultTopic(), false, this.wiringProperty.getGadget());
}
WiringManager.prototype.unhighlightTopic = function()
{
this.unhighlightGadgets();
}
WiringManager.prototype.unhighlightGadgets = function()
{
dojo.forEach(this.overlays,
function(overlay, index, array) {
overlay.destroy();
delete overlay;
}
);
this.overlays = null;
}
WiringManager.prototype._overlayGadgets = function(/* array of gadgets */gadgetArray, topic, /* boolean */publishers, /* gadget */ignoreGadget)
{
var overlays = new Array();
var currGadget;
if (typeof ignoreGadget === "undefined") {
currGadget = null;
} else {
currGadget = ignoreGadget;
}
for (gadgetId in gadgetArray) {
var gadget = gadgetArray[gadgetId];
if (gadget != currGadget) {
var overlay = new WiringOverlay(gadget, topic, publishers);
overlays.push(overlay);
}
}
this.overlays = overlays;
}
WiringManager.prototype.stopWiringProperty = function()
{
// Either the user has completed the wiring session or cancelled it. But
// the dialog hasn't been dismissed, yet, so more wiring sessions could happen
// with the wiring gadget so keep the wiring changes around for now.
this.unhighlightTopic();
}
WiringManager.prototype.notifyWiringPropertyComplete = function()
{
mashupMaker.hub.publish("nomad-wiring-property-complete", null);
this.stopWiringProperty();
}
WiringManager.prototype.wirePropertyToGadget = function(property, /* boolean */selectAPublishingGadget)
{
var topic = null;
if (selectAPublishingGadget) {
topic = getPropertyInfo(property, "topic");
} else {
topic = property.defaultTopic();
}
if (topic == null) {
this.notifyWiringPropertyComplete();
return;
}
this.wiringProperty = property;
// Build overlays for each gadget that listen for topic and since they need
// to be clickable, set their zIndex to the zIndex of the dialog underlay.
// If the user is being asked to selectAPublishingGadget, only overlay
// gadgets that publish topics. Otherwise we'll only overlay gadgets that
// could listen for the topic that the given property publishes.
if (selectAPublishingGadget) {
this._overlayPotentialPublishers();
} else {
this._overlayPotentialListeners();
// this.highlightTopic(topic, selectAPublishingGadget, property.getGadget());
}
var dialogUnderlays = dojo.query("BODY > .dijitDialogUnderlayWrapper");
var zIndex = 0;
if (dialogUnderlays && dialogUnderlays.length) {
// have to ask for z-index this way, doesn't exist in the node.style object
zIndex = dojo.style(dialogUnderlays[0], "zIndex");
if (zIndex && zIndex.length) {
zIndex = parseInt(zIndex);
}
}
if (this.overlays && this.overlays.length) {
var numOverlays = this.overlays.length;
for (var i = 0; i < numOverlays; i++) {
var overlay = this.overlays[i];
overlay.enable(zIndex);
}
}
}
WiringManager.prototype.registerWiringChange = function(property, newTopic, originalTopic, publishingGadget)
{
// Build a list of wiring changes that happened while this property editing
// session was in affect. These will either be forgotten if the user decides
// to cancel or they will be committed when the changes are saved.
// These changes will be changes affecting the subscriptions that property
// has (i.e. topic subscribing to, gadget bound to, etc) since wiring doesn't
// affect publishing.
if (!property || newTopic == null || typeof publishingGadget === "undefined") {
return;
}
if (!this._changeArray) {
this._changeArray = new Array();
}
// Come up with a unique key. We need to know if the change affects the input
// topic or the output topic of the property, too, since it is possible that
// a property will both listen and publish and the user might change both
// wirings.
var uniqueKey = property.getGadget().getId()+'_'+property.name;
this._changeArray[uniqueKey] = {property: property, newTopic: newTopic, oldTopic: originalTopic, publishingGadget: publishingGadget};
if (newTopic != originalTopic) {
var found = false;
var changedGadget = property.getGadget();
// var gadgetProperties = changedGadget.getProperties();
// for (var i = 0; i < gadgetProperties.length; i++) {
// var gadgetProperty = gadgetProperties[i];
// if (gadgetProperty.listen()) {
// if (gadgetProperty.name() != property.name() &&
// gadgetProperty.topic() == originalTopic) {
// // gadget still has a property that listens for this topic so keep
// // it in the list
// found = true;
// break;
// }
// }
// }
var gadgetProperties = changedGadget.OpenAjax._spec.property;
for ( var name in gadgetProperties ) {
var gadgetProperty = gadgetProperties[ name ];
if ( gadgetProperty.sharedAs ) {
if ( name != property.name &&
gadgetProperty.sharedAs == originalTopic) {
// gadget still has a property that listens for this topic so keep
// it in the list
found = true;
break;
}
}
}
// if this gadget no longer listens to oldTopic, remove it from the list
// of listeners
if (!found) {
delete this._subscribedTopics[originalTopic][changedGadget.gID];
}
// if this gadget isn't already on the list for newTopic, add it
if (!this._subscribedTopics[newTopic]) {
this._subscribedTopics[newTopic] = {};
}
if (!this._subscribedTopics[newTopic][changedGadget.getId()]) {
this._subscribedTopics[newTopic][changedGadget.getId()] = changedGadget;
}
}
this.notifyWiringPropertyComplete();
}
WiringManager.prototype.cancelWiringChanges = function()
{
// This is called when the property dialog is dismissed so none of the
// accumulated changes should occur.
this.unhighlightGadgets();
this._changeArray = null;
this._publishedTopics = null;
this._subscribedTopics = null;
this.wiringProperty = null;
}
WiringManager.prototype.commitWiringChanges = function()
{
var changeObj = null;
for (var property in this._changeArray) {
changeObj = this._changeArray[property];
changeObj.property.topic(changeObj.newTopic, changeObj.publishingGadget);
}
this.unhighlightGadgets();
this._changeArray = null;
this._publishedTopics = null;
this._subscribedTopics = null;
this.wiringProperty = null;
}
WiringManager.prototype.getWiringChange = function(property)
{
if (!this._changeArray) {
return null;
}
var uniqueKey = property.getGadget().getId()+'_'+property.name;
if (this._changeArray[uniqueKey]) {
return this._changeArray[uniqueKey];
}
return null;
}
/**
* The Mashup Maker class encapsulate the reponsibility of the mashup canvas such as
* loading and creating gadgets and associating the gadgets with a layout.
*
* @class MashupMaker
*/
function MashupMaker()
{
this.models = new Array();
this.isIE = (navigator.userAgent.indexOf('MSIE') != -1);
this._features = { editor: {file: 'editor.js'}};
// Not really used in the core code, yet...but we can now pass debug=true
// through newmashup.php to stop refimpldojo.js from loading. If that
// happens, this member variable will be set. So if we want to add more
// logging, etc. that we don't want to get rid of but isn't really necessary
// in the release, we can key off of this.
this._debug = false;
//config (for now need trailing '/')
/*
* Resolving the base URL for gadgets
*/
var index;
var loc = document.location.href.toString();
if ( document.location.search ) {
index = loc.indexOf( document.location.search );
loc = loc.substr( 0, index + 1 );
}
index = loc.lastIndexOf('/');
if (index != -1) {
OpenAjax.widget.baseURI = loc.substr(0, index + 1);
} else {
alert("Illegal location href:" + loc);
}
OpenAjax.widget.frameworkURI = OpenAjax.widget.baseURI + "../src/";
if (! this.propertyEditorURL) {
// if not property editor specified default to the simple one
this.propertyEditorURL = OpenAjax.widget.baseURI + 'gadgets/propertyeditor/propertyeditor_oam.xml';
}
this.wiringManager = new WiringManager();
// instantiate managed hub instance
this.loader = new OpenAjax.widget.Loader({
ManagedHub: {
onPublish: this.onWidgetPublish,
onSubscribe: this.onWidgetSubscribe,
onSecurityAlert: this.onWidgetSecurityAlert,
scope: this
}
});
this.hub = this.loader.hub;
}
MashupMaker.prototype.onWidgetPublish = function( topic, message, pcont, scont )
{
var pid = pcont ? pcont.getClientID() : "manager";
var sid = scont ? scont.getClientID() : "manager";
console.log( ">> onPublish: from=" + pid + " to=" + sid + " t=" + topic + " d=" + JSON.stringify(message) );
if ( this.publishManagers && this.publishManagers.length > 0 ) {
for ( var i = 0; i < this.publishManagers.length; i++ ) {
try {
this.publishManagers[i]( topic, message, pcont, scont );
} catch (e) {}
}
}
return true;
}
MashupMaker.prototype.onWidgetSubscribe = function( topic, container )
{
var id = container ? container.getClientID() : "manager";
console.log( "++ onSubscribe: w=" + id + " t=" + topic );
if ( this.subscribeManagers && this.subscribeManagers.length > 0 ) {
for ( var i = 0; i < this.subscribeManagers.length; i++ ) {
try {
this.subscribeManagers[i]( topic, container );
} catch (e) {}
}
}
return true;
}
MashupMaker.prototype.onWidgetSecurityAlert = function( source, alertType )
{
// XXX TODO
}
MashupMaker.prototype.addPubSubManagerCallback = function( type, callback )
{
switch( type ) {
case 'publish' :
if ( ! this.publishManagers ) {
this.publishManagers = [];
}
this.publishManagers.push( callback );
break;
case 'subscribe' :
if ( ! this.subscribeManagers ) {
this.subscribeManagers = [];
}
this.subscribeManagers.push( callback );
break;
}
}
MashupMaker.prototype.removePubSubManagerCallback = function( type, callback )
{
var cbs;
if ( type == 'publish' ) {
cbs = this.publishManagers;
} else if ( type == 'subscribe' ) {
cbs = this.subscribeManagers;
} else {
return;
}
for ( var i = 0; i < cbs.length; i++ ) {
if ( cbs[i] === callback ) {
cbs.splice( i, 1 );
break;
}
}
}
MashupMaker.prototype.getFeature = function(feature, loadedCB)
{
if (this._features[feature]) {
if (! this._features[feature].loaded) {
var head = document.getElementsByTagName('head').item(0);
var scriptBlock = document.createElement('SCRIPT');
scriptBlock.src = this._features[feature].file;
head.appendChild(scriptBlock);
this._featureCB = loadedCB;
this._pendingFeature = feature;
} else {
if (loadedCB) {
loadedCB(this._features[feature].instance);
}
}
}
}
MashupMaker.prototype.featureLoadedCB = function(instance)
{
this._features[this._pendingFeature].loaded = true;
this._features[this._pendingFeature].feature = instance;
if (this._featureCB) {
this._featureCB(instance);
}
}
MashupMaker.prototype.createGadget = function( url, targetDOM, id, nochrome, positionInfo, privileged )
{
if ( ! targetDOM ) {
targetDOM = this.getDefaultGadgetContainer();
}
// <table class="gadgetContainer" _widgetid="gID_4a0c4ba6cc238" style="" cellspacing="0" cellpadding="0">
// <tbody>
// <tr>
// <td>
// <div class="gadgetBoxHeader"><div></div></div>
// <div class="gadgetBoxContent">
// <div class="gadgetHeader">
// <IMG CLASS="absDeleteImg" SRC="gadgets/layout/images/deleteIcon_11x11.gif">
// <IMG ID="gID_4a0c4ba6cc238_propMenuTarget" CLASS="absPropEditImg" SRC="gadgets/layout/images/prop_edit.gif">
// <DIV CLASS="gadgetTitle">ColorPalette</DIV>
// </div>
// <div class="gadgetBody" id="gID_4a0c4ba6cc238_gadgetBody" style="width:208px;height:148px;">
// <SPAN>
// <iframe id="gID_4a0c4ba6cc238_frame" class="gadgetFrame" name="gID_4a0c4ba6cc238_frame" frameborder="0" scrolling="no" style="width:100%;height:100%"></iframe>
// <script>
// var __gID_4a0c4ba6cc238_data = [{"topic":"color","publish":"true","default":"#ffffff","name":"color","datatype":"String"}];
// var __gID_4a0c4ba6cc238_views = ["default"];
// mashupMaker.createGadgetInstance(
// 'gID_4a0c4ba6cc238',
// null,
// __gID_4a0c4ba6cc238_data,
// __gID_4a0c4ba6cc238_views,
// 'http://www.openajax.org/2008_InteropFest/mashupapp/gadgets/samples/gadgets/colorpalette/colorpalette_oam.xml',
// 'page',
// { url: 'http://c50.openajax.org/2008_InteropFest/mashupapp/gadgets/samples/showGadget.php?url=http%3A%2F%2Fwww.openajax.org%2F2008_InteropFest%2Fmashupapp%2Fgadgets%2Fsamples%2Fgadgets%2Fcolorpalette%2Fcolorpalette_oam.xml',
// frameID: 'gID_4a0c4ba6cc238_frame'
// }
// );
//
// </script>
// </SPAN>
// </div>
// </div>
// <div class="gadgetBoxFooter"><div></div></div>
// </td>
// </tr>
// </tbody>
// </table>
if ( ! id ) {
id = "gID_" + ((0x7fffffff * Math.random()) | 0).toString(16);
}
var response = '' +
'<table class="gadgetContainer" _widgetid="' + id + '" style="" cellspacing="0" cellpadding="0">' +
' <tbody>' +
' <tr>' +
' <td>';
if ( ! nochrome ) {
response +=
' <div class="gadgetBoxHeader"><div></div></div>' +
' <div class="gadgetBoxContent">' +
' <div class="gadgetHeader">' +
' <IMG CLASS="absDeleteImg" SRC="gadgets/layout/images/delete.png">' +
//' <IMG ID="' + id + '_propMenuTarget" CLASS="absPropEditImg" SRC="gadgets/layout/images/prop_edit.gif">' +
' <DIV CLASS="gadgetTitle">...loading...</DIV>' +
' </div>';
}
response +=
' <div class="gadgetBody" id="' + id + '_gadgetBody">' +
' </div>';
if ( ! nochrome ) {
response +=
' </div>' +
' <div class="gadgetBoxFooter"><div></div></div>';
}
response +=
' </td>' +
' </tr>' +
' </tbody>' +
'</table>';
var temp = document.createElement('DIV');
temp.innerHTML = response;
var containers = dojo.query(".gadgetContainer", temp);
var gadgetContainer = (containers && containers.length > 0) ? containers[0] : null;
var dropInfo = positionInfo || null;
if (gadgetContainer) {
this.layout.setLayoutStyles(gadgetContainer);
if (dropInfo) {
gadgetContainer.style.left = dropInfo.x;
gadgetContainer.style.top = dropInfo.y;
this._insertWidget( targetDOM, null, gadgetContainer, 'child' );
} else {
this._insertWidget( targetDOM, null, gadgetContainer, 'child' );
}
}
delete temp;
var mm = this;
this.loader.loadMetadata({
url: url,
onComplete: function( spec )
{
// Look through the spec for references to Dojo. If it is the same
// version as our "local" version, then replace with location of
// our Dojo build.
for ( var libname in spec.library ) {
if ( libname.toLowerCase() === "dojo" ) {
var lib = spec.library[ libname ];
var ver = lib.version.split(".");
if ( dojo.version.major == ver[0] && dojo.version.minor == ver[1] ) {
// update 'src' to point to our local Dojo
lib.src = dojo.baseUrl.match(/(.+\/)[^\/]+\/?$/)[1];
// if lib.src is relative, make absolute
if ( lib.src.charAt(0) !== "/" && ! lib.src.match(/^http(s)?\:/) ) {
lib.src = OpenAjax.widget.baseURI + lib.src;
}
// Some JS files may be XD versions of Dojo files --
// rename to remove the "xd".
// Also, remember the index of "dojo.js"; we'll use
// that later when adding in "refimpldojo.js".
var dojoJsIdx = -1;
for ( var i = 0; i < spec.require.length; i++ ) {
var req = spec.require[i];
if ( req._library_ !== "dojo" || req.type !== "javascript" ) {
continue;
}
req.src = req.src.replace( /(.+)\.xd\.js$/, "$1.js" );
if ( dojoJsIdx === -1 && req.src.match( /^(.*\/)?dojo\.js$/ ) ) {
dojoJsIdx = i;
}
}
// Add "refimpldojo.js" as a require, right after
// "dojo.js".
var arr = spec.require.slice( 0, dojoJsIdx + 1 );
var end = spec.require.slice( dojoJsIdx + 1 );
arr.push({
_library_: "dojo",
src: "dojo/refimpldojo.js",
type: "javascript"
});
spec.require = arr.concat( end );
}
break;
}
}
mm.loader.create({
id: id,
spec: spec,
target: id + "_gadgetBody",
sandbox: true,
onComplete: function( widget )
{
// (2) create the widget model -- used by MashupMaker...
// bind widget model to hub -- use __<ID> in order to avoid conflict when
// binding widget instance (which uses <ID>)
// this.hub.bind( "__" + id );
// var widgetModel = null;
// if ( type == "google" ) {
// widgetModel = new OpenAjax.widget.GoogleGadgetModel( id, properties, views, data.url, data.frameID );
// } else {
// widgetModel = new OpenAjax.widget.WidgetModel( id, properties, views );
// }
//
// if ( specurl != null ) {
// widgetModel.setSpecUrl( specurl );
// }
var container = dojo.query(".gadgetContainer[_widgetid=\"" + id + "\"]");
mm.layout.setLayoutStyles(container[0]);
widget.OpenAjax._site = new GadgetSite(container[0], id/*, views*/); // XXX JHP TODO handle views
// set title
if ( ! nochrome ) {
dojo.query( ".gadgetTitle", container[0] )[0].innerHTML = widget.OpenAjax._spec.name;
}
// add "gadgetFrame" class to widget iframe
dojo.query( "iframe", container[0] ).addClass( "gadgetFrame" );
// make sure that the widget container is on top of the other windows on
// the canvas
mm.layout.putOnTop( container );
mm.models[id] = widget; // XXX not needed - use OpenAjax.widget.byId()
mm.wiringManager.addGadget( widget );
// widgetModel.type = type;
// keep a reference to the Widget instance inside the WidgetModel
// (only for 'fragment' [non-sandboxed] widgets)
// widgetModel.widget = widget;
// XXX from sandboxedWidgetLoaded
console.log('widget loaded id='+id);
widget.OpenAjax._loaded = true;
},
onError: function( error )
{
// XXX handle error better
console.log(" !!! failed to create widget instance " + id + " :: " + url );
}
});
},
onError: function( error )
{
// XXX handle error
console.error( error );
}
});
}
MashupMaker.prototype._insertWidget = function( parentElement, referenceElement, widgetContainer, position )
{
// pull out any scripts -- we handle those below
// var scripts = [];
// var elems = widgetContainer.getElementsByTagName('SCRIPT');
// for ( var i = 0; i < elems.length; i++ ) {
// scripts.push( elems[i].parentNode.removeChild( elems[i] ) );
// }
if ( position == 'before' ) {
parentElement.insertBefore( widgetContainer, referenceElement );
} else {
parentElement.appendChild( widgetContainer );
}
// now that the content has been added to the page, run any scripts
// for ( var j = 0; j < scripts.length; j++ ) {
// if ( typeof window.execScript === 'function' ) {
// window.execScript( scripts[j].text );
// } else {
// (function() { eval.call( window, scripts[j].innerHTML ); } )();
// }
// }
}
// XXX this is the old version
MashupMaker.prototype.__createGadget = function(url, targetDOM, id, callback, argobj, nochrome, positionInfo)
{
if ( ! targetDOM ) {
targetDOM = this.getDefaultGadgetContainer();
}
var resourceUri = OpenAjax.widget.baseURI + 'insertGadget.php?url=' + encodeURIComponent(url);
if ( id ) {
resourceUri += '&id=' + id;
}
if ( nochrome ) {
resourceUri += '&nochrome=' + Boolean(nochrome).toString();
}
if ( this._debug ) {
resourceUri += '&debug=true';
}
var mm = this;
var bindArgs = {
preventCache: false,
handleAs: 'text',
url: resourceUri,
sync: true,
load: function(response) {
var temp = document.createElement('DIV');
temp.innerHTML = response;
var containers = dojo.query(".gadgetContainer", temp);
var gadgetContainer = null;
if (containers && containers.length > 0) {
gadgetContainer = containers[0];
}
var frames = temp.getElementsByTagName('IFRAME');
if (frames.length) {
var frame = frames.item(0);
mm._creatingGadget = {frame: frame, callback: callback, argobj: argobj}
}
var dropInfo = null;
if (typeof positionInfo != 'undefined') {
dropInfo = positionInfo;
}
if (gadgetContainer) {
mm.layout.setLayoutStyles(gadgetContainer);
if (dropInfo) {
gadgetContainer.style.left = dropInfo.x;
gadgetContainer.style.top = dropInfo.y;
this.insertWidget( targetDOM, null, gadgetContainer, 'child' );
} else {
this.insertWidget( targetDOM, null, gadgetContainer, 'child' );
}
}
delete temp;
},
error: function(error, request) {
alert(error);
},
insertWidget: function( parentElement, referenceElement, widgetContainer, position ) {
// pull out any scripts -- we handle those below
var scripts = [];
var elems = widgetContainer.getElementsByTagName('SCRIPT');
for ( var i = 0; i < elems.length; i++ ) {
scripts.push( elems[i].parentNode.removeChild( elems[i] ) );
}
if ( position == 'before' ) {
parentElement.insertBefore( widgetContainer, referenceElement );
} else {
parentElement.appendChild( widgetContainer );
}
// now that the content has been added to the page, run any scripts
for ( var j = 0; j < scripts.length; j++ ) {
if ( typeof window.execScript === 'function' ) {
window.execScript( scripts[j].text );
} else {
(function() { eval.call( window, scripts[j].innerHTML ); } )();
}
}
}
};
dojo.xhrGet(bindArgs);
}
MashupMaker.prototype.getDefaultGadgetContainer = function()
{
var dgc = document.getElementById('__replaceablecontent__');
if ( ! dgc ) {
if (document.body) {
dgc = document.createElement('DIV');
dgc.setAttribute('id', '__replaceablecontent__');
document.body.appendChild(dgc);
}
}
return dgc;
}
/**
* @param {String} id The ID to be used for the created widget.
* @param {Function} widgetClass Name of widget class. If not specified,
* uses OpenAjax.widget.Widget.
* @param {array} properties Array of properties for the widget -- these are
* either read from persistent storage or the defaults as specified
* in the widget spec.
* @param {array} views Array of view names
* @param {String} specurl URL for the widget specification file.
* @param {String} type Widget type. Supported values are 'fragment' or 'page'.
* If omitted, defaults to 'fragment'.
* @param {Object} data A data object that is specific to the given 'type'
* @param {Object} dimensions Initial dimensions of widget - {w, h}
*
* @returns the widget instance
* @type Object
*/
MashupMaker.prototype.createGadgetInstance = function( id, widgetClass,
properties, views, specurl, type, data, dimensions )
{
// (1) create the object that is used by the widget...
if ( type != "google" ) { // don't create widget instance for Google Gadgets
// bind widget instance to hub
//AP this.hub.bind( id );
if ( ! type ) {
type = 'fragment';
}
var widget = null;
switch ( type ) {
case 'fragment' :
widget = OpenAjax.widget.Widget.createWidgetObjectInstance( id,
widgetClass, properties, null, dimensions, views );
break;
case 'page' :
// Use SMash to connect to 'page' widgets
function smashLoadError( id, error ) {
// XXX handle smash error
alert( 'The widget ' + id + ' failed to load (' + error + ')' );
}
//AP var newURI = smash.prepareForLoad({ clientName: id, uri: data.url, commErrorCallback: smashLoadError });
// set iframe URL (load sandboxed gadget)
dojo.byId( data.frameID ).src = newURI;
break;
default :
throw 'unsupported widget type';
}
// listen for when this widget gets loaded
var callback = function( success, subHandle ) {
if ( !success ) {
// XXX handle error
alert( "subscribe failed" );
}
};
this.hub.subscribe( "openajax.widget." + id + "._loaded",
callback, dojo.hitch(this, this.sandboxedWidgetLoaded) );
}
// (2) create the widget model -- used by MashupMaker...
// bind widget model to hub -- use __<ID> in order to avoid conflict when
// binding widget instance (which uses <ID>)
this.hub.bind( "__" + id );
var widgetModel = null;
if ( type == "google" ) {
widgetModel = new OpenAjax.widget.GoogleGadgetModel( id, properties, views, data.url, data.frameID );
} else {
widgetModel = new OpenAjax.widget.WidgetModel( id, properties, views );
}
if ( specurl != null ) {
widgetModel.setSpecUrl( specurl );
}
var container = dojo.query(".gadgetContainer[_widgetid=\""+widgetModel.getId()+"\"]");
this.layout.setLayoutStyles(container[0]);
widgetModel._site = new GadgetSite(container[0], id, views);
// make sure that the widget container is on top of the other windows on
// the canvas
this.layout.putOnTop(widgetModel.getSite().getContainer());
this.models[id] = widgetModel;
this.wiringManager.addGadget(widgetModel);
widgetModel.type = type;
// keep a reference to the Widget instance inside the WidgetModel
// (only for 'fragment' [non-sandboxed] widgets)
widgetModel.widget = widget;
return widget;
}
MashupMaker.prototype.onload = function(event, debug)
{
this.hub.publish( 'openajax.widget.mashup.load', null );
// cycle through all 'fragment' widgets and call their __onLoad() method
for ( var id in this.models ) {
var w = this.models[ id ].widget;
if ( w && typeof w.__onLoad === 'function' ) {
w.__onLoad();
}
}
this._debug = debug;
}
MashupMaker.prototype.handleWidgetResize = function( widgetID )
{
if ( ! widgetID ) {
return;
}
var widget = this.getWidgetModelFromID( widgetID );
var dimensions = widget.OpenAjax._site.getDimensions();
widget.OpenAjax.adjustDimensions( dimensions ); // XXX need a better API for this?
}
MashupMaker.prototype.deleteGadget = function( widgetID )
{
// notify widget instance of removal -- before removing widget from canvas
// we have to wait for acknowledgment that it has run its 'onUnload'
// callback.
var widget = this.models[ widgetID ];
if ( widget.OpenAjax._loaded ) {
widget.OpenAjax._unload( dojo.hitch( this, this.finishDeleteGadget ) );
} else {
this.finishDeleteGadget( widgetID );
}
}
MashupMaker.prototype.finishDeleteGadget = function( widgetID )
{
var widgetModel = this.models[ widgetID ];
this.wiringManager.removeGadget( widgetModel );
var site = widgetModel.OpenAjax._site;
if ( site ) {
var container = site.getContainer();
// We cannot delete the widget container directly right now, since one
// of the methods up the stack is a SMash method running from the
// tunnel.html. If we delete the container, we delete the tunnel IFRAME,
// which means that when this method returns to the SMash code, the
// Javascript environment has been destroyed and hilarity ensues. So
// instead, we use a setTimeout with an interval of zero to allow this
// stream of code to conclude, and then delete the widget container.
setTimeout( function() {
container.parentNode.removeChild(container);
delete container;
}, 0 );
}
delete this.models[ widgetID ];
}
/**
* If the widgetID belongs to a widget of type 'page', we'll disconnect the
* widget from smash and tell the widget that it has been removed. The gadget's
* IFRAME src will also be changed to force its document to unload. The
* widgetModel for the widget will still live on, though. maybeDisconnectWidget
* should be called before moving the gadgetContainer in the DOM otherwise smash
* will complain on browsers where IFRAMEs reload when moved in the DOM (like
* Firefox).
*
* @param {String} widgetID The ID of the widget to disconnect
*
* @returns whether the widget was actually disconnected such that a
* maybeReconnectWidget would be necessary
* @type boolean
*/
MashupMaker.prototype.maybeDisconnectWidget = function( widgetID, callback )
{
var widgetModel = this.models[ widgetID ];
// If this isn't a 'page' type of widget, ignore. We only want to be able
// to disconnect a widget in certain instances and we should never have to
// disconnect a non 'page' widget. One such instance is when moving a
// 'page' widget in the DOM since on FF this causes a reload of IFRAMES that
// smash doesn't like.
if ( widgetModel.type != 'page' ) {
return false;
}
var that = this;
this.hub.subscribe( 'openajax.widget.' + widgetID + '._removed',
function( success, subHandle, callback ) {
if ( ! success ) {
// something went wrong -- log the error and finish deletion
console.log( "ERROR: failed to subscribe to '_removed'" );
dojo.hitch( that, That.finishDisconnectWidget, callback );
}
},
dojo.hitch( that, that.finishDisconnectWidget, callback )
);
// notify widget instance of removal
widgetModel.fireEvent( "remove" );
return true;
}
MashupMaker.prototype.finishDisconnectWidget = function( disconnectCallback, subHandle, topic, data )
{
// don't need to listen on this topic anymore
subHandle.unsubscribe();
// pull widget ID from topic
var widgetID = topic.split('.')[2];
smash.prepareForUnload( widgetID );
dojo.byId( widgetID + "_frame" ).src="about:blank";
// don't delete the widget model since we need info from it to reconnect
// We cannot allow the gadgetContainer to move around right now, since one
// of the methods up the stack is a SMash method running from the
// tunnel.html. If we move/remove the container, we delete the tunnel IFRAME,
// which means that when this method returns to the SMash code, the
// Javascript environment has been destroyed and hilarity ensues. So
// instead, we use a setTimeout with an interval of zero to allow this
// stream of code to conclude, and then call the callback. We are assuming
// that the callback is going to move/remove the gadgetContainer otherwise
// way disconnect the gadget?
setTimeout( function(){disconnectCallback();}, 0 );
}
/**
* After maybeDisconnectWidget is called, the caller should reconnect widgets
* only after allowing the disconnected widgets' frames to unload. This is
* because smash has logic in the frames' onunload handler. So this is
* probably best done doing something like setTimeout(reconnectMyWidgetsFunc, 0)
* to set it up after the onunload events in the event queue.
*
* @param {String} widgetID The ID of the widget to reconnect
*
*/
MashupMaker.prototype.maybeReconnectWidget = function( widgetID )
{
// XXX We should also probably only do this if we can determine if the
// widgetModel has already been disconnected. Currently this doesn't do
// anything so commenting it out.
var widgetModel = this.models[ widgetID ];
// if (widgetModel.connHandle.isConnected()) {
// return;
// }
// If this isn't a 'page' type of widget, ignore. We only want to be able
// to reconnect a widget that has been disconnected and we should never have
// to disconnect a non 'page' widget.
if ( widgetModel.type != 'page' ) {
return;
}
// (2) create the object that is used by the widget...
// bind widget instance to hub
this.hub.bind( widgetID );
// Use SMash to connect to 'page' widgets
function smashLoadError( widgetID, error ) {
// XXX handle smash error
alert( 'The widget ' + widgetID + ' failed to load (' + error + ')' );
}
// set iframe URL (load sandboxed gadget), pass in the current widget
// properties so that user changes aren't lost
var properties = widgetModel.getPropertiesDatums();
var propertiesJSON = JSON.stringify(properties);
var showGadgetUrl =
OpenAjax.widget.baseURI+'showGadget.php?url='
+ encodeURI(widgetModel.getSpecUrl()
+ '&properties='+ propertiesJSON);
var newURI = smash.prepareForLoad({ clientName: widgetID, uri: showGadgetUrl,
commErrorCallback: smashLoadError });
dojo.byId( widgetID + "_frame" ).src = newURI;
}
/**
* @param {OpenAjax.widget.Widget} sandboxedWidget Sandboxed widget instance
* @param {DOM:Element} container DOM content related to given widget instance
*/
MashupMaker.prototype.sandboxedWidgetLoaded = function( subHandle, topic, dataString )
{
// pull widget ID from topic
var widgetID = topic.split('.')[2];
var widgetModel = this.models[ widgetID ];
console.log('widget loaded id='+widgetID);
// now that widget is loaded, don't need to listen on this topic anymore
subHandle.unsubscribe();
if (this._creatingGadget) {
// fire an 'insert' event
widgetModel.fireEvent( 'insert' );
var callback = this._creatingGadget.callback;
if ( callback && typeof callback === "function" ) {
console.log('createGadget calling callback id='+widgetID);
callback( /*sandboxedWidget*/ null, this._creatingGadget.argobj );
}
this._creatingGadget = null;
}
widgetModel.loaded = true;
}
MashupMaker.prototype.openGadgetView = function( widgetID, viewName )
{
var widgetModel = this.models[ widgetID ];
widgetModel.fireEvent( "_showView", viewName );
}
MashupMaker.prototype.editGadget = function( widgetID )
{
var widget = OpenAjax.widget.byId( widgetID );
if ( widget.views && dojo.indexOf( widget.views, "edit" ) != -1 ) { // XXX 'views' should exist; shouldn't need to check for it
// widget has an 'edit' content view
widget.fireEvent( "_showView", "edit" );
} else {
// show standard property editor
this._generatePropertyEditor( widget );
}
}
MashupMaker.prototype.shareGadget = function( widgetID )
{
var widgetModel = this.models[ widgetID ];
var script = "&lt;script src=\"" + OpenAjax.widget.baseURI + "embedWidget.php?specURL="
+ widgetModel.getSpecUrl() + "\"&gt;&lt;/script&gt;";
var dialogDiv = dojo.byId('__OAA_shareDialog_container');
dojo.parser.parse(dialogDiv);
dojo.byId("__OAA_shareDialog_script_tag").innerHTML = script;
dijit.byId("__OAA_share_dialog").show();
return;
}
MashupMaker.prototype._generatePropertyEditor = function( widget )
{
if (this.propertyEditorURL) {
var widgetProps = widget.OpenAjax._spec.property;
//if there are no properties, then show an alert
var hasProps = false;
for ( var n in widgetProps ) { // XXX is there a better way to do this?
hasProps = true;
break;
}
if ( ! widgetProps || ! hasProps ) {
alert( "No properties to edit!" );
return;
}
var propDlg = dijit.byId('propertyDialog');
var propDlgContents = dojo.byId(propDlg.id+'_Contents');
var d = new Date();
var t = d.getTime();
var gid = 'gID_' + t;
// var argobj = {gid:gid, editGadgetID: widget.OpenAjax.getId()};
console.log('_generatePropertyEditor gid='+gid);
if (propDlgContents) {
if (!propDlg.propertyEditor) {
var sid = this.hub.subscribe( "nomad-propertyEditor-ready",
function( topic, data ) {
this.hub.unsubscribe( sid );
this._completePropertyEditor( widget.OpenAjax.getId() );
},
this
);
mashupMaker.createGadget(this.propertyEditorURL, propDlgContents, gid,
true, null, true);
} else {
this._completePropertyEditor( widget.OpenAjax.getId() );
}
}
} else {
throw 'No property editor defined';
}
}
/**
* @param {OpenAjax.widget.Widget} propEditorWidget Property Editor widget instance (may be null)
* @param obj Callback object that contains ID of Property Editor widget ('gid')
and ID of widget whose properties we are editing
*/
MashupMaker.prototype._completePropertyEditor = function( editGadgetID )
{
console.log( '_completePropertyEditor gid=' + this.propertyEditor.OpenAjax.getId() );
var propDlg = dijit.byId('propertyDialog');
var propDlgContents = dojo.byId(propDlg.id+'_Contents');
if (propDlgContents) {
if (!propDlg.propertyEditor) {
// propDlg.propertyEditor = propEditorWidget;
propDlg.propertyEditor = this.propertyEditor;
}
// some widgets add tooltips which will tweak the body size in an
// undetectable way and scrollbars will show seemingly without reason
// so get rid of scrollbars with overflow hidden
var widget = this.getWidgetModelFromID( editGadgetID );
var propDlgContainerID = propDlg.propertyEditor.OpenAjax.getId() + 'propertyEditor';
propDlg.propertyEditor.editGadget( widget, propDlgContainerID, false, propDlg );
propDlg.show();
}
}
MashupMaker.prototype.getWidgetModelFromID = function( id )
{
// return this.models[ id ];
// XXX delete this function and replace with OpenAjax.widget.byId()
return OpenAjax.widget.byId( id );
}
MashupMaker.prototype.exportElement = function(root, pruneCB)
{
if (! root && ! this.root) {
root = document.getElementById('__replaceablecontent__');
if (! root) {
root = document.body;
}
} else {
root = root ? root : (this.root ? this.root : document.body);
}
var clone = root.cloneNode(true);
var gadgets = this.layout.getGadgets(clone);
for (var i = 0; i < gadgets.length; i++) {
var gadgetInfo = gadgets[i];
var gadget = gadgetInfo.gadget;
if ( ! gadget.getSite() ) {
continue;
}
if (pruneCB) {
pruneCB(gadget, gadgetInfo.site);
continue;
}
var site = gadgetInfo.site;
if (! site) {
continue;
}
var body = site.getBody();
var gadgetParent = body;
var nextSibling = null;
/*
* remove gadget container chrome
*/
while ( gadgetParent.parentNode ) {
if ( gadgetParent.className == "gadgetContainer" ) {
temp = gadgetParent.parentNode;
nextSibling = gadgetParent.nextSibling;
temp.removeChild(gadgetParent);
gadgetParent = temp;
break;
} else {
gadgetParent = gadgetParent.parentNode;
}
}
var properties = gadget.getProperties();
var newWidget = gadgetParent.ownerDocument.createElement('SPAN');
// need to insert the replacement span into the same place in the DOM
// from which we removed the gadget container in case it was amongst
// text nodes, in a table, etc.
gadgetParent.insertBefore(newWidget, nextSibling);
newWidget.className = 'widget';
newWidget.setAttribute('id', gadget.getId());
newWidget.setAttribute('url', gadget.getSpecUrl());
var dimensions = gadget.getSite().getDimensions();
newWidget.setAttribute('width', dimensions['width'] + 'px');
newWidget.setAttribute('height', dimensions['height'] + 'px');
var position = gadget.getSite().getPosition();
newWidget.setAttribute('top', position.y + 'px');
newWidget.setAttribute('left', position.x + 'px');
if ( properties ) {
var newProperties = gadgetParent.ownerDocument.createElement('SPAN');
newWidget.appendChild(newProperties);
newProperties.className = 'properties';
newProperties.setAttribute('name', 'userproperties');
for (var j = 0; j < properties.length; j++) {
var prop = properties[j];
var newProperty = gadgetParent.ownerDocument.createElement('SPAN');
newProperty.className = 'property';
newProperties.appendChild(newProperty);
newProperty.setAttribute('name', prop.name());
newProperty.setAttribute('datatype', prop.type());
newProperty.setAttribute('publish', prop.publish());
newProperty.setAttribute('subscribe', prop.subscribe());
newProperty.setAttribute('topic', prop.topic());
var sbgId = prop.getSingleBoundGadget();
if ( sbgId ) {
newProperty.setAttribute('singlebound', sbgId);
}
var propValue = prop.evanescent() == "true" ? "" : prop.encodedValue();
var tnode = gadgetParent.ownerDocument.createTextNode(propValue);
newProperty.appendChild(tnode);
}
}
}
return clone;
}
MashupMaker.prototype.getDragManager = function() {
return this._dragInProgress;
}
MashupMaker.prototype.setDragManager = function(/* DragManager*/ dragManager) {
this._dragInProgress = dragManager;
}
MashupMaker.prototype.startDND = function(event, dragItem) {
dijit.byId('searchResults')._toggleDropDown();
this.setDragManager(new DragManager(event, dragItem, null, null));
this.layout.dragPaletteItem(this.getDragManager().dndNode, event.pageX, event.pageY, dojo.hitch(this, this.stopDND));
dojo.stopEvent(event);
return;
}
MashupMaker.prototype.dropCallback = function(/* event */ event, /* drop item */ item) {
return;
}
MashupMaker.prototype.stopDND = function(/* boolean */dropSuccessful, /* object */dropInfo) {
var dragManager = mashupMaker.getDragManager();
// if the drop occurred (drag not cancelled) then create a gadget at the
// drop location
if (dropSuccessful) {
this.createGadget(dragManager.getDragItem().itemUrl, null, null, false, dropInfo);
}
this.getDragManager().cancelDrag();
this.setDragManager(null);
dijit.byId('searchResults')._toggleDropDown();
/* XXX do I need to set focus back to the palette item that was dragged when
* the palette reappers?
*/
return;
}
MashupMaker.prototype.getGadgets = function() {
var root = document.getElementById('__replaceablecontent__');
if (! root) {
root = document.body;
}
return this.layout.getGadgets(root);
}
MashupMaker.prototype.getWiringManager = function() {
if (!this.wiringManager) {
this.wiringManager = new WiringManager();
}
return this.wiringManager;
}
MashupMaker.prototype.getRepositoryList = function(callback) {
if (!this._repositoryList) {
//AP OpenAjax.widget.baseURI+
var resourceUri = 'http://www.openajax.org/samples/mashupapp/repository/oscontroller.php?action=getRepositories';
var that = this;
var bindArgs = {
handleAs: 'json',
url: resourceUri,
sync: true,
load: function(response) {
that._repositoryList = response;
callback(response);
},
error: function(error, request) {
console.log('Error retrieving repository list: \n'+error.message);
}
};
dojo.xhrGet(bindArgs);
}
callback(this._repositoryList);
}
MashupMaker.prototype.getDebug = function() {
return this._debug;
}
MashupMaker.prototype.setEditorWidget = function(editorWidget) {
this.layout.setEditorWidget(editorWidget);
}
if (typeof Node == 'undefined') {
Node = {};
Node.ELEMENT_NODE = 1;
Node.TEXT_NODE = 3;
}
var mashupMaker = new MashupMaker((typeof mashupArgs != 'undefined' ? mashupArgs : null));
mashupMaker.propertyEditorURL = OpenAjax.widget.baseURI + 'nomad/propertyeditor/propertyeditor_oam.xml';
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<!--
Copyright 2009 OpenAjax Alliance
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>widget</title>
<script src="loader.js"></script>
<script>
(function() {
var scriptsLoaded = false,
windowLoaded = false;
function onWindowLoad( event ) {
windowLoaded = true;
loadWidget();
}
function onScriptsLoad( success, error ) {
// XXX handle error (success == false)
scriptsLoaded = true;
loadWidget();
}
function loadWidget() {
if ( scriptsLoaded && windowLoaded ) {
OpenAjax.widget._createRemoteWidget( document.getElementById("oaa_widget") );
}
}
function queryURLParam( param ) {
var result = new RegExp( "[\\?&]" + param + "=([^&#]*)" ).exec( window.location.search );
if ( result ) {
return decodeURIComponent( result[1].replace( /\+/g, "%20" ) );
}
return null;
}
window.onload = onWindowLoad;
var head = document.getElementsByTagName('HEAD').item(0);
var base = document.createElement( "base" );
base.setAttribute( "href", queryURLParam( "oawb" ) );
head.appendChild( base );
// load OpenAjax Hub files
var scripts;
var oaaHubJS = queryURLParam( "oawh" );
var m = oaaHubJS.match( /openajax(?:ManagedHub-.+|-mashup)\.js$/i );
if ( m ) {
var hubRoot = oaaHubJS.substring( 0, m.index );
var baseName = oaaHubJS.substring( m.index );
switch ( baseName.toLowerCase() ) {
case "openajaxmanagedhub-all.js":
scripts = [ { src: hubRoot + "OpenAjaxManagedHub-all.js" } ];
break;
case "openajaxmanagedhub-core.js":
scripts = [ { src: hubRoot + "OpenAjaxManagedHub-core.js" },
{ src: hubRoot + "json2.js" },
{ src: hubRoot + "crypto.js" },
{ src: hubRoot + "iframe.js" },
{ src: hubRoot + "FIM.js" } ];
break;
case "openajaxmanagedhub-std.js":
scripts = [ { src: hubRoot + "OpenAjaxManagedHub-std.js" },
{ src: hubRoot + "FIM.js" } ];
break;
case "openajax-mashup.js":
scripts = [ { src: hubRoot + "OpenAjax-mashup.js" },
{ src: hubRoot + "containers/iframe/json2.js" },
{ src: hubRoot + "containers/iframe/crypto.js" },
{ src: hubRoot + "containers/iframe/iframe.js" },
{ src: hubRoot + "containers/iframe/FIM.js" } ];
break;
}
}
// load "loader.js" -- assume it is a sibling of this file
scripts.push( { src: /(.+:\/\/.+\/)/.exec( window.location.href )[1] + "loader.js" } );
__openajax_widget__._loadScripts( scripts, false, onScriptsLoad );
})();
</script>
<style>
body {
margin: 0;
}
</style>
</head>
<body>
<div id="oaa_widget"></div>
</body>
</html>
var OpenAjax=OpenAjax||{};
if(!OpenAjax.hub){
OpenAjax.hub=function(){
var _1={};
var _2="org.openajax.hub.";
return {implementer:"http://openajax.org",implVersion:"2.0.7",specVersion:"2.0",implExtraData:{},libraries:_1,registerLibrary:function(_3,_4,_5,_6){
_1[_3]={prefix:_3,namespaceURI:_4,version:_5,extraData:_6};
this.publish(_2+"registerLibrary",_1[_3]);
},unregisterLibrary:function(_7){
this.publish(_2+"unregisterLibrary",_1[_7]);
delete _1[_7];
}};
}();
OpenAjax.hub.Error={BadParameters:"OpenAjax.hub.Error.BadParameters",Disconnected:"OpenAjax.hub.Error.Disconnected",Duplicate:"OpenAjax.hub.Error.Duplicate",NoContainer:"OpenAjax.hub.Error.NoContainer",NoSubscription:"OpenAjax.hub.Error.NoSubscription",NotAllowed:"OpenAjax.hub.Error.NotAllowed",WrongProtocol:"OpenAjax.hub.Error.WrongProtocol",IncompatBrowser:"OpenAjax.hub.Error.IncompatBrowser"};
OpenAjax.hub.SecurityAlert={LoadTimeout:"OpenAjax.hub.SecurityAlert.LoadTimeout",FramePhish:"OpenAjax.hub.SecurityAlert.FramePhish",ForgedMsg:"OpenAjax.hub.SecurityAlert.ForgedMsg"};
OpenAjax.hub._debugger=function(){
};
OpenAjax.hub.ManagedHub=function(_8){
if(!_8||!_8.onPublish||!_8.onSubscribe){
throw new Error(OpenAjax.hub.Error.BadParameters);
}
this._p=_8;
this._onUnsubscribe=_8.onUnsubscribe?_8.onUnsubscribe:null;
this._scope=_8.scope||window;
if(_8.log){
var _9=this;
this._log=function(_a){
try{
_8.log.call(_9._scope,"ManagedHub: "+_a);
}
catch(e){
OpenAjax.hub._debugger();
}
};
}else{
this._log=function(){
};
}
this._subscriptions={c:{},s:null};
this._containers={};
this._seq=0;
this._active=true;
this._isPublishing=false;
this._pubQ=[];
};
OpenAjax.hub.ManagedHub.prototype.subscribeForClient=function(_b,_c,_d){
this._assertConn();
if(this._invokeOnSubscribe(_c,_b)){
return this._subscribe(_c,this._sendToClient,this,{c:_b,sid:_d});
}
throw new Error(OpenAjax.hub.Error.NotAllowed);
};
OpenAjax.hub.ManagedHub.prototype.unsubscribeForClient=function(_e,_f){
this._unsubscribe(_f);
this._invokeOnUnsubscribe(_e,_f);
};
OpenAjax.hub.ManagedHub.prototype.publishForClient=function(_10,_11,_12){
this._assertConn();
this._publish(_11,_12,_10);
};
OpenAjax.hub.ManagedHub.prototype.disconnect=function(){
this._active=false;
for(var c in this._containers){
this.removeContainer(this._containers[c]);
}
};
OpenAjax.hub.ManagedHub.prototype.getContainer=function(_13){
var _14=this._containers[_13];
return _14?_14:null;
};
OpenAjax.hub.ManagedHub.prototype.listContainers=function(){
var res=[];
for(var c in this._containers){
res.push(this._containers[c]);
}
return res;
};
OpenAjax.hub.ManagedHub.prototype.addContainer=function(_15){
this._assertConn();
var _16=_15.getClientID();
if(this._containers[_16]){
throw new Error(OpenAjax.hub.Error.Duplicate);
}
this._containers[_16]=_15;
};
OpenAjax.hub.ManagedHub.prototype.removeContainer=function(_17){
var _18=_17.getClientID();
if(!this._containers[_18]){
throw new Error(OpenAjax.hub.Error.NoContainer);
}
_17.remove();
delete this._containers[_18];
};
OpenAjax.hub.ManagedHub.prototype.subscribe=function(_19,_1a,_1b,_1c,_1d){
this._assertConn();
this._assertSubTopic(_19);
if(!_1a){
throw new Error(OpenAjax.hub.Error.BadParameters);
}
_1b=_1b||window;
if(!this._invokeOnSubscribe(_19,null)){
this._invokeOnComplete(_1c,_1b,null,false,OpenAjax.hub.Error.NotAllowed);
return;
}
var _1e=this;
function _1f(_20,_21,sd,_22){
if(_1e._invokeOnPublish(_20,_21,_22,null)){
try{
_1a.call(_1b,_20,_21,_1d);
}
catch(e){
OpenAjax.hub._debugger();
_1e._log("caught error from onData callback to Hub.subscribe(): "+e.message);
}
}
};
var _23=this._subscribe(_19,_1f,_1b,_1d);
this._invokeOnComplete(_1c,_1b,_23,true);
return _23;
};
OpenAjax.hub.ManagedHub.prototype.publish=function(_24,_25){
this._assertConn();
this._assertPubTopic(_24);
this._publish(_24,_25,null);
};
OpenAjax.hub.ManagedHub.prototype.unsubscribe=function(_26,_27,_28){
this._assertConn();
if(!_26){
throw new Error(OpenAjax.hub.Error.BadParameters);
}
this._unsubscribe(_26);
this._invokeOnUnsubscribe(null,_26);
this._invokeOnComplete(_27,_28,_26,true);
};
OpenAjax.hub.ManagedHub.prototype.isConnected=function(){
return this._active;
};
OpenAjax.hub.ManagedHub.prototype.getScope=function(){
return this._scope;
};
OpenAjax.hub.ManagedHub.prototype.getSubscriberData=function(_29){
this._assertConn();
var _2a=_29.split(".");
var sid=_2a.pop();
var sub=this._getSubscriptionObject(this._subscriptions,_2a,0,sid);
if(sub){
return sub.data;
}
throw new Error(OpenAjax.hub.Error.NoSubscription);
};
OpenAjax.hub.ManagedHub.prototype.getSubscriberScope=function(_2b){
this._assertConn();
var _2c=_2b.split(".");
var sid=_2c.pop();
var sub=this._getSubscriptionObject(this._subscriptions,_2c,0,sid);
if(sub){
return sub.scope;
}
throw new Error(OpenAjax.hub.Error.NoSubscription);
};
OpenAjax.hub.ManagedHub.prototype.getParameters=function(){
return this._p;
};
OpenAjax.hub.ManagedHub.prototype._sendToClient=function(_2d,_2e,sd,_2f){
if(!this.isConnected()){
return;
}
if(this._invokeOnPublish(_2d,_2e,_2f,sd.c)){
sd.c.sendToClient(_2d,_2e,sd.sid);
}
};
OpenAjax.hub.ManagedHub.prototype._assertConn=function(){
if(!this.isConnected()){
throw new Error(OpenAjax.hub.Error.Disconnected);
}
};
OpenAjax.hub.ManagedHub.prototype._assertPubTopic=function(_30){
if(!_30||_30===""||(_30.indexOf("*")!=-1)||(_30.indexOf("..")!=-1)||(_30.charAt(0)==".")||(_30.charAt(_30.length-1)==".")){
throw new Error(OpenAjax.hub.Error.BadParameters);
}
};
OpenAjax.hub.ManagedHub.prototype._assertSubTopic=function(_31){
if(!_31){
throw new Error(OpenAjax.hub.Error.BadParameters);
}
var _32=_31.split(".");
var len=_32.length;
for(var i=0;i<len;i++){
var p=_32[i];
if((p==="")||((p.indexOf("*")!=-1)&&(p!="*")&&(p!="**"))){
throw new Error(OpenAjax.hub.Error.BadParameters);
}
if((p=="**")&&(i<len-1)){
throw new Error(OpenAjax.hub.Error.BadParameters);
}
}
};
OpenAjax.hub.ManagedHub.prototype._invokeOnComplete=function(_33,_34,_35,_36,_37){
if(_33){
try{
_34=_34||window;
_33.call(_34,_35,_36,_37);
}
catch(e){
OpenAjax.hub._debugger();
this._log("caught error from onComplete callback: "+e.message);
}
}
};
OpenAjax.hub.ManagedHub.prototype._invokeOnPublish=function(_38,_39,_3a,_3b){
try{
return this._p.onPublish.call(this._scope,_38,_39,_3a,_3b);
}
catch(e){
OpenAjax.hub._debugger();
this._log("caught error from onPublish callback to constructor: "+e.message);
}
return false;
};
OpenAjax.hub.ManagedHub.prototype._invokeOnSubscribe=function(_3c,_3d){
try{
return this._p.onSubscribe.call(this._scope,_3c,_3d);
}
catch(e){
OpenAjax.hub._debugger();
this._log("caught error from onSubscribe callback to constructor: "+e.message);
}
return false;
};
OpenAjax.hub.ManagedHub.prototype._invokeOnUnsubscribe=function(_3e,_3f){
if(this._onUnsubscribe){
var _40=_3f.slice(0,_3f.lastIndexOf("."));
try{
this._onUnsubscribe.call(this._scope,_40,_3e);
}
catch(e){
OpenAjax.hub._debugger();
this._log("caught error from onUnsubscribe callback to constructor: "+e.message);
}
}
};
OpenAjax.hub.ManagedHub.prototype._subscribe=function(_41,_42,_43,_44){
var _45=_41+"."+this._seq;
var sub={scope:_43,cb:_42,data:_44,sid:this._seq++};
var _46=_41.split(".");
this._recursiveSubscribe(this._subscriptions,_46,0,sub);
return _45;
};
OpenAjax.hub.ManagedHub.prototype._recursiveSubscribe=function(_47,_48,_49,sub){
var _4a=_48[_49];
if(_49==_48.length){
sub.next=_47.s;
_47.s=sub;
}else{
if(typeof _47.c=="undefined"){
_47.c={};
}
if(typeof _47.c[_4a]=="undefined"){
_47.c[_4a]={c:{},s:null};
this._recursiveSubscribe(_47.c[_4a],_48,_49+1,sub);
}else{
this._recursiveSubscribe(_47.c[_4a],_48,_49+1,sub);
}
}
};
OpenAjax.hub.ManagedHub.prototype._publish=function(_4b,_4c,_4d){
if(this._isPublishing){
this._pubQ.push({t:_4b,d:_4c,p:_4d});
return;
}
this._safePublish(_4b,_4c,_4d);
while(this._pubQ.length>0){
var pub=this._pubQ.shift();
this._safePublish(pub.t,pub.d,pub.p);
}
};
OpenAjax.hub.ManagedHub.prototype._safePublish=function(_4e,_4f,_50){
this._isPublishing=true;
var _51=_4e.split(".");
this._recursivePublish(this._subscriptions,_51,0,_4e,_4f,_50);
this._isPublishing=false;
};
OpenAjax.hub.ManagedHub.prototype._recursivePublish=function(_52,_53,_54,_55,msg,_56){
if(typeof _52!="undefined"){
var _57;
if(_54==_53.length){
_57=_52;
}else{
this._recursivePublish(_52.c[_53[_54]],_53,_54+1,_55,msg,_56);
this._recursivePublish(_52.c["*"],_53,_54+1,_55,msg,_56);
_57=_52.c["**"];
}
if(typeof _57!="undefined"){
var sub=_57.s;
while(sub){
var sc=sub.scope;
var cb=sub.cb;
var d=sub.data;
if(typeof cb=="string"){
cb=sc[cb];
}
cb.call(sc,_55,msg,d,_56);
sub=sub.next;
}
}
}
};
OpenAjax.hub.ManagedHub.prototype._unsubscribe=function(_58){
var _59=_58.split(".");
var sid=_59.pop();
if(!this._recursiveUnsubscribe(this._subscriptions,_59,0,sid)){
throw new Error(OpenAjax.hub.Error.NoSubscription);
}
};
OpenAjax.hub.ManagedHub.prototype._recursiveUnsubscribe=function(_5a,_5b,_5c,sid){
if(typeof _5a=="undefined"){
return false;
}
if(_5c<_5b.length){
var _5d=_5a.c[_5b[_5c]];
if(!_5d){
return false;
}
this._recursiveUnsubscribe(_5d,_5b,_5c+1,sid);
if(!_5d.s){
for(var x in _5d.c){
return true;
}
delete _5a.c[_5b[_5c]];
}
}else{
var sub=_5a.s;
var _5e=null;
var _5f=false;
while(sub){
if(sid==sub.sid){
_5f=true;
if(sub==_5a.s){
_5a.s=sub.next;
}else{
_5e.next=sub.next;
}
break;
}
_5e=sub;
sub=sub.next;
}
if(!_5f){
return false;
}
}
return true;
};
OpenAjax.hub.ManagedHub.prototype._getSubscriptionObject=function(_60,_61,_62,sid){
if(typeof _60!="undefined"){
if(_62<_61.length){
var _63=_60.c[_61[_62]];
return this._getSubscriptionObject(_63,_61,_62+1,sid);
}
var sub=_60.s;
while(sub){
if(sid==sub.sid){
return sub;
}
sub=sub.next;
}
}
return null;
};
OpenAjax.hub._hub=new OpenAjax.hub.ManagedHub({onSubscribe:function(_64,_65){
return true;
},onPublish:function(_66,_67,_68,_69){
return true;
}});
OpenAjax.hub.subscribe=function(_6a,_6b,_6c,_6d){
if(typeof _6b==="string"){
_6c=_6c||window;
_6b=_6c[_6b]||null;
}
return OpenAjax.hub._hub.subscribe(_6a,_6b,_6c,null,_6d);
};
OpenAjax.hub.unsubscribe=function(_6e){
return OpenAjax.hub._hub.unsubscribe(_6e);
};
OpenAjax.hub.publish=function(_6f,_70){
OpenAjax.hub._hub.publish(_6f,_70);
};
OpenAjax.hub.registerLibrary("OpenAjax","http://openajax.org/hub","2.0",{});
}
OpenAjax.hub.InlineContainer=function(hub,_71,_72){
if(!hub||!_71||!_72||!_72.Container||!_72.Container.onSecurityAlert){
throw new Error(OpenAjax.hub.Error.BadParameters);
}
var _73=_72.Container.scope||window;
var _74=false;
var _75=[];
var _76=0;
var _77=null;
if(_72.Container.log){
var log=function(msg){
try{
_72.Container.log.call(_73,"InlineContainer::"+_71+": "+msg);
}
catch(e){
OpenAjax.hub._debugger();
}
};
}else{
log=function(){
};
}
this._init=function(){
hub.addContainer(this);
};
this.getHub=function(){
return hub;
};
this.sendToClient=function(_78,_79,_7a){
if(_74){
var sub=_75[_7a];
try{
sub.cb.call(sub.sc,_78,_79,sub.d);
}
catch(e){
OpenAjax.hub._debugger();
_77._log("caught error from onData callback to HubClient.subscribe(): "+e.message);
}
}
};
this.remove=function(){
if(_74){
_7b();
}
};
this.isConnected=function(){
return _74;
};
this.getClientID=function(){
return _71;
};
this.getPartnerOrigin=function(){
if(_74){
return window.location.protocol+"//"+window.location.hostname;
}
return null;
};
this.getParameters=function(){
return _72;
};
this.connect=function(_7c,_7d,_7e){
if(_74){
throw new Error(OpenAjax.hub.Error.Duplicate);
}
_74=true;
_77=_7c;
if(_72.Container.onConnect){
try{
_72.Container.onConnect.call(_73,this);
}
catch(e){
OpenAjax.hub._debugger();
log("caught error from onConnect callback to constructor: "+e.message);
}
}
_7f(_7d,_7e,_7c,true);
};
this.disconnect=function(_80,_81,_82){
if(!_74){
throw new Error(OpenAjax.hub.Error.Disconnected);
}
_7b();
if(_72.Container.onDisconnect){
try{
_72.Container.onDisconnect.call(_73,this);
}
catch(e){
OpenAjax.hub._debugger();
log("caught error from onDisconnect callback to constructor: "+e.message);
}
}
_7f(_81,_82,_80,true);
};
this.subscribe=function(_83,_84,_85,_86,_87){
_88();
_89(_83);
if(!_84){
throw new Error(OpenAjax.hub.Error.BadParameters);
}
var _8a=""+_76++;
var _8b=false;
var msg=null;
try{
var _8c=hub.subscribeForClient(this,_83,_8a);
_8b=true;
}
catch(e){
_8a=null;
msg=e.message;
}
_85=_85||window;
if(_8b){
_75[_8a]={h:_8c,cb:_84,sc:_85,d:_87};
}
_7f(_86,_85,_8a,_8b,msg);
return _8a;
};
this.publish=function(_8d,_8e){
_88();
_8f(_8d);
hub.publishForClient(this,_8d,_8e);
};
this.unsubscribe=function(_90,_91,_92){
_88();
if(typeof _90==="undefined"||_90===null){
throw new Error(OpenAjax.hub.Error.BadParameters);
}
var sub=_75[_90];
if(!sub){
throw new Error(OpenAjax.hub.Error.NoSubscription);
}
hub.unsubscribeForClient(this,sub.h);
delete _75[_90];
_7f(_91,_92,_90,true);
};
this.getSubscriberData=function(_93){
_88();
return _94(_93).d;
};
this.getSubscriberScope=function(_95){
_88();
return _94(_95).sc;
};
function _7f(_96,_97,_98,_99,_9a){
if(_96){
try{
_97=_97||window;
_96.call(_97,_98,_99,_9a);
}
catch(e){
OpenAjax.hub._debugger();
_77._log("caught error from onComplete callback: "+e.message);
}
}
};
function _7b(){
for(var _9b in _75){
hub.unsubscribeForClient(this,_75[_9b].h);
}
_75=[];
_76=0;
_74=false;
};
function _88(){
if(!_74){
throw new Error(OpenAjax.hub.Error.Disconnected);
}
};
function _8f(_9c){
if((_9c==null)||(_9c==="")||(_9c.indexOf("*")!=-1)||(_9c.indexOf("..")!=-1)||(_9c.charAt(0)==".")||(_9c.charAt(_9c.length-1)==".")){
throw new Error(OpenAjax.hub.Error.BadParameters);
}
};
function _89(_9d){
if(!_9d){
throw new Error(OpenAjax.hub.Error.BadParameters);
}
var _9e=_9d.split(".");
var len=_9e.length;
for(var i=0;i<len;i++){
var p=_9e[i];
if((p==="")||((p.indexOf("*")!=-1)&&(p!="*")&&(p!="**"))){
throw new Error(OpenAjax.hub.Error.BadParameters);
}
if((p=="**")&&(i<len-1)){
throw new Error(OpenAjax.hub.Error.BadParameters);
}
}
};
function _94(_9f){
var sub=_75[_9f];
if(sub){
return sub;
}
throw new Error(OpenAjax.hub.Error.NoSubscription);
};
this._init();
};
OpenAjax.hub.InlineHubClient=function(_a0){
if(!_a0||!_a0.HubClient||!_a0.HubClient.onSecurityAlert||!_a0.InlineHubClient||!_a0.InlineHubClient.container){
throw new Error(OpenAjax.hub.Error.BadParameters);
}
var _a1=_a0.InlineHubClient.container;
var _a2=_a0.HubClient.scope||window;
if(_a0.HubClient.log){
var log=function(msg){
try{
_a0.HubClient.log.call(_a2,"InlineHubClient::"+_a1.getClientID()+": "+msg);
}
catch(e){
OpenAjax.hub._debugger();
}
};
}else{
log=function(){
};
}
this._log=log;
this.connect=function(_a3,_a4){
_a1.connect(this,_a3,_a4);
};
this.disconnect=function(_a5,_a6){
_a1.disconnect(this,_a5,_a6);
};
this.getPartnerOrigin=function(){
return _a1.getPartnerOrigin();
};
this.getClientID=function(){
return _a1.getClientID();
};
this.subscribe=function(_a7,_a8,_a9,_aa,_ab){
return _a1.subscribe(_a7,_a8,_a9,_aa,_ab);
};
this.publish=function(_ac,_ad){
_a1.publish(_ac,_ad);
};
this.unsubscribe=function(_ae,_af,_b0){
_a1.unsubscribe(_ae,_af,_b0);
};
this.isConnected=function(){
return _a1.isConnected();
};
this.getScope=function(){
return _a2;
};
this.getSubscriberData=function(_b1){
return _a1.getSubscriberData(_b1);
};
this.getSubscriberScope=function(_b2){
return _a1.getSubscriberScope(_b2);
};
this.getParameters=function(){
return _a0;
};
};
var OpenAjax=OpenAjax||{};
OpenAjax.hub=OpenAjax.hub||{};
OpenAjax.gadgets=typeof OpenAjax.gadgets==="object"?OpenAjax.gadgets:typeof gadgets==="object"?gadgets:{};
OpenAjax.gadgets.rpctx=OpenAjax.gadgets.rpctx||{};
(function(){
if(typeof gadgets==="undefined"){
if(typeof oaaConfig==="undefined"){
var _b3=document.getElementsByTagName("script");
var _b4=/openajax(?:managedhub-(?:all|core).*|-mashup)\.js$/i;
for(var i=_b3.length-1;i>=0;i--){
var src=_b3[i].getAttribute("src");
if(!src){
continue;
}
var m=src.match(_b4);
if(m){
var _b5=_b3[i].getAttribute("oaaConfig");
if(_b5){
try{
oaaConfig=eval("({ "+_b5+" })");
}
catch(e){
}
}
break;
}
}
}
if(typeof oaaConfig!=="undefined"&&oaaConfig.gadgetsGlobal){
gadgets=OpenAjax.gadgets;
}
}
})();
if(!OpenAjax.hub.IframeContainer){
(function(){
OpenAjax.hub.IframeContainer=function(hub,_b6,_b7){
_b8(arguments);
var _b9=this;
var _ba=_b7.Container.scope||window;
var _bb=false;
var _bc={};
var _bd;
var _be;
var _bf=_b7.IframeContainer.timeout||15000;
var _c0;
if(_b7.Container.log){
var log=function(msg){
try{
_b7.Container.log.call(_ba,"IframeContainer::"+_b6+": "+msg);
}
catch(e){
OpenAjax.hub._debugger();
}
};
}else{
log=function(){
};
}
this._init=function(){
hub.addContainer(this);
_be=OpenAjax.hub.IframeContainer._rpcRouter.add(_b6,this);
_bd=_114(_b7,_ba,log);
var _c1=_b7.IframeContainer.clientRelay;
var _c2=OpenAjax.gadgets.rpc.getRelayChannel();
if(_b7.IframeContainer.tunnelURI){
if(_c2!=="wpm"&&_c2!=="ifpc"){
throw new Error(OpenAjax.hub.Error.IncompatBrowser);
}
}else{
log("WARNING: Parameter 'IframeContaienr.tunnelURI' not specified. Connection will not be fully secure.");
if(_c2==="rmr"&&!_c1){
_c1=OpenAjax.gadgets.rpc.getOrigin(_b7.IframeContainer.uri)+"/robots.txt";
}
}
_c3();
OpenAjax.gadgets.rpc.setupReceiver(_be,_c1);
_c4();
};
this.sendToClient=function(_c5,_c6,_c7){
OpenAjax.gadgets.rpc.call(_be,"openajax.pubsub",null,"pub",_c5,_c6,_c7);
};
this.remove=function(){
_c8();
clearTimeout(_c0);
OpenAjax.gadgets.rpc.removeReceiver(_be);
var _c9=document.getElementById(_be);
_c9.parentNode.removeChild(_c9);
OpenAjax.hub.IframeContainer._rpcRouter.remove(_be);
};
this.isConnected=function(){
return _bb;
};
this.getClientID=function(){
return _b6;
};
this.getPartnerOrigin=function(){
if(_bb){
var _ca=OpenAjax.gadgets.rpc.getReceiverOrigin(_be);
if(_ca){
return (/^([a-zA-Z]+:\/\/[^:]+).*/.exec(_ca)[1]);
}
}
return null;
};
this.getParameters=function(){
return _b7;
};
this.getHub=function(){
return hub;
};
this.getIframe=function(){
return document.getElementById(_be);
};
function _b8(_cb){
var hub=_cb[0],_b6=_cb[1],_b7=_cb[2];
if(!hub||!_b6||!_b7||!_b7.Container||!_b7.Container.onSecurityAlert||!_b7.IframeContainer||!_b7.IframeContainer.parent||!_b7.IframeContainer.uri){
throw new Error(OpenAjax.hub.Error.BadParameters);
}
};
this._handleIncomingRPC=function(_cc,_cd,_ce){
switch(_cc){
case "pub":
hub.publishForClient(_b9,_cd,_ce);
break;
case "sub":
var _cf="";
try{
_bc[_ce]=hub.subscribeForClient(_b9,_cd,_ce);
}
catch(e){
_cf=e.message;
}
return _cf;
case "uns":
var _d0=_bc[_ce];
hub.unsubscribeForClient(_b9,_d0);
delete _bc[_ce];
return _ce;
case "con":
_d1();
return true;
case "dis":
_c4();
_c8();
if(_b7.Container.onDisconnect){
try{
_b7.Container.onDisconnect.call(_ba,_b9);
}
catch(e){
OpenAjax.hub._debugger();
log("caught error from onDisconnect callback to constructor: "+e.message);
}
}
return true;
}
};
this._onSecurityAlert=function(_d2){
_d3(_113[_d2]);
};
function _c3(){
var _d4=document.createElement("span");
_b7.IframeContainer.parent.appendChild(_d4);
var _d5="<iframe id=\""+_be+"\" name=\""+_be+"\" src=\"javascript:'<html></html>'\"";
var _d6="";
var _d7=_b7.IframeContainer.iframeAttrs;
if(_d7){
for(var _d8 in _d7){
switch(_d8){
case "style":
for(var _d9 in _d7.style){
_d6+=_d9+":"+_d7.style[_d9]+";";
}
break;
case "className":
_d5+=" class=\""+_d7[_d8]+"\"";
break;
default:
_d5+=" "+_d8+"=\""+_d7[_d8]+"\"";
}
}
}
_d6+="visibility:hidden;";
_d5+=" style=\""+_d6+"\"></iframe>";
_d4.innerHTML=_d5;
var _da;
if(_b7.IframeContainer.tunnelURI){
_da="&parent="+encodeURIComponent(_b7.IframeContainer.tunnelURI)+"&forcesecure=true";
}else{
_da="&oahParent="+encodeURIComponent(OpenAjax.gadgets.rpc.getOrigin(window.location.href));
}
var _db="";
if(_be!==_b6){
_db="&oahId="+_be.substring(_be.lastIndexOf("_")+1);
}
document.getElementById(_be).src=_b7.IframeContainer.uri+"#rpctoken="+_bd+_da+_db;
};
function _d1(){
function _dc(_dd){
if(_dd){
_bb=true;
clearTimeout(_c0);
document.getElementById(_be).style.visibility="visible";
if(_b7.Container.onConnect){
try{
_b7.Container.onConnect.call(_ba,_b9);
}
catch(e){
OpenAjax.hub._debugger();
log("caught error from onConnect callback to constructor: "+e.message);
}
}
}
};
OpenAjax.gadgets.rpc.call(_be,"openajax.pubsub",_dc,"cmd","con");
};
function _c8(){
if(_bb){
_bb=false;
document.getElementById(_be).style.visibility="hidden";
for(var s in _bc){
hub.unsubscribeForClient(_b9,_bc[s]);
}
_bc={};
}
};
function _d3(_de){
try{
_b7.Container.onSecurityAlert.call(_ba,_b9,_de);
}
catch(e){
OpenAjax.hub._debugger();
log("caught error from onSecurityAlert callback to constructor: "+e.message);
}
};
function _c4(){
_c0=setTimeout(function(){
_d3(OpenAjax.hub.SecurityAlert.LoadTimeout);
_b9._handleIncomingRPC=function(){
};
},_bf);
};
this._init();
};
OpenAjax.hub.IframeHubClient=function(_df){
if(!_df||!_df.HubClient||!_df.HubClient.onSecurityAlert){
throw new Error(OpenAjax.hub.Error.BadParameters);
}
var _e0=this;
var _e1=_df.HubClient.scope||window;
var _e2=false;
var _e3={};
var _e4=0;
var _e5;
if(_df.HubClient.log){
var log=function(msg){
try{
_df.HubClient.log.call(_e1,"IframeHubClient::"+_e5+": "+msg);
}
catch(e){
OpenAjax.hub._debugger();
}
};
}else{
log=function(){
};
}
this._init=function(){
var _e6=OpenAjax.gadgets.util.getUrlParameters();
if(!_e6.parent){
var _e7=_e6.oahParent+"/robots.txt";
OpenAjax.gadgets.rpc.setupReceiver("..",_e7);
}
if(_df.IframeHubClient&&_df.IframeHubClient.requireParentVerifiable&&OpenAjax.gadgets.rpc.getReceiverOrigin("..")===null){
OpenAjax.gadgets.rpc.removeReceiver("..");
throw new Error(OpenAjax.hub.Error.IncompatBrowser);
}
OpenAjax.hub.IframeContainer._rpcRouter.add("..",this);
_e5=OpenAjax.gadgets.rpc.RPC_ID;
if(_e6.oahId){
_e5=_e5.substring(0,_e5.lastIndexOf("_"));
}
};
this.connect=function(_e8,_e9){
if(_e2){
throw new Error(OpenAjax.hub.Error.Duplicate);
}
function _ea(_eb){
if(_eb){
_e2=true;
if(_e8){
try{
_e8.call(_e9||window,_e0,true);
}
catch(e){
OpenAjax.hub._debugger();
log("caught error from onComplete callback to connect(): "+e.message);
}
}
}
};
OpenAjax.gadgets.rpc.call("..","openajax.pubsub",_ea,"con");
};
this.disconnect=function(_ec,_ed){
if(!_e2){
throw new Error(OpenAjax.hub.Error.Disconnected);
}
_e2=false;
var _ee=null;
if(_ec){
_ee=function(_ef){
try{
_ec.call(_ed||window,_e0,true);
}
catch(e){
OpenAjax.hub._debugger();
log("caught error from onComplete callback to disconnect(): "+e.message);
}
};
}
OpenAjax.gadgets.rpc.call("..","openajax.pubsub",_ee,"dis");
};
this.getPartnerOrigin=function(){
if(_e2){
var _f0=OpenAjax.gadgets.rpc.getReceiverOrigin("..");
if(_f0){
return (/^([a-zA-Z]+:\/\/[^:]+).*/.exec(_f0)[1]);
}
}
return null;
};
this.getClientID=function(){
return _e5;
};
this.subscribe=function(_f1,_f2,_f3,_f4,_f5){
_f6();
_f7(_f1);
if(!_f2){
throw new Error(OpenAjax.hub.Error.BadParameters);
}
_f3=_f3||window;
var _f8=""+_e4++;
_e3[_f8]={cb:_f2,sc:_f3,d:_f5};
function _f9(_fa){
if(_fa!==""){
delete _e3[_f8];
}
if(_f4){
try{
_f4.call(_f3,_f8,_fa==="",_fa);
}
catch(e){
OpenAjax.hub._debugger();
log("caught error from onComplete callback to subscribe(): "+e.message);
}
}
};
OpenAjax.gadgets.rpc.call("..","openajax.pubsub",_f9,"sub",_f1,_f8);
return _f8;
};
this.publish=function(_fb,_fc){
_f6();
_fd(_fb);
OpenAjax.gadgets.rpc.call("..","openajax.pubsub",null,"pub",_fb,_fc);
};
this.unsubscribe=function(_fe,_ff,_100){
_f6();
if(!_fe){
throw new Error(OpenAjax.hub.Error.BadParameters);
}
if(!_e3[_fe]||_e3[_fe].uns){
throw new Error(OpenAjax.hub.Error.NoSubscription);
}
_e3[_fe].uns=true;
function _101(_102){
delete _e3[_fe];
if(_ff){
try{
_ff.call(_100||window,_fe,true);
}
catch(e){
OpenAjax.hub._debugger();
log("caught error from onComplete callback to unsubscribe(): "+e.message);
}
}
};
OpenAjax.gadgets.rpc.call("..","openajax.pubsub",_101,"uns",null,_fe);
};
this.isConnected=function(){
return _e2;
};
this.getScope=function(){
return _e1;
};
this.getSubscriberData=function(_103){
_f6();
if(_e3[_103]){
return _e3[_103].d;
}
throw new Error(OpenAjax.hub.Error.NoSubscription);
};
this.getSubscriberScope=function(_104){
_f6();
if(_e3[_104]){
return _e3[_104].sc;
}
throw new Error(OpenAjax.hub.Error.NoSubscription);
};
this.getParameters=function(){
return _df;
};
this._handleIncomingRPC=function(_105,_106,data,_107){
if(_105==="pub"){
if(_e3[_107]&&!_e3[_107].uns){
try{
_e3[_107].cb.call(_e3[_107].sc,_106,data,_e3[_107].d);
}
catch(e){
OpenAjax.hub._debugger();
log("caught error from onData callback to subscribe(): "+e.message);
}
}
}
if(_106==="con"){
return true;
}
return false;
};
function _f6(){
if(!_e2){
throw new Error(OpenAjax.hub.Error.Disconnected);
}
};
function _f7(_108){
if(!_108){
throw new Error(OpenAjax.hub.Error.BadParameters);
}
var path=_108.split(".");
var len=path.length;
for(var i=0;i<len;i++){
var p=path[i];
if((p==="")||((p.indexOf("*")!=-1)&&(p!="*")&&(p!="**"))){
throw new Error(OpenAjax.hub.Error.BadParameters);
}
if((p=="**")&&(i<len-1)){
throw new Error(OpenAjax.hub.Error.BadParameters);
}
}
};
function _fd(_109){
if(!_109||_109===""||(_109.indexOf("*")!=-1)||(_109.indexOf("..")!=-1)||(_109.charAt(0)==".")||(_109.charAt(_109.length-1)==".")){
throw new Error(OpenAjax.hub.Error.BadParameters);
}
};
this._init();
};
OpenAjax.hub.IframeContainer._rpcRouter=function(){
var _10a={};
function _10b(){
var r=_10a[this.f];
if(r){
return r._handleIncomingRPC.apply(r,arguments);
}
};
function _10c(_10d,_10e){
var r=_10a[_10d];
if(r){
r._onSecurityAlert.call(r,_10e);
}
};
return {add:function(id,_10f){
function _110(id,_111){
if(id===".."){
if(!_10a[".."]){
_10a[".."]=_111;
}
return;
}
var _112=id;
while(document.getElementById(_112)){
_112=id+"_"+((32767*Math.random())|0).toString(16);
}
_10a[_112]=_111;
return _112;
};
OpenAjax.gadgets.rpc.register("openajax.pubsub",_10b);
OpenAjax.gadgets.rpc.config({securityCallback:_10c});
_113[OpenAjax.gadgets.rpc.SEC_ERROR_LOAD_TIMEOUT]=OpenAjax.hub.SecurityAlert.LoadTimeout;
_113[OpenAjax.gadgets.rpc.SEC_ERROR_FRAME_PHISH]=OpenAjax.hub.SecurityAlert.FramePhish;
_113[OpenAjax.gadgets.rpc.SEC_ERROR_FORGED_MSG]=OpenAjax.hub.SecurityAlert.ForgedMsg;
this.add=_110;
return _110(id,_10f);
},remove:function(id){
delete _10a[id];
}};
}();
var _113={};
function _114(_115,_116,log){
if(!OpenAjax.hub.IframeContainer._prng){
var seed=new Date().getTime()+Math.random()+document.cookie;
OpenAjax.hub.IframeContainer._prng=OpenAjax._smash.crypto.newPRNG(seed);
}
var p=_115.IframeContainer||_115.IframeHubClient;
if(p&&p.seed){
try{
var _117=p.seed.call(_116);
OpenAjax.hub.IframeContainer._prng.addSeed(_117);
}
catch(e){
OpenAjax.hub._debugger();
log("caught error from 'seed' callback: "+e.message);
}
}
var _118=(p&&p.tokenLength)||6;
return OpenAjax.hub.IframeContainer._prng.nextRandomB64Str(_118);
};
})();
}
if(typeof OpenAjax._smash=="undefined"){
OpenAjax._smash={};
}
OpenAjax._smash.crypto={"strToWA":function(str,_119){
var bin=Array();
var mask=(1<<_119)-1;
for(var i=0;i<str.length*_119;i+=_119){
bin[i>>5]|=(str.charCodeAt(i/_119)&mask)<<(32-_119-i%32);
}
return bin;
},"hmac_sha1":function(_11a,_11b,_11c){
var ipad=Array(16),opad=Array(16);
for(var i=0;i<16;i++){
ipad[i]=_11a[i]^909522486;
opad[i]=_11a[i]^1549556828;
}
var hash=this.sha1(ipad.concat(this.strToWA(_11b,_11c)),512+_11b.length*_11c);
return this.sha1(opad.concat(hash),512+160);
},"newPRNG":function(_11d){
var that=this;
if((typeof _11d!="string")||(_11d.length<12)){
alert("WARNING: Seed length too short ...");
}
var _11e=[43417,15926,18182,33130,9585,30800,49772,40144,47678,55453,4659,38181,65340,6787,54417,65301];
var _11f=[];
var _120=0;
function _121(_122){
return that.hmac_sha1(_11e,_122,8);
};
function _123(_124){
var _125=_121(_124);
for(var i=0;i<5;i++){
_11f[i]^=_125[i];
}
};
_123(_11d);
return {"addSeed":function(seed){
_123(seed);
},"nextRandomOctets":function(len){
var _126=[];
while(len>0){
_120+=1;
var _127=that.hmac_sha1(_11f,(_120).toString(16),8);
for(i=0;(i<20)&(len>0);i++,len--){
_126.push((_127[i>>2]>>(i%4))%256);
}
}
return _126;
},"nextRandomB64Str":function(len){
var _128="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
var _129=this.nextRandomOctets(len);
var _12a="";
for(var i=0;i<len;i++){
_12a+=_128.charAt(_129[i]&63);
}
return _12a;
}};
},"sha1":function(){
var _12b=function(x,y){
var lsw=(x&65535)+(y&65535);
var msw=(x>>16)+(y>>16)+(lsw>>16);
return (msw<<16)|(lsw&65535);
};
var rol=function(num,cnt){
return (num<<cnt)|(num>>>(32-cnt));
};
function _12c(t,b,c,d){
if(t<20){
return (b&c)|((~b)&d);
}
if(t<40){
return b^c^d;
}
if(t<60){
return (b&c)|(b&d)|(c&d);
}
return b^c^d;
};
function _12d(t){
return (t<20)?1518500249:(t<40)?1859775393:(t<60)?-1894007588:-899497514;
};
return function(_12e,_12f){
_12e[_12f>>5]|=128<<(24-_12f%32);
_12e[((_12f+64>>9)<<4)+15]=_12f;
var W=Array(80);
var H0=1732584193;
var H1=-271733879;
var H2=-1732584194;
var H3=271733878;
var H4=-1009589776;
for(var i=0;i<_12e.length;i+=16){
var a=H0;
var b=H1;
var c=H2;
var d=H3;
var e=H4;
for(var j=0;j<80;j++){
W[j]=((j<16)?_12e[i+j]:rol(W[j-3]^W[j-8]^W[j-14]^W[j-16],1));
var T=_12b(_12b(rol(a,5),_12c(j,b,c,d)),_12b(_12b(e,W[j]),_12d(j)));
e=d;
d=c;
c=rol(b,30);
b=a;
a=T;
}
H0=_12b(a,H0);
H1=_12b(b,H1);
H2=_12b(c,H2);
H3=_12b(d,H3);
H4=_12b(e,H4);
}
return Array(H0,H1,H2,H3,H4);
};
}()};
if(!this.JSON){
JSON={};
}
(function(){
function f(n){
return n<10?"0"+n:n;
};
if(typeof Date.prototype.toJSON!=="function"){
Date.prototype.toJSON=function(key){
return this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z";
};
String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(key){
return this.valueOf();
};
}
var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,_130=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,_131,meta={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r","\"":"\\\"","\\":"\\\\"},rep;
function _132(_133){
_130.lastIndex=0;
return _130.test(_133)?"\""+_133.replace(_130,function(a){
var c=meta[a];
return typeof c==="string"?c:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4);
})+"\"":"\""+_133+"\"";
};
function str(key,_134){
var i,k,v,_135,mind=gap,_136,_137=_134[key];
if(_137&&typeof _137==="object"&&typeof _137.toJSON==="function"){
_137=_137.toJSON(key);
}
if(typeof rep==="function"){
_137=rep.call(_134,key,_137);
}
switch(typeof _137){
case "string":
return _132(_137);
case "number":
return isFinite(_137)?String(_137):"null";
case "boolean":
case "null":
return String(_137);
case "object":
if(!_137){
return "null";
}
gap+=_131;
_136=[];
if(Object.prototype.toString.apply(_137)==="[object Array]"){
_135=_137.length;
for(i=0;i<_135;i+=1){
_136[i]=str(i,_137)||"null";
}
v=_136.length===0?"[]":gap?"[\n"+gap+_136.join(",\n"+gap)+"\n"+mind+"]":"["+_136.join(",")+"]";
gap=mind;
return v;
}
if(rep&&typeof rep==="object"){
_135=rep.length;
for(i=0;i<_135;i+=1){
k=rep[i];
if(typeof k==="string"){
v=str(k,_137);
if(v){
_136.push(_132(k)+(gap?": ":":")+v);
}
}
}
}else{
for(k in _137){
if(Object.hasOwnProperty.call(_137,k)){
v=str(k,_137);
if(v){
_136.push(_132(k)+(gap?": ":":")+v);
}
}
}
}
v=_136.length===0?"{}":gap?"{\n"+gap+_136.join(",\n"+gap)+"\n"+mind+"}":"{"+_136.join(",")+"}";
gap=mind;
return v;
}
};
if(typeof JSON.stringify!=="function"){
JSON.stringify=function(_138,_139,_13a){
var i;
gap="";
_131="";
if(typeof _13a==="number"){
for(i=0;i<_13a;i+=1){
_131+=" ";
}
}else{
if(typeof _13a==="string"){
_131=_13a;
}
}
rep=_139;
if(_139&&typeof _139!=="function"&&(typeof _139!=="object"||typeof _139.length!=="number")){
throw new Error("JSON.stringify");
}
return str("",{"":_138});
};
}
if(typeof JSON.parse!=="function"){
JSON.parse=function(text,_13b){
var j;
function walk(_13c,key){
var k,v,_13d=_13c[key];
if(_13d&&typeof _13d==="object"){
for(k in _13d){
if(Object.hasOwnProperty.call(_13d,k)){
v=walk(_13d,k);
if(v!==undefined){
_13d[k]=v;
}else{
delete _13d[k];
}
}
}
}
return _13b.call(_13c,key,_13d);
};
cx.lastIndex=0;
if(cx.test(text)){
text=text.replace(cx,function(a){
return "\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4);
});
}
if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,""))){
j=eval("("+text+")");
return typeof _13b==="function"?walk({"":j},""):j;
}
throw new SyntaxError("JSON.parse");
};
}
})();
OpenAjax.gadgets.util=function(){
function _13e(url){
var _13f;
var _140=url.indexOf("?");
var _141=url.indexOf("#");
if(_141===-1){
_13f=url.substr(_140+1);
}else{
_13f=[url.substr(_140+1,_141-_140-1),"&",url.substr(_141+1)].join("");
}
return _13f.split("&");
};
var _142=null;
var _143=[];
return {getUrlParameters:function(_144){
if(_142!==null&&typeof _144==="undefined"){
return _142;
}
var _145={};
var _146=_13e(_144||document.location.href);
var _147=window.decodeURIComponent?decodeURIComponent:unescape;
for(var i=0,j=_146.length;i<j;++i){
var pos=_146[i].indexOf("=");
if(pos===-1){
continue;
}
var _148=_146[i].substring(0,pos);
var _149=_146[i].substring(pos+1);
_149=_149.replace(/\+/g," ");
_145[_148]=_147(_149);
}
if(typeof _144==="undefined"){
_142=_145;
}
return _145;
},registerOnLoadHandler:function(_14a){
_143.push(_14a);
},runOnLoadHandlers:function(){
for(var i=0,j=_143.length;i<j;++i){
_143[i]();
}
},"attachBrowserEvent":function(elem,_14b,_14c,_14d){
if(elem.addEventListener){
elem.addEventListener(_14b,_14c,_14d);
}else{
if(elem.attachEvent){
elem.attachEvent("on"+_14b,_14c);
}
}
},"removeBrowserEvent":function(elem,_14e,_14f,_150){
if(elem.removeEventListener){
elem.removeEventListener(_14e,_14f,_150);
}else{
if(elem.detachEvent){
elem.detachEvent("on"+_14e,_14f);
}
}
}};
}();
OpenAjax.gadgets.util.getUrlParameters();
OpenAjax.gadgets.json=OpenAjax.gadgets.json||{};
if(!OpenAjax.gadgets.json.stringify){
OpenAjax.gadgets.json={parse:function(str){
try{
return window.JSON.parse(str);
}
catch(e){
return false;
}
},stringify:function(obj){
try{
return window.JSON.stringify(obj);
}
catch(e){
return null;
}
}};
}
OpenAjax.gadgets.log=function(_151){
OpenAjax.gadgets.log.logAtLevel(OpenAjax.gadgets.log.INFO,_151);
};
OpenAjax.gadgets.warn=function(_152){
OpenAjax.gadgets.log.logAtLevel(OpenAjax.gadgets.log.WARNING,_152);
};
OpenAjax.gadgets.error=function(_153){
OpenAjax.gadgets.log.logAtLevel(OpenAjax.gadgets.log.ERROR,_153);
};
OpenAjax.gadgets.setLogLevel=function(_154){
OpenAjax.gadgets.log.logLevelThreshold_=_154;
};
OpenAjax.gadgets.log.logAtLevel=function(_155,_156){
if(_155<OpenAjax.gadgets.log.logLevelThreshold_||!OpenAjax.gadgets.log._console){
return;
}
var _157;
var _158=OpenAjax.gadgets.log._console;
if(_155==OpenAjax.gadgets.log.WARNING&&_158.warn){
_158.warn(_156);
}else{
if(_155==OpenAjax.gadgets.log.ERROR&&_158.error){
_158.error(_156);
}else{
if(_158.log){
_158.log(_156);
}
}
}
};
OpenAjax.gadgets.log.INFO=1;
OpenAjax.gadgets.log.WARNING=2;
OpenAjax.gadgets.log.ERROR=3;
OpenAjax.gadgets.log.NONE=4;
OpenAjax.gadgets.log.logLevelThreshold_=OpenAjax.gadgets.log.INFO;
OpenAjax.gadgets.log._console=window.console?window.console:window.opera?window.opera.postError:undefined;
(function(){
if(!window.__isgadget){
var _159=false;
function _15a(){
if(!_159){
_159=true;
OpenAjax.gadgets.util.runOnLoadHandlers();
OpenAjax.gadgets.util.registerOnLoadHandler=function(_15b){
setTimeout(_15b,0);
};
if(window.detachEvent){
window.detachEvent("onload",_15a);
}
}
};
if(window.addEventListener){
document.addEventListener("DOMContentLoaded",_15a,false);
window.addEventListener("load",_15a,false);
}else{
if(window.attachEvent){
window.attachEvent("onload",_15a);
}
}
}
})();
OpenAjax.gadgets.rpctx=OpenAjax.gadgets.rpctx||{};
if(!OpenAjax.gadgets.rpctx.frameElement){
OpenAjax.gadgets.rpctx.frameElement=function(){
var _15c="__g2c_rpc";
var _15d="__c2g_rpc";
var _15e;
var _15f;
function _160(_161,from,rpc){
try{
if(from!==".."){
var fe=window.frameElement;
if(typeof fe[_15c]==="function"){
if(typeof fe[_15c][_15d]!=="function"){
fe[_15c][_15d]=function(args){
_15e(OpenAjax.gadgets.json.parse(args));
};
}
fe[_15c](OpenAjax.gadgets.json.stringify(rpc));
return;
}
}else{
var _162=document.getElementById(_161);
if(typeof _162[_15c]==="function"&&typeof _162[_15c][_15d]==="function"){
_162[_15c][_15d](OpenAjax.gadgets.json.stringify(rpc));
return;
}
}
}
catch(e){
}
return true;
};
return {getCode:function(){
return "fe";
},isParentVerifiable:function(){
return false;
},init:function(_163,_164){
_15e=_163;
_15f=_164;
return true;
},setup:function(_165,_166){
if(_165!==".."){
try{
var _167=document.getElementById(_165);
_167[_15c]=function(args){
_15e(OpenAjax.gadgets.json.parse(args));
};
}
catch(e){
return false;
}
}
if(_165===".."){
_15f("..",true);
var _168=function(){
window.setTimeout(function(){
OpenAjax.gadgets.rpc.call(_165,OpenAjax.gadgets.rpc.ACK);
},500);
};
OpenAjax.gadgets.util.registerOnLoadHandler(_168);
}
return true;
},call:function(_169,from,rpc){
_160(_169,from,rpc);
}};
}();
}
OpenAjax.gadgets.rpctx=OpenAjax.gadgets.rpctx||{};
if(!OpenAjax.gadgets.rpctx.ifpc){
OpenAjax.gadgets.rpctx.ifpc=function(){
var _16a=[];
var _16b=0;
var _16c;
var _16d=2000;
var _16e={};
function _16f(args){
var _170=[];
for(var i=0,j=args.length;i<j;++i){
_170.push(encodeURIComponent(OpenAjax.gadgets.json.stringify(args[i])));
}
return _170.join("&");
};
function _171(src){
var _172;
for(var i=_16a.length-1;i>=0;--i){
var ifr=_16a[i];
try{
if(ifr&&(ifr.recyclable||ifr.readyState==="complete")){
ifr.parentNode.removeChild(ifr);
if(window.ActiveXObject){
_16a[i]=ifr=null;
_16a.splice(i,1);
}else{
ifr.recyclable=false;
_172=ifr;
break;
}
}
}
catch(e){
}
}
if(!_172){
_172=document.createElement("iframe");
_172.style.border=_172.style.width=_172.style.height="0px";
_172.style.visibility="hidden";
_172.style.position="absolute";
_172.onload=function(){
this.recyclable=true;
};
_16a.push(_172);
}
_172.src=src;
window.setTimeout(function(){
document.body.appendChild(_172);
},0);
};
function _173(arr,_174){
for(var i=_174-1;i>=0;--i){
if(typeof arr[i]==="undefined"){
return false;
}
}
return true;
};
return {getCode:function(){
return "ifpc";
},isParentVerifiable:function(){
return true;
},init:function(_175,_176){
_16c=_176;
_16c("..",true);
return true;
},setup:function(_177,_178){
_16c(_177,true);
return true;
},call:function(_179,from,rpc){
var _17a=OpenAjax.gadgets.rpc.getRelayUrl(_179);
++_16b;
if(!_17a){
OpenAjax.gadgets.warn("No relay file assigned for IFPC");
return;
}
var src=null,_17b=[];
if(rpc.l){
var _17c=rpc.a;
src=[_17a,"#",_16f([from,_16b,1,0,_16f([from,rpc.s,"","",from].concat(_17c))])].join("");
_17b.push(src);
}else{
src=[_17a,"#",_179,"&",from,"@",_16b,"&"].join("");
var _17d=encodeURIComponent(OpenAjax.gadgets.json.stringify(rpc)),_17e=_16d-src.length,_17f=Math.ceil(_17d.length/_17e),_180=0,part;
while(_17d.length>0){
part=_17d.substring(0,_17e);
_17d=_17d.substring(_17e);
_17b.push([src,_17f,"&",_180,"&",part].join(""));
_180+=1;
}
}
do{
_171(_17b.shift());
}while(_17b.length>0);
return true;
},_receiveMessage:function(_181,_182){
var from=_181[1],_183=parseInt(_181[2],10),_184=parseInt(_181[3],10),_185=_181[_181.length-1],_186=_183===1;
if(_183>1){
if(!_16e[from]){
_16e[from]=[];
}
_16e[from][_184]=_185;
if(_173(_16e[from],_183)){
_185=_16e[from].join("");
delete _16e[from];
_186=true;
}
}
if(_186){
_182(OpenAjax.gadgets.json.parse(decodeURIComponent(_185)));
}
}};
}();
}
OpenAjax.gadgets.rpctx=OpenAjax.gadgets.rpctx||{};
if(!OpenAjax.gadgets.rpctx.rmr){
OpenAjax.gadgets.rpctx.rmr=function(){
var _187=500;
var _188=10;
var _189={};
var _18a;
var _18b;
function _18c(_18d,_18e,data,_18f){
var _190=function(){
document.body.appendChild(_18d);
_18d.src="about:blank";
if(_18f){
_18d.onload=function(){
_1a5(_18f);
};
}
_18d.src=_18e+"#"+data;
};
if(document.body){
_190();
}else{
OpenAjax.gadgets.util.registerOnLoadHandler(function(){
_190();
});
}
};
function _191(_192){
if(typeof _189[_192]==="object"){
return;
}
var _193=document.createElement("iframe");
var _194=_193.style;
_194.position="absolute";
_194.top="0px";
_194.border="0";
_194.opacity="0";
_194.width="10px";
_194.height="1px";
_193.id="rmrtransport-"+_192;
_193.name=_193.id;
var _195=OpenAjax.gadgets.rpc.getRelayUrl(_192);
if(!_195){
_195=OpenAjax.gadgets.rpc.getOrigin(OpenAjax.gadgets.util.getUrlParameters()["parent"])+"/robots.txt";
}
_189[_192]={frame:_193,receiveWindow:null,relayUri:_195,searchCounter:0,width:10,waiting:true,queue:[],sendId:0,recvId:0};
if(_192!==".."){
_18c(_193,_195,_196(_192));
}
_197(_192);
};
function _197(_198){
var _199=null;
_189[_198].searchCounter++;
try{
var _19a=OpenAjax.gadgets.rpc._getTargetWin(_198);
if(_198===".."){
_199=_19a.frames["rmrtransport-"+OpenAjax.gadgets.rpc.RPC_ID];
}else{
_199=_19a.frames["rmrtransport-.."];
}
}
catch(e){
}
var _19b=false;
if(_199){
_19b=_19c(_198,_199);
}
if(!_19b){
if(_189[_198].searchCounter>_188){
return;
}
window.setTimeout(function(){
_197(_198);
},_187);
}
};
function _19d(_19e,_19f,from,rpc){
var _1a0=null;
if(from!==".."){
_1a0=_189[".."];
}else{
_1a0=_189[_19e];
}
if(_1a0){
if(_19f!==OpenAjax.gadgets.rpc.ACK){
_1a0.queue.push(rpc);
}
if(_1a0.waiting||(_1a0.queue.length===0&&!(_19f===OpenAjax.gadgets.rpc.ACK&&rpc&&rpc.ackAlone===true))){
return true;
}
if(_1a0.queue.length>0){
_1a0.waiting=true;
}
var url=_1a0.relayUri+"#"+_196(_19e);
try{
_1a0.frame.contentWindow.location=url;
var _1a1=_1a0.width==10?20:10;
_1a0.frame.style.width=_1a1+"px";
_1a0.width=_1a1;
}
catch(e){
return false;
}
}
return true;
};
function _196(_1a2){
var _1a3=_189[_1a2];
var _1a4={id:_1a3.sendId};
if(_1a3){
_1a4.d=Array.prototype.slice.call(_1a3.queue,0);
_1a4.d.push({s:OpenAjax.gadgets.rpc.ACK,id:_1a3.recvId});
}
return OpenAjax.gadgets.json.stringify(_1a4);
};
function _1a5(_1a6){
var _1a7=_189[_1a6];
var data=_1a7.receiveWindow.location.hash.substring(1);
var _1a8=OpenAjax.gadgets.json.parse(decodeURIComponent(data))||{};
var _1a9=_1a8.d||[];
var _1aa=false;
var _1ab=false;
var _1ac=0;
var _1ad=(_1a7.recvId-_1a8.id);
for(var i=0;i<_1a9.length;++i){
var rpc=_1a9[i];
if(rpc.s===OpenAjax.gadgets.rpc.ACK){
_18b(_1a6,true);
if(_1a7.waiting){
_1ab=true;
}
_1a7.waiting=false;
var _1ae=Math.max(0,rpc.id-_1a7.sendId);
_1a7.queue.splice(0,_1ae);
_1a7.sendId=Math.max(_1a7.sendId,rpc.id||0);
continue;
}
_1aa=true;
if(++_1ac<=_1ad){
continue;
}
++_1a7.recvId;
_18a(rpc);
}
if(_1aa||(_1ab&&_1a7.queue.length>0)){
var from=(_1a6==="..")?OpenAjax.gadgets.rpc.RPC_ID:"..";
_19d(_1a6,OpenAjax.gadgets.rpc.ACK,from,{ackAlone:_1aa});
}
};
function _19c(_1af,_1b0){
var _1b1=_189[_1af];
try{
var _1b2=false;
_1b2="document" in _1b0;
if(!_1b2){
return false;
}
_1b2=typeof _1b0["document"]=="object";
if(!_1b2){
return false;
}
var loc=_1b0.location.href;
if(loc==="about:blank"){
return false;
}
}
catch(ex){
return false;
}
_1b1.receiveWindow=_1b0;
function _1b3(){
_1a5(_1af);
};
if(typeof _1b0.attachEvent==="undefined"){
_1b0.onresize=_1b3;
}else{
_1b0.attachEvent("onresize",_1b3);
}
if(_1af===".."){
_18c(_1b1.frame,_1b1.relayUri,_196(_1af),_1af);
}else{
_1a5(_1af);
}
return true;
};
return {getCode:function(){
return "rmr";
},isParentVerifiable:function(){
return true;
},init:function(_1b4,_1b5){
_18a=_1b4;
_18b=_1b5;
return true;
},setup:function(_1b6,_1b7){
try{
_191(_1b6);
}
catch(e){
OpenAjax.gadgets.warn("Caught exception setting up RMR: "+e);
return false;
}
return true;
},call:function(_1b8,from,rpc){
return _19d(_1b8,rpc.s,from,rpc);
}};
}();
}
OpenAjax.gadgets.rpctx=OpenAjax.gadgets.rpctx||{};
if(!OpenAjax.gadgets.rpctx.wpm){
OpenAjax.gadgets.rpctx.wpm=function(){
var _1b9,_1ba;
var _1bb;
var _1bc=false;
var _1bd=false;
function _1be(){
var hit=false;
function _1bf(_1c0){
if(_1c0.data=="postmessage.test"){
hit=true;
if(typeof _1c0.origin==="undefined"){
_1bd=true;
}
}
};
OpenAjax.gadgets.util.attachBrowserEvent(window,"message",_1bf,false);
window.postMessage("postmessage.test","*");
if(hit){
_1bc=true;
}
OpenAjax.gadgets.util.removeBrowserEvent(window,"message",_1bf,false);
};
function _1c1(_1c2){
var rpc=OpenAjax.gadgets.json.parse(_1c2.data);
if(!rpc||!rpc.f){
return;
}
var _1c3=OpenAjax.gadgets.rpc.getRelayUrl(rpc.f)||OpenAjax.gadgets.util.getUrlParameters()["parent"];
var _1c4=OpenAjax.gadgets.rpc.getOrigin(_1c3);
if(!_1bd?_1c2.origin!==_1c4:_1c2.domain!==/^.+:\/\/([^:]+).*/.exec(_1c4)[1]){
return;
}
_1b9(rpc);
};
return {getCode:function(){
return "wpm";
},isParentVerifiable:function(){
return true;
},init:function(_1c5,_1c6){
_1b9=_1c5;
_1ba=_1c6;
_1be();
if(!_1bc){
_1bb=function(win,msg,_1c7){
win.postMessage(msg,_1c7);
};
}else{
_1bb=function(win,msg,_1c8){
window.setTimeout(function(){
win.postMessage(msg,_1c8);
},0);
};
}
OpenAjax.gadgets.util.attachBrowserEvent(window,"message",_1c1,false);
_1ba("..",true);
return true;
},setup:function(_1c9,_1ca,_1cb){
if(_1c9===".."){
if(_1cb){
OpenAjax.gadgets.rpc._createRelayIframe(_1ca);
}else{
OpenAjax.gadgets.rpc.call(_1c9,OpenAjax.gadgets.rpc.ACK);
}
}
return true;
},call:function(_1cc,from,rpc){
var _1cd=OpenAjax.gadgets.rpc._getTargetWin(_1cc);
var _1ce=OpenAjax.gadgets.rpc.getRelayUrl(_1cc)||OpenAjax.gadgets.util.getUrlParameters()["parent"];
var _1cf=OpenAjax.gadgets.rpc.getOrigin(_1ce);
if(_1cf){
_1bb(_1cd,OpenAjax.gadgets.json.stringify(rpc),_1cf);
}else{
OpenAjax.gadgets.error("No relay set (used as window.postMessage targetOrigin)"+", cannot send cross-domain message");
}
return true;
},relayOnload:function(_1d0,data){
_1ba(_1d0,true);
}};
}();
}
if(!OpenAjax.gadgets.rpc){
OpenAjax.gadgets.rpc=function(){
var _1d1="__cb";
var _1d2="";
var ACK="__ack";
var _1d3=500;
var _1d4=10;
var _1d5={};
var _1d6={};
var _1d7={};
var _1d8={};
var _1d9=0;
var _1da={};
var _1db={};
var _1dc={};
var _1dd={};
var _1de={};
var _1df={};
var _1e0=(window.top!==window.self);
var _1e1=window.name;
var _1e2=function(){
};
var _1e3=0;
var _1e4=1;
var _1e5=2;
var _1e6=(function(){
function _1e7(name){
return function(){
OpenAjax.gadgets.log("gadgets.rpc."+name+"("+OpenAjax.gadgets.json.stringify(Array.prototype.slice.call(arguments))+"): call ignored. [caller: "+document.location+", isChild: "+_1e0+"]");
};
};
return {getCode:function(){
return "noop";
},isParentVerifiable:function(){
return true;
},init:_1e7("init"),setup:_1e7("setup"),call:_1e7("call")};
})();
if(OpenAjax.gadgets.util){
_1dd=OpenAjax.gadgets.util.getUrlParameters();
}
function _1e8(){
return typeof window.postMessage==="function"?OpenAjax.gadgets.rpctx.wpm:typeof window.postMessage==="object"?OpenAjax.gadgets.rpctx.wpm:navigator.userAgent.indexOf("WebKit")>0?OpenAjax.gadgets.rpctx.rmr:navigator.product==="Gecko"?OpenAjax.gadgets.rpctx.frameElement:OpenAjax.gadgets.rpctx.ifpc;
};
function _1e9(_1ea,_1eb){
var tx=_1ec;
if(!_1eb){
tx=_1e6;
}
_1de[_1ea]=tx;
var _1ed=_1df[_1ea]||[];
for(var i=0;i<_1ed.length;++i){
var rpc=_1ed[i];
rpc.t=_1ee(_1ea);
tx.call(_1ea,rpc.f,rpc);
}
_1df[_1ea]=[];
};
var _1ef=false,_1f0=false;
function _1f1(){
if(_1f0){
return;
}
function _1f2(){
_1ef=true;
};
OpenAjax.gadgets.util.attachBrowserEvent(window,"unload",_1f2,false);
_1f0=true;
};
function _1f3(_1f4,_1f5,_1f6,data,_1f7){
if(!_1d8[_1f5]||_1d8[_1f5]!==_1f6){
OpenAjax.gadgets.error("Invalid auth token. "+_1d8[_1f5]+" vs "+_1f6);
_1e2(_1f5,_1e5);
}
_1f7.onunload=function(){
if(_1db[_1f5]&&!_1ef){
_1e2(_1f5,_1e4);
OpenAjax.gadgets.rpc.removeReceiver(_1f5);
}
};
_1f1();
data=OpenAjax.gadgets.json.parse(decodeURIComponent(data));
_1ec.relayOnload(_1f5,data);
};
function _1f8(rpc){
if(rpc&&typeof rpc.s==="string"&&typeof rpc.f==="string"&&rpc.a instanceof Array){
if(_1d8[rpc.f]){
if(_1d8[rpc.f]!==rpc.t){
OpenAjax.gadgets.error("Invalid auth token. "+_1d8[rpc.f]+" vs "+rpc.t);
_1e2(rpc.f,_1e5);
}
}
if(rpc.s===ACK){
window.setTimeout(function(){
_1e9(rpc.f,true);
},0);
return;
}
if(rpc.c){
rpc.callback=function(_1f9){
OpenAjax.gadgets.rpc.call(rpc.f,_1d1,null,rpc.c,_1f9);
};
}
var _1fa=(_1d5[rpc.s]||_1d5[_1d2]).apply(rpc,rpc.a);
if(rpc.c&&typeof _1fa!=="undefined"){
OpenAjax.gadgets.rpc.call(rpc.f,_1d1,null,rpc.c,_1fa);
}
}
};
function _1fb(url){
if(!url){
return "";
}
url=url.toLowerCase();
if(url.indexOf("//")==0){
url=window.location.protocol+url;
}
if(url.indexOf("://")==-1){
url=window.location.protocol+"//"+url;
}
var host=url.substring(url.indexOf("://")+3);
var _1fc=host.indexOf("/");
if(_1fc!=-1){
host=host.substring(0,_1fc);
}
var _1fd=url.substring(0,url.indexOf("://"));
var _1fe="";
var _1ff=host.indexOf(":");
if(_1ff!=-1){
var port=host.substring(_1ff+1);
host=host.substring(0,_1ff);
if((_1fd==="http"&&port!=="80")||(_1fd==="https"&&port!=="443")){
_1fe=":"+port;
}
}
return _1fd+"://"+host+_1fe;
};
function _200(id){
if(typeof id==="undefined"||id===".."){
return window.parent;
}
id=String(id);
var _201=window.frames[id];
if(_201){
return _201;
}
_201=document.getElementById(id);
if(_201&&_201.contentWindow){
return _201.contentWindow;
}
return null;
};
var _1ec=_1e8();
_1d5[_1d2]=function(){
OpenAjax.gadgets.warn("Unknown RPC service: "+this.s);
};
_1d5[_1d1]=function(_202,_203){
var _204=_1da[_202];
if(_204){
delete _1da[_202];
_204(_203);
}
};
function _205(_206,_207,_208){
if(_1db[_206]===true){
return;
}
if(typeof _1db[_206]==="undefined"){
_1db[_206]=0;
}
var _209=document.getElementById(_206);
if(_206===".."||_209!=null){
if(_1ec.setup(_206,_207,_208)===true){
_1db[_206]=true;
return;
}
}
if(_1db[_206]!==true&&_1db[_206]++<_1d4){
window.setTimeout(function(){
_205(_206,_207,_208);
},_1d3);
}else{
_1de[_206]=_1e6;
_1db[_206]=true;
}
};
function _20a(_20b,rpc){
if(typeof _1dc[_20b]==="undefined"){
_1dc[_20b]=false;
var _20c=OpenAjax.gadgets.rpc.getRelayUrl(_20b);
if(_1fb(_20c)!==_1fb(window.location.href)){
return false;
}
var _20d=_200(_20b);
try{
_1dc[_20b]=_20d.OpenAjax.gadgets.rpc.receiveSameDomain;
}
catch(e){
OpenAjax.gadgets.error("Same domain call failed: parent= incorrectly set.");
}
}
if(typeof _1dc[_20b]==="function"){
_1dc[_20b](rpc);
return true;
}
return false;
};
function _20e(_20f,url,_210){
if(!/http(s)?:\/\/.+/.test(url)){
if(url.indexOf("//")==0){
url=window.location.protocol+url;
}else{
if(url.charAt(0)=="/"){
url=window.location.protocol+"//"+window.location.host+url;
}else{
if(url.indexOf("://")==-1){
url=window.location.protocol+"//"+url;
}
}
}
}
_1d6[_20f]=url;
_1d7[_20f]=!!_210;
};
function _1ee(_211){
return _1d8[_211];
};
function _212(_213,_214,_215){
_214=_214||"";
_1d8[_213]=String(_214);
_205(_213,_214,_215);
};
function _216(_217,_218){
function init(_219){
var _21a=_219?_219.rpc:{};
var _21b=_21a.parentRelayUrl;
if(_21b.substring(0,7)!=="http://"&&_21b.substring(0,8)!=="https://"&&_21b.substring(0,2)!=="//"){
if(typeof _1dd.parent==="string"&&_1dd.parent!==""){
if(_21b.substring(0,1)!=="/"){
var _21c=_1dd.parent.lastIndexOf("/");
_21b=_1dd.parent.substring(0,_21c+1)+_21b;
}else{
_21b=_1fb(_1dd.parent)+_21b;
}
}
}
var _21d=!!_21a.useLegacyProtocol;
_20e("..",_21b,_21d);
if(_21d){
_1ec=OpenAjax.gadgets.rpctx.ifpc;
_1ec.init(_1f8,_1e9);
}
var _21e=_218||_1dd.forcesecure||false;
_212("..",_217,_21e);
};
var _21f={parentRelayUrl:OpenAjax.gadgets.config.NonEmptyStringValidator};
OpenAjax.gadgets.config.register("rpc",_21f,init);
};
function _220(_221,_222,_223){
var _224=_223||_1dd.forcesecure||false;
var _225=_222||_1dd.parent;
if(_225){
_20e("..",_225);
_212("..",_221,_224);
}
};
function _226(_227,_228,_229,_22a){
if(!OpenAjax.gadgets.util){
return;
}
var _22b=document.getElementById(_227);
if(!_22b){
throw new Error("Cannot set up gadgets.rpc receiver with ID: "+_227+", element not found.");
}
var _22c=_228||_22b.src;
_20e(_227,_22c);
var _22d=OpenAjax.gadgets.util.getUrlParameters(_22b.src);
var _22e=_229||_22d.rpctoken;
var _22f=_22a||_22d.forcesecure;
_212(_227,_22e,_22f);
};
function _230(_231,_232,_233,_234){
if(_231===".."){
var _235=_233||_1dd.rpctoken||_1dd.ifpctok||"";
if(window["__isgadget"]===true){
_216(_235,_234);
}else{
_220(_235,_232,_234);
}
}else{
_226(_231,_232,_233,_234);
}
};
return {config:function(_236){
if(typeof _236.securityCallback==="function"){
_1e2=_236.securityCallback;
}
},register:function(_237,_238){
if(_237===_1d1||_237===ACK){
throw new Error("Cannot overwrite callback/ack service");
}
if(_237===_1d2){
throw new Error("Cannot overwrite default service:"+" use registerDefault");
}
_1d5[_237]=_238;
},unregister:function(_239){
if(_239===_1d1||_239===ACK){
throw new Error("Cannot delete callback/ack service");
}
if(_239===_1d2){
throw new Error("Cannot delete default service:"+" use unregisterDefault");
}
delete _1d5[_239];
},registerDefault:function(_23a){
_1d5[_1d2]=_23a;
},unregisterDefault:function(){
delete _1d5[_1d2];
},forceParentVerifiable:function(){
if(!_1ec.isParentVerifiable()){
_1ec=OpenAjax.gadgets.rpctx.ifpc;
}
},call:function(_23b,_23c,_23d,_23e){
_23b=_23b||"..";
var from="..";
if(_23b===".."){
from=_1e1;
}
++_1d9;
if(_23d){
_1da[_1d9]=_23d;
}
var rpc={s:_23c,f:from,c:_23d?_1d9:0,a:Array.prototype.slice.call(arguments,3),t:_1d8[_23b],l:_1d7[_23b]};
if(_23b!==".."&&!document.getElementById(_23b)){
OpenAjax.gadgets.log("WARNING: attempted send to nonexistent frame: "+_23b);
return;
}
if(_20a(_23b,rpc)){
return;
}
var _23f=_1de[_23b];
if(!_23f){
if(!_1df[_23b]){
_1df[_23b]=[rpc];
}else{
_1df[_23b].push(rpc);
}
return;
}
if(_1d7[_23b]){
_23f=OpenAjax.gadgets.rpctx.ifpc;
}
if(_23f.call(_23b,from,rpc)===false){
_1de[_23b]=_1e6;
_1ec.call(_23b,from,rpc);
}
},getRelayUrl:function(_240){
var url=_1d6[_240];
if(url&&url.substring(0,1)==="/"){
if(url.substring(1,2)==="/"){
url=document.location.protocol+url;
}else{
url=document.location.protocol+"//"+document.location.host+url;
}
}
return url;
},setRelayUrl:_20e,setAuthToken:_212,setupReceiver:_230,getAuthToken:_1ee,removeReceiver:function(_241){
delete _1d6[_241];
delete _1d7[_241];
delete _1d8[_241];
delete _1db[_241];
delete _1dc[_241];
delete _1de[_241];
},getRelayChannel:function(){
return _1ec.getCode();
},receive:function(_242,_243){
if(_242.length>4){
_1ec._receiveMessage(_242,_1f8);
}else{
_1f3.apply(null,_242.concat(_243));
}
},receiveSameDomain:function(rpc){
rpc.a=Array.prototype.slice.call(rpc.a);
window.setTimeout(function(){
_1f8(rpc);
},0);
},getOrigin:_1fb,getReceiverOrigin:function(_244){
var _245=_1de[_244];
if(!_245){
return null;
}
if(!_245.isParentVerifiable(_244)){
return null;
}
var _246=OpenAjax.gadgets.rpc.getRelayUrl(_244)||OpenAjax.gadgets.util.getUrlParameters().parent;
return OpenAjax.gadgets.rpc.getOrigin(_246);
},init:function(){
if(_1ec.init(_1f8,_1e9)===false){
_1ec=_1e6;
}
if(_1e0){
_230("..");
}
},_getTargetWin:_200,_createRelayIframe:function(_247,data){
var _248=OpenAjax.gadgets.rpc.getRelayUrl("..");
if(!_248){
return;
}
var src=_248+"#..&"+_1e1+"&"+_247+"&"+encodeURIComponent(OpenAjax.gadgets.json.stringify(data));
var _249=document.createElement("iframe");
_249.style.border=_249.style.width=_249.style.height="0px";
_249.style.visibility="hidden";
_249.style.position="absolute";
function _24a(){
document.body.appendChild(_249);
_249.src="javascript:\"<html></html>\"";
_249.src=src;
};
if(document.body){
_24a();
}else{
OpenAjax.gadgets.util.registerOnLoadHandler(function(){
_24a();
});
}
return _249;
},ACK:ACK,RPC_ID:_1e1,SEC_ERROR_LOAD_TIMEOUT:_1e3,SEC_ERROR_FRAME_PHISH:_1e4,SEC_ERROR_FORGED_MSG:_1e5};
}();
OpenAjax.gadgets.rpc.init();
}
/*******************************************************************************
* OpenAjax-mashup.js
*
* Reference implementation of the OpenAjax Hub, as specified by OpenAjax Alliance.
* Specification is under development at:
*
* http://www.openajax.org/member/wiki/OpenAjax_Hub_Specification
*
* Copyright 2006-2009 OpenAjax Alliance
*
* 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.
*
******************************************************************************/
// prevent re-definition of the OpenAjax object
if ( !window["OpenAjax"] ) {
OpenAjax = new function() {
this.hub = {};
var h = this.hub;
h.implementer = "http://openajax.org";
h.implVersion = "2.0";
h.specVersion = "2.0";
h.implExtraData = {};
var libs = {};
h.libraries = libs;
var ooh = "org.openajax.hub.";
h.registerLibrary = function(prefix, nsURL, version, extra){
libs[prefix] = {
prefix: prefix,
namespaceURI: nsURL,
version: version,
extraData: extra
};
this.publish(ooh+"registerLibrary", libs[prefix]);
}
h.unregisterLibrary = function(prefix){
this.publish(ooh+"unregisterLibrary", libs[prefix]);
delete libs[prefix];
}
}
/**
* Error
*
* Standard Error names used when the standard functions need to throw Errors.
*/
OpenAjax.hub.Error = {
// Either a required argument is missing or an invalid argument was provided
BadParameters: "OpenAjax.hub.Error.BadParameters",
// The specified hub has been disconnected and cannot perform the requested
// operation:
Disconnected: "OpenAjax.hub.Error.Disconnected",
// Container with specified ID already exists:
Duplicate: "OpenAjax.hub.Error.Duplicate",
// The specified ManagedHub has no such Container (or it has been removed)
NoContainer: "OpenAjax.hub.Error.NoContainer",
// The specified ManagedHub or Container has no such subscription
NoSubscription: "OpenAjax.hub.Error.NoSubscription",
// Permission denied by manager's security policy
NotAllowed: "OpenAjax.hub.Error.NotAllowed",
// Wrong communications protocol identifier provided by Container or HubClient
WrongProtocol: "OpenAjax.hub.Error.WrongProtocol"
};
/**
* SecurityAlert
*
* Standard codes used when attempted security violations are detected. Unlike
* Errors, these codes are not thrown as exceptions but rather passed into the
* SecurityAlertHandler function registered with the Hub instance.
*/
OpenAjax.hub.SecurityAlert = {
// Container did not load (possible frame phishing attack)
LoadTimeout: "OpenAjax.hub.SecurityAlert.LoadTimeout",
// Hub suspects a frame phishing attack against the specified container
FramePhish: "OpenAjax.hub.SecurityAlert.FramePhish",
// Hub detected a message forgery that purports to come to a specified
// container
ForgedMsg: "OpenAjax.hub.SecurityAlert.ForgedMsg"
};
/**
* Debugging Help
*
* OpenAjax.hub.enableDebug
*
* If OpenAjax.hub.enableDebug is set to true, then the "debugger" keyword
* will get hit whenever a user callback throws an exception, thereby
* bringing up the JavaScript debugger.
*/
OpenAjax.hub._debugger = function() {
if ( OpenAjax.hub.enableDebug ) debugger; // REMOVE ON BUILD
}
////////////////////////////////////////////////////////////////////////////////
/**
* Hub interface
*
* Hub is implemented on the manager side by ManagedHub and on the client side
* by ClientHub.
*/
//OpenAjax.hub.Hub = function() {}
/**
* Subscribe to a topic.
*
* @param {String} topic
* A valid topic string. MAY include wildcards.
* @param {Function} onData
* Callback function that is invoked whenever an event is
* published on the topic
* @param {Object} [scope]
* When onData callback or onComplete callback is invoked,
* the JavaScript "this" keyword refers to this scope object.
* If no scope is provided, default is window.
* @param {Function} [onComplete]
* Invoked to tell the client application whether the
* subscribe operation succeeded or failed.
* @param {*} [subscriberData]
* Client application provides this data, which is handed
* back to the client application in the subscriberData
* parameter of the onData callback function.
*
* @returns subscriptionID
* Identifier representing the subscription. This identifier is an
* arbitrary ID string that is unique within this Hub instance
* @type {String}
*
* @throws {OpenAjax.hub.Error.Disconnected} if this Hub instance is not in CONNECTED state
* @throws {OpenAjax.hub.Error.BadParameters} if the topic is invalid (e.g. contains an empty token)
*/
//OpenAjax.hub.Hub.prototype.subscribe = function( topic, onData, scope, onComplete, subscriberData ) {}
/**
* Publish an event on a topic
*
* @param {String} topic
* A valid topic string. MUST NOT include wildcards.
* @param {*} data
* Valid publishable data. To be portable across different
* Container implementations, this value SHOULD be serializable
* as JSON.
*
* @throws {OpenAjax.hub.Error.Disconnected} if this Hub instance is not in CONNECTED state
* @throws {OpenAjax.hub.Error.BadParameters} if the topic cannot be published (e.g. contains
* wildcards or empty tokens) or if the data cannot be published (e.g. cannot be serialized as JSON)
*/
//OpenAjax.hub.Hub.prototype.publish = function( topic, data ) {}
/**
* Unsubscribe from a subscription
*
* @param {String} subscriptionID
* A subscriptionID returned by Hub.subscribe()
* @param {Function} [onComplete]
* Callback function invoked when unsubscribe completes
* @param {Object} [scope]
* When onComplete callback function is invoked, the JavaScript "this"
* keyword refers to this scope object.
* If no scope is provided, default is window.
*
* @throws {OpenAjax.hub.Error.Disconnected} if this Hub instance is not in CONNECTED state
* @throws {OpenAjax.hub.Error.NoSubscription} if no such subscription is found
*/
//OpenAjax.hub.Hub.prototype.unsubscribe = function( subscriptionID, onComplete, scope ) {}
/**
* Return true if this Hub instance is in the Connected state.
* Else returns false.
*
* This function can be called even if the Hub is not in a CONNECTED state.
*
* @returns Boolean
* @type {Boolean}
*/
//OpenAjax.hub.Hub.prototype.isConnected = function() {}
/**
* Returns the scope associated with this Hub instance and which will be used
* with callback functions.
*
* This function can be called even if the Hub is not in a CONNECTED state.
*
* @returns scope object
* @type {Object}
*/
//OpenAjax.hub.Hub.prototype.getScope = function() {}
/**
* Returns the subscriberData parameter that was provided when
* Hub.subscribe was called.
*
* @param {String} subscriptionID
* The subscriberID of a subscription
*
* @returns subscriberData
* @type {*}
*
* @throws {OpenAjax.hub.Error.Disconnected} if this Hub instance is not in CONNECTED state
* @throws {OpenAjax.hub.Error.NoSubscription} if there is no such subscription
*/
//OpenAjax.hub.Hub.prototype.getSubscriberData = function(subscriptionID) {}
/**
* Returns the scope associated with a specified subscription. This scope will
* be used when invoking the 'onData' callback supplied to Hub.subscribe().
*
* @param {String} subscriberID
* The subscriberID of a subscription
*
* @returns scope
* @type {*}
*
* @throws {OpenAjax.hub.Error.Disconnected} if this Hub instance is not in CONNECTED state
* @throws {OpenAjax.hub.Error.NoSubscription} if there is no such subscription
*/
//OpenAjax.hub.Hub.prototype.getSubscriberScope = function(subscriberID) {}
/**
* Returns the params object associated with this Hub instance.
*
* @returns params
* The params object associated with this Hub instance
* @type {Object}
*/
//OpenAjax.hub.Hub.prototype.getParameters = function() {}
////////////////////////////////////////////////////////////////////////////////
/**
* HubClient interface
*
* Extends Hub interface.
*
* A HubClient implementation is typically specific to a particular
* implementation of Container.
*/
/**
* Create a new HubClient. All HubClient constructors MUST have this
* signature.
* @constructor
*
* @param {Object} params
* Parameters used to instantiate the HubClient.
* Once the constructor is called, the params object belongs to the
* HubClient. The caller MUST not modify it.
* Implementations of HubClient may specify additional properties
* for the params object, besides those identified below.
*
* @param {Function} params.HubClient.onSecurityAlert
* Called when an attempted security breach is thwarted
* @param {Object} [params.HubClient.scope]
* Whenever one of the HubClient's callback functions is called,
* references to "this" in the callback will refer to the scope object.
* If not provided, the default is window.
* @param {Function} [params.HubClient.log]
* Optional logger function. Would be used to log to console.log or
* equivalent.
*
* @throws {OpenAjax.hub.Error.BadParameters} if any of the required
* parameters is missing, or if a parameter value is invalid in
* some way.
*/
//OpenAjax.hub.HubClient = function( params ) {}
/**
* Requests a connection to the ManagedHub, via the Container
* associated with this HubClient.
*
* If the Container accepts the connection request, the HubClient's
* state is set to CONNECTED and the HubClient invokes the
* onComplete callback function.
*
* If the Container refuses the connection request, the HubClient
* invokes the onComplete callback function with an error code.
* The error code might, for example, indicate that the Container
* is being destroyed.
*
* In most implementations, this function operates asynchronously,
* so the onComplete callback function is the only reliable way to
* determine when this function completes and whether it has succeeded
* or failed.
*
* A client application may call HubClient.disconnect and then call
* HubClient.connect.
*
* @param {Function} [onComplete]
* Callback function to call when this operation completes.
* @param {Object} [scope]
* When the onComplete function is invoked, the JavaScript "this"
* keyword refers to this scope object.
* If no scope is provided, default is window.
*
* @throws {OpenAjax.hub.Error.Duplicate} if the HubClient is already connected
*/
//OpenAjax.hub.HubClient.prototype.connect = function( onComplete, scope ) {}
/**
* Disconnect from the ManagedHub
*
* Disconnect immediately:
*
* 1. Sets the HubClient's state to DISCONNECTED.
* 2. Causes the HubClient to send a Disconnect request to the
* associated Container.
* 3. Ensures that the client application will receive no more
* onData or onComplete callbacks associated with this
* connection, except for the disconnect function's own
* onComplete callback.
* 4. Automatically destroys all of the HubClient's subscriptions.
*
* In most implementations, this function operates asynchronously,
* so the onComplete callback function is the only reliable way to
* determine when this function completes and whether it has succeeded
* or failed.
*
* A client application is allowed to call HubClient.disconnect and
* then call HubClient.connect.
*
* @param {Function} [onComplete]
* Callback function to call when this operation completes.
* @param {Object} [scope]
* When the onComplete function is invoked, the JavaScript "this"
* keyword refers to the scope object.
* If no scope is provided, default is window.
*
* @throws {OpenAjax.hub.Error.Disconnected} if the HubClient is already
* disconnected
*/
//OpenAjax.hub.HubClient.prototype.disconnect = function( onComplete, scope ) {}
/**
* If DISCONNECTED: Returns null
* If CONNECTED: Returns the origin associated with the window containing the
* Container associated with this HubClient instance. The origin has the format
*
* [protocol]://[host]
*
* where:
*
* [protocol] is "http" or "https"
* [host] is the hostname of the partner page.
*
* @returns Partner's origin
* @type {String}
*/
//OpenAjax.hub.HubClient.prototype.getPartnerOrigin = function() {}
/**
* Returns the client ID of this HubClient
*
* @returns clientID
* @type {String}
*/
//OpenAjax.hub.HubClient.prototype.getClientID = function() {}
////////////////////////////////////////////////////////////////////////////////
/**
* OpenAjax.hub.ManagedHub
*
* Managed hub API for the manager application and for Containers.
*
* Implements OpenAjax.hub.Hub.
*/
/**
* Create a new ManagedHub instance
* @constructor
*
* This constructor automatically sets the ManagedHub's state to
* CONNECTED.
*
* @param {Object} params
* Parameters used to instantiate the ManagedHub.
* Once the constructor is called, the params object belongs exclusively to
* the ManagedHub. The caller MUST not modify it.
*
* The params object may contain the following properties:
*
* @param {Function} params.onPublish
* Callback function that is invoked whenever a
* data value published by a Container is about
* to be delivered to some (possibly the same) Container.
* This callback function implements a security policy;
* it returns true if the delivery of the data is
* permitted and false if permission is denied.
* @param {Function} params.onSubscribe
* Called whenever a Container tries to subscribe
* on behalf of its client.
* This callback function implements a security policy;
* it returns true if the subscription is permitted
* and false if permission is denied.
* @param {Function} [params.onUnsubscribe]
* Called whenever a Container unsubscribes on behalf of its client.
* Unlike the other callbacks, onUnsubscribe is intended only for
* informative purposes, and is not used to implement a security
* policy.
* @param {Object} [params.scope]
* Whenever one of the ManagedHub's callback functions is called,
* references to the JavaScript "this" keyword in the callback
* function refer to this scope object
* If no scope is provided, default is window.
* @param {Function} [params.log] Optional logger function. Would
* be used to log to console.log or equivalent.
*
* @throws {OpenAjax.hub.Error.BadParameters} if any of the required
* parameters are missing
*/
OpenAjax.hub.ManagedHub = function( params )
{
if ( ! params || ! params.onPublish || ! params.onSubscribe )
throw new Error( OpenAjax.hub.Error.BadParameters );
this._p = params;
this._onUnsubscribe = params.onUnsubscribe ? params.onUnsubscribe : null;
this._scope = params.scope || window;
if ( params.log ) {
var scope = this._scope;
var logfunc = params.log;
this._log = function( msg ) {
logfunc.call( scope, "ManagedHub: " + msg );
};
} else {
this._log = function() {};
}
this._subscriptions = { c:{}, s:null };
this._containers = {};
// Sequence # used to create IDs that are unique within this hub
this._seq = 0;
this._active = true;
this._isPublishing = false;
this._pubQ = [];
}
/**
* Subscribe to a topic on behalf of a Container. Called only by
* Container implementations, NOT by manager applications.
*
* This function:
* 1. Checks with the ManagedHub's onSubscribe security policy
* to determine whether this Container is allowed to subscribe
* to this topic.
* 2. If the subscribe operation is permitted, subscribes to the
* topic and returns the ManagedHub's subscription ID for this
* subscription.
* 3. If the subscribe operation is not permitted, throws
* OpenAjax.hub.Error.NotAllowed.
*
* When data is published on the topic, the ManagedHub's
* onPublish security policy will be invoked to ensure that
* this Container is permitted to receive the published data.
* If the Container is allowed to receive the data, then the
* Container's sendToClient function will be invoked.
*
* When a Container needs to create a subscription on behalf of
* its client, the Container MUST use this function to create
* the subscription.
*
* @param {OpenAjax.hub.Container} container
* A Container
* @param {String} topic
* A valid topic
* @param {String} containerSubID
* Arbitrary string ID that the Container uses to
* represent the subscription. Must be unique within the
* context of the Container
*
* @returns managerSubID
* Arbitrary string ID that this ManagedHub uses to
* represent the subscription. Will be unique within the
* context of this ManagedHub
* @type {String}
*
* @throws {OpenAjax.hub.Error.Disconnected} if this.isConnected() returns false
* @throws {OpenAjax.hub.Error.NotAllowed} if subscription request is denied by the onSubscribe security policy
* @throws {OpenAjax.hub.Error.BadParameters} if one of the parameters, e.g. the topic, is invalid
*/
OpenAjax.hub.ManagedHub.prototype.subscribeForClient = function( container, topic, containerSubID )
{
this._assertConn();
// check subscribe permission
if ( this._invokeOnSubscribe( topic, container ) ) {
// return ManagedHub's subscriptionID for this subscription
return this._subscribe( topic, this._sendToClient, this, { c: container, sid: containerSubID } );
}
throw new Error(OpenAjax.hub.Error.NotAllowed);
}
/**
* Unsubscribe from a subscription on behalf of a Container. Called only by
* Container implementations, NOT by manager application code.
*
* This function:
* 1. Destroys the specified subscription
* 2. Calls the ManagedHub's onUnsubscribe callback function
*
* This function can be called even if the ManagedHub is not in a CONNECTED state.
*
* @param {OpenAjax.hub.Container} container
* container instance that is unsubscribing
* @param {String} managerSubID
* opaque ID of a subscription, returned by previous call to subscribeForClient()
*
* @throws {OpenAjax.hub.Error.NoSubscription} if subscriptionID does not refer to a valid subscription
*/
OpenAjax.hub.ManagedHub.prototype.unsubscribeForClient = function( container, managerSubID )
{
this._unsubscribe( managerSubID );
this._invokeOnUnsubscribe( container, managerSubID );
}
/**
* Publish data on a topic on behalf of a Container. Called only by
* Container implementations, NOT by manager application code.
*
* @param {OpenAjax.hub.Container} container
* Container on whose behalf data should be published
* @param {String} topic
* Valid topic string. Must NOT contain wildcards.
* @param {*} data
* Valid publishable data. To be portable across different
* Container implementations, this value SHOULD be serializable
* as JSON.
*
* @throws {OpenAjax.hub.Error.Disconnected} if this.isConnected() returns false
* @throws {OpenAjax.hub.Error.BadParameters} if one of the parameters, e.g. the topic, is invalid
*/
OpenAjax.hub.ManagedHub.prototype.publishForClient = function( container, topic, data )
{
this._assertConn();
this._publish( topic, data, container );
}
/**
* Destroy this ManagedHub
*
* 1. Sets state to DISCONNECTED. All subsequent attempts to add containers,
* publish or subscribe will throw the Disconnected error. We will
* continue to allow "cleanup" operations such as removeContainer
* and unsubscribe, as well as read-only operations such as
* isConnected
* 2. Remove all Containers associated with this ManagedHub
*/
OpenAjax.hub.ManagedHub.prototype.disconnect = function()
{
this._active = false;
for (var c in this._containers) {
this.removeContainer( this._containers[c] );
}
}
/**
* Get a container belonging to this ManagedHub by its clientID, or null
* if this ManagedHub has no such container
*
* This function can be called even if the ManagedHub is not in a CONNECTED state.
*
* @param {String} containerId
* Arbitrary string ID associated with the container
*
* @returns container associated with given ID
* @type {OpenAjax.hub.Container}
*/
OpenAjax.hub.ManagedHub.prototype.getContainer = function( containerId )
{
var container = this._containers[containerId];
return container ? container : null;
}
/**
* Returns an array listing all containers belonging to this ManagedHub.
* The order of the Containers in this array is arbitrary.
*
* This function can be called even if the ManagedHub is not in a CONNECTED state.
*
* @returns container array
* @type {OpenAjax.hub.Container[]}
*/
OpenAjax.hub.ManagedHub.prototype.listContainers = function()
{
var res = [];
for (var c in this._containers) {
res.push(this._containers[c]);
}
return res;
}
/**
* Add a container to this ManagedHub.
*
* This function should only be called by a Container constructor.
*
* @param {OpenAjax.hub.Container} container
* A Container to be added to this ManagedHub
*
* @throws {OpenAjax.hub.Error.Duplicate} if there is already a Container
* in this ManagedHub whose clientId is the same as that of container
* @throws {OpenAjax.hub.Error.Disconnected} if this.isConnected() returns false
*/
OpenAjax.hub.ManagedHub.prototype.addContainer = function( container )
{
this._assertConn();
var containerId = container.getClientID();
if ( this._containers[containerId] ) {
throw new Error(OpenAjax.hub.Error.Duplicate);
}
this._containers[containerId] = container;
}
/**
* Remove a container from this ManagedHub immediately
*
* This function can be called even if the ManagedHub is not in a CONNECTED state.
*
* @param {OpenAjax.hub.Container} container
* A Container to be removed from this ManagedHub
*
* @throws {OpenAjax.hub.Error.NoContainer} if no such container is found
*/
OpenAjax.hub.ManagedHub.prototype.removeContainer = function( container )
{
var containerId = container.getClientID();
if ( ! this._containers[ containerId ] ) {
throw new Error(OpenAjax.hub.Error.NoContainer);
}
container.remove();
delete this._containers[ containerId ];
}
/*** OpenAjax.hub.Hub interface implementation ***/
/**
* Subscribe to a topic.
*
* This implementation of Hub.subscribe is synchronous. When subscribe
* is called:
*
* 1. The ManagedHub's onSubscribe callback is invoked. The
* container parameter is null, because the manager application,
* rather than a container, is subscribing.
* 2. If onSubscribe returns true, then the subscription is created.
* 3. The onComplete callback is invoked.
* 4. Then this function returns.
*
* @param {String} topic
* A valid topic string. MAY include wildcards.
* @param {Function} onData
* Callback function that is invoked whenever an event is
* published on the topic
* @param {Object} [scope]
* When onData callback or onComplete callback is invoked,
* the JavaScript "this" keyword refers to this scope object.
* If no scope is provided, default is window.
* @param {Function} [onComplete]
* Invoked to tell the client application whether the
* subscribe operation succeeded or failed.
* @param {*} [subscriberData]
* Client application provides this data, which is handed
* back to the client application in the subscriberData
* parameter of the onData and onComplete callback functions.
*
* @returns subscriptionID
* Identifier representing the subscription. This identifier is an
* arbitrary ID string that is unique within this Hub instance
* @type {String}
*
* @throws {OpenAjax.hub.Error.Disconnected} if this Hub instance is not in CONNECTED state
* @throws {OpenAjax.hub.Error.BadParameters} if the topic is invalid (e.g. contains an empty token)
*/
OpenAjax.hub.ManagedHub.prototype.subscribe = function( topic, onData, scope, onComplete, subscriberData )
{
this._assertConn();
this._assertSubTopic(topic);
if ( ! onData ) {
throw new Error( OpenAjax.hub.Error.BadParameters );
}
// check subscribe permission
if ( ! this._invokeOnSubscribe( topic, null ) ) {
this._invokeOnComplete( onComplete, scope, null, false, OpenAjax.hub.Error.NotAllowed );
return;
}
// on publish event, check publish permissions
scope = scope || window;
var that = this;
function publishCB( topic, data, sd, pcont ) {
if ( that._invokeOnPublish( topic, data, pcont, null ) ) {
try {
onData.call( scope, topic, data, subscriberData );
} catch( e ) {
OpenAjax.hub._debugger();
that._log( "caught error from onData callback to Hub.subscribe(): " + e.message );
}
}
}
var subID = this._subscribe( topic, publishCB, scope, subscriberData );
this._invokeOnComplete( onComplete, scope, subID, true );
return subID;
}
/**
* Publish an event on a topic
*
* This implementation of Hub.publish is synchronous. When publish
* is called:
*
* 1. The target subscriptions are identified.
* 2. For each target subscription, the ManagedHub's onPublish
* callback is invoked. Data is only delivered to a target
* subscription if the onPublish callback returns true.
* The pcont parameter of the onPublish callback is null.
* This is because the ManagedHub, rather than a container,
* is publishing the data.
*
* @param {String} topic
* A valid topic string. MUST NOT include wildcards.
* @param {*} data
* Valid publishable data. To be portable across different
* Container implementations, this value SHOULD be serializable
* as JSON.
*
* @throws {OpenAjax.hub.Error.Disconnected} if this Hub instance is not in CONNECTED state
* @throws {OpenAjax.hub.Error.BadParameters} if the topic cannot be published (e.g. contains
* wildcards or empty tokens) or if the data cannot be published (e.g. cannot be serialized as JSON)
*/
OpenAjax.hub.ManagedHub.prototype.publish = function( topic, data )
{
this._assertConn();
this._assertPubTopic(topic);
this._publish( topic, data, null );
}
/**
* Unsubscribe from a subscription
*
* This implementation of Hub.unsubscribe is synchronous. When unsubscribe
* is called:
*
* 1. The subscription is destroyed.
* 2. The ManagedHub's onUnsubscribe callback is invoked, if there is one.
* 3. The onComplete callback is invoked.
* 4. Then this function returns.
*
* @param {String} subscriptionID
* A subscriptionID returned by Hub.subscribe()
* @param {Function} [onComplete]
* Callback function invoked when unsubscribe completes
* @param {Object} [scope]
* When onComplete callback function is invoked, the JavaScript "this"
* keyword refers to this scope object.
* If no scope is provided, default is window.
*
* @throws {OpenAjax.hub.Error.Disconnected} if this Hub instance is not in CONNECTED state
* @throws {OpenAjax.hub.Error.NoSubscription} if no such subscription is found
*/
OpenAjax.hub.ManagedHub.prototype.unsubscribe = function( subscriptionID, onComplete, scope )
{
this._assertConn();
if ( typeof subscriptionID === "undefined" || subscriptionID == null ) {
throw new Error( OpenAjax.hub.Error.BadParameters );
}
this._unsubscribe( subscriptionID );
this._invokeOnUnsubscribe( null, subscriptionID );
this._invokeOnComplete( onComplete, scope, subscriptionID, true );
}
/**
* Returns true if disconnect() has NOT been called on this ManagedHub,
* else returns false
*
* @returns Boolean
* @type {Boolean}
*/
OpenAjax.hub.ManagedHub.prototype.isConnected = function()
{
return this._active;
}
/**
* Returns the scope associated with this Hub instance and which will be used
* with callback functions.
*
* This function can be called even if the Hub is not in a CONNECTED state.
*
* @returns scope object
* @type {Object}
*/
OpenAjax.hub.ManagedHub.prototype.getScope = function()
{
return this._scope;
}
/**
* Returns the subscriberData parameter that was provided when
* Hub.subscribe was called.
*
* @param subscriberID
* The subscriberID of a subscription
*
* @returns subscriberData
* @type {*}
*
* @throws {OpenAjax.hub.Error.Disconnected} if this Hub instance is not in CONNECTED state
* @throws {OpenAjax.hub.Error.NoSubscription} if there is no such subscription
*/
OpenAjax.hub.ManagedHub.prototype.getSubscriberData = function( subscriberID )
{
this._assertConn();
var path = subscriberID.split(".");
var sid = path.pop();
var sub = this._getSubscriptionObject( this._subscriptions, path, 0, sid );
if ( sub )
return sub.data;
throw new Error( OpenAjax.hub.Error.NoSubscription );
}
/**
* Returns the scope associated with a specified subscription. This scope will
* be used when invoking the 'onData' callback supplied to Hub.subscribe().
*
* @param subscriberID
* The subscriberID of a subscription
*
* @returns scope
* @type {*}
*
* @throws {OpenAjax.hub.Error.Disconnected} if this Hub instance is not in CONNECTED state
* @throws {OpenAjax.hub.Error.NoSubscription} if there is no such subscription
*/
OpenAjax.hub.ManagedHub.prototype.getSubscriberScope = function( subscriberID )
{
this._assertConn();
var path = subscriberID.split(".");
var sid = path.pop();
var sub = this._getSubscriptionObject( this._subscriptions, path, 0, sid );
if ( sub )
return sub.scope;
throw new Error( OpenAjax.hub.Error.NoSubscription );
}
/**
* Returns the params object associated with this Hub instance.
* Allows mix-in code to access parameters passed into constructor that created
* this Hub instance.
*
* @returns params the params object associated with this Hub instance
* @type {Object}
*/
OpenAjax.hub.ManagedHub.prototype.getParameters = function()
{
return this._p;
}
/* PRIVATE FUNCTIONS */
/**
* Send a message to a container's client.
* This is an OAH subscriber's data callback. It is private to ManagedHub
* and serves as an adapter between the OAH 1.0 API and Container.sendToClient.
*
* @param {String} topic Topic on which data was published
* @param {Object} data Data to be delivered to the client
* @param {Object} sd Object containing properties
* c: container to which data must be sent
* sid: subscription ID within that container
* @param {Object} pcont Publishing container, or null if this data was
* published by the manager
*/
OpenAjax.hub.ManagedHub.prototype._sendToClient = function(topic, data, sd, pcont)
{
if (!this.isConnected()) {
return;
}
if ( this._invokeOnPublish( topic, data, pcont, sd.c ) ) {
sd.c.sendToClient( topic, data, sd.sid );
}
}
OpenAjax.hub.ManagedHub.prototype._assertConn = function()
{
if (!this.isConnected()) {
throw new Error(OpenAjax.hub.Error.Disconnected);
}
}
OpenAjax.hub.ManagedHub.prototype._assertPubTopic = function(topic)
{
if ((topic == null) || (topic == "") || (topic.indexOf("*") != -1) ||
(topic.indexOf("..") != -1) || (topic.charAt(0) == ".") ||
(topic.charAt(topic.length-1) == "."))
{
throw new Error(OpenAjax.hub.Error.BadParameters);
}
}
OpenAjax.hub.ManagedHub.prototype._assertSubTopic = function(topic)
{
if ( ! topic ) {
throw new Error(OpenAjax.hub.Error.BadParameters);
}
var path = topic.split(".");
var len = path.length;
for (var i = 0; i < len; i++) {
var p = path[i];
if ((p == "") ||
((p.indexOf("*") != -1) && (p != "*") && (p != "**"))) {
throw new Error(OpenAjax.hub.Error.BadParameters);
}
if ((p == "**") && (i < len - 1)) {
throw new Error(OpenAjax.hub.Error.BadParameters);
}
}
}
OpenAjax.hub.ManagedHub.prototype._invokeOnComplete = function( func, scope, item, success, errorCode )
{
if ( func ) { // onComplete is optional
try {
scope = scope || window;
func.call( scope, item, success, errorCode );
} catch( e ) {
OpenAjax.hub._debugger();
this._log( "caught error from onComplete callback: " + e.message );
}
}
}
OpenAjax.hub.ManagedHub.prototype._invokeOnPublish = function( topic, data, pcont, scont )
{
try {
return this._p.onPublish.call( this._scope, topic, data, pcont, scont );
} catch( e ) {
OpenAjax.hub._debugger();
this._log( "caught error from onPublish callback to constructor: " + e.message );
}
return false;
}
OpenAjax.hub.ManagedHub.prototype._invokeOnSubscribe = function( topic, container )
{
try {
return this._p.onSubscribe.call( this._scope, topic, container );
} catch( e ) {
OpenAjax.hub._debugger();
this._log( "caught error from onSubscribe callback to constructor: " + e.message );
}
return false;
}
OpenAjax.hub.ManagedHub.prototype._invokeOnUnsubscribe = function( container, managerSubID )
{
if ( this._onUnsubscribe ) {
var topic = managerSubID.slice( 0, managerSubID.lastIndexOf(".") );
try {
this._onUnsubscribe.call( this._scope, topic, container );
} catch( e ) {
OpenAjax.hub._debugger();
this._log( "caught error from onUnsubscribe callback to constructor: " + e.message );
}
}
}
OpenAjax.hub.ManagedHub.prototype._subscribe = function( topic, onData, scope, subscriberData )
{
var handle = topic + "." + this._seq;
var sub = { scope: scope, cb: onData, data: subscriberData, sid: this._seq++ };
var path = topic.split(".");
this._recursiveSubscribe( this._subscriptions, path, 0, sub );
return handle;
}
OpenAjax.hub.ManagedHub.prototype._recursiveSubscribe = function(tree, path, index, sub)
{
var token = path[index];
if (index == path.length) {
sub.next = tree.s;
tree.s = sub;
} else {
if (typeof tree.c == "undefined") {
tree.c = {};
}
if (typeof tree.c[token] == "undefined") {
tree.c[token] = { c: {}, s: null };
this._recursiveSubscribe(tree.c[token], path, index + 1, sub);
} else {
this._recursiveSubscribe( tree.c[token], path, index + 1, sub);
}
}
}
OpenAjax.hub.ManagedHub.prototype._publish = function( topic, data, pcont )
{
// if we are currently handling a publish event, then queue this request
// and handle later, one by one
if ( this._isPublishing ) {
this._pubQ.push( { t: topic, d: data, p: pcont } );
return;
}
this._safePublish( topic, data, pcont );
while ( this._pubQ.length > 0 ) {
var pub = this._pubQ.shift();
this._safePublish( pub.t, pub.d, pub.p );
}
}
OpenAjax.hub.ManagedHub.prototype._safePublish = function( topic, data, pcont )
{
this._isPublishing = true;
var path = topic.split(".");
this._recursivePublish( this._subscriptions, path, 0, topic, data, pcont );
this._isPublishing = false;
}
OpenAjax.hub.ManagedHub.prototype._recursivePublish = function(tree, path, index, name, msg, pcont)
{
if (typeof tree != "undefined") {
var node;
if (index == path.length) {
node = tree;
} else {
this._recursivePublish(tree.c[path[index]], path, index + 1, name, msg, pcont);
this._recursivePublish(tree.c["*"], path, index + 1, name, msg, pcont);
node = tree.c["**"];
}
if (typeof node != "undefined") {
var sub = node.s;
while ( sub ) {
var sc = sub.scope;
var cb = sub.cb;
var d = sub.data;
var sid = sub.sid;
if (typeof cb == "string") {
// get a function object
cb = sc[cb];
}
cb.call(sc, name, msg, d, pcont);
sub = sub.next;
}
}
}
}
OpenAjax.hub.ManagedHub.prototype._unsubscribe = function( subscriptionID )
{
var path = subscriptionID.split(".");
var sid = path.pop();
if ( ! this._recursiveUnsubscribe( this._subscriptions, path, 0, sid ) ) {
throw new Error( OpenAjax.hub.Error.NoSubscription );
}
}
/**
* @returns 'true' if properly unsubscribed; 'false' otherwise
*/
OpenAjax.hub.ManagedHub.prototype._recursiveUnsubscribe = function(tree, path, index, sid)
{
if ( typeof tree == "undefined" ) {
return false;
}
if (index < path.length) {
var childNode = tree.c[path[index]];
if ( ! childNode ) {
return false;
}
this._recursiveUnsubscribe(childNode, path, index + 1, sid);
if (childNode.s == null) {
for (var x in childNode.c) {
return true;
}
delete tree.c[path[index]];
}
} else {
var sub = tree.s;
var sub_prev = null;
var found = false;
while ( sub ) {
if ( sid == sub.sid ) {
found = true;
if ( sub == tree.s ) {
tree.s = sub.next;
} else {
sub_prev.next = sub.next;
}
break;
}
sub_prev = sub;
sub = sub.next;
}
if ( ! found ) {
return false;
}
}
return true;
}
OpenAjax.hub.ManagedHub.prototype._getSubscriptionObject = function( tree, path, index, sid )
{
if (typeof tree != "undefined") {
if (index < path.length) {
var childNode = tree.c[path[index]];
return this._getSubscriptionObject(childNode, path, index + 1, sid);
}
var sub = tree.s;
while ( sub ) {
if ( sid == sub.sid ) {
return sub;
}
sub = sub.next;
}
}
return null;
}
////////////////////////////////////////////////////////////////////////////////
/**
* Container
* @constructor
*
* Container represents an instance of a manager-side object that contains and
* communicates with a single client of the hub. The container might be an inline
* container, an iframe FIM container, or an iframe PostMessage container, or
* it might be an instance of some other implementation.
*
* @param {OpenAjax.hub.ManagedHub} hub
* Managed Hub instance
* @param {String} clientID
* A string ID that identifies a particular client of a Managed Hub. Unique
* within the context of the ManagedHub.
* @param {Object} params
* Parameters used to instantiate the Container.
* Once the constructor is called, the params object belongs exclusively to
* the Container. The caller MUST not modify it.
* Implementations of Container may specify additional properties
* for the params object, besides those identified below.
* The following params properties MUST be supported by all Container
* implementations:
* @param {Function} params.Container.onSecurityAlert
* Called when an attempted security breach is thwarted. Function is defined
* as follows: function(container, securityAlert)
* @param {Function} [params.Container.onConnect]
* Called when the client connects to the Managed Hub. Function is defined
* as follows: function(container)
* @param {Function} [params.Container.onDisconnect]
* Called when the client disconnects from the Managed Hub. Function is
* defined as follows: function(container)
* @param {Object} [params.Container.scope]
* Whenever one of the Container's callback functions is called, references
* to "this" in the callback will refer to the scope object. If no scope is
* provided, default is window.
* @param {Function} [params.Container.log]
* Optional logger function. Would be used to log to console.log or
* equivalent.
*
* @throws {OpenAjax.hub.Error.BadParameters} if required params are not
* present or null
* @throws {OpenAjax.hub.Error.Duplicate} if a Container with this clientID
* already exists in the given Managed Hub
* @throws {OpenAjax.hub.Error.Disconnected} if ManagedHub is not connected
*/
//OpenAjax.hub.Container = function( hub, clientID, params ) {}
/**
* Send a message to the client inside this container. This function MUST only
* be called by ManagedHub.
*
* @param {String} topic
* The topic name for the published message
* @param {*} data
* The payload. Can be any JSON-serializable value.
* @param {String} containerSubscriptionId
* Container's ID for a subscription, from previous call to
* subscribeForClient()
*/
//OpenAjax.hub.Container.prototype.sendToClient = function( topic, data, containerSubscriptionId ) {}
/**
* Shut down a container. remove does all of the following:
* - disconnects container from HubClient
* - unsubscribes from all of its existing subscriptions in the ManagedHub
*
* This function is only called by ManagedHub.removeContainer
* Calling this function does NOT cause the container's onDisconnect callback to
* be invoked.
*/
//OpenAjax.hub.Container.prototype.remove = function() {}
/**
* Returns true if the given client is connected to the managed hub.
* Else returns false.
*
* @returns true if the client is connected to the managed hub
* @type boolean
*/
//OpenAjax.hub.Container.prototype.isConnected = function() {}
/**
* Returns the clientID passed in when this Container was instantiated.
*
* @returns The clientID
* @type {String}
*/
//OpenAjax.hub.Container.prototype.getClientID = function() {}
/**
* If DISCONNECTED:
* Returns null
* If CONNECTED:
* Returns the origin associated with the window containing the HubClient
* associated with this Container instance. The origin has the format
*
* [protocol]://[host]
*
* where:
*
* [protocol] is "http" or "https"
* [host] is the hostname of the partner page.
*
* @returns Partner's origin
* @type {String}
*/
//OpenAjax.hub.Container.prototype.getPartnerOrigin = function() {}
/**
* Returns the params object associated with this Container instance.
*
* @returns params
* The params object associated with this Container instance
* @type {Object}
*/
//OpenAjax.hub.Container.prototype.getParameters = function() {}
/**
* Returns the ManagedHub to which this Container belongs.
*
* @returns ManagedHub
* The ManagedHub object associated with this Container instance
* @type {OpenAjax.hub.ManagedHub}
*/
//OpenAjax.hub.Container.prototype.getHub = function() {}
////////////////////////////////////////////////////////////////////////////////
/*
* Unmanaged Hub
*/
/**
* OpenAjax.hub._hub is the default ManagedHub instance that we use to
* provide OAH 1.0 behavior.
*/
OpenAjax.hub._hub = new OpenAjax.hub.ManagedHub({
onSubscribe: function(topic, ctnr) { return true; },
onPublish: function(topic, data, pcont, scont) { return true; }
});
/**
* Subscribe to a topic.
*
* @param {String} topic
* A valid topic string. MAY include wildcards.
* @param {Function|String} onData
* Callback function that is invoked whenever an event is published on the
* topic. If 'onData' is a string, then it represents the name of a
* function on the 'scope' object.
* @param {Object} [scope]
* When onData callback is invoked,
* the JavaScript "this" keyword refers to this scope object.
* If no scope is provided, default is window.
* @param {*} [subscriberData]
* Client application provides this data, which is handed
* back to the client application in the subscriberData
* parameter of the onData callback function.
*
* @returns {String} Identifier representing the subscription.
*
* @throws {OpenAjax.hub.Error.BadParameters} if the topic is invalid
* (e.g.contains an empty token)
*/
OpenAjax.hub.subscribe = function(topic, onData, scope, subscriberData)
{
// resolve the 'onData' function if it is a string
if ( typeof onData === "string" ) {
scope = scope || window;
onData = scope[ onData ] || null;
}
return OpenAjax.hub._hub.subscribe( topic, onData, scope, null, subscriberData );
}
/**
* Unsubscribe from a subscription.
*
* @param {String} subscriptionID
* Subscription identifier returned by subscribe()
*
* @throws {OpenAjax.hub.Error.NoSubscription} if no such subscription is found
*/
OpenAjax.hub.unsubscribe = function(subscriptionID)
{
return OpenAjax.hub._hub.unsubscribe( subscriptionID );
}
/**
* Publish an event on a topic.
*
* @param {String} topic
* A valid topic string. MUST NOT include wildcards.
* @param {*} data
* Valid publishable data.
*
* @throws {OpenAjax.hub.Error.BadParameters} if the topic cannot be published
* (e.g. contains wildcards or empty tokens)
*/
OpenAjax.hub.publish = function(topic, data)
{
OpenAjax.hub._hub.publish(topic, data);
}
////////////////////////////////////////////////////////////////////////////////
// Register the OpenAjax Hub itself as a library.
OpenAjax.hub.registerLibrary("OpenAjax", "http://openajax.org/hub", "2.0", {});
} // !window["OpenAjax"]
/*
* (C) Copyright IBM Corp. 2007
*/
var partnerWindowNullError = new Error ("The communication partner's window was set to null.");
var notInitializedError = new Error ("CommLib was not initialized.");
/**
* Communication Library which takes care of the cross domain IFrame communication
*
* Goal: Provide an policy independent communication module for use in experiments such as SEHub or policy for free.
*
* The application using this library should not use any frames with the same name as the tunnelGUID.
**/
/**
* The protocol that is implemented in this object uses the following message types
*
* init: Initializes the CommLib. The data exchanged in this message contains configuration parameters.
* start: The begining of a multimpart message. The data exchanged in this message is the first part of the multipart message.
* part: An intermediate part of a multipart message. The data exchanged in this message is a part of the whole message.
* end: The end of a message. If the message is a multipart message, this is the final part. If this is not a multipart message this is the only and therefore also final part.
* ok: An acknowledgement message. This is sent back to the communicating partner to inform recieval of the message.
* loaded: A message sent by the tunnel iframe to inform the component of the fact that the tunnel is now open.
**/
function CommLib(){
// polling and queue processing interval
var interval=10
// The maximul length of a URL. If the message is longer it will be split into different parts.
var urlLimit = 4000;
// Keeps track of the sending state of the component.
var sending = false;
// The current value of the data behind the Hash. Will be used to discover incoming messages.
var currentHash = null;
// The URL of the partner iFrame.
var partnerURL = null;
// The name of the window of the partner iFrame.
var partnerWindow = null;
// The data that will be send to the partner iFrame.
var messageOut = null;
// The split version of the outgoing message to make sure that we don't go over the limit of the url length.
var messageOutParts = [];
// The index of the current message that is being sent.
var currentOutPart = 0;
// Unique identifier to track the current message which we are communicating. Currently used to make sure "ok" is mapped to the last send message.
var messageNr = 0;
// The incomming message recieved from the partner iFrame.
var messageIn = "";
// This is used to make the object available to the private methods. This is a workaround for an error in the ECMAScript Language Specification which causes this to be set incorrectly for inner functions. See http://www.crockford.com/javascript/private.html
var that=this;
// A queue for outgoing messages. This queue is used when new send requests are done while we are still sending or recieving a message.
var queueOut=[];
// Variable storing the identifier for the setInterval if processing an output queue
var queueOutProcess=null;
// A queue for incomming messages. This queue is used when new messages arrive but there is no reciepient set yet. This can occur if one of the frames requires a long loading time.
var queueIn=[];
// Variable storing the identifier for the setInterval if processing an input queue
var queueInProcess=null;
// Variable storing the callback to the recieved function
var recieved=null;
// Variable storing the callback to the security listener function
var securityListener=null;
// Variable storing the initialization
var initialized=false;
// The security token used for checking message origin
var securityToken=null;
/**
* Sends a message to the partner iFrame.
*
* @param message The textual data that will be send to the partner iFrame
*/
this.send=function(message){
// Queue the message if sending or if there is no communication partner yet
if (sending || partnerWindow==null){
queueOut[queueOut.length]=message;
// Start a timer if no timer has started yet
if (queueOut.length==1){
queueOutProcess=setInterval(processOutQueue,interval);
}
return;
}
//URI encode the message for transport over the fragment
var encodedData=encodeURIComponent(message);
// Current code ignores messages when already sending
if(!sending){
sending = true;
messageOut = encodedData || "";
sendMessageStart();
}
}
/**
* Sets the callback which will be called when a new message has been recieved.
*
* @param callback The callback to the recieved method;
*/
this.setRecieved=function(callback){
recieved=callback;
};
/**
* Sets the callback for security errors.
*
* @param The callback for security errors.
*/
this.setSecurityListener=function(callback){
securityListener=callback;
}
/**
* Starts listening for incomming messages by polling the data after the #
*/
this.listen=function(){
//console.debug ("CommLib.listen " + window.location.href.split("#")[0]);
//Start counter to inspect hash value.
setInterval(pollHash, interval);
}
/**
* Sets the communication partner's info
* @param parWindow The partner window.
* @param parURL The partner URL.
*/
this.setPartnerWindow=function(parWindow){
if (parWindow==null)throw partnerWindowNullError;
partnerWindow=parWindow;
}
/**
* Returns the URL of the partner of this CommLib.
*/
this.getPartnerURL=function(){
return partnerURL;
}
/**
* Returns the security token used by this CommLib.
*/
this.getSecurityToken=function(){
return securityToken;
}
/**
* Processes a the output queue
*/
function processOutQueue(){
// return if not ready to send
if (sending || partnerWindow==null)return;
// Send the first element in the Array
that.send(queueOut.shift());
// Stop sending polling when the queue is empty
if (queueOut.length==0)clearInterval(queueOutProcess);
}
/**
* Checks the information after the hash to see if there is a new incomming message.
*/
function pollHash(){
//Can't use location.hash because at least Firefox does a decodeURIComponent on it.
var urlParts = window.location.href.split("#");
if(urlParts.length == 2){
var newHash = urlParts[1];
if(newHash != currentHash){
try{
messageReceived(newHash);
}catch(e){
//Make sure to not keep processing the error hash value.
currentHash = newHash;
throw e;
}
currentHash = newHash;
}
}
}
/**
* Processes a the output queue
*/
function processInQueue(){
// return if the reciever method has not been set
if (that.recieved==null)return;
// Send the first element in the Array
that.recieved(queueIn.shift());
// Stop polling when the queue is empty
if (queueIn.length==0)clearInterval(queueInProcess);
}
/**
* This method boots the sending of a messages. It is responsible for splitting the data in parts if necessary.
*/
function sendMessageStart(){
//Break the message into parts, if necessary.
messageOutParts = [];
var reqData = messageOut;
var urlLength = partnerURL.length;
var partLength = urlLimit - urlLength;
var reqIndex = 0;
while((reqData.length - reqIndex) + urlLength > urlLimit){
var part = reqData.substring(reqIndex, reqIndex + partLength);
//Safari will do some extra hex escaping unless we keep the original hex
//escaping complete.
var percentIndex = part.lastIndexOf("%");
if(percentIndex == part.length - 1 || percentIndex == part.length - 2){
part = part.substring(0, percentIndex);
}
messageOutParts.push(part);
reqIndex += part.length;
}
messageOutParts.push(reqData.substring(reqIndex, reqData.length));
currentOutPart = 0;
sendMessagePart();
}
/**
* Sends the current part of the message. This method is required for split messages.
*/
function sendMessagePart(){
if(currentOutPart < messageOutParts.length){
messageNr++;
//Get the message part.
var partData = messageOutParts[currentOutPart];
//Get the command.
var cmd = "part";
if(currentOutPart + 1 == messageOutParts.length){
cmd = "end";
}else if (currentOutPart == 0){
cmd = "start";
}
setPartnerUrl(cmd, messageNr, partData);
currentOutPart++;
}
else{
sending=false;
currentOutPart=0;
}
}
/**
* Sets the URL of the partner iFrame. This actually transmits the data to the partner iFrame.
*
* @param cmd The current command that is being used. @see #messageReceived.
* @param messageNr A counter keeping track of the current message we are sending. This counter is used by both partners to identify which message they are exchanging.
* @param message The actual encoded messag which will be send to the partner.
*/
function setPartnerUrl(cmd, messageNr, message){
if (!initialized)throw notInitializedError;
var url = makePartnerUrl(cmd,messageNr,message);
//console.debug ("CommLib.setPartnerUrl " + window.location.href.split("#")[0] + " " + cmd + " " + message);
//Safari won't let us replace across domains.
if(navigator.userAgent.indexOf("Safari") == -1){
partnerWindow.location.replace(url);
}else{
partnerWindow.location = url;
}
}
/**
* Creates the full URL that will be sent to the partner. This method adds the command, message number, and data to the partner URL.
*
* @param cmd The current command that is being used. @see #messageReceived.
* @param messageNr A counter keeping track of the current message we are sending. This counter is used by both partners to identify which message they are exchanging.
* @param message The actual encoded messag which will be send to the partner. *
*/
function makePartnerUrl(cmd, messageNr, message){
var url = partnerURL + "#" + cmd + ":" + messageNr + ":" + securityToken;
if(message){
url += ":" + message;
}
return url;
}
/**
* Unwraps and decodes the incomming message.
* @param encodedMessage The data behind the #.
* @return An object representing the decoded data.
*/
function unpackMessage(encodedMessage){
var parts = encodedMessage.split(":");
var command = parts[0];
messageNr=parts[1];
var token=parts[2];
encodedMessage = parts[3] || "";
var config = null;
if(command == "init"){
// initialize security token
securityToken=token;
var configParts = encodedMessage.split("&");
config = {};
for(var i = 0; i < configParts.length; i++){
var nameValue = configParts[i].split("=");
config[decodeURIComponent(nameValue[0])] = decodeURIComponent(nameValue[1]);
}
}
else{
//verify security token
if (token!=securityToken){
if (securityListener!=null){
securityListener(SecurityErrors.INVALID_TOKEN);
}
else{
throw new Error (SecurityErrors.INVALID_TOKEN_MSG);
}
return null;
}
}
return {command: command, message: encodedMessage, config: config, number: messageNr};
}
/**
* Is called when a new message has been detected. I.e., when the data behind the # has changed.
*
* @param encodedData The URI encoded data which is stored behind the #.
*/
function messageReceived(encodedData){
//console.debug ("CommLib.messageRecieved " + window.location.href.split("#")[0] + " " + encodedData);
var msg = unpackMessage(encodedData);
// msg is null when a security error occurs
if (msg==null) return;
switch(msg.command){
case "init":
init(msg.config);
initialized=true;
break;
case "ok":
sendMessagePart();
break;
case "start":
messageIn = "";
messageIn += msg.message;
setPartnerUrl("ok", messageNr);
break;
case "part":
messageIn += msg.message;
setPartnerUrl("ok", messageNr);
break;
case "end":
messageIn += msg.message;
// Needed to be done before the actual message is processed.
setPartnerUrl("ok", messageNr);
// TODO check the actual impact of this change. It was needed to keep the code running.
// This basically says that recieving a new message before the ACK was recieved is considered as an ACK + the new message.
sending=false;
if (recieved==null || queueIn.length>0){
//Queue the message and start the queue processor
queueIn[queueIn.length]=decodeURIComponent(messageIn);
// Start a timer if no timer has started yet
if (queueIn.length==1){
queueInProcess=setInterval(processInQueue,interval);
}
}
else{
recieved(decodeURIComponent(messageIn));
}
messageIn="";
break;
}
}
function init(config){
// get the partnerURL based on the config information
partnerURL=config.pU;
}
// Triggers reading the initialization information.
pollHash();
};
function CommLibComponent(){
// Variable storing the CommLib (Note the setting of the init callback upon creation)
var commLib=new CommLib();
// GUI which will be used to name the iFrame tunnel.
var tunnelGUID="3827816c-f3b1-11db-8314-0800200c9a66"
/**
* Sets the callback for incomming messages.
*
* @param The callback for incomming messages.
*/
this.setRecieved=function(callback){
commLib.setRecieved(callback);
}
/**
* Sets the callback for security errors.
*
* @param The callback for security errors.
*/
this.setSecurityListener=function(callback){
commLib.setSecurityListener(callback);
}
/**
* Sends a message to the partner iFrame.
*
* @param message The textual data that will be send to the partner iFrame
*/
this.send=function(message){
return commLib.send(message);
}
/**
* Creates the iFrame though which the communication with the main application is done.
*/
function createTunnelIFrame(partnerURL,tunnelGUID, securityToken){
var iframe = document.createElement("iframe");
iframe.src=partnerURL + "#init:0:" + securityToken + ":pU=" + encodeURIComponent(window.location.href.split("#")[0]);
iframe.name=tunnelGUID;
iframe.id=tunnelGUID;
document.body.appendChild(iframe);
if (navigator.userAgent.indexOf("Safari") == -1) {
iframe.style.position = "absolute";
}
iframe.style.left = iframe.style.top = "0px";
iframe.style.height = iframe.style.width = "1px";
iframe.style.visibility = "hidden";
}
/**
* Initializes the communication library based on the information in the config. There are two roles:
* 1. Component: In this role the CommLib resides inside the component and bridges the communication between the domain of the component and the domain of the main application.
* 2. Tunnel: In this role the CommLib resides inside the tunnel iFrame and bridges the communication between the domain of the component and the domain of the main application.
* In the tunnel role the iFrame is hidden internally in the component but since the iFrame in the component and the main application are in the same domain they can talk to each other.
* This is implemented in this way because of the required iFrame recursion for IE 7.0 See http://tagneto.blogspot.com/2006/10/ie-7-and-iframe-apis-part-2.html.
*
* @param config The configuration information on which the lib will base it's initialization.
*/
function init(){
// Generate the tunnel iframe
createTunnelIFrame(commLib.getPartnerURL(),tunnelGUID,commLib.getSecurityToken());
// Set the partner window
commLib.setPartnerWindow(window.frames[tunnelGUID]);
// Start listening
commLib.listen();
}
init();
};
function CommLibTunnel(){
var commLib=new CommLib();
/**
* Returns the URL of the partner of this CommLib.
*/
this.getPartnerURL=function(){
return commLib.getPartnerURL();
}
/**
* Returns the security token used by this CommLib.
*/
this.getSecurityToken=function(){
return commLib.getSecurityToken();
}
/**
* Sets the callback for incomming messages.
*
* @param The callback for incomming messages.
*/
this.setRecieved=function(callback){
commLib.setRecieved(callback);
}
/**
* Sets the callback for security errors.
*
* @param The callback for security errors.
*/
this.setSecurityListener=function(callback){
commLib.setSecurityListener(callback);
}
/**
* Sends a message to the partner iFrame.
*
* @param message The textual data that will be send to the partner iFrame
*/
this.send=function(message){
return commLib.send(message);
}
/**
* Initializes the communication library based on the information in the config. There are two roles:
* 1. Component: In this role the CommLib resides inside the component and bridges the communication between the domain of the component and the domain of the main application.
* 2. Tunnel: In this role the CommLib resides inside the tunnel iFrame and bridges the communication between the domain of the component and the domain of the main application.
* In the tunnel role the iFrame is hidden internally in the component but since the iFrame in the component and the main application are in the same domain they can talk to each other.
* This is implemented in this way because of the required iFrame recursion for IE 7.0 See http://tagneto.blogspot.com/2006/10/ie-7-and-iframe-apis-part-2.html.
*
* @param config The configuration information on which the lib will base it's initialization.
*/
function init(config){
// Set the partner window
commLib.setPartnerWindow(parent);
// Start listening for incomming messages.
commLib.listen();
}
this.setOnunloadHandler=function(callback){
window.onunload=callback;
}
init();
};
/*
Copyright 2006-2009 OpenAjax Alliance
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.
*/
// XXX revert r231 - Revision 231 added support for having the client pass back
// both the initial URI and the current URI, which are different in the case
// or redirection. However, in order for this to work, the final client code
// must set smash._initialClientURI to the initial URI (the URI for the page
// that did the redirection). There isn't a clean way to do this with the
// current Hub 2.0 APIs, so I'm disabling this feature for now. Search the code
// for "XXX revert r231".
if ( typeof OpenAjax === "undefined" ) {
OpenAjax = { hub: {} };
}
(function(){
OpenAjax.hub.IframeFIMContainer = function( container, hub, clientID, params )
{
this._container = container;
this._hub = hub;
this._onSecurityAlert = params.Container.onSecurityAlert;
this._onConnect = params.Container.onConnect ? params.Container.onConnect : null;
this._onDisconnect = params.Container.onDisconnect ? params.Container.onDisconnect : null;
this._scope = params.Container.scope || window;
this._subs = {};
// XXX Need to make sure URI is absolute, or change the "clientURI!=componentURI"
// comparison in SEComm.initializationFinished (where 'clientURI' is always
// absolute, but 'componentURI' is based on params.IframeContainer.uri and
// may be relative, which makes the comparison fail)
this._clientURI = params.IframeContainer.uri;
smash.SEComm.tunnelURI = params.IframeContainer.tunnelURI;
smash._loadTimeout = params.IframeContainer.timeout || 15000;
if ( params.Container.log ) {
var scope = this._scope;
var logfunc = params.Container.log;
this._log = function( msg ) {
logfunc.call( scope, "IframeContainer::" + clientID + ": " + msg );
};
} else {
this._log = function() {};
}
// configurable goodbyeMessage: protects against malicious unloading of the mashup application
// if (params.goodbyeMessage != null) {
// smash._goodbyeMessage = params.goodbyeMessage;
// }
// configurable securityTokenLength
// if (params.securityTokenLength != null) {
// smash._securityTokenLength = params.securityTokenLength;
// smash._computeOtherTokenConstants();
// }
// create and configure the pseudo-random number generator, used to create
// security tokens
smash._createPRNG( this, params );
smash._ensureSingletonManager();
// the 'internal ID' is guaranteed to be unique within the page, not just
// the ManagedHub instance
this._internalID = smash._singletonManager.generateUniqueClientName( clientID );
}
OpenAjax.hub.IframeFIMContainer.prototype.getHub = function() {
return this._hub;
};
OpenAjax.hub.IframeFIMContainer.prototype.sendToClient = function( topic, data, subscriptionID )
{
smash._singletonManager.sendToClient( this._internalID, topic, data, [ subscriptionID ] );
}
OpenAjax.hub.IframeFIMContainer.prototype.remove = function()
{
/**
* Cleans up data-strucrures for communication with the given client. Needs to be called prior to unloading of the
* client to prevent false positives about 'frame phishing' attacks.
* smash.prepareForUnload(clientName: string)
*/
return smash._singletonManager.prepareForUnload( this._internalID );
}
OpenAjax.hub.IframeFIMContainer.prototype.isConnected = function()
{
return smash._singletonManager.isConnected( this._internalID );
}
OpenAjax.hub.IframeFIMContainer.prototype.getPartnerOrigin = function()
{
return smash._singletonManager.getPartnerOrigin( this._internalID );
}
OpenAjax.hub.IframeFIMContainer.prototype.getURI = function()
{
/**
* Prepares for loading of a client in a separate iframe. In addition to setting up internal data-structures,
* it updates the URI (potentially adding a fragment identifier and URI parameters).
* The updates are necessary to pass values needed to bootstrap communication.
*
* string smash.prepareForLoad({clientName: string, uri: string,
* [commErrorCallback:function(clientName:string, error:string)]})
* return value of null indicates failure, a non-null return value is the updated URI
*/
var that = this;
function errorCallback( clientID, error ) {
var alertType = null;
switch( error ) {
case smash.SecurityErrors.INVALID_TOKEN:
case smash.SecurityErrors.TOKEN_VERIFICATION_FAILED:
alertType = OpenAjax.hub.SecurityAlert.ForgedMsg;
break;
case smash.SecurityErrors.TUNNEL_UNLOAD:
alertType = OpenAjax.hub.SecurityAlert.FramePhish;
break;
case smash.SecurityErrors.COMPONENT_LOAD:
alertType = OpenAjax.hub.SecurityAlert.LoadTimeout;
break;
}
try {
that._onSecurityAlert.call( that._scope, that._container, alertType );
} catch( e ) {
OpenAjax.hub._debugger();
that._log( "caught error from onSecurityAlert callback to constructor: " + e.message );
}
}
var newURI = smash._singletonManager.prepareForLoad({ clientName: this._internalID,
uri: this._clientURI, commErrorCallback: errorCallback,
oaaContainer: this, log: this._log });
if ( newURI && OpenAjax.hub.enableDebug ) newURI += ":debug"; // REMOVE ON BUILD
return newURI;
}
//------------------------------------------------------------------------------
OpenAjax.hub.IframeFIMHubClient = function( client, params )
{
// XXX Since server redirection breaks hash communication (the server does
// not receive the fragment value, therefore the final URL does not contain
// this information), the initial message is transmitted as a URL param.
// The SMash code, though, expects messages after the hash. So we copy
// the initial message value into the fragment.
var initialMsg = new RegExp( "[\\?&]oahm=([^&#]*)" ).exec( window.location.search );
if ( ! initialMsg ) {
throw new Error( OpenAjax.hub.Error.WrongProtocol );
}
initialMsg = initialMsg[1];
// check communications protocol ID
var partnerProtocolID = initialMsg.split( ":", 1 );
if ( partnerProtocolID[0] != smash._protocolID ) {
throw new Error( OpenAjax.hub.Error.WrongProtocol );
}
// remove protocol ID from initialMsg, since decodeMessage() doesn't
// expect it
initialMsg = initialMsg.substring( partnerProtocolID[0].length + 1 );
// copy initial message into URL fragment
var url = window.location.href + "#" + initialMsg;
window.location.replace( url );
this._client = client;
this._onSecurityAlert = params.HubClient.onSecurityAlert;
this._scope = params.HubClient.scope || window;
// pull out client id from initial message
var re = new RegExp( "\\d{3}.{" + smash._securityTokenLength + "}.{" + smash._securityTokenLength + "}\\d{3}(.*)" );
var payload = re.exec( initialMsg )[1];
var parts = payload.split(":");
var internalID = decodeURIComponent( parts[0] );
this._id = internalID.substring( internalID.indexOf("_") + 1 );
if ( parts[2] && parts[2] == "debug" ) OpenAjax.hub.enableDebug = true; // REMOVE ON BUILD
if ( params.HubClient.log ) {
var id = this._id;
var scope = this._scope;
var logfunc = params.HubClient.log;
this._log = function( msg ) {
logfunc.call( scope, "IframeHubClient::" + id + ": " + msg );
};
} else {
this._log = function() {};
}
this._connected = false;
this._subs = {};
this._subIndex = 0;
// create and configure the pseudo-random number generator, used to create
// security tokens
smash._createPRNG( this, params );
// configurable initialClientURI: only for those clients which perform URI redirection
// at client load time
// XXX revert r231
// if (params.initialClientURI) {
// smash._initialClientURI = params.initialClientURI;
// }
}
/*** OpenAjax.hub.HubClient interface implementation ***/
OpenAjax.hub.IframeFIMHubClient.prototype.connect = function( onComplete, scope )
{
if ( smash._singletonClientHub == null ) {
// allow a null clientName since the SMash provider can find it in the fragment.
smash._singletonClientHub = new smash.SEHubClient( null, this._log );
// set to be notified of security errors
var that = this;
smash._singletonClientHub.setSecurityErrorCallback( function( errorcode ) {
if ( errorcode != smash.SecurityErrors.INVALID_TOKEN ) {
that._log( "unknown smash security error: " + errorcode );
}
try {
that._onSecurityAlert.call( that._scope, that._client, OpenAjax.hub.SecurityAlert.ForgedMsg );
} catch( e ) {
OpenAjax.hub._debugger();
that._log( "caught error from onSecurityAlert callback to constructor: " + e.message );
}
});
}
var that = this;
function cb( success, seHubClient ) {
if ( success ) {
that._connected = true;
}
if ( onComplete ) {
try {
onComplete.call( scope, that._client, success ); // XXX which error to return when success == false?
} catch( e ) {
OpenAjax.hub._debugger();
that._log( "caught error from onComplete callback to HubClient.connect(): " + e.message );
}
}
}
smash._singletonClientHub.connect( cb );
}
OpenAjax.hub.IframeFIMHubClient.prototype.disconnect = function( onComplete, scope )
{
this._connected = false;
var that = this;
function cb( success, seHubClient ) {
// XXX what happens if success == false
if ( onComplete ) {
try {
onComplete.call( scope, that._client, success ); // XXX which error to return when success == false?
} catch( e ) {
OpenAjax.hub._debugger();
that._log( "caught error from onComplete callback to HubClient.disconnect(): " + e.message );
}
}
}
smash._singletonClientHub.disconnect( cb );
}
OpenAjax.hub.IframeFIMHubClient.prototype.getPartnerOrigin = function()
{
return smash._singletonClientHub ? smash._singletonClientHub.getPartnerOrigin() : null;
}
OpenAjax.hub.IframeFIMHubClient.prototype.getClientID = function()
{
return this._id;
}
/*** OpenAjax.hub.Hub interface implementation ***/
OpenAjax.hub.IframeFIMHubClient.prototype.subscribe = function( topic, onData, scope, onComplete, subscriberData )
{
var subID = "" + this._subIndex++;
var that = this;
var completeCallback = ! onComplete ? null :
function ( success, subHandle, error ) {
try {
onComplete.call( scope, subID, success, error );
} catch( e ) {
OpenAjax.hub._debugger();
that._log( "caught error from onComplete callback to HubClient.subscribe(): " + e.message );
}
};
function dataCallback( subHandle, topic, data ) {
try {
onData.call( scope, topic, data, subscriberData );
} catch( e ) {
OpenAjax.hub._debugger();
that._log( "caught error from onData callback to HubClient.subscribe(): " + e.message );
}
}
this._subs[ subID ] = smash._singletonClientHub.subscribe( topic, completeCallback, dataCallback, scope, subscriberData );
return subID;
}
OpenAjax.hub.IframeFIMHubClient.prototype.publish = function( topic, data )
{
smash._singletonClientHub.publish( topic, data );
}
OpenAjax.hub.IframeFIMHubClient.prototype.unsubscribe = function( subID, onComplete, scope )
{
if ( ! this._subs[ subID ] ) {
throw new Error( OpenAjax.hub.Error.NoSubscription );
}
var that = this;
function cb( success, subHandle ) {
delete that._subs[ subID ];
if ( onComplete ) {
try {
onComplete.call( scope, subID, success/*, error*/ );
} catch( e ) {
OpenAjax.hub._debugger();
that._log( "caught error from onComplete callback to HubClient.unsubscribe(): " + e.message );
}
}
};
this._subs[ subID ].unsubscribe( cb );
}
OpenAjax.hub.IframeFIMHubClient.prototype.isConnected = function()
{
return this._connected;
}
OpenAjax.hub.IframeFIMHubClient.prototype.getScope = function()
{
return this._scope;
}
OpenAjax.hub.IframeFIMHubClient.prototype.getSubscriberData = function( subID )
{
var sub = this._subs[ subID ];
if ( sub ) {
return sub.getSubscriberData();
}
throw new Error( OpenAjax.hub.Error.NoSubscription );
}
OpenAjax.hub.IframeFIMHubClient.prototype.getSubscriberScope = function( subID )
{
var sub = this._subs[ subID ];
if ( sub ) {
return sub.getSubscriberScope();
}
throw new Error( OpenAjax.hub.Error.NoSubscription );
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
if ( typeof OpenAjax._smash == 'undefined' ) { OpenAjax._smash = {}; }
var smash = OpenAjax._smash;
// Ideally, should use a closure for private (and public) data and functions,
// but this was easier for the initial SMash refactoring.
smash._singletonManager = undefined; // the singleton that implements all the manager-side SPI
smash._singletonClientHub = undefined; // the singleton that implements all the client-side SPI
smash._protocolID = "openajax-2.0";
//smash._goodbyeMessage = undefined; // The goodbye message sent when unloading the mashup page. Protects against malicious unloading of the mashup application. If undefined, no message is displayed
//smash._loadTimeout = 20000; // The default timeout time during loading of a component. The lower the value the higher the security against frame-phishing but also the higer the chance of false detections.
// XXX revert r231
//smash._initialClientURI = undefined; // For use by the smash provider loaded by a client. Should only be changed from the default value if the client does URI redirection at load time. Otherwise, we will assume that the current URI was also the initial URI
// --- security token stuff ---
// configurable pseudo random number generator (prng) to use for generating the security token.
// If not set, we use Math.random.
// If set, the provided random number generator must support a function nextRandomB64Str(strlength:integer)
// that returns a string of length strlength, where each character is a "modified Base64 for URL" character.
// This includes A-Z, a-z, and 0-9 for the first 62 digits, like standard Base64 encoding, but
// no padding '='. And the '+', '/' characters of standard Base64 are replaced by '-', '_'.
smash._prng = undefined;
smash._securityTokenLength = 6; // configurable security token length. If default value is not used, both manager and clients have to change it to the same value.
smash._securityTokenOverhead = null; // the number of characters in a serialized message consumed by the security tokens
smash._computeOtherTokenConstants = function() {
smash._securityTokenOverhead = 2*smash._securityTokenLength;
smash._multiplier = Math.pow(10, smash._securityTokenLength-1);
}
smash._computeOtherTokenConstants();
smash._createPRNG = function( container, params )
{
if ( ! smash._prng ) {
// create pseudo-random number generator with a default seed
var seed = new Date().getTime() + Math.random() + document.cookie;
smash._prng = smash.crypto.newPRNG( seed );
}
var p = params.IframeContainer || params.IframeHubClient;
if ( p && p.seed ) {
try {
var extraSeed = p.seed.call( container._scope );
smash._prng.addSeed( extraSeed );
} catch( e ) {
OpenAjax.hub._debugger();
container._log( "caught error from 'seed' callback: " + e.message );
}
}
}
/**
* Randomly generates the security token which will be used to ensure message integrity.
*/
smash._keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
smash._generateSecurityToken = function() {
var r;
if (smash._prng)
r = smash._prng.nextRandomB64Str(smash._securityTokenLength);
else {
var r1 = Math.random(); // value in (0,1)
r = "";
// assuming one Math.random() value has enough bits for smash._securityTokenLenght
for (var i=0; i<smash._securityTokenLength; i++) {
var r2 = r1 * 64; // get the most significant base-64 value
var c = Math.floor(r2);
r1 = (r2 - c); // the remaining fractional value
r = r + smash._keyStr.charAt(c);
}
}
return r;
}
//------------------------- manager-side implementation ------------------------
/**
* lazy creation of the manager-side singleton
*/
smash._ensureSingletonManager = function() {
if (smash._singletonManager == null)
smash._singletonManager = new smash.SEHub();
}
/**
* Constructor.
* The name SEHub is legacy. The provider on the manager-side does not implement any of the hub functionality
* other than communication.
*/
smash.SEHub = function(){
// This is used to make the object available to the private methods. This is a workaround for an error in the ECMAScript Language Specification which causes this to be set incorrectly for inner functions. See http://www.crockford.com/javascript/private.html
var that=this;
// associative array indexed by componentId. Each element is a ComponentInfo object.
// Component is synonymous with client. componentId is the same as clientName
this.componentInfo = [];
/**
* Constructor for ComponentInfo objects
*/
function ComponentInfo(uri, eCallback) {
this.uri = uri;
// this.state = smash.SEHubConstants.START;
this.connected = false;
this.errorCallback = eCallback;
}
// create an ID that is unique within the page
this.generateUniqueClientName = function( clientName ) {
do {
clientName = ((0x7fff * Math.random()) | 0).toString(16) + "_" + clientName;
} while ( that.componentInfo[ clientName ] );
return clientName;
}
// securityListener function registered for each component's security events
function securityListener(errorType, clientName) {
// var errorString = that.getSecurityErrorString(errorType); // get the error as a string
var ci = that.componentInfo[clientName];
if ( ci != null ) {
var errorCallback = ci.errorCallback; // the errorCallback registered by the application
if (errorCallback != null) { // if one was registered
// errorCallback(clientName, errorString);
errorCallback(clientName, errorType);
}
}
}
/**
* string prepareForLoad({clientName: string, uri: string,
* [commErrorCallback:function(clientName:string, error:string)]})
* return value of null indicates failure, a non-null return value is the updated URI
*/
this.prepareForLoad = function(params) {
var clientName = params.clientName; // componentId and clientName are the same thing in this code
var componentURI = params.uri;
if ((clientName == null) || (componentURI == null))
return null;
if (that.componentInfo[clientName] != null) {
return null;
}
that.componentInfo[clientName] = new ComponentInfo(componentURI, params.commErrorCallback);
that.componentInfo[clientName].seComm = new smash.SEComm(); //The SEComm library used for this component
that.componentInfo[clientName].seComm.setSecurityListener(securityListener);
that.componentInfo[clientName].oaaContainer = params.oaaContainer;
return that.componentInfo[clientName].seComm.prepareForLoad(clientName, componentURI, that, smash._loadTimeout, params.log);
}
/**
* boolean prepareForUnload(clientName: string)
*/
this.prepareForUnload = function(clientName) {
if (!that.componentInfo[clientName]) {
// component does not exist.
return true;
}
// // change state. pretty useless, since going to delete anyway
// that.componentInfo[clientName].state = smash.SEHubConstants.UNLOADED;
that._disconnect( clientName );
that.componentInfo[clientName].seComm.prepareForUnload();
// remove the relevant objects
delete that.componentInfo[clientName];
return true;
}
/**
* boolean isConnected(clientName:string)
*/
this.isConnected = function(clientName) {
// if ( that.componentInfo[clientName] && that.componentInfo[clientName].state == smash.SEHubConstants.LOADED )
if ( that.componentInfo[clientName] && that.componentInfo[clientName].connected ) {
return true;
}
return false;
}
/**
* sendToClient(clientName:string, topic: string, data:JSON|string, matchingSubs:array of string)
*/
this.sendToClient = function(clientName, topic, data, matchingSubs) {
// send to the component
if (that.isConnected(clientName)) {
var comms = that.componentInfo[clientName].seComm;
if (comms) {
comms.distribute(topic, matchingSubs, data);
}
}
}
/** Callback when component loaded */
this.componentLoaded = function(clientName, partnerURL) {
if (that.componentInfo[clientName]) {
// that.componentInfo[clientName].state = smash.SEHubConstants.LOADED;
that.componentInfo[clientName].connected = true;
that.componentInfo[clientName].partnerOrigin = new RegExp( "^([a-zA-Z]+://[^:/?#]+).*" ).exec( partnerURL )[1];
var oaaContainer = that.componentInfo[ clientName ].oaaContainer;
oaaContainer._container.getIframe().style.visibility = "visible";
if ( oaaContainer._onConnect ) {
try {
oaaContainer._onConnect.call( oaaContainer._scope, oaaContainer._container );
} catch( e ) {
OpenAjax.hub._debugger();
oaaContainer._log( "caught error from onConnect callback to constructor: " + e.message );
}
}
}
}
/**
* A message received from a component
* @param componentId The component that sent the message
* @param topic
* @param message The payload of the message (JSON|string)
*/
this.publishInternal = function(componentId, topic, message) {
if (that.componentInfo[componentId]) {
// component exists
var oaaContainer = that.componentInfo[ componentId ].oaaContainer;
oaaContainer._hub.publishForClient( oaaContainer._container, topic, message );
}
}
/**
* A subscribe message received from a component
* @param componentId The component that sent the message
* @param subId The subscription id
* @param topic
*/
this.subscribeInternal = function(componentId, subId, topic) {
var oaaContainer = that.componentInfo[ componentId ].oaaContainer;
oaaContainer._subs[ subId ] = oaaContainer._hub.subscribeForClient( oaaContainer._container, topic, subId );
}
/**
* A unsubscribe message received from a component
* @param componentId The component that sent the message
* @param subId
* @returns true if unsubscribe was accepted else false
*/
this.unsubscribeInternal = function(componentId, subId) {
try {
var oaaContainer = that.componentInfo[ componentId ].oaaContainer;
var handle = oaaContainer._subs[ subId ];
oaaContainer._hub.unsubscribeForClient( oaaContainer._container, handle );
return true;
} catch( e ) {}
return false;
}
this.disconnect = function( componentId )
{
that._disconnect( componentId );
var oaaContainer = that.componentInfo[ componentId ].oaaContainer;
if ( oaaContainer._onDisconnect ) {
try {
oaaContainer._onDisconnect.call( oaaContainer._scope, oaaContainer._container );
} catch( e ) {
OpenAjax.hub._debugger();
oaaContainer._log( "caught error from onDisconnect callback to constructor: " + e.message );
}
}
}
this._disconnect = function( componentId )
{
if ( that.componentInfo[ componentId ].connected ) {
that.componentInfo[ componentId ].connected = false;
// hide component iframe
var oaaContainer = that.componentInfo[ componentId ].oaaContainer;
oaaContainer._container.getIframe().style.visibility = "hidden";
// unsubscribe from all subs
for ( var sub in oaaContainer._subs ) {
oaaContainer._hub.unsubscribeForClient( oaaContainer._container, oaaContainer._subs[ sub ] );
}
oaaContainer._subs = {};
}
}
this.getPartnerOrigin = function( componentId )
{
if ( that.componentInfo[ componentId ]. connected ) {
return that.componentInfo[ componentId ].partnerOrigin;
}
return null;
}
/**
* Converts a security error code into a readable error message.
* @param error The error code.
*/
// this.getSecurityErrorString = function(error) {
// switch (error) {
// case smash.SecurityErrors.INVALID_TOKEN: return smash.SecurityErrors.INVALID_TOKEN_MSG;
// case smash.SecurityErrors.TOKEN_VERIFICATION_FAILED: return smash.SecurityErrors.TOKEN_VERIFICATION_FAILED_MSG;
// case smash.SecurityErrors.TUNNEL_UNLOAD: return smash.SecurityErrors.TUNNEL_UNLOAD_MSG;
// case smash.SecurityErrors.COMPONENT_LOAD: return smash.SecurityErrors.COMPONENT_LOAD_MSG;
// default: return "UNKNOWN";
// }
// }
/**
* Sets the unload function which shows the goodbye message.
*/
// window.onunload=function(){
// if (smash._goodbyeMessage != undefined)
// alert(smash._goodbyeMessage);
// }
}
//---------- client-side implementation ----------------------------------------
/**
* SEHubClient implementation linking the SECommClient together with the component side logic.
*/
smash.SEHubClient = function( clientName, logfunc )
{
//-------- interface implemented by connHandle in Hub 1.1. We use the SEHub instance itself
//-------- as the connHandle object for the "manager".
this.equals = function(anotherConn) { return that === anotherConn; }
this.isConnected = function() { return connected; }
this.getClientName = function() { return clientName; }
this.connect = function( callback ) {
connectCallback = function( success ) {
if ( success ) {
connected = true;
}
callback( success, that );
};
seCommClient.connect( connectCallback );
}
this.disconnect = function(callback) {
disconnectCallback = function( success ) {
if ( success ) {
connected = false;
subHandles = []; // delete all existing subscriptions
}
callback( success, that );
};
seCommClient.disconnect();
return;
}
/**
* connHandle.subscribe(topic:string, callback:function, eventCallback:function)
* returns a subHandle object, or null if it fails immediately.
*/
this.subscribe = function(topic, callback, eventCallback, scope, subscriberData) {
// keep track of the callback so that the incomming message can be distributed correctly
var subId = (subCount + ''); // assign the subscription id - making it a string
subCount++;
subHandles[subId] = new SubHandle(subId, topic, callback, eventCallback, that, scope, subscriberData);
seCommClient.subscribe(subId, topic);
return subHandles[subId];
}
/**
* connHandle.publish(topic:string, data:JSON|string)
*/
this.publish = function(topic, data) {
seCommClient.publish(topic,data);
return true;
}
function SubHandle(subId, topic, callback, eventCallback, sehubClient, scope, subscriberData) {
var _isSubscribed = false;
var _data = subscriberData;
var _scope = scope;
var that = this;
this.getTopic = function() {
return topic;
}
this.getConnHandle = function() {
return sehubClient;
}
this.equals = function(anotherSubHandle) {
if ((anotherSubHandle._getSubId != null) && (typeof anotherSubHandle._getSubId == "function")
&& (anotherSubHandle.getConnHandle != null) && (typeof anotherSubHandle.getConnHandle == "function")) {
if ((subId === anotherSubHandle._getSubId()) && (sehubClient === anotherSubHandle.getConnHandle()))
return true;
}
return false;
}
this.isSubscribed = function() {
return _isSubscribed;
}
this.unsubscribe = function(callback) {
return sehubClient._unsubscribe(that, callback);
}
this.getSubscriberData = function() {
return _data;
}
this.getSubscriberScope = function() {
return _scope;
}
this._getSubId = function() {
return subId;
}
this._setIsSubscribed = function(value) {
_isSubscribed = value;
}
this._getCallback = function() {
return callback;
}
this._getEventCallback = function() {
return eventCallback;
}
}
this.getPartnerOrigin = function() {
if ( connected && seCommClient != null ) {
var ptu = seCommClient.getParsedTunnelUrl();
if ( ptu != null ) {
return ptu.scheme + "://" + ptu.host;
}
}
return null;
}
//-------- end of interface implemented by connHandle in Hub 1.1.
//------- addition public interfaces not part of Hub 1.1 -----
/**
* Set a callback to find out about security errors.
* Not part of the OpenAjax Hub 1.1 standard
*/
this.setSecurityErrorCallback = function(errorcallback) {
securityErrorCallback = errorcallback;
}
// this.getManagerDomain = function() {
// if (seCommClient != null) {
// var ptu = seCommClient.getParsedTunnelUrl();
// if (ptu != null) return ptu.host;
// }
// return null;
// }
//------- private stuff ------
/**
* _unsubscribe(subHandle:object, callback:function)
* returns a subHandle object, or null if it fails immediately.
*/
this._unsubscribe = function(subHandle, callback) {
var subId = subHandle._getSubId();
if ( ! subHandles[ subId ] ) {
throw new Error( OpenAjax.hub.Error.NoSubscription );
}
subHandles[subId] = undefined;
seCommClient.unsubscribe(subId);
// no async callback as no confirmation message from manager
if (callback != null) {
callback(true, subHandle); // function(success:boolean, subHandle:object).
}
return subHandle;
}
var securityErrorCallback = undefined; // securityErrorCallback registered by the application in this component/frame
// subscriptions: each subscription is assigned an integer id that is unique to this client
var subCount = 0;
// mapping the subscription ids to the SubHandles
var subHandles=[];
// SECommClient serving the communication between the SEHub and the SEHub client
var seCommClient=new smash.SECommClient( clientName, logfunc );
// var state = smash.SEHubConstants.LOADED; // initialize my state to LOADED.
var connected = false;
// This is used to make the object available to the private methods. This is a workaround for an error in the ECMAScript Language Specification which causes this to be set incorrectly for inner functions. See http://www.crockford.com/javascript/private.html
var that=this;
var connectCallback = null;
var disconnectCallback = null;
/**
* Processes messages received by the SECommClient
* @param message The actual message.
*/
function handleIncomingMessage(message)
{
if ( ! connected && message.type != smash.SECommMessage.CONNECT_ACK ) {
return;
}
switch (message.type) {
case smash.SECommMessage.DISTRIBUTE:
if ((message.additionalHeader != null) && (message.additionalHeader.s != null)) {
var subs = message.additionalHeader.s;
for (var i=0; i < subs.length; i++) {
var subId = subs[i];
if ((subId != null) && (subHandles[subId] != null)) {
var eventCallback = subHandles[subId]._getEventCallback();
if (eventCallback != null)
eventCallback(subHandles[subId], message.topic, message.payload);
}
}
}
break;
case smash.SECommMessage.SUBSCRIBE_ACK:
if (message.additionalHeader != null) {
var subId = message.additionalHeader.subId;
var isOk = message.additionalHeader.isOk;
var err = message.additionalHeader.err;
if ((subId != null) && (isOk != null)) {
if (subHandles[subId] != null) {
var callback = subHandles[subId]._getCallback();
if (isOk) {
subHandles[subId]._setIsSubscribed(true);
if (callback != null)
callback(true, subHandles[subId]);
}
else {
if (callback != null)
callback(false, subHandles[subId], err);
subHandles[subId] = undefined; // unsubscribe
}
}
}
}
// else ignore the message
break;
case smash.SECommMessage.CONNECT_ACK:
connectCallback( true );
break;
case smash.SECommMessage.DISCONNECT_ACK:
disconnectCallback( true );
break;
}
}
function securityListenerCallback(errorcode) {
// var errorString = getSecurityErrorString(errorcode);
if (securityErrorCallback != null) {
// securityErrorCallback(errorString);
securityErrorCallback(errorcode);
}
else {
throw new Error(errorString);
}
}
// function getSecurityErrorString(error) {
// switch (error) {
// case smash.SecurityErrors.INVALID_TOKEN: return smash.SecurityErrors.INVALID_TOKEN_MSG;
// default: return "UNKNOWN";
// }
// }
// Override the SECommClient's received method with our own implementation
seCommClient.handleIncomingMessage = handleIncomingMessage;
seCommClient.setSecurityListener( securityListenerCallback );
}
//-----------------------------------------------------------------------------------------------
//smash.SEHubConstants = {
//
// // Constants representing states of a component.
// // Component State Machine: START -> LOADED -> UNLOADED
//
// START: 0,
// LOADED: 1,
// UNLOADED: 2
//
//};
//-----------------------------------------------------------------------------------------------
/**
* Constants representing the different types of attacks that can be detected and prevented by the library.
*/
smash.SecurityErrors = {
// This error occurs when the CommLib detects a message with a different security token than the one with wich it was initialized.
INVALID_TOKEN: 0,
// INVALID_TOKEN_MSG: "The sender of the received message could not be verified because the received security token was not correct.",
// This error occurs when the SEComm receives a different security token than the one that was sent by the SEComm during the loading of the component.
TOKEN_VERIFICATION_FAILED: 1,
// TOKEN_VERIFICATION_FAILED_MSG: "The security token could not be verified. A different security token than the one that was sent during the loading of the component was received after loading.",
// Phishing error
TUNNEL_UNLOAD: 2,
// TUNNEL_UNLOAD_MSG: "The tunnel was unloaded without the component being unloaded by the mashup application. Frame-phishing may have occured after the component was loaded successfully.",
// Phishing error before successfull load
COMPONENT_LOAD: 3
// COMPONENT_LOAD_MSG: "A timeout occured before the communication channel between the component and the mashup application was set up correctly. Frame-phishing may have occured during the loading of the component."
};
//-----------------------------------------------------------------------------------------------
/**
* The object implementing the message serializer and deserializer for use in SEComm.
* The topic and payload are typically under application control and may contain URI reserved characters.
* These will be percent-encoded and decoded, and the application has to deal with the composition issues
* if it is passing in data or topics that are already percent-encoded.
*/
smash.SECommMessage = function(){
// The type of the message. A string
this.type=null;
// The topic of the message. A string
this.topic=null;
// The remaining header information. A JSON object
this.additionalHeader=null;
// The payload of the message. A string
this.payload=null;
// The name used in the name value pair transmission. one character for efficiency. only use a letter or number
var typeName="y";
var topicName="t";
var additionalHeaderName = "h"; // other header information that is not handled by typeName and topicName
var payloadName="p";
/**
* Serializes the message into a string which can be transmitted over a communication channel.
* URI-encodes the topic and payload and uses "=", "&" as separators. The communication channel
* must not perform any URI-encoding as "=", "&" are not reserved for fragments.
* If using something other than fragment messaging at the communication channel, the serialization
* may need to change.
* @returns The serialized message.
*/
this.serialize=function(){
var returnValue = typeName + "=" + this.type;
if (this.topic != null) {
var topicString = encodeURIComponent(this.topic);
var topicSer = "&" + topicName + "=" + topicString;
returnValue += topicSer;
}
if (this.additionalHeader != null) {
var headerString = encodeURIComponent(JSON.stringify(this.additionalHeader));
var headerSer = "&" + additionalHeaderName + "=" + headerString;
returnValue += headerSer;
}
if (this.payload != null) {
var payloadString = encodeURIComponent(this.payload);
var payloadSer = "&" + payloadName + "=" + payloadString;
returnValue += payloadSer;
}
return returnValue;
}
/**
* Deserializes a serialized message and initializes the objects parameters.
*/
this.deserialize=function(serializedMessage){
var messageParts = serializedMessage.split("&");
for(var i = 0; i < messageParts.length; i++){
var nameValue = messageParts[i].split("=");
switch(nameValue[0]){
case typeName:
this.type=nameValue[1];
break;
case topicName:
this.topic=decodeURIComponent(nameValue[1]);
break;
case additionalHeaderName:
var headerString = decodeURIComponent(nameValue[1]);
this.additionalHeader = JSON.parse(headerString);
break;
case payloadName:
this.payload=decodeURIComponent(nameValue[1]);
break;
}
}
}
}
// only use letters or numbers as characters
// CONNECT message
smash.SECommMessage.CONNECT="con";
smash.SECommMessage.CONNECT_ACK="cac";
// DISCONNECT message
smash.SECommMessage.DISCONNECT="xcon";
smash.SECommMessage.DISCONNECT_ACK="xac";
// PUBLISH message: additionalHeader is {f:"S"} or {f:"J"} representing that the payload is a string or JSON,
// topic and payload are topic, payload of message
smash.SECommMessage.PUBLISH="pub";
// DISTRIBUTE message: additionalHeader is {f: string, s:[string, ...]} where f is defined as in the PUBLISH message,
// and s representing subIds that should receive this message; topic and payload are as in PUBLISH message
smash.SECommMessage.DISTRIBUTE="dis";
// SUSCRIBE message: additionalHeader is {subId: string}, payload==null, topic is subscription topic
smash.SECommMessage.SUBSCRIBE="sub";
// UNSUBSCRIBE message: additionalHeader is {subId: string}, topic==null, payload==null
smash.SECommMessage.UNSUBSCRIBE="uns";
// SUBCRIBE_ACK message: additionalHeader is {subId: string, isOk: boolean, err: string}, topic==null, payload == null
smash.SECommMessage.SUBSCRIBE_ACK="sac";
smash.SECommMessage.ERROR="err"; // TBD
//-----------------------------------------------------------------------------------------------
/**
* Definitions of exceptions used by SECom
*/
smash.SECommErrors = {};
smash.SECommErrors.tunnelNotSetError = new Error ("The tunnel URI was not set. Please set the tunnel URI.");
//smash.SECommErrors.componentNotFoundError = new Error ("The component could not be identified. Please declare the component correctly.");
//smash.SECommErrors.securityTokenNotVerifiedError = new Error (smash.SecurityErrors.TOKEN_VERIFICATION_FAILED_MSG);
//smash.SECommErrors.tunnelUnloadError = new Error (smash.SecurityErrors.TUNNEL_UNLOAD_MSG);
//smash.SECommErrors.componentLoadError = new Error (smash.SecurityErrors.COMPONENT_LOAD_MSG);
/**
* Links the SEHub and the SEHubClient together over the communication implemented by CommLib bridge
*
* TODO: Check if the component loading allows valid HTML.
* TODO: Propagate the style of the enclosing tag into the iFrame
* TODO: Check if there is a better way than polling to see if the tunnel's commLib has been registered
*/
smash.SEComm = function(){
// The timer used to delay the phishing message. This makes sure that a page navigation does not cause phishing errors.
// Setting it to 1 ms is enough for it not to be triggered on regular page navigations.
var unloadTimer=1;
// Variable storing the identifier for the setInterval if processing a registrationTimer
var registrationTimerProcess=null;
var loadTimeout = 0;
var reconnectTimerProcess = null;
// The URI of the component being manages by this SEComm.
var componentURI=null;
// The commLib of the tunnel
var commLib=null;
// Variable storing the identifier to clear when the setInterval is called
var commLibPoll=null;
// The HTML id of the component for which this is a SEComm
var componentID=null;
// A queue for outgoing messages. This queue is used when new send requests are done while we are still sending or receiving a message.
var queueOut=[];
// Variable storing the identifier for the setInterval if processing an output queue
var queueOutProcess=null;
// Variable storing a reference to the SEHub which is managing this SEComm
var seHUB=null;
// The iframe in which the component is loaded
var myIframe = null;
// The security token used for this component
var securityTokenParent=null;
// Variable storing the callback to the security listener function
var securityListener=null;
// This is used to make the object available to the private methods. This is a workaround for an error in the ECMAScript Language Specification which causes this to be set incorrectly for inner functions. See http://www.crockford.com/javascript/private.html
var that=this;
// keeps track of the initialization
var initialized=false;
// logging function
var logfunc = null;
/**
* Sets the callback for security errors.
*
* @param The callback for security errors.
*/
this.setSecurityListener=function(callback){
securityListener=callback;
}
function insertURLParams( uri, params ) {
var parts = uri.split( "?" );
if ( parts.length > 1 ) {
return parts[0] + "?" + params + "&" + parts[1];
}
parts = uri.split( "#" );
if ( parts.length > 1 ) {
return parts[0] + "?" + params + parts[1];
}
return uri + "?" + params;
}
/**
* Prepares for loading a component into an iframe.
* @returns The modified URI
*/
this.prepareForLoad=function(componentId, frameURI, seHub, loadtimeout, logFunc)
{
logfunc = logFunc;
this.log( "Parent connecting to : " + componentId );
// Store the SEHub
seHUB=seHub;
// Store the component Id
componentID=componentId;
loadTimeout = loadtimeout;
// Check if the tunnel is set
if (smash.SEComm.tunnelURI==null)throw smash.SECommErrors.tunnelNotSetError;
// modify the URI
securityTokenParent=smash._generateSecurityToken();
// include the token twice since the child token value does not matter yet
// XXX revert r231
// componentURI = insertURLParams( frameURI, "id=" + encodeURIComponent(componentId) );
// var modifiedURIWithFragment = componentURI + "#100" + securityTokenParent + securityTokenParent + "000" + encodeURIComponent(componentId) + ":" + encodeURIComponent(smash.SEComm.tunnelURI);
// Since a server redirect does not take into account the fragment value
// (it is not transmitted by the browser to the server), the initial
// message must be sent as a URL param.
componentURI = insertURLParams( frameURI, "oahm=" + smash._protocolID + ":100" + securityTokenParent + securityTokenParent + "000" + encodeURIComponent(componentId) + ":" + encodeURIComponent(smash.SEComm.tunnelURI) );
// Make the instance available for the tunnel.
smash.SEComm.instances[componentId]=that;
// Set a timer which detects if the component loaded successfully
// We are using an interval not to lose our evaluation context.
registrationTimerProcess=setInterval(pollForIncomingCommLibTimeout,loadTimeout);
return componentURI;
}
function pollForIncomingCommLibTimeout(){
clearInterval(registrationTimerProcess);
registrationTimerProcess = null;
//No CommLib has been registered.
if ( ! commLib ) {
that.handleSecurityError( smash.SecurityErrors.COMPONENT_LOAD );
}
}
function reconnectTimeout() {
clearInterval( reconnectTimerProcess );
that.handleSecurityError( smash.SecurityErrors.COMPONENT_LOAD );
}
/**
* Gets the scope. Should only be used by the tunnel during INIT.
* @returns scope (object) the scope in which the callback needs to be called.
**/
this.getScope=function(){
return this;
}
/**
* Gets the callback. Should only be used by the tunnel during INIT.
* @param c (string) the name of the callback method
**/
this.getCallback=function(){
return "messageReceived";
}
/**
* Called when the initialisaiton of the library is done and processes all messages in the queue
*/
this.initializationFinished=function(tunnelCommLib, token, currentClientURI, initialClientURI, tunnelWindow)
{
this.log( "Tunnel commLib initialization finished. Processing outgoing queue. Security token: " + token );
// XXX revert r231
// // verify the security token and currentClientURI
// if ((securityTokenParent!=token) || (initialClientURI!=componentURI)) {
// verify the security token
if (securityTokenParent!=token) {
that.handleSecurityError(smash.SecurityErrors.TOKEN_VERIFICATION_FAILED);
return false;
}
else {
commLib=tunnelCommLib;
initialized=true;
this.log( "Token verified." );
// register the onunload handler
tunnelWindow.onunload=tunnelUnloadHandler;
// switch the state to loaded in the seHUB.
seHUB.componentLoaded(componentID, currentClientURI);
// process the current outgoing queue.
while (queueOut.length>0)commLib.send(queueOut.shift());
return true;
}
}
this.prepareForUnload = function() {
// stop all timers
if (registrationTimerProcess != null) {
clearInterval(registrationTimerProcess);
registrationTimerProcess = null;
}
}
function securityListenerClosure(error, componentId) {
return function() {
securityListener(error, componentId);
}
}
this.handleSecurityError = function( error ) {
// if we have a timeout error, then overwrite initializationFinished()
// to return false by default, in order to prevent client connection
if ( error == smash.SecurityErrors.COMPONENT_LOAD ) {
this.initializationFinished = function() {
return false;
}
}
if (securityListener==null){
throw new Error (error);
}
else{
securityListener(error,componentID);
}
return;
}
/**
*
*/
function tunnelUnloadHandler(){
if (securityListener==null){
setTimeout("throw tunnelUnloadError;", unloadTimer);
}
else{
setTimeout(securityListenerClosure(smash.SecurityErrors.TUNNEL_UNLOAD, componentID), unloadTimer);
}
}
/**
* Function processing the incomming data from commLib
*
* @param message The message containing the incomming data
*/
this.messageReceived=function (message){
var msg=new smash.SECommMessage();
msg.deserialize(message);
switch(msg.type){
case smash.SECommMessage.PUBLISH:
if (msg.additionalHeader != null) {
var payload = msg.payload;
if (msg.additionalHeader.f == "J")
payload = JSON.parse(msg.payload);
seHUB.publishInternal(componentID, msg.topic, payload);
} // else no additionalHeader defining the payload format. hence ignore the message
break;
case smash.SECommMessage.SUBSCRIBE:
if (msg.additionalHeader != null) {
var isOk = true;
var errMsg = "";
try {
seHUB.subscribeInternal(componentID, msg.additionalHeader.subId, msg.topic);
} catch( e ) {
isOk = false;
errMsg = e.message;
}
var msgack = new smash.SECommMessage();
msgack.type = smash.SECommMessage.SUBSCRIBE_ACK;
msgack.additionalHeader={subId: msg.additionalHeader.subId, isOk: isOk, err: errMsg};
send(msgack.serialize());
}
break;
case smash.SECommMessage.UNSUBSCRIBE:
if (msg.additionalHeader != null)
seHUB.unsubscribeInternal(componentID, msg.additionalHeader.subId);
break;
case smash.SECommMessage.CONNECT:
clearInterval( reconnectTimerProcess );
// switch the state to loaded in the seHUB.
seHUB.componentLoaded( componentID, msg.payload );
// send acknowledgement
var msg = new smash.SECommMessage();
msg.type = smash.SECommMessage.CONNECT_ACK;
send( msg.serialize() );
break;
case smash.SECommMessage.DISCONNECT:
seHUB.disconnect( componentID );
// Set a timer which detects if the component reloaded
// We are using an interval not to lose our evaluation context.
reconnectTimerProcess = setInterval( reconnectTimeout, loadTimeout );
// send acknowledgement
var msg = new smash.SECommMessage();
msg.type = smash.SECommMessage.DISCONNECT_ACK;
send( msg.serialize() );
break;
}
}
/**
* Sends a published message to the partner component
*/
this.distribute=function(topic, matchingSubs, payload){
var msg=new smash.SECommMessage();
msg.type=smash.SECommMessage.DISTRIBUTE;
msg.topic=topic;
msg.additionalHeader = {s: matchingSubs};
if ((typeof payload) == "string") {
msg.additionalHeader.f = "S";
msg.payload=payload;
}
else {
msg.additionalHeader.f = "J";
msg.payload = JSON.stringify(payload);
}
send(msg.serialize());
}
function send(message) {
// Queue the message if sending or if there is no communication partner yet
if (initialized==false){
queueOut.push(message);
}
else{
commLib.send(message);
}
}
this.log = function( msg )
{
logfunc( msg );
}
}
// Static array which contains the list of the currently loaded instances. The array is indexed by the url of the child component.
smash.SEComm.instances=[];
//-----------------------------------------------------------------------------------------------
/**
* SEHubClient implementation linking the SEComm together with the component side logic.
*/
smash.SECommClient = function( clientName, logfunc )
{
// Storing the CommLib used for communicating
var controllers=[];
controllers["child"]=this;
var commLib=new smash.CommLib(true, controllers, clientName);
// This is used to make the object available to the private methods. This is a workaround for an error in the ECMAScript Language Specification which causes this to be set incorrectly for inner functions. See http://www.crockford.com/javascript/private.html
var that=this;
// A queue for outgoing messages. This queue is used when new send requests are done while we are still sending or receiving a message.
var queueOut=[];
// keeps track of the initialization
var initialized=false;
var securityListener=null;
var jsonPayloadHeader = {f: "J"};
var stringPayloadHeader = {f: "S"};
var parsedTunnelUrl = null;
/**
* Publishes a message to a certain topic
* @param topic string
* @param data JSON|string
*/
this.publish=function(topic, data){
var msg=new smash.SECommMessage();
msg.type=smash.SECommMessage.PUBLISH;
msg.topic=topic;
if ((typeof data) == "string") {
msg.additionalHeader = stringPayloadHeader;
msg.payload=data;
}
else {
msg.additionalHeader = jsonPayloadHeader;
msg.payload = JSON.stringify(data);
}
send(msg.serialize());
}
/**
* subscribes to a certain topic
*/
this.subscribe=function(subId, topic){
var msg=new smash.SECommMessage();
msg.type=smash.SECommMessage.SUBSCRIBE;
msg.topic=topic;
msg.additionalHeader = {subId: subId};
send(msg.serialize());
}
this.connect = function( callback ) {
if ( initialized ) {
var msg = new smash.SECommMessage();
msg.type = smash.SECommMessage.CONNECT;
msg.payload = window.location.href.split("#")[0];
send( msg.serialize() );
return;
}
connectCallback = callback;
}
this.disconnect = function() {
var msg = new smash.SECommMessage();
msg.type = smash.SECommMessage.DISCONNECT;
send( msg.serialize() );
}
/**
* Called when the initialisaiton of the library is done and processes all messages in the queue
*/
this.initializationFinished=function(tunnelUrl)
{
this.log( "Initialization finished. Processing outgoing queue." );
parsedTunnelUrl = new ParsedUrl(tunnelUrl);
initialized=true;
connectCallback( true );
while (queueOut.length>0)commLib.send(queueOut.shift());
}
this.getParsedTunnelUrl=function() { return parsedTunnelUrl; }
var _regex = new RegExp("^((http|https):)?(//([^/?#:]*))?(:([0-9]*))?([^?#]*)(\\?([^#]*))?");
function ParsedUrl(url) {
var matchedurl = url.match(_regex);
this.scheme = (matchedurl[2] == "") ? null : matchedurl[2];
this.host = (matchedurl[4] == "") ? null : matchedurl[4];
this.port = (matchedurl[6] == "") ? null : matchedurl[6];
this.path = (matchedurl[7] == "") ? null : matchedurl[7];
this.query = (matchedurl[8] == "") ? null : matchedurl[8];
}
/**
* unsubscribes
*/
this.unsubscribe=function(subId){
var msg=new smash.SECommMessage();
msg.type=smash.SECommMessage.UNSUBSCRIBE;
msg.additionalHeader={subId: subId};
send(msg.serialize());
}
function send(message) {
// Queue the message if sending or if there is no communication partner yet
if (initialized==false){
queueOut.push(message);
}
else{
commLib.send(message);
}
}
/**
* Function processing the incomming data from commLib
*
* @param message The message containing the incomming data
*/
this.messageReceived=function (message){
var msg=new smash.SECommMessage();
msg.deserialize(message);
// parse the JSON payload
if (msg.type == smash.SECommMessage.DISTRIBUTE) {
var header = msg.additionalHeader;
if ((header != null) && (header.f == "J"))
msg.payload = JSON.parse(msg.payload);
}
// For now, pass all messages to handleIncomingMessage()
// if ((msg.type == smash.SECommMessage.DISTRIBUTE) || (msg.type == smash.SECommMessage.SUBSCRIBE_ACK))
that.handleIncomingMessage(msg);
}
this.handleSecurityError=function (error){
if (securityListener==null){
throw new Error (error);
}
else{
securityListener( error, clientName );
}
return;
}
/**
* Sets the callback for security errors.
*
* @param The callback for security errors.
*/
this.setSecurityListener=function(callback){
securityListener=callback;
}
/**
* This method is the location for the callback to the SECommClient library.
* The application using this library overrides this method with its own implementation.
* HACK: this is terrible from a layering perspective. Ideally all message formatting details, such
* as header formats should be handled at this layer alone.
* The default behavior is to alert a message.
*
* @param message The actual message.
*/
this.handleIncomingMessage=function(message){
alert("SECommClient\n\nTopic: " + message.topic + "\n\nPayload: " + message.payload);
}
this.log = function( msg ) {
logfunc( msg );
}
}
/**
* Provides the low level communication layer.
* @param child (boolean) indicating if this is a child iframe or not.
* @param controllers (object []) an array indexed by the clientName of objects implementing the controller interface.
* @param clientName - only explicitly passed for the child iframe
*
* controller.messageReceived - called when the commlib recieves an incomming message.
* controller.initializationFinished - called when the commlib finished its initialzation.
* controller.handleSecurityError - called when a security error occurs.
*
*/
smash.CommLib=function(child, controllers, clientName){
/**BEGIN of communcation protocol **/
/*
Message format:
| Message Type | Message Sequence Number | Security Token Parent | Security Token Child | ACK | ACK Message Sequence Number | Payload |
| 1 character | 2 characters | x characters | x characters | 1 character | 2 characters | varable length |
*/
// Init message payload=communication partner url
var INIT="1";
// An ack message without any payload. The reciever is not supposed to ack this message therefore the message sequence number will be 00.
var ACK="2";
// The part message indicates that this is a message that needed to be split up. It will contain the payload of a part of the total message.
var PART="3";
// The end message indicates that this is the last part of a split up message. The full message has arrived after processing this message.
var END="4";
/** END of communcation protocol **/
// This is used to make the object available to the private methods. This is a workaround for an error in the ECMAScript Language Specification which causes this to be set incorrectly for inner functions. See http://www.crockford.com/javascript/private.html
var that=this;
// polling and queue processing interval
var interval=100;
// The maximul length of a URL. If the message is longer it will be split into different parts.
var urlLimit = 4000;
// Protocol overhead excluding security token overhead
var protocolOverhead=6;
// Need to do an acknowledgement
var ack=0;
// Raw incoming data
var currentHash=null;
// The newly decoded incoming message
var messageIn=null;
// The last decoded incoming message
var previousIn=null;
// The currently transmitted message
var messageOut=null;
// The previously transmitted message
var previousOut=null;
// The url of the partner
var partnerURL=null;
// The window object of the partner
var partnerWindow=null;
// A queue for outgoing messages. This queue is used when new send requests are done while we are still sending or recieving a message.
var queueOut=[];
// Storing the last sent message number
var msn=00;
// Buffer for storing the incoming message parts
var messageBuffer="";
// Variable storing the timerId of the message timer.
var timerId=null;
// Two security tokens - One created by the parent frame (the manager) and one by the child frame (the client)
var securityTokenParent=null;
var securityTokenChild=null;
//
var controller = null;
var logQ = [];
/**
* Sends a message to the communication partner
* @param message (string) the message that needs to be delivered to the communication partner
*/
this.send=function(message){
// check if we are properly initialized
if (partnerURL==null){
log( "Trying to send without proper initialization. Message will be discarded. " + message );
return;
}
log( "Sending: " + message );
// URL encode the message
// var encodedMessage=encodeURIComponent(message);
var encodedMessage=message;
// determine the payload size
var payloadLength=urlLimit-protocolOverhead-smash._securityTokenOverhead-partnerURL.length;
// DEBUG LARGE MESSAGES
//if(oah_ifr_debug)payloadLength=1;
// Split up into separate messages if necessary
var currentMessage=encodedMessage;
while (currentMessage.length>0){
// split up and put in output queue
var part=currentMessage.substr(0,payloadLength);
currentMessage=currentMessage.substr(payloadLength);
if (currentMessage==0){
queueOut.push({type: END, payload: part});
}
else{
queueOut.push({type: PART, payload: part});
}
}
}
/**
* The timer triggering the flow of messages through the system.
*/
function messageTimer(){
// check if there is a new message
if(checkMessage()){
// check if it can be decoded properly
if (decodeMessage()){
// check if it is conform the security requirements
if (checkSecurity()){
// process it
processMessage();
}
}
}
// Only sent if an ack was received for the last transmitted message.
if (checkAck()){
// send anything that might be in the out queue
sendMessage();
}
}
/**
* Returns true if the previously transmitted message was acknowledged.
*
* Possible exception situations to take into account:
* - One of the parties takes two turns in a row.
* p p c
* c p1 -
* p p1'
*
* c p p c
* c ac1 p1
* p c1 p1'
*
*/
function checkAck(){
// No ack is expected for an ack.
if (previousOut.type==ACK)return true;
// Ack is received.
if ((previousOut.msn==messageIn.ackMsn) && (messageIn.ack==1)) return true;
// Wait for the ack to arrive.
log( "Waiting for ACK : " + previousOut.msn );
return false;
}
/**
* Helper method providing a new message sequence number
* @returns (string) the new sequence number
*/
function getNewMsn(){
msn++;
if (msn==100) msn=0;
if (msn<10) return "0" + msn;
return "" + msn;
}
/**
* Checks the information after the hash to see if there is a new incomming message.
*/
function checkMessage(){
//Can't use location.hash because at least Firefox does a decodeURIComponent on it.
var urlParts = window.location.href.split("#");
if(urlParts.length == 2){
var newHash = urlParts[1];
if(newHash!="" && newHash != currentHash){
currentHash = newHash;
return true;
}
}
return false;
}
/**
* Decodes an incomming message and checks to see if it is syntactially valid.
*/
function decodeMessage() {
// new RegExp( "(\\d)(\\d{2})(.{" + smash._securityTokenLength + "})(.{" + smash._securityTokenLength + "})(\\d)(\\d{2})(.*)" )
var type=currentHash.substr(0,1);
var msn=currentHash.substr(1,2);
var nextStart = 3;
var tokenParent=currentHash.substr(nextStart,smash._securityTokenLength);
nextStart += smash._securityTokenLength;
var tokenChild=currentHash.substr(nextStart,smash._securityTokenLength);
nextStart += smash._securityTokenLength;
var ack=currentHash.substr(nextStart,1);
nextStart += 1;
var ackMsn=currentHash.substr(nextStart,2);
nextStart += 2;
// The payload needs to stay raw since the uri decoding needs to happen on the concatenated data in case of a large message
var payload=currentHash.substr(nextStart);
log( "In : Type: " + type + " msn: " + msn + " tokenParent: " + tokenParent + " tokenChild: " + tokenChild + " ack: " + ack + " msn: " + ackMsn + " payload: " + payload );
messageIn={type: type, msn: msn, tokenParent: tokenParent, tokenChild: tokenChild, ack: ack, ackMsn: ackMsn, payload: payload};
return true;
}
/**
* Check if there have been any security breaches in the message.
*/
function checkSecurity(){
// Check the security tokens
if (messageIn.type!=INIT && (messageIn.tokenParent!=securityTokenParent || messageIn.tokenChild!=securityTokenChild)){
log( "Security token error: Invalid security token received. The message will be discarded." );
handleSecurityError(smash.SecurityErrors.INVALID_TOKEN);
return false;
}
// Attacks should never pass the security check. Code below is to debug the implementation.
// if(oah_ifr_debug){
// if (messageIn.type!=INIT && messageIn.type!=ACK && messageIn.type!=PART && messageIn.type!=END){
// if(oah_ifr_debug)debug("Syntax error: Message Type. The message will be discarded.");
// return false;
// }
// if (!(messageIn.msn>=0 && messageIn.msn<=99)){
// if(oah_ifr_debug)debug("Syntax error: Message Sequence Number. The message will be discarded.");
// return false;
// }
// if (!(messageIn.ack==0 || messageIn.ack==1)){
// if(oah_ifr_debug)debug("Syntax error: ACK. The message will be discarded.");
// return false;
// }
// if (!(messageIn.ackMsn>=0 && messageIn.ackMsn<=99)){
// if(oah_ifr_debug)debug("Syntax error: ACK Message Sequence Number. The message will be discarded.");
// return false;
// }
// }
return true;
}
/**
* Process the incoming message.
*/
function processMessage(){
ack=1;
// The child is initialized as soon as there is an ack for the init message sent by the child.
if (messageIn.type!=INIT && child && previousOut.type==INIT && messageIn.ack=="1" && previousOut.msn==messageIn.ackMsn) {
controller.initializationFinished(partnerURL);
}
// Call the actual processing functions
switch(messageIn.type){
case INIT:
processInit();
break;
case ACK:
processAck();
break;
case PART:
processPart();
break;
case END:
processEnd();
break;
}
// Set the processed message as the previousIn message
previousIn=messageIn;
}
/**
* Implementation of the INIT message type
**/
function processInit(){
var parts = messageIn.payload.split(":");
var cname = decodeURIComponent(parts[0]);
partnerURL=decodeURIComponent(parts[1]);
securityTokenParent=messageIn.tokenParent;
securityTokenChild=messageIn.tokenChild;
// Initialize a component
if (child){
if (clientName != null) cname = clientName; // override what is read from the URL
// generate a real security token for the child
securityTokenChild = smash._generateSecurityToken();
// GUI which will be used to name the iFrame tunnel.
var tunnelGUID="3827816c-f3b1-11db-8314-0800200c9a66";
// Generate the hidden iframe for communicating
var iframe = document.createElement("iframe");
var currentClientURI = encodeURIComponent(window.location.href.split("#")[0]);
var initialClientURI = currentClientURI;
// if (smash._initialClientURI) {
// initialClientURI = encodeURIComponent(smash._initialClientURI);
// }
var initpayload = encodeURIComponent(cname) + ":" + currentClientURI + ":" + initialClientURI;
// sending an ack for msn "00" to the tunnel, since have processed the INIT message,
// and so that the INIT message the component is sending to the tunnel will result
// in an ack to be sent back.
// XXX Since server redirection breaks hash communication (the server does
// not receive the fragment value, therefore the final URL does not contain
// this information), the initial message is transmitted as a URL param.
partnerURL += (partnerURL.indexOf("?") != -1 ? "&" : "?") + "oahm=100" + securityTokenParent + securityTokenChild + "100" + initpayload;
iframe.src = partnerURL;
iframe.name=tunnelGUID;
iframe.id=tunnelGUID;
document.body.appendChild(iframe);
iframe.style.position = "absolute";
iframe.style.left = iframe.style.top = "-10px";
iframe.style.height = iframe.style.width = "1px";
iframe.style.visibility = "hidden";
// We do not send an ack directly to the parent frame since it is impossible to directly communicate with it in IE7
// The ack is done indirectly when the registerTunnelCommLib is done
ack=0;
// set up the partner window
partnerWindow=window.frames[tunnelGUID];
// store the last sent message - will be used to detect intialization and for detecting security breaches
previousOut={type: INIT, msn: "00", tokenParent: securityTokenParent, tokenChild: securityTokenChild, ack: "0", ackMsn: "00", payload: initpayload}; // only using type and msn of previousOut. presumably the rest is for FDK's retransmit stuff? should get rid of this complexity
// set the controller for this component
controller=controllers["child"];
}
// Initialize a tunnel
else{
var initialClientURI = decodeURIComponent(parts[2]);
// set up the partner window
partnerWindow=window.parent;
// set the controller for this component
controller=controllers[cname];
var success = controller.initializationFinished(that, securityTokenParent, partnerURL, initialClientURI, window);
if (!success) ack = 0; // don't send an ack signalling the completion of connection setup.
// store the last sent message - will be used to detect intialization and for detecting security breaches
previousOut={type: INIT, msn: "00", tokenParent: securityTokenParent, tokenChild: securityTokenChild, ack: "0", ackMsn: "00", payload: (encodeURIComponent(cname) + ":" + encodeURIComponent(window.location.href.split("#")[0]))}; // only using type and msn of previousOut. presumably the rest is for FDK's retransmit stuff? should get rid of this complexity
}
if (partnerWindow==null) {
log( "Init failed." );
}
}
/**
* Implementation of the ACK message type
**/
function processAck(){
// do not ack an ack
ack=0;
}
/**
* Implementation of the PART message type
**/
function processPart(){
// Process message
messageBuffer+=messageIn.payload;
}
/**
* Implementation the END message type
**/
function processEnd(){
// Process message
messageBuffer+=messageIn.payload;
// messageBuffer=decodeURIComponent(messageBuffer);
log( "Received: " + messageBuffer );
controller.messageReceived(messageBuffer);
messageBuffer="";
}
/**
* Send a reply to the incoming message.
*/
function sendMessage(){
// If there is nothing in the queue and an ack needs to be sent put the ack on the queue;
if (queueOut.length==0 && ack==1){
// The correct values will be filled in later. Just push a clean ack message
queueOut.push({type: ACK, payload: ""});
}
// Process the output queue
if (queueOut.length!=0){
messageOut=queueOut.shift();
// Fill in the security token
messageOut.tokenParent=securityTokenParent;
messageOut.tokenChild=securityTokenChild;
// Get a new sequence number
messageOut.msn=getNewMsn();
// Fill in the right ack values
// The protocol keeps acking the last received message to ensure that there are no
// problems with overwriting a pure ack message. Which could happen because there is
// no waiting for an ack of an ack.
messageOut.ack="1";
messageOut.ackMsn=previousIn.msn;
// turn of the ack
ack=0;
writeToPartnerWindow();
}
}
/**
* Writes the message to the partner window's fragment id
**/
function writeToPartnerWindow(){
var url = partnerURL + "#" + messageOut.type + messageOut.msn + messageOut.tokenParent + messageOut.tokenChild + messageOut.ack + messageOut.ackMsn + messageOut.payload;
partnerWindow.location.replace(url);
previousOut=messageOut;
log( "Out: Type: " + messageOut.type + " msn: " + messageOut.msn + " tokenParent: " + messageOut.tokenParent + " tokenChild: " + messageOut.tokenChild + " ack: " + messageOut.ack + " msn: " + messageOut.ackMsn + " payload: " + messageOut.payload );
}
/**
* Default handler of the security listener. If a security error occurs, the CommLib is switched off. And communication is no longer possible.
*
*/
function handleSecurityError(error){
// Stop the communication
clearInterval(timerId);
// If there is a securityListener inform the controller of what happened.
controller.handleSecurityError(error);
}
function log( msg )
{
if ( controller ) {
while ( logQ.length > 0 ) {
controller.log( logQ.shift() );
}
controller.log( msg );
} else {
logQ.push( msg );
}
}
// Start listening for incoming messages
timerId=setInterval(messageTimer, interval);
};
})(); // end closure
/*
Copyright 2006-2009 OpenAjax Alliance
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.
*/
// SMASH.CRYPTO
//
// Small library containing some minimal crypto functionality for a
// - a hash-function: SHA-1 (see FIPS PUB 180-2 for definition)
// BigEndianWord[5] <- smash.crypto.sha1( BigEndianWord[*] dataWA, int lenInBits)
//
// - a message authentication code (MAC): HMAC-SHA-1 (RFC2104/2202)
// BigEndianWord[5] <- smash.crypto.hmac_sha1(
// BigEndianWord[3-16] keyWA,
// Ascii or Unicode string dataS,
// int chrsz (8 for Asci/16 for Unicode)
//
// - pseudo-random number generator (PRNG): HMAC-SHA-1 in counter mode, following
// Barak & Halevi, An architecture for robust pseudo-random generation and applications to /dev/random, CCS 2005
// rngObj <- smash.crypto.newPRNG( String[>=12] seedS)
// where rngObj has methods
// addSeed(String seed)
// BigEndianWord[len] <- nextRandomOctets(int len)
// Base64-String[len] <- nextRandomB64Str(int len)
// Note: HMAC-SHA1 in counter-mode does not provide forward-security on corruption.
// However, the PRNG state is kept inside a closure. So if somebody can break the closure, he probably could
// break a whole lot more and forward-security of the prng is not the highest of concerns anymore :-)
if ( typeof OpenAjax._smash == 'undefined' ) { OpenAjax._smash = {}; }
OpenAjax._smash.crypto = {
// Some utilities
// convert a string to an array of big-endian words
'strToWA': function (/* Ascii or Unicode string */ str, /* int 8 for Asci/16 for Unicode */ chrsz){
var bin = Array();
var mask = (1 << chrsz) - 1;
for(var i = 0; i < str.length * chrsz; i += chrsz)
bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i%32);
return bin;
},
// MAC
'hmac_sha1' : function(
/* BigEndianWord[3-16]*/ keyWA,
/* Ascii or Unicode string */ dataS,
/* int 8 for Asci/16 for Unicode */ chrsz)
{
// write our own hmac derived from paj's so we do not have to do constant key conversions and length checking ...
var ipad = Array(16), opad = Array(16);
for(var i = 0; i < 16; i++) {
ipad[i] = keyWA[i] ^ 0x36363636;
opad[i] = keyWA[i] ^ 0x5C5C5C5C;
}
var hash = this.sha1( ipad.concat(this.strToWA(dataS, chrsz)), 512 + dataS.length * chrsz);
return this.sha1( opad.concat(hash), 512 + 160);
},
// PRNG factory method
// see below 'addSeed', 'nextRandomOctets' & 'nextRandomB64Octets' for public methods of returnd prng object
'newPRNG' : function (/* String[>=12] */ seedS) {
that = this;
// parameter checking
// We cannot really verify entropy but obviously the string must have at least a minimal length to have enough entropy
// However, a 2^80 security seems ok, so we check only that at least 12 chars assuming somewhat random ASCII
if ( (typeof seedS != 'string') || (seedS.length < 12) ) {
alert("WARNING: Seed length too short ...");
}
// constants
var __refresh_keyWA = [ 0xA999, 0x3E36, 0x4706, 0x816A,
0x2571, 0x7850, 0xC26C, 0x9CD0,
0xBA3E, 0xD89D, 0x1233, 0x9525,
0xff3C, 0x1A83, 0xD491, 0xFF15 ]; // some random key for refresh ...
// internal state
var _keyWA = []; // BigEndianWord[5]
var _cnt = 0; // int
function extract(seedS) {
return that.hmac_sha1(__refresh_keyWA, seedS, 8);
}
function refresh(seedS) {
// HMAC-SHA1 is not ideal, Rijndal 256bit block/key in CBC mode with fixed key might be better
// but to limit the primitives and given that we anyway have only limited entropy in practise
// this seems good enough
var uniformSeedWA = extract(seedS);
for(var i = 0; i < 5; i++) {
_keyWA[i] ^= uniformSeedWA[i];
}
}
// inital state seeding
refresh(seedS);
// public methods
return {
// Mix some additional seed into the PRNG state
'addSeed' : function (/* String */ seed) {
// no parameter checking. Any added entropy should be fine ...
refresh(seed);
},
// Get an array of len random octets
'nextRandomOctets' : /* BigEndianWord[len] <- */ function (/* int */ len) {
var randOctets = [];
while (len > 0) {
_cnt+=1;
var nextBlock = that.hmac_sha1(_keyWA, (_cnt).toString(16), 8);
for (i=0; (i < 20) & (len > 0); i++, len--) {
randOctets.push( (nextBlock[i>>2] >> (i % 4) ) % 256);
}
// Note: if len was not a multiple 20, some random octets are ignored here but who cares ..
}
return randOctets;
},
// Get a random string of Base64-like (see below) chars of length len
// Note: there is a slightly non-standard Base64 with no padding and '-' and '_' for '+' and '/', respectively
'nextRandomB64Str' : /* Base64-String <- */ function (/* int */ len) {
var b64StrMap = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
var randOctets = this.nextRandomOctets(len);
var randB64Str = '';
for (var i=0; i < len; i++) {
randB64Str += b64StrMap.charAt(randOctets[i] & 0x3F);
}
return randB64Str;
}
}
},
// Digest function:
// BigEndianWord[5] <- sha1( BigEndianWord[*] dataWA, int lenInBits)
'sha1' : function(){
// Note: all Section references below refer to FIPS 180-2.
// private utility functions
// - 32bit addition with wrap-around
var add_wa = function (x, y){
var lsw = (x & 0xFFFF) + (y & 0xFFFF);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
}
// - 32bit rotatate left
var rol = function(num, cnt) {
return (num << cnt) | (num >>> (32 - cnt));
}
// - round-dependent function f_t from Section 4.1.1
function sha1_ft(t, b, c, d) {
if(t < 20) return (b & c) | ((~b) & d);
if(t < 40) return b ^ c ^ d;
if(t < 60) return (b & c) | (b & d) | (c & d);
return b ^ c ^ d;
}
// - round-dependent SHA-1 constants from Section 4.2.1
function sha1_kt(t) {
return (t < 20) ? 1518500249 :
(t < 40) ? 1859775393 :
(t < 60) ? -1894007588 :
/* (t < 80) */ -899497514 ;
}
// main algorithm.
return function( /* BigEndianWord[*] */ dataWA, /* int */ lenInBits) {
// Section 6.1.1: Preprocessing
//-----------------------------
// 1. padding: (see also Section 5.1.1)
// - append one 1 followed by 0 bits filling up 448 bits of last (512bit) block
dataWA[lenInBits >> 5] |= 0x80 << (24 - lenInBits % 32);
// - encode length in bits in last 64 bits
// Note: we rely on javascript to zero file elements which are beyond last (partial) data-block
// but before this length encoding!
dataWA[((lenInBits + 64 >> 9) << 4) + 15] = lenInBits;
// 2. 512bit blocks (actual split done ondemand later)
var W = Array(80);
// 3. initial hash using SHA-1 constants on page 13
var H0 = 1732584193;
var H1 = -271733879;
var H2 = -1732584194;
var H3 = 271733878;
var H4 = -1009589776;
// 6.1.2 SHA-1 Hash Computation
for(var i = 0; i < dataWA.length; i += 16) {
// 1. Message schedule, done below
// 2. init working variables
var a = H0; var b = H1; var c = H2; var d = H3; var e = H4;
// 3. round-functions
for(var j = 0; j < 80; j++)
{
// postponed step 2
W[j] = ( (j < 16) ? dataWA[i+j] : rol(W[j-3] ^ W[j-8] ^ W[j-14] ^ W[j-16], 1));
var T = add_wa( add_wa( rol(a, 5), sha1_ft(j, b, c, d)),
add_wa( add_wa(e, W[j]), sha1_kt(j)) );
e = d;
d = c;
c = rol(b, 30);
b = a;
a = T;
}
// 4. intermediate hash
H0 = add_wa(a, H0);
H1 = add_wa(b, H1);
H2 = add_wa(c, H2);
H3 = add_wa(d, H3);
H4 = add_wa(e, H4);
}
return Array(H0, H1, H2, H3, H4);
}
}()
};
/*
Copyright 2006-2009 OpenAjax Alliance
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.
*/
if ( typeof OpenAjax === "undefined" ) {
OpenAjax = { hub: {} };
}
/**
* Create a new Iframe Container.
* @constructor
* @extends OpenAjax.hub.Container
*
* IframeContainer implements the Container interface to provide a container
* that isolates client components into secure sandboxes by leveraging the
* isolation features provided by browser iframes.
*
* @param {OpenAjax.hub.ManagedHub} hub
* Managed Hub instance to which this Container belongs
* @param {String} clientID
* A string ID that identifies a particular client of a Managed Hub. Unique
* within the context of the ManagedHub.
* @param {Object} params
* Parameters used to instantiate the IframeContainer.
* Once the constructor is called, the params object belongs exclusively to
* the IframeContainer. The caller MUST not modify it.
* The following are the pre-defined properties on params:
* @param {Function} params.Container.onSecurityAlert
* Called when an attempted security breach is thwarted. Function is defined
* as follows: function(container, securityAlert)
* @param {Function} [params.Container.onConnect]
* Called when the client connects to the Managed Hub. Function is defined
* as follows: function(container)
* @param {Function} [params.Container.onDisconnect]
* Called when the client disconnects from the Managed Hub. Function is
* defined as follows: function(container)
* @param {Object} [params.Container.scope]
* Whenever one of the Container's callback functions is called, references
* to "this" in the callback will refer to the scope object. If no scope is
* provided, default is window.
* @param {Function} [params.Container.log]
* Optional logger function. Would be used to log to console.log or
* equivalent.
* @param {Object} params.IframeContainer.parent
* DOM element that is to be parent of iframe
* @param {String} params.IframeContainer.uri
* Initial Iframe URI (Container will add parameters to this URI)
* @param {String} params.IframeContainer.tunnelURI
* URI of the tunnel iframe. Must be from the same origin as the page which
* instantiates the IframeContainer.
* @param {Object} [params.IframeContainer.iframeAttrs]
* Attributes to add to IFRAME DOM entity. For example:
* { style: { width: "100%",
* height: "100%" },
* className: "some_class" }
* @param {Number} [params.IframeContainer.timeout]
* Load timeout in milliseconds. If not specified, defaults to 15000. If
* the client at params.IframeContainer.uri does not establish a connection
* with this container in the given time, the onSecurityAlert callback is
* called with a LoadTimeout error code.
* @param {Function} [params.IframeContainer.seed]
* A function that returns a string that will be used to seed the
* pseudo-random number generator, which is used to create the security
* tokens. An implementation of IframeContainer may choose to ignore this
* value.
* @param {Number} [params.IframeContainer.tokenLength]
* Length of the security tokens used when transmitting messages. If not
* specified, defaults to 6. An implementation of IframeContainer may choose
* to ignore this value.
*
* @throws {OpenAjax.hub.Error.BadParameters} if required params are not
* present or null
* @throws {OpenAjax.hub.Error.Duplicate} if a Container with this clientID
* already exists in the given Managed Hub
* @throws {OpenAjax.hub.Error.Disconnected} if hub is not connected
*/
OpenAjax.hub.IframeContainer = function( hub, clientID, params )
{
if ( ! hub || ! clientID || ! params ||
! params.Container || ! params.Container.onSecurityAlert ||
! params.IframeContainer || ! params.IframeContainer.parent ||
! params.IframeContainer.uri || ! params.IframeContainer.tunnelURI ) {
throw new Error(OpenAjax.hub.Error.BadParameters);
}
this._params = params;
this._id = clientID;
if ( window.postMessage ) {
this._delegate = new OpenAjax.hub.IframePMContainer( this, hub, clientID, params );
} else {
this._delegate = new OpenAjax.hub.IframeFIMContainer( this, hub, clientID, params );
}
// Create IFRAME to hold the client
this._iframe = this._createIframe( params.IframeContainer.parent, this._delegate.getURI(),
params.IframeContainer.iframeAttrs );
hub.addContainer( this );
}
/*** OpenAjax.hub.Container interface implementation ***/
OpenAjax.hub.IframeContainer.prototype.getHub = function()
{
return this._delegate.getHub();
}
OpenAjax.hub.IframeContainer.prototype.sendToClient = function( topic, data, subscriptionID )
{
this._delegate.sendToClient( topic, data, subscriptionID );
}
OpenAjax.hub.IframeContainer.prototype.remove = function()
{
this._delegate.remove();
this._iframe.parentNode.removeChild( this._iframe );
delete this._iframe;
}
OpenAjax.hub.IframeContainer.prototype.isConnected = function()
{
return this._delegate.isConnected();
}
OpenAjax.hub.IframeContainer.prototype.getClientID = function()
{
return this._id;
}
OpenAjax.hub.IframeContainer.prototype.getPartnerOrigin = function()
{
return this._delegate.getPartnerOrigin();
}
OpenAjax.hub.IframeContainer.prototype.getParameters = function()
{
return this._params;
}
/**
* Get the iframe associated with this iframe container
*
* This function returns the iframe associated with an IframeContainer,
* allowing the Manager Application to change its size, styles, scrollbars, etc.
*
* CAUTION: The iframe is owned exclusively by the IframeContainer. The Manager
* Application MUST NOT destroy the iframe directly. Also, if the iframe is
* hidden and disconnected, the Manager Application SHOULD NOT attempt to make
* it visible. The Container SHOULD automatically hide the iframe when it is
* disconnected; to make it visible would introduce security risks.
*
* @returns iframeElement
* @type {Object}
*/
OpenAjax.hub.IframeContainer.prototype.getIframe = function()
{
return this._iframe;
}
/*** Helper Functions ***/
/**
* Return function that runs in given scope.
*
* @param {Object} toWhom scope in which to run given function
* @param {Function} callback function to run in given scope
* @returns {Function}
*/
OpenAjax.hub.IframeContainer.bind = function( toWhom, callback )
{
var __method = callback;
return function() {
return __method.apply(toWhom, arguments);
}
}
/*** Private Functions ***/
OpenAjax.hub.IframeContainer.prototype._createIframe = function( parent, src, attrs )
{
var iframe = document.createElement( "iframe" );
// Add iframe attributes
if ( attrs ) {
for ( var attr in attrs ) {
if ( attr == "style" ) {
for ( var style in attrs.style ) {
iframe.style[ style ] = attrs.style[ style ];
}
} else {
iframe[ attr ] = attrs[ attr ];
}
}
}
// initially hide IFRAME content, in order to lessen frame phishing impact
iframe.style.visibility = "hidden";
// (1) Setting the iframe src after it has been added to the DOM can cause
// problems in IE6/7. Specifically, if the code is being executed on a page
// that was served through HTTPS, then IE6/7 will see an iframe with a blank
// src as a non-secure item and display a dialog warning the user that "this
// page contains both secure and nonsecure items." To prevent that, we
// first set the src to a dummy value, then add the iframe to the DOM, then
// set the real src value.
// (2) Trying to fix the above issue by setting the real src before adding
// the iframe to the DOM breaks Firefox 3.x. For some reason, when
// reloading a page that has instantiated an IframeContainer, Firefox will
// load a previously cached version of the iframe content, whose source
// contains stale URL query params or hash. This results in errors in the
// Hub code, which is expected different values.
iframe.src = 'javascript:"<html></html>"';
parent.appendChild( iframe );
iframe.src = src;
return iframe;
}
//------------------------------------------------------------------------------
/**
* Create a new IframeHubClient.
* @constructor
* @extends OpenAjax.hub.HubClient
*
* @param {Object} params
* Once the constructor is called, the params object belongs to the
* HubClient. The caller MUST not modify it.
* The following are the pre-defined properties on params:
* @param {Function} params.HubClient.onSecurityAlert
* Called when an attempted security breach is thwarted
* @param {Object} [params.HubClient.scope]
* Whenever one of the HubClient's callback functions is called,
* references to "this" in the callback will refer to the scope object.
* If not provided, the default is window.
* @param {Function} [params.HubClient.log]
* Optional logger function. Would be used to log to console.log or
* equivalent.
* @param {Function} [params.IframeHubClient.seed]
* A function that returns a string that will be used to seed the
* pseudo-random number generator, which is used to create the security
* tokens. An implementation of IframeHubClient may choose to ignore
* this value.
* @param {Number} [params.IframeHubClient.tokenLength]
* Length of the security tokens used when transmitting messages. If
* not specified, defaults to 6. An implementation of IframeHubClient
* may choose to ignore this value.
*
* @throws {OpenAjax.hub.Error.BadParameters} if any of the required
* parameters is missing, or if a parameter value is invalid in
* some way.
*/
OpenAjax.hub.IframeHubClient = function( params )
{
if ( ! params || ! params.HubClient || ! params.HubClient.onSecurityAlert ) {
throw new Error( OpenAjax.hub.Error.BadParameters );
}
this._params = params;
if ( window.postMessage ) {
this._delegate = new OpenAjax.hub.IframePMHubClient( this, params );
} else {
this._delegate = new OpenAjax.hub.IframeFIMHubClient( this, params );
}
}
/*** OpenAjax.hub.HubClient interface implementation ***/
OpenAjax.hub.IframeHubClient.prototype.connect = function( onComplete, scope )
{
scope = scope || window;
if ( this.isConnected() ) {
throw new Error( OpenAjax.hub.Error.Duplicate );
}
this._delegate.connect( onComplete, scope );
}
OpenAjax.hub.IframeHubClient.prototype.disconnect = function( onComplete, scope )
{
scope = scope || window;
if ( ! this.isConnected() ) {
throw new Error( OpenAjax.hub.Error.Disconnected );
}
this._delegate.disconnect( onComplete, scope );
}
OpenAjax.hub.IframeHubClient.prototype.getPartnerOrigin = function()
{
return this._delegate.getPartnerOrigin();
}
OpenAjax.hub.IframeHubClient.prototype.getClientID = function()
{
return this._delegate.getClientID();
}
/*** OpenAjax.hub.Hub interface implementation ***/
OpenAjax.hub.IframeHubClient.prototype.subscribe = function( topic, onData, scope, onComplete, subscriberData )
{
this._assertConn();
this._assertSubTopic( topic );
if ( ! onData ) {
throw new Error( OpenAjax.hub.Error.BadParameters );
}
scope = scope || window;
return this._delegate.subscribe( topic, onData, scope, onComplete, subscriberData );
}
OpenAjax.hub.IframeHubClient.prototype.publish = function( topic, data )
{
this._assertConn();
this._assertPubTopic( topic );
this._delegate.publish( topic, data );
}
OpenAjax.hub.IframeHubClient.prototype.unsubscribe = function( subscriptionID, onComplete, scope )
{
this._assertConn();
if ( typeof subscriptionID === "undefined" || subscriptionID == null ) {
throw new Error( OpenAjax.hub.Error.BadParameters );
}
scope = scope || window;
this._delegate.unsubscribe( subscriptionID, onComplete, scope );
}
OpenAjax.hub.IframeHubClient.prototype.isConnected = function()
{
return this._delegate.isConnected();
}
OpenAjax.hub.IframeHubClient.prototype.getScope = function()
{
return this._delegate.getScope();
}
OpenAjax.hub.IframeHubClient.prototype.getSubscriberData = function( subscriptionID )
{
this._assertConn();
return this._delegate.getSubscriberData( subscriptionID );
}
OpenAjax.hub.IframeHubClient.prototype.getSubscriberScope = function( subscriptionID )
{
this._assertConn();
return this._delegate.getSubscriberScope( subscriptionID );
}
OpenAjax.hub.IframeHubClient.prototype.getParameters = function()
{
return this._params;
}
/*** Private Functions ***/
OpenAjax.hub.IframeHubClient.prototype._assertConn = function()
{
if ( ! this.isConnected() ) {
throw new Error( OpenAjax.hub.Error.Disconnected );
}
}
OpenAjax.hub.IframeHubClient.prototype._assertSubTopic = function( topic )
{
if ( ! topic ) {
throw new Error(OpenAjax.hub.Error.BadParameters);
}
var path = topic.split(".");
var len = path.length;
for (var i = 0; i < len; i++) {
var p = path[i];
if ((p == "") ||
((p.indexOf("*") != -1) && (p != "*") && (p != "**"))) {
throw new Error(OpenAjax.hub.Error.BadParameters);
}
if ((p == "**") && (i < len - 1)) {
throw new Error(OpenAjax.hub.Error.BadParameters);
}
}
}
OpenAjax.hub.IframeHubClient.prototype._assertPubTopic = function( topic )
{
if ((topic == null) || (topic == "") || (topic.indexOf("*") != -1) ||
(topic.indexOf("..") != -1) || (topic.charAt(0) == ".") ||
(topic.charAt(topic.length-1) == "."))
{
throw new Error(OpenAjax.hub.Error.BadParameters);
}
}
/******************************************************************************
* PostMessage Iframe Container
*
* Implementation of the Iframe Container which uses window.postMessage()
* for communicating between an iframe and its parent.
******************************************************************************/
OpenAjax.hub.IframePMContainer = function( container, hub, clientID, params )
{
this._container = container;
this._hub = hub;
this._id = clientID;
this._onSecurityAlert = params.Container.onSecurityAlert;
this._onConnect = params.Container.onConnect ? params.Container.onConnect : null;
this._onDisconnect = params.Container.onDisconnect ? params.Container.onDisconnect : null;
this._scope = params.Container.scope || window;
this._uri = params.IframeContainer.uri;
this._tunnelURI = params.IframeContainer.tunnelURI;
this._timeout = params.IframeContainer.timeout || 15000;
if ( params.Container.log ) {
var scope = this._scope;
var logfunc = params.Container.log;
this._log = function( msg ) {
logfunc.call( scope, "IframeContainer::" + clientID + ": " + msg );
};
} else {
this._log = function() {};
}
this._securityToken = this._generateSecurityToken( params );
this._connected = false;
this._subs = {};
// test if the postMessage impl of this browser is synchronous
if ( typeof OpenAjax.hub.IframePMContainer._pmCapabilities === "undefined" ) {
this._testPostMessage();
}
// if postMessage is synchronous, wrap in a setTimeout
if ( OpenAjax.hub.IframePMContainer._pmCapabilities.indexOf("s") == -1 ) {
this._postMessage = function( win, msg, origin ) {
win.postMessage( msg, origin );
}
} else {
this._postMessage = function( win, msg, origin ) {
setTimeout(
function() {
win.postMessage( msg, origin );
},
0
);
}
}
// register this container with the singleton message listener
if ( ! OpenAjax.hub.IframePMContainer._pmListener ) {
OpenAjax.hub.IframePMContainer._pmListener =
new OpenAjax.hub.IframePMContainer.PMListener();
}
// the 'internal ID' is guaranteed to be unique within the page, not just
// the ManagedHub instance
this._internalID = OpenAjax.hub.IframePMContainer._pmListener.addContainer( this );
this._startLoadTimer();
}
// communications protocol identifier
OpenAjax.hub.IframePMContainer.protocolID = "openajax-2.0";
// Singleton message listener
OpenAjax.hub.IframePMContainer._pmListener = null;
OpenAjax.hub.IframePMContainer.prototype.getHub = function() {
return this._hub;
};
OpenAjax.hub.IframePMContainer.prototype.sendToClient = function( topic, data, subscriptionID )
{
this._sendMessage( "pub", { t: topic, d: data, s: subscriptionID } );
}
OpenAjax.hub.IframePMContainer.prototype.remove = function()
{
this._disconnect();
OpenAjax.hub.IframePMContainer._pmListener.removeContainer( this._internalID );
clearTimeout( this._loadTimer );
delete this._iframe;
}
OpenAjax.hub.IframePMContainer.prototype.isConnected = function()
{
return this._connected;
}
OpenAjax.hub.IframePMContainer.prototype.getPartnerOrigin = function()
{
if ( this._connected ) {
// remove port, if it is present
return new RegExp( "^([a-zA-Z]+://[^:]+).*" ).exec( this._partnerOrigin )[1];
}
return null;
}
OpenAjax.hub.IframePMContainer.prototype.receiveMessage = function( event, msg )
{
// check that security token and client window origin for incoming message
// are what we expect
if ( msg.t != this._securityToken ||
( typeof this._partnerOrigin != "undefined" &&
! OpenAjax.hub.IframePMContainer.originMatches( this, event )))
{
// security error -- incoming message is not valid; ignore
this._invokeSecurityAlert( OpenAjax.hub.SecurityAlert.ForgedMsg );
return;
}
this._log( "received message: [" + event.data + "]" );
switch ( msg.m ) {
// subscribe
case "sub":
var errCode = ""; // empty string is success
try {
this._subs[ msg.p.s ] = this._hub.subscribeForClient( this._container, msg.p.t, msg.p.s );
} catch( e ) {
errCode = e.message;
}
this._sendMessage( "sub_ack", { s: msg.p.s, e: errCode } );
break;
// publish
case "pub":
this._hub.publishForClient( this._container, msg.p.t, msg.p.d );
break;
// unsubscribe
case "uns":
var handle = this._subs[ msg.p.s ];
this._hub.unsubscribeForClient( this._container, handle );
delete this._subs[ msg.p.s ];
this._sendMessage( "uns_ack", msg.p.s );
break;
// connect is handled elsewhere -- see IframePMContainer.prototype.connect
// disconnect
case "dis":
this._startLoadTimer();
this._disconnect();
this._sendMessage( "dis_ack", null );
if ( this._onDisconnect ) {
try {
this._onDisconnect.call( this._scope, this._container );
} catch( e ) {
OpenAjax.hub._debugger();
this._log( "caught error from onDisconnect callback to constructor: " + e.message );
}
}
break;
}
}
/**
* Complete connection from HubClient to this Container.
*
* @param {String} origin IframePMHubClient's window's origin
* @param {String} securityToken Security token originally sent by Container
* @param {Object} tunnelWindow window object reference of tunnel window
*/
OpenAjax.hub.IframePMContainer.prototype.connect = function( origin, securityToken, tunnelWindow )
{
this._log( "client connecting to container " + this._id +
" :: origin = " + origin + " :: securityToken = " + securityToken );
// check that security token is what we expect
if ( securityToken != this._securityToken ) {
// security error -- incoming message is not valid
this._invokeSecurityAlert( OpenAjax.hub.SecurityAlert.ForgedMsg );
return;
}
// set unload handler on tunnel window
var that = this;
tunnelWindow.onunload = function() {
if ( that.isConnected() ) {
// Use a timer to delay the phishing message. This makes sure that
// page navigation does not cause phishing errors.
// Setting it to 1 ms is enough for it not to be triggered on
// regular page navigations.
setTimeout(
function() {
that._invokeSecurityAlert( OpenAjax.hub.SecurityAlert.FramePhish );
}, 1
);
}
};
clearTimeout( this._loadTimer );
this._iframe = this._container.getIframe();
this._iframe.style.visibility = "visible";
this._partnerOrigin = origin;
// if "message" event doesn't support "origin" property, then save hostname
// (domain) also
if ( OpenAjax.hub.IframePMContainer._pmCapabilities.indexOf("d") != -1 ) {
this._partnerDomain = new RegExp( "^.+://([^:]+).*" ).exec( this._partnerOrigin )[1];
}
this._sendMessage( "con_ack", null );
this._connected = true;
if ( this._onConnect ) {
try {
this._onConnect.call( this._scope, this._container );
} catch( e ) {
OpenAjax.hub._debugger();
this._log( "caught error from onConnect callback to constructor: " + e.message );
}
}
}
OpenAjax.hub.IframePMContainer.prototype.getURI = function()
{
// add the client ID and a security token as URL query params when loading
// the client iframe
var paramStr =
"oahpv=" + encodeURIComponent( OpenAjax.hub.IframePMContainer.protocolID ) +
"&oahi=" + encodeURIComponent( this._internalID ) +
"&oaht=" + this._securityToken +
"&oahu=" + encodeURIComponent( this._tunnelURI ) +
"&oahpm=" + OpenAjax.hub.IframePMContainer._pmCapabilities;
if ( this._id !== this._internalID ) {
paramStr += "&oahj=" + this._internalID;
}
paramStr += OpenAjax.hub.enableDebug ? "&oahd=true" : ""; // REMOVE ON BUILD
var parts = this._uri.split("#");
parts[0] = parts[0] + ((parts[0].indexOf( "?" ) != -1) ? "&" : "?") + paramStr;
if ( parts.length == 1 ) {
return parts[0];
}
return parts[0] + "#" + parts[1];
}
/*** Helper Functions ***/
OpenAjax.hub.IframePMContainer.originMatches = function( obj, event )
{
if ( event.origin ) {
return event.origin == obj._partnerOrigin;
} else {
return event.domain == obj._partnerDomain;
}
}
/*** Private Function ***/
OpenAjax.hub.IframePMContainer.prototype._generateSecurityToken = function( params )
{
if ( ! OpenAjax.hub.IframePMContainer._prng ) {
// create pseudo-random number generator with a default seed
var seed = new Date().getTime() + Math.random() + document.cookie;
OpenAjax.hub.IframePMContainer._prng = OpenAjax._smash.crypto.newPRNG( seed );
}
if ( params.IframeContainer.seed ) {
try {
var extraSeed = params.IframeContainer.seed.call( this._scope );
OpenAjax.hub.IframePMContainer._prng.addSeed( extraSeed );
} catch( e ) {
OpenAjax.hub._debugger();
this._log( "caught error from 'seed' callback: " + e.message );
}
}
var tokenLength = params.IframeContainer.tokenLength || 6;
return OpenAjax.hub.IframePMContainer._prng.nextRandomB64Str( tokenLength );
}
/**
* Some browsers (IE, Opera) have an implementation of postMessage that is
* synchronous, although HTML5 specifies that it should be asynchronous. In
* order to make all browsers behave consistently, we run a small test to detect
* if postMessage is asynchronous or not. If not, we wrap calls to postMessage
* in a setTimeout with a timeout of 0.
* Also, Opera's "message" event does not have an "origin" property (at least,
* it doesn't in version 9.64; presumably, it will in version 10). If
* event.origin does not exist, use event.domain. The other difference is that
* while event.origin looks like <scheme>://<hostname>:<port>, event.domain
* consists only of <hostname>.
*/
OpenAjax.hub.IframePMContainer.prototype._testPostMessage = function()
{
// String identifier that specifies whether this browser's postMessage
// implementation differs from the spec:
// contains "s" - postMessage is synchronous
// contains "d" - "message" event does not have an "origin" property;
// the code looks for the "domain" property instead
OpenAjax.hub.IframePMContainer._pmCapabilities = "";
var hit = false;
function receiveMsg(event) {
if ( event.data == "postmessage.test" ) {
hit = true;
if ( typeof event.origin === "undefined" ) {
OpenAjax.hub.IframePMContainer._pmCapabilities += "d";
}
}
}
if ( window.addEventListener ) {
window.addEventListener( "message", receiveMsg, false );
} else if ( window.attachEvent ) {
window.attachEvent( "onmessage", receiveMsg );
}
window.postMessage( "postmessage.test", "*" );
// if 'hit' is true here, then postMessage is synchronous
if ( hit ) {
OpenAjax.hub.IframePMContainer._pmCapabilities += "s";
}
if ( window.removeEventListener ) {
window.removeEventListener( "message", receiveMsg, false );
} else {
window.detachEvent( "onmessage", receiveMsg );
}
}
OpenAjax.hub.IframePMContainer.prototype._startLoadTimer = function()
{
var that = this;
this._loadTimer = setTimeout(
function() {
// don't accept any messages from client
OpenAjax.hub.IframePMContainer._pmListener.removeContainer( that._internalID );
// alert the security alert callback
that._invokeSecurityAlert( OpenAjax.hub.SecurityAlert.LoadTimeout );
},
this._timeout
);
}
/**
* Send a string message to the associated hub client.
*
* The message is a JSON representation of the following object:
* {
* m: message type,
* i: client id,
* t: security token,
* p: payload (depends on message type)
* }
*
* The payload for each message type is as follows:
* TYPE DESCRIPTION PAYLOAD
* "con_ack" connect acknowledgment N/A
* "dis_ack" disconnect acknowledgment N/A
* "sub_ack" subscribe acknowledgment { s: subscription id, e: error code (empty string if no error) }
* "uns_ack" unsubscribe acknowledgment { s: subscription id }
* "pub" publish (i.e. sendToClient()) { t: topic, d: data, s: subscription id }
*/
OpenAjax.hub.IframePMContainer.prototype._sendMessage = function( type, payload )
{
var msg = JSON.stringify({
m: type,
i: this._internalID,
t: this._securityToken,
p: payload
});
this._postMessage( this._iframe.contentWindow, msg, this._partnerOrigin );
}
OpenAjax.hub.IframePMContainer.prototype._disconnect = function()
{
if ( this._connected ) {
this._connected = false;
this._iframe.style.visibility = "hidden";
// unsubscribe from all subs
for ( var sub in this._subs ) {
this._hub.unsubscribeForClient( this._container, this._subs[ sub ] );
}
this._subs = {};
}
}
OpenAjax.hub.IframePMContainer.prototype._invokeSecurityAlert = function( errorMsg )
{
try {
this._onSecurityAlert.call( this._scope, this._container, errorMsg );
} catch( e ) {
OpenAjax.hub._debugger();
this._log( "caught error from onSecurityAlert callback to constructor: " + e.message );
}
}
//------------------------------------------------------------------------------
OpenAjax.hub.IframePMContainer.PMListener = function()
{
this._containers = {};
if ( window.addEventListener ) {
window.addEventListener( "message",
OpenAjax.hub.IframeContainer.bind( this, this._receiveMessage ), false);
} else if ( window.attachEvent ) {
window.attachEvent( "onmessage",
OpenAjax.hub.IframeContainer.bind( this, this._receiveMessage ) );
}
}
/**
* Add an IframePMContainer to listen for messages. Returns an ID for the given
* container that is unique within the PAGE, not just the ManagedHub instance.
*/
OpenAjax.hub.IframePMContainer.PMListener.prototype.addContainer = function( container )
{
var id = container._id;
while ( this._containers[ id ] ) {
// a client with the specified ID already exists on this page;
// create a unique ID
id = ((0x7fff * Math.random()) | 0).toString(16) + "_" + id;
}
this._containers[ id ] = container;
return id;
}
OpenAjax.hub.IframePMContainer.PMListener.prototype.removeContainer = function( internalID )
{
delete this._containers[ internalID ];
// XXX TODO If no more postMessage containers, remove listener?
}
/**
* Complete connection between HubClient and Container identified by "id". This
* function is only called by the tunnel window.
*/
OpenAjax.hub.IframePMContainer.PMListener.prototype.connectFromTunnel = function( internalID, origin, securityToken, tunnelWindow )
{
if ( this._containers[ internalID ] ) {
this._containers[ internalID ].connect( origin, securityToken, tunnelWindow );
}
}
OpenAjax.hub.IframePMContainer.PMListener.prototype._receiveMessage = function( event )
{
// If the received message isn't JSON parseable or if the resulting
// object doesn't have the structure we expect, then just return.
try {
var msg = JSON.parse( event.data );
} catch( e ) {
return;
}
if ( ! this._verifyMsg( msg ) ) {
return;
}
if ( this._containers[ msg.i ] ) {
var container = this._containers[ msg.i ].receiveMessage( event, msg );
}
}
OpenAjax.hub.IframePMContainer.PMListener.prototype._verifyMsg = function( msg )
{
return typeof msg.m == "string" && typeof msg.i == "string" &&
"t" in msg && "p" in msg;
}
//------------------------------------------------------------------------------
OpenAjax.hub.IframePMHubClient = function( client, params )
{
// check communications protocol ID
this._checkProtocolID();
this._client = client;
this._onSecurityAlert = params.HubClient.onSecurityAlert;
this._scope = params.HubClient.scope || window;
this._id = OpenAjax.hub.IframePMHubClient.queryURLParam( "oahi" );
this._internalID = OpenAjax.hub.IframePMHubClient.queryURLParam( "oahj" ) || this._id;
this._securityToken = OpenAjax.hub.IframePMHubClient.queryURLParam( "oaht" );
this._tunnelURI = OpenAjax.hub.IframePMHubClient.queryURLParam( "oahu" );
OpenAjax.hub.IframePMContainer._pmCapabilities = OpenAjax.hub.IframePMHubClient.queryURLParam( "oahpm" );
// if any of the URL params are missing, throw WrongProtocol error
if ( ! this._id || ! this._securityToken || ! this._tunnelURI ) {
throw new Error( OpenAjax.hub.Error.WrongProtocol );
}
if ( OpenAjax.hub.IframePMHubClient.queryURLParam("oahd") ) OpenAjax.hub.enableDebug = true; // REMOVE ON BUILD
this._partnerOrigin = new RegExp( "^([a-zA-Z]+://[^/?#]+).*" ).exec( this._tunnelURI )[1];
// if "message" event doesn't support "origin" property, then save hostname
// (domain) also
if ( OpenAjax.hub.IframePMContainer._pmCapabilities.indexOf("d") != -1 ) {
this._partnerDomain = new RegExp( "^.+://([^:]+).*" ).exec( this._partnerOrigin )[1];
}
if ( params.HubClient.log ) {
var id = this._id;
var scope = this._scope;
var logfunc = params.HubClient.log;
this._log = function( msg ) {
logfunc.call( scope, "IframeHubClient::" + id + ": " + msg );
};
} else {
this._log = function() {};
}
this._connected = false;
this._subs = {};
this._subIndex = 0;
// if postMessage is synchronous, wrap in a setTimeout
if ( OpenAjax.hub.IframePMContainer._pmCapabilities.indexOf("s") == -1 ) {
this._postMessage = function( win, msg, origin ) {
win.postMessage( msg, origin );
}
} else {
this._postMessage = function( win, msg, origin ) {
setTimeout(
function() {
win.postMessage( msg, origin );
},
0
);
}
}
}
// communications protocol identifier
OpenAjax.hub.IframePMHubClient.protocolID = "openajax-2.0";
/*** OpenAjax.hub.HubClient interface implementation ***/
OpenAjax.hub.IframePMHubClient.prototype.connect = function( onComplete, scope )
{
if ( onComplete ) {
this._connectOnComplete = { cb: onComplete, sc: scope };
}
// start listening for messages
this._msgListener = OpenAjax.hub.IframeContainer.bind( this, this._receiveMessage );
if ( window.addEventListener ) {
window.addEventListener( "message", this._msgListener, false);
} else if ( window.attachEvent ) {
window.attachEvent( "onmessage", this._msgListener );
}
// create tunnel iframe, which will finish connection to container
var origin = window.location.protocol + "//" + window.location.host;
var iframe = document.createElement( "iframe" );
document.body.appendChild( iframe );
iframe.src = this._tunnelURI +
(this._tunnelURI.indexOf("?") == -1 ? "?" : "&") +
"oahj=" + encodeURIComponent( this._internalID ) +
"&oaht=" + this._securityToken +
"&oaho=" + encodeURIComponent( origin );
iframe.style.position = "absolute";
iframe.style.left = iframe.style.top = "-10px";
iframe.style.height = iframe.style.width = "1px";
iframe.style.visibility = "hidden";
this._tunnelIframe = iframe;
}
OpenAjax.hub.IframePMHubClient.prototype.disconnect = function( onComplete, scope )
{
this._connected = false;
if ( onComplete ) {
this._disconnectOnComplete = { cb: onComplete, sc: scope };
}
this._sendMessage( "dis", null );
}
OpenAjax.hub.IframePMHubClient.prototype.getPartnerOrigin = function()
{
if ( this._connected ) {
// remove port, if it is present
return new RegExp( "^([a-zA-Z]+://[^:]+).*" ).exec( this._partnerOrigin )[1];
}
return null;
}
OpenAjax.hub.IframePMHubClient.prototype.getClientID = function()
{
return this._id;
}
/*** OpenAjax.hub.Hub interface implementation ***/
OpenAjax.hub.IframePMHubClient.prototype.subscribe = function( topic, onData, scope, onComplete, subscriberData )
{
var subID = "" + this._subIndex++;
this._subs[ subID ] = { cb: onData, sc: scope, d: subscriberData, oc: onComplete };
this._sendMessage( "sub", { t: topic, s: subID } );
return subID;
}
OpenAjax.hub.IframePMHubClient.prototype.publish = function( topic, data )
{
this._sendMessage( "pub", { t: topic, d: data } );
}
OpenAjax.hub.IframePMHubClient.prototype.unsubscribe = function( subID, onComplete, scope )
{
// if no such subID, or in process of unsubscribing given ID, throw error
if ( ! this._subs[ subID ] || this._subs[ subID ].uns ) {
throw new Error( OpenAjax.hub.Error.NoSubscription );
}
this._subs[ subID ].uns = { cb: onComplete, sc: scope };
this._sendMessage( "uns", { s: subID } );
}
OpenAjax.hub.IframePMHubClient.prototype.isConnected = function()
{
return this._connected;
}
OpenAjax.hub.IframePMHubClient.prototype.getScope = function()
{
return this._scope;
}
OpenAjax.hub.IframePMHubClient.prototype.getSubscriberData = function( subID )
{
var sub = this._subs[ subID ];
if ( sub ) {
return sub.d;
}
throw new Error( OpenAjax.hub.Error.NoSubscription );
}
OpenAjax.hub.IframePMHubClient.prototype.getSubscriberScope = function( subID )
{
var sub = this._subs[ subID ];
if ( sub ) {
return sub.sc;
}
throw new Error( OpenAjax.hub.Error.NoSubscription );
}
/*** Helper Functions ***/
OpenAjax.hub.IframePMHubClient.queryURLParam = function( param )
{
var result = new RegExp( "[\\?&]" + param + "=([^&#]*)" ).exec( window.location.search );
if ( result ) {
return decodeURIComponent( result[1].replace( /\+/g, "%20" ) );
}
return null;
};
/*** Private Functions ***/
OpenAjax.hub.IframePMHubClient.prototype._checkProtocolID = function()
{
var partnerProtocolID = OpenAjax.hub.IframePMHubClient.queryURLParam( "oahpv" );
if ( partnerProtocolID != OpenAjax.hub.IframePMHubClient.protocolID ) {
throw new Error( OpenAjax.hub.Error.WrongProtocol );
}
}
OpenAjax.hub.IframePMHubClient.prototype._receiveMessage = function( event )
{
// If the received message isn't JSON parseable or if the resulting
// object doesn't have the structure we expect, then just return. This
// message might belong to some other code on the page that is also using
// postMessage for communication.
try {
var msg = JSON.parse( event.data );
} catch( e ) {
return;
}
if ( ! this._verifyMsg( msg ) ) {
return;
}
// check that security token and window source for incoming message
// are what we expect
if ( msg.i != this._internalID ) {
// this message might belong to an IframeContainer on this page
return;
} else if ( ! OpenAjax.hub.IframePMContainer.originMatches( this, event ) ||
msg.t != this._securityToken )
{
// security error -- incoming message is not valid
try{
this._onSecurityAlert.call( this._scope, this._client,
OpenAjax.hub.SecurityAlert.ForgedMsg );
} catch( e ) {
OpenAjax.hub._debugger();
this._log( "caught error from onSecurityAlert callback to constructor: " + e.message );
}
return;
}
this._log( "received message: [" + event.data + "]" );
switch ( msg.m ) {
// subscribe acknowledgement
case "sub_ack":
var subID = msg.p.s;
var onComplete = this._subs[ subID ].oc;
if ( onComplete ) {
try {
delete this._subs[ subID ].oc;
var scope = this._subs[ subID ].sc;
onComplete.call( scope, msg.p.s, msg.p.e == "", msg.p.e );
} catch( e ) {
OpenAjax.hub._debugger();
this._log( "caught error from onComplete callback to HubClient.subscribe(): " + e.message );
}
}
break;
// publish event
case "pub":
var subID = msg.p.s;
// if subscription exists and we are not in process of unsubscribing...
if ( this._subs[ subID ] && ! this._subs[ subID ].uns ) {
var onData = this._subs[ subID ].cb;
var scope = this._subs[ subID ].sc;
var subscriberData = this._subs[ subID ].d;
try {
onData.call( scope, msg.p.t, msg.p.d, subscriberData );
} catch( e ) {
OpenAjax.hub._debugger();
this._log( "caught error from onData callback to HubClient.subscribe(): " + e.message );
}
}
break;
// unsubscribe acknowledgement
case "uns_ack":
var subID = msg.p;
if ( this._subs[ subID ] ) {
var onComplete = this._subs[ subID ].uns.cb;
if ( onComplete ) {
try {
var scope = this._subs[ subID ].uns.sc;
onComplete.call( scope, subID, true );
} catch( e ) {
OpenAjax.hub._debugger();
this._log( "caught error from onComplete callback to HubClient.unsubscribe(): " + e.message );
}
}
delete this._subs[ subID ];
}
break;
// connect acknowledgement
case "con_ack":
this._connected = true;
if ( this._connectOnComplete ) {
var onComplete = this._connectOnComplete.cb;
var scope = this._connectOnComplete.sc;
try {
onComplete.call( scope, this._client, true );
} catch( e ) {
OpenAjax.hub._debugger();
this._log( "caught error from onComplete callback to HubClient.connect(): " + e.message );
}
delete this._connectOnComplete;
}
break;
// disconnect acknowledgment
case "dis_ack":
// stop listening for messages
if ( window.removeEventListener ) {
window.removeEventListener( "message", this._msgListener, false );
} else {
window.detachEvent( "onmessage", this._msgListener );
}
delete this._msgListener;
this._tunnelIframe.parentNode.removeChild( this._tunnelIframe );
delete this._tunnelIframe;
if ( this._disconnectOnComplete ) {
try {
var onComplete = this._disconnectOnComplete.cb;
var scope = this._disconnectOnComplete.sc;
onComplete.call( scope, this._client, true );
} catch( e ) {
OpenAjax.hub._debugger();
this._log( "caught error from onComplete callback to HubClient.disconnect(): " + e.message );
}
delete this._disconnectOnComplete;
}
break;
}
}
OpenAjax.hub.IframePMHubClient.prototype._verifyMsg = function( msg )
{
return typeof msg.m == "string" && "t" in msg && "p" in msg;
}
/**
* Send a string message to the associated container.
*
* The message is a JSON representation of the following object:
* {
* m: message type,
* i: client id,
* t: security token,
* p: payload (depends on message type)
* }
*
* The payload for each message type is as follows:
* TYPE DESCRIPTION PAYLOAD
* "con" connect N/A
* "dis" disconnect N/A
* "sub" subscribe { t: topic, s: subscription id }
* "uns" unsubscribe { s: subscription id }
* "pub" publish { t: topic, d: data }
*/
OpenAjax.hub.IframePMHubClient.prototype._sendMessage = function( type, payload )
{
var msg = JSON.stringify({
m: type,
i: this._internalID,
t: this._securityToken,
p: payload
});
this._postMessage( window.parent, msg, this._partnerOrigin );
}
/*
http://www.JSON.org/json2.js
2008-11-19
Public Domain.
NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
See http://www.JSON.org/js.html
This file creates a global JSON object containing two methods: stringify
and parse.
JSON.stringify(value, replacer, space)
value any JavaScript value, usually an object or array.
replacer an optional parameter that determines how object
values are stringified for objects. It can be a
function or an array of strings.
space an optional parameter that specifies the indentation
of nested structures. If it is omitted, the text will
be packed without extra whitespace. If it is a number,
it will specify the number of spaces to indent at each
level. If it is a string (such as '\t' or '&nbsp;'),
it contains the characters used to indent at each level.
This method produces a JSON text from a JavaScript value.
When an object value is found, if the object contains a toJSON
method, its toJSON method will be called and the result will be
stringified. A toJSON method does not serialize: it returns the
value represented by the name/value pair that should be serialized,
or undefined if nothing should be serialized. The toJSON method
will be passed the key associated with the value, and this will be
bound to the object holding the key.
For example, this would serialize Dates as ISO strings.
Date.prototype.toJSON = function (key) {
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
return this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z';
};
You can provide an optional replacer method. It will be passed the
key and value of each member, with this bound to the containing
object. The value that is returned from your method will be
serialized. If your method returns undefined, then the member will
be excluded from the serialization.
If the replacer parameter is an array of strings, then it will be
used to select the members to be serialized. It filters the results
such that only members with keys listed in the replacer array are
stringified.
Values that do not have JSON representations, such as undefined or
functions, will not be serialized. Such values in objects will be
dropped; in arrays they will be replaced with null. You can use
a replacer function to replace those with JSON values.
JSON.stringify(undefined) returns undefined.
The optional space parameter produces a stringification of the
value that is filled with line breaks and indentation to make it
easier to read.
If the space parameter is a non-empty string, then that string will
be used for indentation. If the space parameter is a number, then
the indentation will be that many spaces.
Example:
text = JSON.stringify(['e', {pluribus: 'unum'}]);
// text is '["e",{"pluribus":"unum"}]'
text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
// text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
text = JSON.stringify([new Date()], function (key, value) {
return this[key] instanceof Date ?
'Date(' + this[key] + ')' : value;
});
// text is '["Date(---current time---)"]'
JSON.parse(text, reviver)
This method parses a JSON text to produce an object or array.
It can throw a SyntaxError exception.
The optional reviver parameter is a function that can filter and
transform the results. It receives each of the keys and values,
and its return value is used instead of the original value.
If it returns what it received, then the structure is not modified.
If it returns undefined then the member is deleted.
Example:
// Parse the text. Values that look like ISO date strings will
// be converted to Date objects.
myData = JSON.parse(text, function (key, value) {
var a;
if (typeof value === 'string') {
a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
if (a) {
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+a[5], +a[6]));
}
}
return value;
});
myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
var d;
if (typeof value === 'string' &&
value.slice(0, 5) === 'Date(' &&
value.slice(-1) === ')') {
d = new Date(value.slice(5, -1));
if (d) {
return d;
}
}
return value;
});
This is a reference implementation. You are free to copy, modify, or
redistribute.
This code should be minified before deployment.
See http://javascript.crockford.com/jsmin.html
USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
NOT CONTROL.
*/
/*jslint evil: true */
/*global JSON */
/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
lastIndex, length, parse, prototype, push, replace, slice, stringify,
test, toJSON, toString, valueOf
*/
// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.
if (!this.JSON) {
JSON = {};
}
(function () {
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
if (typeof Date.prototype.toJSON !== 'function') {
Date.prototype.toJSON = function (key) {
return this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z';
};
String.prototype.toJSON =
Number.prototype.toJSON =
Boolean.prototype.toJSON = function (key) {
return this.valueOf();
};
}
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
gap,
indent,
meta = { // table of character substitutions
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
},
rep;
function quote(string) {
// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.
escapable.lastIndex = 0;
return escapable.test(string) ?
'"' + string.replace(escapable, function (a) {
var c = meta[a];
return typeof c === 'string' ? c :
'\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}) + '"' :
'"' + string + '"';
}
function str(key, holder) {
// Produce a string from holder[key].
var i, // The loop counter.
k, // The member key.
v, // The member value.
length,
mind = gap,
partial,
value = holder[key];
// If the value has a toJSON method, call it to obtain a replacement value.
if (value && typeof value === 'object' &&
typeof value.toJSON === 'function') {
value = value.toJSON(key);
}
// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.
if (typeof rep === 'function') {
value = rep.call(holder, key, value);
}
// What happens next depends on the value's type.
switch (typeof value) {
case 'string':
return quote(value);
case 'number':
// JSON numbers must be finite. Encode non-finite numbers as null.
return isFinite(value) ? String(value) : 'null';
case 'boolean':
case 'null':
// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.
return String(value);
// If the type is 'object', we might be dealing with an object or an array or
// null.
case 'object':
// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.
if (!value) {
return 'null';
}
// Make an array to hold the partial results of stringifying this object value.
gap += indent;
partial = [];
// Is the value an array?
if (Object.prototype.toString.apply(value) === '[object Array]') {
// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.
length = value.length;
for (i = 0; i < length; i += 1) {
partial[i] = str(i, value) || 'null';
}
// Join all of the elements together, separated with commas, and wrap them in
// brackets.
v = partial.length === 0 ? '[]' :
gap ? '[\n' + gap +
partial.join(',\n' + gap) + '\n' +
mind + ']' :
'[' + partial.join(',') + ']';
gap = mind;
return v;
}
// If the replacer is an array, use it to select the members to be stringified.
if (rep && typeof rep === 'object') {
length = rep.length;
for (i = 0; i < length; i += 1) {
k = rep[i];
if (typeof k === 'string') {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
} else {
// Otherwise, iterate through all of the keys in the object.
for (k in value) {
if (Object.hasOwnProperty.call(value, k)) {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
}
// Join all of the member texts together, separated with commas,
// and wrap them in braces.
v = partial.length === 0 ? '{}' :
gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
mind + '}' : '{' + partial.join(',') + '}';
gap = mind;
return v;
}
}
// If the JSON object does not yet have a stringify method, give it one.
if (typeof JSON.stringify !== 'function') {
JSON.stringify = function (value, replacer, space) {
// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.
var i;
gap = '';
indent = '';
// If the space parameter is a number, make an indent string containing that
// many spaces.
if (typeof space === 'number') {
for (i = 0; i < space; i += 1) {
indent += ' ';
}
// If the space parameter is a string, it will be used as the indent string.
} else if (typeof space === 'string') {
indent = space;
}
// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.
rep = replacer;
if (replacer && typeof replacer !== 'function' &&
(typeof replacer !== 'object' ||
typeof replacer.length !== 'number')) {
throw new Error('JSON.stringify');
}
// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.
return str('', {'': value});
};
}
// If the JSON object does not yet have a parse method, give it one.
if (typeof JSON.parse !== 'function') {
JSON.parse = function (text, reviver) {
// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.
var j;
function walk(holder, key) {
// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.
var k, v, value = holder[key];
if (value && typeof value === 'object') {
for (k in value) {
if (Object.hasOwnProperty.call(value, k)) {
v = walk(value, k);
if (v !== undefined) {
value[k] = v;
} else {
delete value[k];
}
}
}
}
return reviver.call(holder, key, value);
}
// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.
cx.lastIndex = 0;
if (cx.test(text)) {
text = text.replace(cx, function (a) {
return '\\u' +
('0000' + a.charCodeAt(0).toString(16)).slice(-4);
});
}
// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.
// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
if (/^[\],:{}\s]*$/.
test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.
j = eval('(' + text + ')');
// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.
return typeof reviver === 'function' ?
walk({'': j}, '') : j;
}
// If the text is not JSON parseable, then a SyntaxError is thrown.
throw new SyntaxError('JSON.parse');
};
}
})();
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<!--
Copyright 2006-2009 OpenAjax Alliance
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Hub Tunnel</title>
<script src="iframe.js"></script><script src="FIM.js"></script>
<script type="text/javascript">
function init()
{
if ( window.postMessage ) {
var internalID = OpenAjax.hub.IframePMHubClient.queryURLParam( "oahj" );
var origin = OpenAjax.hub.IframePMHubClient.queryURLParam( "oaho" );
var securityToken = OpenAjax.hub.IframePMHubClient.queryURLParam( "oaht" );
window.parent.parent.OpenAjax.hub.IframePMContainer._pmListener
.connectFromTunnel( internalID, origin, securityToken, window );
} else {
var commLib = new smash.CommLib( false,
window.parent.parent.smash.SEComm.instances );
}
}
</script>
</head>
<body onload="init();"></body>
</html>
...@@ -16,6 +16,15 @@ ...@@ -16,6 +16,15 @@
<link type="text/css" rel="stylesheet" href="css/ung.css" /> <link type="text/css" rel="stylesheet" href="css/ung.css" />
<link type="text/css" rel="stylesheet" href="css/jquery-ui.css" /> <link type="text/css" rel="stylesheet" href="css/jquery-ui.css" />
<link type="text/css" rel="stylesheet" href="css/gadget.css" /> <link type="text/css" rel="stylesheet" href="css/gadget.css" />
<script type="text/javascript" src="openajaxhub/src/OpenAjax-mashup.js"></script>
<script type="text/javascript" src="dojo/dojo/dojo.js"></script>
<script type="text/javascript" src="openajaxhub/src/containers/inline/inline.js"></script>
<script type="text/javascript" src="openajaxhub/src/containers/iframe/iframe.js"></script>
<script type="text/javascript" src="openajaxhub/src/containers/iframe/FIM.js"></script>
<script type="text/javascript" src="openajaxhub/src/containers/iframe/json2.js"></script>
<script type="text/javascript" src="openajaxhub/src/containers/iframe/crypto.js"></script>
<script type="text/javascript" src="js/jquery/jquery.js"></script> <script type="text/javascript" src="js/jquery/jquery.js"></script>
<script type="text/javascript" src="js/jquery/jquery-ui.js"></script> <script type="text/javascript" src="js/jquery/jquery-ui.js"></script>
<script type="text/javascript" src="js/tools.js"></script> <script type="text/javascript" src="js/tools.js"></script>
...@@ -23,11 +32,35 @@ ...@@ -23,11 +32,35 @@
<script type="text/javascript" src="js/theme.js"></script> <script type="text/javascript" src="js/theme.js"></script>
<script type="text/javascript" src="js/ung.js"></script> <script type="text/javascript" src="js/ung.js"></script>
<script type="text/javascript" src="js/loader.js"></script>
<link href="dojo/dijit/themes/tundra/tundra.css" rel="stylesheet">
<!--link href="dojo/dojo/resources/dojo.css" rel="stylesheet"-->
<link href="gadgets/layout/css/whitebox/whitebox.css" rel="stylesheet"/>
<link href="gadgets/layout/css/default/mashup.css" rel="stylesheet"/>
<script src="gadgets/layout/absolutelayout/absolutelayout.js"></script>
<script src="gadgets/layout/browser.js"></script>
<script src="gadgets/layout/gadgetsite.js"></script>
<link href="gadgets/layout/absolutelayout/absolutelayout.css" rel="stylesheet"/>
<script type="text/javascript" src="js/minimashup.js"></script>
<script>
mashupMaker.layout = new AbsoluteLayout();
</script>
<script src="gadgets/refimpldojo.js"></script>
<script type="text/javascript" src="gadgets/toolbar.js"></script>
<script>
var baseURL = window.location.protocol + "//" + window.location.host + window.location.pathname.substring( 0, window.location.pathname.lastIndexOf("/")+1 );
djConfig = { usePlainJson: true, parseOnLoad: false, isDebug: false,
modulePaths: { "nomad": baseURL + "nomad"} };
</script>
<link rel="icon" type="image/x-icon"
href="images/ung/favicon.ico" />
<link rel="shortcut icon" type="image/x-icon" <link rel="icon" type="image/x-icon" href="images/ung/favicon.ico" />
href="images/ung/favicon.ico" /> <link rel="shortcut icon" type="image/x-icon" href="images/ung/favicon.ico" />
<script type="text/javascript"> <script type="text/javascript">
...@@ -348,7 +381,16 @@ ...@@ -348,7 +381,16 @@
</div> </div>
<div id="add_new_gadget_link"> <div id="add_new_gadget_link">
<a title="Add gadgets" id="add-gadgets" href="..."><span>Add gadgets</span></a> <div id="mashupToolbarContainer">
<div id="mashupToolbar" dojoType="dijit.Toolbar">
<div id="searchResults" dojoType="dijit.form.DropDownButton" showLabel="true" iconClass="nomadToolbarIcon nomadIconSearchResults">
<span>Add gadgets</span>
<div id="searchResultsMenu" dojoType="nomad.widget.PaletteMenu">
</div>
</div>
</div>
</div>
<!--a title="Add gadgets" id="add-gadgets"><span>Add gadgets</span></a-->
</div> </div>
</div> </div>
...@@ -363,7 +405,10 @@ ...@@ -363,7 +405,10 @@
<div id="page_wrapper"> <div id="page_wrapper">
<!-- No gadgets message --> <!-- No gadgets message -->
<div> <div id="gadget_container">
<div id="__replaceablecontent__">
<!-- Here is where the gadgets appear-->
</div>
<h3> Your tab is empty. </h3> <h3> Your tab is empty. </h3>
<h4> Please use link (<b>Add gadgets</b>) to prepare it yourself. </h4> <h4> Please use link (<b>Add gadgets</b>) to prepare it yourself. </h4>
</div> </div>
......
[buildout] [buildout]
extensions = mr.developer extensions = mr.developer
auto-checkout = xinha auto-checkout = dojo xinha
parts = jsquery jsquery-ui parts = jsquery jsquery-ui
sources-dir = sources-dir =
[sources] [sources]
xinha = svn http://svn.xinha.org/tags/0.96.1 path=UNGProject egg=false xinha = svn http://svn.xinha.org/tags/0.96.1 path=UNGProject egg=false
dojo = svn http://svn.dojotoolkit.org/src/branches/1.6/ path=UNGProject egg=false
[jsquery] [jsquery]
recipe = hexagonit.recipe.download recipe = hexagonit.recipe.download
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment