Merge pull request #18770 from civicrm/5.31
[civicrm-core.git] / ext / search / ang / search.module.js
1 (function(angular, $, _) {
2 "use strict";
3
4 // Shared between router and searchMeta service
5 var searchEntity,
6 // For loading saved search
7 savedSearch,
8 undefined;
9
10 // Declare module and route/controller/services
11 angular.module('search', CRM.angRequires('search'))
12
13 .config(function($routeProvider) {
14 $routeProvider.when('/:mode/:entity/:name?', {
15 controller: 'searchRoute',
16 template: '<div id="bootstrap-theme" class="crm-search"><crm-search ng-if="$ctrl.mode === \'create\'" entity="$ctrl.entity" load=":: $ctrl.savedSearch"></crm-search></div>',
17 reloadOnSearch: false,
18 resolve: {
19 // For paths like /load/Group/MySmartGroup, load the group, stash it in the savedSearch variable, and then redirect
20 // For paths like /create/Contact, return the stashed savedSearch if present
21 savedSearch: function($route, $location, $timeout, crmApi4) {
22 var retrievedSearch = savedSearch,
23 getParams,
24 params = $route.current.params;
25 savedSearch = undefined;
26 switch (params.mode) {
27 case 'create':
28 return retrievedSearch;
29
30 case 'load':
31 // Load savedSearch by `id` (the SavedSearch entity doesn't have `name`)
32 if (params.entity === 'SavedSearch' && /^\d+$/.test(params.name)) {
33 getParams = {
34 where: [['id', '=', params.name]]
35 };
36 }
37 // Load attached entity (e.g. Smart Groups) with a join via saved_search_id
38 else if (params.entity === 'Group' && params.name) {
39 getParams = {
40 select: ['id', 'title', 'saved_search_id', 'saved_search.*'],
41 where: [['name', '=', params.name]]
42 };
43 }
44 // In theory savedSearches could be attached to something other than groups, but for now that's not supported
45 else {
46 throw 'Failed to load ' + params.entity;
47 }
48 return crmApi4(params.entity, 'get', getParams, 0).then(function(retrieved) {
49 savedSearch = retrieved;
50 savedSearch.type = params.entity;
51 if (params.entity !== 'SavedSearch') {
52 savedSearch.api_entity = retrieved['saved_search.api_entity'];
53 savedSearch.api_params = retrieved['saved_search.api_params'];
54 savedSearch.form_values = retrieved['saved_search.form_values'];
55 }
56 $timeout(function() {
57 $location.url('/create/' + savedSearch.api_entity);
58 });
59 });
60 }
61 }
62 }
63 });
64 })
65
66 // Controller binds entity to route
67 .controller('searchRoute', function($scope, $routeParams, $location, savedSearch) {
68 searchEntity = this.entity = $routeParams.entity;
69 this.mode = $routeParams.mode;
70 this.savedSearch = savedSearch;
71 $scope.$ctrl = this;
72
73 // Changing entity will refresh the angular page
74 $scope.$watch('$ctrl.entity', function(newEntity, oldEntity) {
75 if (newEntity && oldEntity && newEntity !== oldEntity) {
76 $location.url('/create/' + newEntity);
77 }
78 });
79 })
80
81 .factory('searchMeta', function() {
82 function getEntity(entityName) {
83 if (entityName) {
84 return _.find(CRM.vars.search.schema, {name: entityName});
85 }
86 }
87 function getField(fieldName, entityName) {
88 var dotSplit = fieldName.split('.'),
89 joinEntity = dotSplit.length > 1 ? dotSplit[0] : null,
90 name = _.last(dotSplit).split(':')[0];
91 // Custom fields contain a dot in their fieldname
92 // If 3 segments, the first is the joinEntity and the last 2 are the custom field
93 if (dotSplit.length === 3) {
94 name = dotSplit[1] + '.' + name;
95 }
96 // If 2 segments, it's ambiguous whether this is a custom field or joined field. Search the main entity first.
97 if (dotSplit.length === 2) {
98 var field = _.find(getEntity(entityName).fields, {name: dotSplit[0] + '.' + name});
99 if (field) {
100 return field;
101 }
102 }
103 if (joinEntity) {
104 entityName = _.find(CRM.vars.search.links[entityName], {alias: joinEntity}).entity;
105 }
106 return _.find(getEntity(entityName).fields, {name: name});
107 }
108 return {
109 getEntity: getEntity,
110 getField: getField,
111 parseExpr: function(expr) {
112 var result = {fn: null, modifier: ''},
113 fieldName = expr,
114 bracketPos = expr.indexOf('(');
115 if (bracketPos >= 0) {
116 var parsed = expr.substr(bracketPos).match(/[ ]?([A-Z]+[ ]+)?([\w.:]+)/);
117 fieldName = parsed[2];
118 result.fn = _.find(CRM.vars.search.functions, {name: expr.substring(0, bracketPos)});
119 result.modifier = _.trim(parsed[1]);
120 }
121 result.field = expr ? getField(fieldName, searchEntity) : undefined;
122 if (result.field) {
123 var split = fieldName.split(':'),
124 prefixPos = split[0].lastIndexOf(result.field.name);
125 result.path = split[0];
126 result.prefix = prefixPos > 0 ? result.path.substring(0, prefixPos) : '';
127 result.suffix = !split[1] ? '' : ':' + split[1];
128 }
129 return result;
130 }
131 };
132 })
133
134 // Reformat an array of objects for compatibility with select2
135 // Todo this probably belongs in core
136 .factory('formatForSelect2', function() {
137 return function(input, key, label, extra) {
138 return _.transform(input, function(result, item) {
139 var formatted = {id: item[key], text: item[label]};
140 if (extra) {
141 _.merge(formatted, _.pick(item, extra));
142 }
143 result.push(formatted);
144 }, []);
145 };
146 });
147
148 })(angular, CRM.$, CRM._);