X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=js%2Fangular-crm-util.js;h=3a1a8fd54ff177827d12d023028c6dbe7c0deb58;hb=8935eb8546c4710a52602f0d8f931e03391dafef;hp=1d73ef7bbe2b167304ee9f21c825de51fb8901da;hpb=e7f7aef7265627c99d27b5e9cecdfe5d3e50f7de;p=civicrm-core.git diff --git a/js/angular-crm-util.js b/js/angular-crm-util.js index 1d73ef7bbe..3a1a8fd54f 100644 --- a/js/angular-crm-util.js +++ b/js/angular-crm-util.js @@ -2,14 +2,197 @@ (function (angular, $, _) { angular.module('crmUtil', []); - // Adapter for CRM.status which supports Angular promises (instead of jQuery promises) - // example: crmStatus('Saving', crmApi(...)).then(function(result){...}) - angular.module('crmUtil').factory('crmStatus', function($q){ - return function(options, aPromise){ - return CRM.toAPromise($q, CRM.status(options, CRM.toJqPromise(aPromise))); + // usage: + // crmApi('Entity', 'action', {...}).then(function(apiResult){...}) + // + // Note: To mock API results in unit-tests, override crmApi.backend, e.g. + // var apiSpy = jasmine.createSpy('crmApi'); + // crmApi.backend = apiSpy.and.returnValue(crmApi.val({ + // is_error: 1 + // })); + angular.module('crmUtil').factory('crmApi', function($q) { + var crmApi = function(entity, action, params, message) { + // JSON serialization in CRM.api3 is not aware of Angular metadata like $$hash, so use angular.toJson() + var deferred = $q.defer(); + var p; + var backend = crmApi.backend || CRM.api3; + if (_.isObject(entity)) { + // eval content is locally generated. + /*jshint -W061 */ + p = backend(eval('('+angular.toJson(entity)+')'), message); + } else { + // eval content is locally generated. + /*jshint -W061 */ + p = backend(entity, action, eval('('+angular.toJson(params)+')'), message); + } + // CRM.api3 returns a promise, but the promise doesn't really represent errors as errors, so we + // convert them + p.then( + function(result) { + if (result.is_error) { + deferred.reject(result); + } else { + deferred.resolve(result); + } + }, + function(error) { + deferred.reject(error); + } + ); + return deferred.promise; + }; + crmApi.backend = null; + crmApi.val = function(value) { + var d = $.Deferred(); + d.resolve(value); + return d.promise(); + }; + return crmApi; + }); + + // Get and cache the metadata for an API entity. + // usage: + // $q.when(crmMetadata.getFields('MyEntity'), function(fields){ + // console.log('The fields are:', options); + // }); + angular.module('crmUtil').factory('crmMetadata', function($q, crmApi) { + + // Convert {key:$,value:$} sequence to unordered {$key: $value} map. + function convertOptionsToMap(options) { + var result = {}; + angular.forEach(options, function(o) { + result[o.key] = o.value; + }); + return result; + } + + var cache = {}; // cache[entityName+'::'+action][fieldName].title + var deferreds = {}; // deferreds[cacheKey].push($q.defer()) + var crmMetadata = { + // usage: $q.when(crmMetadata.getField('MyEntity', 'my_field')).then(...); + getField: function getField(entity, field) { + return $q.when(crmMetadata.getFields(entity)).then(function(fields){ + return fields[field]; + }); + }, + // usage: $q.when(crmMetadata.getFields('MyEntity')).then(...); + // usage: $q.when(crmMetadata.getFields(['MyEntity', 'myaction'])).then(...); + getFields: function getFields(entity) { + var action = '', cacheKey; + if (_.isArray(entity)) { + action = entity[1]; + entity = entity[0]; + cacheKey = entity + '::' + action; + } else { + cacheKey = entity; + } + + if (_.isObject(cache[cacheKey])) { + return cache[cacheKey]; + } + + var needFetch = _.isEmpty(deferreds[cacheKey]); + deferreds[cacheKey] = deferreds[cacheKey] || []; + var deferred = $q.defer(); + deferreds[cacheKey].push(deferred); + + if (needFetch) { + crmApi(entity, 'getfields', {action: action, sequential: 1, options: {get_options: 'all'}}) + .then( + // on success: + function(fields) { + cache[cacheKey] = _.indexBy(fields.values, 'name'); + angular.forEach(cache[cacheKey],function (field){ + if (field.options) { + field.optionsMap = convertOptionsToMap(field.options); + } + }); + angular.forEach(deferreds[cacheKey], function(dfr) { + dfr.resolve(cache[cacheKey]); + }); + delete deferreds[cacheKey]; + }, + // on error: + function() { + cache[cacheKey] = {}; // cache nack + angular.forEach(deferreds[cacheKey], function(dfr) { + dfr.reject(); + }); + delete deferreds[cacheKey]; + } + ); + } + + return deferred.promise; + } }; + + return crmMetadata; }); + // usage: + // var block = $scope.block = crmBlocker(); + // $scope.save = function() { return block(crmApi('MyEntity','create',...)); }; + // + angular.module('crmUtil').factory('crmBlocker', function() { + return function() { + var blocks = 0; + var result = function(promise) { + blocks++; + return promise.finally(function() { + blocks--; + }); + }; + result.check = function() { + return blocks > 0; + }; + return result; + }; + }); + + angular.module('crmUtil').factory('crmLegacy', function() { + return CRM; + }); + + // example: scope.$watch('foo', crmLog.wrap(function(newValue, oldValue){ ... })); + angular.module('crmUtil').factory('crmLog', function(){ + var level = 0; + var write = console.log; + function indent() { + var s = '>'; + for (var i = 0; i < level; i++) s = s + ' '; + return s; + } + var crmLog = { + log: function(msg, vars) { + write(indent() + msg, vars); + }, + wrap: function(label, f) { + return function(){ + level++; + crmLog.log(label + ": start", arguments); + var r; + try { + r = f.apply(this, arguments); + } finally { + crmLog.log(label + ": end"); + level--; + } + return r; + }; + } + }; + return crmLog; + }); + + angular.module('crmUtil').factory('crmNavigator', ['$window', function($window) { + return { + redirect: function(path) { + $window.location.href = path; + } + }; + }]); + angular.module('crmUtil').factory('crmNow', function($q){ // FIXME: surely there's already some helper which can do this in one line? // @return string "YYYY-MM-DD hh:mm:ss" @@ -30,6 +213,18 @@ }; }); + // Adapter for CRM.status which supports Angular promises (instead of jQuery promises) + // example: crmStatus('Saving', crmApi(...)).then(function(result){...}) + angular.module('crmUtil').factory('crmStatus', function($q){ + return function(options, aPromise){ + if (aPromise) { + return CRM.toAPromise($q, CRM.status(options, CRM.toJqPromise(aPromise))); + } else { + return CRM.toAPromise($q, CRM.status(options)); + } + }; + }); + // crmWatcher allows one to setup event listeners and temporarily suspend // them en masse. // @@ -89,38 +284,7 @@ }; return this; - } - }); - - // example: scope.$watch('foo', crmLog.wrap(function(newValue, oldValue){ ... })); - angular.module('crmUtil').factory('crmLog', function(){ - var level = 0; - var write = console.log; - function indent() { - var s = '>'; - for (var i = 0; i < level; i++) s = s + ' '; - return s; - } - var crmLog = { - log: function(msg, vars) { - write(indent() + msg, vars); - }, - wrap: function(label, f) { - return function(){ - level++; - crmLog.log(label + ": start", arguments); - var r; - try { - r = f.apply(this, arguments); - } finally { - crmLog.log(label + ": end"); - level--; - } - return r; - } - } }; - return crmLog; }); })(angular, CRM.$, CRM._);