summaryrefslogtreecommitdiff
path: root/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmUtil.js
diff options
context:
space:
mode:
Diffstat (limited to 'www/crm/wp-content/plugins/civicrm/civicrm/ang/crmUtil.js')
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ang/crmUtil.js361
1 files changed, 361 insertions, 0 deletions
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmUtil.js b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmUtil.js
new file mode 100644
index 00000000..ab460ab7
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ang/crmUtil.js
@@ -0,0 +1,361 @@
+/// crmUi: Sundry UI helpers
+(function (angular, $, _) {
+ angular.module('crmUtil', CRM.angRequires('crmUtil'));
+
+ // Angular implementation of CRM.api3
+ // @link http://wiki.civicrm.org/confluence/display/CRMDOC/AJAX+Interface#AJAXInterface-CRM.api3
+ //
+ // 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 (params && params.body_html) {
+ // CRM-18474 - remove Unicode Character 'LINE SEPARATOR' (U+2028)
+ // and 'PARAGRAPH SEPARATOR' (U+2029) from the html if present.
+ params.body_html = params.body_html.replace(/([\u2028]|[\u2029])/g, '\n');
+ }
+ if (_.isObject(entity)) {
+ // eval content is locally generated.
+ /*jshint -W061 */
+ p = backend(eval('('+angular.toJson(entity)+')'), action);
+ } 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',...)); };
+ // <button ng-click="save()" ng-disabled="block.check()">Do something</button>
+ 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;
+ }
+ };
+ }]);
+
+ // Wrap an async function in a queue, ensuring that independent async calls are issued in strict sequence.
+ // usage: qApi = crmQueue(crmApi); qApi(entity,action,...).then(...); qApi(entity2,action2,...).then(...);
+ // This is similar to promise-chaining, but allows chaining independent procs (without explicitly sharing promises).
+ angular.module('crmUtil').factory('crmQueue', function($q) {
+ // @param worker A function which generates promises
+ return function crmQueue(worker) {
+ var queue = [];
+ function next() {
+ var task = queue[0];
+ worker.apply(null, task.a).then(
+ function onOk(data) {
+ queue.shift();
+ task.dfr.resolve(data);
+ if (queue.length > 0) next();
+ },
+ function onErr(err) {
+ queue.shift();
+ task.dfr.reject(err);
+ if (queue.length > 0) next();
+ }
+ );
+ }
+ function enqueue() {
+ var dfr = $q.defer();
+ queue.push({a: arguments, dfr: dfr});
+ if (queue.length === 1) {
+ next();
+ }
+ return dfr.promise;
+ }
+ return enqueue;
+ };
+ });
+
+ // 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.
+ //
+ // example:
+ // angular.controller(... function($scope, crmWatcher){
+ // var watcher = crmWatcher();
+ // function myfunc() {
+ // watcher.suspend('foo', function(){
+ // ...do stuff...
+ // });
+ // }
+ // watcher.setup('foo', function(){
+ // return [
+ // $scope.$watch('foo', myfunc),
+ // $scope.$watch('bar', myfunc),
+ // $scope.$watch('whiz', otherfunc)
+ // ];
+ // });
+ // });
+ angular.module('crmUtil').factory('crmWatcher', function(){
+ return function() {
+ var unwatches = {}, watchFactories = {}, suspends = {};
+
+ // Specify the list of watches
+ this.setup = function(name, newWatchFactory) {
+ watchFactories[name] = newWatchFactory;
+ unwatches[name] = watchFactories[name]();
+ suspends[name] = 0;
+ return this;
+ };
+
+ // Temporarily disable watches and run some logic
+ this.suspend = function(name, f) {
+ suspends[name]++;
+ this.teardown(name);
+ var r;
+ try {
+ r = f.apply(this, []);
+ } finally {
+ if (suspends[name] === 1) {
+ unwatches[name] = watchFactories[name]();
+ if (!angular.isArray(unwatches[name])) {
+ unwatches[name] = [unwatches[name]];
+ }
+ }
+ suspends[name]--;
+ }
+ return r;
+ };
+
+ this.teardown = function(name) {
+ if (!unwatches[name]) return;
+ _.each(unwatches[name], function(unwatch){
+ unwatch();
+ });
+ delete unwatches[name];
+ };
+
+ return this;
+ };
+ });
+
+ // Run a given function. If it is already running, wait for it to finish before running again.
+ // If multiple requests are made before the first request finishes, all but the last will be ignored.
+ // This prevents overwhelming the server with redundant queries during e.g. an autocomplete search while the user types.
+ // Given function should return an angular promise. crmThrottle will deliver the contents when resolved.
+ angular.module('crmUtil').factory('crmThrottle', function($q) {
+ var pending = [],
+ executing = [];
+ return function(func) {
+ var deferred = $q.defer();
+
+ function checkResult(result, success) {
+ _.pull(executing, func);
+ if (_.includes(pending, func)) {
+ runNext();
+ } else if (success) {
+ deferred.resolve(result);
+ } else {
+ deferred.reject(result);
+ }
+ }
+
+ function runNext() {
+ executing.push(func);
+ _.pull(pending, func);
+ func().then(function(result) {
+ checkResult(result, true);
+ }, function(result) {
+ checkResult(result, false);
+ });
+ }
+
+ if (!_.includes(executing, func)) {
+ runNext();
+ } else if (!_.includes(pending, func)) {
+ pending.push(func);
+ }
+ return deferred.promise;
+ };
+ });
+
+ angular.module('crmUtil').factory('crmLoadScript', function($q) {
+ return function(url) {
+ var deferred = $q.defer();
+
+ CRM.loadScript(url).done(function() {
+ deferred.resolve(true);
+ });
+
+ return deferred.promise;
+ };
+ });
+
+})(angular, CRM.$, CRM._);