All Downloads are FREE. Search and download functionalities are using the official Maven repository.

js.1.2.source-code.simplity.js Maven / Gradle / Ivy

/*
 * Copyright (c) 2016 simplity.org
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
/**
 * conventions while communicating with a simplity based server
 * 
 */
var POCOL = {
	/**
	 * as of now, we are hard coding. we do not see a need to make it flexible..
	 */
	CHAR_ENCODING : "UTF-8",

	/*
	 * client to server.
	 */
	/**
	 * name of service to be executed
	 */
	SERVICE_NAME : "_serviceName",

	/**
	 * used for login-process.
	 */
	USER_TOKEN : "_userToken",

	/**
	 * file-upload call, header field for optional name of file
	 */
	FILE_NAME : "_fileName",

	/**
	 * special file name to get log contents
	 */
	FILE_NAME_FOR_LOGS : '_logs',

	/**
	 * mime type of the file being uploaded.
	 */
	MIME_TYPE : "_mimeType",

	/**
	 * serviceName header field value to be used to discard a file that was
	 * uploaded earlier. For example when user decides against using it as part
	 * of next upload. This is only a courtesy to the server, but not required.
	 * Unused files will any way be garbage collected
	 */
	DISCARD_FILE : "_discard",
	/*
	 * server response back to client
	 */
	/**
	 * what happened to the request? REQUEST_OK means all OK, and response is
	 * data. Any other value means that the response is an array of messages
	 */
	REQUEST_STATUS : "_requestStatus",
	/**
	 * header field name used by server to intimate the token for the uploaded
	 * file. This token is to be sent back as part of data for next service that
	 * may use this uploaded file
	 */
	FILE_TOKEN : "_fileToken",

	/**
	 * server trace being pumped to client
	 */
	TRACE_FIELD_NAME : "_trace",
	/**
	 * messages returned along with data. like {..., "_mesages":[{}..]...}
	 */
	MESSAGES : "_messages",
	/*
	 * possible values for status field in response
	 */
	/**
	 * valid response is delivered as all is well. There may be messages, but
	 * they are not error messages.
	 */
	STATUS_OK : 'ok',
	/**
	 * Not valid login. Could be because of session-out, or the client did not
	 * authenticate before sending this request.
	 */
	STATUS_NO_LOGIN : 'noLogin',
	/**
	 * Error. Either service related issues, like validation error, or server
	 * error like internal error. Usually, response will only have messages.
	 */
	STATUS_ERROR : 'error',
	/**
	 * time taken by this engine to execute this service in milliseconds
	 */
	SERVICE_EXECUTION_TIME : "_serviceExecutionTime",
	/**
	 * message type : some specific operation/action succeeded.
	 */
	MESSAGE_SUCCESS : "success",
	/**
	 * message type : general information.
	 */
	MESSGAE_INFO : "info",
	/**
	 * message type : warning/alert
	 */
	MESSAGE_WARNING : "warning",
	/**
	 * message type : ERROR
	 */
	MESSAGE_ERROR : "error",

	/*
	 * Some conventions used by server special features
	 */
	/**
	 * field name that directs a specific save action for the table
	 */
	TABLE_ACTION_FIELD_NAME : "_saveAction",
	/**
	 * tableSaveTask can get the action at run time
	 */
	TABLE_ACTION_ADD : "add",
	/**
	 * tableSaveTask can get the action at run time
	 */
	TABLE_ACTION_MODIFY : "modify",
	/**
	 * tableSaveTask can get the action at run time
	 */
	TABLE_ACTION_DELETE : "delete",
	/**
	 * tableSaveTask can get the action at run time
	 */
	TABLE_ACTION_SAVE : "save",

	/*
	 * filter-field is a special field that has associated comparator for
	 * communicating filtering criterion with the server. -------------- filter
	 * field comparators ----------------
	 */
	EQUAL : "=",
	NOT_EQUAL : "!=",
	LESS : "<",
	LESS_OR_EQUAL : "<=",
	GREATER : ">",
	GREATER_OR_EQUAL : ">=",
	LIKE : "~",
	STARTS_WITH : "^",
	BETWEEN : "><",
	IN_LIST : "@",

	/**
	 * suffix for the to-Field If field is "age" then to-field would be "ageTo"
	 */
	TO_FIELD_SUFFIX : "To",

	/**
	 * like ageComparator
	 */
	COMPARATOR_SUFFIX : "Comparator",
	/**
	 * comma separated names of columns that are to be used for sorting rows
	 */
	SORT_COLUMN_NAME : "_sortColumns",

	/**
	 * sort order asc or desc. asc is the default
	 */
	SORT_ORDER : "_sortOrder",
	/**
	 * ascending
	 */
	SORT_ORDER_ASC : "asc",
	/**
	 * descending
	 */
	SORT_ORDER_DESC : "desc",

	PAGINATION_SERVICE : "_p",

	/**
	 * if a service request wants a table to be paginated in its way back t
	 * client, then it should send the page size in a field names sheetName
	 * suffixed with this indicator
	 */
	PAGE_SIZE_SUFFIX : "PageSize",

	/**
	 * if a sheet is paginated, server would return total count of rows in an
	 * additional field for the sheet name with this suffix
	 */
	TOTAL_COUNT_SUFFIX : "TotalCount",

	/**
	 * field name that has the table name while requesting for a specific page
	 */
	PAGINATION_TABLE : "_tableName",

	/**
	 * field name that has the page size for pagination service
	 */
	PAGINATION_SIZE : "_pageSize",

	/**
	 * field name that has the page number for pagination service
	 */
	PAGINATION_PAGE_NUMBER : "_pageNumber",
	/**
	 * list service typically sends a key value
	 */
	LIST_SERVICE_KEY : "_key",

	/**
	 * should suggestion service suggest matching strings that start with the
	 * starting key?
	 */
	SUGGEST_STARTING : "_matchStarting",
	/**
	 * use this special name to indicate all fields whenever a list of fields is
	 * expected
	 */
	ALL_FIELDS : '_allFields',

	/**
	 * mark column names in tables like __colName__
	 */
	COL_MARKER : '__',
	/**
	 * mark a space to put the index suffix. like row__i__ to suffix row with _i
	 * where is the row number
	 */
	IDX_MARKER : 'i',
	/**
	 * attribute name to mark an element as target for a data table
	 */
	DATA_TABLE : 'data-table',
	/**
	 * attribute to mark an element as row for a table. this element is repeated
	 * for each row of a table
	 */
	DATA_ROW : 'data-row',
	/**
	 * attribute to indicate that this table is hierarchical
	 */
	HAS_CHILDREN : 'data-has-children',
	/**
	 * attribute set to true to hide this element if the associated table has no
	 * data. Otherwise, only the row element is hidden, and the table element is
	 * shown. For example, if this is not set, you may see the table header row,
	 * but no data
	 */
	HIDE_IF_NO_DATA : 'data-hide-if-no-data',
	/**
	 * window level var that has the value of the last json object response.
	 * This gets replaced with the next response
	 */
	LAST_JSON : '_lastJson',

	/**
	 * name with which a json is saved in local storage (sessionStorage?) for
	 * db.
	 */
	LOCAL_STORAGE_NAME : '_localData',
	/**
	 * name of the object in page-specific script that has functions for service
	 * indexed by serviceName
	 */
	LOCAL_SERVICES : '_localServices',
	/**
	 * name of the object in page-specific script that has ready response object
	 * for service indexed by serviceName
	 */
	LOCAL_RESPONSES : '_localResponses'
};
/**
 * Simple way to get response from your service
 */
