UNPKG

zettapi_client

Version:

Client side CRUD operations in angular to use with zettapi_server rest api to get started quickly in any CMS project

1,862 lines (1,619 loc) 171 kB
var app = angular.module('zapi', [ 'ngSanitize', 'ngAnimate', 'ui.bootstrap', 'blockUI', 'inform', 'chart.js', 'btford.socket-io', 'ngCsv', 'ngTable', 'selector', 'angularUtils.directives.dirPagination', 'angularMoment' ]); app.constant('zapiPath', 'node_modules/zettapi_client/'); app.controller('entityCtrl', function ($entity, entityMap, ErrorSvc, $page, mySocket, $window, $routeParams, $controller, $location, $scope, $uibModal, blockUI, inform, $route, NgTableParams) { //set scope variables $scope.table = { params: null }; $scope.console = null; $scope.entities = entityMap; $scope.entityName = $routeParams.entity; $scope.lookup = {}; $scope.items = []; $scope.item = {}; $scope.newFile = {}; //set scope functions $scope.add = function (item) { blockUI.start("A enviar dados..."); $entity.add($routeParams.entity, item) .then(function (response) { if (item._id) { inform.add($scope.entity.title + ' - registo alterado com sucesso!', { ttl: 2000, type: 'info' }); } else { inform.add($scope.entity.title + ' - novo registo criado com sucesso!', { ttl: 2000, type: 'info' }); } //$location.path($scope.entityName + "/list"); }) .catch(function (response) { inform.add(response.data, { ttl: 2000, type: 'danger' }); }) .finally(function () { blockUI.stop(); }); }; $scope.remove = function (item) { blockUI.start("Averiguando eligibilidade de remoção..."); $entity.remove($routeParams.entity, item) .then(function (response) { inform.add($scope.entity.title + ' - registo apagado com sucesso!', { ttl: 2000, type: 'info' }); }) .catch(function (response) { inform.add(response.error ? response.error : response.data, { ttl: 2000, type: 'danger' }); }) .finally(function () { blockUI.stop(); }); }; $scope.openEdit = function (item) { if (!$scope.entity.modal) { if (item) { $location.path($routeParams.entity + '/edit/' + item._id); } else { $location.path($routeParams.entity + '/edit'); } return; } setEdit(item); var modalInstance = $uibModal.open({ animation: true, templateUrl: 'client/entity/entity.modal.html', controller: 'entityModalCtrl', size: 'lg', backdrop: 'static', scope: $scope }); modalInstance.result.then(function (newItem) { $scope.add(newItem); }, function () { inform.add('Alteração ao registo cancelada', { ttl: 2000, type: 'info' }); $scope.item = {}; }); }; $scope.validate = function () { if (typeof $scope.getError === 'function') { var response = $scope.getError($scope.item, $scope.items); if (response.disabled) { $scope.console = response.tooltip; return true; } } $scope.console = null; return false; }; $scope.unflattenArray = function (items) { var data = []; items.forEach(function (item) { data.push(unflatten(item)); }); return data; }; $scope.flattenArray = function (items) { var table = [], keys = [], key, data = []; items.forEach(function (item) { var row = flatten(item); for (key in row) { if (keys.indexOf(key) !== -1) { continue; } keys.push(key); } table.push(row); }); table.forEach(function (row) { var datum = {}; for (var i = 0; i < keys.length; i++) { key = keys[i]; if (typeof row[key] === 'undefined') { row[key] = ""; } datum[key] = row[key]; } data.push(datum); }); return data; }; function flatten(data) { var result = {}; function recurse(cur, prop) { if (Object(cur) !== cur) { result[prop] = cur; } else if (Array.isArray(cur)) { for (var i = 0, l = cur.length; i < l; i++) recurse(cur[i], prop + "[" + i + "]"); if (l === 0) result[prop] = []; } else { var isEmpty = true; for (var p in cur) { isEmpty = false; recurse(cur[p], prop ? prop + "." + p : p); } if (isEmpty && prop) result[prop] = {}; } } recurse(data, ""); return result; } function unflatten(data) { if (Object(data) !== data || Array.isArray(data)) { return data; } var regex = /\.?([^.\[\]]+)|\[(\d+)\]/g, resultholder = {}; for (var p in data) { var cur = resultholder, prop = "", m; while ((m = regex.exec(p))) { cur = cur[prop] || (cur[prop] = (m[2] ? [] : {})); prop = m[2] || m[1]; } cur[prop] = data[p]; } return resultholder[""] || resultholder; } //set private methods function get(entity, id, callback) { blockUI.start("A obter dados..."); getLookups($scope.lookup, function (err, lookups) { if (err) { inform.add("Não foi possível obter dados.", { ttl: 2000, type: 'danger' }); blockUI.stop(); callback(err); return; } $scope.lookup = lookups; //if user is creating a new record if (!$routeParams.id && $routeParams.action === 'edit') { blockUI.stop(); callback(); return; } $entity.get(entity, id) .then(function (response) { if (id) { $scope.item = response.data; } else { $scope.items = response.data; updateTable(); } }) .catch(function (response) { inform.add(response.error ? response.error : response.data, { ttl: 2000, type: 'danger' }); }) .finally(function () { blockUI.stop(); callback(); }); }); } function getLookups(lookup, callback) { if (lookup) { if (lookup.length) { callback(null, lookup); return; } } $entity.getLookups($routeParams.entity, function (err, data) { if (err) { callback(err); return; } callback(null, data); }); } function setEdit(item) { if (item) { $scope.item = angular.copy(item); } else { $scope.item = typeof $scope.blank === 'function' ? $scope.blank() : {}; } } function updateTable() { $scope.table.params = new NgTableParams({ page: 1, count: $scope.items.length, sorting: { nome: "asc" } }, { total: 1, counts: [], dataset: $scope.items }); } function loadFinished() { //inject child controller $controller($routeParams.entity + 'Ctrl', { $scope: $scope }); var websocket; if (typeof entityMap[$routeParams.entity].websocket === 'undefined') { websocket = $routeParams.entity; } else { websocket = eval(entityMap[$routeParams.entity].websocket) + "." + $routeParams.entity; } mySocket.on(websocket + '.remove', onThisEntityRemove); mySocket.on(websocket + '.new', onThisEntityAdd); mySocket.on(websocket + '.edit', onThisEntityEdit); } function onThisEntityAdd(data) { switch ($routeParams.action) { case 'list': $scope.items.unshift(data); updateTable(); break; } //todo update lookup } function onThisEntityEdit(data) { switch ($routeParams.action) { case 'list': for (var i = 0; i < $scope.items.length; i++) { if ($scope.items[i]._id === data._id) { $scope.items[i] = data; updateTable(); break; } } break; case 'view': case 'edit': if ($scope.item._id === data._id) { $scope.item = data; } break; } //todo update lookup } function onThisEntityRemove(data) { switch ($routeParams.action) { case 'list': for (var i = 0; i < $scope.items.length; i++) { if ($scope.items[i]._id === data) { $scope.items.splice(i, 1); updateTable(); break; } } break; case 'view': case 'edit': if ($scope.item._id === data._id) { $location.path('/' + $routeParams.entity + '/list'); } break; } //todo update lookup } //initialization function initialize(callback) { $page.setTitle('Onde quer ir?'); $scope.entity = entityMap[$routeParams.entity]; if (!$scope.entity) { return; } switch ($routeParams.action) { case 'edit': if (!$routeParams.id) { $page.setTitle($scope.entity.title + ' - Novo Registo'); get($routeParams.entity, null, callback); } else { $page.setTitle($scope.entity.title + ' - Alterar Registo'); get($routeParams.entity, $routeParams.id, callback); } break; case 'view': if (!$routeParams.id) { return; } $page.setTitle($scope.entity.title + ' - Visualizar'); get($routeParams.entity, $routeParams.id, callback); break; case 'list': if ($routeParams.id) { return; } $page.setTitle($scope.entity.title); get($routeParams.entity, null, callback); break; default: return; } } initialize(loadFinished); }); app.controller('entityModalCtrl', function($uibModalInstance, $scope) { $scope.ok = function() { $scope.$close($scope.item); }; $scope.cancel = function() { $scope.$dismiss('cancel'); }; }); app.controller('activityCtrl', function($scope) { }); app.controller('alertCtrl', function() { }); app.controller('countryCtrl', function($scope) { }); app.controller('errorCtrl', function($scope) { }); app.controller('holidayCtrl', function(ErrorSvc, $scope, $holiday) { $scope.getError = function(holiday, holidays) { $scope.nextOccurrence = null; if (!holiday) { return ErrorSvc.validationArgs(true); } if (!holiday.name) { return ErrorSvc.validationArgs(true, "Introduza um nome do feriado"); } if (holiday.name.trim() === "") { return ErrorSvc.validationArgs(true, "Introduza um nome do feriado"); } for (var i = 0; i < holidays.length; i++) { if (holidays[i].name == holiday.name && holiday._id != holidays[i]._id) { return ErrorSvc.validationArgs(true, "Feriado já existente"); } } if (!holiday.date && !holiday.formula) { return ErrorSvc.validationArgs(true, "Impossivel calcular próxima ocorrência para este feriado"); } if (holiday.date) { var pattern = /^(?:(?:[12][0-9]|0[1-9])\/(02)|(?:30|[12][0-9]|0[1-9])\/(?:(?:0[469]|11))|(?:3[01]|[12][0-9]|0[1-9])\/(?:(?:0[13578]|1[02])))$/; if (!pattern.test(holiday.date)) { return ErrorSvc.validationArgs(true, "A data deve ser no formato dd/mm"); } } else if (holiday.formula) { try { var currentYear = new Date().getFullYear(); eval('my_function = ' + holiday.formula); var d = my_function(currentYear); if (Object.prototype.toString.call(d) === "[object Date]" ) { //it is a date if (isNaN(d.getTime())) { return ErrorSvc.validationArgs(true, "Data inválida"); } } else { return ErrorSvc.validationArgs(true, "Data inválida"); } } catch(err) { return ErrorSvc.validationArgs(true, "A formula que introduziu contém um erro: " + err); } } return ErrorSvc.validationArgs(false); }; $scope.calculateNextOccurrence = $holiday.calculateNextOccurrence; }); app.controller('messageCtrl', function($scope) { }); app.controller('roleCtrl', function (ErrorSvc, $scope, $role) { $scope.permissions = {}; $role.getPermissions().then(function (response) { $scope.permissions = response.data; }); $scope.togglePermissions = function (value) { if (!$scope.item.permissions) { $scope.item.permissions = {}; } for (var entity in $scope.permissions) { if (!$scope.item.permissions[entity]) { $scope.item.permissions[entity] = {}; } for (var action in $scope.permissions[entity]) { if (action === 'label') { continue; } $scope.item.permissions[entity][action] = value; } } }; $scope.getError = function (role, roles) { if (!role) { return ErrorSvc.validationArgs(true); } if (!role.name) { return ErrorSvc.validationArgs(true, "Introduza um nome para o perfil de utilizador"); } if (role.name.trim() === "") { return ErrorSvc.validationArgs(true, "Introduza um nome para o perfil de utilizador"); } for (var i = 0; i < roles.length; i++) { if (roles[i].name == role.name && roles[i]._id != role._id) { return ErrorSvc.validationArgs(true, "Perfil de utilizador já existente"); } } return ErrorSvc.validationArgs(false); }; $scope.blank = function () { var permissions = {}; for (var entity in $scope.permissions) { permissions[entity] = {}; for (var action in $scope.permissions[entity]) { if (action === 'label') { continue; } permissions[entity][action] = true; } } return { name: "", approvalLevel: 0, admin: false, permissions: permissions }; }; }); //manage tasks Controller app.controller('taskCtrl', function ($scope, $date, ErrorSvc, $task, mySocket, blockUI) { $scope.taskTypes = []; $scope.getError = function (task, tasks) { if (!task) { return ErrorSvc.validationArgs(true); } if (!task.name) { return ErrorSvc.validationArgs(true, "Indique um nome para a tarefa"); } if (task.name.trim() === "") { return ErrorSvc.validationArgs(true, "Indique um nome para a tarefa"); } if (!task.type) { return ErrorSvc.validationArgs(true, "Escolha um tipo de tarefa"); } if (!task.criteria) { return ErrorSvc.validationArgs(true, "Complete o critério de periodicidade"); } if (!task.criteria.month) { return ErrorSvc.validationArgs(true, "Complete o critério de periodicidade"); } if (!task.criteria.monthday) { return ErrorSvc.validationArgs(true, "Complete o critério de periodicidade"); } if (!task.criteria.weekday) { return ErrorSvc.validationArgs(true, "Complete o critério de periodicidade"); } if (!task.criteria.hour) { return ErrorSvc.validationArgs(true, "Complete o critério de periodicidade"); } if (!task.criteria.minute) { return ErrorSvc.validationArgs(true, "Complete o critério de periodicidade"); } return ErrorSvc.validationArgs(false); }; $scope.formatDatetime = $date.formatDatetime; $scope.calculateTimespan = $date.calculateTimespan; $scope.toggleState = $task.toggleState; $scope.run = function (task) { $task.run(task) .then(function (response) { blockUI.start('A executar...'); }) .catch(function (response) { swal('Ocorreu um erro ao iniciar a tarefa manualmente.', response.data, 'error'); }); }; mySocket.on($scope.login._id, function(data) { swal('Tarefa executada com sucesso.', null, 'success'); blockUI.stop(); }); $task.getTypes(function (types) { $scope.taskTypes = types; }); }); app.filter("dateFilter", function(moment) { return function(items, from, to) { if (!from || !to) { return items; } from = moment(from); to = moment(to); var result = []; for (var i = 0; i < items.length; i++) { var date = moment(items[i].data_pagamento); if (date >= from && date <= to) { result.push(items[i]); } } return result; }; }); app.filter('orderObjectBy', function () { return function (items, field, reverse) { var filtered = []; angular.forEach(items, function (item) { filtered.push(item); }); filtered.sort(function (a, b) { return (a[field] > b[field] ? 1 : -1); }); if (reverse) filtered.reverse(); return filtered; }; }); app.directive('zlActivation', function (zapiPath) { return { restrict: 'E', scope: true, replace: false, templateUrl: zapiPath + '/directives/activation/activation.html', controller: function ($scope, $auth, $location, $routeParams) { $scope.user = null; $auth.activateAccount($routeParams.code, function (err, user) { if (err) { return $location.path('/'); } $scope.user = user; }); $scope.changePassword = function (newPassword1, newPassword2) { $auth.changePassword($scope.user, newPassword1, newPassword2, function (err) { if (!err) { return $location.path('/'); } }); }; } }; }); app.directive('zlAddress', function (zapiPath) { return { restrict: 'E', scope: { item: '=', countries: '=' }, replace: false, templateUrl: zapiPath + '/directives/address/address.html', controller: function ($scope, $address, inform) { $scope.getAddressPT = function(zipcode) { $address.getAddressPT(zipcode, function (err, data) { if (err) { $scope.item.address = ""; $scope.item.city = ""; $scope.item.country = ""; $scope.item.district = ""; $scope.item.county = ""; $scope.item.locality = ""; return inform.add("Não foi possível completar o arruamento", { ttl: 2000, type: "warning" }); } $scope.item.address = data.address; $scope.item.city = data.city; $scope.item.country = data.country; $scope.item.district = data.district; $scope.item.county = data.county; $scope.item.locality = data.locality; $address.getCoordinates(zipcode, function (err, coords) { if (!err) { $scope.item.coords = coords; } }); }); }; $scope.validateZipcode = $address.validateZipcode; } }; }); app.directive('console', function(zapiPath) { return { restrict: 'E', scope: true, replace: false, templateUrl: zapiPath + '/directives/console/console.html' }; }); app.directive('zlContainer', function(zapiPath) { return { restrict: 'E', replace: false, templateUrl: zapiPath + '/directives/container/container.html', scope: { entity: '@', item: '=', lookup: '=', label: '@', var: '@', key: '@', isVisible: '&?', isMovable: '@', noInsert: '@', removable: '@', approvalMaxLevel: '@' }, controller: function($scope, $rootScope, $controller, $timeout) { if ($scope.key) { $scope.newContainerItem = {}; } else { $scope.newContainerItem = ""; } $scope.append = function(newContainerItem) { append(newContainerItem, function(err) { if (err) { return swal("Atenção", err, "warning"); } //clear new container item fields if ($scope.key) { $scope.newContainerItem = {}; } else { $scope.newContainerItem = ""; } }); }; $scope.remove = function(containerItem) { var index = $scope.item[$scope.var].indexOf(containerItem); if (index != -1) { $scope.item[$scope.var].splice(index, 1); } }; $scope.validate = function(newContainerItem) { if (!newContainerItem) { return true; } if ($scope.key) { if (Object.keys(newContainerItem).length === 0 && JSON.stringify(newContainerItem) === JSON.stringify({})) { return true; } } if (typeof $scope.getError === 'function') { var response = $scope.getError(newContainerItem, $scope.item[$scope.var]); if (response.disabled) { $scope.console = response.tooltip; return true; } } $scope.console = null; return false; }; $scope.pushBack = function(index) { var _containerItem = $scope.item[$scope.var].splice(index, 1); $scope.item[$scope.var].splice(index - 1, 0, _containerItem[0]); }; $scope.pushForward = function(index) { var _containerItem = $scope.item[$scope.var].splice(index, 1); $scope.item[$scope.var].splice(index + 1, 0, _containerItem[0]); }; //$timeout is only ready after directive has fully loaded //this way when the controller loads, $scope will be fully populated $timeout(function() { //inject child controller $controller($scope.var + 'Ctrl', {$scope: $scope}); }); function append(newContainerItem, callback) { var i, property; if (!$scope.item[$scope.var]) { $scope.item[$scope.var] = []; } //check if containerItem is not empty if ($scope.key) { var key = { property: null, key: null }; if (newContainerItem[$scope.key]) { key.key = newContainerItem[$scope.key]; } else { for (property in newContainerItem) { if (newContainerItem[property][$scope.key]) { key.property = property; key.key = newContainerItem[property][$scope.key]; break; } } } if (key.key) { //check if containerItem already exists in container for (i = 0; i < $scope.item[$scope.var].length; i++) { var obj = key.property ? $scope.item[$scope.var][i][key.property] : $scope.item[$scope.var][i]; if (obj[$scope.key] === key.key) { return callback('Registo duplicado em ' + $scope.label); } } } } else { for (i = 0; i < $scope.item[$scope.var].length; i++) { if ($scope.item[$scope.var][i] === newContainerItem) { return callback('Registo duplicado em ' + $scope.label); } } } //append item to container $scope.item[$scope.var].unshift(newContainerItem); //approvable containers only if ($scope.approvalMaxLevel) { //assign approval if ($scope.approvalMaxLevel) { newContainerItem.approval = { level: $rootScope.login.role.approvalLevel, maxLevel: $scope.approvalMaxLevel }; } } callback(); } } }; }); app.directive("contenteditable", function() { return { require: "ngModel", link: function(scope, element, attrs, ngModel) { function read() { ngModel.$setViewValue(element.html()); } ngModel.$render = function() { element.html(ngModel.$viewValue || ""); }; element.bind("blur keyup change", function() { scope.$apply(read); }); } }; }); app.directive('zlCountry', function(zapiPath) { return { restrict: 'E', scope: { item: '=', 'var': '@', label: '@', lookup: '=' }, replace: false, templateUrl: zapiPath + '/directives/country/country.html' }; }); app.directive('zlFile', function(zapiPath) { return { restrict: 'A', scope: { item: '=', var: '=', docid: '=' }, replace: false, link: function (scope, element, attrs) { if (scope.docid) { if (scope.docid._id) { scope.docid = scope.docid._id; } } }, templateUrl: zapiPath + '/directives/document/file.html' }; }); app.directive('fileModel', function ($parse) { return { restrict: 'A', replace: false, link: function(scope, element, attrs) { var model = $parse(attrs.fileModel); var modelSetter = model.assign; element.bind('change', function() { scope.$apply(function() { modelSetter(scope, element[0].files[0]); }); }); } }; }); app.directive('zlDynamicField', function (zapiPath) { return { restrict: 'E', scope: { field: '=', value: '=' }, replace: false, template: '<div ng-include src="contentUrl" include-replace></div>', link: function (scope, element, attrs) { var type = scope.field.type || 'Unknown'; if (scope.field.type === 'ObjectId' && typeof scope.field.ref === 'undefined') { type = "String"; } scope.contentUrl = zapiPath + "/directives/dynamicField/" + type + ".html"; } }; }); app.directive('entityEdit', function(zapiPath) { return { restrict: 'E', scope: true, replace: false, link: function(scope, element, attrs) { var scaffoldUrl = scope.entity.custom ? 'client/entity/' : (zapiPath + '/entity/'); scope.contentUrl = scaffoldUrl + scope.entityName + '/' + scope.entityName + '.edit.html'; }, template: '<div ng-include="contentUrl"></div>' }; }); app.directive('entityList', function(zapiPath) { return { restrict: 'E', scope: true, replace: false, link: function(scope, element, attrs) { var scaffoldUrl = scope.entity.custom ? 'client/entity/' : (zapiPath + '/entity/'); scope.contentUrl = scaffoldUrl + scope.entityName + '/' + scope.entityName + '.list.html'; }, template: '<div ng-include src="contentUrl" include-replace></div>' }; }); app.directive('entityView', function(zapiPath) { return { restrict: 'E', scope: true, replace: false, link: function(scope, element, attrs) { var scaffoldUrl = scope.entity.custom ? 'client/entity/' : (zapiPath + 'entity/'); scope.contentUrl = scaffoldUrl + scope.entityName + '/' + scope.entityName + '.view.html'; }, template: '<div ng-include="contentUrl"></div>' }; }); app.directive('zlGraph', function (zapiPath) { return { restrict: 'E', scope: { item: '=' }, replace: false, templateUrl: zapiPath + '/directives/graph/graph.html', controller: function ($scope, blockUI, $graph) { var chartTypes = [ ['bar', 'doughnut', 'pie', 'horizontalBar'], ['line', 'radar'], ['bubble', 'polar-area'] ]; $scope.getNext = function () { var index = chartTypes[$scope.item.dimension].indexOf($scope.item.type) + 1; return chartTypes[$scope.item.dimension][index === chartTypes[$scope.item.dimension].length ? 0 : index]; }; $scope.toggle = function () { $scope.item.type = $scope.getNext(); }; $scope.chart = {}; var graphBlockUI = blockUI.instances.get('graphBlockUI'); graphBlockUI.start("A criar gráfico..."); $graph.get($scope.item.namespace, $scope.item.collection, $scope.item.query, $scope.item.obj, $scope.item.seriesKey, $scope.item.dataKey, $scope.item.labelsKey, function (chart) { $scope.chart = chart; console.log(chart); graphBlockUI.stop(); }); } }; }); app.directive('zlLogin', function (zapiPath) { return { restrict: 'E', scope: true, replace: false, templateUrl: zapiPath + '/directives/login/login.html', controller: function (blockUI, $auth, $location, $scope, $uibModalStack) { $scope.login = function () { if ($scope.validateLogin()) { return; } $auth.login($scope.username, $scope.password, function (err) { if (err) { reset(); } else { $location.path('/profile'); $uibModalStack.dismissAll(); } }); }; $scope.validateLogin = function () { if (!$scope.username) { return true; } if (!$scope.password) { return true; } return false; }; function reset() { $scope.username = ""; $scope.password = ""; } } }; }); app.directive('zlNewsletter', function (zapiPath, blockUI, inform, $entity) { return { restrict: 'E', scope: true, replace: false, templateUrl: zapiPath + '/directives/newsletter/newsletter.html', controller: function ($scope, $newsletter) { $scope.saveEmail = function (item) { $newsletter.subscribe(item, function () { $scope.newsItem = {}; }); }; } }; }); app.directive('asDate', function () { return { require: '^ngModel', restrict: 'A', link: function (scope, element, attrs, ctrl) { ctrl.$formatters.splice(0, ctrl.$formatters.length); ctrl.$parsers.splice(0, ctrl.$parsers.length); ctrl.$formatters.push(function (modelValue) { if (!modelValue) { return; } return new Date(modelValue).toISOString().slice(0,10); }); ctrl.$parsers.push(function (modelValue) { return modelValue; }); } }; }); app.directive('dlKeyCode', dlKeyCode); function dlKeyCode() { return { restrict: 'A', link: function($scope, $element, $attrs) { $element.bind("keypress", function(event) { var keyCode = event.which || event.keyCode; if (keyCode == $attrs.code) { $scope.$apply(function() { $scope.$eval($attrs.dlKeyCode, {$event: event}); }); } }); } }; } app.directive('asFloat', function () { return { require: '^ngModel', restrict: 'A', link: function (scope, element, attrs, ctrl) { ctrl.$formatters.splice(0, ctrl.$formatters.length); ctrl.$parsers.splice(0, ctrl.$parsers.length); ctrl.$formatters.push(function (modelValue) { if (!modelValue) { return; } return parseFloat(modelValue); }); ctrl.$parsers.push(function (modelValue) { return modelValue; }); } }; }); app.directive('asInteger', function () { return { require: '^ngModel', restrict: 'A', link: function (scope, element, attrs, ctrl) { ctrl.$formatters.splice(0, ctrl.$formatters.length); ctrl.$parsers.splice(0, ctrl.$parsers.length); ctrl.$formatters.push(function (modelValue) { if (!modelValue) { return; } return parseInt(modelValue); }); ctrl.$parsers.push(function (modelValue) { return modelValue; }); } }; }); app.directive('lettersOnly', function () { return { require: 'ngModel', link: function (scope, element, attr, ngModelCtrl) { function onlyLetters(text) { if (text) { var transformedInput = text.replace(/[^A-Za-z]/g, ''); if (transformedInput !== text) { ngModelCtrl.$setViewValue(transformedInput); ngModelCtrl.$render(); } return transformedInput; } return undefined; } if (attr.lettersOnly === 'true') { ngModelCtrl.$parsers.push(onlyLetters); } } }; }); app.directive('numbersOnly', function () { return { require: 'ngModel', link: function (scope, element, attr, ngModelCtrl) { function onlyNumbers(text) { if (text) { var transformedInput = text.replace(/[^0-9]/g, ''); if (transformedInput !== text) { ngModelCtrl.$setViewValue(transformedInput); ngModelCtrl.$render(); } return transformedInput; } return undefined; } if (attr.numbersOnly === 'true') { ngModelCtrl.$parsers.push(onlyNumbers); } } }; }); app.directive('zlQueryBuilder', function (zapiPath) { return { restrict: 'E', scope: false, replace: false, template: zapiPath + '/directives/queryBuilder/queryBuilder.html', controller: function($scope) { $scope.choices = [ { key: '$exists', caption: 'existe', template: 'Boolean.html' }, { key: '' } ]; } }; }); /* <option ng-value="'exists'">existe</option> <option ng-value="'startsWith'">começa com</option> <option ng-value="'endsWith'">acaba em</option> <option ng-value="'contains'">contém</option> <option ng-value="'equals'">igual a</option> <option ng-value="'notEquals'">diferente de</option> <option ng-value="'in'">algum de</option> <option ng-value="'nin'">nenhum de</option> <option ng-value="'gt'">gt</option> <option ng-value="'gte'">gte</option> <option ng-value="'lt'">lt</option> <option ng-value="'lte'">lte</option> <option ng-value="'inclusiveBetween'">entre (inclusive)</option> <option ng-value="'exclusiveBetween'">entre (exclusive)</option> */ app.directive('zlReset', function (zapiPath) { return { restrict: 'E', scope: true, replace: false, templateUrl: zapiPath + '/directives/reset/reset.html', controller: function ($scope, $auth, $location, blockUI) { $scope.reset = function (item) { if ($scope.validateReset(item)) { return; } $auth.resetPassword(item.email, item.username, function (err) { if (!err) { return $location.path("/"); } }); }; $scope.validateReset = function (item) { if (!item) { return true; } if (!item.email) { return true; } if (!item.username) { return true; } return false; }; } }; }); app.directive('zlSignup', function (zapiPath) { return { restrict: 'E', scope: true, replace: false, templateUrl: zapiPath + '/directives/signup/signup.html', controller: function ($scope, blockUI, $entity, $uibModalStack) { $scope.signup = function (item) { if ($scope.validateSignup(item)) { return; } blockUI.start("A reservar conta..."); $entity.add('user', item) .then(function (response) { swal("Está quase!", "Consulte o seu email para ativar a conta de utilizador", "success"); $uibModalStack.dismissAll(); }) .catch(function (response) { swal("Erro", response.data, "error"); }) .finally(function () { blockUI.stop(); }); }; $scope.validateSignup = function (item) { if (!item) { return true; } if (!item.username) { return true; } var validUsername = new RegExp('^[a-zA-Z0-9_]*$'); if (!validUsername.test(item.username)) { return true; } if (!item.email) { return true; } return false; }; } }; }); app.service('$address', function($http, blockUI) { this.getAddressPT = function(zipcode, callback) { if (this.validateZipcode(zipcode)) { return callback("Código postal inválido"); } blockUI.start('A procurar arruamento...'); $http.get("./api/address/pt/" + zipcode) .then(function(response) { callback(null, response.data); }) .catch(function(response) { callback(response); }) .finally(function() { blockUI.stop(); }); }; this.validateZipcode = function(zipcode) { if (!zipcode) { return true; } if (typeof zipcode !== 'string') { zipcode = zipcode + ''; } var cps = zipcode.split('-'); if (cps.length < 1 || cps.length > 2) { return true; } if (isNaN(cps[0])) { return true; } if (cps.length > 1) { if (isNaN(cps[1])) { return true; } } return false; }; this.getCoordinates = function(zipcode, callback) { try { var geocoder = new google.maps.Geocoder(); geocoder.geocode({ 'address': zipcode }, function (results, status) { if (status !== google.maps.GeocoderStatus.OK) { callback(status); } callback(null, { lat: results[0].geometry.location.lat(), lng: results[0].geometry.location.lng() }); }); } catch (error) { callback(error); } }; }); app.factory('$auth', function ($http, $crypto, blockUI) { var service = { currentUser: null, login: function (username, password, callback) { blockUI.start("A autenticar credenciais..."); return $http.post('/api/session/login/', { username: username, password: $crypto.md5(password) }) .then(function (response) { service.currentUser = response.data.user; callback(); }) .catch(function (response) { swal("Login Falhou", response.data, "error"); callback(true); }) .finally(function () { blockUI.stop(); }); }, logout: function (callback) { $http.get('/api/session/logout') .then(function (response) { service.currentUser = null; callback(null, response); }) .catch(function (response) { callback(response); }); }, requestCurrentUser: function (callback) { if (service.isAuthenticated()) { callback(null, service.currentUser); } else { $http.get('/api/session/currentuser') .then(function (response) { service.currentUser = response.data.user; callback(null, service.currentUser); }) .catch(function (response) { service.currentUser = null; callback(response); }); } }, isAuthenticated: function () { return !!service.currentUser; }, activateAccount: function (rawCode, callback) { if (!rawCode) { return callback(true); } var codes = rawCode.split('&'); if (codes.length !== 2) { return callback(true); } blockUI.start("A verificar código de ativação..."); $http.post('/api/user/activate/', { email: codes[0], code: codes[1] }) .then(function (response) { callback(null, response.data); }) .catch(function (response) { swal({ title: "Conta já ativa!", text: "Consulte o email que recebeu com instruções de acesso", type: "info", confirmButtonText: "OK" }); callback(true); }) .finally(function () { blockUI.stop(); }); }, resetPassword: function (email, username, callback) { blockUI.start("A comunicar pedido de recuperação..."); return $http.post('/api/user/resetpassword/', { email: email, username: username }) .then(function (response) { swal("Recuperação de Conta", "A sua conta foi recuperada! Consulte a sua conta de email.", "success"); callback(); }) .catch(function (response) { swal("Erro", response.data, "error"); callback(true); }) .finally(function () { blockUI.stop(); }); }, changePassword: function (user, newPassword1, newPassword2, callback) { if (!newPassword1) { return; } if (newPassword1 !== newPassword2) { return swal("Atenção!", "As palavras chave têm de ser iguais", "error"); } user.newPassword = $crypto.md5(newPassword1); blockUI.start("A atualizar dados..."); $http.post('/api/user/changepassword/', user) .then(function (response) { swal({ title: "Parabéns!", text: "Palavra chave alterada com sucesso!", type: "success", confirmButtonText: "Entrar" }); callback(); }) .catch(function (response) { swal("Erro", "Ocorreu um erro ao criar a sua password, por favor tente novamente mais tarde", "error"); callback(true); }) .finally(function () { blockUI.stop(); }); } }; return service; }); app.service('$crypto', function() { function md5cycle(x, k) { var a = x[0], b = x[1], c = x[2], d = x[3]; a = ff(a, b, c, d, k[0], 7, -680876936); d = ff(d, a, b, c, k[1], 12, -389564586); c = ff(c, d, a, b, k[2], 17, 606105819); b = ff(b, c, d, a, k[3], 22, -1044525330); a = ff(a, b, c, d, k[4], 7, -176418897); d = ff(d, a, b, c, k[5], 12, 1200080426); c = ff(c, d, a, b, k[6], 17, -1473231341); b = ff(b, c, d, a, k[7], 22, -45705983); a = ff(a, b, c, d, k[8], 7, 1770035416); d = ff(d, a, b, c, k[9], 12, -1958414417); c = ff(c, d, a, b, k[10], 17, -42063); b = ff(b, c, d, a, k[11], 22, -1990404162); a = ff(a, b, c, d, k[12], 7, 1804603682); d = ff(d, a, b, c, k[13], 12, -40341101); c = ff(c, d, a, b, k[14], 17, -1502002290); b = ff(b, c, d, a, k[15], 22, 1236535329); a = gg(a, b, c, d, k[1], 5, -165796510); d = gg(d, a, b, c, k[6], 9, -1069501632); c = gg(c, d, a, b, k[11], 14, 643717713); b = gg(b, c, d, a, k[0], 20, -373897302); a = gg(a, b, c, d, k[5], 5, -701558691); d = gg(d, a, b, c, k[10], 9, 38016083); c = gg(c, d, a, b, k[15], 14, -660478335); b = gg(b, c, d, a, k[4], 20, -405537848); a = gg(a, b, c, d, k[9], 5, 568446438); d = gg(d, a, b, c, k[14], 9, -1019803690); c = gg(c, d, a, b, k[3], 14, -187363961); b = gg(b, c, d, a, k[8], 20, 1163531501); a = gg(a, b, c, d, k[13], 5, -1444681467); d = gg(d, a, b, c, k[2], 9, -51403784); c = gg(c, d, a, b, k[7], 14, 1735328473); b = gg(b, c, d, a, k[12], 20, -1926607734); a = hh(a, b, c, d, k[5], 4, -378558); d = hh(d, a, b, c, k[8], 11, -2022574463); c = hh(c, d, a, b, k[11], 16, 1839030562); b = hh(b, c, d, a, k[14], 23, -35309556); a = hh(a, b, c, d, k[1], 4, -1530992060); d = hh(d, a, b, c, k[4], 11, 1272893353); c = hh(c, d, a, b, k[7], 16, -155497632); b = hh(b, c, d, a, k[10], 23, -1094730640); a = hh(a, b, c, d, k[13], 4, 681279174); d = hh(d, a, b, c, k[0], 11, -358537222); c = hh(c, d, a, b, k[3], 16, -722521979); b = hh(b, c, d, a, k[6], 23, 76029189); a = hh(a, b, c, d, k[9], 4, -640364487); d = hh(d, a, b, c, k[12], 11, -421815835); c = hh(c, d, a, b, k[15], 16, 530742520); b = hh(b, c, d, a, k[2], 23, -995338651); a = ii(a, b, c, d, k[0], 6, -198630844); d = ii(d, a, b, c, k[7], 10, 1126891415); c = ii(c, d, a, b, k[14], 15, -1416354905); b = ii(b, c, d, a, k[5], 21, -57434055); a = ii(a, b, c, d, k[12], 6, 1700485571); d = ii(d, a, b, c, k[3], 10, -1894986606); c = ii(c, d, a, b, k[10], 15, -1051523); b = ii(b, c, d, a, k[1], 21, -2054922799); a = ii(a, b, c, d, k[8], 6, 1873313359); d = ii(d, a, b, c, k[15], 10, -30611744); c = ii(c, d, a, b, k[6], 15, -1560198380); b = ii(b, c, d, a, k[13], 21, 1309151649); a = ii(a, b, c, d, k[4], 6, -145523070); d = ii(d, a, b, c, k[11], 10, -1120210379); c = ii(c, d, a, b, k[2], 15, 718787259); b = ii(b, c, d, a, k[9], 21, -343485551); x[0] = add32(a, x[0]); x[1] = add32(b, x[1]); x[2] = add32(c, x[2]); x[3] = add32(d, x[3]); } function cmn(q, a, b, x, s, t) { a = add32(add32(a, q), add32(x, t)); return add32((a << s) | (a >>> (32 - s)), b); } function ff(a, b, c, d, x, s, t) { return cmn((b & c) | ((~b) & d), a, b, x, s, t); } function gg(a, b, c, d, x, s, t) { return cmn((b & d) | (c & (~d)), a, b, x, s, t); } function hh(a, b, c, d, x, s, t) { return cmn(b ^ c ^ d, a, b, x, s, t); } function ii(a, b, c, d, x, s, t) { return cmn(c ^ (b | (~d)), a, b, x, s, t); } function md51(s) { txt = ''; var n = s.length, state = [1732584193, -271733879, -1732584194, 271733878], i; for (i=64; i<=s.length; i+=64) { md5cycle(state, md5blk(s.substring(i-64, i))); } s = s.substring(i-64); var tail = [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0]; for (i=0; i<s.length; i++) tail[i>>2] |= s.charCodeAt(i) << ((i%4) << 3); tail[i>>2] |= 0x80 << ((i%4) << 3); if (i > 55) { md5cycle(state, tail); for (i=0; i<16; i++) tail[i] = 0; } tail[14] = n*8; md5cycle(state, tail); return state; } /* there needs to be support for Unicode here, * unless we pretend that we can redefine the MD-5 * algorithm for multi-byte characters (perhaps * by adding every four 16-bit characters and * shortening the sum to 32 bits). Otherwise * I suggest performing MD-5 as if every character * was two bytes--e.g., 0040 0025 = @%--but then * how will an ordinary MD-5 sum be matched? * There is no way to standardize text to something * like UTF-8 before transformation; speed cost is * utterly prohibitive. The JavaScript standard * itself needs to look at this: it should start * providing access to strings as preformed UTF-8 * 8-bit unsigned value arrays. */ function md5blk(s) { /* I figured global was faster. */ var md5blks = [], i; /* Andy King said do it this way. */ for (i=0; i<64; i+=4) { md5blks[i>>2] = s.charCodeAt(i) + (s.charCodeAt(i+1) << 8) + (s.charCodeAt(i+2) << 16) + (s.charCodeAt(i+3) << 24); } return md5blks; } var hex_chr = '0123456789abcdef'.split(''); function rhex(n) { var s='', j=0; for(; j<4; j++) s += hex_chr[(n >> (j * 8 + 4)) & 0x0F] + hex_chr[(n >> (j * 8)) & 0x0F]; return s; } function hex(x) { for (var i=0; i<x.length; i++) x[i] = rhex(x[i]); return x.join(''); } this.md5 = function(s) { return hex(md51(s)); }; /* this function is much faster, so if possible we use it. Some IEs are the only ones I know of that need the idiotic second function, generated by an if clause. */ var add32 = function(a, b) { return (a + b) & 0xFFFFFFFF; }; if (this.md5('hello') != '5d41402abc4b2a76b9719d911017c592') { add32 = function(x, y) { var lsw = (x & 0xFFFF) + (y & 0xFFFF), msw = (x >> 16) + (y >> 16) + (lsw >> 16); return (msw << 16) | (lsw & 0xFFFF); }; } }); app.factory('$date', function(moment) { var factory = {}; factory.collisionDetection = function(start, end, events) { start = factory.extract(start); end = end ? factory.extract(end) : start; if (end < start) { return null; } for (var i = 0; i < events.length; i++) { /* if (events[i].approval) { if (events[i].approval.cancelled) { continue; } if (events[i].approval.level < events[i].approval.maxLevel) { continue; } } */ var eventStart = factory.extract(events[i].dateStart); var eventEnd = eventStart; if (events[i].dateEnd) { eventEnd = factory.extract(events[i].dateEnd); } // $ |__$__| if (eventEnd > start && eventEnd <= end) { return true; } // |__$__| $ if (eventStart >= start && eventStart < end) { return true; } // $ |____| $ if (eventStart <= start && eventEnd >= end) { return true; } // |___$___$____| if (eventStart >= start && eventEnd <= end) { return true; } } return false; }; factory.extract = function(datetime) { try { return moment(datetime).format('YYYY-MM-DD'); } catch (err) { return factory.extract(new Date(datetime)); } }; factory.extractMinutes = function(time) { try { return factory.pad(time.getHours()) + ':' + factory.pad(time.getMinutes()); } catch (err) { time = new Date(time); return factory.pad(time.getHours()) + ':' + factory.pad(time.getMinutes()); } }; factory.formatDatetime = function(datetime) { if (!datetime) { return '-'; } var d = new Date(datetime); return d.getFullYear() + '-' + factory.pad(d.getMonth() + 1) + '-' + factory.pad(d.getDate()) + ' às ' + factory.pad(d.getHours()) + ':' + factory.pad(d.getMinutes()) + ':' + factory.pad(d.getSeconds()); }; factory.calculateTimespan = function(startTimestamp, endTimestamp) { var end; if (endTimestamp) { end = moment(endTimestamp); } else { end = moment(); } var start = new Date(startTimestamp); var duration = moment.duration(end.diff(start)); var mins = duration.asMinutes().toFixed(2); return mins; }; factory.totalMinutes = function(timeString) { var HourMinutes = timeString.split(':'); if (HourMinutes.length == 1) { HourMinutes.push(0); } var hours = parseInt(HourMinutes[0]); var minutes = parseInt(HourMinutes[1]); return hours * 60 + minutes; }; factory.pad = function(number, count) { var res = number + ""; count = count || 2; while (res.length < count) { res = "0" + res; } return res; }; factory.getTimestamp = function() { var d = new Date(); var timestamp = factory.pad(d.getHours()) + ":" + factory.pad(d.getMinutes()); return timestamp; }; factory.getWeekDay = function(wee