| /* |
| * 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 + '<' + item.getAttribute("member-type") + '>'; |
| } |
| 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 = '<' + xnode.nodeName + '>...'; |
| 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(); |
| } |
| |
| |