var Simplity = (function() {
	var htmlEscape = function(txt) {
		if (!txt || !txt.replace) {
			return txt;
		}
		return txt.replace(/&/g, '&').replace(/
	 * for fields: 
	 * if a tag with that id is found, value is set (either as value or innerHTML). 
	 * However, if this tag has an attribute named 'data-value' then the value is set
	 * to that attribute instead of value/innerHTML.
	 * 
	 * If no tag is found, we try a tag with id fieldName-true and fieldName-false. 
	 * If found, the tag is shown/hidden based on the field value
	 * 
	 * for tables : if a tag is is found with this id, 
	 * 	a. we clone that tag for each row in the table. cloned tag has an attribute named
	 * 		'data-idx' that is 1-based index into the table, and its id is set to 'tableName-idx'
	 *      e.g. data-idx='3', id='orderLine-3'...
	 *      
	 *  b. push data form rows to these tags by replacing --colName-- with value of that cell
	 *  c. replace the innerHTML of parent with all these
	 *  d. hide the original row with id
	 * 
	 * 
*/ var pushDataToPage = function(json, doc) { if (Array.isArray(json)) { log('Simplity funciton pushDataToPage() requires that the json is an object-map, but not an array. Data not pushed to page'); return; } if (!doc) { doc = window.document; } var selectTables = getSelectSources(doc); /* * go with each attribute of the json */ for ( var att in json) { /* * attribute starting with _ are not ours */ if (att.indexOf('_') === 0) { log('Ignoring the reserved attribute ' + att); continue; } var val = json[att]; var ele; /* * we have an issue with null. Let us try that as value as well as * empty array */ if (val === null || Array.isArray(val)) { if (!val) { val = []; } /* * array can be used in two ways. Simple table or hierarchical. * can be source for a dom element with id="__att__" */ ele = doc.getElementById(POCOL.COL_MARKER + att + POCOL.COL_MARKER); if (ele) { setDataToTable(ele, val || [], att); continue; } ele = doc.getElementById(att); if (ele && ele.getAttribute(POCOL.DATA_TABLE)) { setHierarchicalDataToTable(ele, val || [], att); continue; } var eles = selectTables && selectTables[att]; if (eles) { for (var i = eles.length - 1; i >= 0; i--) { setOptionsForEle(eles[i], val || []); } continue; } /* * if we did not find an element for this table, we will try * other options later */ if (val) { log('No destination found for table ' + att); continue; } } /* * it could be a primitive, or an object. In any case, we need an * element */ ele = doc.getElementById(att); if (ele) { setValueToEle(ele, att, val); continue; } /* * we try show-hide element for this */ var ele1 = doc.getElementById(att + '-true'); var ele2 = doc.getElementById(att + '-false'); if (ele1 || ele2) { showOrHideEle(ele1, ele2, val); continue; } /* * no destination found */ } }; var setOptionsForEle = function(ele, data) { var opt = ele.options; var val = null; if (opt && opt.length) { opt = opt[ele.selectedIndex]; val = opt && opt.value; } var t = []; var n = data.length; for (var i = 0; i < n; i++) { var pair = data[i]; t.push(''); } ele.innerHTML = t.join(''); }; /** * @method assign rows data to a dom element. row is of the form __colName1__= 0; i--) { var ele = eles[i]; var att = ele.getAttribute(POCOL.DATA_TABLE); if (att) { var list = sources[att]; if (!list) { list = sources[att] = []; } list.push(ele); nbr++; } } if (nbr) { return sources; } return null; }; /* * */ var downloadCsv = function(arrData) { if (!Array.isArray(arrData)) { log('Simplity function downloadCsv() requires that the json to be an array. Data not pushed to page'); return; } var csv = ''; // 1st loop is to extract each row for (var i = 0; i < arrData.length; i++) { var row = ""; // 2nd loop will extract each column and convert it in string // comma-seprated for ( var index in arrData[i]) { row += '"' + arrData[i][index] + '",'; } row.slice(0, row.length - 1); // add a line break after each row csv += row + '\r\n'; } // 1st loop is to extract each row for (var i = 0; i < arrData.length; i++) { var row = ""; // 2nd loop will extract each column and convert it in string // comma-seprated for ( var index in arrData[i]) { row += '"' + arrData[i][index] + '",'; } row.slice(0, row.length - 1); // add a line break after each row csv += row + '\r\n'; } if (csv == '') { alert("Invalid data"); return; } // Generate a file name var fileName = "download"; // Initialize file format you want csv or xls var uri = 'data:text/csv;charset=utf-8,' + escape(csv); var link = document.createElement("a"); link.href = uri; // set the visibility hidden so it will not effect on your web-layout link.style = "visibility:hidden"; link.download = fileName + ".csv"; // this part will append the anchor tag and remove it after automatic // click document.body.appendChild(link); link.click(); document.body.removeChild(link); }; /** * @method Hide or show complementary elements based on field value * @param {boolean} * val * @param {DOMElement} * eleTrue element to be shown on tue, and hidden on false * @param {DOMElement} * element to be hidden on true, and shown on false */ var showOrHideEle = function(val, eleTrue, eleFalse) { if (eleTrue) { eleTrue.style.display == val ? '' : 'none'; } if (eleFalse) { eleFalse.style.display == val ? 'none' : ''; } }; /** * @method push value to a field * @param {DOMElement} * ele * @param {string} * fieldName * @param {string} * fieldValue */ var setValueToEle = function(ele, fieldName, fieldValue) { if (fieldValue == null) { fieldVaue = ''; } var tag = ele.tagName.toLowerCase(); /* * most common - input field */ if (tag === 'input') { if (ele.type.toLowerCase() === 'checkbox') { ele.checked = ele.value ? true : false; } else { ele.value = fieldValue; } return; } /* * drop-down, possibly multi */ if (tag === 'select') { if (ele.multiple) { var vals = getVals(fieldValue); var el = ele.firstChild; while (el) { if (vals[el.value]) { ele.setAttribute('selected', 'selected'); } else { ele.removeAttribute('selected'); } el = el.nextSibling; } } else { ele.value = fieldValue; } return; } /* * meant for changing style */ if (ele.hasAttribute('data-value')) { ele.setAttribute('data-value', fieldValue); return; } /* * a radio group */ if (ele.hasAttribute('data-radio')) { var eles = ele.getElementsByTagName('input'); if (!eles.length) { log('we found tag for ' + fieldName + ' as data-radio but it does not have radio child nodes'); return; } for (var i = eles.length - 1; i >= 0; i--) { var el = eles[i]; el.checked = el.value == fieldValue; } return; } /* * check-box group that takes comma separated list of values */ if (ele.hasAttribute('data-checkbox')) { var vals = getVals(fieldValue); var eles = ele.getElementsByTagName('input'); if (!eles.length) { log('we found tag for ' + fieldName + ' but it does not have check-box child nodes'); return; } for (var i = eles.length - 1; i >= 0; i--) { var el = eles[i]; el.checked = vals[el.value]; } return; } /* * when all else fails, we have textContent.. */ ele.textContent = fieldValue; }; /** * agent who knows how to contact server */ var METHOD = "POST"; var URL = 'a._s'; var FILE_URL = 'a._f'; var LOGIN_URL = 'a._i'; var LOGOUT_URL = 'a._o'; var TIMEOUT = 12000; /** * function to be called whenever server returns with a status of NO-LOGIN */ var reloginFunction = null; /** * @method login to server with credentials * @param {text} * login name * @param {text} * abrakadabra * @param {Function} * successFn function to be called when login succeeds. Typically * you want to navigate to your home page * * @param {Function} * failureFn function to be called when login fails. Optional. If * not supplied, just a message is flashed, so that the user can * re-try */ var login = function(userId, userToken, successFn, failureFn) { successFn = successFn || pushDataToPage; failureFn = failureFn || showMessages; var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if (this.readyState != '4') { return; } var json = {}; if (xhr.responseText) { json = JSON.parse(xhr.responseText); } /* * let us act as per status */ if (xhr.status && xhr.status != 200) { log('HTTP error from server (non-200)\n' + xhr.responseText); failureFn(createMessageArray('Server or the communication infrastructure has failed to respond.')); return; } var st = json[POCOL.REQUEST_STATUS] || POCOL.STATUS_OK; if (st == POCOL.STATUS_OK) { successFn(json); } else if (json[POCOL.MESSAGES]) { failureFn(json[POCOL.MESSAGES]); } else { failureFn(createMessageArray('Login failed')); } }; xhr.ontimeout = function() { failureFn(createMessageArray("Sorry, there seem to be some red-tapism on the server. Can't wait any more for a decision. Giving up.")); }; try { xhr.open(METHOD, LOGIN_URL, true); xhr.timeout = TIMEOUT; xhr.setRequestHeader("Content-Type", "text/html; charset=utf-8"); var text = userId; if (userToken) { text += ' ' + userToken; } xhr.setRequestHeader(POCOL.USER_TOKEN, text); xhr.send(''); } catch (e) { failureFn(createMessageArray('Unable to connect to server. Error : ' + e.message)); } }; /** * @method one-way communication - we tell server to logout */ var logout = function() { var xhr = new XMLHttpRequest(); try { xhr.open(METHOD, LOGOUT_URL, true); xhr.setRequestHeader("Content-Type", "text/html; charset=utf-8"); xhr.send(''); } catch (e) { showMessages(createMessageArray('Unable to connect to server. Error : ' + e)); } }; /** * gets response from server for the service and invokes call-back function * with the response * * @param {string} * serviceName qualified service name to be invoked * @param {string} * optional json string to be sent to server as input for this * service * @param {Function} * successFn function is called with jsonObject (not json string) * that is returned from server. if this is not specified, * Simplity.pushDataToPage() is used. * @param {Function} * failureFn optional. function that is invoked in case of any * error. it is called with an array of message objects. * Simplity.showMessages() is used as default. * @param {text} * fileToken optional. To be used if we are trying to access * pending response using a file token rather than getting * response for a service */ var getResponse = function(serviceName, data, successFn, failureFn, fileToken, forPendingService) { if(!data){ data = '{}'; }else if(!data.indexOf){ data = JSON.stringify(data); } successFn = successFn || pushDataToPage; failureFn = failureFn || showMessages; if (!serviceName && !fileToken) { log('No service'); failureFn(createMessageArray('No serviceName specified')); return; } log('Service ' + serviceName + ' invoked'); var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if (this.readyState != '4') { return; } var json = {}; if (xhr.responseText) { try { json = JSON.parse(xhr.responseText); } catch (e) { log('Response is not json. response text is returned instead of js object....'); /* * utility services may use non-jsons */ json = xhr.responseText; } } /* * any issue with our web agent? */ if (xhr.status && xhr.status != 200) { /* * special case of pending services */ if (forPendingService && xhr.status == 404) { log('Pending request is still not ready.. will retry again..') getPendingResponse(token, successFn, failureFn); return; } log('HTTP error from server (non-200)\n' + xhr.responseText); failureFn(createMessageArray('Server or the communication infrastructure has failed to respond.')); return; } var st = json[POCOL.REQUEST_STATUS] || POCOL.STATUS_OK; if (st == POCOL.STATUS_OK) { var token = json[POCOL.FILE_TOKEN]; log(token + " is the token " + POCOL.FILE_TOKEN); if (token) { log('Service is pending on the server. We will collect the response later..') getPendingResponse(token, successFn, failureFn); return; } window[POCOL.LAST_JSON] = json; log('Json saved as ' + POCOL.LAST_JSON); successFn(json); return; } if (st == POCOL.STATUS_NO_LOGIN) { if (reloginFunction) { log('Login required. invoking relogin'); reloginFunction(serviceName, data, successFn, failureFn); return; } failureFn(createMessageArray('This service requires a valid login. Please login and try again.')); return; } var msgs = json[POCOL.MESSAGES]; if (!msgs) { msgs = createMessageArray('Server reported a failure, but did not specify any error text.'); } failureFn(msgs); }; xhr.ontimeout = function() { log('time out'); failureFn(createMessageArray('Sorry, there seem to be some red-tapism on the server. giving-up')); }; try { xhr.open(METHOD, URL, true); xhr.timeout = TIMEOUT; if (fileToken) { xhr.setRequestHeader(POCOL.FILE_TOKEN, fileToken); } else { xhr.setRequestHeader("Content-Type", "text/html; charset=utf-8"); xhr.setRequestHeader(POCOL.SERVICE_NAME, serviceName); } xhr.send(data); } catch (e) { log("error during xhr : " + e.message); failureFn(createMessageArray('Unable to connect to server. Error : ' + e.message)); } }; /** * we have put this function outside getResponse function to avoid building * stack of object for subsequent calls */ var getPendingResponse = function(token, successFn, failureFn) { log('Time-out set for token ' + token); setTimeout(function() { getResponse(null, null, successFn, failureFn, token, true); }, 3000); }; /** * create an array with a message object with the supplied message text */ var createMessageArray = function(messageText) { return [ { messageType : 'error', text : messageText } ]; }; /** * create a response with an error message */ var createErrorResponse = function(messageText) { var resp = {}; resp[POCOL.REQUEST_STATUS] = POCOL.STATUS_ERROR; resp[POCOL.MESSAGES] = createMessageArray(messageText); return resp; }; /** * create a response with an error message */ var createSuccessResponse = function() { var resp = {}; resp[POCOL.REQUEST_STATUS] = POCOL.STATUS_OK; resp[POCOL.MESSAGES] = []; return resp; }; /** * dummy server functionality. Mimics server with local data/function */ var getResponseLocal = function(serviceName, data, successFn, failureFn) { successFn = successFn || pushDataToPage; failureFn = failureFn || showMessages; if (!serviceName) { log('No service'); FailureFn(createMessageArray('No serviceName specified')); return; } if (data && data.length) { try { data = JSON.parse(data); } catch (e) { log('Invaid input json. Assuming that the service is a special one that expects plain text..'); } } else { data = {}; } log('Service ' + serviceName + ' invoked locally'); var response = localServer.getResponse(serviceName, data); log('local response for service ' + serviceName + ' :'); log(response); try { response = JSON.parse(response); } catch (e) { log('Response is not a valid JSON. Assumed to be text and returning text instead of JSON.'); } var st = response[POCOL.REQUEST_STATUS]; if (!st || st == POCOL.STATUS_OK) { /* * save json to a window variable */ window[POCOL.LAST_JSON] = response; successFn(response); return; } var msgs = response[POCOL.MESSAGES]; if (!msgs) { msgs = createMessageArray('Server indicated failure of service, but offered no explanation!'); } failureFn(msgs); }; /** * uploads a file to web-tier and gets a token for this uploaded file. * successFn()is called back with this token. Server can be asked to pick * this file-up as part of a subsequent service request. * * @param {File} * File object. This is typically fileField.files[0] * @param {Function} * call back function is called with file-token as the only * argument. Null argument implies that there was some error,and * the operation did not go thru. * @param {Function} * call back function for progress bar. is called back with % * completed periodically */ var uploadFile = function(file, callbackFn, progressFn) { log('File ' + file.name + ' of mime-type ' + file.mime + ' of size ' + file.size + ' is being uploaded'); if (!callbackFn) { error('No callback funciton. We will set window._uploadedFileKey to the returned key'); } var xhr = new XMLHttpRequest(); /* * attach call back function */ xhr.onreadystatechange = function() { if (this.readyState != '4') { return; } /* * any issue with HTTP Connection? */ var token = null; if (xhr.status && xhr.status != 200) { log('File upload failed with non 200 status : ' + xhr.status); } else { token = xhr.getResponseHeader(POCOL.FILE_TOKEN); } if (callbackFn) { callbackFn(token); } else { window._uploadedFileKey = token; } }; /* * safe to use time-out */ xhr.ontimeout = function() { log('file upload timed out'); if (callbackFn) { callbackFn(null); } }; /* * is there a progress call back? */ if (progressFn) { xhr.upload.onprogress = function(e) { if (e.lengthComputable) { var progress = Math.round((e.loaded * 100) / e.total); progressFn(progress); } }; } /* * let us upload the file */ try { xhr.open(METHOD, FILE_URL, true); xhr.timeout = TIMEOUT; xhr.setRequestHeader(POCOL.FILE_NAME, file.name); log("header field " + POCOL.FILE_NAME + '=' + file.name); if (file.mime) { xhr.setRequestHeader(POCOL.MIME_TYPE, file.mime); log("header field " + POCOL.MIME_TYPE + '=' + file.mime); } xhr.send(file); } catch (e) { error("error during xhr : " + e); if (callbackFn) { callbackFn(null); } } }; /** * lets the server know that we wouldn't be using the token returned by an * earlier file-upload utility. * * @param {string} * token that is being discarded */ var discardFile = function(key) { if (!key) { error("No file token specified for discard request"); return; } var xhr = new XMLHttpRequest(); try { xhr.open(METHOD, FILE_URL, true); xhr.setRequestHeader(POCOL.FILE_TOKEN, key); xhr.setRequestHeader(POCOL.SERVICE_NAME, POCOL.DISCARD); xhr.send(); } catch (e) { error("error during xhr for discarding token : " + key + ". error :" + e); } }; /** * downoads the file and calls back function with the content. * * @param {string} * key. A token that is returned by a service call that actually * downloaded the file to buffer area. * @param {Function} * call back function is called with content of ths file. called * back with null in case of any issue. */ var downloadFile = function(key, fileName, fileType, callbackFn, progressFn) { if (!key) { error("No file token specified for download request"); return; } var xhr = new XMLHttpRequest(); /* * attach call back function */ xhr.onloadend = function() { /* * any issue with HTTP Connection? */ var resp = null; if (xhr.status && xhr.status != 200) { log('non 200 status : ' + xhr.status); callbackFn(null); } else { resp = xhr.response; } if (callbackFn) { callbackFn(resp, fileName, fileType); } else { Simplity.message('We successfully downloaded file for key ' + key + ' with content-type=' + xhr.getResponseHeader('Content-Type') + ' and total size of ' + xhr.getResponseHeader('Content-Length')); } }; /* * safe to use time-out */ xhr.ontimeout = function() { log('file download timed out'); callbackFn(null); }; /* * is there a progress call back? */ if (progressFn) { xhr.onprogress = function(e) { if (e.lengthComputable) { var progress = Math.round((e.loaded * 100) / e.total); progressFn(progress); } }; } try { xhr.open('GET', FILE_URL + '?' + key, true); if (fileName || fileType) { xhr.responseType = "blob"; } xhr.send(); } catch (e) { error("error during xhr for downloading token : " + key + ". error :" + e); } }; /** * * prompt the user to download the file with file name * */ var saveAsFile = function(contents, name, mime_type) { mime_type = mime_type || "text/plain"; var dlink = document.createElement('a'); dlink.download = name; dlink.href = window.URL.createObjectURL(contents); dlink.onclick = function(e) { // revokeObjectURL needs a delay to work properly var that = this; setTimeout(function() { window.URL.revokeObjectURL(that.href); }, 1500); }; dlink.click(); dlink.remove(); }; /** * register a call-back function to be called whenever client detects that a * login is required * * @param {Function} * reloginFn this funciton is called when server returns wth a * stus indicating no-login. This function is called back with * all the four parameters of getResponse() so that the last * service request can be triggered again, there y user not * losing anything. */ var registerRelogin = function(reloginFn) { reloginFunction = reloginFn; }; var doNothing = function() { }; var getLogs = function(callBackFn) { callBackFn = callBackFn || doNothing; downloadFile(POCOL.FILE_NAME_FOR_LOGS, null, null, callBackFn); }; /* * deal with local service during prototyping/testing */ var localServer = (function() { var localData = { tables : {}, views : {}, responses : {} }; /** * function to be called before making any service calls */ var initialize = function(data) { localData = data; }; /** * copy attributes from one item to the other. You may also use this to * clone an item, but remember we do it only at one level. That is, we * expect the attributes to be primitive. We do not handle objects * containing other objects. * * @param {Object} * fromObject from which to copy attributes. * @param {Object} * toObject. If not passed, we create a new one. * @param {Array} * attNames in case you have a list of possible attributes to * be copied * @returns {Object} toObject. (received as parameter, or new one) */ var copyAtts = function(fromObject, toObject, attNames) { if (!toObject) { toObject = {}; } if (attNames) { for (var i = 0; i < attNames.length; i++) { var attName = attNames[i]; if (fromObject.hasOwnProperty(attName)) { toObject[attName] = fromObject[attName]; } } } else { for ( var a in fromObject) { toObject[a] = fromObject[a]; } } return toObject; }; var copyReferredAtts = function(fromObject, toObject, refObject) { if (!toObject) { toObject = {}; } for ( var a in refObject) { if (fromObject.hasOwnProperty(a)) { toObject[a] = fromObject[a]; } } return toObject; }; var copyItems = function(items) { var n = (items && items.length) || 0; var result = []; for (var i = 0; i < n; i++) { result.push(copyAtts(items[i])); } return result; }; /** * filter an array of items for matching value of an attribute */ var filterWithKey = function(items, attName, valueToMatch) { var result = []; var n = items && items.length || 0; for (var i = 0; i < n; i++) { var item = items[i]; if (item[attName] == valueToMatch) { result.push(item); } } return result; }; /** * extract set of filters from data received from client. * * @param {Object} * sampleItem an item that has all the attributes that can be * used for filtering * @param {Object} * json contains name-value pairs. nameOperator and nameTo * are relatedFields * @returns {Array} filtered items that satisfy filtering criterion */ var getFilters = function(item, json) { var filters = []; for (fieldName in item) { if (!json.hasOwnProperty(fieldName)) { continue; } /* * create a default filter */ var filter = { name : fieldName, value : json[fieldName], op : '=' }; filters.push(filter); var val = json[fieldName + 'Operator']; if (!val) { continue; } /* * operator is specified */ filter.op = val; if (val != '><') { continue; } // to operator val = json[fieldName + 'To']; if (!val) { throw 'we expected a value for ' + fieldName + 'To because the operator is between (><)'; } filter.to = val; } return filters; }; /** * apply filter criterion on an item * * @param {Object} * item * @param {Object} * filter * @returns true if this items clears filtering criterion (it should be * selected). False otherwise */ var filterItem = function(item, filter) { var val = item[filter.name]; switch (filter.op) { case '=': return filter.value == val; case '!=': return filter.value != val; case '<': return val < filter.value; case '<=': return filter.value <= val; case '>': return val > filter.value; case '>=': return filter.value >= val; case '><': return val > filter.value && val < filter.to; case '^': return val.toUpperCase().indexOf(filter.value.toUpperCase()) == 0; case '~': return val.toUpperCase().indexOf(filter.value.toUpperCase()) != -1; case '@': var list = filter.value && filter.value.split(','); var n = list.length || 0; for (var i = 0; i < n; i++) { if (val == list[i]) { return true; } } return false; } }; /** * copy attributes and items from related table/view based on list of * filters * * @param {Array} * items to be filtered * @param {Array} * filters * @returns filtered items */ var filterData = function(items, filters) { var result = []; var n = items.length || 0; var m = filters.length || 0; for (var i = 0; i < n; i++) { var item = items[i]; var selected = true; for (var j = 0; j < m; j++) { if (!filterItem(item, filters[j])) { selected = false; break; } } if (selected) { result.push(item); } } return result; }; /** * add attributes/children to items from related items from join * specification * * @param {Array} * joins - array of joins from a view specification * @param {Array} * items - base list of items to which we join others * @returns array of freshly created items to which other attributes are * copied */ var doJoin = function(joins, items) { var nbrItems = items.length || 0; var nbrJoins = joins.length || 0; if (!nbrItems || !nbrJoins) { return items; } for (var i = 0; i < nbrItems; i++) { var item = items[i]; for (var j = 0; j < nbrJoins; j++) { var join = joins[j]; var attName = join.baseField; if (!item.hasOwnProperty(attName)) { throw 'base table does not have the attribute ' + attName + '. Invalid join specification.'; } /* * get children with matching values of key field */ var children = getData(join.joinedTable, null, join.joinedField, item[attName], false); if (children.length > 1) { throw 'We got ' + children.length + ' items as children from ' + join.joinedTable + ' with its parent key field ' + join.joinedFeild + ' = ' + item[join.fieldName] + ' but the join definition has not specified childrenName indicating that we do not expect more than one child items.'; } if (children.length) { copyAtts(children[0], item); } else { log('No row from joining table ' + join.joinedTable + ' with ' + join.joinedField + ' = ' + item[attName]); } } } }; /** * get all items from a table/view. * * @param {string} * tableName table/view name * @param {Object} * forKey as an attribute-value for key matching. null if all * rows * @returns {Array} rows from this table/view. all or for the supplied * key */ var getData = function(tableName, forKey, fieldName, fieldValue, getChildrenAswell) { var topTable = localData.tables[tableName]; if (!topTable) { throw tableName + ' is not defined as a table/view under _localData'; } var children = getChildrenAswell && topTable.children; log('getting data for table ' + tableName + ' with children = ' + children); var check = {}; var views = []; var table = null; /* * we keep going down the tbales till we get a table with data. Then * come up the chain of views. */ while (true) { table = localData.tables[tableName]; if (!table) { throw tableName + ' is not defined as a table/view under _localData'; } if (table.data) { break; } /* * keep track of tables in the set to avoid cycling in an * infinite loop */ check[tableName] = true; views.push(table); tableName = table.baseTable; if (check[tableName]) { throw 'We found cyclic dependence of table ' + tableName; } } var items = table.data; if (forKey) { items = filterWithKey(items, table.key, forKey[table.key]); } else if (fieldName) { items = filterWithKey(items, fieldName, fieldValue); } items = copyItems(items); if (!items || !items.length) { return items; } /* * we found the base data. We keep coming up the chain and join at * each level */ for (var i = views.length - 1; i >= 0; i--) { doJoin(views[i].joins, items); } if (!children) { return items; } log('going to add rows from children ' + children); var n = items.length; var keyName = topTable.key; var filter = { name : keyName, op : '=' }; var filters = [ filter ]; for (var i = 0; i < children.length; i++) { var childName = children[i]; var childItems = getData(childName, null, null, null, true); for (var j = 0; j < n; j++) { var item = items[j]; filter.value = item[keyName]; item[childName] = filterData(childItems, filters); } } return items; }; var getItemForKey = function(items, keyName, keyValue) { var n = items.length; for (var i = 0; i < n; i++) { var item = items[i]; if (item[keyName] == keyValue) { return item; } } return null; }; var saveData = function(tableName, json) { var table = localData.tables[tableName]; if (!table) { throw tableName + ' is not a table and hence data can not be saved using an express service'; } var keyName = table.key; var keyValue = json[keyName]; var items = table.data; var item = null; if (keyValue) { item = getItemForKey(items, keyName, keyValue); if (!item) { throw 'Item not found for the supplied key. Data can not be saved.'; } } var result = {}; item = copyReferredAtts(json, item, items[0]); if (!keyValue) { keyValue = items[items.length - 1][keyName]; keyValue++; item[keyName] = keyValue; items.push(item); result[keyName] = keyValue; } return result; }; var getList = function(tableName, json) { var table = localData.tables[tableName]; if (!table) { throw tableName + ' is not a table and hence we are unable to get a list of vname-value pairs'; } var key = table.key; var value = table.valueFieldName; if (!key || !value) { throw tableName + ' need to have attributes key and valueFieldName for us to create a list of name-value pairs'; } var result = []; var items = table.data; var n = items.length; for (var i = 0; i < n; i++) { var item = items[i]; result.push({ key : item[key], value : item[value] }); } return result; }; var expressServices = { get : function(tableName, json) { var data = getData(tableName, json, null, null, true); var resp = createSuccessResponse(); data = data && data[0]; if (data) { copyAtts(data, resp); } return resp; }, filter : function(tableName, json) { var data = getData(tableName, null, null, null, true); if (data.length && json) { var filters = getFilters(data[0], json); if (filters.length) { data = filterData(data, filters); } } var result = createSuccessResponse(); result[tableName] = data; return result; }, save : function(tableName, json) { return saveData(tableName, json); }, 'delete' : function(tableName, json) { var table = localData.tables[tableName]; if (!table) { throw (tableName + ' is not a valid table name'); } return deleteData(table, json); }, list : function(tableName, json) { var data = getList(tableName, json); var result = createSuccessResponse(); result[tableName] = data; return result; }, suggest : function(tableName, json) { return createErrorResponse('local functionaity for suggest is not yet built'); } }; var serve = function(serviceName, json) { /* * has the page included any ready response? */ var resp = window[POCOL.LOCAL_RESPONSES]; resp = resp && resp[serviceName]; if (resp) { log('response located in page-specific response'); return resp; } /* * is there a page specific fn for this? */ resp = window[POCOL.LOCAL_SERVICES]; resp = resp && resp[serviceName]; if (resp) { log('service function found in page-specific script'); if (typeof (resp) !== 'function') { throw resp + ' is expected to be a function that returns a valid response to a request for service ' + serviceName; } return resp(json); } /* * no page specific option. Let us do it ourselves */ resp = localData.responses[serviceName]; if (resp) { log('response located in local ready-responses'); return resp; } /* * express service? */ var parts = serviceName.split('_'); if (parts.length > 1) { var prefix = parts[0]; var fn = expressServices[prefix]; if (fn) { var rec = serviceName.substring(prefix.length + 1); log('trying ' + prefix + ' for table ' + rec); return fn(rec, json); } } throw serviceName + ' is not served by the local server.'; }; var getResponse = function(serviceName, json) { var resp = null; try { resp = serve(serviceName, json); } catch (e) { resp = createErrorResponse(e.message ? e.message : e); } return JSON.stringify(resp); }; return { initialize : initialize, getResponse : getResponse }; })(); var savedLocalData = null; /* * patch for demo/testing/client-only development */ if (window.location.protocol === 'file:') { /* * we are operating in local mode with no server */ if (!window.sessionStorage) { alert("Sorry, your browser setting does not allow certain features when html is opened from file-system. Use a different browser, or see if you can enable sessionStorage feature using any setting options.") return; } var text = sessionStorage[POCOL.LOCAL_STORAGE_NAME]; if (text) { log('Session storage found in session'); savedLocalData = JSON.parse(text); } else { savedLocalData = window[POCOL.LOCAL_STORAGE_NAME]; if (savedLocalData) { log('local storage found as windows variable..'); sessionStorage[POCOL.LOCAL_STORAGE_NAME] = JSON .stringify(savedLocalData); } } if (savedLocalData) { /* * set this to our local server */ localServer.initialize(savedLocalData); /* * save it in session for next page to load before we die.. */ window.addEventListener('beforeunload', function() { sessionStorage[POCOL.LOCAL_STORAGE_NAME] = JSON .stringify(savedLocalData); }); } else { log('local storage NOT found'); } getResponse = getResponseLocal; } /* * what we want to expose as API */ return { log : log, error : error, warn : warn, pipeLogging : pipeLogging, showMessage : showMessage, showMessages : showMessages, overrideShowMessage : overrideShowMessage, overrideShowMessages : overrideShowMessages, getResponse : getResponse, login : login, logout : logout, pushDataToPage : pushDataToPage, uploadFile : uploadFile, discardFile : discardFile, downloadFile : downloadFile, registerRelogin : registerRelogin, getLogs : getLogs, htmlEscape : htmlEscape, downloadCsv : downloadCsv }; })();



© 2015 - 2024 Weber Informatics LLC | Privacy Policy