blob: a288b554dba5637c88838088b323e06e81c240e4 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
dojo.require("dijit.form.Button");
dojo.require("dijit.TitlePane");
dojo.require("dojox.xml.parser");
dojo.require("dijit.Dialog");
/** -----------------------------------------------------------------------------------------
* Navigation functions
*
* Available commands appear on left menu. Clicking on any command makes a corresponding
* highlighted section to appear at a fixed position. These command windows are identified
* and these identifiers are used to make only one of the section to be visible while
* hiding the rest.
*
* The jest.html document will use these same identifiers in their command section.
* ------------------------------------------------------------------------------------------
*
* The identifiers of every element to be shown or hidden.
* --------------------------------------------------------------------------------------- */
var menuIds = new Array('home', 'deploy', 'find', 'query', 'domain', 'properties');
/**
* Opening a menu implies clearing the canvas, hiding the current menu item and making
* the given menu identifier visible.
*
* @param id menu section identifier
*/
function openMenu(/*HTML element id*/id) {
clearElement('canvas');
switchId(id, menuIds);
document.location = "#top";
}
/**
* Show the division identified by the given id, and hide all others
*/
function switchId(/*string*/id, /*string[]*/highlightedSections) {
for (var i=0; i < highlightedSections.length; i++){
var section = document.getElementById(highlightedSections[i]);
if (section != null) {
section.style.display = 'none';
}
}
var div = document.getElementById(id);
if (div != null) {
div.style.display = 'block';
div.focus();
}
}
/** -----------------------------------------------------------------------------------------
* Generic Command handling functions
*
* All available JEST commands are enumerated by their qualifiers and arguments.
*
* Specification of a JEST Command requires the following:
* a) a name
* b) zero or more Qualifiers. Qualifiers are not ordered. All Qualifiers are optional.
* c) zero or more Arguments. Arguments are ordered. Some Arguments can be mandatory.
*
* Command, Qualifier and Argument are 'objects' -- in a curious JavaScript sense. They
* are responsible to harvest their state value from the HTML element such as a input
* text box or a check box etc. The aim of harvesting the values is to construct a
* relative URI that can be passed to the server via a HTTP get request.
*
* jest.html attaches change event handlers to all input elements and the on change
* event handler updates the URI.
*
* The complexity is added because some commands may take variable number of arguments.
* Hence the input text boxes to enter arbitrary number of key-value pair can be created
* or removed by the user.
*
* A lot of implicit naming convention for the element identifiers are used in this
* script. These naming conventions are documented in jest.html.
* --------------------------------------------------------------------------------------- */
var findCommand = new Command('find',
new Array( //
new Qualifier("plan", "plan", false),
new Qualifier("format", "format", false),
new Qualifier("ignoreCache", "ignoreCache", true)
),
new Array( // Order of arguments is significant
new Argument("type", "type", true),
new Argument("pk", null, true)
));
var queryCommand = new Command('query',
new Array( // Qualifiers are not ordered
new Qualifier("plan", "plan", false),
new Qualifier("format", "format", false),
new Qualifier("single", "single", true),
new Qualifier("named", "named", true),
new Qualifier("ignoreCache", "ignoreCache", true)
),
new Array( // Order of arguments is significant
new Argument("q", "q", true)
));
var domainCommand = new Command('domain', new Array(), new Array());
var propertiesCommand = new Command('properties', new Array(), new Array());
var commands = new Array(findCommand, queryCommand, domainCommand, propertiesCommand);
/** -----------------------------------------------------------------------------------------
* Creates a relative URI for the given commandName by reading the content of the given HTML
* element and its children.
* The URI is written to the HTML anchor element identified by {commandName} + '.uri'.
*
* @param commandName name of the command. All source HTML element are identified by this
* command name as prefix.
* --------------------------------------------------------------------------------------- */
function toURI(commandName) {
var command = null;
switch (commandName) {
case 'find' : command = findCommand; break;
case 'query' : command = queryCommand; break;
case 'domain' : command = domainCommand; break;
case 'properties' : command = propertiesCommand; break;
}
if (command != null)
command.toURI();
}
/** -----------------------------------------------------------------------------------------
* Adds columns to the given row for entering a variable argument key-value pair.
* A remove button is created as well to remove the row.
* A new row is created to invoke this function again to add another new row.
*
* @param rowIdPrefix a string such as query.vararg or find.vararg
* @param index a integer appended to the prefix to identify the row such as query.vararg.3
* @param message the label on the new button
* --------------------------------------------------------------------------------------- */
function addVarArgRow(rowIdPrefix, index, message) {
var rowId = rowIdPrefix + '.' + index;
var row = document.getElementById(rowId);
clearElement(rowId);
// New input column for parameter name. Element id is rowId + '.key'
var argNameColumn = document.createElement('td');
var argNameInput = document.createElement('input');
argNameInput.setAttribute('type', 'text');
argNameInput.setAttribute('id', rowId + '.key');
argNameInput.setAttribute('onblur', 'javascript:toURI("' + rowIdPrefix.split('.')[0] + '");');
argNameColumn.appendChild(argNameInput);
// New input column for parameter value. Element id is rowId + '.value'
var argValueColumn = document.createElement('td');
var argValueInput = document.createElement('input');
argValueInput.setAttribute('type', 'text');
argValueInput.setAttribute('id', rowId + '.value');
argValueInput.setAttribute('onblur', 'javascript:toURI("' + rowIdPrefix.split('.')[0] + '");');
argValueColumn.appendChild(argValueInput);
// New column for remove button. Will remove this row.
var removeColumn = document.createElement('td');
var removeColumnButton = document.createElement('button');
removeColumnButton.innerHTML = 'Remove';
removeColumnButton.setAttribute('onclick', 'javascript:removeVarArgRow("' + rowId + '");');
removeColumn.appendChild(removeColumnButton);
// Empty column as the first column
var emptyColumn = document.createElement('td');
emptyColumn.appendChild(document.createTextNode("Key-Value pair"));
// Add the empty column, two input columns and remove button to the current row
row.appendChild(emptyColumn);
row.appendChild(argNameColumn);
row.appendChild(argValueColumn);
row.appendChild(removeColumn);
// create a new row with a single column to add another parameter.
// This new row looks similar to the original state of the modified column
var newIndex = index + 1;
var newRowId = rowIdPrefix + '.' + newIndex;
var newRow = document.createElement('tr');
newRow.setAttribute('id', newRowId);
var newColumn = document.createElement('td');
var addColumnButton = document.createElement('button');
addColumnButton.innerHTML = message;
addColumnButton.setAttribute('onclick', 'javascript:addVarArgRow("' + rowIdPrefix + '",' + newIndex + ',"'
+ message + '");');
newColumn.appendChild(addColumnButton);
newRow.appendChild(newColumn);
row.parentNode.appendChild(newRow);
}
/** -----------------------------------------------------------------------------------------
* Removes a variable argument row.
* The URI is updated.
*
* @param rowId the identifier of the row to be removed. The identifier follows the
* naming convention of the variable argument row i.e. {commandName}.varargs.{n}
* --------------------------------------------------------------------------------------- */
function removeVarArgRow(rowId) {
var row = document.getElementById(rowId);
row.parentNode.removeChild(row);
toURI(rowId.split('.')[0]);
}
/** -----------------------------------------------------------------------------------------
* Definition of Command as a JavScript object.
*
* @param name name of the command. Used to identify the command, or identify input elements.
* @param qualifiers zero or more Qualifier objects
* @param arguments zero or more Argument objects
*
*
* --------------------------------------------------------------------------------------- */
function Command(name, qualifiers, arguments) {
this.name = name;
this.qualifiers = qualifiers;
this.arguments = arguments;
this.toURI = Command_toURI;
}
/** -----------------------------------------------------------------------------------------
* Harvests the input HTML elements for a commands qualifiers and arguments and builds up
* a URI.
* Uses several naming convention that are documented in jest.html to identify the input
* elements.
* The naming of the function and its capitalization follows JavaScript convention for it
* to behave as a faux object method.
*
* @returns a string form of URI
* --------------------------------------------------------------------------------------- */
function Command_toURI() {
var uri = this.name; // command name is same as URI name -- need not be
var iformat = 'xml'; // default response format
for (var i = 0; i < this.qualifiers.length; i++) {
var id = this.name + '.' + this.qualifiers[i].name;
var inode = document.getElementById(id);
var path = this.qualifiers[i].toURI(inode);
if (path != null) {
uri = uri.concat('/').concat(path);
if (this.qualifiers[i].key == 'format') {
iformat = getNodeValue(inode);
}
}
}
var args = "";
var invalid = null;
for (var i = 0; i < this.arguments.length; i++) {
var id = this.name + '.' + this.arguments[i].name;
var inode = document.getElementById(id);
var arg = this.arguments[i].toURI(inode);
if (arg != null) {
args = args.concat(args.length == 0 ? '' : '&').concat(arg);
} else if (this.arguments[i].mandatory) {
invalid = 'Missing mandatory ' + this.arguments[i].name + ' argument';
}
}
// Variable argument processing
var children = document.getElementById(this.name + '.command').getElementsByTagName('tr');
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (isVarArgRow(child, this.name)) {
var varargRow = child;
var pair = varargRow.getElementsByTagName('input');
var key = getNodeValue(pair[0]);
var value = getNodeValue(pair[1]);
if (key != null && value != null) {
args = args.concat(args.length == 0 ? '' : '&').concat(key).concat('=').concat(escape(value));
}
}
}
if (args.length > 0) {
uri = uri.concat('?').concat(args);
}
// update the command URI element
console.log("New URI is " + uri);
var uriNode = document.getElementById(this.name + ".uri");
var uriCtrl = document.getElementById(this.name + ".execute");
if (invalid == null) {
uriNode.setAttribute('class', 'url');
uriNode.innerHTML = uri;
var contentType = getContentTypeForCommand(this.name);
uriCtrl.setAttribute('onclick',
'javascript:render("'
+ uri
+ '", "canvas"' + ',"'
+ contentType + '","'
+ iformat + '");');
uriCtrl.style.display = 'inline';
} else {
uriNode.setAttribute('class', 'url-invalid');
uriNode.innerHTML = uri + ' (' + invalid + ')';
uriCtrl.style.display = 'none';
uriCtrl.removeAttribute('onclick');
}
return uri;
}
function getContentTypeForCommand(/*string*/ commandName) {
if (commandName == 'find' || commandName == 'query') return 'instances';
if (commandName == 'domain') return 'domain';
if (commandName == 'properties') return 'properties';
}
/** -----------------------------------------------------------------------------------------
* Definition of Qualifier JavaScript object.
*
* A qualifier decorates a Command. For example, a query command can be decorated with
* 'single' qualifier to return a single result. A 'plan' qualifier can decorate a find or
* query command to use a named fetch plan etc.
* A qualifier is encoded in the path segment of JEST URI followed by the
* command name e.g. /query/single or /find/plan=myFetchPlan etc.
*
*
* @param name a name when prefixed by the name of the command identifies the HTML element
* that carries the value of this qualifier.
* @param key the identifier for this qualifier used in the JEST URI
* @param isBoolean is this qualifier carries a boolean value?
*
* @returns {Qualifier}
* -------------------------------------------------------------------------------------- */
function Qualifier(name, key, isBoolean) {
this.name = name;
this.key = key;
this.isBoolean = isBoolean;
this.toURI = Qualifier_toURI;
}
/** -----------------------------------------------------------------------------------------
* Generates a string for this qualifier to appear in the command URI.
*
* A qualifier is translated to a URI fragment as a key=value pair. A boolean
* qualifier is translated to a URI fragment only if the corresponding HTML
* element is checked. And even then, only the key is sufficient.
*
* @returns a string
* --------------------------------------------------------------------------------------- */
function Qualifier_toURI(inode) {
var value = getNodeValue(inode);
if (isEmpty(value) || (this.isBoolean && !inode.checked)) {
return null;
}
if (this.isBoolean) {
return this.key + (value == 'true' ? '' : '=' + value);
}
return this.key + '=' + value;
}
/** -----------------------------------------------------------------------------------------
* Definition of Argument JavaScript object.
*
* An argument for a command. Some argument can be mandatory. <br>
* Each argument is encoded as key=value pair in JEST URI in query parameters
* separated by '&' character.
*
* @param name a name when prefixed by the name of the command identifies the HTML element
* that carries the value of this argument.
* @param key the identifier for this argument used in the JEST URI
* @param mandatory is this argument mandatory?
*
* @returns {Argument}
* -------------------------------------------------------------------------------------- */
function Argument(name, key, mandatory) {
this.name = name;
this.key = key;
this.mandatory = mandatory;
this.toURI = Argument_toURI;
}
/** -----------------------------------------------------------------------------------------
* Generates a string for this argument to appear in the command URI.
*
* An argument is translated to a URI fragment as a key=value pair.
*
* @returns a string
* --------------------------------------------------------------------------------------- */
function Argument_toURI(inode) {
var value = getNodeValue(inode);
if (isEmpty(value))
return null;
if (this.key == null) {
return value;
} else {
return this.key + '=' + value;
}
}
/** ----------------------------------------------------------------------------------------
* Utility functions
* ------------------------------------------------------------------------------------- */
/**
* Trims a String.
*/
String.prototype.trim = function () {
return this.replace(/^\s*/, "").replace(/\s*$/, "");
};
/**
* Affirms if the given string appears at the start of this string.
*/
String.prototype.startsWith = function(s) {
return this.indexOf(s, 0) == 0;
};
/**
* Affirms if the given string is null or zero-length or trimmed to zero-length.
*
* @param str a string to test for 'emptiness'
* @returns {Boolean}
*/
function isEmpty(str) {
return str == null || str.length == 0 || str.trim().length == 0;
}
/**
* Gets the string value of the given node.
*
* @param inode a HTML element
* @returns null if given node is null or its value is an empty string.
* Otherwise, trimmed string.
*/
function getNodeValue(inode) {
if (inode == null) {
return null;
}
if (isEmpty(inode.value)) {
return null;
} else {
return inode.value.trim();
}
}
/**
* Affirms if the given HTML row element represents a variable argument row
* for the given commandName.
* @param row a HTML row element
* @param commandName name of a command
*
* @returns true if row identifer starts with commandName + '.vararg.'
*/
function isVarArgRow(row, commandName) {
return row != null && row.nodeName != '#text'
&& row.hasAttribute("id")
&& row.getAttribute("id").startsWith(commandName + '.vararg.');
}
/**
* Removes all children of the given element.
*/
function clearElement(/* HTML element id */ id) {
var element = dojo.byId(id);
if (element == null) return;
while (element.childNodes.length > 0) {
element.removeChild(element.firstChild);
}
}
/**
* Prints and alerts with the given message string.
* @param message a warning message
*/
function warn(/*string*/message) {
console.log(message);
alert(message);
}
/** -----------------------------------------------------------------------------------------
* Rendering functions
*
* Several rendering functions to display server response in the canvas section.
* The server responds in following formats :
* 1a) XML
* 1b) JSON
* The response can be rendered in following display modes :
* 2a) raw XML text
* 2b) HTML table
* 2c) Dojo Widgets
* 2d) JSON as text
* The content can be one of the following
* 3a) instances from find() or query() command
* 3b) domain model from domain() command
* 3c) configuration properties from properties() command
* 3d) error stack trace
*
* Thus there are 2x4x4 = 32 possible combinations. However, response format for
* certain content type is fixed e.g. server always sends domain/properties/error
* stack trace in XML format. Moreover certain content-response format-display mode
* combinations are not supported. The following matrix describes the supported
* display modes for content-response format combinations.
* [y] : supported
* [x] : not supported
* n/a : not available
* --------------------------------------------------------------------------------
* Response Content
* --------------------------------------------------------------------------------
* instances domain properties error
* --------------------------------------------------------------------------------
* XML [y] XML text [y] XML text [y] XML text [x] XML text
* [y] HTML [y] HTML [y] HTML [y] HTML
* [y] Dojo Widgets [y] Dojo Widgets [x] Dojo Widgets [x] Dojo Widgets
* [x] JSON [x] JSON [x] JSON [x] JSON
*
* JSON [x] XML text n/a n/a n/a
* [x] HTML Table n/a n/a [y] HTML
* [x] Dojo Widgets n/a n/a n/a
* [y] JSON n/a n/a n/a
* ---------------------------------------------------------------------------------
* The above matrix shows that there are 10 supported combinations.
* ------------------------------------------------------------------------------------- */
var supportedResponseFormats = new Array('xml', 'json');
var supportedContentTypes = new Array('instances', 'domain', 'properties', 'error');
var renderingCombo = new Array(
/*XML*/ new Array(new Array('xml', 'dojo', 'html'), // instances
new Array('xml', 'dojo', 'html'), // domain
new Array('xml','html'), // properties
new Array('html')), // error
/*JSON*/new Array(new Array('json'), // instances
new Array('xml', 'dojo', 'html'), // domain
new Array('xml', 'html'), // properties
new Array('html'))); // error
/**
* Gets the ordinal index of the given key in the given array
* @param array an array of enumerated strings.
* @param key a key to search for.
*
* @returns {Number} 0-based index of the key in the array.
*/
function getOrdinal(/*Array*/array, /*string*/key) {
for (var i = 0; i < array.length; i++) {
if (key == array[i]) return i;
}
console.log(key + " is not a valid enum in " + array);
return 0;
}
/**
* Gets ordinal number for enumerated response format.
*
* @param iformat response format. one of 'xml', 'json'
*
* @returns {Number} ordinal number 0 for 'xml',
*/
function getOrdinalResponseFormat(/*enum*/iformat) {
return getOrdinal(supportedResponseFormats, iformat);
}
/**
* Gets ordinal number of the enumerated content types.
* @param contentType type of content. One of 'instances', 'domain', 'properties', 'error'
* @returns
*/
function getOrdinalContentType(/*enum*/contentType) {
return getOrdinal(supportedContentTypes, contentType);
}
/**
* Gets the array of enumerated strings of display format for the given response format and content type.
* @param iformat
* @param contentType
* @returns
*/
function getSupportedDisplayModes(/*enum*/iformat,/*enum*/contentType) {
var displayModes = renderingCombo[getOrdinalResponseFormat(iformat)][getOrdinalContentType(contentType)];
if (displayModes == null) {
warn("No display format for response format [" + iformat + "] and content type [" + contentType + "]");
}
return displayModes;
}
/**
* Render the response from the given URI on to the given HTML element identified by targetId.
*
* The URI is requested from server in an asynchronous call. Then the server response is rendered
* in all supported display format but only the given display format is made visible.
*
* @param uri the request URI
* @param targetId identifier of the HTML element that will display the data
* @param contentType type of the content, one of 'instances', 'domain', 'properties', 'error'
* @param iformat format of the server response, one of 'xml' or 'json'
* @param oformat format for display, one of 'xml', 'json', 'dojo', 'html'
*
* The combination of iformat-contentType-oformat must be compatiable as described in above matrix.
*
* @returns {Boolean} to prevent default event propagation
*/
function render(/* string */ uri, /* id */ targetId, /* enum */contentType, /* enum */iformat) {
var targetNode = dojo.byId(targetId);
clearElement(targetId);
//The parameters to pass to xhrGet, the url, how to handle it, and the callbacks.
var xhrArgs = {
url: uri,
handleAs: (iformat == 'json' && contentType == 'instances') ? 'json' : 'xml',
preventCache: contentType == 'instances',
timeout : 1000,
load: function(data, ioargs) {
if (ioargs.xhr.status == 200) { // HTTP OK
var newDivs = null;
if (iformat == 'json') {
newDivs = renderJSONResponse(data, contentType);
} else {
newDivs = renderXMLResponse(data, contentType);
}
var displayModes = getSupportedDisplayModes(iformat, contentType);
targetNode.appendChild(createDisplayModeControl(displayModes));
for (var i = 0; i < newDivs.length; i++) {
targetNode.appendChild(newDivs[i]);
}
} else {
var errorDiv = renderErrorFromXMLAsHTML(data, ioargs);
targetNode.appendChild(errorDiv);
}
},
error: function(error, ioargs) {
var errorDiv = renderErrorFromXMLAsHTML(ioargs.xhr.responseXML, ioargs);
targetNode.appendChild(errorDiv);
}
};
//Call the asynchronous xhrGet
var deferred = dojo.xhrGet(xhrArgs);
return false;
}
/**
* Creates a table with radio buttons for supported display modes.
*
* @param displayModes name of supported display modes.
*
* @returns an unattached HTML table
*/
function createDisplayModeControl(displayModes) {
var displayMode = document.createElement("table");
displayMode.style.width = "100%";
var tr = document.createElement("tr");
displayMode.appendChild(tr);
// append columns. 0-th an dfirst columns are descriptive texts.
var caption = document.createElement("th");
caption.style.width = (100 - displayModes.length*12) + '%';
caption.appendChild(document.createTextNode("JEST Response"));
tr.appendChild(caption);
for (var i = 0; i < displayModes.length; i++) {
var mode = displayModes[i];
var modeColumn = document.createElement("th");
modeColumn.style.width = "12%";
tr.appendChild(modeColumn);
var radio = document.createElement("input");
radio.setAttribute("type", "radio");
radio.setAttribute("value", mode);
radio.setAttribute("name", "display.mode");
if (i == 0) radio.setAttribute("checked", "checked");
radio.setAttribute('onchange', createModeSwitchFunction(mode, displayModes));
modeColumn.appendChild(radio);
modeColumn.appendChild(document.createTextNode(mode.toUpperCase()));
}
return displayMode;
}
/**
* Creates a string for javascript function call to switch between display modes
* @param visible the visible display mode
* @param all available display modes
* @returns {String} a event handler function string
*/
function createModeSwitchFunction(/* string */ visible, /* string[] */ all) {
var array = '';
for (var i = 0; i < all.length; i++) {
if (all[i] != visible) {
array = array + (array.length == 0 ? '' : ', ') + '"display.mode.' + all[i] + '"';
}
}
return 'javascript:switchId("display.mode.' + visible+ '", [' + array + '])';
}
/**
* The big switch for rendering all content types received as XML DOM.
* Finds out the supported display format for given content type and renders each display format
* in separate divs. The div corresponding to the given display format is made visible, and others
* are hidden. None of the divs are attached to the main document.
*
* @param dom server response as a XML DOM document
* @param contentType enumerated content type. One of 'instances', 'domain', 'properties' or 'error'
*
* @returns an array of unattached divs only one of which is visible.
*/
function renderXMLResponse(/*XML DOM*/dom, /*enum*/contentType) {
var displayModes = getSupportedDisplayModes('xml', contentType);
var newDivs = new Array(displayModes.length);
for (var i = 0; i < displayModes.length; i++) {
var displayMode = displayModes[i];
if (displayMode == 'xml') {
newDivs[i] = renderXMLasXML(dom);
} else if (contentType == 'instances') {
if (displayMode == 'html') {
newDivs[i] = renderInstancesFromXMLAsHTML(dom);
} else if (displayMode == 'dojo') {
newDivs[i] = renderInstancesFromXMLAsDojo(dom);
}
} else if (contentType == 'domain') {
if (displayMode == 'html') {
newDivs[i] = renderDomainFromXMLAsHTML(dom);
} else if (displayMode == 'dojo') {
newDivs[i] = renderDomainFromXMLAsDojo(dom);
}
} else if (contentType == 'properties') {
newDivs[i] = renderPropertiesFromXMLAsHTML(dom);
}
newDivs[i].style.display = (i == 0 ? 'block' : 'none');
newDivs[i].setAttribute("id", "display.mode." + displayMode);
}
return newDivs;
}
/**
* Renders the given instance data in the format of a XML DOM document into a set of Dojo widgets
* inside a div element.
*
* @param data the root node of a XML document containing instances data
*
* @returns an unattached div containing a set of dojo widgets
*/
function renderInstancesFromXMLAsDojo(/* XML DOM*/data) {
var target = document.createElement('div');
var panels = new Array();
dojo.query("instance", data).forEach(function(item, index) {
var panel = createInstanceDojoWidget(item);
panels[index] = panel;
});
// assign random location to each panel and add them to canvas
dojo.forEach(panels, function(item, index) {
var domNode = item.domNode;
domNode.style.width = "200px";
domNode.style.position = "absolute";
domNode.style.left = 100 + (index % 5)*300 + "px";
domNode.style.top = 100 + Math.floor(index / 5)*200 +"px";
target.appendChild(domNode);
});
return target;
}
/**
* Renders given DOM for metamodel as dojo widgets.
*
* @param data XML DOM for domain model.
* @returns a HTML div
*/
function renderDomainFromXMLAsDojo(/*XML DOM*/data) {
var target = document.createElement('div');
var panels = new Array();
dojo.query("entity, embeddable, mapped-superclass", data)
.forEach(function(item, index) {
var panel = createEntityTypeDojoWidget(item);
panels[index] = panel;
});
// assign random location to each panel and add them to canvas
dojo.forEach(panels, function(item, index) {
var domNode = item.domNode;
domNode.style.width = "200px";
domNode.style.position = "absolute";
domNode.style.left = 100 + (index % 5)*300 + "px";
domNode.style.top = 100 + Math.floor(index / 5)*200 +"px";
target.appendChild(domNode);
});
return target;
}
/**
* Renders given XML DOM for instances to HTML tables.
* *** NOT IMPLEMENTED
*
* @param data XML DOM for list of instances. All instanes may not belong to same entity.
* @returns a div with zero or more tables.
*/
function renderInstancesFromXMLAsHTML(/* XML DOM */data) {
return unimplemented("Rendering Instances as HTML is not implemented");
}
/**
* Renders given XML DOM for domain model to HTML tables.
* *** NOT IMPLEMENTED
*
* @param data XML DOM for list of domain model.
* @returns a div with zero or more tables.
*/
function renderDomainFromXMLAsHTML(/* XML DOM */data) {
return unimplemented("Rendering Domain as HTML is not implemented");
}
function unimplemented(/*string*/message) {
var empty = document.createElement('img');
empty.setAttribute("src", "images/underconstruction.jpg");
empty.setAttribute("alt", message);
return empty;
}
/**
* Renders configration (name-value pairs) in a HTML table.
*
* @param data XML DOM for name-value pair properties.
* @returns a HTML table
*/
function renderPropertiesFromXMLAsHTML(/* XML DOM */data) {
var table = document.createElement("table");
var caption = document.createElement("caption");
caption.innerHTML = "Configuration Properties";
table.appendChild(caption);
dojo.query("property", data)
.forEach(function(item, index) {
var row = document.createElement("tr");
row.className = index%2 == 0 ? 'even' : 'odd';
var key = document.createElement("td");
var val = document.createElement("td");
key.innerHTML = item.getAttribute("name");
val.innerHTML = item.getAttribute("value");
row.appendChild(key);
row.appendChild(val);
table.appendChild(row);
}
);
return table;
}
/**
* Renders error message in HTML
*
* @param data XML DOM containing error description
*
* @returns a div element with error details
*/
function renderErrorFromXMLAsHTML(/*response as XML DOM*/responseXML, ioargs) {
var div = document.createElement("div");
var ecode = document.createElement("h3");
var header = document.createElement("p");
var msg = document.createElement("p");
var trace = document.createElement("pre");
ecode.setAttribute("class", "error-header");
header.setAttribute("class", "error-message");
msg.setAttribute("class", "error-message");
var serverError = responseXML.documentElement;
ecode.innerHTML = "HTTP Error " + ioargs.xhr.status;
header.innerHTML = dojox.xml.parser.textContent(serverError.getElementsByTagName("error-header").item(0));
msg.innerHTML = dojox.xml.parser.textContent(serverError.getElementsByTagName("error-message").item(0));
trace.innerHTML = dojox.xml.parser.textContent(serverError.getElementsByTagName("stacktrace").item(0));
div.appendChild(ecode);
div.appendChild(header);
div.appendChild(msg);
div.appendChild(trace);
return div;
}
/**
* Creates a dojo Title Pane from a DOM instance node. The pane has the instance
* id as its title. The content is a table with name and value of each attribute
* in each row. Multi-cardinality values are in separate row without the attribute
* name repeated except the first row.
*
* @param instanceNode an XML instance node
*
* @returns dojo widget for a single instance
*/
function createInstanceDojoWidget(/*XML node*/instanceNode) {
var instanceTable = document.createElement("table");
dojo.query('id, basic, enum, version', instanceNode)
.forEach(function(item) {
var attrRow = document.createElement("tr");
var nameColumn = document.createElement("td");
var valueColumn = document.createElement("td");
nameColumn.className = item.nodeName.toLowerCase(); /* May be cross-browser trouble */
nameColumn.innerHTML = item.getAttribute("name");
valueColumn.innerHTML = dojox.xml.parser.textContent(item);
attrRow.appendChild(nameColumn);
attrRow.appendChild(valueColumn);
instanceTable.appendChild(attrRow);
}
);
dojo.query('one-to-one, many-to-one', instanceNode)
.forEach(function(item) {
var attrRow = document.createElement("tr");
var nameColumn = document.createElement("td");
var valueColumn = document.createElement("td");
nameColumn.className = item.nodeName.toLowerCase(); /* May be cross-browser trouble */
nameColumn.innerHTML = item.getAttribute("name");
dojo.query('ref, null', instanceNode)
.forEach(function(ref) {
valueColumn.innerHTML = ref.nodeName == 'null' ? 'null' : ref.getAttribute("id");
valueColumn.className = ref.nodeName.toLowerCase();
attrRow.appendChild(nameColumn);
attrRow.appendChild(valueColumn);
instanceTable.appendChild(attrRow);
});
});
dojo.query('one-to-many, many-to-many', instanceNode).forEach(function(item) {
var attrRow = document.createElement("tr");
var nameColumn = document.createElement("td");
var valueColumn = document.createElement("td");
nameColumn.className = item.nodeName.toLowerCase(); /* May be cross-browser trouble */
nameColumn.innerHTML = item.getAttribute("name");
var refs = item.getElementsByTagName("ref");
for (var i = 0; i < refs.length; i++) {
if (i == 0) {
valueColumn.innerHTML = refs[i].getAttribute("id");
valueColumn.className = refs[i].nodeName.toLowerCase();
attrRow.appendChild(nameColumn);
attrRow.appendChild(valueColumn);
instanceTable.appendChild(attrRow);
} else {
var attrRow = document.createElement("tr");
var nameColumn = document.createElement("td"); // empty column
var valueColumn = document.createElement("td");
valueColumn.className = refs[i].nodeName.toLowerCase();
valueColumn.innerHTML = refs[i].getAttribute("id");
attrRow.appendChild(nameColumn);
attrRow.appendChild(valueColumn);
instanceTable.appendChild(attrRow);
}
}
}
);
var pane = new dijit.TitlePane({title:instanceNode.getAttribute("id"),content:instanceTable});
return pane;
}
/**
* Creates a dojo Title Pane from a DOM instance node. The pane has the instance
* id as its title. The content is name and value of each attribute in separate
* line.
*
* @param node
* an instance node
* @returns
*/
function createEntityTypeDojoWidget(node) {
var entityTable = document.createElement("table");
dojo.query('id, basic, enum, version, one-to-one, many-to-one, one-to-many, many-to-many', node)
.forEach(function(item) {
var attr = document.createElement("tr");
var name = document.createElement("td");
name.className = item.nodeName.toLowerCase(); /* May be cross-browser trouble */
var type = item.getAttribute("type");
name.innerHTML = type;
if (name.className == 'one-to-many') {
name.innerHTML = type + '&lt;' + item.getAttribute("member-type") + '&gt;';
}
var value = document.createElement("td");
value.innerHTML = dojox.xml.parser.textContent(item);
attr.appendChild(name);
attr.appendChild(value);
entityTable.appendChild(attr);
}
);
var pane = new dijit.TitlePane({title:node.getAttribute("name"), content: entityTable});
return pane;
}
/**
* Generic routine to render the given XML Document as a raw but indented text in to an unattached div section.
*
* @param dom a XML DOM
*
* @returns an unattached div section
*/
function renderXMLasXML(/*XML DOM*/dom) {
var newDiv = document.createElement('div');
print(dom.documentElement, newDiv, 0);
return newDiv;
}
/**
* Renders a XML DOM node as a new child of the given HTML node.
*
* CSS styles used:
* node-value : The value of a text node
* attr-name : The name of an attribute
* attr-value : The value of an attribute
* delimiter : symbols such = " < \ > used in visual XML
* the XML element name : e.g. A <metamodel> tag will be decorated with .metamodel CSS style
*
*/
function print(/* XML node */xnode, /* HTML node*/ hnode, /*int*/counter) {
if (xnode.nodeName == '#text') {
addTextNode(hnode, xnode.nodeValue, "node-value");
return;
}
var root = document.createElement('div');
root.style.position = 'relative';
root.style.left = '2em';
addRoot(xnode, hnode, root, ++counter);
var attrs = xnode.attributes;
if (attrs) {
for (var i = 0; i < attrs.length; i++) {
var attr = attrs[i];
addTextNode(root, ' ' + attr.nodeName, "attr-name");
addDelimiter(root, '=');
addTextNode(root, '"' + attr.nodeValue + '"', "attr-value");
}
addDelimiter(root, '>');
}
var children = xnode.childNodes;
if (children) {
for (var i = 0; i < children.length; i++) {
print(children[i], root, ++counter);
}
}
addDelimiter(root, '</');
addTextNode(root, xnode.nodeName, xnode.nodeName);
addDelimiter(root, '>');
return;
}
/**
* Adds the given delimiter text with CSS style 'delimiter' to the given parent node
* @param parentNode
* @param text
*/
function addDelimiter(/* HTML node*/ parentNode, /* Delimiter String*/ delim) {
addTextNode(parentNode, delim, 'delimiter');
}
/**
* Adds a <span> node of given className to the given parentNode with the given text.
*
* @param parentNode the parent node to which new text is added as a <span> element.
* @param text text to be added to the new <span> element
* @param className class of the new <span> element
* @returns the new <span> node
*/
function addTextNode(/* HTML node*/parentNode, /* String */text, /* String*/className) {
if (isEmpty(text)) return null;
newNode = document.createElement('span');
if (className) {
newNode.className = className;
}
if (text) {
newNode.appendChild(document.createTextNode(text));
}
if (parentNode) {
parentNode.appendChild(newNode);
}
return newNode;
}
function isTextNode(/* XML node */ xnode) {
return xnode == null || xnode.nodeName == '#text';
}
function isTogglable(/* XML node */ xnode) {
if (xnode == null) return false;
if (isTextNode(xnode)) return false;
var children = xnode.childNodes;
if (children == null || children.length == 0) return false;
if (children.length == 1 && isTextNode(children[0])) return false;
return true;
}
function addRoot(xnode, hnode, root, counter) {
if (isTogglable(xnode)) {
hnode.appendChild(document.createElement('br'));
var ctrl = addTextNode(hnode, '-');
root.setAttribute("id", counter);
var moniker = '&lt;' + xnode.nodeName + '&gt;...';
ctrl.setAttribute("onclick", 'javascript:toggle(this, "' + moniker + '", "' + counter + '");');
}
addDelimiter(root, '<');
addTextNode(root, xnode.nodeName, xnode.nodeName);
hnode.appendChild(root);
}
function toggle(/* HTML node */ctrl, /* id */ moniker, /* id */ targetId) {
var visible = ctrl.innerHTML == '-';
ctrl.innerHTML = visible ? '+' + moniker : '-';
var target = document.getElementById(targetId);
if (visible) {
target.style.display = "none";
} else {
target.style.display = "inline";
}
}
/**
* Renders server response of JSON objects.
* Server sends always an array of JSON objects.
* @param json an array of hopefully non-empty array of JSON objects
* @param contentType type of content. Currently only instances are JSONized.
* @returns an array of div with a single member
*/
function renderJSONResponse(/*JSON[]*/json, /*enum*/contentType) {
var text = dojo.toJson(json, true);
var div = document.createElement("div");
var pre = document.createElement("pre");
pre.innerHTML = text;
div.appendChild(pre);
return [div]; // an array of a single div
}
/**
* Help related utilities.
*/
var helpDialog;
function createDialog() {
if (helpDialog == null) {
helpDialog = new dijit.Dialog({style: "width: 400px; height:300px;overflow:auto"});
}
return helpDialog;
}
function showHelp(title, href) {
var dialog = createDialog();
dialog.set("title", title);
dialog.set("href", href);
dialog.show();
}