Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* 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
};
})();