/** * Queries a JSON RPC 2.0 service. This function requires jQuery for the jQuery.post() functionality. * @param {string} url The URL of the service. * @param {string} method Name of the method to call on the server. * @param {?Array|Object} params Parameters for the server method. * @param {Function} resultHandler Optional Function to call when the RPC call returns. This function will be passed the result of the method as the first parameter. * @param {string|number=} queryId Optional id to be associated with this RPC call. This will be passed as the second parameter to the resultHandler function. * @return A Promise. */ function queryService(url, method, params, resultHandler, queryId) { var request = { jsonrpc: "2.0", id: queryId || "no_id", method: method, params: params }; jQuery.post(url, JSON.stringify(request), _handleResponse, "json"); var promise, resolve, reject; if (window.Promise) { promise = new Promise(function(_resolve, _reject) { resolve = _resolve; reject = _reject; }); } function _handleResponse(response) { try { if (response.error) { if (promise) reject(response.error); else console.error(JSON.stringify(response, null, 3)); } else { if (resultHandler) resultHandler(response.result, queryId); if (promise) resolve(response.result); } } catch (e) { if (promise) reject(e); else console.error(e); } } return promise; } /** * Makes a batch request to a JSON RPC 2.0 service. This function requires jQuery for the jQuery.post() functionality. * @param {string} url The URL of the service. * @param {string} method Name of the method to call on the server for each entry in the queryIdToParams mapping. * @param {Array|Object} queryIdToParams A mapping from queryId to RPC parameters. * @param {function(Array|Object)} resultsHandler Optional Function to receive a mapping from queryId to RPC result. * @return A Promise. */ function bulkQueryService(url, method, queryIdToParams, resultsHandler) { var batch = []; for (var queryId in queryIdToParams) { var m = typeof method === 'string' ? method : method[queryId]; batch.push({jsonrpc: "2.0", id: queryId, method: m, params: queryIdToParams[queryId]}); } if (batch.length) jQuery.post(url, JSON.stringify(batch), handleBatch, "json"); else setTimeout(handleBatch, 0); var promise, resolve, reject; if (window.Promise) { promise = new Promise(function(_resolve, _reject) { resolve = _resolve; reject = _reject; }); } function handleBatch(batchResponse) { try { var results = Array.isArray(queryIdToParams) ? [] : {}; var foundError = false; for (var i in batchResponse) { var response = batchResponse[i]; if (response.error) { results[response.id] = response.error; foundError = true; } else { results[response.id] = response.result; } } if (foundError) { if (promise) reject(results); else console.error(JSON.stringify(results, null, 3)); } else { if (resultsHandler) resultsHandler(results); if (promise) resolve(results); } } catch (e) { if (promise) reject(e); else console.error(e); } } return promise; } /** * Queries a Weave data server, assumed to be at the root folder at the current host. * Available methods are listed here: http://ivpr.github.io/Weave-Binaries/javadoc/weave/servlets/DataService.html * This function requires jQuery for the jQuery.post() functionality. * @param {string} method Name of the method to call on the server. * @param {?Array|Object} params Parameters for the server method. * @param {Function} resultHandler Function to call when the RPC call returns. This function will be passed the result of the method as the first parameter. * @param {string|number=} queryId Optional id to be associated with this RPC call. This will be passed as the second parameter to the resultHandler function. */ function queryDataService(method, params, resultHandler, queryId) { return queryService('/WeaveServices/DataService', method, params, resultHandler, queryId); } /** * Gets a complete tree of Entity objects. * @param {string} url The URL to the Weave data service, such as "/WeaveServices/DataService". Also supports AdminService from weave-server-migration-tools.js. * @param {number} rootEntityId The ID of a Weave entity, or an Array of IDs. * @param {function(Object)} callback Optional Function which will receive the root Entity object with a 'children' property * which will be an Array of child Entity objects, also having 'children' properties, and so on. * If rootEntityId is an Array of IDs, the result will be an Array of Entity trees. * @return A Promise. */ function getEntityTree(url, rootEntityId, callback) { var rootIds = Array.isArray(rootEntityId) ? rootEntityId : [rootEntityId]; var promise, resolve, reject; if (window.Promise) { promise = new Promise(function(_resolve, _reject) { resolve = _resolve; reject = _reject; }); } var lookup = {}; function cacheEntityTrees(ids) { function handleEntities(entities) { // compile a list of all child ids var allChildIds = []; entities.forEach(function(entity) { lookup[entity.id] = entity; allChildIds = allChildIds.concat(entity.childIds); }); // filter out cached ids allChildIds = allChildIds.filter(function(id) { return !lookup[id]; }); // recursive call if we are still missing some entities if (allChildIds.length) return cacheEntityTrees(allChildIds); // all done, so fill in 'children' property of all entities and return the root entities for (var id in lookup) lookup[id].children = lookup[id].childIds.map(function(id){ return lookup[id]; }); var results = rootIds.map(function(id){ return lookup[id]; }); if (!Array.isArray(rootEntityId)) results = results[0]; if (callback) callback(results); if (promise) resolve(results); } if (typeof url === 'string') return queryService(url, 'getEntities', [ids], handleEntities); else return url.queue('getEntities', [ids]).then(handleEntities); } cacheEntityTrees(rootIds); return promise; } /** * This will find a column using its title and its parent table's title as search criteria. * Note that title metadata can be changed by the admin, and there is nothing preventing multiple columns or tables from having identical titles. * If there are multiple data tables with the same title, only the last matching table will be checked. * @param {string} dataTableTitle The value of the "title" metadata for a data table. * @param {string} columnTitle The value of the "title" metadata for a column which is a child of that data table. * @param {function(Object)} resultHandler A callback function which will be called on success. The function will receive a single entity object for a matching column. */ function getMatchingColumnEntity(dataTableTitle, columnTitle, resultHandler) { queryDataService("findEntityIds", [{"title": dataTableTitle}, []], function(tableIds) { if (tableIds.length == 0) return fail(); queryDataService("getEntities", [tableIds.pop()], function(tables) { queryDataService("getEntities", [tables[0].childIds], function(entities) { entities = entities.filter(function (entity) { return entity.publicMetadata['title'] == columnTitle; }); if (entities.length == 0) return fail(); resultHandler(entities.pop()); }); }); }); function fail() { console.error("No matching column found (" + [dataTableTitle, columnTitle] + ")"); } } /** * This function modifies a session state object generated by Weave by inserting a value at a specified path. * @param {Object} stateToModify The session state object to modify. * @param {Array.} path A series of object names in the Weave session state hierarchy. * @param {Object} value The replacement session state to insert at the given path. * @return {boolean} true on success, false on failure */ function modifySessionState(stateToModify, path, value) { if (path.length == 0) return false; var property = path[0]; path = path.slice(1); if (Array.isArray(stateToModify)) { for (var i in stateToModify) { var dynamicState = stateToModify[i]; if (property == dynamicState.objectName) { if (path.length) return modifySessionState(dynamicState.sessionState, path, value); dynamicState.sessionState = value; return true; } } return false; } if (path.length) return modifySessionState(stateToModify[property], path, value); stateToModify[property] = value; return true; } function weaveAdminAuthenticate(url, user, pass) { return queryService(url, 'authenticate', [user, pass]); } /** * This function can be used for bulk loading of SQL tables without going through the Admin Console. * It's not recommended to be used on a public website. * @param {string} connectionName Weave Admin connection name * @param {string} password Weave Admin password * @param {string} sqlSchema Schema name * @param {string} sqlTable Table name * @param {string} keyColumn Name of column in sql table that uniquely identifies rows in the table. * @return A Promise. */ function weaveAdminImportSQL(connectionName, password, sqlSchema, sqlTable, keyColumn) { var url = '/WeaveServices/AdminService'; var tableTitle = sqlTable; // the name which will be visible to end-users var keyType = sqlTable; var secondaryKeyColumn = null; // used for dimension slider format var filterColumnNames = []; // used for generating filtered column queries var append = true; // set to false to force creation of a new Weave table entity even if a matching one already exists if (resultHandler == null) resultHandler = function(result) { console.log("Successfully imported table " + sqlTable + "; Weave table ID = " + result); }; var method = "importSQL"; var params = { schemaName: sqlSchema, tableName: sqlTable, keyColumnName: keyColumn, secondaryKeyColumnName: secondaryKeyColumn, configDataTableName: tableTitle, keyType: keyType, filterColumnNames: filterColumnNames, append: append }; return queryService(url, method, params); } /** * Updates the metadata for columns of a specified table. * It's not recommended to use this function on a public website. * See documentation for DataEntityWithRelationships (referred to here as an "entity object") * http://ivpr.github.io/Weave-Binaries/javadoc/weave/config/DataConfig.DataEntityWithRelationships.html * @param {number} tableId The ID of the table. * @param {function(Object):Object} entityUpdater A function that alters an entity object's metadata. * Example: function(entity) { entity.privateMetadata.sqlQuery += " where myfield = 'myvalue'"; } */ function weaveAdminUpdateColumns(tableId, entityUpdater) { var url = '/WeaveServices/AdminService'; var getEntities = queryService.bind(null, url, 'getEntities'); var bulkUpdateEntities = bulkQueryService.bind(null, url, 'updateEntity'); getEntities([[tableId]]).then(function(tables) { getEntities([tables[0].childIds]).then(function(columns) { bulkUpdateEntities( columns.map(function(e){ entityUpdater(e); return [e.id, e]; }) ) }); }); } //////////////////////////////////////////////////////////////// /** * @deprecated Consider using weave.path([...]).setColumn(metadata[, dataSourceName]) instead. * @example weave.path('defaultColorDataColumn').setColumn({weaveEntityId: 123, sqlParams: [1,2,3]}) * * * This will create or update a DynamicColumn to refer to an attribute column on a Weave data server. * @param {Weave} weave A Weave instance. * @param {Array|WeavePath} path The path to an existing DynamicColumn object, or the path specifying the location to create one inside a LinkableHashMap. * @param {number} columnId The id of an attribute column on a Weave server (visible through the Admin Console and in its configuration tables) * or a set of metadata used to uniquely identify the column. If a metadata object is used, the idFields property of * the WeaveDataSource must be set accordingly to specify which fields will be used to uniquely identify columns. * @param {string=} dataSourceName The name of an existing WeaveDataSource object in the Weave session state. * @param {Array=} sqlParams optional set of parameters to use that correspond to the '?' placeholders in the SQL query on the server. */ function setWeaveColumnId(weave, path, columnId, dataSourceName, sqlParams) { // convert an Array to a WeavePath object if (Array.isArray(path)) path = weave.path(path); if (!dataSourceName) dataSourceName = weave.evaluateExpression([], 'this.getNames(WeaveDataSource)[0]'); var metadata = {"sqlParams": sqlParams}; if (typeof columnId == 'object') for (var k in columnId) metadata[k] = columnId[k]; else metadata['weaveEntityId'] = columnId; path.setColumn(metadata, dataSourceName); } /** * @deprecated Replacement code: weave.path(toolName).pushLayerSettings(layerName).state('visible', enable) * * This will show or hide a layer on a visualization. * @param {Object} weave Weave instance * @param {string} toolName String * @param {string} layerName String * @param {boolean} enable true to show, false to hide */ function enableWeaveVisLayer(weave, toolName, layerName, enable) { weave.path(toolName).pushLayerSettings(layerName).state('visible', enable); } /** * Deterministic JSON encoding * @param value The value to stringify * @param replacer A replacer function that you would give to JSON.stringify() * @param indent An indent value that you would give to JSON.stringify() * @param json_values_only Set this to true to change NaN and undefined values to null * @returns A JSON string */ function weaveStringify(value, replacer, indent, json_values_only) { if (typeof indent == 'number') { var str = ' '; while (str.length < indent) str += str; indent = str.substr(0, indent); } if (!indent) indent = ''; return _weaveStringify("", value, replacer, indent ? '\n' : '', indent, json_values_only); } function _weaveStringify(key, value, replacer, lineBreak, indent, json_values_only) { if (replacer != null) value = replacer(key, value); var output; var item; var key; if (typeof value == 'string') return _weaveEncodeString(value); // non-string primitives if (value == null || typeof value != 'object') { if (json_values_only && (value === undefined || !isFinite(value))) value = null; if (value == null) return 'null'; return value + ''; } // loop over keys in Array or Object var lineBreakIndent = lineBreak + indent; var valueIsArray = Array.isArray(value); output = []; if (valueIsArray) { for (var i = 0; i < value.length; i++) output.push(_weaveStringify(String(i), value[i], replacer, lineBreakIndent, indent, json_values_only)); } else { for (key in value) output.push(_weaveEncodeString(key) + ": " + _weaveStringify(key, value[key], replacer, lineBreakIndent, indent, json_values_only)); // sort keys output.sort(); } if (output.length == 0) return valueIsArray ? "[]" : "{}"; return (valueIsArray ? "[" : "{") + lineBreakIndent + output.join(indent ? ',' + lineBreakIndent : ', ') + lineBreak + (valueIsArray ? "]" : "}"); } var WEAVE_ENCODE_LOOKUP = {'\b':'b', '\f':'f', '\n':'n', '\r':'r', '\t':'t', '\\':'\\'}; function _weaveEncodeString(string, quote) { if (!quote) quote = '"'; if (string == null) return 'null'; var result = new Array(string.length); for (var i = 0; i < string.length; i++) { var chr = string.charAt(i); var esc = chr == quote ? quote : WEAVE_ENCODE_LOOKUP[chr]; result[i] = esc ? '\\' + esc : chr; } return quote + result.join('') + quote; }