UNPKG

cronapp-common-js

Version:
1,422 lines (1,231 loc) 166 kB
//v2.1.0 var ISO_PATTERN = new RegExp("(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+([+-][0-2]\\d:[0-5]\\d|Z))|(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d([+-][0-2]\\d:[0-5]\\d|Z))|(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d([+-][0-2]\\d:[0-5]\\d|Z))"); var TIME_PATTERN = new RegExp("PT(?:(\\d+)H)?(?:(\\d+)M)?(?:(\\d+)(?:\\.(\\d+)?)?S)?"); var DEP_PATTERN = new RegExp("\\{\\{(.*?)\\|raw\\}\\}"); var HTTP_STATUS = new RegExp("HTTP\\/1\\.1 (\\d+) (.*)"); var DS_ID = new RegExp("[\\w\\_\\.\\d]+"); const isCronapiApiPath = (url) => /^\/?api\/cronapi\/.*\/?$/.test(url); (function ($) { var defaults = { callback: function () { }, runOnLoad: true, frequency: 100, previousVisibility : null }; var methods = {}; methods.checkVisibility = function (element, options) { if (jQuery.contains(document, element[0])) { var previousVisibility = options.previousVisibility; var isVisible = element.is(':visible'); options.previousVisibility = isVisible; var initialLoad = previousVisibility == null if (initialLoad) { if (options.runOnLoad) { options.callback(element, isVisible, initialLoad); } } else if (previousVisibility !== isVisible) { options.callback(element, isVisible, initialLoad); } setTimeout(function() { methods.checkVisibility(element, options); }, options.frequency); } }; $.fn.visibilityChanged = function (options) { var settings = $.extend({}, defaults, options); return this.each(function () { methods.checkVisibility($(this), settings); }); }; })(jQuery); var initDatasource = function(scope, element, attrs, DatasetManager, $timeout, $parse, Notification, $translate, $location, $rootScope, $compile, $interpolate) { var instanceId = parseInt(Math.random() * 9999); //Add in header the path from the request was executed if (cronapi.internal.isJsonString(attrs.headers)) { let headersJson = JSON.parse(attrs.headers); headersJson.push({"key" : "origin-path", "type" : "string", "value" : $location.path()}); attrs.headers = JSON.stringify(headersJson); } else { var originPath = "origin-path:" + $location.path(); if (attrs.headers === undefined || attrs.headers === null) { attrs.headers = originPath; } else { attrs.headers = attrs.headers.concat(";", originPath); } } var props = { name: attrs.name, entity: attrs.entity, apiVersion: attrs.apiVersion, enabled: (attrs.hasOwnProperty('enabled')) ? (attrs.enabled === "true") : true, keys: attrs.keys, endpoint: attrs.endpoint, lazy: attrs.lazy === "true", append: !attrs.hasOwnProperty('append') || attrs.append === "true", prepend: (attrs.hasOwnProperty('prepend') && attrs.prepend === "") || attrs.prepend === "true", watch: attrs.watch, rowsPerPage: attrs.rowsPerPage, offset: attrs.offset, filterURL: attrs.filter, watchFilter: attrs.watchFilter, deleteMessage: attrs.deleteMessage || attrs.deleteMessage === "" ? attrs.deleteMessage : $translate.instant('General.RemoveData'), headers: attrs.headers, autoPost: attrs.autoPost === "true", autoRefresh: (attrs.autoRefresh !== undefined && attrs.autoRefresh !== null) ? attrs.autoRefresh : 0, onError: attrs.onError, onAfterFill: attrs.onAfterFill, onBeforeCreate: attrs.onBeforeCreate, onAfterCreate: attrs.onAfterCreate, onBeforeUpdate: attrs.onBeforeUpdate, onAfterUpdate: attrs.onAfterUpdate, onBeforeDelete: attrs.onBeforeDelete, onAfterDelete: attrs.onAfterDelete, onChangeStatus: attrs.onChangeStatus, onGet: attrs.onGet, onPost: attrs.onPost, onPut: attrs.onPut, onDelete: attrs.onDelete, defaultNotSpecifiedErrorMessage: $translate.instant('General.ErrorOnServerCommunication'), dependentBy: attrs.dependentBy, dependentLazyPost: attrs.dependentLazyPost, batchPost: attrs.batchpost === "true", dependentLazyPostField: attrs.dependentLazyPostField, parameters: attrs.parameters, parametersNullStrategy: attrs.parametersNullStrategy?attrs.parametersNullStrategy:"default", parametersExpression: $(element).attr('parameters'), conditionExpression: $(element).attr('condition'), condition: attrs.condition, orderBy: attrs.orderBy, loadDataStrategy: attrs.loadDataStrategy, schema: attrs.schema ? JSON.parse(attrs.schema) : undefined, checkRequired: !attrs.hasOwnProperty('checkrequired') || attrs.checkrequired === "" || attrs.checkrequired === "true", fetchOnVisible: (attrs.fetchOnVisible !== undefined && attrs.fetchOnVisible !== null) ? (attrs.fetchOnVisible === 'true') : false, offline: attrs.offline === "true" ? true : false, insideModal: attrs.insideModal === "true" ? true : false } var firstLoad = { filter: true, entity: true, enabled: true, parameters: true } var urlParameters; if (scope.params) { for (var paramKey in scope.params) { if (scope.params.hasOwnProperty(paramKey)) { var value = scope.params[paramKey]; if (paramKey.startsWith("$"+attrs.name+".")) { var key = paramKey.split("."); if (key.length == 2) { if (key[1] == "$filterMode") { props.startMode = value; } else { if (urlParameters) { urlParameters += ";"; } else { urlParameters = ""; } if (!isNaN(value)) { urlParameters += key[1]+"="+value; } else { urlParameters += key[1]+"='"+value+"'"; } } } } } } if (urlParameters) { props.parameters = urlParameters; props.parametersExpression = urlParameters; } } var instanceId = parseInt(Math.random() * 9999); var datasource = DatasetManager.initDataset(props, scope, $compile, $parse, $interpolate, instanceId, $translate); var timeoutPromise; attrs.$observe('filter', function(value) { if (datasource.isPostingBatchData()) { return; } if (!firstLoad.filter) { // Stop the pending timeout $timeout.cancel(timeoutPromise); // Start a timeout timeoutPromise = $timeout(function() { if (datasource.events.overRideRefresh) { datasource.callDataSourceEvents('overRideRefresh', 'filter', value); } else { datasource.filter(value, function (data) { if (datasource.events.refresh) { datasource.callDataSourceEvents('refresh', data, 'filter'); } }); } datasource.lastFilterParsed = value; }, 100); } else { $timeout(function() { firstLoad.filter = false; }, 0); } }); if (!urlParameters) { attrs.$observe('parameters', function(value) { if (datasource.isPostingBatchData()) { return; } if (datasource.parameters != value) { datasource.parameters = value; $timeout.cancel(timeoutPromise); timeoutPromise =$timeout(function() { datasource.callDataSourceEvents('changeDependency', 'parameters', datasource.parameters); if (datasource.events.overRideRefresh) { datasource.callDataSourceEvents('overRideRefresh', 'parameters', datasource.parameters); } else { datasource.fetch({ params: {} }, { success: function (data) { if (datasource.events.refresh) { datasource.callDataSourceEvents('refresh', data, 'parameters'); } } }); } }, 0); } }); } let calculate = { lastCondition: undefined, timeForCondition: undefined, typingSpeedLimit: 1000, maxWaitingValue: 1000, wait: function() { this.timeForCondition = 0; if (this.lastCondition) { let elapsedTime = new Date().getTime() - this.lastCondition.getTime(); if (elapsedTime < this.typingSpeedLimit) { this.timeForCondition = this.maxWaitingValue; } } this.lastCondition = new Date(); return this.timeForCondition; } }; attrs.$observe('condition', function(value) { if (datasource.isPostingBatchData()) { return; } if (datasource.condition != value) { datasource.condition = value; if (datasource.loadDataStrategy === "button") { return; } $timeout.cancel(timeoutPromise); timeoutPromise =$timeout(function() { datasource.callDataSourceEvents('changeDependency', 'condition', datasource.condition); if (datasource.events.overRideRefresh) { datasource.callDataSourceEvents('overRideRefresh', 'condition', datasource.condition); } else { datasource.fetch({ params: {} }, { success: function (data) { if (datasource.events.refresh) { datasource.callDataSourceEvents('refresh', data, 'condition'); } } }); } }, calculate.wait()); } }); attrs.$observe('enabled', function(value) { var boolValue = (value === "true"); if (datasource.enabled != boolValue) { datasource.enabled = boolValue; if (datasource.enabled) { $timeout.cancel(timeoutPromise); timeoutPromise =$timeout(function () { if (datasource.events.overRideRefresh) { datasource.callDataSourceEvents('overRideRefresh', 'enabled', datasource.parameters); } else { datasource.fetch({ params: {} }, { success: function (data) { if (datasource.events.refresh) { datasource.callDataSourceEvents('refresh', data, 'enabled'); } } } ); } }, 200); } } }); attrs.$observe('entity', function(value) { datasource.entity = value; if (datasource.entity.match(DS_ID) && !isCronapiApiPath(datasource.entity)) { datasource.entity = "api/cronapi/odata/v2/" + datasource.entity.replaceAll(".", "/"); } if (!firstLoad.entity) { // Only fetch if it's not the first load $timeout.cancel(timeoutPromise); timeoutPromise = $timeout(function() { datasource.fetch({ params: {} }, { success : function (data) { if (datasource.events.refresh) { datasource.callDataSourceEvents('refresh', data, 'entity'); } } } ); }, 200); } else { $timeout(function() { firstLoad.entity = false; }); } }); attrs.$observe('offline', function (value) { datasource.offline = value === 'true'; }); scope.$on('$destroy', function () { if ($rootScope[attrs.name] && $rootScope[attrs.name + ".instanceId"] == instanceId) { $rootScope[attrs.name].destroy(); delete window[attrs.name]; delete $rootScope[attrs.name]; delete $rootScope[attrs.name+".instanceId"]; } }); }; angular.module('datasourcejs', []) /** * Global factory responsible for managing all datasets */ .factory('DatasetManager', ['$http', '$q', '$timeout', '$rootScope', '$window', 'Notification', 'SyncService', function($http, $q, $timeout, $rootScope, $window, Notification, SyncService) { // Global dataset List this.datasets = {}; let DatasetManagerUtil = { getMainScope: function () { return angular.element(document.getElementById('main-view')).scope(); }, resolveExpressionMap: async function (map) { let result; let $scope = this.getMainScope(); if (cronapi.internal.isJsonString(map)) { result = {}; let json = JSON.parse(map); for (var idx in json) { let m = json[idx]; if (m.type === 'blockly') { let toPromise = this.onMatchRegex(/cronapi.server/g, '.run(', '.toPromise().disableNotification()'); result[m.key] = await $scope.$eval(toPromise(m.value)); } else if (m.type === 'expression') { result[m.key] = $scope.$eval(m.value); } else { result[m.key] = m.value; } } } return result; }, getHeaders: async function(headersParam) { let result; if (headersParam && headersParam.length > 0) { if (!cronapi.internal.isJsonString(headersParam)) { result = {}; let headers = headersParam.trim().split(";"); let header; for (var i = 0; i < headers.length; i++) { header = headers[i].split(":"); if (header.length === 2) { result[header[0]] = header[1]; } } } else { result = await this.resolveExpressionMap(headersParam); } } if (result) result["X-From-DataSource"] = "true"; return result; }, onMatchRegex: function (regex, run, complete) { return (callbackFunction) => { let result = regex.exec(callbackFunction); if (result && result.length) return callbackFunction.split(run).join(complete + run); return callbackFunction; } } }; const $httpLegacy = (config) => { var promise = { verb: config.method, successCallback: null, errorCallback: null, success: function(successCallback) { this.successCallback = successCallback; return this; }, error: function(errorCallback) { this.errorCallback = errorCallback; return this; } }; DatasetManagerUtil.getHeaders(config.headers).then(function (headers) { config.headers = headers || config.headers; if (config.offlineMode) { SyncService.toQueue(config); let result = config.originalObject || config.rawData || config.data; this.successCallback(result, 200, null, config); } else { let http = $http(config); http.then(response => { if (this.successCallback) this.successCallback(response.data, response.status, response.headers, response.config); }); http.catch(response => { if (this.errorCallback) this.errorCallback(response.data, response.status, response.headers, response.config); }); } }.bind(promise)); return promise; }; /** * Class representing a single dataset */ var DataSet = function(name, scope, fetchOnVisible) { var NO_IMAGE_UPLOAD = "data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTYuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjEyOHB4IiBoZWlnaHQ9IjEyOHB4IiB2aWV3Qm94PSIwIDAgNDQuNTAyIDQ0LjUwMiIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNDQuNTAyIDQ0LjUwMjsiIHhtbDpzcGFjZT0icHJlc2VydmUiPgo8Zz4KCTxnPgoJCTxwYXRoIGQ9Ik05Ljg2MiwzNS42MzhoMjQuNzc5YzAtNS41NDYtMy44NjMtMTAuMjAzLTkuMTEzLTExLjYwNGMyLjc1LTEuMjQ4LDQuNjY4LTQuMDEzLDQuNjY4LTcuMjI5ICAgIGMwLTQuMzg4LTMuNTU5LTcuOTQyLTcuOTQyLTcuOTQyYy00LjM4NywwLTcuOTQzLDMuNTU3LTcuOTQzLDcuOTQyYzAsMy4yMTksMS45MTYsNS45OCw0LjY2OCw3LjIyOSAgICBDMTMuNzI1LDI1LjQzNSw5Ljg2MiwzMC4wOTIsOS44NjIsMzUuNjM4eiIgZmlsbD0iIzkxOTE5MSIvPgoJCTxwYXRoIGQ9Ik0xLjUsMTQuMTY5YzAuODI4LDAsMS41LTAuNjcyLDEuNS0xLjVWNC4zMzNoOC4zMzZjMC44MjgsMCwxLjUtMC42NzIsMS41LTEuNWMwLTAuODI4LTAuNjcyLTEuNS0xLjUtMS41SDIuNzc1ICAgIEMxLjI0NCwxLjMzMywwLDIuNTc3LDAsNC4xMDh2OC41NjFDMCwxMy40OTcsMC42NywxNC4xNjksMS41LDE0LjE2OXoiIGZpbGw9IiM5MTkxOTEiLz4KCQk8cGF0aCBkPSJNNDEuNzI3LDEuMzMzaC04LjU2MmMtMC44MjcsMC0xLjUsMC42NzItMS41LDEuNWMwLDAuODI4LDAuNjczLDEuNSwxLjUsMS41aDguMzM2djguMzM2YzAsMC44MjgsMC42NzMsMS41LDEuNSwxLjUgICAgczEuNS0wLjY3MiwxLjUtMS41di04LjU2QzQ0LjUwMiwyLjU3OSw0My4yNTYsMS4zMzMsNDEuNzI3LDEuMzMzeiIgZmlsbD0iIzkxOTE5MSIvPgoJCTxwYXRoIGQ9Ik00My4wMDIsMzAuMzMzYy0wLjgyOCwwLTEuNSwwLjY3Mi0xLjUsMS41djguMzM2aC04LjMzNmMtMC44MjgsMC0xLjUsMC42NzItMS41LDEuNXMwLjY3MiwxLjUsMS41LDEuNWg4LjU2ICAgIGMxLjUzLDAsMi43NzYtMS4yNDYsMi43NzYtMi43NzZ2LTguNTZDNDQuNTAyLDMxLjAwNSw0My44MywzMC4zMzMsNDMuMDAyLDMwLjMzM3oiIGZpbGw9IiM5MTkxOTEiLz4KCQk8cGF0aCBkPSJNMTEuMzM2LDQwLjE2OUgzdi04LjMzNmMwLTAuODI4LTAuNjcyLTEuNS0xLjUtMS41Yy0wLjgzLDAtMS41LDAuNjcyLTEuNSwxLjV2OC41NmMwLDEuNTMsMS4yNDQsMi43NzYsMi43NzUsMi43NzZoOC41NjEgICAgYzAuODI4LDAsMS41LTAuNjcyLDEuNS0xLjVTMTIuMTY1LDQwLjE2OSwxMS4zMzYsNDAuMTY5eiIgZmlsbD0iIzkxOTE5MSIvPgoJPC9nPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+Cjwvc3ZnPgo="; var NO_FILE_UPLOAD = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTYuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjUxMnB4IiBoZWlnaHQ9IjUxMnB4IiB2aWV3Qm94PSIwIDAgNTQ4LjE3NiA1NDguMTc2IiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA1NDguMTc2IDU0OC4xNzY7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPGc+Cgk8cGF0aCBkPSJNNTI0LjMyNiwyOTcuMzUyYy0xNS44OTYtMTkuODktMzYuMjEtMzIuNzgyLTYwLjk1OS0zOC42ODRjNy44MS0xMS44LDExLjcwNC0yNC45MzQsMTEuNzA0LTM5LjM5OSAgIGMwLTIwLjE3Ny03LjEzOS0zNy40MDEtMjEuNDA5LTUxLjY3OGMtMTQuMjczLTE0LjI3Mi0zMS40OTgtMjEuNDExLTUxLjY3NS0yMS40MTFjLTE4LjA4MywwLTMzLjg3OSw1LjkwMS00Ny4zOSwxNy43MDMgICBjLTExLjIyNS0yNy40MS0yOS4xNzEtNDkuMzkzLTUzLjgxNy02NS45NWMtMjQuNjQ2LTE2LjU2Mi01MS44MTgtMjQuODQyLTgxLjUxNC0yNC44NDJjLTQwLjM0OSwwLTc0LjgwMiwxNC4yNzktMTAzLjM1Myw0Mi44MyAgIGMtMjguNTUzLDI4LjU0NC00Mi44MjUsNjIuOTk5LTQyLjgyNSwxMDMuMzUxYzAsMi40NzQsMC4xOTEsNi41NjcsMC41NzEsMTIuMjc1Yy0yMi40NTksMTAuNDY5LTQwLjM0OSwyNi4xNzEtNTMuNjc2LDQ3LjEwNiAgIEM2LjY2MSwyOTkuNTk0LDAsMzIyLjQzLDAsMzQ3LjE3OWMwLDM1LjIxNCwxMi41MTcsNjUuMzI5LDM3LjU0NCw5MC4zNThjMjUuMDI4LDI1LjAzNyw1NS4xNSwzNy41NDgsOTAuMzYyLDM3LjU0OGgzMTAuNjM2ICAgYzMwLjI1OSwwLDU2LjA5Ni0xMC43MTEsNzcuNTEyLTMyLjEyYzIxLjQxMy0yMS40MDksMzIuMTIxLTQ3LjI0NiwzMi4xMjEtNzcuNTE2QzU0OC4xNzIsMzM5Ljk0NCw1NDAuMjIzLDMxNy4yNDgsNTI0LjMyNiwyOTcuMzUyICAgeiBNMzYyLjcyOSwyODkuNjQ4Yy0xLjgxMywxLjgwNC0zLjk0OSwyLjcwNy02LjQyLDIuNzA3aC02My45NTN2MTAwLjUwMmMwLDIuNDcxLTAuOTAzLDQuNjEzLTIuNzExLDYuNDIgICBjLTEuODEzLDEuODEzLTMuOTQ5LDIuNzExLTYuNDIsMi43MTFoLTU0LjgyNmMtMi40NzQsMC00LjYxNS0wLjg5Ny02LjQyMy0yLjcxMWMtMS44MDQtMS44MDctMi43MTItMy45NDktMi43MTItNi40MlYyOTIuMzU1ICAgSDE1NS4zMWMtMi42NjIsMC00Ljg1My0wLjg1NS02LjU2My0yLjU2M2MtMS43MTMtMS43MTQtMi41NjgtMy45MDQtMi41NjgtNi41NjZjMC0yLjI4NiwwLjk1LTQuNTcyLDIuODUyLTYuODU1bDEwMC4yMTMtMTAwLjIxICAgYzEuNzEzLTEuNzE0LDMuOTAzLTIuNTcsNi41NjctMi41N2MyLjY2NiwwLDQuODU2LDAuODU2LDYuNTY3LDIuNTdsMTAwLjQ5OSwxMDAuNDk1YzEuNzE0LDEuNzEyLDIuNTYyLDMuOTAxLDIuNTYyLDYuNTcxICAgQzM2NS40MzgsMjg1LjY5NiwzNjQuNTM1LDI4Ny44NDUsMzYyLjcyOSwyODkuNjQ4eiIgZmlsbD0iI2NlY2VjZSIvPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+Cjwvc3ZnPgo="; // Publiic members this.fetchOnVisible = fetchOnVisible; this.parent = $("[name='"+name+"']").parent(); if (this.fetchOnVisible) { this.parent.visibilityChanged({ callback: function(element, visible, initialLoad) { if (visible && this.holdServiceCall) { this.holdServiceCall(); } }.bind(this), runOnLoad: false, frequency: 100 }); } this.Notification = Notification; this.$scope = scope; this.noImageUpload = NO_IMAGE_UPLOAD; this.noFileUpload = NO_FILE_UPLOAD; this.$apply = function(fc) { scope.safeApply(fc); }.bind(scope); this.columns = []; this.data = []; this.name = name; this.keys = []; this.enabled = true; this.endpoint = null; this.active = {}; this.inserting = false; this.editing = false; this.fetchSize = 2; this.observers = []; this.rowsPerPage = null; this.append = true; this.headers = null; this.responseHeaders = null; this._activeValues = null; this.errorMessage = ""; this.onError = null; this.links = null; this.loadedFinish = null; this.lastLoadedTime = 0; this.lastFilterParsed = null; this.rowsCount = -1; this.events = {}; this.busy = false; this.cursor = 0; this._savedProps; this.hasMoreResults = false; this.loaded = false; this.unregisterDataWatch = null; this.dependentBufferLazyPostData = null; this.lastAction = null; this.dependentData = null; this.hasMemoryData = false; this.batchPost= false; this.caseInsensitive = null; this.terms = null; this.checkRequired = true; this.schema; var _self = this; var service = null; this.odataFile = []; function reverseArr(input) { if (input) { var ret = new Array; for (var i = input.length - 1; i >= 0; i--) { ret.push(input[i]); } return ret; } else { return []; } } this.destroy = function() { } // Public methods /** * Initialize a single datasource */ this.init = function() { var dsScope = this; // Get the service resource service = { save: function(object) { return this.call(_self.entity, "POST", object, true); }, update: function(url, object) { return this.call(url, "PUT", object, false); }, remove: function(url, object) { return this.call(url, "DELETE", object, true); }, call: function(url, verb, obj, applyScope) { var object = {}; var isCronapiQuery = (url.indexOf('/cronapi/query/') >= 0); if (isCronapiQuery) { object.inputs = [obj]; var fields = {}; var _callback; var _callbackError; _self.busy = true; url = url.replace('/specificSearch', ''); url = url.replace('/generalSearch', ''); if (_self && _self.$scope && _self.$scope.vars) { fields["vars"] = {}; for (var attr in _self.$scope.vars) { fields.vars[attr] = _self.$scope.vars[attr]; } } for (var key in _self.$scope) { if (_self.$scope[key] && _self.$scope[key].constructor && _self.$scope[key].constructor.name == "DataSet") { fields[key] = {}; fields[key].active = _self.$scope[key].active; } } object.fields = fields; } else { object = obj; } var cloneObject = {}; _self.copy(object, cloneObject, true); delete cloneObject.__original; delete cloneObject.__status; delete cloneObject.__originalIdx; delete cloneObject.__sender; delete cloneObject.__$id; delete cloneObject.__parentId; delete cloneObject.$$hashKey; delete cloneObject.__fromMemory; delete cloneObject.__odatafiles; delete cloneObject.__$masterExpression; for (var key in cloneObject) { if (key.indexOf('__odataFile_') > -1) { cloneObject[key.replace('__odataFile_','')] = undefined; delete cloneObject[key]; } } for (var key in cloneObject) { if (cloneObject.hasOwnProperty(key)) { let value = cloneObject[key]; if (_self.isAutoGeneratedValue(value)) { delete cloneObject[key]; } } } var success = function(data, status, headers, config, batchPostponed) { _self.busy = false; if (_callback) { if (_self.isOData()) { if (data.d != null && data.d.result != null) { _self.normalizeData(data.d.result); _callback(data.d.result, true, batchPostponed); } else if (data.d != null) { _self.normalizeObject(data.d); _callback(data.d, true, batchPostponed); } else { _callback(data, true, batchPostponed); } } else { _callback(isCronapiQuery?data.value:data, true, batchPostponed); } } if (isCronapiQuery || _self.isOData()) { var commands = data; if (_self.isOData()) { commands = {}; commands.commands = data.__callback; _self.normalizeData(commands.commands); } _self.$scope.cronapi.evalInContext(JSON.stringify(commands)); } }; var ds = _self.getParentDatasource(); let batchObj = ds.getBatchDataItem(object); if (batchObj && batchObj.result) { let idx = ds.batchServiceData.indexOf(batchObj); ds.batchServiceData.splice(idx, 1); $timeout(() => { success(batchObj.result); },0); this.$promise = {}; } else { // Get an ajax promise this.$promise = _self.getService(verb, object)({ method: verb, url: _self.removeSlash(((window.hostApp || "") + url)), data: verb!='DELETE'?((object) ? JSON.stringify(cloneObject) : null):null, headers: _self.headers, rawData: (object) ? cloneObject : null, originalObject: (object) ? object : null, urlPart: url, offlineMode: _self.isOfflineMode() }).success(success).error(function (data, status, headers, config) { _self.busy = false; var msg; if (_self.isOData()) { msg = data.error.message.value; } else { msg = isCronapiQuery && data.value ? data.value : data } _self.handleError(msg); if (_callbackError) { _callbackError(msg); } }); } this.$promise.then = function (callback) { _callback = callback; return this; } this.$promise.error = function (callback) { _callbackError = callback; return this; } return this; } } this.isAutoGeneratedValue = function(value) { return typeof value === 'string' && value.length > 10 && (value.startsWith("$autogenerated$") || value.substring(1).startsWith("$autogenerated$")); } this.getIndexedDB = function(properties) { if (!window.indexedDB) { window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB; window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction; window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange; } return { properties: properties, successCallback: null, errorCallback: null, type: null, args: null, success: function(successCallback) { this.successCallback = successCallback; return this; }, error: function(errorCallback) { this.errorCallback = errorCallback; return this; }, post: function() { this.type = 'put'; this.args = arguments; return this; }, put: function() { this.type = 'put'; this.args = arguments; return this; }, delete: function() { this.type = 'delete'; this.args = arguments; return this; }, get: function() { this.type = 'getAll'; this.args = arguments; return this; }, call: function() { request = window.indexedDB.open(this.properties.dbname, this.properties.dbversion||1); request.onerror = function (event) { if (this.errorCallback) { this.errorCallback(event); } }.bind(this); request.onsuccess = function (event) { this.proceed(event.target.result); }.bind(this); request.onupgradeneeded = function (event) { var db = event.target.result; var objectStore = db.createObjectStore(this.properties.objectStore, {keyPath: this.properties.key}); objectStore.transaction.oncomplete = function(event) { var objectStore = db.transaction(this.properties.objectStore, "readwrite").objectStore(this.properties.objectStore); objectStore.add({id: 123, name: 'Jose'}); }.bind(this); }.bind(this); }, proceed: function(db) { if (this.type) { var transaction = db.transaction(this.properties.objectStore, "readwrite") var store = transaction.objectStore(this.properties.objectStore); var request = store[this.type].apply(store, this.args); request.onsuccess = function(event) { if (this.successCallback) { if (this.type === 'put') { this.successCallback(this.args[0]); } else { this.successCallback(event.target.result); } } }.bind(this); } else { if (this.successCallback) { this.successCallback(); } } } }; } this.batchServiceData = []; this.getBatchService = function(verb) { return function(properties) { var promise = { verb: verb, properties: properties, successCallback: null, errorCallback: null, success: function (successCallback) { this.successCallback = successCallback; return this; }, error: function (errorCallback) { this.errorCallback = errorCallback; return this; } }; $timeout(function() { var ds = this.getParentDatasource(); let obj = properties.originalObject || properties.rawData || properties.data || this.active let entity = this.entity; if (verb == 'PUT') { entity = this.getEditionURL(obj); } if (verb == 'DELETE') { entity = this.getDeletionURL(obj); } if (entity.indexOf("/") > 0) { entity = entity.substring(entity.lastIndexOf("/")+1, entity.length); } ds.batchServiceData.push({ entity: entity, data: obj, promise: promise }); if (promise.successCallback) { promise.successCallback.call(this, obj, null, null, null, true); } }.bind(this)); return promise; }.bind(this); } this.batchEnabled = function(obj) { if (obj && this.hasPendingChanges()) { return this.getBatchDataItem(obj) != null ? false : true; } return false; } this.getBatchDataItem = function(obj) { if (obj) { var ds = this.getParentDatasource(); for (let i = 0; i < ds.batchServiceData.length; i++) { let data = ds.batchServiceData[i].data; if (data.__$id == obj.__$id) { return ds.batchServiceData[i]; } } } return null; } this.performBatchPost = function(originCallback) { return new Promise((resolve, reject) => { let boundary = "batch_" + this.uuidv4(); let odataPost = ""; odataPost += "--" + boundary + "\n"; let changeSet = "changeset_" + this.uuidv4(); odataPost += "Content-Type: multipart/mixed; boundary=" + changeSet + "\n"; for (let i = 0; i < this.batchServiceData.length; i++) { let batchData = this.batchServiceData[i]; odataPost += "--" + changeSet + "\n"; odataPost += "Content-Type: application/http\n"; odataPost += "Content-Transfer-Encoding:binary\n"; odataPost += batchData.promise.properties.method + " " + batchData.entity + " HTTP/1.1\n"; odataPost += "Content-Type: application/json\n"; odataPost += "Accept: application/json\n"; odataPost += "X-Master-Id: "+batchData.promise.properties.originalObject.__$id+"\n"; if (batchData.promise.properties.originalObject.__$masterExpression) { odataPost += "X-Detail-Fill: " + batchData.promise.properties.originalObject.__$masterExpression + "\n"; } let headers = batchData.promise.properties.headers; for (var key in headers) { if (headers.hasOwnProperty(key)) { odataPost += key + ": " + headers[key] + "\n"; } } if (batchData.promise.properties.method != 'DELETE') { odataPost += batchData.promise.properties.data + "\n"; } } odataPost += "--" + changeSet + "--\n"; odataPost += "--" + boundary + "--\n"; console.log(odataPost); let url = this.entity; if (url.indexOf("/") > 0) { url = url.substring(0, url.lastIndexOf("/")); } let headers = {}; this.copy(this.headers, headers); headers["Content-Type"] = "multipart/mixed; boundary=" + boundary; let error = (data, status, headers, config) => { let callback; if (this.batchServiceData.length > 0) { callback = this.batchServiceData[0].promise.errorCallback; } if (callback) { callback(data, status, headers, config, false); } else { this.handleError(data) } this.batchServiceData = []; }; $httpLegacy({ method: "POST", url: _self.removeSlash(((window.hostApp || "") + url + "/$batch")), data: odataPost, headers: headers, offlineMode: _self.isOfflineMode() }).success(function (data, status, headers, config, batchPostponed) { let result = this.parseBatchResult(data); for (let i = 0; i < result.length; i++) { if (result[i].status >= 400) { error(result[i].data, result[i].status); return; } } for (let i = 0; i < this.batchServiceData.length; i++) { this.batchServiceData[i].result = !result[i] || result[i].data == null ? this.batchServiceData[i].data : result[i].data; } if (originCallback) { originCallback(); } else { let callback; if (this.batchServiceData.length > 0) { callback = this.batchServiceData[0].promise.successCallback; } if (callback) { this.batchServiceData.splice(0, 1); callback(result[0].data, result[0].status, headers, config, false); } } }.bind(this)).error(error); }); } this.parseBatchResult = function(data) { let results = []; let lines = data.split("\n"); let status; for (let j=0;j<lines.length;j++) { let line = lines[j]; if (line.startsWith("{")) { results.push({ status: status, data: JSON.parse(line) }); } if (line.startsWith("HTTP/1.1")) { var g = HTTP_STATUS.exec(line); status = g[1]; } if (line.startsWith("HTTP/1.1") && line.indexOf("No Content") > 0) { results.push({ status: status, data: null }); } } return results; } this.uuidv4 = function() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } this.getService = function(verb, object) { _self = this; var event = eval("this.on" + verb); if ( verb === 'GET' && ( event || this.isLocalData() || this.isNavigatorOffline()) ) { return function (properties) { var promise = { verb: verb, properties: properties, successCallback: null, errorCallback: null, success: function(successCallback) { this.successCallback = successCallback; return this; }, error: function(errorCallback) { this.errorCallback = errorCallback; return this; } }; $timeout(function() { var contextVars = { 'currentData': this.properties.rawData||this.properties.data||_self.active, 'filter': this.properties.filter||"", 'datasource': _self, 'selectedIndex': _self.cursor, 'index': _self.cursor, 'selectedRow': _self.active, 'item': _self.active, 'selectedKeys': _self.getKeyValues(_self.active, true), 'selectedKey': _self.getFirstKeyValue(_self.active, true), 'callback': this.successCallback }; var result; if (event) { result = _self.$scope.$eval(event, contextVars); if (result instanceof Promise) { result.then(function(result) { if (result && Object.prototype.toString.call(result) !== '[object Array]') { result = [result]; } _self.$scope.safeApply(function() { this.successCallback(result) }.bind(this)); }.bind(this)).catch(function(reason){_self.handleError(reason)}.bind(this)) } else if(result){ this.successCallback(result); } } else { var args = []; let db = _self.getPouchDB(); if (this.verb == 'GET' && !this.properties.filter) { db.allDocs({ include_docs: true, attachments: true }).catch(this.errorCallback).then(function(result) { var data = []; for (var i=0;i<result.rows.length;i++) { data.push(result.rows[i].doc); } result.rows = data; if (this.successCallback) { this.successCallback(result); } }.bind(this)); } else if (this.verb == 'GET') { var filter = peg$parse(this.properties.filter); db.find({selector: filter}).catch(this.errorCallback).then(function(result) { result.rows = result.docs; result.total_rows = result.rows.length; if (this.successCallback) { this.successCallback(result); } }.bind(this)); } } }.bind(promise),0); return promise; } } if (this.hasOfflineSupport() && object) { this.operationPouchDB(verb, object); } if (this.batchEnabled(object) && verb != 'GET') { return this.getBatchService(verb); } return $httpLegacy; } /** * Check if the datasource is waiting for any request response */ this.isBusy = function() { return this.busy; } /** * Check if the datasource was loaded by service */ this.isLoaded = function() { return this.loaded; } this.toString = function() { return "[Datasource]" } this.handleAfterCallBack = function(callBackFunction, callback) { if (callBackFunction) { try { var contextVars = { 'currentData': this.data, 'datasource': this, 'selectedIndex': this.cursor, 'index': this.cursor, 'selectedRow': this.active, 'item': this.active, 'selectedKeys': this.getKeyValues(this.active, true), 'selectedKey': this.getFirstKeyValue(_self.active, true), 'callback': callback }; this.$scope.$eval(callBackFunction, contextVars); } catch (e) { this.handleError(e); } } } this.handleBeforeCallBack = async function(callBackFunction, callback) { var isValid = true; let toPromise = DatasetManagerUtil.onMatchRegex(/cronapi.server/g, '.run(', '.toPromise().disableNotification()'); if (callBackFunction) { try { var contextVars = { 'currentData': this.data, 'datasource': this, 'selectedIndex': this.cursor, 'index': this.cursor, 'selectedRow': this.active, 'item': this.active, 'selectedKeys': this.getKeyValues(this.active, true), 'selectedKey': this.getFirstKeyValue(_self.active, true), 'callback': callback }; await this.$scope.$eval(toPromise(callBackFunction), contextVars); } catch (e) { isValid = false; this.handleError(e); } } return isValid; } /** * Error Handler function */ this.handleError = function(data) { console.log(data); if (data instanceof XMLDocument) { var errorMsg = data.getElementsByTagName("message")[0].textContent; data = errorMsg; console.log(data); } var error = ""; if (data) { if (Object.prototype.toString.call(data) === "[object String]") { error = data; } else { var errorMsg = (data.msg || data.desc || data.message || data.error || data.responseText); if (this.isOData() && data.error.message && data.error.message.value) { errorMsg = data.error.message.value; } if (errorMsg) { error = errorMsg; } } } if (!error) { error = this.defaultNotSpecifiedErrorMessage; } var regex = /<h1>(.*)<\/h1>/gmi; result = regex.exec(error); if (result && result.length >= 2) { error = result[1]; } this.errorMessage = error; if (this.onError && this.onError !== '') { if (typeof(this.onError) === 'string') { try { var contextVars = { 'currentData': this.active, 'filter': "", 'error': error, 'datasource': this, 'selectedIndex': this.cursor, 'index': this.cursor, 'selectedRow': this.active, 'item': this.active, 'selectedKeys': this.getKeyValues(this.active, true), 'selectedKey': this.getFirstKeyValue(this.active, true), 'callback': this.successCallback }; var func = this.$scope.$eval(this.onError, contextVars); if (typeof(func) === 'function') { this.onError = func; } } catch (e) { isValid = false; Notification.error(e); } } } else { this.onError = function(error) { Notification.error(error); }; } if (typeof(this.onError) === 'function') { this.onError.call(this, error); } } // Start watching for changes in activeRow to notify observers if (this.observers && this.observers.length > 0) { $rootScope.$watch(function() { return this.active; }.bind(this), function(activeRow) { if (activeRow) { this.notifyObservers(activeRow); } }.bind(this), true); } } //Public methods this.setFile = function($file, object, field) { if ($file && $file.$error === 'pattern') { return; } if ($file) { toBase64($file, function(base64Data) { this.$apply = function(value) { object[field] = value; scope.$apply(object); }.bind(scope); this.$apply(base64Data); }); } }; this.downloadFile = function(field, keys) { if (keys === undefined) return; var url = (window.hostApp || "") + this.entity + "/download/" + field; for (var index = 0; index < keys.length; index++) { url += "/" + keys[index]; } var req = { url: url, method: 'GET', responseType: 'arraybuffer' }; $http(req).then(function(result) { var blob = new Blob([result.data], { type: 'application/*' }); $window.open(URL.createObjectURL(blob)); }); }; function toBase64(file, cb) { var fileReader = new FileReader(); fileReader.readAsDataURL(file); fileReader.onload = function(e) { var base64Data = e.target.result.substr(e.target.result.indexOf('base64,') + 'base64,'.length); cb(base64Data); }; } this.openImage = function(data) { if (data.indexOf('https://') == -1 && data.indexOf('http://') == -1) { var value = 'data:image/png;base64,' + data; var w = $window.open("", '_blank', 'height=300,width=400'); w.document.write('<img src="'+ value + '"/>'); } else { $window.open(data, '_blank', 'height=300,width=400'); } }; this.byteSize = function(base64String) { if (!angular.isString(base64String)) { return ''; } function endsWith(suffix, str) { return str.indexOf(suffix, str.length - suffix.length) !== -1; } function paddingSize(base64String) { if (endsWith('==', base64String)) { return 2; } if (endsWith('=', base64String)) { return 1; } return 0; } function size(base64String) { return base64String.length / 4 * 3 - paddingSize(base64String); } function formatAsBytes(size) { return size.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' ') + ' bytes'; } return formatAsBytes(size(base64String)); }; function uuid() { var uuid = "", i, random; for (i = 0; i < 32; i++) { random = Math.random() * 16 | 0; if (i == 8 || i == 12 || i == 16 || i == 20) { uuid += "-" } uuid += (i == 12 ? 4 : (i == 16 ? (random & 3 | 8) : random)).toString(16); } return uuid; } /** * Append a new value to the end of this dataset. */ this.insert = async function(obj, onSuccess, onError, forceSave) { if (await this.handleBeforeCallBack(this.onBeforeCreate)) { //Check if contains dependentBy, if contains, only store in data if ((this.dependentLazyPost || this.batchPost) && !forceSave) { obj.__status = 'inserted'; if (this.dependentLazyPost) { obj.__parentId = eval(this.dependentLazyPost).active.__$id; } this.hasMemoryData = true; this.notifyPendingChanges(this.hasMemoryData); if (onSuccess) onSuccess(obj); } else { service.save(obj).$promise.error(onError).then(onSuccess); } } }; //Public methods /** * Append a datasource to be notify when has a post or cancel */ this.addDependentDatasource = function(dts) { if (!this.children) this.children = []; this.children.push(dts); if (this.dependentLaz