PageSourceSearch

https://moncountyfamilysupportcenter.org/wp-content/uploads/civicr…modules.9a0d6a88d36cc6197e5af92dff3a6dbf.js

js moncountyfamilysupportcenter.org collected 2026-10-08 04:37:27 UTC 394,269 bytes, 6,990 lines download raw bytes

1(function(angular, $, _) {
2  angular.module('ng')
3    .config(['$locationProvider', function($locationProvider) {
4      $locationProvider.hashPrefix('');
5    }]);
6
7
8angular.module('af', CRM.angRequires('af'));
9
10  angular.module('af').directive('afButton', function() {
11    return {
12      restrict: 'C',
13      bindToController: {
14        type: '@'
15      },
16      require: {
17        afForm: '?^^afForm',
18      },
19      controller: function($scope, $element) {
20        const ctrl = this;
21
22        this.$onInit = () => {
23          const type = $element.attr('type');
24          if (type === 'reset') {
25            $element.on('click', (e) => {
26              e.preventDefault();
27              $scope.$apply(() => {
28                if (this.afForm) {
29                  this.afForm.resetForm();
30                }
31                else {
32                  $scope.$parent.$broadcast('afFormReset');
33                }
34              });
35            });
36          }
37        };
38      }
39    };
40  });
41
42  angular.module('af').component('afCharacterCount', {
43    bindings: {
44      maxlength: '<',
45      getter: '<',
46    },
47    templateUrl: '~/af/afCharacterCount.html',
48    controller: function($scope, $element) {
49      this.$onInit = () => {
50      };
51
52      this.getLength = function() {
53        return (this.getter() || '').length;
54      };
55
56      this.getStatusClass = function() {
57        let fraction = this.getLength() / (this.maxlength || 1);
58        if (fraction > 0.9) {
59          return 'text-danger';
60        }
61        if (fraction > 0.7) {
62          return 'text-warning';
63        }
64        return 'text-success';
65      };
66
67    }
68  });
69
70const modelProps = {
71    type: '@',
72    data: '=',
73    actions: '=',
74    modelName: '@name',
75    label: '@'
76  };
77  angular.module('af').component('afEntity', {
78    require: {afForm: '^afForm'},
79    bindings: modelProps,
80    controller: function() {
81
82      this.$onInit = () => {
83        const entity = Object.keys(modelProps).reduce((obj, key) => {
84          if (this[key] !== undefined) {
85            obj[key] = this[key];
86          }
87          return obj;
88        }, {});
89        entity.actions = entity.actions || {update: true, create: true};
90        entity.id = null;
91        this.afForm.registerEntity(entity);
92      };
93    }
94
95  });
96
97let afFieldId = 0;
98  angular.module('af').component('afField', {
99    require: {
100      afForm: '?^^afForm',
101      afFieldset: '?^^afFieldset',
102      afJoin: '?^^afJoin',
103      afRepeatItem: '?^^afRepeatItem'
104    },
105    templateUrl: '~/af/afField.html',
106    bindings: {
107      fieldName: '@name',
108      defn: '='
109    },
110    controller: function($scope, $element, crmApi4, $timeout) {
111      const ts = $scope.ts = CRM.ts('org.civicrm.afform');
112      const ctrl = this;
113      let namePrefix = '';
114      let fieldOptions = null;
115      let isExtra = false;
116      this.inputAttrs = [];
117
118      this.$onInit = () => {
119        if (!this.fieldName) {
120          isExtra = true;
121          $scope.dataProvider = this.afForm;
122          this.fieldName = this.defn.name;
123        } else {
124          const closestController = $($element).closest('[af-fieldset],[af-join],[af-repeat-item]');
125          $scope.dataProvider = closestController.is('[af-repeat-item]') ? this.afRepeatItem : this.afJoin || this.afFieldset;
126        }
127        $scope.fieldId = _.kebabCase(this.fieldName) + '-' + afFieldId++;
128
129        $element.addClass('af-field-type-' + _.kebabCase(this.defn.input_type));
130
131        if (this.defn.input_attrs && this.defn.input_attrs.multiple) {
132          $element.addClass('af-field-type-multiple');
133        }
134
135        this.fkEntity = this.defn.fk_entity || null;
136
137        if (this.defn.name !== this.fieldName) {
138          if (!this.defn.name) {
139            console.error('Missing field definition for: ' + this.fieldName);
140            return;
141          }
142          namePrefix = this.fieldName.substr(0, this.fieldName.length - this.defn.name.length);
143        }
144
145        if (this.defn.search_operator) {
146          this.search_operator = this.defn.search_operator;
147        }
148
149        fieldOptions = this.defn.options || null;
150        if (this.defn.data_type === 'Boolean') {
151          if (Array.isArray(fieldOptions)) {
152            fieldOptions.forEach((option) => option.id = !!option.id);
153          } else {
154            fieldOptions = [{id: true, label: ts('Yes')}, {id: false, label: ts('No')}];
155          }
156        }
157        if (this.fieldName === 'is_primary' && 'repeatIndex' in $scope.dataProvider) {
158          fieldOptions = [{id: true, label: ''}];
159          $scope.$watch('dataProvider.afRepeat.getEntityController().getData()', function (items, prev) {
160            const index = $scope.dataProvider.repeatIndex;
161            if (items && !index && !items.some((item) => item.is_primary)) {
162              $scope.dataProvider.getFieldData().is_primary = true;
163            }
164            if (items && prev &&
165              items.length === prev.length &&
166              items[index].is_primary &&
167              prev[index].is_primary &&
168              items.filter((item) => item.is_primary).length > 1
169            ) {
170              $scope.dataProvider.getFieldData().is_primary = false;
171            }
172          }, true);
173        }
174        const tokens = this.afForm?.identifyTokens(this.defn.afform_default);
175        if (tokens && tokens.length) {
176          const calculateValueWatcher = $scope.$watchCollection(() => Object.values(this.afForm.getTokenValues(tokens)), () => {
177            if ($element[0].querySelector('.ng-touched')) {
178              calculateValueWatcher();
179              return;
180            }
181            const calculatedValue = this.afForm.replaceTokens(this.defn.afform_default);
182            setValue(calculatedValue);
183          });
184        }
185        if (this.defn.input_type === 'ChainSelect' && this.defn.input_attrs.control_field) {
186          const controlField = namePrefix + this.defn.input_attrs.control_field;
187          $scope.$watch('dataProvider.getFieldData()["' + controlField + '"]', function(val) {
188            if (val && (typeof val === 'number' || val.length)) {
189              $('input[crm-ui-select]', $element).addClass('loading').prop('disabled', true);
190              const params = {
191                name: ctrl.afFieldset.getFormName(),
192                modelName: ctrl.afFieldset.getName(),
193                fieldName: ctrl.fieldName,
194                joinEntity: ctrl.afJoin ? ctrl.afJoin.entity : null,
195                values: $scope.dataProvider.getFieldData()
196              };
197              crmApi4('Afform', 'getOptions', params)
198                .then((data) => {
199                  $('input[crm-ui-select]', $element).removeClass('loading').prop('disabled', !data.length);
200                  fieldOptions = data;
201                });
202            } else {
203              fieldOptions = null;
204            }
205          }, true);
206        }
207        if (this.defn.input_type === 'ChainSelect' || (Array.isArray(fieldOptions) && fieldOptions.some((o) => o && o.if && o.if.length))) {
208          $scope.$watchCollection(() => $scope.getOptions().map((o) => o.id), () => this.validateValue());
209        }
210        if (this.defn.input_type === 'EntityRef' && this.defn.dfk_entities && this.defn.input_attrs.control_field) {
211          const controlField = namePrefix + this.defn.input_attrs.control_field;
212          $scope.$watch('dataProvider.getFieldData()["' + controlField + '"]', (val) => {
213            if (val && val.length) {
214              if (Array.isArray(val)) {
215                this.fkEntity = this.defn.dfk_entities[val[0]];
216              } else {
217                this.fkEntity = this.defn.dfk_entities[val];
218              }
219            } else {
220              this.fkEntity = null;
221            }
222          });
223        }
224        $timeout(() => {
225          initializeValue(true);
226          $scope.$watch('$parent.routeParams', setValueFromRouteParams);
227        });
228        function setValueFromRouteParams(routeParams) {
229          if (!routeParams) {
230            return;
231          }
232          const entityName = ctrl.afFieldset?.getName();
233          const joinEntity = ctrl.afJoin?.entity;
234          let uniquePrefix = '';
235          if (!isExtra && entityName) {
236            const index = ctrl.getEntityIndex();
237            uniquePrefix = entityName + (index ? index + 1 : '') + (joinEntity ? '.' + joinEntity : '') + '.';
238          }
239          if ((uniquePrefix + ctrl.fieldName) in routeParams) {
240            setValue(routeParams[uniquePrefix + ctrl.fieldName]);
241          }
242          else if (ctrl.fieldName in routeParams) {
243            setValue(routeParams[ctrl.fieldName]);
244          }
245          else if (!isExtra && routeParams._s) {
246            setValue(ctrl.afFieldset.getSearchParamSetFieldValue(ctrl.fieldName));
247          }
248        }
249
250        function initializeValue(firstLoad) {
251          if (firstLoad && ctrl.afFieldset?.getStoredValue(ctrl.fieldName) !== undefined) {
252            setValue(ctrl.afFieldset.getStoredValue(ctrl.fieldName));
253          }
254          else if ('afform_default' in ctrl.defn) {
255            if (ctrl.afForm?.identifyTokens(ctrl.defn.afform_default)) {
256              setValue(ctrl.afForm.replaceTokens(ctrl.defn.afform_default));
257            }
258            else {
259              setValue(ctrl.defn.afform_default);
260            }
261          }
262
263          if (ctrl.defn.search_range) {
264            const initialVal = $scope.dataProvider.getFieldData()[ctrl.fieldName];
265            if (!Array.isArray($scope.dataProvider.getFieldData()[ctrl.fieldName]) &&
266              (ctrl.defn.input_type !== 'Select' || !ctrl.defn.is_date || initialVal === '{}')
267            ) {
268              $scope.dataProvider.getFieldData()[ctrl.fieldName] = {};
269            }
270            if (ctrl.defn.is_date) {
271              ctrl.inputAttrs.push(ctrl.defn.input_attrs || {});
272              for (let i = 1; i <= 2; ++i) {
273                const attrs = _.cloneDeep(ctrl.defn.input_attrs || {});
274                attrs.placeholder = attrs['placeholder' + i];
275                attrs.timePlaceholder = attrs['timePlaceholder' + i];
276                ctrl.inputAttrs.push(attrs);
277              }
278            }
279          }
280        }
281        $scope.$on('afFormReset', function() {
282          delete $scope.dataProvider.getFieldData()[ctrl.fieldName];
283          initializeValue(false);
284        });
285      };
286      $scope.$on('afIfDestroy', function() {
287        if (ctrl.defn.input_type !== 'DisplayOnly') {
288          delete $scope.dataProvider.getFieldData()[ctrl.fieldName];
289        }
290      });
291      function correctValueType(value, dataType) {
292        if (value === null) {
293          return value;
294        }
295        if (Array.isArray(value)) {
296          return value.map((val) => correctValueType(val, dataType));
297        } else if (dataType === 'Integer' || dataType === 'Float') {
298          return Number(value);
299        } else if (dataType === 'Boolean') {
300          return (value == 1);
301        }
302        return value;
303      }
304
305      this.isMultiple = function() {
306        return (
307          (['Select', 'EntityRef', 'ChainSelect'].includes(ctrl.defn.input_type) && ctrl.defn.input_attrs.multiple) ||
308          ((ctrl.defn.input_type === 'CheckBox' || ctrl.defn.input_type === 'Toggle') && ctrl.defn.data_type !== 'Boolean') ||
309          ((ctrl.defn.input_type === 'Hidden' || ctrl.defn.input_type === 'DisplayOnly') && (ctrl.defn.serialize || ctrl.defn.data_type === 'Array'))
310        );
311      };
312      function setValue(value) {
313        if (typeof value === 'string' && ctrl.isMultiple()) {
314          value = value.split(',');
315        }
316        if (typeof value === 'object' && value !== null && !Array.isArray(value) && ctrl.search_operator) {
317          if (ctrl.defn.expose_operator) {
318            ctrl.search_operator = Object.keys(value)[0];
319          }
320          value = value[ctrl.search_operator] ? value[ctrl.search_operator] : null;
321        }
322        if (ctrl.defn.input_type === 'EntityRef' && ['Contact', 'Individual'].includes(ctrl.fkEntity) && value === 'user_contact_id') {
323          value = CRM.config.cid;
324        }
325        if (ctrl.defn.input_type !== 'DisplayOnly') {
326          value = correctValueType(value, ctrl.defn.data_type);
327        }
328
329        if (ctrl.defn.input_type === 'Date' && typeof value === 'string' && value.startsWith('now')) {
330          value = getRelativeDate(value, ctrl.defn.input_attrs.time);
331        }
332        if (ctrl.defn.input_type === 'Number' && ctrl.defn.search_range) {
333          if (!_.isPlainObject(value)) {
334            value = {
335              '>=': +(('' + value).split('-')[0] || 0),
336              '<=': +(('' + value).split('-')[1] || 0),
337            };
338          }
339        } else if (ctrl.defn.input_type === 'Number') {
340          value = Number(value);
341        }
342        else if (ctrl.defn.search_range && !_.isPlainObject(value) &&
343          !(ctrl.defn.options && ctrl.defn.options.some((option) => option.id === value))
344        ) {
345          value = {
346            '>=': ('' + value).split('-')[0],
347            '<=': ('' + value).split('-')[1] || '',
348          };
349        }
350        $scope.getSetValue(value);
351      }
352      ctrl.getEntityIndex = function() {
353        if ('repeatIndex' in $scope.dataProvider && $scope.dataProvider.afRepeat.getRepeatType() === 'join') {
354          return $scope.dataProvider.outerRepeatItem ? $scope.dataProvider.outerRepeatItem.repeatIndex : 0;
355        } else {
356          return ctrl.afRepeatItem ? ctrl.afRepeatItem.repeatIndex : 0;
357        }
358      };
359
360      ctrl.isReadonly = function() {
361        if (!isExtra && ctrl.defn.input_attrs && ctrl.defn.input_attrs.autofill && !ctrl.afJoin) {
362          return ctrl.afFieldset.getEntity()?.actions?.[ctrl.defn.input_attrs.autofill] === false;
363        }
364        return ctrl.defn.input_type === 'DisplayOnly';
365      };
366
367      ctrl.isDisabled = function() {
368        if (ctrl.isReadonly()) {
369          return true;
370        }
371        return ctrl.defn.input_type === 'EntityRef' && !ctrl.fkEntity;
372      };
373
374      ctrl.getDisplayValue = function(value) {
375        if (value === undefined || value === null || value === '' || (Array.isArray(value) && !value.length)) {
376          return '';
377        }
378        if (Array.isArray(fieldOptions)) {
379          let keys = Array.isArray(value) ? value : [value];
380          let options = fieldOptions.filter((option) => keys.includes(option.id));
381          return options.map((option) => option.label).join(', ');
382        }
383        if (ctrl.defn.data_type === 'Date' || ctrl.defn.data_type === 'Timestamp') {
384          try {
385            return CRM.utils.formatDate(value, null, ctrl.defn.data_type === 'Timestamp');
386          } catch (e) {
387            return '';
388          }
389        }
390        if (ctrl.fkEntity) {
391          const ids = Array.isArray(value) ? value : [value];
392          if (!ctrl._entityLabels) {
393            ctrl._entityLabels = {};
394          }
395          if (!(ids.join() in ctrl._entityLabels)) {
396            ctrl._entityLabels[ids.join()] = null;
397            const params = ctrl.getAutocompleteParams();
398            params.ids = ids;
399            crmApi4(ctrl.fkEntity, 'autocomplete', params)
400              .then((result) => {
401                ctrl._entityLabels[ids.join()] = result.map((item) => item.label).join(', ');
402              });
403          }
404          return ctrl._entityLabels[ids.join()] || ts('Loading...');
405        }
406        return value;
407      };
408      ctrl.onSelectEntity = function() {
409        if (ctrl.afForm && ctrl.defn.input_attrs && ctrl.defn.input_attrs.autofill) {
410          const val = $scope.getSetSelect();
411          const entity = ctrl.afFieldset.modelName;
412          const entityIndex = ctrl.getEntityIndex();
413          const joinEntity = ctrl.afJoin ? ctrl.afJoin.entity : null;
414          const joinIndex = ctrl.afJoin && $scope.dataProvider.repeatIndex || 0;
415          ctrl.afFieldset.afFormCtrl.loadData(entity, entityIndex, val, ctrl.defn.name, joinEntity, joinIndex);
416        }
417      };
418      ctrl.getFileUploadParams = function() {
419        return {
420          modelName: ctrl.afFieldset.getName(),
421          fieldName: ctrl.fieldName,
422          joinEntity: ctrl.afJoin ? ctrl.afJoin.entity : null,
423          entityIndex: ctrl.getEntityIndex(),
424          joinIndex: ctrl.afJoin && $scope.dataProvider.repeatIndex || null
425        };
426      };
427
428      ctrl.getAutocompleteParams = function() {
429        let fieldName = isExtra ? 'extra' : ctrl.afFieldset.getName();
430        if (!isExtra && ctrl.afJoin) {
431          fieldName += '+' + ctrl.afJoin.entity;
432        }
433        fieldName += ':' + ctrl.fieldName;
434        return {
435          formName: 'afform:' + ctrl.afFieldset.getFormName(),
436          fieldName: fieldName,
437          values: $scope.dataProvider.getFieldData()
438        };
439      };
440
441      $scope.getOptions = () => {
442        if (!Array.isArray(fieldOptions)) {
443          return [];
444        }
445        return fieldOptions.filter((opt) => {
446          if (!opt.if || !opt.if.length) return true;
447          try {
448            return this.afForm.checkConditions(opt.if);
449          } catch (e) {
450            console.warn('[afField] `if` evaluation failed for option', opt.id, e);
451            return true;
452          }
453        });
454      };
455
456      this.validateValue = () => {
457        const current = $scope.dataProvider.getFieldData()[this.fieldName];
458        const options = $scope.getOptions().map((o) => o.id);
459
460        if (Array.isArray(current)) {
461          const valid = current.filter((v) => options.includes(v));
462          if (valid.length < current.length) {
463            $scope.dataProvider.getFieldData()[this.fieldName] = valid;
464            $('input[crm-ui-select]', $element).val(valid).change();
465          }
466        } else {
467          if (!options.includes(current)) {
468            $('input[crm-ui-select]', $element).val('').change();
469            delete $scope.dataProvider.getFieldData()[this.fieldName];
470          }
471        }
472      };
473
474      $scope.select2Options = function() {
475        return {
476          results: _.transform($scope.getOptions(), function(result, opt) {
477            result.push({id: opt.id, text: opt.label});
478          }, [])
479        };
480      };
481
482      this.onChangeOperator = function() {
483        $scope.dataProvider.getFieldData()[ctrl.fieldName] = {};
484      };
485      $scope.getSetValue = function(val) {
486        const currentVal = $scope.dataProvider.getFieldData()[ctrl.fieldName];
487        if (arguments.length) {
488          if (ctrl.search_operator) {
489            if (typeof currentVal !== 'object') {
490              $scope.dataProvider.getFieldData()[ctrl.fieldName] = {};
491            }
492            return ($scope.dataProvider.getFieldData()[ctrl.fieldName][ctrl.search_operator] = val);
493          }
494          return ($scope.dataProvider.getFieldData()[ctrl.fieldName] = val);
495        }
496        if (ctrl.search_operator) {
497          return (currentVal || {})[ctrl.search_operator];
498        }
499        return currentVal;
500      };
501      $scope.getSetSelect = function(val) {
502        const currentVal = $scope.dataProvider.getFieldData()[ctrl.fieldName];
503        if (arguments.length) {
504          if (ctrl.defn.is_date) {
505            if (val === '{}') {
506              val = !_.isPlainObject(currentVal) ? {} : currentVal;
507            }
508          }
509          else if (ctrl.defn.search_range) {
510            return ($scope.dataProvider.getFieldData()[ctrl.fieldName]['>='] = val);
511          }
512          else if (ctrl.search_operator) {
513            if (typeof currentVal !== 'object') {
514              $scope.dataProvider.getFieldData()[ctrl.fieldName] = {};
515            }
516            return ($scope.dataProvider.getFieldData()[ctrl.fieldName][ctrl.search_operator] = val);
517          }
518          if (ctrl.defn.data_type === 'Boolean') {
519            return ($scope.dataProvider.getFieldData()[ctrl.fieldName] = (val === 'true'));
520          }
521          if (ctrl.defn.data_type === 'Integer' || ctrl.defn.data_type === 'Float') {
522            if (typeof val === 'string') {
523              return ($scope.dataProvider.getFieldData()[ctrl.fieldName] = val.length ? Number(val) : null);
524            } else if (Array.isArray(val)) {
525              return ($scope.dataProvider.getFieldData()[ctrl.fieldName] = val.map(Number));
526            }
527          }
528          return ($scope.dataProvider.getFieldData()[ctrl.fieldName] = val);
529        }
530        if (ctrl.defn.is_date) {
531          return _.isPlainObject(currentVal) ? '{}' : currentVal;
532        }
533        else if (ctrl.defn.search_range) {
534          return currentVal['>='];
535        }
536        else if (ctrl.search_operator) {
537          return (currentVal || {})[ctrl.search_operator];
538        }
539        else if (!ctrl.isMultiple() && (typeof currentVal === 'boolean' || typeof currentVal === 'number')) {
540          return JSON.stringify(currentVal);
541        }
542        return currentVal;
543      };
544
545      function getRelativeDate(dateString, includeTime) {
546        const parts = dateString.split(' ');
547        const baseDate = new Date();
548        let unit = parts[2] || 'day';
549        let offset = parseInt(parts[1] || '0', 10);
550
551        switch (unit) {
552          case 'week':
553            offset *= 7;
554            break;
555
556          case 'year':
557            offset *= 365;
558        }
559        let newDate = new Date(baseDate.getTime() + offset * 24 * 60 * 60 * 1000);
560        let localYear = newDate.getFullYear();
561        let localMonth = String(newDate.getMonth() + 1).padStart(2, '0'); // Months are 0-indexed
562        let localDay = String(newDate.getDate()).padStart(2, '0');
563        let defaultDate = `${localYear}-${localMonth}-${localDay}
563`; // Format YYYY-MM-DD
564
565        if (includeTime) {
566          defaultDate += ' ' + newDate.toTimeString().slice(0,8);
567        }
568        return defaultDate;
569      }
570
571    }
572  });
573
574angular.module('af').directive('afFieldset', function() {
575    return {
576      restrict: 'A',
577      require: ['afFieldset', '?^^afForm', '?afRepeat'],
578      bindToController: {
579        modelName: '@afFieldset',
580        storeValues: '<'
581      },
582      link: function($scope, $el, $attr, ctrls) {
583        const self = ctrls[0];
584        self.afFormCtrl = ctrls[1];
585        self.afRepeatCtrl = ctrls[2];
586      },
587      controller: function($scope, $element, crmApi4) {
588        const ctrl = this;
589        const localData = [];
590        const joinOffsets = {};
591        const ts = $scope.ts = CRM.ts('org.civicrm.afform');
592
593        this.getData = function() {
594          return ctrl.afFormCtrl ? ctrl.afFormCtrl.getData(ctrl.modelName) : localData;
595        };
596        this.getName = function() {
597          return this.modelName ||
598            $element.find('[search-name][display-name]').attr('display-name');
599        };
600        this.getEntity = function() {
601          return this.afFormCtrl ? this.afFormCtrl.getEntity(this.modelName) : null;
602        };
603        this.getEntityType = function() {
604          return this.afFormCtrl ? this.afFormCtrl.getEntity(this.modelName)?.type : null;
605        };
606        this.getFieldData = function() {
607          const data = ctrl.getData();
608          if (!data.length && !ctrl.afRepeatCtrl) {
609            data.push({fields: {}});
610          }
611          return data[0]?.fields;
612        };
613        this.addRepeatItem = () => {
614          this.getData().push({fields: {}});
615        };
616
617        this.getFormName = function() {
618          return ctrl.afFormCtrl ? ctrl.afFormCtrl.getFormMeta().name : $scope.meta.name;
619        };
620
621        this.getFieldMeta = () => {
622          const meta = {};
623          const fieldElements = $element[0].querySelectorAll('af-field');
624          fieldElements.forEach((field) => {
625            const name = field.getAttribute('name');
626            const defn = $scope.$eval(field.getAttribute('defn'));
627            meta[name] = defn;
628          });
629          return meta;
630        };
631
632        this.getJoinOffset = function(joinEntity) {
633          joinOffsets[joinEntity] = joinEntity in joinOffsets ? joinOffsets[joinEntity] + 1 : 0;
634          return joinOffsets[joinEntity];
635        };
636        function getCacheKey() {
637          return 'afform:' + ctrl.getFormName() + ctrl.getName();
638        }
639        this.getStoredValue = function(fieldName) {
640          if (!this.storeValues) {
641            return;
642          }
643          if (!this.reloadedStoredValues) {
644            this.reloadedStoredValues = CRM.cache.get(getCacheKey(), {});
645          }
646          return this.reloadedStoredValues[fieldName];
647        };
648
649        this.$onInit = function() {
650          $scope.$watch(ctrl.getFieldData, (newVal, oldVal) => {
651            $element[0].dispatchEvent(new Event('crmFormChangeFilters'));
652            if (this.storeValues) {
653              if (typeof newVal === 'object' && typeof oldVal === 'object' && Object.keys(newVal).length) {
654                CRM.cache.set(getCacheKey(), newVal);
655              }
656            }
657          }, true);
658
659          $scope.$watch(this.getSearchParamSetId, () => this.fetchSearchParamSetValues());
660        };
661
662        /**
663         * Get fieldset values to use for afform filters
664         *
665         * Filter out empty values (null, '', [], {}) at top level and one level
666         * below for object values (fields with ranges/operators)
667         *
668         * @returns Object
669         */
670        this.getFilterValues = () => {
671          const isEmpty = (v) => {
672            if (v === null || v === '') {
673              return true;
674            }
675            if (Array.isArray(v) && v.length === 0) {
676              return true;
677            }
678            if (typeof v === 'object' && Object.keys(v).length === 0) {
679              return true;
680            }
681          };
682          const data = _.cloneDeep(this.getFieldData());
683
684          Object.keys(data).forEach((key) => {
685            if (typeof data[key] === 'object' && data[key] !== null) {
686              Object.keys(data[key]).forEach((subKey) => {
687                if (isEmpty(data[key][subKey])) {
688                  delete data[key][subKey];
689                }
690              });
691            }
692            if (isEmpty(data[key])) {
693              delete data[key];
694            }
695          });
696
697          return data;
698        };
699
700        /**
701         * Get value for a given field based on currently applied SearchParamSet
702         * Used by afField controller on load
703         */
704        this.getSearchParamSetFieldValue = (fieldName) => {
705          if (this.selectedSearchParamSet && this.selectedSearchParamSet.filters && this.selectedSearchParamSet.filters.hasOwnProperty(fieldName)) {
706            return this.selectedSearchParamSet.filters[fieldName];
707          }
708          return null;
709        };
710
711        /**
712         * Fetch values for a SearchParamSet from the server
713         */
714        this.fetchSearchParamSetValues = () => {
715          const searchParamSetId = this.getSearchParamSetId();
716          if (!searchParamSetId) {
717            return;
718          }
719          crmApi4('SearchParamSet', 'get', {
720            where: [
721              ['id', '=', searchParamSetId],
722              ['afform_name', '=', this.getFormName()]
723            ],
724            select: ['filters', 'columns'],
725          })
726          .then((result) => {
727            if (result && result[0]) {
728              this.selectedSearchParamSet = result[0];
729            }
730            else {
731              this.selectedSearchParamSet = {};
732            }
733            $scope.$broadcast('afFormReset');
734          });
735        };
736
737        /**
738         * Get search param set ID from URL hash param
739         */
740        this.getSearchParamSetId = () => ($scope && $scope.routeParams) ? $scope.routeParams._s : null;
741      }
742    };
743  });
744
745  angular.module('af').component('afForm', {
746    bindings: {
747      ctrl: '@'
748    },
749    require: {
750      ngForm: 'form'
751    },
752    controller: function($scope, $element, $timeout, crmApi4, crmStatus, $window, $location, $parse, FileUploader) {
753      const
754        ctrl = this,
755        ts = CRM.ts('org.civicrm.afform');
756
757      const saveDraftButtons = [];
758      const schema = {};
759      const data = {
760        extra: {fields: {}},
761      };
762
763      let
764        args,
765        submissionResponse,
766        autoSave = () => {},
767        draftStatus = 'pristine',
768        cancelDraftWatcher,
769        uploadingDraftFiles = false;
770
771      this.$onInit = () => {
772        $scope.$parent[this.ctrl] = this;
773
774        $timeout(() => {
775          $element[0].querySelectorAll('.af-markup:not(af-markup)').forEach((el) => {
776            const renderer = document.createElement('af-markup');
777            renderer.markup = el.innerHTML;
778            el.replaceChildren(renderer);
779          });
780
781          ctrl.loadData()
782            .then(setupDraftWatcher);
783
784          ctrl.showSubmitButton = displaySubmitButton(args);
785        });
786      };
787
788      this.registerEntity = function registerEntity(entity) {
789        schema[entity.modelName] = entity;
790        data[entity.modelName] = [];
791      };
792      this.getEntity = function getEntity(name) {
793        return schema[name];
794      };
795      this.getData = function getData(name) {
796        return data[name];
797      };
798      this.getSchema = function getSchema(name) {
799        return schema[name];
800      };
801      this.getFormMeta = function getFormMeta() {
802        return $scope.$parent.meta;
803      };
804      this.resetForm = function() {
805        this.ngForm.$setPristine();
806        $scope.$parent.$broadcast('afFormReset');
807        this.loadData();
808      };
809      this.loadData = function(selectedEntity, selectedIndex, selectedId, selectedField, joinEntity, joinIndex) {
810        let toLoad = true;
811        const params = {name: ctrl.getFormMeta().name, args: {}};
812        if (selectedEntity) {
813          toLoad = !!selectedId;
814          params.args[selectedEntity] = {};
815          params.args[selectedEntity][selectedIndex] = {};
816          if (joinEntity) {
817            params.fillMode = 'join';
818            params.args[selectedEntity][selectedIndex].joins = {};
819            params.args[selectedEntity][selectedIndex].joins[joinEntity] = {};
820            params.args[selectedEntity][selectedIndex].joins[joinEntity][joinIndex] = {};
821            params.args[selectedEntity][selectedIndex].joins[joinEntity][joinIndex][selectedField] = selectedId;
822          } else {
823            params.fillMode = 'entity';
824            params.args[selectedEntity][selectedIndex][selectedField] = selectedId;
825          }
826        }
827        else {
828          params.fillMode = 'form';
829          args = Object.assign({}, $scope.$parent.routeParams || {}, $scope.$parent.options || {});
830          Object.keys(schema).forEach((entityName) => {
831            if (args[entityName] && typeof args[entityName] === 'string') {
832              args[entityName] = args[entityName].split(',');
833            }
834          });
835          params.args = args;
836          ctrl.showSubmitButton = displaySubmitButton(args);
837        }
838        if (toLoad) {
839          if (params.fillMode === 'form') {
840            $element.block();
841          }
842          return crmApi4('Afform', 'prefill', params)
843            .then((result) => {
844              result.forEach((item) => {
845                _.each(item.values, (values, index) => {
846                  data[item.name][index] = data[item.name][index] || {};
847                  data[item.name][index].joins = data[item.name][index].joins || {};
848                  angular.merge(data[item.name][index], values, {fields: _.cloneDeep(schema[item.name]?.data || {})});
849                });
850              });
851              $element.unblock();
852            }, (error) => {
853              disableForm(error.error_message, ts('Sorry'), 'error');
854              $element.unblock();
855            });
856        }
857        else if (joinEntity) {
858          data[selectedEntity][selectedIndex].joins[joinEntity][joinIndex] = {};
859        }
860        else if (selectedEntity) {
861          Object.keys(data[selectedEntity][selectedIndex].fields).forEach((key) => delete data[selectedEntity][selectedIndex].fields[key]);
862          angular.merge(data[selectedEntity][selectedIndex].fields, _.cloneDeep(schema[selectedEntity].data || {}));
863          data[selectedEntity][selectedIndex].joins = {};
864        }
865      };
866
867      function displaySubmitButton(args) {
868        if (args.sid && args.sid.length > 0) {
869          return false;
870        }
871        return true;
872      }
873      const token = new URLSearchParams(window.location.search).get('_aff');
874      const headers = {'X-Requested-With': 'XMLHttpRequest'};
875      if (token) {
876        headers['X-Civi-Auth-Afform'] = token;
877      }
878      this.fileUploader = new FileUploader({
879        url: CRM.url('civicrm/ajax/api4/Afform/submitFile'),
880        headers: headers,
881        onAfterAddingFile: (item) => {
882          setDraftStatus('unsaved');
883        },
884        onSuccessItem: onFileUploadSuccess,
885        onCompleteAll: onFileUploadsComplete,
886        onBeforeUploadItem: (item) => {
887          item._status = CRM.status({
888            start: ts('Uploading %1', {1: item.file.name}),
889            error: ts('Upload failed'),
890            success: ts('Upload complete'),
891          });
892        },
893        onCompleteItem: (item, response, status, headers) => {
894          if (status === 200) {
895            item._status.resolve();
896          } else {
897            item._status.reject();
898          }
899        }
900      });
901
902      function onFileUploadSuccess(item, response, status, headers) {
903        if (response.values && response.values[0] && response.values[0].id) {
904          const dataProvider = item.crmDataProvider;
905          dataProvider.getFieldData()[item.crmFieldName] = response.values[0];
906        }
907      }
908
909      function onFileUploadsComplete() {
910        if (uploadingDraftFiles) {
911          uploadingDraftFiles = false;
912          setDraftStatus('saved');
913          if (draftStatus === 'unsaved') {
914            autoSave();
915          }
916        } else {
917          postProcess();
918        }
919      }
920      function setupDraftWatcher() {
921        const buttons = getDraftButtons();
922        const autoSaveEnabled = ctrl.getFormMeta().autosave_draft;
923
924        if ((!autoSaveEnabled && !buttons.length) || !ctrl.showSubmitButton || (!CRM.config.cid && !token)) {
925          return;
926        }
927        $.each(buttons, function(index, button) {
928          saveDraftButtons[index] = {
929            text: $(button).text(),
930            icon: $(button).attr('crm-icon'),
931          };
932        });
933        if (autoSaveEnabled) {
934          autoSave = _.debounce(ctrl.submitDraft, 10000);
935        }
936
937        cancelDraftWatcher = $scope.$watch(() => data, function (newVal, oldVal) {
938            if (oldVal) {
939              if (draftStatus === 'pristine') {
940                setDraftStatus('saved');
941              } else {
942                setDraftStatus('unsaved');
943                autoSave(newVal);
944              }
945            }
946          },
947          true
948        );
949      }
950      this.checkConditions = function(conditions, op) {
951        op = op || 'AND';
952        let ret = op === 'AND',
953          flip = !ret;
954        _.each(conditions, function(clause) {
955          if (Array.isArray(clause[1])) {
956            if (ctrl.checkConditions(clause[1], clause[0]) === flip) {
957              ret = flip;
958            }
959          } else {
960            if (typeof clause[0] === 'string' && clause[0].charAt(0) !== '"') {
961              clause[0] = clause[0].replace(/\[([^'"])/g, "['$1").replace(/([^'"])]/g, "$1']");
962            }
963            let parser1 = $parse(clause[0]);
964            let parser2 = $parse(clause[2]);
965            let result = compareConditions(parser1(data), clause[1], parser2(data));
966            if (result === flip) {
967              ret = flip;
968            }
969          }
970        });
971        return op === 'NOT' ? !ret : ret;
972      };
973
974      function compareConditions(val1, op, val2) {
975        const yes = (op !== '!=' && !op.includes('NOT '));
976
977        switch (op) {
978          case '=':
979          case '!=':
980          case '==':
981            if (typeof val1 === 'string') {
982              val1 = val1.toLowerCase();
983            }
984            if (typeof val2 === 'string') {
985              val2 = val2.toLowerCase();
986            }
987            return angular.equals(val1, val2) === yes;
988
989          case '>':
990            return val1 > val2;
991
992          case '<':
993            return val1 < val2;
994
995          case '>=':
996            return val1 >= val2;
997
998          case '<=':
999            return val1 <= val2;
1000
1001          case 'IS EMPTY':
1002            return !val1;
1003
1004          case 'IS NOT EMPTY':
1005            return !!val1;
1006
1007          case 'CONTAINS':
1008          case 'NOT CONTAINS':
1009            if (Array.isArray(val1)) {
1010              return val1.includes(val2) === yes;
1011            } else if (typeof val1 === 'string' && typeof val2 === 'string') {
1012              return val1.toLowerCase().includes(val2.toLowerCase()) === yes;
1013            }
1014            return angular.equals(val1, val2) === yes;
1015
1016          case 'IN':
1017          case 'NOT IN':
1018            if (Array.isArray(val2)) {
1019              return val2.includes(val1) === yes;
1020            }
1021            return angular.equals(val1, val2) === yes;
1022
1023          case 'LIKE':
1024          case 'NOT LIKE':
1025            if (typeof val1 === 'string' && typeof val2 === 'string') {
1026              return likeCompare(val1, val2) === yes;
1027            }
1028            return angular.equals(val1, val2) === yes;
1029        }
1030      }
1031
1032      function likeCompare(str, pattern) {
1033        const regexPattern = pattern
1034          .replace(/([.+?^=!:${}()|\[\]\/\\])/g, "\\$1")
1035          .replace(/%/g, '.*') // Convert % to .*
1036          .replace(/_/g, '.'); // Convert _ to .
1037        const regex = new RegExp(`^${regexPattern}$`, 'i');
1038        return regex.test(str);
1039      }
1040      function postProcess() {
1041        const metaData = ctrl.getFormMeta(),
1042          dialog = $element.closest('.ui-dialog-content');
1043        ctrl.ngForm.$setPristine();
1044
1045        $element.trigger('crmFormSuccess', {
1046          afform: metaData,
1047          data: data,
1048          submissionResponse: submissionResponse,
1049        });
1050
1051        if (submissionResponse[0].redirect) {
1052          let url = submissionResponse[0].redirect;
1053          if (url.indexOf('civicrm/') === 0) {
1054            url = CRM.url(url);
1055          } else if (url.indexOf('/') === 0) {
1056            let port = $location.port();
1057            port = port ? `:${port}` : '';
1058            url = `${$location.protocol()}://${$location.host()}${port}${url}`;
1059          }
1060          $window.location.href = url;
1061          return;
1062        }
1063
1064        if (submissionResponse[0].message) {
1065          $element.hide();
1066          const $confirmation = $('<div class="afform-confirmation" />');
1067          $confirmation.html(submissionResponse[0].message);
1068          $confirmation.insertAfter($element);
1069        }
1070        else if (dialog.length) {
1071          dialog.dialog('close');
1072        }
1073        else {
1074          $element.unblock();
1075        }
1076      }
1077
1078      function validateFileFields() {
1079        let valid = true;
1080        $("af-form[ng-form=" + ctrl.getFormMeta().name + "] input[type='file']").each((index, fld) => {
1081          if ($(fld).attr('required') && $(fld).get(0).files.length == 0) {
1082            valid = false;
1083          }
1084        });
1085        return valid;
1086      }
1087
1088      function disableForm(errorMsg) {
1089        $('af-form[ng-form="' + ctrl.getFormMeta().name + '"]')
1090          .addClass('disabled')
1091          .find('button[ng-click="afform.submit()"]').prop('disabled', true);
1092        CRM.alert(errorMsg, ts('Sorry'), 'error');
1093      }
1094
1095      const handleError = (error) => {
1096        if (error && error.error_code !== '1') {
1097          CRM.alert(error.error_message, ts('Please resolve these issues'), 'warning');
1098        }
1099        else {
1100          const message = error?.error_message ? error.error_message : ts('Unknown error');
1101          CRM.alert(message, ts('There is a problem'), 'error');
1102        }
1103      };
1104
1105      this.validate = () => {
1106        if (!ctrl.ngForm.$valid || !validateFileFields()) {
1107          return new Promise((resolve) => resolve({
1108            is_error: true,
1109            message: ts('Please fill all required fields.'),
1110          }));
1111        }
1112        return crmApi4('Afform', 'validate', {
1113          name: this.getFormMeta().name,
1114          args: args,
1115          values: data,
1116        });
1117      };
1118
1119      this.submit = function () {
1120        if (!ctrl.ngForm.$valid || !validateFileFields()) {
1121          const firstInvalidInput = $element[0].closest('af-form').querySelector('.ng-invalid');
1122          const isRequired = firstInvalidInput.classList.contains('ng-invalid-required');
1123          const message = isRequired ? ts('Please fill all required fields.') : ts('Please check all answers are valid.');
1124
1125          if (document.getElementById('crm-notification-container')) {
1126            CRM.alert(message, ts('Form Error'));
1127          }
1128          else {
1129            if (firstInvalidInput.classList.contains('select2-container')) {
1130              firstInvalidInput.scrollIntoView();
1131              firstInvalidInput.focus();
1132              CRM.alert(message, ts('Form Error'));
1133            }
1134          }
1135          return;
1136        }
1137        $element.block();
1138        if (cancelDraftWatcher) {
1139          cancelDraftWatcher();
1140        }
1141
1142        const submitApi = crmApi4('Afform', 'submit', {
1143          name: ctrl.getFormMeta().name,
1144          args: args,
1145          values: data,
1146        }).then((response) => {
1147          submissionResponse = response;
1148          if (ctrl.fileUploader.getNotUploadedItems().length) {
1149            _.each(ctrl.fileUploader.getNotUploadedItems(), function(file) {
1150              file.formData.push({
1151                params: JSON.stringify(_.extend({
1152                  token: response[0].token,
1153                  name: ctrl.getFormMeta().name
1154                }, file.crmApiParams()))
1155              });
1156            });
1157            ctrl.fileUploader.uploadAll();
1158          }
1159          else {
1160            postProcess();
1161          }
1162        })
1163        .catch((error) => {
1164          $element.unblock();
1165
1166          handleError(error);
1167
1168          $element.trigger('crmFormError', {
1169            afform: ctrl.getFormMeta(),
1170            data: data,
1171            submissionResponse: submissionResponse,
1172            error: error
1173          });
1174        });
1175        crmStatus({
1176          error: ts('Not Saved'),
1177        }, submitApi);
1178      };
1179
1180      this.submitDraft = function() {
1181        if (uploadingDraftFiles) {
1182          return;
1183        }
1184        setDraftStatus('saving');
1185        const submitDraft = crmApi4('Afform', 'submitDraft', {
1186          name: ctrl.getFormMeta().name,
1187          args: args,
1188          values: data,
1189        }).then((response) => {
1190          if (ctrl.fileUploader.getNotUploadedItems().length) {
1191            uploadingDraftFiles = true;
1192            ctrl.fileUploader.getNotUploadedItems().forEach((file) => {
1193              file.formData.push({
1194                params: JSON.stringify(_.extend({
1195                  name: ctrl.getFormMeta().name
1196                }, file.crmApiParams()))
1197              });
1198            });
1199            ctrl.fileUploader.uploadAll();
1200          } else {
1201            setDraftStatus('saved');
1202          }
1203        })
1204        .catch((error) => {
1205          setDraftStatus('unsaved');
1206          handleError(error);
1207        });
1208        crmStatus({
1209          start: ts('Saving Draft'),
1210          success: ts('Draft Saved'),
1211          error: ts('Not Saved'),
1212        }, submitDraft);
1213      };
1214
1215      function getDraftButtons() {
1216        return $element.find('button[ng-click="afform.submitDraft()"]');
1217      }
1218
1219      function setDraftStatus(newStatus) {
1220        if (draftStatus === newStatus) {
1221          return;
1222        }
1223        if (draftStatus === 'unsaved' && newStatus === 'saved') {
1224          return;
1225        }
1226        if (newStatus === 'unsaved' && !uploadingDraftFiles) {
1227          restoreDraftButtons();
1228        }
1229        else if (!uploadingDraftFiles) {
1230          const newText = newStatus === 'saving' ? ts('Saving Draft') : ts('Draft Saved');
1231          const newIcon = newStatus === 'saving' ? 'fa-spinner fa-spin' : 'fa-check';
1232          disableDraftButtons(newText, newIcon);
1233        }
1234        draftStatus = newStatus;
1235      }
1236
1237      function disableDraftButtons(text, icon) {
1238        const buttons = getDraftButtons();
1239        $.each(buttons, function(index, button) {
1240          $(button).text(text).attr('disabled', true);
1241          $(button).prepend('<i class="crm-i ' + icon + '" role="img" aria-hidden="true"></i> ');
1242        });
1243      }
1244
1245      function restoreDraftButtons() {
1246        const buttons = getDraftButtons();
1247        $.each(buttons, function(index, button) {
1248          const initialState = saveDraftButtons[index] || saveDraftButtons[0];
1249          $(button).text(initialState.text).attr('disabled', false);
1250          if (initialState.icon) {
1251            $(button).prepend('<i class="crm-i ' + saveDraftButtons[index].icon + '" role="img" aria-hidden="true"></i> ');
1252          }
1253        });
1254      }
1255      this.getFieldData = () => {
1256        return data.extra.fields;
1257      };
1258      this.identifyTokens = (message) => {
1259        if (typeof message !== 'string') {
1260          return null;
1261        }
1262        const tokens = new Set(message.match(/\[[a-zA-Z0-9_]+\.[0-9]+\.[^\]]+\]/g));
1263
1264        return tokens.size ? tokens : null;
1265      };
1266
1267      this.getTokenValues = (tokens) => {
1268        const values = {};
1269
1270        tokens.forEach((token) => {
1271          const parts = token.slice(1, -1).split('.');
1272          const entity = parts[0];
1273          const index = parts[1];
1274          const fieldName = parts.slice(2).join('.');
1275          if (!data || !data[entity] || !data[entity][index] || !data[entity][index].fields || (data[entity][index].fields[fieldName] === undefined) ) {
1276            values[token] = '';
1277          }
1278          else {
1279            values[token] = data[entity][index].fields[fieldName];
1280          }
1281        });
1282
1283        return values;
1284      };
1285
1286      this.replaceTokens = (message) => {
1287        const tokens = this.identifyTokens(message);
1288        const tokenValues = this.getTokenValues(tokens);
1289        tokens.forEach((token) => message = message.replaceAll(token, tokenValues[token]));
1290        return message;
1291      };
1292    }
1293  });
1294
1295angular.module('af').directive('afIf', function($compile, $animate, $parse) {
1296    return {
1297      multiElement: true,
1298      transclude: 'element',
1299      priority: 601,
1300      terminal: true,
1301      restrict: 'A',
1302      require: ['^^afForm'],
1303      $$tlb: true,
1304      link: function($scope, $element, $attr, ctrl, $transclude) {
1305        let block, childScope, previousElements;
1306
1307        function watcher() {
1308          const conditions = $parse($attr.afIf)();
1309          return ctrl[0].checkConditions(conditions);
1310        }
1311
1312        $scope.$watch(watcher, function(value) {
1313          if (value) {
1314            if (!childScope) {
1315              $transclude(function(clone, newScope) {
1316                childScope = newScope;
1317                clone[clone.length++] = $compile.$$createComment('end afIf', $attr.afIf);
1318                block = {
1319                  clone: clone
1320                };
1321                $animate.enter(clone, $element.parent(), $element);
1322              });
1323            }
1324          } else {
1325            if (previousElements) {
1326              previousElements.remove();
1327              previousElements = null;
1328            }
1329            if (childScope) {
1330              childScope.$broadcast('afIfDestroy');
1331              childScope.$destroy();
1332              childScope = null;
1333            }
1334            if (block) {
1335              previousElements = getBlockNodes(block.clone);
1336              $animate.leave(previousElements).done(function(response) {
1337                if (response !== false) previousElements = null;
1338              });
1339              block = null;
1340            }
1341          }
1342        });
1343      }
1344    };
1345  });
1346
1347  /**
1348   * Return the DOM siblings between the first and last node in the given array.
1349   * @param {Array} array like object
1350   * @returns {Array} the inputted object or a jqLite collection containing the nodes
1351   */
1352  function getBlockNodes(nodes) {
1353    var node = nodes[0];
1354    var endNode = nodes[nodes.length - 1];
1355    var blockNodes;
1356
1357    for (var i = 1; node !== endNode && (node = node.nextSibling); i++) {
1358      if (blockNodes || nodes[i] !== node) {
1359        if (!blockNodes) {
1360          blockNodes = $(slice.call(nodes, 0, i));
1361        }
1362        blockNodes.push(node);
1363      }
1364    }
1365
1366    return blockNodes || nodes;
1367  }
1368
1369
1370angular.module('af')
1371    .directive('afJoin', function() {
1372      return {
1373        restrict: 'A',
1374        require: ['afJoin', '^^afFieldset', '?^^afRepeatItem'],
1375        bindToController: {
1376          entity: '@afJoin',
1377        },
1378        link: function($scope, $el, $attr, ctrls) {
1379          const self = ctrls[0];
1380          self.afFieldset = ctrls[1];
1381          self.repeatItem = ctrls[2];
1382          self.offset = self.afFieldset.getJoinOffset($attr.afJoin);
1383        },
1384        controller: function($scope) {
1385          const self = this;
1386          this.getEntityType = function() {
1387            return this.entity;
1388          };
1389          this.getData = function() {
1390            let data, fieldsetData;
1391            if (self.repeatItem) {
1392              data = self.repeatItem.item;
1393            } else {
1394              fieldsetData = self.afFieldset.getData();
1395              if (!fieldsetData.length) {
1396                fieldsetData.push({fields: {}, joins: {}});
1397              }
1398              data = fieldsetData[0];
1399            }
1400            if (!data.joins) {
1401              data.joins = {};
1402            }
1403            if (!data.joins[self.entity]) {
1404              data.joins[self.entity] = [];
1405            }
1406            return data.joins[self.entity];
1407          };
1408          this.addRepeatItem = () => {
1409            this.getData().push({});
1410          };
1411
1412          this.getFieldData = function() {
1413            const data = this.getData();
1414            if (!data[this.offset] || (Array.isArray(data[this.offset]) && !data[this.offset].length)) {
1415              data[this.offset] = {};
1416            }
1417            return data[this.offset];
1418          };
1419        }
1420      };
1421    });
1422})(angular, CRM.$, CRM._);
1423
1424(function (CRM, angular) {
1425
1426
1427  class AfMarkup extends HTMLElement {
1428
1429    /* jshint ignore:start */
1430    static observedAttributes = ['markup'];
1431    /* jshint ignore:end */
1432
1433    connectedCallback() {
1434      this.afForm = this.closest('af-form');
1435      setTimeout(() => this.render());
1436    }
1437
1438    attributeChangedCallback() {
1439      this.render();
1440    }
1441
1442    render() {
1443      let markup = this.markup;
1444      if (this.afForm) {
1445        markup = this.replaceTokensWithElements(markup);
1446      }
1447      this.innerHTML = markup;
1448    }
1449
1450    get markup() {
1451      return this.getAttribute('markup');
1452    }
1453
1454    set markup(content) {
1455      this.setAttribute('markup', content);
1456    }
1457
1458    replaceTokensWithElements(markup) {
1459      const tokens = new Set(markup.match(/\[[a-zA-Z0-9_]+\.[0-9]+\.[^\]]+\]/g));
1460      tokens?.forEach((token) => markup = markup.replaceAll(token, `<af-token expression="${token}"></af-token>`));
1461      return markup;
1462    }
1463  }
1464
1465  customElements.define('af-markup', AfMarkup);
1466
1467})(CRM, angular);
1468
1469(function(angular, $, _) {
1470  angular.module('af')
1471    .directive('afRepeat', function() {
1472      return {
1473        restrict: 'A',
1474        require: ['?afFieldset', '?afJoin'],
1475        transclude: true,
1476        scope: {
1477          min: '=',
1478          max: '=',
1479          afRepeatDefault: '=',
1480          addLabel: '@afRepeat',
1481          addIcon: '@',
1482          copyLabel: '@afCopy',
1483          copyIcon: '@'
1484        },
1485        templateUrl: '~/af/afRepeat.html',
1486        link: function($scope, $el, $attr, ctrls) {
1487          $scope.afFieldset = ctrls[0];
1488          $scope.afJoin = ctrls[1];
1489          $scope.element = $el;
1490        },
1491        controller: function($scope) {
1492
1493          this.$onInit = () => {
1494            $scope.$evalAsync(() => {
1495              const data = getEntityController().getData();
1496              let defaultCount = $scope.afRepeatDefault;
1497              if (defaultCount === undefined || defaultCount === '' || isNaN(defaultCount)) {
1498                defaultCount = 1;
1499              }
1500              if ($scope.min !== undefined && $scope.min !== '' && defaultCount < $scope.min) {
1501                defaultCount = $scope.min;
1502              }
1503              while (data.length < defaultCount) {
1504                getEntityController().addRepeatItem();
1505              }
1506            });
1507          };
1508
1509          this.getItems = $scope.getItems = () => {
1510            return getEntityController().getData();
1511          };
1512
1513          function getRepeatType() {
1514            return $scope.afJoin ? 'join' : 'fieldset';
1515          }
1516          this.getRepeatType = getRepeatType;
1517
1518          function getEntityController() {
1519            return $scope.afJoin || $scope.afFieldset;
1520          }
1521          this.getEntityController = getEntityController;
1522
1523          $scope.addItem = function() {
1524            getEntityController().addRepeatItem();
1525          };
1526
1527          $scope.copyItem = function() {
1528            const data = $scope.getItems();
1529            const last = data[data.length - 1];
1530            data.push(getRepeatType() === 'join' ? angular.copy(last) : {fields: angular.copy(last.fields)});
1531          };
1532
1533          $scope.removeItem = function(index) {
1534            $scope.getItems().splice(index, 1);
1535          };
1536
1537          $scope.canAdd = function() {
1538            return !$scope.max || $scope.getItems().length < $scope.max;
1539          };
1540
1541          $scope.canRemove = function() {
1542            return !$scope.min || $scope.getItems().length > $scope.min;
1543          };
1544        }
1545      };
1546    })
1547    .directive('afRepeatItem', function() {
1548      return {
1549        restrict: 'A',
1550        require: {
1551          afRepeat: '^^',
1552          outerRepeatItem: '?^^afRepeatItem'
1553        },
1554        bindToController: {
1555          item: '=afRepeatItem',
1556          repeatIndex: '='
1557        },
1558        controller: function() {
1559          this.getFieldData = function() {
1560            return this.afRepeat.getRepeatType() === 'join' ? this.item : this.item.fields;
1561          };
1562
1563          this.getEntityType = function() {
1564            return this.afRepeat.getEntityController().getEntityType();
1565          };
1566        }
1567      };
1568    });
1569
1570  angular.module('af').component('afSearchParamSets', {
1571    require: {
1572      afFieldset: '^^'
1573    },
1574    templateUrl: '~/af/afSearchParamSets.html',
1575    controller: function($scope, $element, crmApi4, $window, $location) {
1576      const ts = $scope.ts = CRM.ts('org.civicrm.afform');
1577      this.savedSets = [];
1578
1579      this.$onInit = () => {
1580        this.formName = this.afFieldset.getFormName();
1581        this.saveDialog.reset();
1582
1583        this.fetchSearchParamSets();
1584      };
1585
1586      this.fetchSearchParamSets = () => crmApi4('SearchParamSet', 'get', {
1587          select: ['label', 'filters', 'columns', 'created_by.display_name', 'created_date'],
1588          where: [['afform_name', '=', this.formName]],
1589          orderBy: {'label': 'ASC'}
1590        })
1591        .then((results) => this.savedSets = results.map((paramSet) => {
1592          const created = new Date(paramSet.created_date);
1593          const createdDate = created.toLocaleDateString();
1594          const isToday = createdDate === (new Date()).toLocaleDateString();
1595          paramSet.created_date_or_time = isToday ? ts('%1 today', {1: created.toLocaleTimeString()}) : createdDate;
1596          return paramSet;
1597        }))
1598        .catch(() => this.savedSets = [{id: '', label: ts('Error fetching Saved Searches')}]);
1599
1600      this.applySearchParamSet = (id) => $window.location.hash = `#?_s=${id ? id : 0}`;
1601
1602      this.getSetSearchParamSet = (value) => {
1603        if (value !== undefined) {
1604          this.applySearchParamSet(value);
1605          return '';
1606        }
1607        const selected = this.getSelectedParamSet();
1608        return (selected && selected.id) ? `${selected.id}` : '';
1609      };
1610
1611      this.getSelectedParamSet = () => {
1612        const hashParams = new URLSearchParams($window.location.hash.slice(1));
1613        const urlValue = parseInt(hashParams.get('_s'));
1614        return this.savedSets.find((searchParamSet) => searchParamSet.id === urlValue);
1615      };
1616
1617      this.getCurrentParams = () => {
1618        if (!this.afFieldset) {
1619          return {};
1620        }
1621        const params = {};
1622
1623        params.filters = this.afFieldset.getFilterValues();
1624
1625        params.columns = {};
1626        $element.closest('[af-fieldset]').find('crm-search-display-table').each((i, $table) => {
1627          const tableCtrl = angular.element($table).controller('crmSearchDisplayTable');
1628          if (!tableCtrl) {
1629            return;
1630          }
1631          const columns = tableCtrl.getToggledColumns();
1632          if (!Object.keys(columns).length) {
1633            return;
1634          }
1635          params.columns[tableCtrl.getSearchDisplayKey()] = columns;
1636        });
1637        return params;
1638      };
1639
1640      this.renderSearchParamSetDescription = (paramSet) => ts('Created %1 by %2', {
1641        1: paramSet.created_date_or_time,
1642        2: (paramSet['created_by.display_name'] ? paramSet['created_by.display_name'] : 'UNKNOWN')
1643      });
1644
1645      this.renderSearchParamSetDetails = (paramSet) => {
1646        const rendered = {};
1647
1648        if (paramSet.filters) {
1649          const fieldMeta = this.afFieldset.getFieldMeta();
1650
1651          Object.keys(paramSet.filters).forEach((key) => {
1652            const defn = fieldMeta[key];
1653            const rawValue = paramSet.filters[key];
1654
1655            const formatValue = (v) => {
1656              if (v && v.map) {
1657                return v.map(formatValue).join(' OR ');
1658              }
1659              if (defn.options && defn.options.length) {
1660                const selected = defn.options.find((o) => o.id == v);
1661                if (selected) {
1662                  return selected.label;
1663                }
1664              }
1665              switch (typeof v) {
1666                case 'number':
1667                case 'string':
1668                  return v;
1669              }
1670              return Object.entries(v)
1671                .filter((e) => e[1])
1672                .map((e) => `${e[0]} ${e[1]}`)
1673                .join(' AND ');
1674            };
1675            rendered[defn.label] = formatValue(rawValue);
1676          });
1677        }
1678
1679        if (paramSet.columns) {
1680          const displayKeys = Object.keys(paramSet.columns);
1681          if (displayKeys.length > 1)
1682            displayKeys.forEach((displayKey) => {
1683              const label = ts('%1 columns', {1: displayKey});
1684              const columns = Object.values(paramSet.columns[displayKey]).join(', ');
1685              rendered[label] = columns;
1686            });
1687          else if (displayKeys.length === 1) {
1688            const displayKey = displayKeys[0];
1689            rendered[ts('Columns')] = Object.values(paramSet.columns[displayKey]).join(', ');
1690          }
1691        }
1692
1693        return rendered;
1694      };
1695
1696      this.saveDialog = {
1697        open: () => $element[0].querySelector('dialog.af-search-param-set-new').showModal(),
1698        close: () => $element[0].querySelector('dialog.af-search-param-set-new').close(),
1699        reset: () => {
1700          this.saveDialog.label = '';
1701          this.saveDialog.inProgress = false;
1702        },
1703        canOpen: () => {
1704          const params = this.getCurrentParams();
1705          if (Object.keys(params.filters).length) {
1706            return true;
1707          }
1708          if (Object.keys(params.columns).length) {
1709            return true;
1710          }
1711          return false;
1712        },
1713        canSave: () => !this.saveDialog.inProgress && this.saveDialog.label && Object.keys(this.getCurrentParams()).length,
1714        save: () => {
1715          if (!this.saveDialog.canSave()) {
1716            return;
1717          }
1718
1719          this.saveDialog.inProgress = true;
1720
1721          const current = this.getCurrentParams();
1722
1723          const values = {
1724            afform_name: this.formName,
1725            filters: current.filters,
1726            columns: current.columns,
1727            label: this.saveDialog.label,
1728            created_by: CRM.config.cid,
1729          };
1730
1731          let newId = null;
1732
1733          return crmApi4('SearchParamSet', 'create', {
1734            values: values
1735          })
1736          .then((result) => newId = (result && result[0]) ? result[0].id : null)
1737          .then(() => this.fetchSearchParamSets())
1738          .then(() => this.applySearchParamSet(newId))
1739          .then(() => this.saveDialog.close())
1740          .catch((error) => {
1741            const errorMessage = (error && error.error_message) ? error.error_message : ts('Unknown error');
1742            CRM.alert(ts('Error saving filters: %1', {1: errorMessage}));
1743          })
1744          .finally(() => this.saveDialog.reset());
1745        }
1746      };
1747
1748      this.manageDialog = {
1749        open: () => $element[0].querySelector('dialog.af-search-param-sets-manage').showModal(),
1750        close: () => $element[0].querySelector('dialog.af-search-param-sets-manage').close(),
1751        deleteItem: (searchParamSet) => {
1752          crmApi4('SearchParamSet', 'delete', {
1753            where: [['id', '=', searchParamSet.id]]
1754          })
1755          .then(() => this.fetchSearchParamSets());
1756        },
1757      };
1758
1759      this.updateDialog = {
1760        id: null,
1761        label: '',
1762        open: () => {
1763          const selected = this.getSelectedParamSet();
1764          this.updateDialog.id = selected.id;
1765          this.updateDialog.oldLabel = selected.label;
1766          this.updateDialog.newLabel = selected.label;
1767          this.updateDialog.valueComparison = this.updateDialog.getValueComparison();
1768          $element[0].querySelector('dialog.af-search-param-set-update').showModal();
1769        },
1770        close: () => $element[0].querySelector('dialog.af-search-param-set-update').close(),
1771        inProgress: false,
1772        canOpen: () => {
1773          const selected = this.getSelectedParamSet();
1774          if (!selected) {
1775            return false;
1776          }
1777          const newParams = this.getCurrentParams();
1778          if (JSON.stringify(selected.filters) !== JSON.stringify(newParams.filters)) {
1779            return true;
1780          }
1781          if (JSON.stringify(selected.columns) !== JSON.stringify(newParams.columns)) {
1782            return true;
1783          }
1784          return false;
1785        },
1786        canUpdate: () => this.updateDialog.id && this.updateDialog.newLabel,
1787        update: () => {
1788          if (!this.updateDialog.canUpdate()) {
1789            return;
1790          }
1791
1792          this.updateDialog.inProgress = true;
1793
1794          const newParams = this.getCurrentParams();
1795
1796          crmApi4('SearchParamSet', 'update', {
1797            where: [['id', '=', this.updateDialog.id]],
1798            values: {
1799              label: this.updateDialog.newLabel,
1800              filters: newParams.filters,
1801              columns: newParams.columns
1802            }
1803          })
1804          .then(() => this.fetchSearchParamSets())
1805          .then(() => this.updateDialog.inProgress = false)
1806          .then(() => this.updateDialog.close());
1807        },
1808        getValueComparison: () => {
1809          const current = this.getSelectedParamSet();
1810          const oldValues = this.renderSearchParamSetDetails(current);
1811          const newValues = this.renderSearchParamSetDetails(this.getCurrentParams());
1812
1813          const oldKeys = Object.keys(oldValues);
1814          const newKeys = Object.keys(newValues);
1815
1816          const comparison = {};
1817          oldKeys.filter((key) => !newKeys.includes(key)).forEach((key) => comparison[key] = [oldValues[key], ts('[none]')]);
1818          oldKeys.filter((key) => newKeys.includes(key)).forEach((key) => {
1819            if (newValues[key] === oldValues[key]) {
1820              comparison[key] = [oldValues[key]];
1821              return;
1822            }
1823            comparison[key] = [oldValues[key], newValues[key]];
1824          });
1825          newKeys.filter((key) => !oldKeys.includes(key)).forEach((key) => comparison[key] = [ts('[none]'), newValues[key]]);
1826
1827          return comparison;
1828        }
1829      };
1830    }
1831  });
1832
1833
1834  let tabNumber = 0;
1835
1836  angular.module('af').component('afTabset', {
1837    templateUrl: '~/af/afTabset.html',
1838    transclude: true,
1839    require: {
1840      afFormCtrl: '?^^afForm',
1841    },
1842    bindings: {
1843      urlArg: '@',
1844      selectedTab: '=?',
1845      rememberSelection: '<',
1846    },
1847    controller: function($scope, $element, $timeout) {
1848      this.tabs = [];
1849
1850      this.$onInit = function() {
1851        $element.addClass('crm-tabset');
1852
1853        if (this.urlArg) {
1854          $scope.$bindToRoute({
1855            expr: '$ctrl.selectedTab',
1856            param: this.urlArg,
1857            format: 'raw'
1858          });
1859        }
1860
1861        $timeout(() => {
1862          if (!this.selectedTab && this.rememberSelection) {
1863            this.selectedTab = CRM.cache.get(this.getCacheKey());
1864          }
1865          if (!this.selectedTab && this.tabs.length) {
1866            this.selectedTab = this.tabs[0].name;
1867          }
1868        });
1869
1870        if (this.rememberSelection) {
1871          $scope.$watch('$ctrl.selectedTab', (newTab) => {
1872            if (newTab) {
1873              CRM.cache.set(this.getCacheKey(), newTab);
1874            }
1875          });
1876        }
1877      };
1878
1879      this.addTab = (tab) => {
1880        this.tabs.push(tab);
1881      };
1882
1883      this.selectTab = (tabName) => {
1884        this.selectedTab = tabName;
1885      };
1886
1887      this.getFormName = () => this.afFormCtrl?.getFormMeta().name ?? $scope.$parent.meta.name;
1888
1889      this.getCacheKey = () => this.getFormName() + 'SelectedTab';
1890    }
1891  });
1892
1893  angular.module('af').directive('afTab', function() {
1894    return {
1895      restrict: 'E',
1896      require: '^afTabset',
1897      scope: {
1898        title: '@',
1899        icon: '@',
1900        count: '@',
1901        name: '@',
1902      },
1903      transclude: true,
1904      template: '<div ng-transclude role="tabpanel" ng-show="name === afTabsetCtrl.selectedTab" class="ng-hide"></div>',
1905      link: function (scope, element, attrs, afTabsetCtrl) {
1906        scope.name = scope.name || 'tab' + tabNumber++;
1907        scope.afTabsetCtrl = afTabsetCtrl;
1908        afTabsetCtrl.addTab(scope);
1909      }
1910    };
1911  });
1912
1913  angular.module('af').directive('afTitle', function() {
1914    return {
1915      restrict: 'A',
1916      bindToController: {
1917        title: '@afTitle'
1918      },
1919      controller: function($scope, $element) {
1920
1921        $scope.$watch(() => this.title, (text) => {
1922          let tag = 'h4';
1923          if ($element.is('fieldset')) {
1924            tag = 'legend';
1925          }
1926          if ($element.is('details')) {
1927            tag = 'summary';
1928          }
1929          let $title = $element.children(tag + '.af-title');
1930          if (!$title.length) {
1931            $title = $(`<${tag} class="af-title" />`).prependTo($element);
1932          }
1933          $title.text(text);
1934        });
1935      }
1936    };
1937  });
1938})(angular, CRM.$, CRM._);
1939
1940(function (CRM, angular) {
1941
1942
1943  class AfToken extends HTMLElement {
1944
1945    connectedCallback() {
1946      this.afForm = this.closest('af-form');
1947      if (!this.afForm) {
1948        throw new Error('af-token should be placed within an af-form');
1949      }
1950
1951      this.render();
1952      this.registerListener();
1953    }
1954
1955    disconnectedCallback() {
1956      this.removeListener();
1957    }
1958
1959    render() {
1960      this.innerText = this.evaluate(this.expression);
1961    }
1962
1963    registerListener() {
1964      this.afForm?.addEventListener('change', () => this.render());
1965    }
1966
1967    removeListener() {
1968      this.afForm?.removeEventListener('change', () => this.render());
1969    }
1970
1971    get expression() {
1972      return this.getAttribute('expression');
1973    }
1974
1975    get afFormCtrl() {
1976      return angular.element(this.afForm).controller('afForm');
1977    }
1978
1979    evaluate(expression) {
1980      return this.afFormCtrl ? this.afFormCtrl.replaceTokens(expression) : '';
1981    }
1982
1983  }
1984
1985  customElements.define('af-token', AfToken);
1986
1987})(CRM, angular);
1988(function(angular, $, _) {
1989  "use strict";
1990  angular.module('af').component('afLocationInput', {
1991    require: {
1992      ngModel: 'ngModel',
1993    },
1994    templateUrl: '~/af/fields/afLocationInput.html',
1995    controller: function($scope, $element) {
1996      const ts = $scope.ts = CRM.ts('org.civicrm.afform');
1997
1998      this.values = {
1999        distance: null,
2000        distance_unit: null,
2001        address: '',
2002      };
2003
2004      this.$onInit = () => {
2005        $scope.$watchCollection('$ctrl.values', (values) => {
2006          if (values.address && values.distance_unit && values.distance !== null) {
2007            this.ngModel.$setViewValue(values);
2008          } else {
2009            this.ngModel.$setViewValue(null);
2010          }
2011        });
2012        this.ngModel.$render = () => {
2013          this.values = this.ngModel.$viewValue || this.values;
2014        };
2015
2016      };
2017
2018    }
2019  });
2020
2021angular.module('afCore', CRM.angRequires('afCore'));
2022  angular.module('afCore').service('afCoreDirective', function($location, crmApi4, crmStatus, crmUiAlert) {
2023    return function(camelName, meta, d) {
2024      d.restrict = 'E';
2025      d.scope = {};
2026      d.scope.options = '=';
2027      d.link = {
2028        pre: function($scope, $el, $attr) {
2029          $scope.ts = CRM.ts(camelName);
2030          $scope.meta = meta;
2031          $scope.crmApi4 = crmApi4;
2032          $scope.crmStatus = crmStatus;
2033          $scope.crmUiAlert = crmUiAlert;
2034          $scope.crmUrl = CRM.url;
2035
2036          $el.addClass('afform-directive');
2037          const dialog = $el.closest('.ui-dialog-content');
2038          if (!dialog.length) {
2039            $scope.routeParams = $location.search();
2040            $scope.$watch(() => $location.search(), params => $scope.routeParams = params);
2041          } else {
2042            $scope.routeParams = {};
2043            if (typeof dialog.data('urlHash') === 'string' && dialog.data('urlHash').includes('?')) {
2044              const searchParams = new URLSearchParams(dialog.data('urlHash').split('?')[1]);
2045              searchParams.forEach((value, key) => $scope.routeParams[key] = value);
2046            }
2047          }
2048
2049          $scope.$parent.afformTitle = meta.title;
2050          $scope.addTitle = function(addition) {
2051            $scope.$parent.afformTitle = addition + ' ' + meta.title;
2052          };
2053
2054          $scope.checkLinkPerm = function(permissionName, createdId) {
2055            if (permissionName.startsWith('manage own') && CRM.config.cid !== createdId) {
2056              permissionName = permissionName.replace('manage own', 'administer');
2057            }
2058            return CRM.checkPerm(permissionName);
2059          };
2060
2061        }
2062      };
2063      return d;
2064    };
2065  });
2066
2067angular.module('afCore').directive('afApi3Ctrl', function() {
2068    return {
2069      restrict: 'EA',
2070      scope: {
2071        afApi3Ctrl: '=',
2072        afApi3: '@',
2073        afApi3Refresh: '@',
2074        onRefresh: '@'
2075      },
2076      controllerAs: 'afApi3Ctrl',
2077      controller: function($scope, $parse, crmThrottle, crmApi) {
2078        var ctrl = this;
2079        var parts = $parse($scope.afApi3)($scope.$parent);
2080        ctrl.entity = parts[0];
2081        ctrl.action = parts[1];
2082        ctrl.params = parts[2];
2083        ctrl.result = {};
2084        ctrl.loading = ctrl.firstLoad = true;
2085
2086        ctrl.refresh = function refresh() {
2087          ctrl.loading = true;
2088          crmThrottle(function () {
2089            return crmApi(ctrl.entity, ctrl.action, ctrl.params)
2090              .then(function (response) {
2091                ctrl.result = response;
2092                ctrl.loading = ctrl.firstLoad = false;
2093                if ($scope.onRefresh) {
2094                  $scope.$parent.$eval($scope.onRefresh, ctrl);
2095                }
2096              });
2097          });
2098        };
2099
2100        $scope.afApi3Ctrl = this;
2101
2102        var mode = $scope.afApi3Refresh ? $scope.afApi3Refresh : 'auto';
2103        switch (mode) {
2104          case 'auto': $scope.$watchCollection('afApi3Ctrl.params', ctrl.refresh); break;
2105          case 'init': ctrl.refresh(); break;
2106          case 'manual': break;
2107          default: throw 'Unrecognized refresh mode: '+ mode;
2108        }
2109      }
2110    };
2111  });
2112
2113
2114angular.module('afCore').directive('afApi4Action', function($parse, crmStatus, crmApi4) {
2115    return {
2116      restrict: 'A',
2117      scope: {
2118        afApi4Action: '@',
2119        afApi4StartMsg: '=',
2120        afApi4ErrorMsg: '=',
2121        afApi4SuccessMsg: '=',
2122        afApi4Success: '@',
2123        onError: '@'
2124      },
2125      link: function($scope, $el, $attr) {
2126        const ts = CRM.ts(null);
2127
2128        function running(x) {$el.toggleClass('af-api4-action-running', x).toggleClass('af-api4-action-idle', !x);}
2129
2130        running(false);
2131
2132        $el.click(function(){
2133          const parts = $parse($scope.afApi4Action)($scope.$parent);
2134          const msgs = {
2135            start: $scope.afApi4StartMsg || ts('Submitting...'),
2136            success: $scope.afApi4SuccessMsg,
2137            error: $scope.afApi4ErrorMsg
2138          };
2139          running(true);
2140          crmStatus(msgs, crmApi4(parts[0], parts[1], parts[2]))
2141            .finally(function(){running(false);})
2142            .then(function(response){$scope.$parent.$eval($scope.afApi4Success, {response: response});})
2143            .catch(function(error){$scope.$parent.$eval($scope.onError, {error: error});});
2144        });
2145      }
2146    };
2147  });
2148
2149
2150angular.module('afCore').directive('afApi4Ctrl', function() {
2151    return {
2152      restrict: 'EA',
2153      scope: {
2154        afApi4Ctrl: '=',
2155        afApi4: '@',
2156        afApi4Refresh: '@',
2157        onRefresh: '@'
2158      },
2159      controllerAs: 'afApi4Ctrl',
2160      controller: function($scope, $parse, crmThrottle, crmApi4) {
2161        var ctrl = this;
2162        var parts = $parse($scope.afApi4)($scope.$parent);
2163        ctrl.entity = parts[0];
2164        ctrl.action = parts[1];
2165        ctrl.params = parts[2];
2166        ctrl.index = parts[3];
2167        ctrl.result = {};
2168        ctrl.loading = ctrl.firstLoad = true;
2169
2170        ctrl.refresh = function refresh() {
2171          ctrl.loading = true;
2172          crmThrottle(function () {
2173            return crmApi4(ctrl.entity, ctrl.action, ctrl.params, ctrl.index)
2174              .then(function (response) {
2175                ctrl.result = response;
2176                ctrl.loading = ctrl.firstLoad = false;
2177                if ($scope.onRefresh) {
2178                  $scope.$parent.$eval($scope.onRefresh, ctrl);
2179                }
2180              });
2181          });
2182        };
2183
2184        $scope.afApi4Ctrl = this;
2185
2186        var mode = $scope.afApi4Refresh ? $scope.afApi4Refresh : 'auto';
2187        switch (mode) {
2188          case 'auto':
2189            $scope.$watchCollection('afApi4Ctrl.params', ctrl.refresh, true);
2190            $scope.$watch('afApi4Ctrl.index', ctrl.refresh, true);
2191            $scope.$watch('afApi4Ctrl.entity', ctrl.refresh, true);
2192            $scope.$watch('afApi4Ctrl.action', ctrl.refresh, true);
2193            break;
2194          case 'init': ctrl.refresh(); break;
2195          case 'manual': break;
2196          default: throw 'Unrecognized refresh mode: '+ mode;
2197        }
2198      }
2199    };
2200  });
2201
2202
2203angular.module('afsearchUpcomingPlaygroups', CRM.angRequires('afsearchUpcomingPlaygroups'));
2204  angular.module('afsearchUpcomingPlaygroups').directive('afsearchUpcomingPlaygroups', function(afCoreDirective) {
2205    return afCoreDirective("afsearchUpcomingPlaygroups", {"title":"upcoming playgroups","redirect":null,"autosave_draft":false,"confirmation_type":"redirect_to_url","confirmation_message":null,"name":"afsearchUpcomingPlaygroups"}, {
2206      templateUrl: "~/afsearchUpcomingPlaygroups/afsearchUpcomingPlaygroups.aff.html"
2207    });
2208  });
2209})(angular, CRM.$, CRM._);
2210
2211
vendor: 23,361 bytes, lines 2212-2217
2212/*
2213 angular-file-upload v2.6.1
2214 https://github.com/nervgh/angular-file-upload
2215*/
2216
2217!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports["angular-file-upload"]=t():e["angular-file-upload"]=t()}(this,function(){return function(e){function t(n){if(o[n])return o[n].exports;var r=o[n]={exports:{},id:n,loaded:!1};return e[n].call(r.exports,r,r.exports,t),r.loaded=!0,r.exports}var o={};return t.m=e,t.c=o,t.p="",t(0)}([function(e,t,o){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}var r=o(1),i=n(r),s=o(2),a=n(s),u=o(3),l=n(u),p=o(4),c=n(p),f=o(5),d=n(f),h=o(6),y=n(h),m=o(7),v=n(m),_=o(8),g=n(_),b=o(9),F=n(b),O=o(10),C=n(O),T=o(11),I=n(T),w=o(12),A=n(w),U=o(13),x=n(U);angular.module(i.default.name,[]).value("fileUploaderOptions",a.default).factory("FileUploader",l.default).factory("FileLikeObject",c.default).factory("FileItem",d.default).factory("FileDirective",y.default).factory("FileSelect",v.default).factory("FileDrop",F.default).factory("FileOver",C.default).factory("Pipeline",g.default).directive("nvFileSelect",I.default).directive("nvFileDrop",A.default).directive("nvFileOver",x.default).run(["FileUploader","FileLikeObject","FileItem","FileDirective","FileSelect","FileDrop","FileOver","Pipeline",function(e,t,o,n,r,i,s,a){e.FileLikeObject=t,e.FileItem=o,e.FileDirective=n,e.FileSelect=r,e.FileDrop=i,e.FileOver=s,e.Pipeline=a}])},function(e,t){e.exports={name:"angularFileUpload"}},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default={url:"/",alias:"file",headers:{},queue:[],progress:0,autoUpload:!1,removeAfterUpload:!1,method:"POST",filters:[],formData:[],queueLimit:Number.MAX_VALUE,withCredentials:!1,disableMultipart:!1}},function(e,t,o){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t,o,n,i,a,u,g){var b=n.File,F=n.FormData,O=function(){function n(t){r(this,n);var o=p(e);c(this,o,t,{isUploading:!1,_nextIndex:0,_directives:{select:[],drop:[],over:[]}}),this.filters.unshift({name:"queueLimit",fn:this._queueLimitFilter}),this.filters.unshift({name:"folder",fn:this._folderFilter})}return n.prototype.addToQueue=function(e,t,o){var n=this,r=this.isArrayLikeObject(e)?Array.prototype.slice.call(e):[e],i=this._getFilters(o),l=this.queue.length,p=[],c=function e(){var o=r.shift();if(v(o))return f();var l=n.isFile(o)?o:new a(o),c=n._convertFiltersToPipes(i),d=new g(c),h=function(t){var o=t.pipe.originalFilter,r=s(t.args,2),i=r[0],a=r[1];n._onWhenAddingFileFailed(i,o,a),e()},y=function(t,o){var r=new u(n,t,o);p.push(r),n.queue.push(r),n._onAfterAddingFile(r),e()};d.onThrown=h,d.onSuccessful=y,d.exec(l,t)},f=function(){n.queue.length!==l&&(n._onAfterAddingAll(p),n.progress=n._getTotalProgress()),n._render(),n.autoUpload&&n.uploadAll()};c()},n.prototype.removeFromQueue=function(e){var t=this.getIndexOfItem(e),o=this.queue[t];o.isUploading&&o.cancel(),this.queue.splice(t,1),o._destroy(),this.progress=this._getTotalProgress()},n.prototype.clearQueue=function(){for(;this.queue.length;)this.queue[0].remove();this.progress=0},n.prototype.uploadItem=function(e){var t=this.getIndexOfItem(e),o=this.queue[t],n=this.isHTML5?"_xhrTransport":"_iframeTransport";o._prepareToUploading(),this.isUploading||(this._onBeforeUploadItem(o),o.isCancel||(o.isUploading=!0,this.isUploading=!0,this[n](o),this._render()))},n.prototype.cancelItem=function(e){var t=this,o=this.getIndexOfItem(e),n=this.queue[o],r=this.isHTML5?"_xhr":"_form";if(n)if(n.isCancel=!0,n.isUploading)n[r].abort();else{var s=[void 0,0,{}],a=function(){t._onCancelItem.apply(t,[n].concat(s)),t._onCompleteItem.apply(t,[n].concat(s))};i(a)}},n.prototype.uploadAll=function(){var e=this.getNotUploadedItems().filter(function(e){return!e.isUploading});e.length&&(f(e,function(e){return e._prepareToUploading()}),e[0].upload())},n.prototype.cancelAll=function(){var e=this.getNotUploadedItems();f(e,function(e){return e.cancel()})},n.prototype.isFile=function(e){return this.constructor.isFile(e)},n.prototype.isFileLikeObject=function(e){return this.constructor.isFileLikeObject(e)},n.prototype.isArrayLikeObject=function(e){return this.constructor.isArrayLikeObject(e)},n.prototype.getIndexOfItem=function(e){return h(e)?e:this.queue.indexOf(e)},n.prototype.getNotUploadedItems=function(){return this.queue.filter(function(e){return!e.isUploaded})},n.prototype.getReadyItems=function(){return this.queue.filter(function(e){return e.isReady&&!e.isUploading}).sort(function(e,t){return e.index-t.index})},n.prototype.destroy=function(){var e=this;f(this._directives,function(t){f(e._directives[t],function(e){e.destroy()})})},n.prototype.onAfterAddingAll=function(e){},n.prototype.onAfterAddingFile=function(e){},n.prototype.onWhenAddingFileFailed=function(e,t,o){},n.prototype.onBeforeUploadItem=function(e){},n.prototype.onProgressItem=function(e,t){},n.prototype.onProgressAll=function(e){},n.prototype.onSuccessItem=function(e,t,o,n){},n.prototype.onErrorItem=function(e,t,o,n){},n.prototype.onCancelItem=function(e,t,o,n){},n.prototype.onCompleteItem=function(e,t,o,n){},n.prototype.onTimeoutItem=function(e){},n.prototype.onCompleteAll=function(){},n.prototype._getTotalProgress=function(e){if(this.removeAfterUpload)return e||0;var t=this.getNotUploadedItems().length,o=t?this.queue.length-t:this.queue.length,n=100/this.queue.length,r=(e||0)*n/100;return Math.round(o*n+r)},n.prototype._getFilters=function(e){if(!e)return this.filters;if(m(e))return e;var t=e.match(/[^\s,]+/g);return this.filters.filter(function(e){return t.indexOf(e.name)!==-1})},n.prototype._convertFiltersToPipes=function(e){var t=this;return e.map(function(e){var o=l(t,e.fn);return o.isAsync=3===e.fn.length,o.originalFilter=e,o})},n.prototype._render=function(){t.$$phase||t.$apply()},n.prototype._folderFilter=function(e){return!(!e.size&&!e.type)},n.prototype._queueLimitFilter=function(){return this.queue.length<this.queueLimit},n.prototype._isSuccessCode=function(e){return e>=200&&e<300||304===e},n.prototype._transformResponse=function(e,t){var n=this._headersGetter(t);return f(o.defaults.transformResponse,function(t){e=t(e,n)}),e},n.prototype._parseHeaders=function(e){var t,o,n,r={};return e?(f(e.split("\n"),function(e){n=e.indexOf(":"),t=e.slice(0,n).trim().toLowerCase(),o=e.slice(n+1).trim(),t&&(r[t]=r[t]?r[t]+", "+o:o)}),r):r},n.prototype._headersGetter=function(e){return function(t){return t?e[t.toLowerCase()]||null:e}},n.prototype._xhrTransport=function(e){var t,o=this,n=e._xhr=new XMLHttpRequest;if(e.disableMultipart?t=e._file:(t=new F,f(e.formData,function(e){f(e,function(e,o){t.append(o,e)})}),t.append(e.alias,e._file,e.file.name)),"number"!=typeof e._file.size)throw new TypeError("The file specified is no longer valid");n.upload.onprogress=function(t){var n=Math.round(t.lengthComputable?100*t.loaded/t.total:0);o._onProgressItem(e,n)},n.onload=function(){var t=o._parseHeaders(n.getAllResponseHeaders()),r=o._transformResponse(n.response,t),i=o._isSuccessCode(n.status)?"Success":"Error",s="_on"+i+"Item";o[s](e,r,n.status,t),o._onCompleteItem(e,r,n.status,t)},n.onerror=function(){var t=o._parseHeaders(n.getAllResponseHeaders()),r=o._transformResponse(n.response,t);o._onErrorItem(e,r,n.status,t),o._onCompleteItem(e,r,n.status,t)},n.onabort=function(){var t=o._parseHeaders(n.getAllResponseHeaders()),r=o._transformResponse(n.response,t);o._onCancelItem(e,r,n.status,t),o._onCompleteItem(e,r,n.status,t)},n.ontimeout=function(t){var r=o._parseHeaders(n.getAllResponseHeaders()),i="Request Timeout.";o._onTimeoutItem(e),o._onCompleteItem(e,i,408,r)},n.open(e.method,e.url,!0),n.timeout=e.timeout||0,n.withCredentials=e.withCredentials,f(e.headers,function(e,t){n.setRequestHeader(t,e)}),n.send(t)},n.prototype._iframeTransport=function(e){var t=this,o=_('<form style="display: none;" />'),n=_('<iframe name="iframeTransport'+Date.now()+'">'),r=e._input,i=0,s=null,a=!1;e._form&&e._form.replaceWith(r),e._form=o,r.prop("name",e.alias),f(e.formData,function(e){f(e,function(e,t){var n=_('<input type="hidden" name="'+t+'" />');n.val(e),o.append(n)})}),o.prop({action:e.url,method:"POST",target:n.prop("name"),enctype:"multipart/form-data",encoding:"multipart/form-data"}),n.bind("load",function(){var o="",r=200;try{o=n[0].contentDocument.body.innerHTML}catch(e){r=500}if(s&&clearTimeout(s),s=null,a)return!1;var i={response:o,status:r,dummy:!0},u={},l=t._transformResponse(i.response,u);t._onSuccessItem(e,l,i.status,u),t._onCompleteItem(e,l,i.status,u)}),o.abort=function(){var i,s={status:0,dummy:!0},a={};n.unbind("load").prop("src","javascript:false;"),o.replaceWith(r),t._onCancelItem(e,i,s.status,a),t._onCompleteItem(e,i,s.status,a)},r.after(o),o.append(r).append(n),i=e.timeout||0,s=null,i&&(s=setTimeout(function(){a=!0,e.isCancel=!0,e.isUploading&&(n.unbind("load").prop("src","javascript:false;"),o.replaceWith(r));var i={},s="Request Timeout.";t._onTimeoutItem(e),t._onCompleteItem(e,s,408,i)},i)),o[0].submit()},n.prototype._onWhenAddingFileFailed=function(e,t,o){this.onWhenAddingFileFailed(e,t,o)},n.prototype._onAfterAddingFile=function(e){this.onAfterAddingFile(e)},n.prototype._onAfterAddingAll=function(e){this.onAfterAddingAll(e)},n.prototype._onBeforeUploadItem=function(e){e._onBeforeUpload(),this.onBeforeUploadItem(e)},n.prototype._onProgressItem=function(e,t){var o=this._getTotalProgress(t);this.progress=o,e._onProgress(t),this.onProgressItem(e,t),this.onProgressAll(o),this._render()},n.prototype._onSuccessItem=function(e,t,o,n){e._onSuccess(t,o,n),this.onSuccessItem(e,t,o,n)},n.prototype._onErrorItem=function(e,t,o,n){e._onError(t,o,n),this.onErrorItem(e,t,o,n)},n.prototype._onCancelItem=function(e,t,o,n){e._onCancel(t,o,n),this.onCancelItem(e,t,o,n)},n.prototype._onCompleteItem=function(e,t,o,n){e._onComplete(t,o,n),this.onCompleteItem(e,t,o,n);var r=this.getReadyItems()[0];return this.isUploading=!1,y(r)?void r.upload():(this.onCompleteAll(),this.progress=this._getTotalProgress(),void this._render())},n.prototype._onTimeoutItem=function(e){e._onTimeout(),this.onTimeoutItem(e)},n.isFile=function(e){return b&&e instanceof b},n.isFileLikeObject=function(e){return e instanceof a},n.isArrayLikeObject=function(e){return d(e)&&"length"in e},n.inherit=function(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,e.super_=t},n}();return O.prototype.isHTML5=!(!b||!F),O.isHTML5=O.prototype.isHTML5,O}Object.defineProperty(t,"__esModule",{value:!0});var s=function(){function e(e,t){var o=[],n=!0,r=!1,i=void 0;try{for(var s,a=e[Symbol.iterator]();!(n=(s=a.next()).done)&&(o.push(s.value),!t||o.length!==t);n=!0);}catch(e){r=!0,i=e}finally{try{!n&&a.return&&a.return()}finally{if(r)throw i}}return o}return function(t,o){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return e(t,o);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}();t.default=i;var a=o(1),u=(n(a),angular),l=u.bind,p=u.copy,c=u.extend,f=u.forEach,d=u.isObject,h=u.isNumber,y=u.isDefined,m=u.isArray,v=u.isUndefined,_=u.element;i.$inject=["fileUploaderOptions","$rootScope","$http","$window","$timeout","FileLikeObject","FileItem","Pipeline"]},function(e,t,o){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(){return function(){function e(t){r(this,e);var o=l(t),n=o?t.value:t,i=p(n)?"FakePath":"Object",s="_createFrom"+i;this[s](n,t)}return e.prototype._createFromFakePath=function(e,t){this.lastModifiedDate=null,this.size=null,this.type="like/"+e.slice(e.lastIndexOf(".")+1).toLowerCase(),this.name=e.slice(e.lastIndexOf("/")+e.lastIndexOf("\\")+2),this.input=t},e.prototype._createFromObject=function(e){this.lastModifiedDate=u(e.lastModifiedDate),this.size=e.size,this.type=e.type,this.name=e.name,this.input=e.input},e}()}Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var s=o(1),a=(n(s),angular),u=a.copy,l=a.isElement,p=a.isString},function(e,t,o){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){return function(){function o(e,n,i){r(this,o);var s=!!n.input,a=s?p(n.input):null,c=s?null:n;l(this,{url:e.url,alias:e.alias,headers:u(e.headers),formData:u(e.formData),removeAfterUpload:e.removeAfterUpload,withCredentials:e.withCredentials,disableMultipart:e.disableMultipart,method:e.method,timeout:e.timeout},i,{uploader:e,file:new t(n),isReady:!1,isUploading:!1,isUploaded:!1,isSuccess:!1,isCancel:!1,isError:!1,progress:0,index:null,_file:c,_input:a}),a&&this._replaceNode(a)}return o.prototype.upload=function(){try{this.uploader.uploadItem(this)}catch(t){var e=t.name+":"+t.message;this.uploader._onCompleteItem(this,e,t.code,[]),this.uploader._onErrorItem(this,e,t.code,[])}},o.prototype.cancel=function(){this.uploader.cancelItem(this)},o.prototype.remove=function(){this.uploader.removeFromQueue(this)},o.prototype.onBeforeUpload=function(){},o.prototype.onProgress=function(e){},o.prototype.onSuccess=function(e,t,o){},o.prototype.onError=function(e,t,o){},o.prototype.onCancel=function(e,t,o){},o.prototype.onComplete=function(e,t,o){},o.prototype.onTimeout=function(){},o.prototype._onBeforeUpload=function(){this.isReady=!0,this.isUploading=!1,this.isUploaded=!1,this.isSuccess=!1,this.isCancel=!1,this.isError=!1,this.progress=0,this.onBeforeUpload()},o.prototype._onProgress=function(e){this.progress=e,this.onProgress(e)},o.prototype._onSuccess=function(e,t,o){this.isReady=!1,this.isUploading=!1,this.isUploaded=!0,this.isSuccess=!0,this.isCancel=!1,this.isError=!1,this.progress=100,this.index=null,this.onSuccess(e,t,o)},o.prototype._onError=function(e,t,o){this.isReady=!1,this.isUploading=!1,this.isUploaded=!0,this.isSuccess=!1,this.isCancel=!1,this.isError=!0,this.progress=0,this.index=null,this.onError(e,t,o)},o.prototype._onCancel=function(e,t,o){this.isReady=!1,this.isUploading=!1,this.isUploaded=!1,this.isSuccess=!1,this.isCancel=!0,this.isError=!1,this.progress=0,this.index=null,this.onCancel(e,t,o)},o.prototype._onComplete=function(e,t,o){this.onComplete(e,t,o),this.removeAfterUpload&&this.remove()},o.prototype._onTimeout=function(){this.isReady=!1,this.isUploading=!1,this.isUploaded=!1,this.isSuccess=!1,this.isCancel=!1,this.isError=!0,this.progress=0,this.index=null,this.onTimeout()},o.prototype._destroy=function(){this._input&&this._input.remove(),this._form&&this._form.remove(),delete this._form,delete this._input},o.prototype._prepareToUploading=function(){this.index=this.index||++this.uploader._nextIndex,this.isReady=!0},o.prototype._replaceNode=function(t){var o=e(t.clone())(t.scope());o.prop("value",null),t.css("display","none"),t.after(o)},o}()}Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var s=o(1),a=(n(s),angular),u=a.copy,l=a.extend,p=a.element;a.isElement;i.$inject=["$compile","FileLikeObject"]},function(e,t,o){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(){var e=function(){function e(t){r(this,e),u(this,t),this.uploader._directives[this.prop].push(this),this._saveLinks(),this.bind()}return e.prototype.bind=function(){for(var e in this.events){var t=this.events[e];this.element.bind(e,this[t])}},e.prototype.unbind=function(){for(var e in this.events)this.element.unbind(e,this.events[e])},e.prototype.destroy=function(){var e=this.uploader._directives[this.prop].indexOf(this);this.uploader._directives[this.prop].splice(e,1),this.unbind()},e.prototype._saveLinks=function(){for(var e in this.events){var t=this.events[e];this[t]=this[t].bind(this)}},e}();return e.prototype.events={},e}Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var s=o(1),a=(n(s),angular),u=a.extend},function(e,t,o){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function s(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function a(e,t){return function(t){function o(e){r(this,o);var n=p(e,{events:{$destroy:"destroy",change:"onChange"},prop:"select"}),s=i(this,t.call(this,n));return s.uploader.isHTML5||s.element.removeAttr("multiple"),s.element.prop("value",null),s}return s(o,t),o.prototype.getOptions=function(){},o.prototype.getFilters=function(){},o.prototype.isEmptyAfterSelection=function(){return!!this.element.attr("multiple")},o.prototype.onChange=function(){var t=this.uploader.isHTML5?this.element[0].files:this.element[0],o=this.getOptions(),n=this.getFilters();this.uploader.isHTML5||this.destroy(),this.uploader.addToQueue(t,o,n),this.isEmptyAfterSelection()&&(this.element.prop("value",null),this.element.replaceWith(e(this.element.clone())(this.scope)))},o}(t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var u=o(1),l=(n(u),angular),p=l.extend;a.$inject=["$compile","FileDirective"]},function(e,t){"use strict";function o(e){if(Array.isArray(e)){for(var t=0,o=Array(e.length);t<e.length;t++)o[t]=e[t];return o}return Array.from(e)}function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function r(e){return function(){function t(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];n(this,t),this.pipes=e}return t.prototype.next=function(t){var n=this.pipes.shift();if(a(n))return void this.onSuccessful.apply(this,o(t));var r=new Error("The filter has not passed");if(r.pipe=n,r.args=t,n.isAsync){var i=e.defer(),u=s(this,this.next,t),l=s(this,this.onThrown,r);i.promise.then(u,l),n.apply(void 0,o(t).concat([i]))}else{var p=Boolean(n.apply(void 0,o(t)));p?this.next(t):this.onThrown(r)}},t.prototype.exec=function(){for(var e=arguments.length,t=Array(e),o=0;o<e;o++)t[o]=arguments[o];this.next(t)},t.prototype.onThrown=function(e){},t.prototype.onSuccessful=function(){},t}()}Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=angular,s=i.bind,a=i.isUndefined;r.$inject=["$q"]},function(e,t,o){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function s(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function a(e){return function(e){function t(o){r(this,t);var n=p(o,{events:{$destroy:"destroy",drop:"onDrop",dragover:"onDragOver",dragleave:"onDragLeave"},prop:"drop"});return i(this,e.call(this,n))}return s(t,e),t.prototype.getOptions=function(){},t.prototype.getFilters=function(){},t.prototype.onDrop=function(e){var t=this._getTransfer(e);if(t){var o=this.getOptions(),n=this.getFilters();this._preventAndStop(e),c(this.uploader._directives.over,this._removeOverClass,this),this.uploader.addToQueue(t.files,o,n)}},t.prototype.onDragOver=function(e){var t=this._getTransfer(e);this._haveFiles(t.types)&&(t.dropEffect="copy",this._preventAndStop(e),c(this.uploader._directives.over,this._addOverClass,this))},t.prototype.onDragLeave=function(e){e.currentTarget!==this.element[0]&&(this._preventAndStop(e),c(this.uploader._directives.over,this._removeOverClass,this))},t.prototype._getTransfer=function(e){return e.dataTransfer?e.dataTransfer:e.originalEvent.dataTransfer},t.prototype._preventAndStop=function(e){e.preventDefault(),e.stopPropagation()},t.prototype._haveFiles=function(e){return!!e&&(e.indexOf?e.indexOf("Files")!==-1:!!e.contains&&e.contains("Files"))},t.prototype._addOverClass=function(e){e.addOverClass()},t.prototype._removeOverClass=function(e){e.removeOverClass()},t}(e)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var u=o(1),l=(n(u),angular),p=l.extend,c=l.forEach;a.$inject=["FileDirective"]},function(e,t,o){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function s(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function a(e){return function(e){function t(o){r(this,t);var n=p(o,{events:{$destroy:"destroy"},prop:"over",overClass:"nv-file-over"});return i(this,e.call(this,n))}return s(t,e),t.prototype.addOverClass=function(){this.element.addClass(this.getOverClass())},t.prototype.removeOverClass=function(){this.element.removeClass(this.getOverClass())},t.prototype.getOverClass=function(){return this.overClass},t}(e)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var u=o(1),l=(n(u),angular),p=l.extend;a.$inject=["FileDirective"]},function(e,t,o){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}function r(e,t,o){return{link:function(n,r,i){var s=n.$eval(i.uploader);if(!(s instanceof t))throw new TypeError('"Uploader" must be an instance of FileUploader');var a=new o({uploader:s,element:r,scope:n});a.getOptions=e(i.options).bind(a,n),a.getFilters=function(){return i.filters}}}}Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=o(1);n(i);r.$inject=["$parse","FileUploader","FileSelect"]},function(e,t,o){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}function r(e,t,o){return{link:function(n,r,i){var s=n.$eval(i.uploader);if(!(s instanceof t))throw new TypeError('"Uploader" must be an instance of FileUploader');if(s.isHTML5){var a=new o({uploader:s,element:r});a.getOptions=e(i.options).bind(a,n),a.getFilters=function(){return i.filters}}}}}Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=o(1);n(i);r.$inject=["$parse","FileUploader","FileDrop"]},function(e,t,o){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}function r(e,t){return{link:function(o,n,r){var i=o.$eval(r.uploader);if(!(i instanceof e))throw new TypeError('"Uploader" must be an instance of FileUploader');var s=new t({uploader:i,element:n});s.getOverClass=function(){return r.overClass||s.overClass}}}}Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=o(1);n(i);r.$inject=["FileUploader","FileOver"]}])});
2218
2219(function(angular, $, _) {
2220  angular.module('api4', CRM.angRequires('api4'));
2221
2222angular.module('api4').factory('crmApi4', function($q) {
2223    var crmApi4 = function(entity, action, params, index) {
2224      var deferred = $q.defer();
2225      var p;
2226      var backend = crmApi4.backend || CRM.api4;
2227      if (_.isObject(entity)) {
2228        /*jshint -W061 */
2229        p = backend(eval('('+angular.toJson(entity)+')'), action);
2230      } else {
2231        /*jshint -W061 */
2232        p = backend(entity, action, eval('('+angular.toJson(params)+')'), index);
2233      }
2234      p.then(
2235        function(result) {
2236          deferred.resolve(result);
2237        },
2238        function(error) {
2239          deferred.reject(error);
2240        }
2241      );
2242      return deferred.promise;
2243    };
2244    crmApi4.backend = null;
2245    crmApi4.val = function(value) {
2246      var d = $.Deferred();
2247      d.resolve(value);
2248      return d.promise();
2249    };
2250    return crmApi4;
2251  });
2252
2253})(angular, CRM.$, CRM._);
2254
2255/**
2256 * Checklist-model
2257 * AngularJS directive for list of checkboxes
2258 * https://github.com/vitalets/checklist-model
2259 * License: MIT http://opensource.org/licenses/MIT
2260 */
2261
2262 /* commonjs package manager support (eg componentjs) */
2263 if (typeof module !== "undefined" && typeof exports !== "undefined" && module.exports === exports){
2264   module.exports = 'checklist-model';
2265 }
2266
2267angular.module('checklist-model', [])
2268.directive('checklistModel', ['$parse', '$compile', function($parse, $compile) {
2269  function contains(arr, item, comparator) {
2270    if (angular.isArray(arr)) {
2271      for (var i = arr.length; i--;) {
2272        if (comparator(arr[i], item)) {
2273          return true;
2274        }
2275      }
2276    }
2277    return false;
2278  }
2279  function add(arr, item, comparator) {
2280    arr = angular.isArray(arr) ? arr : [];
2281      if(!contains(arr, item, comparator)) {
2282          arr.push(item);
2283      }
2284    return arr;
2285  }
2286  function remove(arr, item, comparator) {
2287    if (angular.isArray(arr)) {
2288      for (var i = arr.length; i--;) {
2289        if (comparator(arr[i], item)) {
2290          arr.splice(i, 1);
2291          break;
2292        }
2293      }
2294    }
2295    return arr;
2296  }
2297  function postLinkFn(scope, elem, attrs) {
2298    var checklistModel = attrs.checklistModel;
2299    attrs.$set("checklistModel", null);
2300    $compile(elem)(scope);
2301    attrs.$set("checklistModel", checklistModel);
2302    var checklistModelGetter = $parse(checklistModel);
2303    var checklistChange = $parse(attrs.checklistChange);
2304    var checklistBeforeChange = $parse(attrs.checklistBeforeChange);
2305    var ngModelGetter = $parse(attrs.ngModel);
2306
2307
2308
2309    var comparator = function (a, b) {
2310      if(!isNaN(a) && !isNaN(b)) {
2311        return String(a) === String(b);
2312      } else {
2313        return angular.equals(a,b);
2314      }
2315    };
2316
2317    if (attrs.hasOwnProperty('checklistComparator')){
2318      if (attrs.checklistComparator[0] == '.') {
2319        var comparatorExpression = attrs.checklistComparator.substring(1);
2320        comparator = function (a, b) {
2321          return a[comparatorExpression] === b[comparatorExpression];
2322        };
2323
2324      } else {
2325        comparator = $parse(attrs.checklistComparator)(scope.$parent);
2326      }
2327    }
2328    var unbindModel = scope.$watch(attrs.ngModel, function(newValue, oldValue) {
2329      if (newValue === oldValue) {
2330        return;
2331      }
2332
2333      if (checklistBeforeChange && (checklistBeforeChange(scope) === false)) {
2334        ngModelGetter.assign(scope, contains(checklistModelGetter(scope.$parent), getChecklistValue(), comparator));
2335        return;
2336      }
2337
2338      setValueInChecklistModel(getChecklistValue(), newValue);
2339
2340      if (checklistChange) {
2341        checklistChange(scope);
2342      }
2343    });
2344    var unbindCheckListValue = scope.$watch(getChecklistValue, function(newValue, oldValue) {
2345      if( newValue != oldValue && angular.isDefined(oldValue) && scope[attrs.ngModel] === true ) {
2346        var current = checklistModelGetter(scope.$parent);
2347        checklistModelGetter.assign(scope.$parent, remove(current, oldValue, comparator));
2348        checklistModelGetter.assign(scope.$parent, add(current, newValue, comparator));
2349      }
2350    }, true);
2351
2352    var unbindDestroy = scope.$on('$destroy', destroy);
2353
2354    function destroy() {
2355      unbindModel();
2356      unbindCheckListValue();
2357      unbindDestroy();
2358    }
2359
2360    function getChecklistValue() {
2361      return attrs.checklistValue ? $parse(attrs.checklistValue)(scope.$parent) : attrs.value;
2362    }
2363
2364    function setValueInChecklistModel(value, checked) {
2365      var current = checklistModelGetter(scope.$parent);
2366      if (angular.isFunction(checklistModelGetter.assign)) {
2367        if (checked === true) {
2368          checklistModelGetter.assign(scope.$parent, add(current, value, comparator));
2369        } else {
2370          checklistModelGetter.assign(scope.$parent, remove(current, value, comparator));
2371        }
2372      }
2373
2374    }
2375    function setChecked(newArr, oldArr) {
2376      if (checklistBeforeChange && (checklistBeforeChange(scope) === false)) {
2377        setValueInChecklistModel(getChecklistValue(), ngModelGetter(scope));
2378        return;
2379      }
2380      ngModelGetter.assign(scope, contains(newArr, getChecklistValue(), comparator));
2381    }
2382    if (angular.isFunction(scope.$parent.$watchCollection)) {
2383        scope.$parent.$watchCollection(checklistModel, setChecked);
2384    } else {
2385        scope.$parent.$watch(checklistModel, setChecked, true);
2386    }
2387  }
2388
2389  return {
2390    restrict: 'A',
2391    priority: 1000,
2392    terminal: true,
2393    scope: true,
2394    compile: function(tElement, tAttrs) {
2395
2396      if (!tAttrs.checklistValue && !tAttrs.value) {
2397        throw 'You should provide `value` or `checklist-value`.';
2398      }
2399      if (!tAttrs.ngModel) {
2400        tAttrs.$set("ngModel", "checked");
2401      }
2402
2403      return postLinkFn;
2404    }
2405  };
2406}]);
2407
2408(function(angular, $, _) {
2409  "use strict";
2410  angular.module('civiCaseTasks', CRM.angRequires('civiCaseTasks'));
2411
2412
2413  angular.module('civiCaseTasks').controller('civiCaseTaskCaseRole', function($scope, crmApi4, searchTaskBaseTrait) {
2414    const ts = $scope.ts = CRM.ts('civi_case');
2415    const ctrl = angular.extend(this, $scope.model, searchTaskBaseTrait);
2416    const values = this.task.values && !Array.isArray(this.task.values) ? this.task.values : {};
2417    $scope.values = values;
2418
2419    this.existingCount = null;
2420    this.autocompleteParams = {};
2421    this.relationshipInfo = this.task.relationshipTypes.reduce((acc, item) => {
2422      acc[item.id] = item;
2423      return acc;
2424    }, {});
2425    if (values.relationship_type) {
2426      setCountAndFilters();
2427    }
2428
2429    this.onChangeRole = function() {
2430      values.addOrReplace = null;
2431      setCountAndFilters();
2432    };
2433
2434    function setCountAndFilters() {
2435      ctrl.existingCount = null;
2436      crmApi4('Relationship', 'get', {
2437        select: ['row_count'],
2438        where: [
2439          ['case_id', 'IN', ctrl.ids],
2440          ['is_current', '=', true],
2441          ['relationship_type_id', '=', values.relationship_type.split('_')[0]],
2442        ],
2443        groupBy: ['case_id']
2444      }).then(function(result) {
2445        ctrl.existingCount = result.count;
2446      });
2447      ctrl.autocompleteParams = {
2448        filters: {contact_sub_type: ctrl.relationshipInfo[values.relationship_type].contact_sub_type}
2449      };
2450    }
2451
2452    this.submit = function() {
2453      const relationshipType = values.relationship_type.split('_')[0];
2454      const a = values.relationship_type.split('_')[1];
2455      const b = values.relationship_type.split('_')[2];
2456      if (values.addOrReplace === 'replace') {
2457        crmApi4('Relationship', 'update', {
2458          values: {end_date: 'now', is_active: false},
2459          where: [
2460            ['case_id', 'IN', ctrl.ids],
2461            ['is_current', '=', true],
2462            ['relationship_type_id', '=', relationshipType],
2463            ['contact_id_' + b, '!=', values.contact_id],
2464          ]
2465        });
2466      }
2467      const apiValues = {
2468        case_id: '$case_id',
2469        relationship_type_id: relationshipType,
2470        start_date: 'now',
2471      };
2472      apiValues['contact_id_' + a] = '$contact_id';
2473      apiValues['contact_id_' + b] = values.contact_id;
2474      const apiParams = {
2475        chain: {relationship: ['Relationship', 'create', {values: apiValues}]}
2476      };
2477      ctrl.start(apiParams);
2478    };
2479
2480    this.onSuccess = function(result) {
2481      let added = 0;
2482      let duplicate = 0;
2483      result.forEach(function(item) {
2484        item.relationship.forEach(function(relationship) {
2485          if (relationship.duplicate_id) {
2486            duplicate++;
2487          } else {
2488            added++;
2489          }
2490        });
2491      });
2492      let msg = _.escape(ts('1 case role added.', {plural: '%count case roles added.', count: added}));
2493      if (duplicate) {
2494        msg += '<br>' + _.escape(ts('1 case role already occupied by the selected contact.', {plural: '%count case roles already occupied by the selected contact.', count: duplicate}));
2495      }
2496      CRM.alert(msg, ts('Saved'), 'success');
2497      this.close(result);
2498    };
2499
2500  });
2501
2502
2503  angular.module('civiCaseTasks').controller('civiCaseTaskOpenCase', function($scope, crmApi4, searchTaskBaseTrait, searchTaskFieldsTrait) {
2504    const ts = $scope.ts = CRM.ts('civi_case');
2505    const ctrl = angular.extend(this, $scope.model, searchTaskBaseTrait, searchTaskFieldsTrait);
2506
2507    this.skipDuplicates = true;
2508    this.multipleClients = false;
2509
2510    this.loadFieldsAndValues(this.task, 'Case',
2511      {action: 'create', where: [['name', 'NOT IN', ['contact_id', 'is_deleted']]]},
2512      {existingCaseContacts: ['CaseContact', 'get', {
2513          where: [
2514            ['contact_id', 'IN', ctrl.ids],
2515            ['case_id.status_id:name', '!=', 'Closed']
2516          ],
2517        }, ['contact_id']]}
2518    ).then((results) => {
2519      this.existingCaseContacts = results.existingCaseContacts;
2520      this.addField('subject');
2521    });
2522
2523    this.submit = function() {
2524      const apiParams = {};
2525      if (ctrl.skipDuplicates) {
2526        ctrl.ids = ctrl.ids.filter(id => !ctrl.existingCaseContacts.includes(id));
2527      }
2528      if (!ctrl.ids.length) {
2529        CRM.alert(_.escape(ts('No cases to open.')), _.escape(ts('All contacts skipped')));
2530        ctrl.cancel();
2531      }
2532      if (ctrl.multipleClients) {
2533        apiParams.values = _.zipObject(ctrl.values);
2534        apiParams.values.contact_id = ctrl.ids;
2535      }
2536      else {
2537        apiParams.records = [_.zipObject(ctrl.values)];
2538      }
2539      ctrl.start(apiParams);
2540    };
2541
2542    this.onSuccess = function(result) {
2543      let msg = _.escape(ts('Successfully opened 1 case.', {plural: 'Successfully opened %count cases.', count: result.length}));
2544      if (result.length === 1) {
2545        const viewLink = this.getUrl('view', result[0]);
2546        if (viewLink) {
2547          msg += '<br><a href="' + viewLink + '" target="_blank"><i class="crm-i fa-external-link" role="img" aria-hidden="true"></i> ' + _.escape(ts('View case')) + '</a>';
2548        }
2549      }
2550      CRM.alert(msg, ts('Saved'), 'success');
2551      this.close(result);
2552    };
2553
2554  });
2555
2556
2557  angular.module('crmDialog', CRM.angRequires('crmDialog'));
2558  angular.module('crmDialog').directive('crmDialogPopup', function(dialogService) {
2559    return {
2560      restrict: 'A',
2561      bindToController: {
2562        popupTpl: '@',
2563        popupName: '@crmDialogPopup',
2564        popupData: '<'
2565      },
2566      controller: function($scope, $element) {
2567        var ctrl = this;
2568        $element.on('click', function() {
2569          var options = CRM.utils.adjustDialogDefaults({
2570            autoOpen: false,
2571            title: _.trim($element.attr('title') || $element.text())
2572          });
2573          dialogService.open(ctrl.popupName, ctrl.popupTpl, ctrl.popupData || {}, options)
2574            .then(function(success) {
2575              $element.trigger('crmPopupFormSuccess');
2576            });
2577        });
2578      }
2579    };
2580  });
2581  angular.module('crmDialog').directive('crmDialog', function(dialogService) {
2582    return {
2583      restrict: 'A',
2584      controllerAs: '$dialog',
2585      controller: function($scope, $parse, $timeout) {
2586        var $dialog = this;
2587        $dialog.buttons = [];
2588
2589        $dialog.close = function(result) {
2590          dialogService.close($dialog.name, result);
2591        };
2592
2593        $dialog.cancel = function() {
2594          dialogService.cancel($dialog.name);
2595        };
2596
2597        $dialog.loadButtons = function() {
2598          var buttons = [];
2599          angular.forEach($dialog.buttons, function (crmDialogButton) {
2600            var button = _.pick(crmDialogButton, ['icons', 'text', 'disabled']);
2601            button.click = function() {
2602              $scope.$apply(crmDialogButton.onClick);
2603            };
2604            buttons.push(button);
2605          });
2606          dialogService.setButtons($dialog.name, buttons);
2607        };
2608
2609        $timeout(function() {
2610          $('.ui-dialog:last input:not([disabled]):not([type="submit"]):first').focus();
2611        });
2612
2613      },
2614      link: function(scope, element, attrs, controller) {
2615        controller.name = attrs.crmDialog;
2616        scope[attrs.crmDialog] = controller;
2617      }
2618    };
2619  });
2620  angular.module('crmDialog').component('crmDialogButton', {
2621    bindings: {
2622      disabled: '<',
2623      icons: '<',
2624      text: '<',
2625      onClick: '&'
2626    },
2627    require: {
2628      crmDialog: '?^^crmDialog'
2629    },
2630    controller: function($scope) {
2631      var $ctrl = this;
2632      $ctrl.$onInit = function() {
2633        $ctrl.crmDialog.buttons.push(this);
2634        $scope.$watch('$ctrl.disabled', $ctrl.crmDialog.loadButtons);
2635        $scope.$watch('$ctrl.text', $ctrl.crmDialog.loadButtons);
2636        $scope.$watch('$ctrl.icons', $ctrl.crmDialog.loadButtons);
2637      };
2638    }
2639  });
2640
2641
2642angular.module('crmResource', []);
2643
2644  angular.module('crmResource').factory('crmResource', function($q, $http) {
2645    var deferreds = {}; // null|object; deferreds[url][idx] = Deferred;
2646
2647    var notify = function notify() {
2648      var oldDfrds = deferreds;
2649      deferreds = null;
2650
2651      angular.forEach(oldDfrds, function(dfrs, url) {
2652        if (CRM.angular.templates[url]) {
2653          angular.forEach(dfrs, function(dfr) {
2654            dfr.resolve({
2655              status: 200,
2656              headers: function(name) {
2657                var headers = {'Content-type': 'text/html'};
2658                return name ? headers[name] : headers;
2659              },
2660              data: CRM.angular.templates[url]
2661            });
2662          });
2663        }
2664        else {
2665          angular.forEach(dfrs, function(dfr) {
2666            dfr.reject({status: 500}); // FIXME
2667          });
2668        }
2669      });
2670    };
2671
2672    var moduleUrl = CRM.angular.bundleUrl;
2673    $http.get(moduleUrl)
2674      .then(function httpSuccess(response) {
2675        CRM.angular.templates = CRM.angular.templates || {};
2676        angular.forEach(response.data, function (module) {
2677          if (module.partials) {
2678            angular.extend(CRM.angular.templates, module.partials);
2679          }
2680          if (module.strings) {
2681            CRM.addStrings(module.domain, module.strings);
2682          }
2683        });
2684        notify();
2685      }, function httpError() {
2686        notify();
2687      });
2688
2689    return {
2690      getUrl: function getUrl(url) {
2691        if (CRM.angular.templates && CRM.angular.templates[url]) {
2692          return CRM.angular.templates[url];
2693        }
2694        else {
2695          var deferred = $q.defer();
2696          if (!deferreds[url]) {
2697            deferreds[url] = [];
2698          }
2699          deferreds[url].push(deferred);
2700          return deferred.promise;
2701        }
2702      }
2703    };
2704  });
2705
2706  angular.module('crmResource').config(function($provide) {
2707    $provide.decorator('$templateCache', function($delegate, $http, $q, crmResource) {
2708      var origGet = $delegate.get;
2709      var urlPat = /^~\//;
2710      $delegate.get = function(url) {
2711        if (urlPat.test(url)) {
2712          return crmResource.getUrl(url);
2713        }
2714        else {
2715          return origGet.call(this, url);
2716        }
2717      };
2718      return $delegate;
2719    });
2720  });
2721
2722
2723  angular.module('crmSearchDisplay', CRM.angRequires('crmSearchDisplay'));
2724
2725
2726  angular.module('crmSearchDisplay').component('crmSearchDisplay', {
2727    bindings: {
2728      type: '@',
2729      apiEntity: '@',
2730      search: '<',
2731      display: '<',
2732      settings: '<',
2733      filters: '<',
2734    },
2735    template: () => {
2736      let html = '';
2737      const displayTypes = CRM.crmSearchDisplay.viewableDisplayTypes;
2738      Object.entries(displayTypes).forEach(([type, directive]) => {
2739        html += `<${directive} ng-if="$ctrl.type === '${type}'" api-entity="{{:: $ctrl.apiEntity }}" search="$ctrl.search" display="$ctrl.display" settings="$ctrl.settings" filters="$ctrl.filters"></${directive}>`;
2740      });
2741      return html;
2742    },
2743    controller: function($scope, crmApi4) {
2744
2745      this.$onInit = () => {
2746        if (!this.settings || !this.apiEntity || !this.type) {
2747          const apiCalls = {
2748            search: ['SavedSearch', 'get', {
2749              select: ['id', 'name', 'api_entity'],
2750              where: [['name', '=', this.search]],
2751            }, 0],
2752          };
2753          if (this.display) {
2754            apiCalls.display = ['SearchDisplay', 'get', {
2755              select: ['type', 'settings'],
2756              where: [['name', '=', this.display], ['saved_search_id.name', '=', this.search]],
2757            }, 0];
2758          } else {
2759            apiCalls.display = ['SearchDisplay', 'getDefault', {
2760              select: ['type', 'settings'],
2761              savedSearch: this.search,
2762            }, 0];
2763          }
2764          crmApi4(apiCalls).then((result) => {
2765            this.apiEntity = result.search.api_entity;
2766            this.settings = result.display.settings;
2767            this.type = result.display.type;
2768          });
2769        }
2770      };
2771
2772    }
2773
2774  });
2775
2776
2777
2778  angular.module('crmSearchDisplay').component('crmSearchDisplayEditable', {
2779    bindings: {
2780      row: '<?',
2781      display: '<',
2782      colKey: '<',
2783      colData: '<?',
2784      isFullRowMode: '<',
2785    },
2786    templateUrl: '~/crmSearchDisplay/crmSearchDisplayEditable.html',
2787    controller: function($scope, $element, crmApi4, crmStatus) {
2788      const ts = $scope.ts = CRM.ts('org.civicrm.search_kit');
2789      const ctrl = this;
2790      let initialValue;
2791      let editableInfo;
2792      let valuePath;
2793
2794      this.$onInit = function() {
2795        editableInfo = this.display.results.editable[this.colKey];
2796        valuePath = this.colKey.split(':')[0];
2797        this.display.editValues = this.display.editValues || {};
2798        if (this.row) {
2799          initialValue = JSON.parse(JSON.stringify(this.row.data[valuePath]));
2800          this.display.editValues[this.colKey] = JSON.parse(JSON.stringify(this.row.data[valuePath]));
2801        }
2802
2803        this.field = {
2804          data_type: editableInfo.data_type,
2805          input_type: editableInfo.input_type,
2806          entity: editableInfo.entity,
2807          name: editableInfo.value_key,
2808          options: editableInfo.options,
2809          fk_entity: editableInfo.fk_entity,
2810          serialize: editableInfo.serialize,
2811          nullable: editableInfo.nullable && ctrl.row && ctrl.row.data[editableInfo.id_path],
2812        };
2813
2814        if (this.field.options === true) {
2815          loadOptions();
2816        }
2817
2818        $(document).off('.crmSearchDisplayEditable');
2819        $(document).on('keydown.crmSearchDisplayEditable', (e) => {
2820          if (e.key === 'Escape') {
2821            $scope.$apply(() => ctrl.cancel());
2822          }
2823          else if (e.key === 'Enter' && e.target.type !== 'textarea') {
2824            $scope.$apply(() => ctrl.save());
2825          }
2826        });
2827      };
2828
2829      this.$onDestroy = function() {
2830        $(document).off('.crmSearchDisplayEditable');
2831      };
2832
2833      this.save = function() {
2834        const value = ctrl.display.editValues[ctrl.colKey];
2835        if (value !== initialValue || ctrl.isFullRowMode) {
2836          ctrl.display.saveEditing(ctrl.row, ctrl.colKey);
2837        }
2838        else {
2839          ctrl.display.cancelEditing(ctrl.row);
2840        }
2841      };
2842
2843      this.cancel = function() {
2844        ctrl.display.cancelEditing(ctrl.row);
2845      };
2846      function loadOptions() {
2847        crmApi4(editableInfo.entity, 'getFields', {
2848          action: 'update',
2849          select: ['options'],
2850          values: ctrl.row && ctrl.row.data,
2851          loadOptions: ['id', 'name', 'label', 'description', 'color', 'icon'],
2852          where: [['name', '=', ctrl.field.name]]
2853        }, 0).then(function(fieldInfo) {
2854          ctrl.field.options = fieldInfo.options;
2855        });
2856      }
2857    }
2858  });
2859
2860
2861  angular.module('crmSearchDisplay').factory('searchDisplayBaseTrait', function($timeout, $interval, $sce, crmApi4, crmStatus) {
2862    return {
2863      page: 1,
2864      rowCount: null,
2865      onInitialize: [],
2866      onChangeFilters: [],
2867      onPreRun: [],
2868      onPostRun: [],
2869      _runCount: 0,
2870      isArray: Array.isArray,
2871      initializeDisplay: function($scope, $element) {
2872        const ctrl = this;
2873        this.$element = $element;
2874        this.limit = this.settings.limit;
2875        this.sort = Array.isArray(this.settings.sort) ? _.cloneDeep(this.settings.sort) : [];
2876        this.uniqueId = Math.floor(Math.random() * 10e10);
2877        this.placeholders = [];
2878        const placeholderCount = 'placeholder' in this.settings ? this.settings.placeholder : 5;
2879        for (let p=0; p < placeholderCount; ++p) {
2880          this.placeholders.push({});
2881        }
2882        const isSubsearch = $element.closest('.crm-search-col-type-subsearch').length > 0;
2883        const setColumnDefaults = (col) => {
2884          col.enabled = true;
2885          col.fetched = true;
2886        };
2887        $scope.crmUrl = CRM.url;
2888        if (this.settings.columnMode === 'auto' && (!this.settings.columns || !this.settings.columns.length)) {
2889          this.columns = [];
2890          crmApi4('SearchDisplay', 'getDefault', {
2891            savedSearch: this.search,
2892            select: ['settings'],
2893          })
2894          .then((result) => this.columns = result[0].settings.columns)
2895          .then(() => this.columns.forEach(setColumnDefaults))
2896          .catch((error) => CRM.alert(ts('Error loading search columns')));
2897        }
2898        else {
2899          this.columns = _.cloneDeep(this.settings.columns);
2900          this.columns.forEach(setColumnDefaults);
2901        }
2902
2903        ctrl.onInitialize.forEach(callback => callback.call(ctrl, $scope, $element));
2904        this.getResultsPronto = _.debounce(ctrl.runSearch, 300, {leading: true, trailing: false});
2905        this.getResultsSoon = _.debounce(function() {
2906          $scope.$apply(function() {
2907            ctrl.runSearch();
2908          });
2909        }, 600);
2910        let contactTab = $element.closest('.crm-contact-page .ui-tabs-panel').attr('id');
2911        if (contactTab && !$element.is($('#' + contactTab + ' [search][display]').first())) {
2912          contactTab = null;
2913        }
2914        let hasCounter = !isSubsearch && (contactTab || ctrl.hasOwnProperty('totalCount'));
2915        if (hasCounter) {
2916          $scope.$watch('$ctrl.rowCount', function(rowCount) {
2917            if (typeof rowCount === 'number' && angular.equals({}, ctrl.getAfformFilters())) {
2918              setTotalCount(rowCount);
2919            }
2920          });
2921        }
2922
2923        function setTotalCount(rowCount) {
2924          if (ctrl.hasOwnProperty('totalCount')) {
2925            ctrl.totalCount = rowCount;
2926          }
2927          if (contactTab) {
2928            CRM.tabHeader.updateCount(contactTab.replace('contact-', '#tab_'), rowCount);
2929          }
2930        }
2931        if (!isSubsearch) {
2932          $element.closest('form').on('crmPopupFormSuccess crmFormSuccess', () => {
2933            ctrl.rowCount = null;
2934            ctrl.getResultsPronto();
2935          });
2936        }
2937        function onChangeFilters() {
2938          ctrl.page = 1;
2939          ctrl.rowCount = null;
2940          ctrl.onChangeFilters.forEach(callback => callback.call(ctrl));
2941          if (!ctrl.settings.button) {
2942            ctrl.getResultsSoon();
2943          }
2944        }
2945
2946        function onChangePageSize() {
2947          ctrl.page = 1;
2948          if (ctrl.results) {
2949            ctrl.getResultsSoon();
2950          }
2951        }
2952        if (this.settings.toolbar) {
2953          this.onPostRun.push(function (apiResults) {
2954            if (apiResults.run.toolbar) {
2955              ctrl.toolbar = apiResults.run.toolbar;
2956              ctrl.toolbar.forEach((link) => {
2957                if (link.autoOpen && ctrl._runCount === 1 && !ctrl.results.length) {
2958                  CRM.loadForm(link.url)
2959                    .on('crmFormSuccess', (e, data) => {
2960                      ctrl.rowCount = null;
2961                      ctrl.getResultsPronto();
2962                    });
2963                }
2964              });
2965            }
2966          });
2967        }
2968
2969        if (this.afFieldset) {
2970          this.onPostRun.push(function (apiResults) {
2971            if (apiResults.run.labels && apiResults.run.labels.length && $scope.$parent.addTitle) {
2972              $scope.$parent.addTitle(apiResults.run.labels.join(' '));
2973            }
2974          });
2975        }
2976        this.onPostRun.push(() => $element[0].dispatchEvent(new Event('load')));
2977        function setUpWatches() {
2978          if (ctrl.afFieldset && !isSubsearch) {
2979            $scope.$watch(ctrl.afFieldset.getFilterValues, onChangeFilters, true);
2980          }
2981          if (ctrl.settings.pager && ctrl.settings.pager.expose_limit) {
2982            $scope.$watch('$ctrl.limit', onChangePageSize);
2983          }
2984          $scope.$watch('$ctrl.filters', onChangeFilters, true);
2985        }
2986        $element.css('display', 'block');
2987        if ($element.is(':visible')) {
2988          setUpWatches();
2989        }
2990        else {
2991          let checkVisibility = $interval(() => {
2992            if ($element.is(':visible')) {
2993              $interval.cancel(checkVisibility);
2994              setUpWatches();
2995            }
2996          }, 250);
2997        }
2998        $timeout(function() {
2999          if (hasCounter && (!(ctrl.loading || ctrl.results) || !angular.equals({}, ctrl.getAfformFilters()))) {
3000            const params = ctrl.getApiParams('row_count');
3001            params.filters = ctrl.filters;
3002            crmApi4('SearchDisplay', 'run', params).then(function(result) {
3003              setTotalCount(result.count);
3004            });
3005          }
3006        }, 900);
3007      },
3008
3009      hasExtraFirstColumn: function() {
3010        return this.settings.actions || this.settings.draggable || this.settings.collapsible || this.settings.editableRow || (this.settings.tally && this.settings.tally.label);
3011      },
3012
3013      getFilters: function() {
3014        return _.assign({}, this.getAfformFilters(), this.filters);
3015      },
3016
3017      getAfformFilters: function() {
3018        return this.afFieldset ? this.afFieldset.getFilterValues() : {};
3019      },
3020      getRawHtml(html) {
3021        return $sce.trustAsHtml(html);
3022      },
3023      getApiParams: function(mode) {
3024        const apiParams = {
3025          return: arguments.length ? mode : 'page:' + this.page,
3026          savedSearch: this.search,
3027          display: this.display,
3028          sort: this.sort,
3029          limit: this.limit,
3030          seed: this.uniqueId,
3031          filters: this.getFilters(),
3032          afform: this.afFieldset ? this.afFieldset.getFormName() : null
3033        };
3034        const toggleColumns = this.columns.reduce((indices, col, index) => {
3035          if (col.enabled) {
3036            indices.push(index);
3037          }
3038          return indices;
3039        }, []);
3040        if (toggleColumns.length < this.columns.length) {
3041          apiParams.toggleColumns = toggleColumns;
3042        }
3043        return apiParams;
3044      },
3045
3046      onClickSearchButton: function() {
3047        this.rowCount = null;
3048        this.page = 1;
3049        this.getResultsPronto();
3050      },
3051      runSearch: function(apiCalls, statusParams, editedRow) {
3052        const ctrl = this;
3053        const requestId = ++this._runCount;
3054        const apiParams = this.getApiParams();
3055        if (!statusParams) {
3056          this.loading = true;
3057        }
3058        apiCalls = apiCalls || {};
3059        apiCalls.run = ['SearchDisplay', 'run', apiParams];
3060        ctrl.onPreRun.forEach(callback => callback.call(ctrl, apiCalls));
3061        const apiRequest = crmApi4(apiCalls);
3062        apiRequest.then(function(apiResults) {
3063          if (requestId < ctrl._runCount) {
3064            return; // Another request started after this one
3065          }
3066          ctrl.results = apiResults.run;
3067          ctrl.loading = false;
3068          if (!ctrl.rowCount || editedRow) {
3069            if (!ctrl.limit || (ctrl.results.length < ctrl.limit && ctrl.page === 1)) {
3070              ctrl.rowCount = ctrl.results.length;
3071            } else if (ctrl.settings.pager || ctrl.settings.headerCount) {
3072              const params = ctrl.getApiParams('row_count');
3073              crmApi4('SearchDisplay', apiCalls.run[1], params).then(function(result) {
3074                if (requestId < ctrl._runCount) {
3075                  return; // Another request started after this one
3076                }
3077
3078                ctrl.rowCount = result.count;
3079              });
3080            }
3081          }
3082          ctrl.onPostRun.forEach(callback => callback.call(ctrl, apiResults, 'success', editedRow));
3083        }, function(error) {
3084          if (requestId < ctrl._runCount) {
3085            return; // Another request started after this one
3086          }
3087          ctrl.results = [];
3088          ctrl.loading = false;
3089          ctrl.onPostRun.forEach(callback => callback.call(ctrl, error, 'error', editedRow));
3090        });
3091        if (statusParams) {
3092          crmStatus(statusParams, apiRequest);
3093        }
3094        return apiRequest;
3095      },
3096
3097      getFieldClass: function(colIndex, colData) {
3098        return (colData.cssClass || '') + ' crm-search-col-type-' + this.columns[colIndex].type + (this.columns[colIndex].break ? '' : ' crm-inline-block');
3099      },
3100
3101      getFieldTemplate: function(colIndex, colData) {
3102        let colType = this.columns[colIndex].type;
3103        if (colType === 'include') {
3104          if (/^~\/.+/.test(this.columns[colIndex].path) === false) {
3105            throw 'Invalid path for include column: "' + this.columns[colIndex].path + '"';
3106          }
3107          return this.columns[colIndex].path;
3108        }
3109        if (colType === 'field') {
3110          if (colData.edit) {
3111            colType = 'editable';
3112          } else if (colData.links) {
3113            colType = 'link';
3114          }
3115        }
3116        return '~/crmSearchDisplay/colType/' + colType + '.html';
3117      },
3118
3119      getSearchDisplayKey: function() {
3120        return this.display ? `${this.search}.${this.display}` : this.search;
3121      }
3122
3123    };
3124  });
3125
3126
3127  angular.module('crmSearchDisplay').factory('searchDisplayEditableTrait', function(crmApi4, crmStatus, $timeout) {
3128    return {
3129
3130      editing: false,
3131
3132      onPostRun: [function(apiResults) {
3133        this.cancelEditing();
3134      }],
3135
3136      startEditing: function(row, colIndex) {
3137        if (this.editing === false && row.columns[colIndex].edit) {
3138          this.editValues = {};
3139          this.editing = row.key;
3140          row.columns.forEach((col, index) => {
3141            col.editing = (index === colIndex || (this.settings.editableRow && this.settings.editableRow.full));
3142            col.focused = index === colIndex;
3143          });
3144        }
3145      },
3146
3147      startCreating: function() {
3148        this.editing = -1;
3149        this.editValues = {};
3150      },
3151
3152      cancelEditing: function(row) {
3153        this.editing = false;
3154        this.editValues = {};
3155        if (row && row.columns) {
3156          row.columns.forEach((col) => col.editing = false);
3157        }
3158      },
3159
3160      saveEditing: function(row, colKey) {
3161        const ctrl = this;
3162        const apiParams = this.getApiParams(null);
3163        if (row) {
3164          if (colKey) {
3165            const colIndex = this.settings.columns.findIndex(column => column.key === colKey);
3166            row.columns[colIndex].loading = true;
3167          } else {
3168            row.columns.forEach((col) => col.loading = true);
3169          }
3170          apiParams.rowKey = row.key;
3171        }
3172        apiParams.values = this.editValues;
3173        this.cancelEditing();
3174
3175        crmStatus({}, crmApi4('SearchDisplay', 'inlineEdit', apiParams))
3176          .then(function(result) {
3177            ctrl.refreshAfterEditing(result, row ? row.key : null);
3178          });
3179      },
3180
3181      refreshAfterEditing: function(result, rowKey) {
3182        if (!rowKey && result.length) {
3183          this.results.push(result[0]);
3184          this.rowCount++;
3185          return;
3186        }
3187        const rowIndex = this.results.findIndex(result =>
3187 result.key === rowKey);
3188        if (result.length && rowIndex >= 0) {
3189          const row = this.results[rowIndex];
3190          _.defaults(result[0].data, row.data);
3191          delete (row.cssClass);
3192          _.defaults(result[0], row);
3193          this.results.splice(rowIndex, 1);
3194          $timeout(() => this.results.splice(rowIndex, 0, result[0]));
3195        }
3196        else {
3197          this.rowCount = null;
3198          this.getResultsPronto();
3199          this.$element.trigger('crmPopupFormSuccess');
3200        }
3201      }
3202
3203    };
3204  });
3205
3206
3207  angular.module('crmSearchDisplay').factory('searchDisplaySortableTrait', function() {
3208    const ts = CRM.ts('org.civicrm.search_kit');
3209    return {
3210
3211      isSortable: function(col) {
3212        return !this.settings.draggable && ['field', 'html'].includes(col.type) && col.sortable !== false;
3213      },
3214
3215      getSort: function(col) {
3216        const dir = this.sort.reduce((dir, item) => item[0] === col.key ? item[1] : dir, null);
3217        if (dir) {
3218          return 'fa-sort-' + dir.toLowerCase();
3219        }
3220        return 'fa-sort disabled';
3221      },
3222
3223      setSort: function(col, $event) {
3224        if (!this.isSortable(col)) {
3225          return;
3226        }
3227        const dir = this.getSort(col) === 'fa-sort-asc' ? 'DESC' : 'ASC';
3228        if (!$event.shiftKey || !this.sort) {
3229          this.sort = [];
3230        }
3231        const index = this.sort.findIndex(item => item[0] === col.key);
3232        if (index > -1) {
3233          this.sort[index][1] = dir;
3234        } else {
3235          this.sort.push([col.key, dir]);
3236        }
3237        if (this.results || !this.settings.button) {
3238          this.getResultsPronto();
3239        }
3240      }
3241
3242    };
3243  });
3244
3245
3246  angular.module('crmSearchDisplayBatch', CRM.angRequires('crmSearchDisplayBatch'));
3247
3248
3249  let displayInstance = 0;
3250
3251  angular.module('crmSearchDisplayBatch').component('crmSearchDisplayBatch', {
3252    bindings: {
3253      apiEntity: '@',
3254      search: '<',
3255      display: '<',
3256      apiParams: '<',
3257      settings: '<',
3258      filters: '<',
3259      totalCount: '=?'
3260    },
3261    require: {
3262      afFieldset: '?^^afFieldset',
3263      formCtrl: '?^form',
3264    },
3265    templateUrl: '~/crmSearchDisplayBatch/crmSearchDisplayBatch.html',
3266    controller: function($scope, $element, $location, $interval, $q, crmApi4, searchDisplayBaseTrait, searchDisplayEditableTrait) {
3267      const ts = $scope.ts = CRM.ts('org.civicrm.search_kit');
3268      const ctrl = angular.extend(this, _.cloneDeep(searchDisplayBaseTrait), _.cloneDeep(searchDisplayEditableTrait));
3269
3270      let autoSaveTimer;
3271      let errorNotification;
3272      this.loading = true;
3273      this.unsaved = [];
3274      this.formName = 'searchDisplayBatch' + displayInstance++;
3275
3276      this.$onInit = function() {
3277        this.limit = this.settings.limit || 0;
3278        if (errorNotification && errorNotification.close) {
3279          errorNotification.close();
3280        }
3281        this.isPreviewMode = typeof this.search !== 'string';
3282        this.userJobId = this.isPreviewMode ? null : $location.search().batch;
3283        this.columns = _.cloneDeep(this.settings.columns);
3284        if (this.userJobId) {
3285          this.runSearch();
3286          autoSaveTimer = $interval(function() {
3287            ctrl.saveRows();
3288          }, 10000);
3289        }
3290        else {
3291          this.newBatch = {
3292            rowCount: 1,
3293            targets: {}
3294          };
3295          this.reportLinks = [
3296            {
3297              title: ts('View My Import Batches'),
3298              href: CRM.url('civicrm/imports/my-listing#/?job_type=search_batch_import'),
3299              icon: 'fa-user-tag',
3300            },
3301          ];
3302          if (CRM.checkPerm('administer queues')) {
3303            this.reportLinks.push({
3304              title: ts('View All Import Batches'),
3305              href: CRM.url('civicrm/imports/all-imports#/?job_type=search_batch_import'),
3306              icon: 'fa-list-alt',
3307            });
3308          }
3309        }
3310        if (this.isPreviewMode) {
3311          this.results = [{data: {}}];
3312          this.loading = false;
3313        }
3314      };
3315
3316      this.$onDestroy = function() {
3317        if (typeof autoSaveTimer !== 'undefined') {
3318          $interval.cancel(autoSaveTimer);
3319        }
3320      };
3321      this.getResultsPronto = function() {
3322        if (this.limit) {
3323          const start = (this.page - 1) * this.limit;
3324          this.resultsPage = this.results.slice(start, start + this.limit);
3325        } else {
3326          this.resultsPage = this.results;
3327        }
3328      };
3329
3330      $scope.$watch('$ctrl.results.length', function() {
3331        if (ctrl.results) {
3332          ctrl.rowCount = ctrl.results.length;
3333          if (ctrl.limit && ctrl.page > 1 && Math.ceil(ctrl.results.length / ctrl.limit) < ctrl.page) {
3334            ctrl.page--;
3335          }
3336          ctrl.getResultsPronto();
3337        }
3338      });
3339
3340      $scope.$watch('$ctrl.limit', function() {
3341        if (ctrl.results) {
3342          ctrl.page = 1;
3343          ctrl.getResultsPronto();
3344        }
3345      });
3346
3347      this.createNewBatch = function() {
3348        this.creatingBatch = true;
3349        crmApi4('SearchDisplay', 'createBatch', {
3350          savedSearch: this.search,
3351          display: this.display,
3352          rowCount: this.newBatch.rowCount,
3353          targets: this.newBatch.targets,
3354          label: this.newBatch.label,
3355        }, 0).then(function(userJob) {
3356          $location.search('batch', userJob.id);
3357          ctrl.$onInit();
3358        });
3359      };
3360
3361      this.addRows = function(rowCount) {
3362        this.addingRows = true;
3363        const newRows = [];
3364        for (let i = 0; i < rowCount; i++) {
3365          newRows.push({});
3366        }
3367        crmApi4(getApiName(), 'save', {records: newRows, reload: ['*']}).then(function(savedRows) {
3368          savedRows.forEach(function(row) {
3369            delete row._entity_id;
3370            delete row._status;
3371            delete row._status_message;
3372            ctrl.results.push({data: row});
3373          });
3374          ctrl.addingRows = false;
3375        });
3376      };
3377
3378      this.deleteRow = function(index) {
3379        const rowNum = (this.page - 1) * this.limit + index;
3380        const id = this.results[rowNum].data._id;
3381        const unsavedIndex = this.unsaved.findIndex(item => item._id === id);
3382        if (unsavedIndex !== -1) {
3383          this.unsaved.splice(unsavedIndex, 1);
3384        }
3385        crmApi4(getApiName(), 'delete', {where: [['_id', '=', id]]});
3386        this.results.splice(rowNum, 1);
3387      };
3388
3389      this.onChangeData = function(row) {
3390        if (!this.unsaved.some(item => item._id === row.data._id)) {
3391          this.unsaved.push(row.data);
3392        }
3393        this.settings.columns.forEach(function(col) {
3394          const editable = ctrl.results.editable[col.key];
3395          if (editable && editable.input_attrs && editable.input_attrs.formula) {
3396            let formula = editable.input_attrs.formula;
3397            const prefix = editable.explicit_join ? (editable.explicit_join + '.') : '';
3398            formula = formula.replace(/\[/g, '(data["' + prefix);
3399            formula = formula.replace(/]/g, '"] || 0)');
3400            row.data[col.spec.name] = $scope.$eval(formula, {data: row.data});
3401          }
3402        });
3403      };
3404
3405      this.saveRows = function() {
3406        if (this.saving) {
3407          return this.saving;
3408        }
3409        if (!this.unsaved.length) {
3410          return $q.resolve();
3411        }
3412        const records = this.unsaved;
3413        this.unsaved = [];
3414        this.saving = crmApi4(getApiName(), 'save', {
3415          records: records,
3416        }).then(function(savedRows) {
3417          ctrl.saving = false;
3418        }, function(error) {
3419          ctrl.saving = false;
3420        });
3421        return this.saving;
3422      };
3423
3424      this.getFieldName = function(index, key) {
3425        const rowIndex = ((this.page - 1) * this.limit) + index;
3426        return 'batch-row-' + rowIndex + '-' + _.snakeCase(key);
3427      };
3428
3429      this.isValid = function() {
3430        return ctrl.formCtrl.$valid;
3431      };
3432
3433      this.doImport = function() {
3434        if (errorNotification && errorNotification.close) {
3435          errorNotification.close();
3436        }
3437        if (!this.isValid()) {
3438          this.showValidationErrors();
3439          return;
3440        }
3441        const tallyMismatches = getTallyMismatches();
3442        if (tallyMismatches.length) {
3443          let markup = '';
3444          tallyMismatches.forEach((item, index, array) => {
3445            markup += '<p><i class="crm-i fa-warning" role="img" aria-hidden="true"></i> ' + _.escape(item) + '</p>';
3446          });
3447          CRM.confirm({
3448            title: ts('Totals Mismatch'),
3449            message: markup + '<p>' + _.escape(ts('Run import anyway?')) + '</p>',
3450            options: {
3451              no: ts('Cancel'),
3452              yes: ts('Run Import'),
3453            },
3454          }).on('crmConfirm:yes', runImport);
3455        } else {
3456          runImport();
3457        }
3458      };
3459
3460      function runImport() {
3461        $element.block();
3462        ctrl.saveRows().then(function() {
3463          crmApi4('SearchDisplay', 'importBatch', {
3464            savedSearch: ctrl.search,
3465            display: ctrl.display,
3466            userJobId: ctrl.userJobId,
3467          }).then(function(result) {
3468            window.location.href = result[0].url;
3469          });
3470        });
3471      }
3472
3473      this.showValidationErrors = () => {
3474        const formCtrl = this.formCtrl[this.formName];
3475        let invalidRows = {};
3476        let messages = [];
3477        Object.keys(formCtrl).forEach((key) => {
3478          if (key.startsWith('batch-row-') && formCtrl[key].$invalid) {
3479            const rowNum = 1 + parseInt(key.split('-')[2], 10);
3480            const fieldKey = key.split('-')[3];
3481            const col = this.settings.columns.find(col => _.snakeCase(col.key) === fieldKey);
3482            invalidRows[rowNum] = invalidRows[rowNum] || [];
3483            invalidRows[rowNum].push(col.label);
3484          }
3485        });
3486        const invalidRowNumbers = Object.keys(invalidRows);
3487        invalidRowNumbers.sort((a, b) => a - b);
3488        let more = 0;
3489        if (invalidRowNumbers.length > 3) {
3490          more = invalidRowNumbers.length - 3;
3491          invalidRowNumbers.length = 3;
3492        }
3493
3494        invalidRowNumbers.forEach((rowNum) => {
3495          messages.push(_.escape(ts('Row %1: %2', {1: rowNum, 2: invalidRows[rowNum].join(', ')})));
3496        });
3497        if (more) {
3498          messages.push(_.escape(ts('And %1 more', {1: more})));
3499        }
3500
3501        errorNotification = CRM.alert(
3502          '<ul><li>' + messages.join('</li><li>') + '</li></ul>',
3503          _.escape(ts('Please complete the following:')),
3504          'error'
3505        );
3506      };
3507
3508      this.copyCol = function(index) {
3509        const fieldName = this.settings.columns[index].spec.name;
3510        const value = this.results[0].data[fieldName];
3511        const updateValue = {};
3512        updateValue[fieldName] = value;
3513        this.results.forEach((row) => row.data[fieldName] = value);
3514        crmApi4(getApiName(), 'update', {
3515          where: [['_id', '>', this.results[0].data._id]],
3516          values: updateValue,
3517        });
3518      };
3519
3520      this.getTally = function(col) {
3521        if (this.isPreviewMode) {
3522          return 0;
3523        }
3524        const fn = col.tally.fn;
3525        let tally = 0;
3526        this.results.forEach(function(row) {
3527          if (row.data[col.spec.name]) {
3528            if (fn === 'COUNT') {
3529              tally++;
3530            } else {
3531              const colVal = Number(row.data[col.spec.name]);
3532              if (!isNaN(colVal)) {
3533                tally += colVal;
3534              }
3535            }
3536          }
3537        });
3538        if (fn === 'AVG' && this.results.length) {
3539          return tally / this.results.length;
3540        }
3541        return tally;
3542      };
3543
3544      this.getTallyClass = function(col) {
3545        if (this.isPreviewMode) {
3546          return '';
3547        }
3548        const tallyTarget = this.getTallyTarget(col);
3549        if (tallyTarget) {
3550          return this.getTally(col) == tallyTarget ? 'text-success' : 'text-danger';
3551        }
3552        return '';
3553      };
3554
3555      this.getTallyTarget = function(col) {
3556        if (col.tally && col.tally.fn === 'SUM' && ctrl.results && ctrl.results.editable[col.key]) {
3557          return ctrl.results.editable[col.key].target;
3558        }
3559      };
3560
3561      function getTallyMismatches() {
3562        const tallyMismatches = [];
3563        ctrl.settings.columns.forEach(function(col) {
3564          const tallyTarget = ctrl.getTallyTarget(col);
3565          if (tallyTarget) {
3566            const tally = ctrl.getTally(col);
3567            if (tally != tallyTarget) {
3568              tallyMismatches.push(ts('%1 value %2 does not match expected %3', {1: col.label, 2: tally, 3: tallyTarget}));
3569            }
3570          }
3571        });
3572        return tallyMismatches;
3573      }
3574
3575      function getApiName() {
3576        return 'Import_' + ctrl.userJobId;
3577      }
3578      const _getApiParams = this.getApiParams;
3579      this.getApiParams = function(mode) {
3580        const apiParams = _getApiParams.call(this, mode);
3581        apiParams.userJobId = this.userJobId;
3582        return apiParams;
3583      };
3584      this.onPreRun.push(function(apiCalls) {
3585        apiCalls.run[1] = 'runBatch';
3586      });
3587
3588    }
3589  });
3590
3591
3592  angular.module('crmSearchDisplayGrid', CRM.angRequires('crmSearchDisplayGrid'));
3593
3594
3595
3596  angular.module('crmSearchDisplayGrid').component('crmSearchDisplayGrid', {
3597    bindings: {
3598      apiEntity: '@',
3599      search: '<',
3600      display: '<',
3601      apiParams: '<',
3602      settings: '<',
3603      filters: '<',
3604      totalCount: '=?'
3605    },
3606    require: {
3607      afFieldset: '?^^afFieldset'
3608    },
3609    templateUrl: '~/crmSearchDisplayGrid/crmSearchDisplayGrid.html',
3610    controller: function($scope, $element, searchDisplayBaseTrait, searchDisplayTasksTrait, searchDisplayEditableTrait) {
3611      const ts = $scope.ts = CRM.ts('org.civicrm.search_kit'),
3612        ctrl = angular.extend(this, _.cloneDeep(searchDisplayBaseTrait), _.cloneDeep(searchDisplayTasksTrait), _.cloneDeep(searchDisplayEditableTrait));
3613
3614      this.$onInit = function() {
3615        this.initializeDisplay($scope, $element);
3616      };
3617
3618    }
3619  });
3620
3621
3622  angular.module('crmSearchDisplayList', CRM.angRequires('crmSearchDisplayList'));
3623
3624
3625
3626  angular.module('crmSearchDisplayList').component('crmSearchDisplayList', {
3627    bindings: {
3628      apiEntity: '@',
3629      search: '<',
3630      display: '<',
3631      apiParams: '<',
3632      settings: '<',
3633      filters: '<',
3634      totalCount: '=?'
3635    },
3636    require: {
3637      afFieldset: '?^^afFieldset'
3638    },
3639    templateUrl: '~/crmSearchDisplayList/crmSearchDisplayList.html',
3640    controller: function($scope, $element, searchDisplayBaseTrait, searchDisplayTasksTrait, searchDisplayEditableTrait) {
3641      const ts = $scope.ts = CRM.ts('org.civicrm.search_kit'),
3642        ctrl = angular.extend(this, _.cloneDeep(searchDisplayBaseTrait), _.cloneDeep(searchDisplayTasksTrait), _.cloneDeep(searchDisplayEditableTrait));
3643
3644      this.$onInit = function() {
3645        this.initializeDisplay($scope, $element);
3646      };
3647
3648    }
3649  });
3650
3651
3652  angular.module('crmSearchDisplayTable', CRM.angRequires('crmSearchDisplayTable'));
3653
3654
3655
3656  angular.module('crmSearchDisplayTable').component('crmSearchDisplayTable', {
3657    bindings: {
3658      apiEntity: '@',
3659      search: '<',
3660      display: '<',
3661      settings: '<',
3662      filters: '<',
3663      totalCount: '=?'
3664    },
3665    require: {
3666      afFieldset: '?^^afFieldset'
3667    },
3668    templateUrl: '~/crmSearchDisplayTable/crmSearchDisplayTable.html',
3669    controller: function($scope, $element, searchDisplayBaseTrait, searchDisplayTasksTrait, searchDisplaySortableTrait, searchDisplayEditableTrait, crmApi4, crmStatus) {
3670      const ts = $scope.ts = CRM.ts('org.civicrm.search_kit'),
3671        ctrl = angular.extend(this, _.cloneDeep(searchDisplayBaseTrait), _.cloneDeep(searchDisplayTasksTrait), _.cloneDeep(searchDisplaySortableTrait), _.cloneDeep(searchDisplayEditableTrait));
3672
3673      this.$onInit = function() {
3674        let tallyParams;
3675
3676        ctrl.onPreRun.push(this.trackFetchedColumns);
3677        if (ctrl.settings.tally) {
3678          ctrl.onPreRun.push(function (apiCalls) {
3679            ctrl.tally = null;
3680            tallyParams = _.cloneDeep(apiCalls.run[2]);
3681          });
3682
3683          ctrl.onPostRun.push(function (apiResults, status) {
3684            ctrl.tally = null;
3685            if (status === 'success' && tallyParams) {
3686              tallyParams.return = 'tally';
3687              crmApi4('SearchDisplay', 'run', tallyParams).then(function (result) {
3688                ctrl.tally = result[0];
3689              });
3690            }
3691          });
3692        }
3693
3694        this.initializeDisplay($scope, $element);
3695
3696        if (ctrl.settings.draggable) {
3697          ctrl.draggableOptions = {
3698            containment: $element.children('div').first(),
3699            handle: '.crm-draggable',
3700            forcePlaceholderSize: true,
3701            helper: function(e, ui) {
3702              ui.children().each(function() {
3703                $(this).width($(this).width());
3704              });
3705              return ui;
3706            },
3707            stop: function(e, ui) {
3708              const movedItem = ui.item.sortable.model,
3709                oldPosition = ui.item.sortable.index,
3710                newPosition = ctrl.results.indexOf(movedItem),
3711                displacement = newPosition < oldPosition ? -1 : 1,
3712                displacedItem = ctrl.results[newPosition - displacement];
3713              if (newPosition > -1 && oldPosition !== newPosition) {
3714                updateDraggableWeights(movedItem.key, displacedItem.data);
3715              }
3716            }
3717          };
3718        }
3719        if (this.afFieldset) {
3720          $scope.$on('afFormReset', () => this.onAfformReset());
3721        }
3722      };
3723
3724      this.onAfformReset = () => {
3725        const savedSearchParamSet = this.afFieldset.selectedSearchParamSet;
3726        if (!savedSearchParamSet) {
3727          this.columns.forEach((col) => col.enabled = true);
3728          return;
3729        }
3730        const columns = savedSearchParamSet.columns ? savedSearchParamSet.columns[this.getSearchDisplayKey()] : null;
3731        if (!columns || !Object.keys(columns).length) {
3732          this.columns.forEach((col) => col.enabled = true);
3733          return;
3734        }
3735        this.columns.forEach((col) => col.enabled = false);
3736        Object.keys(columns).forEach((keyOrLabel) => {
3737          const findByKey = this.columns.findIndex((col) => col.key === keyOrLabel);
3738          if (findByKey > -1) {
3739            this.columns[findByKey].enabled = true;
3740            return;
3741          }
3742          const findByLabel = this.columns.findIndex((col) => this.getColumnToggleLabel(col) === keyOrLabel);
3743          if (findByLabel > -1) {
3744            this.columns[findByLabel].enabled = true;
3745            return;
3746          }
3747        });
3748        this.toggleColumns();
3749      };
3750
3751      function updateDraggableWeights(key, data) {
3752        const weightField = ctrl.settings.draggable;
3753        const newWeight = data[weightField];
3754        const apiParams = ctrl.getApiParams('draggableWeight');
3755        apiParams.rowKey = key;
3756        apiParams.values = {};
3757        apiParams.values[weightField] = newWeight;
3758        crmStatus({}, crmApi4('SearchDisplay', 'inlineEdit', apiParams))
3759          .then(function(newWeights) {
3760            const weightColumn = ctrl.settings.columns.findIndex(col => col.key === weightField);
3761            ctrl.results.forEach(function(row) {
3762              if (row.key in newWeights) {
3763                row.data[weightField] = newWeights[row.key];
3764                if (weightColumn >= 0) {
3765                  row.columns[weightColumn].val = newWeights[row.key];
3766                  row.columns[weightColumn] = JSON.parse(angular.toJson(row.columns[weightColumn]));
3767                }
3768              }
3769            });
3770          });
3771      }
3772      this.getHeaderClass = function (column) {
3773        let headerClasses = [];
3774        if (ctrl.isSortable(column)) {
3775          headerClasses.push('crm-sortable-col');
3776        }
3777        if (column.alignment) {
3778          headerClasses.push(column.alignment);
3779        }
3780        if (Array.isArray(column.cssRules)) {
3781          column.cssRules.forEach(cssRule => {
3782            if (cssRule.length === 1) {
3783              headerClasses.push(cssRule[0]);
3784            }
3785          });
3786        }
3787        return headerClasses.join(' ');
3788      };
3789
3790      this.getRowClass = function (row) {
3791        let cssClass = '';
3792        if (ctrl.settings.hierarchical) {
3793          cssClass += ' crm-hierarchical-row crm-hierarchical-depth-' + row.data._depth;
3794          if (row.data._depth) {
3795            cssClass += ' crm-hierarchical-child';
3796          }
3797          if (row.data._descendents) {
3798            cssClass += ' crm-hierarchical-parent';
3799          }
3800        }
3801        return cssClass;
3802      };
3803
3804      this.getColumnToggleLabel = (col) => {
3805        if (col.label) {
3806          return col.label;
3807        }
3808        if (col.key) {
3809          return `[${col.key}]`;
3810        }
3811        if (col.type === 'menu') {
3812          return ts('Menu');
3813        }
3814        if (col.type === 'buttons') {
3815          return ts('Buttons');
3816        }
3817        if (col.type === 'links') {
3818          return ts('Links');
3819        }
3820        return ts('Column %1', {1: col.index});
3821      };
3822
3823      /**
3824       * Update the settings for which columns to include.
3825       *
3826       * If including columns that we haven't fetched, trigger a refetch
3827       */
3828      this.toggleColumns = () => {
3829        if (this.columns.find((col) => col.enabled && !col.fetched)) {
3830          this.getResultsPronto();
3831        }
3832      };
3833
3834      /**
3835       * Get a list of currently selected columns, for saving in SearchParamSet
3836       * Unfortunately not all columns have keys, so we sometimes save the label,
3837       * which may not be unique :(
3838       *
3839       * @returns string[]
3840       */
3841      this.getToggledColumns = () => {
3842        const toggledColumns = this.columns.filter((col) => col.enabled);
3843        if (!toggledColumns.length || toggledColumns.length === this.columns.length) {
3844          return [];
3845        }
3846        const keyLabelMap = {};
3847        toggledColumns.forEach((col) => {
3848          const label = this.getColumnToggleLabel(col);
3849          const key = col.key ? col.key : label;
3850          keyLabelMap[key] = label;
3851        });
3852        return keyLabelMap;
3853      };
3854
3855      this.resetColumnToggles = () => {
3856        this.columns.forEach((col, index) => {
3857          this.columns[index].enabled = true;
3858        });
3859        this.toggleColumns();
3860      };
3861
3862      this.clearColumnToggles = () => {
3863        this.columns.forEach((col, index) => {
3864          this.columns[index].enabled = false;
3865        });
3866        this.toggleColumns();
3867      };
3868
3869      /**
3870       * Keep track of which columns we have fetched each
3871       * time we fetch results. This allows us to only
3872       * refetch if we need columns we don't have (we can
3873       * hide columns without refetching)
3874       */
3875      this.trackFetchedColumns = () => {
3876        this.columns.forEach((col) => {
3877          col.fetched = col.enabled;
3878        });
3879      };
3880
3881      this.onToggleDisclosure = (col, event) => {
3882        col.hasBeenOpened = true;
3883        const detailsElement = event.target.closest('details.crm-search-display-subsearch-dropdown');
3884        if (detailsElement && !detailsElement.open) {
3885          detailsElement.closest('.crm-search-display-table')
3886            .querySelectorAll('details.crm-search-display-subsearch-dropdown[open]')
3887            .forEach((details) => details.open = false);
3888        }
3889      };
3890
3891    }
3892  });
3893
3894
3895
3896  angular.module('crmSearchDisplayTable').component('crmSearchDisplayToggleCollapse', {
3897    bindings: {
3898      rows: '<',
3899      rowIndex: '<',
3900      defaultCollapsed: '<',
3901    },
3902    templateUrl: '~/crmSearchDisplayTable/crmSearchDisplayToggleCollapse.html',
3903    controller: function($scope, $element) {
3904      const ts = $scope.ts = CRM.ts('org.civicrm.search_kit'),
3905        ctrl = this;
3906
3907      this.$onInit = function() {
3908        const row = this.getRow();
3909        if (!('collapsed' in row)) {
3910          row.collapsed = row.data._descendents && this.defaultCollapsed === 'closed';
3911        }
3912        if (row.collapsed && row.data._descendents) {
3913          row.collapsed = false;
3914          this.toggleCollapsed();
3915        } else {
3916          row.collapsed = false;
3917        }
3918        if (!row.data._descendents) {
3919          $element.css('visibility', 'hidden');
3920        }
3921      };
3922
3923      this.getRow = function () {
3924        return this.rows[this.rowIndex];
3925      };
3926
3927      this.isCollapsed = function() {
3928        return this.getRow().collapsed && this.getRow().data._descendents;
3929      };
3930
3931      this.countDescendents = function () {
3932        return this.getRow().data._descendents;
3933      };
3934
3935      this.toggleCollapsed = function() {
3936        const row = this.getRow();
3937        row.collapsed = !row.collapsed;
3938
3939        let descendentsEnd = this.rowIndex + row.data._descendents;
3940        for (let i = this.rowIndex + 1; i <= descendentsEnd; i++) {
3941          let hide = row.collapsed;
3942          if (!ctrl.rows[i]) {
3943            return;
3944          }
3945          ctrl.rows[i].hidden = hide;
3946          if (!hide && ctrl.rows[i].collapsed) {
3947            i += ctrl.rows[i].data._descendents;
3948          }
3949        }
3950      };
3951
3952    }
3953  });
3954
3955
3956  angular.module('crmSearchDisplayTree', CRM.angRequires('crmSearchDisplayTree'));
3957
3958
3959
3960  angular.module('crmSearchDisplayTree').component('crmSearchDisplayTree', {
3961    bindings: {
3962      apiEntity: '@',
3963      search: '<',
3964      display: '<',
3965      apiParams: '<',
3966      settings: '<',
3967      filters: '<',
3968      totalCount: '=?'
3969    },
3970    require: {
3971      afFieldset: '?^^afFieldset'
3972    },
3973    templateUrl: '~/crmSearchDisplayTree/crmSearchDisplayTree.html',
3974    controller: function($scope, $element, searchDisplayBaseTrait, searchDisplayTasksTrait, searchDisplayEditableTrait) {
3975      const ts = $scope.ts = CRM.ts('org.civicrm.search_kit');
3976      const ctrl = angular.extend(this, _.cloneDeep(searchDisplayBaseTrait), _.cloneDeep(searchDisplayTasksTrait), _.cloneDeep(searchDisplayEditableTrait));
3977
3978      this.tree = [];
3979
3980      this.$onInit = function() {
3981        this.onPostRun.push(function(apiResults, status) {
3982          if (status === 'success') {
3983            ctrl.tree = sortTree(apiResults.run);
3984          }
3985        });
3986
3987        this.initializeDisplay($scope, $element);
3988      };
3989      function sortTree(results) {
3990        const parentField = ctrl.settings.parent_field;
3991        if (!parentField) {
3992          return results;
3993        }
3994        const indexedResults = results.reduce((acc, item) => {
3995          item.children = [];
3996          acc[item.key] = item;
3997          return acc;
3998        }, {});
3999        results.forEach(item => {
4000          const parentKey = item.data[parentField];
4001          if (parentKey && indexedResults[parentKey]) {
4002            indexedResults[parentKey].children.push(item);
4003            indexedResults[parentKey].collapsed = ctrl.settings.collapsible && ctrl.settings.collapsible === 'closed';
4004          }
4005        });
4006        return results.filter(item => !item.data[parentField]);
4007      }
4008
4009    }
4010  });
4011
4012
4013
4014  angular.module('crmSearchDisplayTree').component('crmSearchDisplayTreeBranch', {
4015    bindings: {
4016      items: '<',
4017      parentKey: '<',
4018    },
4019    require: {
4020      displayCtrl: '^crmSearchDisplayTree',
4021    },
4022    templateUrl: '~/crmSearchDisplayTree/crmSearchDisplayTreeBranch.html',
4023    controllerAs: '$branch',
4024    controller: function($scope, $element, crmApi4, crmStatus) {
4025      const ts = $scope.ts = CRM.ts('org.civicrm.search_kit');
4026      const ctrl = this;
4027
4028      this.$onInit = function() {
4029        $scope.$ctrl = this.displayCtrl;
4030
4031        ctrl.draggableOptions = {
4032          handle: '.crm-draggable',
4033          connectWith: 'crm-search-display-tree [ui-sortable]',
4034          placeholder: 'crm-search-display-tree-placeholder',
4035          tolerance: 'pointer', // seems to work better for dragging large items into small gaps
4036          update: function(e, ui) {
4037            const movedItem = ui.item.sortable.model;
4038            let newPosition = ui.item.sortable.dropindex;
4039            let displacedItem;
4040            if (ui.item.sortable.source[0] === ui.item.sortable.droptarget[0]) {
4041              displacedItem = ctrl.items[newPosition];
4042              updateDraggableWeights(movedItem, displacedItem);
4043            }
4044            else if (ui.sender) {
4045              displacedItem = ctrl.items[newPosition + 1];
4046              updateDraggableWeights(movedItem, displacedItem);
4047            }
4048          }
4049        };
4050      };
4051
4052      function updateDraggableWeights(movedItem, displacedItem) {
4053        const weightField = ctrl.displayCtrl.settings.draggable;
4054        const parentField = ctrl.displayCtrl.settings.parent_field;
4055        const apiParams = ctrl.displayCtrl.getApiParams('draggableWeight');
4056        apiParams.rowKey = movedItem.key;
4057        apiParams.values = {};
4058        if (displacedItem) {
4059          apiParams.values[weightField] = displacedItem.data[weightField];
4060        }
4061        else if (ctrl.items.length > 1) {
4062          apiParams.values[weightField] = ctrl.items[ctrl.items.length - 1].data[weightField] + 1;
4063        }
4064        else {
4065          apiParams.values[weightField] = 1;
4066        }
4067        apiParams.values[parentField] = ctrl.parentKey;
4068        crmStatus({}, crmApi4('SearchDisplay', 'inlineEdit', apiParams))
4069          .then(function(newWeights) {
4070            const weightColumn = ctrl.displayCtrl.settings.columns.findIndex(col => col.key === weightField);
4071            ctrl.displayCtrl.results.forEach(function(row) {
4072              if (row.key in newWeights) {
4073                row.data[weightField] = newWeights[row.key];
4074                if (weightColumn >= 0) {
4075                  row.columns[weightColumn].val = newWeights[row.key];
4076                  row.columns[weightColumn] = JSON.parse(angular.toJson(row.columns[weightColumn]));
4077                }
4078              }
4079            });
4080          });
4081      }
4082
4083    }
4084  });
4085
4086
4087  angular.module('crmSearchTasks', CRM.angRequires('crmSearchTasks'));
4088
4089
4090
4091  angular.module('crmSearchTasks').component('crmSearchBatchRunner', {
4092    bindings: {
4093      entity: '<',
4094      action: '@',
4095      ids: '<',
4096      idField: '@',
4097      params: '<',
4098      displayCtrl: '<',
4099      isLink: '<',
4100      success: '&',
4101      error: '&'
4102    },
4103    templateUrl: '~/crmSearchTasks/crmSearchBatchRunner.html',
4104    controller: function($scope, $timeout, $interval, crmApi4) {
4105      const ts = $scope.ts = CRM.ts('org.civicrm.search_kit');
4106      const ctrl = this;
4107
4108      let currentBatch = 0;
4109      let totalBatches;
4110      let processedCount = 0;
4111      let countMatched = 0;
4112      let incrementer;
4113      let batchResult;
4114
4115      this.progress = 0;
4116      const BATCH_SIZE = 500;
4117      const EST_BATCH_TIME = 5;
4118
4119      this.$onInit = function() {
4120        if (ctrl.action === 'create' && !ctrl.idField) {
4121          ctrl.ids = [0];
4122        }
4123        totalBatches = Math.ceil(ctrl.ids.length / BATCH_SIZE);
4124        runBatch();
4125      };
4126
4127      this.$onDestroy = function() {
4128        stopIncrementer();
4129      };
4130
4131      function runBatch() {
4132        let entityName = ctrl.entity;
4133        let actionName = ctrl.action;
4134        ctrl.first = currentBatch * BATCH_SIZE;
4135        ctrl.last = (currentBatch + 1) * BATCH_SIZE;
4136        if (ctrl.last > ctrl.ids.length) {
4137          ctrl.last = ctrl.ids.length;
4138        }
4139        const params = _.cloneDeep(ctrl.params);
4140        if (ctrl.action === 'save' || (ctrl.action === 'create' && ctrl.idField)) {
4141          actionName = 'save';
4142          let originalRecords = params.records || [{}];
4143          if ('values' in params) {
4144            originalRecords = [params.values];
4145            delete params.values;
4146          }
4147          params.records = _.transform(ctrl.ids.slice(ctrl.first, ctrl.last), function(records, id) {
4148            _.each(_.cloneDeep(originalRecords), function(record) {
4149              record[ctrl.idField || 'id'] = id;
4150              records.push(record);
4151            });
4152          });
4153        } else if (ctrl.isLink && ctrl.action === 'update' && ctrl.ids.length === 1 && ctrl.displayCtrl) {
4154          entityName = 'SearchDisplay';
4155          actionName = 'inlineEdit';
4156          angular.extend(params, ctrl.displayCtrl.getApiParams(null));
4157          delete params.where;
4158          params.rowKey = ctrl.ids[0];
4159        } else if (ctrl.action !== 'create') {
4160          params.where = params.where || [];
4161          params.where.push([ctrl.idField || 'id', 'IN', ctrl.ids.slice(ctrl.first, ctrl.last)]);
4162        }
4163        crmApi4(entityName, actionName, params).then(
4164          function(result) {
4165            stopIncrementer();
4166            ctrl.progress = Math.floor(100 * ++currentBatch / totalBatches);
4167            processedCount += result.countFetched;
4168            countMatched += ('countMatched' in result ? result.countMatched : result.count);
4169            if (batchResult) {
4170              batchResult.push(...result);
4171            } else {
4172              batchResult = result;
4173            }
4174            if (ctrl.last >= ctrl.ids.length) {
4175              $timeout(function() {
4176                batchResult.batchCount = processedCount;
4177                batchResult.countMatched = countMatched;
4178                ctrl.success({result: batchResult});
4179              }, 500);
4180            } else {
4181              runBatch();
4182            }
4183          }, function(error) {
4184            ctrl.error({error: error});
4185          });
4186        incrementer = $interval(function(i) {
4187          const est = Math.floor(100 * (currentBatch + (i / EST_BATCH_TIME)) / totalBatches);
4188          ctrl.progress = est > 100 ? 100 : est;
4189        }, 1000, EST_BATCH_TIME);
4190      }
4191
4192      function stopIncrementer() {
4193        if (angular.isDefined(incrementer)) {
4194          $interval.cancel(incrementer);
4195          incrementer = undefined;
4196        }
4197      }
4198
4199    }
4200  });
4201
4202
4203
4204  angular.module('crmSearchTasks')
4205    .directive('crmMultiSelectDate', function () {
4206      return {
4207        restrict: 'A',
4208        require: 'ngModel',
4209        link: function (scope, element, attrs, ngModel) {
4210
4211          let defaultDate = null;
4212
4213          function getDisplayDate(date) {
4214            return $.datepicker.formatDate(CRM.config.dateInputFormat, $.datepicker.parseDate('yy-mm-dd', date));
4215          }
4216
4217          ngModel.$render = function () {
4218            element.val(Array.isArray(ngModel.$viewValue) ? ngModel.$viewValue.join(',') : ngModel.$viewValue).change();
4219          };
4220
4221          element
4222            .crmSelect2({
4223              multiple: true,
4224              data: [],
4225              initSelection: function(element, callback) {
4226                const values = [];
4227                $.each($(element).val().split(','), function(k, v) {
4228                  values.push({
4229                    text: getDisplayDate(v),
4230                    id: v
4231                  });
4232                });
4233                callback(values);
4234              }
4235            })
4236            .on('select2-opening', function(e) {
4237              const $el = $(this),
4238                $input = $('.select2-search-field input', $el.select2('container'));
4239              e.preventDefault();
4240              if (!$input.data('datepicker')) {
4241                $input
4242                  .datepicker({
4243                    beforeShow: function() {
4244                      const existingSelections = _.pluck($el.select2('data') || [], 'id');
4245                      return {
4246                        changeMonth: true,
4247                        changeYear: true,
4248                        defaultDate: defaultDate,
4249                        beforeShowDay: function(date) {
4250                          const dateStr = $.datepicker.formatDate('yy-mm-dd', date);
4251                          if (_.includes(existingSelections, dateStr)) {
4252                            return [false, '', ''];
4253                          }
4254                          return [true, '', ''];
4255                        }
4256                      };
4257                    }
4258                  })
4259                  .datepicker('show')
4260                  .on('change.crmDate', function() {
4261                    if ($(this).val()) {
4262                      const data = $el.select2('data') || [];
4263                      defaultDate = $(this).datepicker('getDate');
4264                      data.push({
4265                        text: $.datepicker.formatDate(CRM.config.dateInputFormat, defaultDate),
4266                        id: $.datepicker.formatDate('yy-mm-dd', defaultDate)
4267                      });
4268                      $el.select2('data', data, true);
4269                    }
4270                  })
4271                  .on('keyup', function() {
4272                    $(this).val('').datepicker('show');
4273                  });
4274              }
4275            })
4276            .on('select2-removed', function() {
4277              $('input.hasDatepicker', $(this).select2('container'))
4278                .datepicker('hide');
4279            })
4280            .on('change', function() {
4281              ngModel.$setViewValue(element.val().split(','));
4282            });
4283        }
4284      };
4285    });
4286
4287  let searchInputInstance = 0;
4288
4289  angular.module('crmSearchTasks').component('crmSearchInput', {
4290    bindings: {
4291      field: '<',
4292      op: '<',
4293      format: '<',
4294      optionKey: '<',
4295      showLabel: '<',
4296      name: '@',
4297    },
4298    require: {ngModel: 'ngModel'},
4299    templateUrl: '~/crmSearchTasks/crmSearchInput/crmSearchInput.html',
4300    controller: function($scope) {
4301      const ts = $scope.ts = CRM.ts('org.civicrm.search_kit'),
4302        ctrl = this;
4303
4304      this.domId = 'search-input-' + searchInputInstance++;
4305
4306      this.$onInit = function() {
4307
4308        $scope.$watch('$ctrl.value', function() {
4309          ctrl.ngModel.$setViewValue(ctrl.value);
4310        });
4311        ctrl.ngModel.$parsers.push(function(viewValue) {
4312          return ctrl.format === 'json' && typeof viewValue === 'string' && viewValue.length ? JSON.stringify(viewValue) : viewValue;
4313        });
4314        ctrl.ngModel.$formatters.push(function(value) {
4315          return ctrl.format === 'json' && typeof value === 'string' && value.length ? JSON.parse(value) : value;
4316        });
4317
4318        this.ngModel.$render = function() {
4319          ctrl.value = formatDataType(ctrl.ngModel.$viewValue);
4320        };
4321
4322        function formatDataType(val) {
4323          if (val === null || val === undefined) {
4324            return val;
4325          }
4326          if (ctrl.optionKey && ctrl.optionKey !== 'id') {
4327            return val;
4328          }
4329          if (ctrl.op && ctrl.op.includes('REGEXP')) {
4330            return val;
4331          }
4332          if (Array.isArray(val)) {
4333            const formatted = angular.copy(val);
4334            formatted.forEach((v, i) => formatted[i] = formatDataType(v));
4335            return formatted;
4336          }
4337          if (['Integer', 'Float'].includes(ctrl.field ? ctrl.field.data_type : null) && ctrl.field.category !== 'partial_date') {
4338            let newVal = Number(val);
4339            if ((ctrl.field.name === 'id' || ctrl.field.fk_entity) && ('' + newVal) !== val) {
4340              return val;
4341            }
4342            return newVal;
4343          }
4344          return val;
4345        }
4346
4347      };
4348    }
4349  });
4350
4351})(angular, CRM.$, CRM._);
4352
4353(function(angular, $) {
4354  "use strict";
4355
4356  angular.module('crmSearchTasks').directive('crmSearchInputFocus', function($timeout) {
4357    function waitForElement(selector, context = document) {
4358      return new Promise((resolve) => {
4359        const observer = new MutationObserver((mutations, observer) => {
4360          const match = $(selector, context);
4361          if (match.length) {
4362            observer.disconnect();
4363            resolve(match);
4364          }
4365        });
4366
4367        observer.observe(context, {
4368          childList: true,
4369          subtree: true,
4370        });
4371      });
4372    }
4373
4374    return {
4375      link: function(scope, element, attrs) {
4376        function getFocusableInput() {
4377          const types = new Map ([
4378            ['select2', 'input[crm-ui-select], input[crm-autocomplete]'],
4379            ['datepicker', 'input[crm-ui-datepicker]'],
4380            ['boolean', 'input[type="radio"], input[type="checkbox"]'],
4381            ['default', ':focusable']
4382          ]);
4383          for (const [type, selector] of types) {
4384            const jQueryObject = $(selector, element);
4385            if (jQueryObject.length > 0 || type === 'default') {
4386              return [jQueryObject, type];
4387            }
4388          }
4389        }
4390
4391        function focusOn() {
4392          let [jQueryObject, type] = getFocusableInput();
4393
4394          if (type === 'default') {
4395            jQueryObject.first().trigger('focus');
4396          }
4397
4398          else if (type === 'boolean') {
4399            let checkedItem = $(':checked', element);
4400            if (checkedItem.length) {
4401              jQueryObject = checkedItem;
4402            }
4403            jQueryObject.first().trigger('focus');
4404          }
4405
4406          else if (type === 'select2') {
4407            waitForElement('.select2-choice', element[0]).then((elements) => {
4408              $timeout(() => {
4409                const container = elements.first().closest('.select2-container');
4410                container.select2('open');
4411                $(element[0]).one('initSelectionComplete', () => {
4412                  container.select2('open');
4413                });
4414              });
4415            });
4416          }
4417
4418          else if (type === 'datepicker') {
4419            waitForElement('.hasDatepicker', element[0]).then((elements) => {
4420              elements.first().datepicker('show');
4421            });
4422          }
4423        }
4424
4425        scope.$watch(attrs.crmSearchInputFocus, function(flag) {
4426          if(flag === true) {
4427            $timeout(focusOn);
4428          }
4429        });
4430      }
4431    };
4432  });
4433
4434})(angular, CRM.$);
4435
4436(function(angular, $, _) {
4437  "use strict";
4438
4439  angular.module('crmSearchTasks').component('crmSearchInputVal', {
4440    bindings: {
4441      op: '<',
4442      labelId: '@',
4443    },
4444    require: {
4445      ngModel: 'ngModel',
4446      input: '^crmSearchInput'
4447    },
4448    template: '<div class="form-group" ng-include="$ctrl.getTemplate()"></div>',
4449    controller: function($scope, formatForSelect2, crmApi4) {
4450      const ts = $scope.ts = CRM.ts('org.civicrm.search_kit'),
4451        ctrl = this;
4452
4453      this.$onInit = function() {
4454        const field = getField();
4455        let rendered = false;
4456        this.dateRanges = CRM.crmSearchTasks.dateRanges;
4457
4458        this.ngModel.$render = function() {
4459          ctrl.value = ctrl.ngModel.$viewValue;
4460          if (!rendered && isDateField(field)) {
4461            setDateType();
4462          }
4463          rendered = true;
4464        };
4465
4466        $scope.$watch('$ctrl.value', function() {
4467          ctrl.ngModel.$setViewValue(ctrl.value);
4468        });
4469
4470        function setDateType() {
4471          if (_.findWhere(ctrl.dateRanges, {id: ctrl.value})) {
4472            ctrl.dateType = 'range';
4473          } else if (ctrl.value === 'now') {
4474            ctrl.dateType = 'now';
4475          } else if (_.includes(ctrl.value, 'now -')) {
4476            ctrl.dateType = 'now -';
4477          } else if (_.includes(ctrl.value, 'now +')) {
4478            ctrl.dateType = 'now +';
4479          } else {
4480            ctrl.dateType = 'fixed';
4481          }
4482        }
4483      };
4484
4485      this.getFkEntity = function() {
4486        const field = getField();
4487        return field.fk_entity || field.entity || null;
4488      };
4489
4490      const autocompleteStaticOptions = {
4491        Contact: ['user_contact_id'],
4492        Individual: ['user_contact_id'],
4493        '': []
4494      };
4495
4496      this.getAutocompleteStaticOptions = function() {
4497        if (ctrl.op) {
4498          return autocompleteStaticOptions[ctrl.getFkEntity() || ''] || autocompleteStaticOptions[''];
4499        }
4500        return autocompleteStaticOptions[''];
4501      };
4502
4503      this.isMulti = function() {
4504        if (ctrl.op) {
4505          return ctrl.op === 'IN' || ctrl.op === 'NOT IN' || ctrl.op === 'CONTAINS' || ctrl.op === 'NOT CONTAINS' || ctrl.op === 'CONTAINS ONE OF' || ctrl.op === 'NOT CONTAINS ONE OF';
4506        }
4507        return ctrl.input.field && (ctrl.input.field.serialize || ctrl.input.field.data_type === 'Array') ? true : null;
4508      };
4509
4510      this.changeDateType = function() {
4511        switch (ctrl.dateType) {
4512          case 'fixed':
4513            ctrl.value = '';
4514            break;
4515
4516          case 'range':
4517            ctrl.value = ctrl.dateRanges[0].id;
4518            break;
4519
4520          case 'now':
4521            ctrl.value = 'now';
4522            break;
4523
4524          default:
4525            ctrl.value = ctrl.dateType + ' 1 day';
4526        }
4527      };
4528
4529      this.dateUnits = function(setUnit) {
4530        const vals = ctrl.value.split(' ');
4531        if (arguments.length) {
4532          vals[3] = setUnit;
4533          ctrl.value = vals.join(' ');
4534        } else {
4535          return vals[3];
4536        }
4537      };
4538
4539      this.dateNumber = function(setNumber) {
4540        const vals = ctrl.value.split(' ');
4541        if (arguments.length) {
4542          vals[2] = setNumber;
4543          ctrl.value = vals.join(' ');
4544        } else {
4545          return parseInt(vals[2], 10);
4546        }
4547      };
4548
4549      this.lookupAddress = function() {
4550        ctrl.value.geo_code_1 = null;
4551        ctrl.value.geo_code_2 = null;
4552        if (ctrl.value.address) {
4553          crmApi4('Address', 'getCoordinates', {
4554            address: ctrl.value.address
4555          }).then(function(coordinates) {
4556            if (coordinates[0]) {
4557              ctrl.value.geo_code_1 = coordinates[0].geo_code_1;
4558              ctrl.value.geo_code_2 = coordinates[0].geo_code_2;
4559            }
4560          });
4561        }
4562      };
4563
4564      this.getTemplate = function() {
4565        const field = getField();
4566
4567        if (['LIKE', 'NOT LIKE', 'REGEXP', 'NOT REGEXP', 'REGEXP BINARY', 'NOT REGEXP BINARY'].includes(ctrl.op)) {
4568          return '~/crmSearchTasks/crmSearchInput/text.html';
4569        }
4570
4571        if (field.input_type === 'Location') {
4572          ctrl.value = ctrl.value || {distance_unit: CRM.crmSearchAdmin.defaultDistanceUnit};
4573          return '~/crmSearchTasks/crmSearchInput/location.html';
4574        }
4575
4576        if (isDateField(field)) {
4577          return '~/crmSearchTasks/crmSearchInput/date.html';
4578        }
4579
4580        if (field.data_type === 'Boolean') {
4581          return '~/crmSearchTasks/crmSearchInput/boolean.html';
4582        }
4583
4584        if (!['>', '<', '>=', '<='].includes(ctrl.op)) {
4585          if (field.options && (!field.suffixes || field.suffixes.includes('name'))) {
4586            return '~/crmSearchTasks/crmSearchInput/select.html';
4587          }
4588          if (field.fk_entity || field.name === 'id') {
4589            return '~/crmSearchTasks/crmSearchInput/entityRef.html';
4590          }
4591        }
4592
4593        if (field.data_type === 'Integer') {
4594          return '~/crmSearchTasks/crmSearchInput/integer.html';
4595        }
4596
4597        if (field.data_type === 'Float') {
4598          return '~/crmSearchTasks/crmSearchInput/float.html';
4599        }
4600
4601        if (field.input_type === 'Email') {
4602          return '~/crmSearchTasks/crmSearchInput/email.html';
4603        }
4604
4605        if (field.input_type === 'TextArea') {
4606          return '~/crmSearchTasks/crmSearchInput/textArea.html';
4607        }
4608
4609        return '~/crmSearchTasks/crmSearchInput/text.html';
4610      };
4611
4612      this.getFieldOptions = function() {
4613        const field = getField();
4614        return {results: formatForSelect2(field.options || [], ctrl.input.optionKey || 'id', 'label', ['description', 'color', 'icon'])};
4615      };
4616
4617      function getField() {
4618        return ctrl.input.field || {};
4619      }
4620
4621      function isDateField(field) {
4622        return field.category === 'partial_date' || field.data_type === 'Date' || field.data_type === 'Timestamp';
4623      }
4624
4625    }
4626  });
4627
4628
4629  angular.module('crmSearchTasks').controller('crmSearchTaskApiBatch', function($scope, searchTaskBaseTrait) {
4630    const ts = $scope.ts = CRM.ts('org.civicrm.search_kit'),
4631      ctrl = angular.extend(this, $scope.model, searchTaskBaseTrait);
4632
4633    this.apiBatch = $scope.apiBatch = this.task.apiBatch;
4634    this.entityTitle = this.getEntityTitle();
4635    this.apiBatch.entity = this.apiBatch.entity || this.entity;
4636    this.apiBatch.idField = this.apiBatch.idField || this.entity
4636Info.primary_key[0];
4637    if (!ctrl.apiBatch.confirmMsg && !ctrl.apiBatch.fields) {
4638      ctrl.start(ctrl.apiBatch.params);
4639    }
4640
4641    if (ctrl.apiBatch.fields) {
4642      ctrl.apiBatch.params = ctrl.apiBatch.params || {};
4643      ctrl.apiBatch.params.values = ctrl.apiBatch.params.values || {};
4644      ctrl.apiBatch.fields.forEach((field) => {
4645        let value = '';
4646        if ('default_value' in field) {
4647          value = field.default_value;
4648        } else if (field.serialize || field.data_type === 'Array') {
4649          value = [];
4650        } else if (field.data_type === 'Boolean') {
4651          value = true;
4652        } else if (field.options && field.options.length) {
4653          value = field.options[0].id;
4654        }
4655        ctrl.apiBatch.params.values[field.name] = value;
4656      });
4657    }
4658
4659    this.onSuccess = function(result) {
4660      const entityTitle = this.getEntityTitle(result.batchCount);
4661      if (result.action === 'inlineEdit') {
4662        CRM.status(ts('Saved'));
4663      } else {
4664        if (ctrl.apiBatch.successMsg) {
4665          CRM.alert(ts(ctrl.apiBatch.successMsg, { 1: result.batchCount, 2: entityTitle }), ts('%1 Complete', { 1: ctrl.task.title }), 'success');
4666        }
4667      }
4668      this.close(result);
4669    };
4670
4671    this.onError = function(error) {
4672      CRM.alert(ts(ctrl.apiBatch.errorMsg || error.error_message || '', {1: ctrl.ids.length, 2: ctrl.entityTitle}), ts('Error'), 'error');
4673      this.cancel();
4674    };
4675
4676  });
4677
4678
4679  angular.module('crmSearchTasks').controller('crmSearchTaskDownload', function($scope, $http, searchTaskBaseTrait, $timeout, $interval) {
4680    const ts = $scope.ts = CRM.ts('org.civicrm.search_kit'),
4681      ctrl = angular.extend(this, $scope.model, searchTaskBaseTrait);
4682
4683    this.entityTitle = this.getEntityTitle();
4684    this.format = 'csv';
4685    this.progress = null;
4686
4687    this.download = function() {
4688      ctrl.progress = 0;
4689      $('div.ui-dialog').last().find('.ui-dialog-titlebar .ui-button').hide();
4690      let incrementer = $interval(function() {
4691        if (ctrl.progress < 90) {
4692          ctrl.progress += 10;
4693        }
4694      }, 1000);
4695      const apiParams = ctrl.taskManager.getApiParams();
4696      delete apiParams.return;
4697      delete apiParams.limit;
4698      apiParams.filters.id = ctrl.ids || null;
4699      apiParams.format = ctrl.format;
4700      const httpConfig = {
4701        responseType: 'arraybuffer',
4702        headers: {'X-Requested-With': 'XMLHttpRequest', 'Content-Type': 'application/x-www-form-urlencoded'}
4703      };
4704      $http.post(CRM.url('civicrm/ajax/api4/SearchDisplay/download'), $.param({
4705        params: JSON.stringify(apiParams)
4706      }), httpConfig)
4707        .then(function(response) {
4708          $interval.cancel(incrementer);
4709          ctrl.progress = 100;
4710          const blob = new Blob([response.data], {
4711            type: response.headers('Content-Type')
4712          }),
4713            a = document.createElement("a"),
4714            url = a.href = window.URL.createObjectURL(blob),
4715            fileName = getFileNameFromHeader(response.headers('Content-Disposition'));
4716          a.download = fileName;
4717          a.click();
4718          window.URL.revokeObjectURL(url);
4719          $timeout(function() {
4720            CRM.alert(ts('%1 has been downloaded to your computer.', {1: fileName}), ts('Download Complete'), 'success');
4721            ctrl.cancel();
4722          }, 1000);
4723        });
4724    };
4725    function getFileNameFromHeader(contentDisposition) {
4726      const utf8FilenameRegex = /filename\*=utf-8''([\w%\-\.]+)(?:; ?|$)/i,
4727        asciiFilenameRegex = /filename=(["']?)(.*?[^\\])\1(?:; ?|$)/;
4728
4729      if (contentDisposition && contentDisposition.length) {
4730        if (utf8FilenameRegex.test(contentDisposition)) {
4731          return decodeURIComponent(utf8FilenameRegex.exec(contentDisposition)[1]);
4732        } else {
4733          const matches = asciiFilenameRegex.exec(contentDisposition);
4734          if (matches != null && matches[2]) {
4735            return matches[2];
4736          }
4737        }
4738      }
4739      return ctrl.entityTitle + '.' + ctrl.format;
4740    }
4741
4742  });
4743
4744
4745  angular.module('crmSearchTasks').controller('crmSearchTaskMailing', function($scope, crmApi4, searchTaskBaseTrait, formatForSelect2) {
4746    const ts = $scope.ts = CRM.ts('org.civicrm.search_kit'),
4747      ctrl = angular.extend(this, $scope.model, searchTaskBaseTrait);
4748    let mailingId;
4749
4750    this.mailing = {
4751      name: '',
4752      template_type: null,
4753    };
4754    this.entityTitle = this.getEntityTitle();
4755    crmApi4({
4756      templateTypes: ['Mailing', 'getFields', {
4757        loadOptions: ['id', 'label', 'description'],
4758        where: [['name', '=', 'template_type']]
4759      }, ['options']],
4760      recipientCount: ['Contact', 'get', {
4761        select: ['row_count'],
4762        join: [['Email AS email', 'INNER', ['id', '=', 'email.contact_id']]],
4763        where: [['id', 'IN', ctrl.ids], ['do_not_email', '!=', true], ['is_opt_out', '!=', true], ['email.on_hold', '=', 0]],
4764        groupBy: ['id']
4765      }]
4766    }).then(function(results) {
4767      ctrl.templateTypes = formatForSelect2(results.templateTypes[0], 'id', 'label', ['description']);
4768      ctrl.mailing.template_type = ctrl.templateTypes[0].id;
4769      ctrl.recipientCount = results.recipientCount.count;
4770    });
4771
4772    this.submit = function() {
4773      ctrl.start({
4774        values: {
4775          title: 'Hidden Group ' + Date.now(),
4776          is_hidden: true,
4777          'group_type:name': ['Mailing List'],
4778        },
4779        chain: {
4780          mailing: ['Mailing', 'create', {
4781            values: ctrl.mailing,
4782          }, 0],
4783          mailingGroup: ['MailingGroup', 'create', {
4784            values: {
4785              group_type: 'Include',
4786              'entity_table:name': 'Group',
4787              entity_id: '$id',
4788              mailing_id: '$mailing.id'
4789            },
4790          }, 0]
4791        }
4792      });
4793    };
4794    this.afterGroupCreate = function(result) {
4795      mailingId = result[0].mailing.id;
4796      ctrl.addContacts = {
4797        defaults: {group_id: result[0].id}
4798      };
4799    };
4800
4801    this.onSuccess = function(result) {
4802      window.location = CRM.url('civicrm/a#/mailing/' + mailingId);
4803    };
4804
4805    this.onError = function(error) {
4806      CRM.alert(ts('An error occurred while attempting to create mailing.'), ts('Error'), 'error');
4807      this.cancel();
4808    };
4809
4810  });
4811
4812
4813  angular.module('crmSearchTasks').controller('crmSearchTaskRelationship', function($scope, crmApi4, searchTaskBaseTrait) {
4814    const ts = $scope.ts = CRM.ts('org.civicrm.search_kit');
4815    const ctrl = angular.extend(this, $scope.model, searchTaskBaseTrait);
4816    const values = this.task.values && !Array.isArray(this.task.values) ? this.task.values : {};
4817    $scope.values = values;
4818
4819    this.autocompleteParams = {};
4820    this.relationshipInfo = this.task.relationshipTypes.reduce((acc, item) => {
4821      acc[item.id] = item;
4822      return acc;
4823    }, {});
4824    if (values.relationship_type) {
4825      this.onChangeRelationship();
4826    }
4827
4828    this.onChangeRelationship = function() {
4829      ctrl.relationshipFields = null;
4830        ctrl.autocompleteParams = {
4831        filters: {contact_sub_type: ctrl.relationshipInfo[values.relationship_type].contact_sub_type}
4832      };
4833      crmApi4('Relationship', 'getFields', {
4834        action: 'create',
4835        values: {relationship_type_id: values.relationship_type.split('_')[0]},
4836        loadOptions: ['id', 'name', 'label', 'description', 'color', 'icon'],
4837        select: ['name', 'label', 'description', 'input_type', 'data_type', 'serialize', 'options', 'fk_entity', 'nullable', 'required'],
4838        where: [
4839          ['readonly', '=', false],
4840          ['OR', [
4841            ['type', '=', 'Custom'],
4842            ['name', 'IN', ['description', 'start_date', 'end_date']]]
4843          ]
4844        ]
4845      }).then(function(result) {
4846        ctrl.relationshipFields = result;
4847      });
4848    };
4849
4850    this.submit = function() {
4851      const apiValues = _.cloneDeep(values);
4852      const relationshipType = values.relationship_type.split('_')[0];
4853      const a = values.relationship_type.split('_')[1];
4854      const b = values.relationship_type.split('_')[2];
4855      ctrl.idField = 'contact_id_' + a;
4856      delete apiValues.relationship_type;
4857      delete apiValues.contact_id;
4858      delete apiValues.disableRelationshipSelect;
4859      apiValues.relationship_type_id = relationshipType;
4860      apiValues['contact_id_' + b] = values.contact_id;
4861      ctrl.start({defaults: apiValues});
4862    };
4863
4864    this.onSuccess = function(result) {
4865      let added = 0;
4866      let duplicate = 0;
4867      result.forEach(function(relationship) {
4868        if (relationship.duplicate_id) {
4869          duplicate++;
4870        } else {
4871          added++;
4872        }
4873      });
4874      let msg = _.escape(ts('1 relationship added.', {plural: '%count relationships added.', count: added}));
4875      if (duplicate) {
4876        msg += '<br>' + _.escape(ts('1 relationship already exists.', {plural: '%count relationships already exist.', count: duplicate}));
4877      }
4878      CRM.alert(msg, ts('Saved'), 'success');
4879      this.close(result);
4880    };
4881
4882  });
4883
4884
4885  angular.module('crmSearchTasks').controller('crmSearchTaskTag', function($scope, crmApi4, searchTaskBaseTrait) {
4886    const ts = $scope.ts = CRM.ts('org.civicrm.search_kit'),
4887      ctrl = angular.extend(this, $scope.model, searchTaskBaseTrait);
4888
4889    this.entityTitle = this.getEntityTitle();
4890    this.action = 'save';
4891    this.selection = [];
4892    this.selectedTags = [];
4893    this.selectedTagsetTags = {};
4894
4895    crmApi4({
4896      tags: ['Tag', 'get', {
4897        select: ['id', 'label', 'color', 'description', 'is_selectable', 'parent_id'],
4898        where: [
4899          ['is_tagset', '=', false],
4900          ['used_for:name', 'CONTAINS', this.entity],
4901          ['OR', [['parent_id', 'IS NULL'], ['parent_id.is_tagset', '=', false]]]
4902        ],
4903        orderBy: {label: 'ASC'}
4904      }],
4905      tagsets: ['Tag', 'get', {
4906        select: ['id', 'name', 'label'],
4907        where: [['is_tagset', '=', true], ['used_for:name', 'CONTAINS', this.entity]]
4908      }],
4909    }).then(function(result) {
4910      ctrl.tagsets = result.tagsets;
4911      ctrl.tags = sortTagsForSelect2(result.tags);
4912    });
4913    function sortTagsForSelect2(rawTags) {
4914      const sorted = rawTags.reduce((accum, tag) => {
4915        accum[tag.id] = {
4916          id: tag.id,
4917          text: tag.label,
4918          description: tag.description,
4919          color: tag.color,
4920          disabled: !tag.is_selectable,
4921          parent_id: tag.parent_id
4922        };
4923        return accum;
4924      }, {});
4925      Object.values(sorted).forEach(tag => {
4926        if (tag.parent_id && sorted[tag.parent_id]) {
4927          sorted[tag.parent_id].children = sorted[tag.parent_id].children || [];
4928          sorted[tag.parent_id].children.push(tag);
4929        }
4930      });
4931      return Object.values(sorted).filter(tag => tag.parent_id === null);
4932    }
4933
4934    this.saveTags = function() {
4935      const params = {};
4936      if (ctrl.action === 'save') {
4937        params.defaults = {
4938          'entity_table:name': ctrl.entity
4939        };
4940        params.records = ctrl.selection.map(tagId => {
4941          return {tag_id: tagId};
4942        });
4943        params.match = ['entity_id', 'tag_id', 'entity_table'];
4944      }
4945      else {
4946        params.where = [
4947          ['entity_table:name', '=', ctrl.entity],
4948          ['tag_id', 'IN', ctrl.selection]
4949        ];
4950      }
4951      ctrl.start(params);
4952    };
4953
4954    this.onSelectTags = function() {
4955      ctrl.selection = [...ctrl.selectedTags];
4956      Object.values(ctrl.selectedTagsetTags).forEach(set => {
4957        ctrl.selection = [...ctrl.selection, ...set];
4958      });
4959    };
4960
4961    this.onSuccess = function(result) {
4962      let msg;
4963      if (ctrl.action === 'delete') {
4964        msg = ts('1 tag removed.', {plural: '%count tags removed.', count: result.batchCount});
4965        CRM.alert(msg, ts('Saved'), 'success');
4966      } else {
4967        const added = result.batchCount - result.countMatched;
4968        msg = ts('1 tag added', {plural: '%count tags added.', count: added});
4969        msg += '<br/>';
4970        if (result.countMatched > 0) {
4971          msg += ts('1 tag already exists and was not added.', {plural: '%count tags already exist and were not added.', count: result.countMatched});
4972        }
4973        CRM.alert(msg, ts('Saved'), 'success');
4974      }
4975      this.close();
4976    };
4977
4978    this.onError = function(error) {
4979      CRM.alert(ts('An error occurred while updating tags.'), ts('Error'), 'error');
4980      this.cancel();
4981    };
4982
4983  });
4984
4985
4986  angular.module('crmSearchTasks').controller('crmSearchTaskUpdate', function ($scope, $timeout, crmApi4, searchTaskBaseTrait, searchTaskFieldsTrait) {
4987    const ts = $scope.ts = CRM.ts('org.civicrm.search_kit');
4988    const ctrl = angular.extend(this, $scope.model, searchTaskBaseTrait, searchTaskFieldsTrait);
4989
4990    this.entityTitle = this.getEntityTitle();
4991
4992    this.loadFieldsAndValues(this.task, this.entity);
4993
4994    this.save = function() {
4995      ctrl.start({
4996        values: _.zipObject(ctrl.values)
4997      });
4998    };
4999
5000    this.onSuccess = function() {
5001      CRM.alert(ts('Successfully updated %1 %2.', {1: ctrl.ids.length, 2: ctrl.entityTitle}), ts('Saved'), 'success');
5002      this.close();
5003    };
5004
5005    this.onError = function(error) {
5006      CRM.alert(ts('An error occurred while attempting to update %1 %2.', {1: ctrl.ids.length, 2: ctrl.entityTitle}), ts('Error'), 'error');
5007      this.cancel();
5008    };
5009
5010  });
5011
5012
5013  angular.module('crmSearchTasks').component('crmSearchTasksMenu', {
5014    bindings: {
5015      taskManager: '<',
5016      displayMode: '<',
5017      ids: '<'
5018    },
5019    template: '<div class="btn-group" ng-include="\'~/crmSearchTasks/crmSearchTasks-\'+$ctrl.displayMode+\'.html\'"></div>',
5020    controller: function($scope) {
5021      const ts = $scope.ts = CRM.ts('org.civicrm.search_kit'),
5022        ctrl = this;
5023
5024      this.$onInit = function() {
5025        if (ctrl.displayMode === 'buttons') {
5026          ctrl.taskManager.getMetadata();
5027        }
5028        let unwatchIDs = $scope.$watch('$ctrl.ids.length', function (idsLength) {
5029          if (idsLength) {
5030            unwatchIDs();
5031            ctrl.taskManager.getMetadata();
5032          }
5033        });
5034      };
5035
5036      this.isActionAllowed = function(action) {
5037        return $scope.$eval('' + ctrl.ids.length + action.number);
5038      };
5039
5040      this.getActionTitle = function(action) {
5041        if (ctrl.isActionAllowed(action)) {
5042          return ctrl.ids.length ?
5043            ts('Perform action on %1 %2', {1: ctrl.ids.length, 2: ctrl.taskManager.entityInfo[ctrl.ids.length === 1 ? 'title' : 'title_plural']}) :
5044            ts('Perform action on all %1', {1: ctrl.taskManager.entityInfo.title_plural});
5045        }
5046        return ts('Selected number must be %1', {1: action.number.replace('===', '')});
5047      };
5048
5049      this.doAction = function(action) {
5050        if (!ctrl.isActionAllowed(action)) {
5051          return;
5052        }
5053        ctrl.taskManager.doTask(action, ctrl.ids);
5054      };
5055    }
5056  });
5057
5058
5059  angular.module('crmSearchTasks').factory('searchDisplayTasksTrait', function($rootScope, $window, crmApi4, dialogService) {
5060    const ts = CRM.ts('org.civicrm.search_kit');
5061    function TaskManager(displayCtrl, $element) {
5062      const mngr = this;
5063      let fetchedMetadata;
5064      this.tasks = null;
5065      this.entityInfo = null;
5066
5067      this.getMetadata = function() {
5068        if (!fetchedMetadata) {
5069          fetchedMetadata = crmApi4('SearchDisplay', 'getSearchTasks', {
5070            savedSearch: displayCtrl.search,
5071            display: displayCtrl.display,
5072          }).then(function(result) {
5073            mngr.entityInfo = result.editable.entityInfo;
5074            mngr.tasks = result;
5075          }, function(failure) {
5076            mngr.tasks = [];
5077            mngr.entityInfo = [];
5078          });
5079        }
5080        return fetchedMetadata;
5081      };
5082
5083      this.getApiParams = function() {
5084        return displayCtrl.getApiParams();
5085      };
5086      this.getRowCount = function() {
5087        return displayCtrl.rowCount;
5088      };
5089      this.isDisplayReady = function() {
5090        return !displayCtrl.loading && displayCtrl.results && displayCtrl.results.length;
5091      };
5092      this.getTaskInfo = function(taskName) {
5093        return _.findWhere(mngr.tasks, {name: taskName});
5094      };
5095
5096      this.doTask = function(task, ids, isLink) {
5097        const data = {
5098          ids: ids,
5099          entity: mngr.entityInfo.name,
5100          search: displayCtrl.search,
5101          display: displayCtrl.display,
5102          displayCtrl: displayCtrl,
5103          taskManager: mngr,
5104          entityInfo: mngr.entityInfo,
5105          isLink: isLink,
5106          task: _.cloneDeep(task),
5107        };
5108        if (task.crmPopup) {
5109          const mode = ('mode' in task.crmPopup) ? task.crmPopup.mode : 'back';
5110          const path = $rootScope.$eval(task.crmPopup.path, data),
5111            query = task.crmPopup.query && $rootScope.$eval(task.crmPopup.query, data);
5112          CRM.loadForm(CRM.url(path, query, mode), {post: task.crmPopup.data && $rootScope.$eval(task.crmPopup.data, data)})
5113            .on('crmFormSuccess', (e) => {
5114                e.stopPropagation();
5115                mngr.refreshAfterTask();
5116            });
5117        }
5118        else if (task.redirect) {
5119          const mode = ('mode' in task.redirect) ? task.redirect.mode : 'back';
5120          const redirectPath = $rootScope.$eval(task.redirect.path, data),
5121            redirectQuery = task.redirect.query && $rootScope.$eval(task.redirect.query, data) && $rootScope.$eval(task.redirect.data, data);
5122          $window.open(CRM.url(redirectPath, redirectQuery, mode), '_blank');
5123        }
5124        else {
5125          const options = CRM.utils.adjustDialogDefaults({
5126            autoOpen: false,
5127            dialogClass: 'crm-search-task-dialog',
5128            title: task.title
5129          });
5130          dialogService.open('crmSearchTask', (task.uiDialog && task.uiDialog.templateUrl) || '~/crmSearchTasks/crmSearchTaskApiBatch.html', data, options)
5131            .then((result) => mngr.refreshAfterTask(result, ids), _.noop);
5132        }
5133      };
5134
5135      this.refreshAfterTask = function(result, ids) {
5136        displayCtrl.selectedRows = [];
5137        displayCtrl.allRowsSelected = false;
5138        if (result && ids && result.action === 'inlineEdit' && ids.length === 1) {
5139          displayCtrl.refreshAfterEditing(result, ids[0]);
5140        }
5141        else {
5142          displayCtrl.rowCount = null;
5143          displayCtrl.getResultsPronto();
5144          $element.trigger('crmPopupFormSuccess');
5145        }
5146      };
5147    }
5148    return {
5149      selectAllPages: function() {
5150        const ctrl = this;
5151        ctrl.loadingAllRows = ctrl.allRowsSelected = true;
5152        const params = ctrl.getApiParams('id');
5153        crmApi4('SearchDisplay', 'run', params).then(function(ids) {
5154          ctrl.loadingAllRows = false;
5155          ctrl.selectedRows = _.uniq(_.toArray(ids));
5156        });
5157      },
5158      selectPage: function() {
5159        this.allRowsSelected = (this.rowCount <= this.results.length);
5160        this.selectedRows = _.uniq(_.pluck(this.results, 'key'));
5161      },
5162      selectNone: function() {
5163        this.allRowsSelected = false;
5164        this.selectedRows = [];
5165      },
5166      toggleAllRows: function() {
5167        if (this.selectedRows && this.selectedRows.length) {
5168          this.selectNone();
5169        }
5170        else if (this.page === 1 && this.rowCount === this.results.length) {
5171          this.selectPage();
5172        }
5173        else {
5174          this.selectAllPages();
5175        }
5176      },
5177      toggleRow: function(row, event) {
5178        this.selectedRows = this.selectedRows || [];
5179        const ctrl = this,
5180          index = ctrl.selectedRows.indexOf(row.key);
5181        function checkRange(allRows, checkboxPosition, dir) {
5182          for (let row = checkboxPosition; row >= 0 && row <= allRows.length; row += dir) {
5183            if (ctrl.selectedRows.indexOf(allRows[row]) > -1) {
5184              return row;
5185            }
5186          }
5187        }
5188        function selectRange(allRows, start, end) {
5189          for (let row = start; row <= end; ++row) {
5190            ctrl.selectedRows.push(allRows[row]);
5191          }
5192        }
5193
5194        if (index < 0) {
5195          if (event.shiftKey && ctrl.selectedRows.length) {
5196            const allRows = _.pluck(ctrl.results, 'key'),
5197              checkboxPosition = allRows.indexOf(row.key);
5198
5199            const nearestBefore = checkRange(allRows, checkboxPosition, -1),
5200              nearestAfter = checkRange(allRows, checkboxPosition, 1);
5201            if (nearestAfter !== undefined && (nearestBefore === undefined || nearestAfter === allRows.indexOf(_.last(ctrl.selectedRows)))) {
5202              selectRange(allRows, checkboxPosition + 1, nearestAfter - 1);
5203            } else if (nearestBefore !== undefined && (nearestAfter === undefined || nearestBefore === allRows.indexOf(_.last(ctrl.selectedRows)))) {
5204              selectRange(allRows, nearestBefore + 1, checkboxPosition -1);
5205            }
5206          }
5207          ctrl.selectedRows = _.uniq(ctrl.selectedRows.concat([row.key]));
5208          ctrl.allRowsSelected = (ctrl.rowCount === ctrl.selectedRows.length);
5209        } else {
5210          ctrl.allRowsSelected = false;
5211          ctrl.selectedRows.splice(index, 1);
5212        }
5213      },
5214      isRowSelected: function(row) {
5215        return this.allRowsSelected || _.includes(this.selectedRows, row.key);
5216      },
5217
5218      isPageSelected: function() {
5219        return (this.allRowsSelected && this.rowCount === this.results.length) ||
5220          (!this.allRowsSelected && this.selectedRows && this.selectedRows.length === this.results.length);
5221      },
5222      onClickLink: function(link, id, event) {
5223        if (link.task) {
5224          const mngr = this.taskManager;
5225          event.preventDefault();
5226          mngr.getMetadata().then(function() {
5227            mngr.doTask(_.extend({title: link.title}, mngr.getTaskInfo(link.task)), [id], true);
5228          });
5229        }
5230      },
5231      onInitialize: [function($scope, $element) {
5232        if (!this.taskManager) {
5233          this.taskManager = new TaskManager(this, $element);
5234        }
5235      }],
5236      onChangeFilters: [function() {
5237        this.selectedRows = [];
5238        this.allRowsSelected = false;
5239      }],
5240      onPostRun: [function(apiResults, status, editedRow) {
5241        if (editedRow && status === 'success' && this.selectedRows) {
5242          const index = this.selectedRows.indexOf(editedRow.key);
5243          if (index > -1 && !_.findWhere(apiResults.run, {key: editedRow.key})) {
5244            this.selectedRows.splice(index, 1);
5245          }
5246        }
5247        else if (status === 'success' && !editedRow && apiResults.run && apiResults.run[0]) {
5248          const mngr = this.taskManager;
5249          if (this.settings.actions_display_mode === 'buttons') {
5250            mngr.getMetadata();
5251            return;
5252          }
5253          _.each(apiResults.run[0].columns, function(column) {
5254            if ((column.link && column.link.task) || _.find(column.links || [], 'task')) {
5255              mngr.getMetadata();
5256            }
5257          });
5258        }
5259      }]
5260
5261    };
5262  });
5263
5264
5265  angular.module('crmSearchTasks').factory('searchTaskBaseTrait', function(dialogService) {
5266    const ts = CRM.ts('org.civicrm.search_kit');
5267    return {
5268
5269      getEntityTitle: function(count) {
5270        if (typeof count !== 'number') {
5271          count = this.ids.length;
5272        }
5273        return count === 1 ? this.entityInfo.title : this.entityInfo.title_plural;
5274      },
5275
5276      start: function(runParams) {
5277        $('.ui-dialog-titlebar button').hide();
5278        this.run = runParams || {};
5279      },
5280
5281      cancel: function() {
5282        dialogService.cancel('crmSearchTask');
5283      },
5284
5285      close: function(result) {
5286        dialogService.close('crmSearchTask', result);
5287      }
5288
5289    };
5290  });
5291
5292
5293
5294  let paths = {};
5295  angular.module('crmSearchTasks').factory('searchTaskFieldsTrait', function(crmApi4, $timeout) {
5296    const ctrl = {
5297
5298      values: [],
5299      fields: [],
5300
5301      loadFieldsAndValues: function(task, entityName, getFieldsParams, apiCalls) {
5302        this.fields.length = 0;
5303        this.values.length = 0;
5304        if (task.values && !Array.isArray(task.values)) {
5305          Object.keys(task.values).forEach(key => this.values.push([key, task.values[key]]));
5306        }
5307
5308        apiCalls = apiCalls || {};
5309        getFieldsParams = _.merge({
5310            action: 'update',
5311            select: ['name', 'label', 'description', 'input_type', 'data_type', 'serialize', 'options', 'fk_entity', 'nullable', 'required', 'default_value'],
5312            loadOptions: ['id', 'name', 'label', 'description', 'color', 'icon'],
5313            where: [],
5314          },
5315          getFieldsParams || {}
5316        );
5317        getFieldsParams.where.push(['deprecated', '=', false], ['readonly', '=', false]);
5318        apiCalls.getFields = [entityName, 'getFields', getFieldsParams];
5319        apiCalls.entityInfo = ['Entity', 'get', {
5320          where: [['name', '=', entityName]],
5321          select: ['paths']
5322        }, 0];
5323
5324        const apiResult = crmApi4(apiCalls);
5325        apiResult.then((results) => {
5326          ctrl.fields.push(... results.getFields);
5327          paths = results.entityInfo.paths;
5328          if (getFieldsParams.action === 'create') {
5329            results.getFields.forEach(field => {
5330              if (field.required && !field.default_value) {
5331                this.addField(field.name);
5332              }
5333            });
5334          }
5335        });
5336        return apiResult;
5337      },
5338
5339      getField: function(fieldName) {
5340        return ctrl.fields.find(field => field.name === fieldName);
5341      },
5342
5343      addField: function(selection) {
5344        if (selection && !fieldInUse(selection)) {
5345          const field = this.getField(selection);
5346          let value = '';
5347          if (field.serialize || field.data_type === 'Array') {
5348            value = [];
5349          } else if (field.data_type === 'Boolean') {
5350            value = true;
5351          } else if (field.default_value) {
5352            value = field.default_value;
5353          } else if (field.options && field.options.length) {
5354            value = field.options[0].id;
5355          }
5356          this.values.push([selection, value]);
5357        }
5358      },
5359
5360      updateField: function(index) {
5361        $timeout(() => {
5362          if (!this.values[index][0]) {
5363            this.values.splice(index, 1);
5364          }
5365        });
5366      },
5367
5368      availableFields: function() {
5369        const results = ctrl.fields.map(item => {
5370          const formatted = {
5371            id: item.name,
5372            text: item.label,
5373            description: item.description
5374          };
5375          if (fieldInUse(item.name)) {
5376            formatted.disabled = true;
5377          }
5378          return formatted;
5379        });
5380        return {results: results};
5381      },
5382
5383      getUrl(action, values) {
5384        const path = paths[action];
5385        if (!path) {
5386          return null;
5387        }
5388        return CRM.url(path.replace(/\[(.*?)]/g, (match, key) => values[key]));
5389      }
5390
5391    };
5392
5393    function fieldInUse(fieldName) {
5394      return ctrl.values.map(value => value[0]).includes(fieldName);
5395    }
5396
5397    return ctrl;
5398  });
5399
5400
5401let uidCount = 0,
5402    pageTitleHTML = 'CiviCRM',
5403    documentTitle = 'CiviCRM';
5404
5405  angular.module('crmUi', CRM.angRequires('crmUi'))
5406    .directive('crmUiAccordion', function() {
5407      return {
5408        scope: {
5409          crmUiAccordion: '='
5410        },
5411        template: '<details class="crm-accordion-bold"><summary>{{crmUiAccordion.title}} <a crm-ui-help="help" ng-if="help"></a></summary><div class="crm-accordion-body" ng-transclude></div></details>',
5412        transclude: true,
5413        link: function (scope, element, attrs) {
5414          scope.help = null;
5415          let openSet = false;
5416          scope.$watch('crmUiAccordion', function(crmUiAccordion, oldVal) {
5417            if (crmUiAccordion) {
5418              if (!openSet) {
5419                $(element).children('details').prop('open', !crmUiAccordion.collapsed);
5420                openSet = true;
5421              }
5422              if (crmUiAccordion.help) {
5423                scope.help = crmUiAccordion.help.clone({}, {
5424                  title: crmUiAccordion.title
5425                });
5426              }
5427            }
5428          });
5429        }
5430      };
5431    })
5432    .service('crmUiAlert', function($compile, $rootScope, $templateRequest, $q) {
5433      let count = 0;
5434      return function crmUiAlert(params) {
5435        const id = 'crmUiAlert_' + (++count);
5436        let tpl = null;
5437        if (params.templateUrl) {
5438          tpl = $templateRequest(params.templateUrl);
5439        }
5440        else if (params.template) {
5441          tpl = params.template;
5442        }
5443        if (tpl) {
5444          params.text = '<div id="' + id + '"></div>'; // temporary stub
5445        }
5446        const result = CRM.alert(params.text, params.title, params.type, params.options);
5447        if (tpl) {
5448          $q.when(tpl, function(html) {
5449            const scope = params.scope || $rootScope.$new();
5450            const linker = $compile(html);
5451            $('#' + id).append($(linker(scope)));
5452          });
5453        }
5454        return result;
5455      };
5456    })
5457    .directive('crmUiDatepicker', function ($timeout) {
5458      return {
5459        restrict: 'AE',
5460        require: 'ngModel',
5461        scope: {
5462          crmUiDatepicker: '='
5463        },
5464        link: function (scope, element, attrs, ngModel) {
5465          ngModel.$render = function () {
5466            const viewVal = ngModel.$viewValue || '';
5467            if (element.val() != viewVal) {
5468              element.val(viewVal).change();
5469            }
5470          };
5471          let settings = angular.copy(scope.crmUiDatepicker || {});
5472          settings.start_date_years = settings.start_date_years || 100;
5473          settings.end_date_years = settings.end_date_years || 100;
5474          $timeout(function() {
5475            element
5476              .crmDatepicker(settings)
5477              .on('change', function () {
5478                $timeout(function() {
5479                  let requiredLength = 19;
5480                  if (settings.time === false) {
5481                    requiredLength = 10;
5482                  }
5483                  if (settings.date === false) {
5484                    requiredLength = 8;
5485                  }
5486                  else if (typeof settings.date === 'string') {
5487                    const lowerFormat = settings.date.toLowerCase();
5488                    if (lowerFormat.indexOf('y') < 0 || lowerFormat.indexOf('m') < 0 || lowerFormat.indexOf('d') < 0) {
5489                      requiredLength = element.val().length;
5490                    }
5491                  }
5492                  ngModel.$setValidity('incompleteDateTime', !(element.val().length && element.val().length !== requiredLength));
5493                });
5494              });
5495          });
5496        }
5497      };
5498    })
5499    .directive('crmUiDebug', function ($location) {
5500      return {
5501        restrict: 'AE',
5502        scope: {
5503          crmUiDebug: '@'
5504        },
5505        template: function() {
5506          const args = $location.search();
5507          if (args && args.angularDebug) {
5508            const jsonTpl = (CRM.angular.modules.indexOf('jsonFormatter') < 0) ? '<pre>{{data|json}}</pre>' : '<json-formatter json="data" open="1"></json-formatter>';
5509            return '<div crm-ui-accordion=\'{title: ts("Debug (%1)", {1: crmUiDebug}), collapsed: true}\'>' + jsonTpl + '</div>';
5510          }
5511          return '';
5512        },
5513        link: function(scope, element, attrs) {
5514          const args = $location.search();
5515          if (args && args.angularDebug) {
5516            scope.ts = CRM.ts(null);
5517            scope.$parent.$watch(attrs.crmUiDebug, function(data) {
5518              scope.data = data;
5519            });
5520          }
5521        }
5522      };
5523    })
5524    .directive('crmUiField', function() {
5525      const templateUrls = {
5526        default: '~/crmUi/field.html',
5527        checkbox: '~/crmUi/field-cb.html'
5528      };
5529
5530      return {
5531        require: '^crmUiIdScope',
5532        restrict: 'EA',
5533        scope: {
5534          crmUiField: '='
5535        },
5536        templateUrl: function(tElement, tAttrs){
5537          const layout = tAttrs.crmLayout ? tAttrs.crmLayout : 'default';
5538          return templateUrls[layout];
5539        },
5540        transclude: true,
5541        link: function (scope, element, attrs, crmUiIdCtrl) {
5542          $(element).addClass('crm-section');
5543          scope.help = null;
5544          scope.$watch('crmUiField', function(crmUiField) {
5545            if (crmUiField && crmUiField.help) {
5546              scope.help = crmUiField.help.clone({}, {
5547                title: crmUiField.title
5548              });
5549            }
5550          });
5551        }
5552      };
5553    })
5554    .directive('crmUiId', function () {
5555      return {
5556        require: '^crmUiIdScope',
5557        restrict: 'EA',
5558        link: {
5559          pre: function (scope, element, attrs, crmUiIdCtrl) {
5560            const id = crmUiIdCtrl.get(attrs.crmUiId);
5561            element.attr('id', id);
5562          }
5563        }
5564      };
5565    })
5566    .service('crmUiHelp', function(){
5567      function FieldHelp(options) {
5568        this.options = options;
5569      }
5570      angular.extend(FieldHelp.prototype, {
5571        get: function(n) {
5572          return this.options[n];
5573        },
5574        open: function open() {
5575          CRM.help(this.options.title, {id: this.options.id, file: this.options.file});
5576        },
5577        clone: function clone(options, defaults) {
5578          return new FieldHelp(angular.extend({}, defaults, this.options, options));
5579        }
5580      });
5581      return function(defaults){
5582        return function(options) {
5583          if (typeof options === 'string') {
5584            options = {id: options};
5585          }
5586          return new FieldHelp(angular.extend({}, defaults, options));
5587        };
5588      };
5589    })
5590    .directive('crmUiHelp', function() {
5591      return {
5592        restrict: 'EA',
5593        link: function(scope, element, attrs) {
5594          setTimeout(function() {
5595            const crmUiHelp = scope.$eval(attrs.crmUiHelp);
5596            let title = crmUiHelp && crmUiHelp.get('title') ? ts('%1 Help', {1: crmUiHelp.get('title')}) : ts('Help');
5597            element.attr('title', title);
5598          }, 50);
5599
5600          element
5601            .addClass('helpicon')
5602            .attr('href', '#')
5603            .on('click', function(e) {
5604              e.preventDefault();
5605              scope.$eval(attrs.crmUiHelp).open();
5606            });
5607        }
5608      };
5609    })
5610    .directive('crmUiFor', function ($parse, $timeout) {
5611      return {
5612        require: '^crmUiIdScope',
5613        restrict: 'EA',
5614        template: '<span ng-class="cssClasses"><span ng-transclude/><span crm-ui-visible="crmIsRequired" class="crm-marker" title="This field is required.">*</span></span>',
5615        transclude: true,
5616        link: function (scope, element, attrs, crmUiIdCtrl) {
5617          scope.crmIsRequired = false;
5618          scope.cssClasses = {};
5619
5620          if (!attrs.crmUiFor) return;
5621
5622          const id = crmUiIdCtrl.get(attrs.crmUiFor);
5623          element.attr('for', id);
5624          let ngModel = null;
5625
5626          const updateCss = function () {
5627            scope.cssClasses['crm-error'] = !ngModel.$valid && !ngModel.$pristine;
5628          };
5629          const init = function (retries, retryDelay) {
5630            const input = $('#' + id);
5631            if (input.length === 0 && !attrs.crmUiForceRequired) {
5632              if (retries) {
5633                $timeout(function(){
5634                  init(retries-1, retryDelay);
5635                }, retryDelay);
5636              }
5637              return;
5638            }
5639
5640            if (attrs.crmUiForceRequired) {
5641              scope.crmIsRequired = true;
5642              return;
5643            }
5644
5645            let tgtScope = scope;//.$parent;
5646            if (attrs.crmDepth) {
5647              for (let i = attrs.crmDepth; i > 0; i--) {
5648                tgtScope = tgtScope.$parent;
5649              }
5650            }
5651
5652            if (input.attr('ng-required')) {
5653              scope.crmIsRequired = scope.$parent.$eval(input.attr('ng-required'));
5654              scope.$parent.$watch(input.attr('ng-required'), function (isRequired) {
5655                scope.crmIsRequired = isRequired;
5656              });
5657            }
5658            else {
5659              scope.crmIsRequired = input.prop('required');
5660            }
5661
5662            ngModel = $parse(attrs.crmUiFor)(tgtScope);
5663            if (ngModel) {
5664              ngModel.$viewChangeListeners.push(updateCss);
5665            }
5666          };
5667
5668          $timeout(function(){
5669            init(3, 100);
5670          });
5671        }
5672      };
5673    })
5674    .directive('crmUiIdScope', function () {
5675      return {
5676        restrict: 'EA',
5677        scope: {},
5678        controllerAs: 'crmUiIdCtrl',
5679        controller: function($scope) {
5680          const ids = {};
5681          this.get = function(name) {
5682            if (!ids[name]) {
5683              ids[name] = "crmUiId_" + (++uidCount);
5684            }
5685            return ids[name];
5686          };
5687        },
5688        link: function (scope, element, attrs) {}
5689      };
5690    })
5691    .directive('crmUiIframe', function ($parse) {
5692      return {
5693        scope: {
5694          crmUiIframeSrc: '@', // expression which evaluates to a URL
5695          crmUiIframe: '@' // expression which evaluates to HTML content
5696        },
5697        link: function (scope, elm, attrs) {
5698          const iframe = $(elm)[0];
5699          iframe.setAttribute('width', '100%');
5700          iframe.setAttribute('height', '250px');
5701          iframe.setAttribute('frameborder', '0');
5702
5703          const refresh = function () {
5704            if (attrs.crmUiIframeSrc) {
5705              iframe.setAttribute('src', scope.$parent.$eval(attrs.crmUiIframeSrc));
5706            }
5707            else {
5708              let iframeHtml = scope.$parent.$eval(attrs.crmUiIframe);
5709
5710              let doc = iframe.document;
5711              if (iframe.contentDocument) {
5712                doc = iframe.contentDocument;
5713              }
5714              else if (iframe.contentWindow) {
5715                doc = iframe.contentWindow.document;
5716              }
5717
5718              doc.open();
5719              doc.writeln(iframeHtml);
5720              doc.close();
5721            }
5722          };
5723          $(elm).parent().on('dialogresize dialogopen', function(e, ui) {
5724            $(this).css({padding: '0', margin: '0', overflow: 'hidden'});
5725            iframe.setAttribute('height', '' + $(this).innerHeight() + 'px');
5726          });
5727
5728          $(elm).parent().on('dialogresize', function(e, ui) {
5729            iframe.setAttribute('class', 'resized');
5730          });
5731
5732          scope.$parent.$watch(attrs.crmUiIframe, refresh);
5733        }
5734      };
5735    })
5736    .directive('crmUiInsertRx', function() {
5737      return {
5738        link: function(scope, element, attrs) {
5739          scope.$on(attrs.crmUiInsertRx, function(e, tokenName) {
5740            CRM.wysiwyg.insert(element, tokenName);
5741            $(element).select2('close').select2('val', '');
5742            CRM.wysiwyg.focus(element);
5743          });
5744        }
5745      };
5746    })
5747    .directive('crmUiRichtext', function ($timeout) {
5748      return {
5749        require: '?ngModel',
5750        link: function (scope, elm, attr, ngModel) {
5751          $timeout(function() {
5752            const editor = CRM.wysiwyg.create(elm);
5753
5754            if (!ngModel) {
5755              return;
5756            }
5757
5758            if (attr.ngBlur) {
5759              $(elm).on('blur', function() {
5760                $timeout(function() {
5761                  scope.$eval(attr.ngBlur);
5762                });
5763              });
5764            }
5765
5766            ngModel.$render = function(value) {
5767              editor.done(function() {
5768                CRM.wysiwyg.setVal(elm, ngModel.$viewValue || '');
5769              });
5770            };
5771          });
5772        }
5773      };
5774    })
5775    .directive('crmUiLock', function ($parse, $rootScope) {
5776      const defaultVal = function (defaultValue) {
5777        const f = function (scope) {
5778          return defaultValue;
5779        };
5780        f.assign = function (scope, value) {
5781        };
5782        return f;
5783      };
5784      const parse = function (expr, defaultValue) {
5785        return expr ? $parse(expr) : defaultVal(defaultValue);
5786      };
5787
5788      return {
5789        template: '',
5790        link: function (scope, element, attrs) {
5791          const binding = parse(attrs.binding, true);
5792          const titleLocked = parse(attrs.titleLocked, ts('Locked'));
5793          const titleUnlocked = parse(attrs.titleUnlocked, ts('Unlocked'));
5794
5795          $(element).addClass('crm-i lock-button');
5796          const refresh = function () {
5797            const locked = binding(scope);
5798            if (locked) {
5799              $(element)
5800                .removeClass('fa-unlock')
5801                .addClass('fa-lock')
5802                .prop('title', titleLocked(scope))
5803              ;
5804            }
5805            else {
5806              $(element)
5807                .removeClass('fa-lock')
5808                .addClass('fa-unlock')
5809                .prop('title', titleUnlocked(scope))
5810              ;
5811            }
5812          };
5813
5814          $(element).click(function () {
5815            binding.assign(scope, !binding(scope));
5816            $rootScope.$digest();
5817          });
5818
5819          scope.$watch(attrs.binding, refresh);
5820          scope.$watch(attrs.titleLocked, refresh);
5821          scope.$watch(attrs.titleUnlocked, refresh);
5822
5823          refresh();
5824        }
5825      };
5826    })
5827    .service('CrmUiOrderCtrl', function(){
5828      function CrmUiOrderCtrl(defaults){
5829        this.values = defaults;
5830      }
5831      angular.extend(CrmUiOrderCtrl.prototype, {
5832        get: function get() {
5833          return this.values;
5834        },
5835        getDir: function getDir(name) {
5836          if (this.values.indexOf(name) >= 0 || this.values.indexOf('+' + name) >= 0) {
5837            return '+';
5838          }
5839          if (this.values.indexOf('-' + name) >= 0) {
5840            return '-';
5841          }
5842          return '';
5843        },
5844        remove: function remove(name) {
5845          const idx = this.values.indexOf(name);
5846          if (idx >= 0) {
5847            this.values.splice(idx, 1);
5848            return true;
5849          }
5850          else {
5851            return false;
5852          }
5853        },
5854        setDir: function setDir(name, dir) {
5855          return this.toggle(name, dir);
5856        },
5857        toggle: function toggle(name, next) {
5858          if (!next && next !== '') {
5859            next = '+';
5860            if (this.remove(name) || this.remove('+' + name)) {
5861              next = '-';
5862            }
5863            if (this.remove('-' + name)) {
5864              next = '';
5865            }
5866          }
5867
5868          if (next == '+') {
5869            this.values.unshift('+' + name);
5870          }
5871          else if (next == '-') {
5872            this.values.unshift('-' + name);
5873          }
5874        }
5875      });
5876      return CrmUiOrderCtrl;
5877    })
5878    .directive('crmUiOrder', function(CrmUiOrderCtrl) {
5879      return {
5880        link: function(scope, element, attrs){
5881          const options = angular.extend({var: 'crmUiOrderBy'}, scope.$eval(attrs.crmUiOrder));
5882          scope[options.var] = new CrmUiOrderCtrl(options.defaults);
5883        }
5884      };
5885    })
5886    .directive('crmUiOrderBy', function() {
5887      return {
5888        link: function(scope, element, attrs) {
5889          function updateClass(crmUiOrderCtrl, name) {
5890            const dir = crmUiOrderCtrl.getDir(name);
5891            element
5892              .toggleClass('sorting_asc', dir === '+')
5893              .toggleClass('sorting_desc', dir === '-')
5894              .toggleClass('sorting', dir === '');
5895          }
5896
5897          element.on('click', function(e){
5898            const tgt = scope.$eval(attrs.crmUiOrderBy);
5899            tgt[0].toggle(tgt[1]);
5900            updateClass(tgt[0], tgt[1]);
5901            e.preventDefault();
5902            scope.$digest();
5903          });
5904
5905          const tgt = scope.$eval(attrs.crmUiOrderBy);
5906          updateClass(tgt[0], tgt[1]);
5907        }
5908      };
5909    })
5910    .directive('crmUiSelect', function ($parse, $timeout) {
5911      return {
5912        require: '?ngModel',
5913        priority: 1,
5914        scope: {
5915          crmUiSelect: '='
5916        },
5917        link: function (scope, element, attrs, ngModel) {
5918
5919          if (ngModel && !attrs.ngOptions) {
5920            ngModel.$render = function () {
5921              $timeout(function () {
5922                let newVal = _.cloneDeep(ngModel.$modelValue);
5923                if (typeof newVal === 'string' && element.select2('container').hasClass('select2-container-multi')) {
5924                  newVal = newVal.length ? newVal.split(scope.crmUiSelect.separator || ',') : [];
5925                }
5926                element.select2('val', newVal);
5927              });
5928            };
5929          }
5930          function refreshModel() {
5931            const oldValue = ngModel.$viewValue;
5932            let newValue = element.select2('val');
5933            if (Array.isArray(newValue) && attrs.ngList) {
5934              newValue = newValue.join(attrs.ngList);
5935            }
5936            if (oldValue != newValue) {
5937              scope.$parent.$apply(function () {
5938                ngModel.$setViewValue(newValue);
5939              });
5940            }
5941          }
5942
5943          function init() {
5944            element.crmSelect2(scope.crmUiSelect || {});
5945            if (ngModel) {
5946              element.on('change', refreshModel);
5947            }
5948          }
5949          if (attrs.ngOptions) {
5950            $timeout(function() {
5951              element.crmSelect2(scope.crmUiSelect || {});
5952              ngModel.$render = function () {
5953                element.val(ngModel.$viewValue || '').change();
5954              };
5955            });
5956          } else {
5957            $timeout(init);
5958          }
5959        }
5960      };
5961    })
5962    .directive('onCrmUiSelect', function () {
5963      return {
5964        priority: 10,
5965        link: function (scope, element, attrs) {
5966          element.on('select2-selecting', function(e) {
5967            e.preventDefault();
5968            element.select2('close').select2('val', '');
5969            scope.$apply(function() {
5970              scope.$eval(attrs.onCrmUiSelect, {selection: e.val});
5971            });
5972          });
5973        }
5974      };
5975    })
5976    .directive('crmEntityref', function ($parse, $timeout) {
5977      return {
5978        require: '?ngModel',
5979        scope: {
5980          crmEntityref: '='
5981        },
5982        link: function (scope, element, attrs, ngModel) {
5983
5984          ngModel.$render = function () {
5985            $timeout(function () {
5986              let newVal = _.cloneDeep(ngModel.$modelValue);
5987              if (typeof newVal === 'string' && element.select2('container').hasClass('select2-container-multi')) {
5988                newVal = newVal.length ? newVal.split(',') : [];
5989              }
5990              element.select2('val', newVal);
5991            });
5992          };
5993          function refreshModel() {
5994            const oldValue = ngModel.$viewValue;
5995            let newValue = element.select2('val');
5996            if (oldValue != newValue) {
5997              scope.$parent.$apply(function () {
5998                ngModel.$setViewValue(newValue);
5999              });
6000            }
6001          }
6002
6003          function init() {
6004            element.crmEntityRef(scope.crmEntityref || {});
6005            element.on('change', refreshModel);
6006            $timeout(ngModel.$render);
6007          }
6008
6009          init();
6010        }
6011      };
6012    })
6013    .directive('crmAutocomplete', function () {
6014      return {
6015        require: {
6016          crmAutocomplete: 'crmAutocomplete',
6017          ngModel: '?ngModel'
6018        },
6019        priority: 100,
6020        bindToController: {
6021          entity: '<crmAutocomplete',
6022          crmAutocompleteParams: '<',
6023          multi: '<',
6024          autoOpen: '<',
6025          quickAdd: '<',
6026          quickEdit: '<',
6027          staticOptions: '<'
6028        },
6029        link: function(scope, element, attr, ctrl) {
6030          const parseList = function(viewValue) {
6031            if (typeof viewValue === 'undefined') {
6032              return;
6033            }
6034
6035            if (!ctrl.crmAutocomplete.multi) {
6036              return viewValue;
6037            }
6038
6039            const list = [];
6040
6041            if (viewValue) {
6042              _.each(viewValue.split(','), function(value) {
6043                if (value) {
6044                  list.push(_.trim(value));
6045                }
6046              });
6047            }
6048
6049            return list;
6050          };
6051
6052          if (ctrl.ngModel) {
6053            ctrl.ngModel.$render = function() {
6054              const newValue = (ctrl.ngModel.$viewValue === null || ctrl.ngModel.$viewValue === undefined || ctrl.ngModel.$viewValue === false) ? '' : ctrl.ngModel.$viewValue.toString();
6055              if (newValue !== element.val().toString()) {
6056                element.val(newValue).change();
6057              }
6058            };
6059            ctrl.ngModel.$parsers.push(parseList);
6060            ctrl.ngModel.$formatters.push(function(value) {
6061              return Array.isArray(value) ? value.join(',') : value;
6062            });
6063            ctrl.ngModel.$isEmpty = function(value) {
6064              return !value || !value.length;
6065            };
6066          }
6067        },
6068        controller: function($element, $timeout) {
6069          const ctrl = this;
6070          this.$onChanges = function() {
6071            $timeout(function() {
6072              const autoOpen = ctrl.autoOpen &&
6073                !(ctrl.staticOptions && ctrl.staticOptions.length) &&
6074                !(ctrl.quickAdd === true || (ctrl.quickAdd && ctrl.quickAdd.length));
6075              $element.crmAutocomplete(ctrl.entity, ctrl.crmAutocompleteParams || {}, {
6076                multiple: ctrl.multi,
6077                minimumInputLength: autoOpen ? 0 : 1,
6078                static: ctrl.staticOptions || [],
6079                quickAdd: ctrl.quickAdd,
6080                quickEdit: ctrl.quickEdit,
6081              });
6082            });
6083          };
6084        }
6085      };
6086    })
6087    .directive('crmMultipleEmail', function ($parse, $timeout) {
6088      return {
6089        require: 'ngModel',
6090        link: function(scope, element, attrs, ctrl) {
6091          ctrl.$parsers.unshift(function(viewValue) {
6092            if (_.isEmpty(viewValue)) {
6093              ctrl.$setValidity('crmMultipleEmail', true);
6094              return viewValue;
6095            }
6096            const emails = viewValue.split(',');
6097            const emailRegex = /\S+@\S+\.\S+/;
6098
6099            const validityArr = emails.map(function(str){
6100              return emailRegex.test(str.trim());
6101            });
6102
6103            if ($.inArray(false, validityArr) > -1) {
6104              ctrl.$setValidity('crmMultipleEmail', false);
6105            } else {
6106              ctrl.$setValidity('crmMultipleEmail', true);
6107            }
6108            return viewValue;
6109          });
6110        }
6111      };
6112    })
6113    .directive('crmUiTab', function($parse) {
6114      return {
6115        require: '^crmUiTabSet',
6116        restrict: 'EA',
6117        scope: {
6118          crmTitle: '@',
6119          crmIcon: '@',
6120          count: '@',
6121          id: '@'
6122        },
6123        template: '<div ng-transclude></div>',
6124        transclude: true,
6125        link: function (scope, element, attrs, crmUiTabSetCtrl) {
6126          crmUiTabSetCtrl.add(scope);
6127        }
6128      };
6129    })
6130    .directive('crmUiTabSet', function() {
6131      return {
6132        restrict: 'EA',
6133        scope: {
6134          crmUiTabSet: '@',
6135          tabSetOptions: '<'
6136        },
6137        templateUrl: '~/crmUi/tabset.html',
6138        transclude: true,
6139        controllerAs: 'crmUiTabSetCtrl',
6140        controller: function($scope, $element, $timeout) {
6141          let init;
6142          $scope.tabs = [];
6143          this.add = function(tab) {
6144            if (!tab.id) throw "Tab is missing 'id'";
6145            $scope.tabs.push(tab);
6146            if (init) {
6147              $timeout.cancel(init);
6148            }
6149            init = $timeout(function() {
6150              $element.find('.crm-tabset').tabs($scope.tabSetOptions);
6151            });
6152          };
6153        }
6154      };
6155    })
6156    .directive('crmUiValidate', function() {
6157      return {
6158        restrict: 'EA',
6159        require: 'ngModel',
6160        link: function(scope, element, attrs, ngModel) {
6161          const validationKey = attrs.crmUiValidateName ? attrs.crmUiValidateName : 'crmUiValidate';
6162          scope.$watch(attrs.crmUiValidate, function(newValue){
6163            ngModel.$setValidity(validationKey, !!newValue);
6164          });
6165        }
6166      };
6167    })
6168    .directive('crmUiVisible', function($parse) {
6169      return {
6170        restrict: 'EA',
6171        scope: {
6172          crmUiVisible: '@'
6173        },
6174        link: function (scope, element, attrs) {
6175          const model = $parse(attrs.crmUiVisible);
6176          function updatecChildren() {
6177            element.css('visibility', model(scope.$parent) ? 'inherit' : 'hidden');
6178          }
6179          updatecChildren();
6180          scope.$parent.$watch(attrs.crmUiVisible, updatecChildren);
6181        }
6182      };
6183    })
6184    .directive('crmUiWizard', function() {
6185      return {
6186        restrict: 'EA',
6187        scope: {
6188          crmUiWizard: '@',
6189          crmUiWizardNavClass: '@' // string, A list of classes that will be added to the nav items
6190        },
6191        templateUrl: '~/crmUi/wizard.html',
6192        transclude: true,
6193        controllerAs: 'crmUiWizardCtrl',
6194        controller: function($scope, $parse) {
6195          const steps = $scope.steps = []; // array<$scope>
6196          let maxVisited = 0;
6197          let selectedIndex = null;
6198
6199          const findIndex = function() {
6200            let found = null;
6201            angular.forEach(steps, function(step, stepKey) {
6202              if (step.selected) found = stepKey;
6203            });
6204            return found;
6205          };
6206          this.$index = function() { return selectedIndex; };
6207          this.$first = function() { return this.$index() === 0; };
6208          this.$last = function() { return this.$index() === steps.length -1; };
6209          this.$maxVisit = function() { return maxVisited; };
6210          this.$validStep = function() {
6211            return steps[selectedIndex] && steps[selectedIndex].isStepValid();
6212          };
6213          this.iconFor = function(index) {
6214            if (index < this.$index()) return 'crm-i fa-check';
6215            if (index === this.$index()) return 'crm-i fa-angle-double-right';
6216            return '';
6217          };
6218          this.isSelectable = function(step) {
6219            if (step.selected) return false;
6220            return this.$validStep();
6221          };
6222
6223          /*** @param Object step the $scope of the step */
6224          this.select = function(step) {
6225            angular.forEach(steps, function(otherStep, otherKey) {
6226              otherStep.selected = (otherStep === step);
6227              if (otherStep === step && maxVisited < otherKey) maxVisited = otherKey;
6228            });
6229            selectedIndex = findIndex();
6230          };
6231          /*** @param Object step the $scope of the step */
6232          this.add = function(step) {
6233            if (steps.length === 0) {
6234              step.selected = true;
6235              selectedIndex = 0;
6236            }
6237            steps.push(step);
6238            steps.sort(function(a,b){
6239              return a.crmUiWizardStep - b.crmUiWizardStep;
6240            });
6241            selectedIndex = findIndex();
6242          };
6243          this.remove = function(step) {
6244            let key = null;
6245            angular.forEach(steps, function(otherStep, otherKey) {
6246              if (otherStep === step) key = otherKey;
6247            });
6248            if (key !== null) {
6249              steps.splice(key, 1);
6250            }
6251          };
6252          this.goto = function(index) {
6253            if (index < 0) index = 0;
6254            if (index >= steps.length) index = steps.length-1;
6255            this.select(steps[index]);
6256          };
6257          this.previous = function() { this.goto(this.$index()-1); };
6258          this.next = function() { this.goto(this.$index()+1); };
6259          if ($scope.crmUiWizard) {
6260            $parse($scope.crmUiWizard).assign($scope.$parent, this);
6261          }
6262        },
6263        link: function (scope, element, attrs) {
6264          scope.ts = CRM.ts(null);
6265
6266          element.find('.crm-wizard-buttons button[ng-click^=crmUiWizardCtrl]').click(function () {
6267            const topOfWizard = element.offset().top;
6268            const heightOfMenu = $('#civicrm-menu').height() || 0;
6269
6270            $('html')
6271              .stop()
6272              .animate({scrollTop: topOfWizard - heightOfMenu}, 1000);
6273          });
6274        }
6275      };
6276    })
6277    .directive('crmUiWizardButtons', function() {
6278      return {
6279        require: '^crmUiWizard',
6280        restrict: 'EA',
6281        scope: {},
6282        template: '<span ng-transclude></span>',
6283        transclude: true,
6284        link: function (scope, element, attrs, crmUiWizardCtrl) {
6285          const realButtonsEl = $(element).closest('.crm-wizard').find('.crm-wizard-buttons');
6286          $(element).appendTo(realButtonsEl);
6287        }
6288      };
6289    })
6290    .directive('crmIcon', function() {
6291      return {
6292        restrict: 'EA',
6293        link: function (scope, element, attrs) {
6294          if (element.is('[crm-ui-tab]')) {
6295            return;
6296          }
6297          if (attrs.crmIcon) {
6298            if (attrs.crmIcon.substring(0,3) == 'fa-') {
6299              $(element).prepend('<i class="crm-i ' + attrs.crmIcon + '" role="img" aria-hidden="true"></i> ');
6300            }
6301            else {
6302              $(element).prepend('<span class="icon ui-icon-' + attrs.crmIcon + '"></span> ');
6303            }
6304          }
6305          if ($(element).is('button:not(.btn)')) {
6306            $(element).addClass('crm-button');
6307          }
6308        }
6309      };
6310    })
6311    .directive('crmUiWizardStep', function() {
6312      let nextWeight = 1;
6313      return {
6314        require: ['^crmUiWizard', 'form'],
6315        restrict: 'EA',
6316        scope: {
6317          crmTitle: '@', // expression, evaluates to a printable string
6318          crmUiWizardStep: '@', // int, a weight which determines the ordering of the steps
6319          crmUiWizardStepClass: '@' // string, A list of classes that will be added to the template
6320        },
6321        template: '<div class="crm-wizard-step {{crmUiWizardStepClass}}" ng-show="selected" ng-transclude/></div>',
6322        transclude: true,
6323        link: function (scope, element, attrs, ctrls) {
6324          const crmUiWizardCtrl = ctrls[0];
6325          const form = ctrls[1];
6326          if (scope.crmUiWizardStep) {
6327            scope.crmUiWizardStep = parseInt(scope.crmUiWizardStep);
6328          } else {
6329            scope.crmUiWizardStep = nextWeight++;
6330          }
6331          scope.isStepValid = function() {
6332            return form.$valid;
6333          };
6334          crmUiWizardCtrl.add(scope);
6335          scope.$on('$destroy', function(){
6336            crmUiWizardCtrl.remove(scope);
6337          });
6338        }
6339      };
6340    })
6341    .directive('crmConfirm', function ($compile, $rootScope, $templateRequest, $q) {
6342      const defaultFuncs = {
6343        'disable': function (options) {
6344          return {
6345            message: ts('Are you sure you want to disable this?'),
6346            options: {no: ts('Cancel'), yes: ts('Disable')},
6347            width: 300,
6348            title: ts('Disable %1?', {
6349              1: options.obj.title || options.obj.label || options.obj.name || ts('the record')
6350            })
6351          };
6352        },
6353        'revert': function (options) {
6354          return {
6355            message: ts('Are you sure you want to revert this?'),
6356            options: {no: ts('Cancel'), yes: ts('Revert')},
6357            width: 300,
6358            title: ts('Revert %1?', {
6359              1: options.obj.title || options.obj.label || options.obj.name || ts('the record')
6360            })
6361          };
6362        },
6363        'delete': function (options) {
6364          return {
6365            message: ts('Are you sure you want to delete this?'),
6366            options: {no: ts('Cancel'), yes: ts('Delete')},
6367            width: 300,
6368            title: ts('Delete %1?', {
6369              1: options.obj.title || options.obj.label || options.obj.name || ts('the record')
6370            })
6371          };
6372        }
6373      };
6374      let confirmCount = 0;
6375      return {
6376        link: function (scope, element, attrs) {
6377          $(element).click(function () {
6378            const options = scope.$eval(attrs.crmConfirm);
6379            if (attrs.title && !options.title) {
6380              options.title = attrs.title;
6381            }
6382            const defaults = (options.type) ? defaultFuncs[options.type](options) : {};
6383
6384            let tpl = null;
6385            let stubId = null;
6386            if (!options.message) {
6387              if (options.templateUrl) {
6388                tpl = $templateRequest(options.templateUrl);
6389              }
6390              else if (options.template) {
6391                tpl = options.template;
6392              }
6393              if (tpl) {
6394                stubId = 'crmUiConfirm_' + (++confirmCount);
6395                options.message = '<div id="' + stubId + '"></div>';
6396              }
6397            }
6398
6399            if (options.confirmed) {
6400              scope.$apply(attrs.onYes);
6401              return;
6402            }
6403
6404            CRM.confirm(_.extend(defaults, options))
6405              .on('crmConfirm:yes', function() { scope.$apply(attrs.onYes); })
6406              .on('crmConfirm:no', function() { scope.$apply(attrs.onNo); });
6407
6408            if (tpl && stubId) {
6409              $q.when(tpl, function(html) {
6410                const scope = options.scope || $rootScope.$new();
6411                if (options.export) {
6412                  angular.extend(scope, options.export);
6413                }
6414                const linker = $compile(html);
6415                $('#' + stubId).append($(linker(scope)));
6416              });
6417            }
6418          });
6419        }
6420      };
6421    })
6422    .directive('crmPageTitle', function($timeout) {
6423      return {
6424        scope: {
6425          crmDocumentTitle: '='
6426        },
6427        link: function(scope, $el, attrs) {
6428          pageTitleHTML = attrs.initialPageTitle || 'CiviCRM';
6429          documentTitle = attrs.initialDocumentTitle || 'CiviCRM';
6430
6431          function update() {
6432            $timeout(function() {
6433              const newPageTitleHTML = $el.html().trim(),
6434                newDocumentTitle = scope.crmDocumentTitle || $el.text(),
6435                dialog = $el.closest('.ui-dialog-content');
6436              if (dialog.length) {
6437                dialog.dialog('option', 'title', newDocumentTitle);
6438                $el.hide();
6439              } else {
6440                document.title = $('title').text().replace(documentTitle, newDocumentTitle);
6441                [].forEach.call(document.querySelectorAll('h1:not(.crm-container h1), .crm-page-title-wrapper>h1'), h1 => {
6442                  if (h1.classList.contains('crm-page-title') || h1.innerHTML.trim() === pageTitleHTML) {
6443                    h1.classList.add('crm-page-title');
6444                    h1.innerHTML = newPageTitleHTML;
6445                    $el.hide();
6446                  }
6447                });
6448                pageTitleHTML = newPageTitleHTML;
6449                documentTitle = newDocumentTitle;
6450              }
6451            });
6452          }
6453
6454          scope.$watch(function() {return scope.crmDocumentTitle + $el.html();}, update);
6455        }
6456      };
6457    })
6458    .directive("crmUiEditable", function() {
6459      return {
6460        restrict: "A",
6461        require: "ngModel",
6462        scope: {
6463          defaultValue: '='
6464        },
6465        link: function(scope, element, attrs, ngModel) {
6466          function read() {
6467            let htmlVal = element.text();
6468            if (!htmlVal) {
6469              htmlVal = scope.defaultValue || '';
6470              element.text(htmlVal);
6471            }
6472            ngModel.$setViewValue(htmlVal);
6473          }
6474
6475          ngModel.$render = function() {
6476            element.text(ngModel.$viewValue || scope.defaultValue || '');
6477          };
6478          element.on('keydown', function(e) {
6479            if (e.which === 13) {
6480              e.preventDefault();
6481              element.blur();
6482            }
6483            if (e.which === 27) {
6484              element.text(ngModel.$viewValue || scope.defaultValue || '');
6485              element.blur();
6486            }
6487          });
6488
6489          element.on("blur change", function() {
6490            scope.$apply(read);
6491          });
6492
6493          element.attr('contenteditable', 'true');
6494        }
6495      };
6496    })
6497    .directive('crmUiIconPicker', function($timeout) {
6498      return {
6499        restrict: 'A',
6500        require: '?ngModel', // Soft require ngModel
6501        controller: function($element, $scope, $attrs) {
6502          CRM.loadScript(CRM.config.resourceBase + 'js/jquery/jquery.crmIconPicker.js').then(function() {
6503            $timeout(function() {
6504              $element.crmIconPicker();
6505              if ($attrs.ngModel) {
6506                $scope.$watch($attrs.ngModel, function(newValue) {
6507                  if (newValue !== undefined) {
6508                    $element.val(newValue).trigger('change');
6509                  }
6510                });
6511              }
6512            });
6513          });
6514        }
6515      };
6516    })
6517    .factory('formatForSelect2', function() {
6518      return function(input, key, label, extra) {
6519        return input.reduce((result, item) => {
6520          const formatted = {id: item[key], text: item[label]};
6521
6522          if (extra) {
6523            extra.forEach(prop => formatted[prop] = item[prop]);
6524          }
6525
6526          result.push(formatted);
6527          return result;
6528        }, []);
6529      };
6530    })
6531
6532    .run(function($rootScope, $location) {
6533      $rootScope.goto = function(path) {
6534        $location.path(path);
6535      };
6536    });
6537
6538
6539angular.module('crmUtil', CRM.angRequires('crmUtil'));
6540  angular.module('crmUtil').factory('crmApi', function($q) {
6541    var crmApi = function(entity, action, params, message) {
6542      var deferred = $q.defer();
6543      var p;
6544      var backend = crmApi.backend || CRM.api3;
6545      if (params && params.body_html) {
6546        params.body_html = params.body_html.replace(/([\u2028]|[\u2029])/g, '\n');
6547      }
6548      if (_.isObject(entity)) {
6549        /*jshint -W061 */
6550        p = backend(eval('('+angular.toJson(entity)+')'), action);
6551      } else {
6552        /*jshint -W061 */
6553        p = backend(entity, action, eval('('+angular.toJson(params)+')'), message);
6554      }
6555      p.then(
6556        function(result) {
6557          if (result.is_error) {
6558            deferred.reject(result);
6559          } else {
6560            deferred.resolve(result);
6561          }
6562        },
6563        function(error) {
6564          deferred.reject(error);
6565        }
6566      );
6567      return deferred.promise;
6568    };
6569    crmApi.backend = null;
6570    crmApi.val = function(value) {
6571      var d = $.Deferred();
6572      d.resolve(value);
6573      return d.promise();
6574    };
6575    return crmApi;
6576  });
6577  angular.module('crmUtil').factory('crmMetadata', function($q, crmApi) {
6578    function convertOptionsToMap(options) {
6579      var result = {};
6580      angular.forEach(options, function(o) {
6581        result[o.key] = o.value;
6582      });
6583      return result;
6584    }
6585
6586    var cache = {}; // cache[entityName+'::'+action][fieldName].title
6587    var deferreds = {}; // deferreds[cacheKey].push($q.defer())
6588    var crmMetadata = {
6589      getField: function getField(entity, field) {
6590        return $q.when(crmMetadata.getFields(entity)).then(function(fields){
6591          return fields[field];
6592        });
6593      },
6594      getFields: function getFields(entity) {
6595        var action = '', cacheKey;
6596        if (_.isArray(entity)) {
6597          action = entity[1];
6598          entity = entity[0];
6599          cacheKey = entity + '::' + action;
6600        } else {
6601          cacheKey = entity;
6602        }
6603
6604        if (_.isObject(cache[cacheKey])) {
6605          return cache[cacheKey];
6606        }
6607
6608        var needFetch = _.isEmpty(deferreds[cacheKey]);
6609        deferreds[cacheKey] = deferreds[cacheKey] || [];
6610        var deferred = $q.defer();
6611        deferreds[cacheKey].push(deferred);
6612
6613        if (needFetch) {
6614          crmApi(entity, 'getfields', {action: action, sequential: 1, options: {get_options: 'all'}})
6615            .then(
6616            function(fields) {
6617              cache[cacheKey] = _.indexBy(fields.values, 'name');
6618              angular.forEach(cache[cacheKey],function (field){
6619                if (field.options) {
6620                  field.optionsMap = convertOptionsToMap(field.options);
6621                }
6622              });
6623              angular.forEach(deferreds[cacheKey], function(dfr) {
6624                dfr.resolve(cache[cacheKey]);
6625              });
6626              delete deferreds[cacheKey];
6627            },
6628            function() {
6629              cache[cacheKey] = {}; // cache nack
6630              angular.forEach(deferreds[cacheKey], function(dfr) {
6631                dfr.reject();
6632              });
6633              delete deferreds[cacheKey];
6634            }
6635          );
6636        }
6637
6638        return deferred.promise;
6639      }
6640    };
6641
6642    return crmMetadata;
6643  });
6644  angular.module('crmUtil').factory('crmBlocker', function() {
6645    return function() {
6646      var blocks = 0;
6647      var result = function(promise) {
6648        blocks++;
6649        return promise.finally(function() {
6650          blocks--;
6651        });
6652      };
6653      result.check = function() {
6654        return blocks > 0;
6655      };
6656      return result;
6657    };
6658  });
6659
6660  angular.module('crmUtil').factory('crmLegacy', function() {
6661    return CRM;
6662  });
6663  angular.module('crmUtil').factory('crmLog', function(){
6664    var level = 0;
6665    var write = console.log;
6666    function indent() {
6667      var s = '>';
6668      for (var i = 0; i < level; i++) s = s + '  ';
6669      return s;
6670    }
6671    var crmLog = {
6672      log: function(msg, vars) {
6673        write(indent() + msg, vars);
6674      },
6675      wrap: function(label, f) {
6676        return function(){
6677          level++;
6678          crmLog.log(label + ": start", arguments);
6679          var r;
6680          try {
6681            r = f.apply(this, arguments);
6682          } finally {
6683            crmLog.log(label + ": end");
6684            level--;
6685          }
6686          return r;
6687        };
6688      }
6689    };
6690    return crmLog;
6691  });
6692
6693  angular.module('crmUtil').factory('crmNavigator', ['$window', function($window) {
6694    return {
6695      redirect: function(path) {
6696        $window.location.href = path;
6697      }
6698    };
6699  }]);
6700  angular.module('crmUtil').factory('crmQueue', function($q) {
6701    return function crmQueue(worker) {
6702      var queue = [];
6703      function next() {
6704        var task = queue[0];
6705        worker.apply(null, task.a).then(
6706          function onOk(data) {
6707            queue.shift();
6708            task.dfr.resolve(data);
6709            if (queue.length > 0) next();
6710          },
6711          function onErr(err) {
6712            queue.shift();
6713            task.dfr.reject(err);
6714            if (queue.length > 0) next();
6715          }
6716        );
6717      }
6718      function enqueue() {
6719        var dfr = $q.defer();
6720        queue.push({a: arguments, dfr: dfr});
6721        if (queue.length === 1) {
6722          next();
6723        }
6724        return dfr.promise;
6725      }
6726      return enqueue;
6727    };
6728  });
6729  angular.module('crmUtil').factory('crmStatus', function($q){
6730    return function(options, aPromise){
6731      if (aPromise) {
6732        return CRM.toAPromise($q, CRM.status(options, CRM.toJqPromise(aPromise)));
6733      } else {
6734        return CRM.toAPromise($q, CRM.status(options));
6735      }
6736    };
6737  });
6738  angular.module('crmUtil').factory('crmWatcher', function(){
6739    return function() {
6740      var unwatches = {}, watchFactories = {}, suspends = {};
6741      this.setup = function(name, newWatchFactory) {
6742        watchFactories[name] = newWatchFactory;
6743        unwatches[name] = watchFactories[name]();
6744        suspends[name] = 0;
6745        return this;
6746      };
6747      this.suspend = function(name, f) {
6748        suspends[name]++;
6749        this.teardown(name);
6750        var r;
6751        try {
6752          r = f.apply(this, []);
6753        } finally {
6754          if (suspends[name] === 1) {
6755            unwatches[name] = watchFactories[name]();
6756            if (!angular.isArray(unwatches[name])) {
6757              unwatches[name] = [unwatches[name]];
6758            }
6759          }
6760          suspends[name]--;
6761        }
6762        return r;
6763      };
6764
6765      this.teardown = function(name) {
6766        if (!unwatches[name]) return;
6767        _.each(unwatches[name], function(unwatch){
6768          unwatch();
6769        });
6770        delete unwatches[name];
6771      };
6772
6773      return this;
6774    };
6775  });
6776  angular.module('crmUtil').factory('crmThrottle', function($q) {
6777    var pending = [],
6778      executing = [];
6779    return function(func) {
6780      var deferred = $q.defer();
6781
6782      function checkResult(result, success) {
6783        _.pull(executing, func);
6784        if (_.includes(pending, func)) {
6785          runNext();
6786        } else if (success) {
6787          deferred.resolve(result);
6788        } else {
6789          deferred.reject(result);
6790        }
6791      }
6792
6793      function runNext() {
6794        executing.push(func);
6795        _.pull(pending, func);
6796        func().then(function(result) {
6797          checkResult(result, true);
6798        }, function(result) {
6799          checkResult(result, false);
6800        });
6801      }
6802
6803      if (!_.includes(executing, func)) {
6804        runNext();
6805      } else if (!_.includes(pending, func)) {
6806        pending.push(func);
6807      }
6808      return deferred.promise;
6809    };
6810  });
6811
6812  angular.module('crmUtil').factory('crmLoadScript', function($q) {
6813    return function(url) {
6814      var deferred = $q.defer();
6815
6816      CRM.loadScript(url).done(function() {
6817        deferred.resolve(true);
6818      });
6819
6820      return deferred.promise;
6821    };
6822  });
6823  angular.module('crmUtil').filter('encodeURIComponent', function() {
6824    return function(input) {
6825      return input ? encodeURIComponent(input) : '';
6826    };
6827  });
6828
6829
6830
6831  /**
6832   * Allows using ng-model with a custom element like civi-rich-text-input
6833   */
6834  angular.module("crmUtil").directive("crmCustomElementModel", () => ({
6835    restrict: "A",
6836    require: 'ngModel',
6837    link: function ($scope, $element, attributes, ngModelController) {
6838      const element = $element[0];
6839      ngModelController.$render = () => element.value = ngModelController.$viewValue;
6840      element.addEventListener('change', () => ngModelController.$setViewValue(element.value));
6841    }
6842  }));
6843
6844})(angular, CRM.$, CRM._);
6845
6846(function($, angular){
6847angular.module('dialogService', []).service('dialogService',
6848	['$rootScope', '$q', '$compile', '$templateCache', '$http',
6849	function($rootScope, $q, $compile, $templateCache, $http) {
6850
6851			var _this = this;
6852			_this.dialogs = {};
6853
6854			this.open = function(id, template, model, options) {
6855				if (!angular.isDefined(id)) {
6856					throw "dialogService requires id in call to open";
6857				}
6858
6859				if (!angular.isDefined(template)) {
6860					throw "dialogService requires template in call to open";
6861				}
6862				if (!angular.isDefined(model)) {
6863					model = null;
6864				}
6865				var dialogOptions = {};
6866				if (angular.isDefined(options)) {
6867					angular.extend(dialogOptions, options);
6868				}
6869				var dialog = { scope: null, ref: null, deferred: $q.defer() };
6870				loadTemplate(template).then(
6871					function(dialogTemplate) {
6872						dialog.scope = $rootScope.$new();
6873						dialog.scope.model = model;
6874						var dialogLinker = $compile(dialogTemplate);
6875						dialog.ref = $(dialogLinker(dialog.scope));
6876						var customCloseFn = dialogOptions.close;
6877						dialogOptions.close = function(event, ui) {
6878							if (customCloseFn) {
6879								customCloseFn(event, ui);
6880							}
6881							cleanup(id);
6882						};
6883						dialog.ref.dialog(dialogOptions);
6884						dialog.ref.dialog("open");
6885						_this.dialogs[id] = dialog;
6886
6887					}, function(error) {
6888						throw error;
6889					}
6890				);
6891				return dialog.deferred.promise;
6892			};
6893
6894			this.close = function(id, result) {
6895				var dialog = getExistingDialog(id);
6896				dialog.deferred.resolve(result);
6897				dialog.ref.dialog("close");
6898			};
6899
6900			this.cancel = function(id) {
6901				var dialog = getExistingDialog(id);
6902				dialog.deferred.reject();
6903				dialog.ref.dialog("close");
6904			};
6905
6906			this.setButtons = function(id, buttons) {
6907				var dialog = getExistingDialog(id);
6908				dialog.ref.dialog("option", 'buttons', buttons);
6909			};
6910
6911			function cleanup (id) {
6912				var dialog = getExistingDialog(id);
6913				dialog.deferred.reject();
6914				dialog.scope.$destroy();
6915				dialog.ref.remove();
6916				delete _this.dialogs[id];
6917			};
6918
6919			function getExistingDialog(id) {
6920				var dialog = _this.dialogs[id];
6921				if (!angular.isDefined(dialog)) {
6922					throw "DialogService does not have a reference to dialog id " + id;
6923				}
6924				return dialog;
6925			};
6926			function loadTemplate(template) {
6927
6928				var deferred = $q.defer();
6929				var html = $templateCache.get(template);
6930
6931				if (angular.isDefined(html)) {
6932					html = html.trim();
6933					deferred.resolve(html);
6934				} else {
6935					return $http.get(template, { cache : $templateCache }).then(
6936						function(response) {
6937							var html = response.data;
6938							if(!html || !html.length) {
6939								return $q.reject("Template " + template + " was not found");
6940							}
6941							html = html.trim();
6942							$templateCache.put(template, html);
6943							return html;
6944						}, function() {
6945							return $q.reject("Template " + template + " was not found");
6946			        	}
6947			        );
6948				}
6949			    return deferred.promise;
6950			}
6951		}
vendor: 4,665 bytes, lines 6952-6967
6952]);
6953})(jQuery, angular);
6954
6955/*
6956 AngularJS v1.8.0
6957 (c) 2010-2020 Google, Inc. http://angularjs.org
6958 License: MIT
6959*/
6960(function(s,c){'use strict';function P(c){var h=[];C(h,E).chars(c);return h.join("")}var D=c.$$minErr("$sanitize"),F,h,G,H,I,q,E,J,K,C;c.module("ngSanitize",[]).provider("$sanitize",function(){function f(a,e){return B(a.split(","),e)}function B(a,e){var d={},b;for(b=0;b<a.length;b++)d[e?q(a[b]):a[b]]=!0;return d}function t(a,e){e&&e.length&&h(a,B(e))}function Q(a){for(var e={},d=0,b=a.length;d<b;d++){var k=a[d];e[k.name]=k.value}return e}function L(a){return a.replace(/&/g,"&amp;").replace(z,function(a){var d=
6961a.charCodeAt(0);a=a.charCodeAt(1);return"&#"+(1024*(d-55296)+(a-56320)+65536)+";"}).replace(u,function(a){return"&#"+a.charCodeAt(0)+";"}).replace(/</g,"&lt;").replace(/>/g,"&gt;")}function A(a){for(;a;){if(a.nodeType===s.Node.ELEMENT_NODE)for(var e=a.attributes,d=0,b=e.length;d<b;d++){var k=e[d],g=k.name.toLowerCase();if("xmlns:ns1"===g||0===g.lastIndexOf("ns1:",0))a.removeAttributeNode(k),d--,b--}(e=a.firstChild)&&A(e);a=v("nextSibling",a)}}function v(a,e){var d=e[a];if(d&&J.call(e,d))throw D("elclob",
6962e.outerHTML||e.outerText);return d}var y=!1,g=!1;this.$get=["$$sanitizeUri",function(a){y=!0;g&&h(m,l);return function(e){var d=[];K(e,C(d,function(b,d){return!/^unsafe:/.test(a(b,d))}));return d.join("")}}];this.enableSvg=function(a){return I(a)?(g=a,this):g};this.addValidElements=function(a){y||(H(a)&&(a={htmlElements:a}),t(l,a.svgElements),t(r,a.htmlVoidElements),t(m,a.htmlVoidElements),t(m,a.htmlElements));return this};this.addValidAttrs=function(a){y||h(M,B(a,!0));return this};F=c.bind;h=c.extend;
6963G=c.forEach;H=c.isArray;I=c.isDefined;q=c.$$lowercase;E=c.noop;K=function(a,e){null===a||void 0===a?a="":"string"!==typeof a&&(a=""+a);var d=N(a);if(!d)return"";var b=5;do{if(0===b)throw D("uinput");b--;a=d.innerHTML;d=N(a)}while(a!==d.innerHTML);for(b=d.firstChild;b;){switch(b.nodeType){case 1:e.start(b.nodeName.toLowerCase(),Q(b.attributes));break;case 3:e.chars(b.textContent)}var k;if(!(k=b.firstChild)&&(1===b.nodeType&&e.end(b.nodeName.toLowerCase()),k=v("nextSibling",b),!k))for(;null==k;){b=
6964v("parentNode",b);if(b===d)break;k=v("nextSibling",b);1===b.nodeType&&e.end(b.nodeName.toLowerCase())}b=k}for(;b=d.firstChild;)d.removeChild(b)};C=function(a,e){var d=!1,b=F(a,a.push);return{start:function(a,g){a=q(a);!d&&w[a]&&(d=a);d||!0!==m[a]||(b("<"),b(a),G(g,function(d,g){var c=q(g),f="img"===a&&"src"===c||"background"===c;!0!==M[c]||!0===O[c]&&!e(d,f)||(b(" "),b(g),b('="'),b(L(d)),b('"'))}),b(">"))},end:function(a){a=q(a);d||!0!==m[a]||!0===r[a]||(b("</"),b(a),b(">"));a==d&&(d=!1)},chars:function(a){d||
6965b(L(a))}}};J=s.Node.prototype.contains||function(a){return!!(this.compareDocumentPosition(a)&16)};var z=/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,u=/([^#-~ |!])/g,r=f("area,br,col,hr,img,wbr"),x=f("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),p=f("rp,rt"),n=h({},p,x),x=h({},x,f("address,article,aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,section,table,ul")),p=h({},p,f("a,abbr,acronym,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var")),
6966l=f("circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph,hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline,radialGradient,rect,stop,svg,switch,text,title,tspan"),w=f("script,style"),m=h({},r,x,p,n),O=f("background,cite,href,longdesc,src,xlink:href,xml:base"),n=f("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,valign,value,vspace,width"),
6967p=f("accent-height,accumulate,additive,alphabetic,arabic-form,ascent,baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,keySplines,keyTimes,lang,marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mathematical,max,min,offset,opacity,orient,origin,overline-position,overline-thickness,panose-1,path,pathLength,points,preserveAspectRatio,r,refX,refY,repeatCount,repeatDur,requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,stemv,stop-color,stop-opacity,str
6967ikethrough-position,strikethrough-thickness,stroke,stroke-dasharray,stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,underline-position,underline-thickness,unicode,unicode-range,units-per-em,values,version,viewBox,visibility,width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,xlink:show,xlink:title,xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,zoomAndPan",
6968!0),M=h({},O,p,n),N=function(a,e){function d(b){b="<remove></remove>"+b;try{var d=(new a.DOMParser).parseFromString(b,"text/html").body;d.firstChild.remove();return d}catch(e){}}function b(a){c.innerHTML=a;e.documentMode&&A(c);return c}var g;if(e&&e.implementation)g=e.implementation.createHTMLDocument("inert");else throw D("noinert");var c=(g.documentElement||g.getDocumentElement()).querySelector("body");c.innerHTML='<svg><g onload="this.parentNode.remove()"></g></svg>';return c.querySelector("svg")?
6969(c.innerHTML='<svg><p><style><img src="</style><img src=x onerror=alert(1)//">',c.querySelector("svg img")?d:b):function(b){b="<remove></remove>"+b;try{b=encodeURI(b)}catch(d){return}var e=new a.XMLHttpRequest;e.responseType="document";e.open("GET","data:text/html;charset=utf-8,"+b,!1);e.send(null);b=e.response.body;b.firstChild.remove();return b}}(s,s.document)}).info({angularVersion:"1.8.0"});c.module("ngSanitize").filter("linky",["$sanitize",function(f){var h=/((s?ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"\u201d\u2019]/i,
6970t=/^mailto:/i,q=c.$$minErr("linky"),s=c.isDefined,A=c.isFunction,v=c.isObject,y=c.isString;return function(c,z,u){function r(c){c&&l.push(P(c))}function x(c,g){var f,a=p(c);l.push("<a ");for(f in a)l.push(f+'="'+a[f]+'" ');!s(z)||"target"in a||l.push('target="',z,'" ');l.push('href="',c.replace(/"/g,"&quot;"),'">');r(g);l.push("</a>")}if(null==c||""===c)return c;if(!y(c))throw q("notstring",c);for(var p=A(u)?u:v(u)?function(){return u}:function(){return{}},n=c,l=[],w,m;c=n.match(h);)w=c[0],c[2]||
6971c[4]||(w=(c[3]?"http://":"mailto:")+w),m=c.index,r(n.substr(0,m)),x(w,c[0].replace(t,"")),n=n.substring(m+c[0].length);r(n);return f(l.join(""))}}])})(window,window.angular);
6972
6973/*
6974 * angular-ui-bootstrap
6975 * http://angular-ui.github.io/bootstrap/
6976
6977 * Version: 2.5.0 - 2017-01-28
6978 * License: MIT
6979 */angular.module("ui.bootstrap",["ui.bootstrap.tpls","ui.bootstrap.collapse","ui.bootstrap.tabindex","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.isClass","ui.bootstrap.datepicker","ui.bootstrap.position","ui.bootstrap.datepickerPopup","ui.bootstrap.debounce","ui.bootstrap.multiMap","ui.bootstrap.dropdown","ui.bootstrap.stackedMap","ui.bootstrap.modal","ui.bootstrap.paging","ui.bootstrap.pager","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]),angular.module("ui.bootstrap.tpls",["uib/template/accordion/accordion-group.html","uib/template/accordion/accordion.html","uib/template/alert/alert.html","uib/template/carousel/carousel.html","uib/template/carousel/slide.html","uib/template/datepicker/datepicker.html","uib/template/datepicker/day.html","uib/template/datepicker/month.html","uib/template/datepicker/year.html","uib/template/datepickerPopup/popup.html","uib/template/modal/window.html","uib/template/pager/pager.html","uib/template/pagination/pagination.html","uib/template/tooltip/tooltip-html-popup.html","uib/template/tooltip/tooltip-popup.html","uib/template/tooltip/tooltip-template-popup.html","uib/template/popover/popover-html.html","uib/template/popover/popover-template.html","uib/template/popover/popover.html","uib/template/progressbar/bar.html","uib/template/progressbar/progress.html","uib/template/progressbar/progressbar.html","uib/template/rating/rating.html","uib/template/tabs/tab.html","uib/template/tabs/tabset.html","uib/template/timepicker/timepicker.html","uib/template/typeahead/typeahead-match.html","uib/template/typeahead/typeahead-popup.html"]),angular.module("ui.bootstrap.collapse",[]).directive("uibCollapse",["$animate","$q","$parse","$injector",function(a,b,c,d){var e=d.has("$animateCss")?d.get("$animateCss"):null;return{link:function(d,f,g){function h(){r=!!("horizontal"in g),r?(s={width:""},t={width:"0"}):(s={height:""},t={height:"0"}),d.$eval(g.uibCollapse)||f.addClass("in").addClass("collapse").attr("aria-expanded",!0).attr("aria-hidden",!1).css(s)}function i(a){return r?{width:a.scrollWidth+"px"}:{height:a.scrollHeight+"px"}}function j(){f.hasClass("collapse")&&f.hasClass("in")||b.resolve(n(d)).then(function(){f.removeClass("collapse").addClass("collapsing").attr("aria-expanded",!0).attr("aria-hidden",!1),e?e(f,{addClass:"in",easing:"ease",css:{overflow:"hidden"},to:i(f[0])}).start()["finally"](k):a.addClass(f,"in",{css:{overflow:"hidden"},to:i(f[0])}).then(k)},angular.noop)}function k(){f.removeClass("collapsing").addClass("collapse").css(s),o(d)}function l(){return f.hasClass("collapse")||f.hasClass("in")?void b.resolve(p(d)).then(function(){f.css(i(f[0])).removeClass("collapse").addClass("collapsing").attr("aria-expanded",!1).attr("aria-hidden",!0),e?e(f,{removeClass:"in",to:t}).start()["finally"](m):a.removeClass(f,"in",{to:t}).then(m)},angular.noop):m()}function m(){f.css(t),f.removeClass("collapsing").addClass("collapse"),q(d)}var n=c(g.expanding),o=c(g.expanded),p=c(g.collapsing),q=c(g.collapsed),r=!1,s={},t={};
vendor: 4,630 bytes, line 6979
6979h(),d.$watch(g.uibCollapse,function(a){a?l():j()})}}}]),angular.module("ui.bootstrap.tabindex",[]).directive("uibTabindexToggle",function(){return{restrict:"A",link:function(a,b,c){c.$observe("disabled",function(a){c.$set("tabindex",a?-1:null)})}}}),angular.module("ui.bootstrap.accordion",["ui.bootstrap.collapse","ui.bootstrap.tabindex"]).constant("uibAccordionConfig",{closeOthers:!0}).controller("UibAccordionController",["$scope","$attrs","uibAccordionConfig",function(a,b,c){this.groups=[],this.closeOthers=function(d){var e=angular.isDefined(b.closeOthers)?a.$eval(b.closeOthers):c.closeOthers;e&&angular.forEach(this.groups,function(a){a!==d&&(a.isOpen=!1)})},this.addGroup=function(a){var b=this;this.groups.push(a),a.$on("$destroy",function(c){b.removeGroup(a)})},this.removeGroup=function(a){var b=this.groups.indexOf(a);-1!==b&&this.groups.splice(b,1)}}]).directive("uibAccordion",function(){return{controller:"UibAccordionController",controllerAs:"accordion",transclude:!0,templateUrl:function(a,b){return b.templateUrl||"uib/template/accordion/accordion.html"}}}).directive("uibAccordionGroup",function(){return{require:"^uibAccordion",transclude:!0,restrict:"A",templateUrl:function(a,b){return b.templateUrl||"uib/template/accordion/accordion-group.html"},scope:{heading:"@",panelClass:"@?",isOpen:"=?",isDisabled:"=?"},controller:function(){this.setHeading=function(a){this.heading=a}},link:function(a,b,c,d){b.addClass("panel"),d.addGroup(a),a.openClass=c.openClass||"panel-open",a.panelClass=c.panelClass||"panel-default",a.$watch("isOpen",function(c){b.toggleClass(a.openClass,!!c),c&&d.closeOthers(a)}),a.toggleOpen=function(b){a.isDisabled||b&&32!==b.which||(a.isOpen=!a.isOpen)};var e="accordiongroup-"+a.$id+"-"+Math.floor(1e4*Math.random());a.headingId=e+"-tab",a.panelId=e+"-panel"}}}).directive("uibAccordionHeading",function(){return{transclude:!0,template:"",replace:!0,require:"^uibAccordionGroup",link:function(a,b,c,d,e){d.setHeading(e(a,angular.noop))}}}).directive("uibAccordionTransclude",function(){function a(){return"uib-accordion-header,data-uib-accordion-header,x-uib-accordion-header,uib\\:accordion-header,[uib-accordion-header],[data-uib-accordion-header],[x-uib-accordion-header]"}return{require:"^uibAccordionGroup",link:function(b,c,d,e){b.$watch(function(){return e[d.uibAccordionTransclude]},function(b){if(b){var d=angular.element(c[0].querySelector(a()));d.html(""),d.append(b)}})}}}),angular.module("ui.bootstrap.alert",[]).controller("UibAlertController",["$scope","$element","$attrs","$interpolate","$timeout",function(a,b,c,d,e){a.closeable=!!c.close,b.addClass("alert"),c.$set("role","alert"),a.closeable&&b.addClass("alert-dismissible");var f=angular.isDefined(c.dismissOnTimeout)?d(c.dismissOnTimeout)(a.$parent):null;f&&e(function(){a.close()},parseInt(f,10))}]).directive("uibAlert",function(){return{controller:"UibAlertController",controllerAs:"alert",restrict:"A",templateUrl:function(a,b){return b.templateUrl||"uib/template/alert/alert.html"},transclude:!0,scope:{close:"&"}}}),angular.module("ui.bootstrap.buttons",[]).constant("uibButtonConfig",{activeClass:"active",toggleEvent:"click"}).controller("UibButtonsController",["uibButtonConfig",function(a){this.activeClass=a.activeClass||"active",this.toggleEvent=a.toggleEvent||"click"}]).directive("uibBtnRadio",["$parse",function(a){return{require:["uibBtnRadio","ngModel"],controller:"UibButtonsController",controllerAs:"buttons",link:function(b,c,d,e){var f=e[0],g=e[1],h=a(d.uibUncheckable);c.find("input").css({display:"none"}),g.$render=function(){c.toggleClass(f.activeClass,angular.equals(g.$modelValue,b.$eval(d.uibBtnRadio)))},c.on(f.toggleEvent,function(){if(!d.disabled){var a=c.hasClass(f.activeClass);a&&!angular.isDefined(d.uncheckable)||b.$apply(function(){g.$setViewValue(a?null:b.$eval(d.uibBtnRadio)),g.$render()})}}),d.uibUncheckable&&b.$watch(h,function(a){d.$set("uncheckable",a?"":void 0)})}}}]).directive("uibBtnCheckbox",function(){return{require:["uibBtnCheckbox","ngModel"],controller:"UibButtonsController",controllerAs:"button",link:function(a,b,c,d){function e(){return g(c.btnCheckboxTrue,!0)}function f(){return g(c.btnCheckboxFalse,!1)}function g(b,c){return angular.isDefined(b)?a.$eval(b):c}var h=d[0],i=d[1];b.find("input").css({display:"none"}),i.$render=function(){b.toggleClass(h.activeClass,angular.equals(i.$modelValue,e()))},b.on(h.toggleEvent,function(){c.disabled||a.$apply(function(){i.$setViewValue(b.hasClass(h.activeClass)?f():e()),i.$render()})})}}}),angular.module("ui.bootstrap.carousel",[]).controller("UibCarouselController",["$scope","$element","$interval","$t
6979imeout","$animate",function(a,b,c,d,e){function f(a){for(var b=0;b<p.length;b++)p[b].slide.active=b===a}function g(c,d,g){if(!s){if(angular.extend(c,{direction:g}),angular.extend(p[r].slide||{},{direction:g}),e.enabled(b)&&!a.$currentTransition&&p[d].element&&o.slides.length>1){p[d].element.data(q,c.direction);var h=o.getCurrentIndex();angular.isNumber(h)&&p[h].element&&p[h].element.data(q,c.direction),a.$currentTransition=!0,e.on("addClass",p[d].element,function(b,c){"close"===c&&(a.$currentTransition=null,e.off("addClass",b))})}a.active=c.index,r=c.index,f(d),k()}}function h(a){for(var b=0;b<p.length;b++)if(p[b].slide===a)return b}function i(){m&&(c.cancel(m),m=null)}function j(b){b.length||(a.$currentTransition=null)}function k(){i();var b=+a.interval;!isNaN(b)&&b>0&&(m=c(l,b))}function l(){var b=+a.interval;n&&!isNaN(b)&&b>0&&p.length?a.next():a.pause()}var m,n,o=this,p=o.slides=a.slides=[],q="uib-slideDirection",r=a.active,s=!1;b.addClass("carousel"),o.addSlide=function(b,c){p.push({slide:b,element:c}),p.sort(function(a,b){return+a.slide.index-+b.slide.index}),(b.index===a.active||1===p.length&&!angular.isNumber(a.active))&&(a.$currentTransition&&(a.$currentTransition=null),r=b.index,a.active=b.index,f(r),o.select(p[h(b)]),1===p.length&&a.play())},o.getCurrentIndex=function(){for(var a=0;a<p.length;a++)if(p[a].slide.index===r)return a},o.next=a.next=function(){var b=(o.getCurrentIndex()+1)%p.length;return 0===b&&a.noWrap()?void a.pause():o.select(p[b],"next")},o.prev=a.prev=function(){var b=o.getCurrentIndex()-1<0?p.length-1:o.getCurrentIndex()-1;return a.noWrap()&&b===p.length-1?void a.pause():o.select(p[b],"prev")},o.removeSlide=function(b){var c=h(b);p.splice(c,1),p.length>0&&r===c?c>=p.length?(r=p.length-1,a.active=r,f(r),o.select(p[p.length-1])):(r=c,a.active=r,f(r),o.select(p[c])):r>c&&(r--,a.active=r),0===p.length&&(r=null,a.active=null)},o.select=a.select=function(b,c){var d=h(b.slide);void 0===c&&(c=d>o.getCurrentIndex()?"next":"prev"),b.slide.index===r||a.$currentTransition||g(b.slide,d,c)},a.indexOfSlide=function(a){return+a.slide.index},a.isActive=function(b){return a.active===b.slide.index},a.isPrevDisabled=function(){return 0===a.active&&a.noWrap()},a.isNextDisabled=function(){return a.active===p.length-1&&a.noWrap()},a.pause=function(){a.noPause||(n=!1,i())},a.play=function(){n||(n=!0,k())},b.on("mouseenter",a.pause),b.on("mouseleave",a.play),a.$on("$destroy",function(){s=!0,i()}),a.$watch("noTransition",function(a){e.enabled(b,!a)}),a.$watch("interval",k),a.$watchCollection("slides",j),a.$watch("active",function(a){if(angular.isNumber(a)&&r!==a){for(var b=0;b<p.length;b++)if(p[b].slide.index===a){a=b;break}var c=p[a];c&&(f(a),o.select(p[a]),r=a)}})}]).directive("uibCarousel",function(){return{transclude:!0,controller:"UibCarouselController",controllerAs:"carousel",restrict:"A",templateUrl:function(a,b){return b.templateUrl||"uib/template/carousel/carousel.html"},scope:{active:"=",interval:"=",noTransition:"=",noPause:"=",noWrap:"&"}}}).directive("uibSlide",["$animate",function(a){return{require:"^uibCarousel",restrict:"A",transclude:!0,templateUrl:function(a,b){return b.templateUrl||"uib/template/carousel/slide.html"},scope:{actual:"=?",index:"=?"},link:function(b,c,d,e){c.addClass("item"),e.addSlide(b,c),b.$on("$destroy",function(){e.removeSlide(b)}),b.$watch("active",function(b){a[b?"addClass":"removeClass"](c,"active")})}}}]).animation(".item",["$animateCss",function(a){function b(a,b,c){a.removeClass(b),c&&c()}var c="uib-slideDirection";return{beforeAddClass:function(d,e,f){if("active"===e){var g=!1,h=d.data(c),i="next"===h?"left":"right",j=b.bind(this,d,i+" "+h,f);return d.addClass(h),a(d,{addClass:i}).start().done(j),function(){g=!0}}f()},beforeRemoveClass:function(d,e,f){if("active"===e){var g=!1,h=d.data(c),i="next"===h?"left":"right",j=b.bind(this,d,i,f);return a(d,{addClass:i}).start().done(j),function(){g=!0}}f()}}}]),angular.module("ui.bootstrap.dateparser",[]).service("uibDateParser",["$log","$locale","dateFilter","orderByFilter","filterFilter",function(a,b,c,d,e){function f(a){return e(s,{key:a},!0)[0]}
vendor: 28,178 bytes, lines 6979-6980
6979function g(a){var b=[],c=a.split(""),e=a.indexOf("'");if(e>-1){var f=!1;a=a.split("");for(var g=e;g<a.length;g++)f?("'"===a[g]&&(g+1<a.length&&"'"===a[g+1]?(a[g+1]="$",c[g+1]=""):(c[g]="",f=!1)),a[g]="$"):"'"===a[g]&&(a[g]="$",c[g]="",f=!0);a=a.join("")}return angular.forEach(s,function(d){var e=a.indexOf(d.key);if(e>-1){a=a.split(""),c[e]="("+d.regex+")",a[e]="$";for(var f=e+1,g=e+d.key.length;g>f;f++)c[f]="",a[f]="$";a=a.join(""),b.push({index:e,key:d.key,apply:d.apply,matcher:d.regex})}}),{regex:new RegExp("^"+c.join("")+"$"),map:d(b,"index")}}function h(a){for(var b,c,d=[],e=0;e<a.length;)if(angular.isNumber(c)){if("'"===a.charAt(e))(e+1>=a.length||"'"!==a.charAt(e+1))&&(d.push(i(a,c,e)),c=null);else if(e===a.length)for(;c<a.length;)b=j(a,c),d.push(b),c=b.endIdx;e++}else"'"!==a.charAt(e)?(b=j(a,e),d.push(b.parser),e=b.endIdx):(c=e,e++);return d}function i(a,b,c){return function(){return a.substr(b+1,c-b-1)}}function j(a,b){for(var c=a.substr(b),d=0;d<s.length;d++)if(new RegExp("^"+s[d].key).test(c)){var e=s[d];return{endIdx:b+e.key.length,parser:e.formatter}}return{endIdx:b+1,parser:function(){return c.charAt(0)}}}function k(a,b,c){return 1>c?!1:1===b&&c>28?29===c&&(a%4===0&&a%100!==0||a%400===0):3===b||5===b||8===b||10===b?31>c:!0}function l(a){return parseInt(a,10)}function m(a,b){return a&&b?q(a,b):a}function n(a,b){return a&&b?q(a,b,!0):a}function o(a,b){a=a.replace(/:/g,"");var c=Date.parse("Jan 01, 1970 00:00:00 "+a)/6e4;return isNaN(c)?b:c}function p(a,b){return a=new Date(a.getTime()),a.setMinutes(a.getMinutes()+b),a}function q(a,b,c){c=c?-1:1;var d=a.getTimezoneOffset(),e=o(b,d);return p(a,c*(e-d))}var r,s,t=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;this.init=function(){r=b.id,this.parsers={},this.formatters={},s=[{key:"yyyy",regex:"\\d{4}",apply:function(a){this.year=+a},formatter:function(a){var b=new Date;return b.setFullYear(Math.abs(a.getFullYear())),c(b,"yyyy")}},{key:"yy",regex:"\\d{2}",apply:function(a){a=+a,this.year=69>a?a+2e3:a+1900},formatter:function(a){var b=new Date;return b.setFullYear(Math.abs(a.getFullYear())),c(b,"yy")}},{key:"y",regex:"\\d{1,4}",apply:function(a){this.year=+a},formatter:function(a){var b=new Date;return b.setFullYear(Math.abs(a.getFullYear())),c(b,"y")}},{key:"M!",regex:"0?[1-9]|1[0-2]",apply:function(a){this.month=a-1},formatter:function(a){var b=a.getMonth();return/^[0-9]$/.test(b)?c(a,"MM"):c(a,"M")}},{key:"MMMM",regex:b.DATETIME_FORMATS.MONTH.join("|"),apply:function(a){this.month=b.DATETIME_FORMATS.MONTH.indexOf(a)},formatter:function(a){return c(a,"MMMM")}},{key:"MMM",regex:b.DATETIME_FORMATS.SHORTMONTH.join("|"),apply:function(a){this.month=b.DATETIME_FORMATS.SHORTMONTH.indexOf(a)},formatter:function(a){return c(a,"MMM")}},{key:"MM",regex:"0[1-9]|1[0-2]",apply:function(a){this.month=a-1},formatter:function(a){return c(a,"MM")}},{key:"M",regex:"[1-9]|1[0-2]",apply:function(a){this.month=a-1},formatter:function(a){return c(a,"M")}},{key:"d!",regex:"[0-2]?[0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a},formatter:function(a){var b=a.getDate();return/^[1-9]$/.test(b)?c(a,"dd"):c(a,"d")}},{key:"dd",regex:"[0-2][0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a},formatter:function(a){return c(a,"dd")}},{key:"d",regex:"[1-2]?[0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a},formatter:function(a){return c(a,"d")}},{key:"EEEE",regex:b.DATETIME_FORMATS.DAY.join("|"),formatter:function(a){return c(a,"EEEE")}},{key:"EEE",regex:b.DATETIME_FORMATS.SHORTDAY.join("|"),formatter:function(a){return c(a,"EEE")}},{key:"HH",regex:"(?:0|1)[0-9]|2[0-3]",apply:function(a){this.hours=+a},formatter:function(a){return c(a,"HH")}},{key:"hh",regex:"0[0-9]|1[0-2]",apply:function(a){this.hours=+a},formatter:function(a){return c(a,"hh")}},{key:"H",regex:"1?[0-9]|2[0-3]",apply:function(a){this.hours=+a},formatter:function(a){return c(a,"H")}},{key:"h",regex:"[0-9]|1[0-2]",apply:function(a){this.hours=+a},formatter:function(a){return c(a,"h")}},{key:"mm",regex:"[0-5][0-9]",apply:function(a){this.minutes=+a},formatter:function(a){return c(a,"mm")}},{key:"m",regex:"[0-9]|[1-5][0-9]",apply:function(a){this.minutes=+a},formatter:function(a){return c(a,"m")}},{key:"sss",regex:"[0-9][0-9][0-9]",apply:function(a){this.milliseconds=+a},formatter:function(a){return c(a,"sss")}},{key:"ss",regex:"[0-5][0-9]",apply:function(a){this.seconds=+a},formatter:function(a){return c(a,"ss")}},{key:"s",regex:"[0-9]|[1-5][0-9]",apply:function(a){this.seconds=+a},formatter:function(a){return c(a,"s")}},{key:"a",regex:b.DATETIME_FORMATS.AMPMS.join("|"),apply:function(a){12===this.hours&&(this.hours=0),"PM"===a&&(this.hours+=12)},formatter:function(a){return c(a,"a")}},{key:"Z",regex:"[+-]\\d{4}",apply:function(a){var b=a.match(/([+-])(\d{2})(\d{2})/),c=b[1],d=b[2],e=b[3];this.hours+=l(c+d),this.minutes+=l(c+e)},formatter:function(a){return c(a,"Z")}},{key:"ww",regex:"[0-4][0-9]|5[0-3]",formatter:function(a){return c(a,"ww")}},{key:"w",regex:"[0-9]|[1-4][0-9]|5[0-3]",formatter:function(a){return c(a,"w")}},{key:"GGGG",regex:b.DATETIME_FORMATS.ERANAMES.join("|").replace(/\s/g,"\\s"),formatter:function(a){return c(a,"GGGG")}},{key:"GGG",regex:b.DATETIME_FORMATS.ERAS.join("|"),formatter:function(a){return c(a,"GGG")}},{key:"GG",regex:b.DATETIME_FORMATS.ERAS.join("|"),formatter:function(a){return c(a,"GG")}},{key:"G",regex:b.DATETIME_FORMATS.ERAS.join("|"),formatter:function(a){return c(a,"G")}}],angular.version.major>=1&&angular.version.minor>4&&s.push({key:"LLLL",regex:b.DATETIME_FORMATS.STANDALONEMONTH.join("|"),apply:function(a){this.month=b.DATETIME_FORMATS.STANDALONEMONTH.indexOf(a)},formatter:function(a){return c(a,"LLLL")}})},this.init(),this.getParser=function(a){var b=f(a);return b&&b.apply||null},this.overrideParser=function(a,b){var c=f(a);c&&angular.isFunction(b)&&(this.parsers={},c.apply=b)}.bind(this),this.filter=function(a,c){if(!angular.isDate(a)||isNaN(a)||!c)return"";c=b.DATETIME_FORMATS[c]||c,b.id!==r&&this.init(),this.formatters[c]||(this.formatters[c]=h(c));var d=this.formatters[c];return d.reduce(function(b,c){return b+c(a)},"")},this.parse=function(c,d,e){if(!angular.isString(c)||!d)return c;d=b.DATETIME_FORMATS[d]||d,d=d.replace(t,"\\$&"),b.id!==r&&this.init(),this.parsers[d]||(this.parsers[d]=g(d,"apply"));var f=this.parsers[d],h=f.regex,i=f.map,j=c.match(h),l=!1;if(j&&j.length){var m,n;angular.isDate(e)&&!isNaN(e.getTime())?m={year:e.getFullYear(),month:e.getMonth(),date:e.getDate(),hours:e.getHours(),minutes:e.getMinutes(),seconds:e.getSeconds(),milliseconds:e.getMilliseconds()}:(e&&a.warn("dateparser:","baseDate is not a valid date"),m={year:1900,month:0,date:1,hours:0,minutes:0,seconds:0,milliseconds:0});for(var o=1,p=j.length;p>o;o++){var q=i[o-1];"Z"===q.matcher&&(l=!0),q.apply&&q.apply.call(m,j[o])}var s=l?Date.prototype.setUTCFullYear:Date.prototype.setFullYear,u=l?Date.prototype.setUTCHours:Date.prototype.setHours;return k(m.year,m.month,m.date)&&(!angular.isDate(e)||isNaN(e.getTime())||l?(n=new Date(0),s.call(n,m.year,m.month,m.date),u.call(n,m.hours||0,m.minutes||0,m.seconds||0,m.milliseconds||0)):(n=new Date(e),s.call(n,m.year,m.month,m.date),u.call(n,m.hours,m.minutes,m.seconds,m.milliseconds))),n}},this.toTimezone=m,this.fromTimezone=n,this.timezoneToOffset=o,this.addDateMinutes=p,this.convertTimezoneToLocal=q}]),angular.module("ui.bootstrap.isClass",[]).directive("uibIsClass",["$animate",function(a){var b=/^\s*([\s\S]+?)\s+on\s+([\s\S]+?)\s*$/,c=/^\s*([\s\S]+?)\s+for\s+([\s\S]+?)\s*$/;return{restrict:"A",compile:function(d,e){function f(a,b,c){i.push(a),j.push({scope:a,element:b}),o.forEach(function(b,c){g(b,a)}),a.$on("$destroy",h)}function g(b,d){var e=b.match(c),f=d.$eval(e[1]),g=e[2],h=k[b];if(!h){var i=function(b){var c=null;j.some(function(a){var d=a.scope.$eval(m);return d===b?(c=a,!0):void 0}),h.lastActivated!==c&&(h.lastActivated&&a.removeClass(h.lastActivated.element,f),c&&a.addClass(c.element,f),h.lastActivated=c)};k[b]=h={lastActivated:null,scope:d,watchFn:i,compareWithExp:g,watcher:d.$watch(g,i)}}h.watchFn(d.$eval(g))}function h(a){var b=a.targetScope,c=i.indexOf(b);if(i.splice(c,1),j.splice(c,1),i.length){var d=i[0];angular.forEach(k,function(a){a.scope===b&&(a.watcher=d.$watch(a.compareWithExp,a.watchFn),a.scope=d)})}else k={}}var i=[],j=[],k={},l=e.uibIsClass.match(b),m=l[2],n=l[1],o=n.split(",");return f}}}]),angular.module("ui.bootstrap.datepicker",["ui.bootstrap.dateparser","ui.bootstrap.isClass"]).value("$datepickerSuppressError",!1).value("$datepickerLiteralWarning",!0).constant("uibDatepickerConfig",{datepickerMode:"day",formatDay:"dd",formatMonth:"MMMM",formatYear:"yyyy",formatDayHeader:"EEE",formatDayTitle:"MMMM yyyy",formatMonthTitle:"yyyy",maxDate:null,maxMode:"year",minDate:null,minMode:"day",monthColumns:3,ngModelOptions:{},shortcutPropagation:!1,showWeeks:!0,yearColumns:5,yearRows:4}).controller("UibDatepickerController",["$scope","$element","$attrs","$parse","$interpolate","$locale","$log","dateFilter","uibDatepickerConfig","$datepickerLiteralWarning","$datepickerSuppressError","uibDateParser",function(a,b,c,d,e,f,g,h,i,j,k,l){function m(b){a.datepickerMode=b,a.datepickerOptions.datepickerMode=b}function n(b){var c;if(angular.version.minor<6)c=b.$options||a.datepickerOptions.ngModelOptions||i.ngModelOptions||{},c.getOption=function(a){return c[a]};else{var d=b.$options.getOption("timezone")||(a.datepickerOptions.ngModelOptions?a.datepickerOptions.ngModelOptions.timezone:null)||(i.ngModelOptions?i.ngModelOptions.timezone:null);c=b.$options.createChild(i.ngModelOptions).createChild(a.datepickerOptions.ngModelOptions).createChild(b.$options).createChild({timezone:d})}return c}var o=this,p={$setViewValue:angular.noop},q={},r=[];b.addClass("uib-datepicker"),c.$set("role","application"),a.datepickerOptions||(a.datepickerOptions={}),this.modes=["day","month","year"],["customClass","dateDisabled","datepickerMode","formatDay","formatDayHeader","formatDayTitle","formatMonth","formatMonthTitle","formatYear","maxDate","maxMode","minDate","minMode","monthColumns","showWeeks","shortcutPropagation","startingDay","yearColumns","yearRows"].forEach(function(b){switch(b){case"customClass":case"dateDisabled":a[b]=a.datepickerOptions[b]||angular.noop;break;case"datepickerMode":a.datepickerMode=angular.isDefined(a.datepickerOptions.datepickerMode)?a.datepickerOptions.datepickerMode:i.datepickerMode;break;case"formatDay":case"formatDayHeader":case"formatDayTitle":case"formatMonth":case"formatMonthTitle":case"formatYear":o[b]=angular.isDefined(a.datepickerOptions[b])?e(a.datepickerOptions[b])(a.$parent):i[b];break;case"monthColumns":case"showWeeks":case"shortcutPropagation":case"yearColumns":case"yearRows":o[b]=angular.isDefined(a.datepickerOptions[b])?a.datepickerOptions[b]:i[b];break;case"startingDay":angular.isDefined(a.datepickerOptions.startingDay)?o.startingDay=a.datepickerOptions.startingDay:angular.isNumber(i.startingDay)?o.startingDay=i.startingDay:o.startingDay=(f.DATETIME_FORMATS.FIRSTDAYOFWEEK+8)%7;break;case"maxDate":case"minDate":a.$watch("datepickerOptions."+b,function(a){a?angular.isDate(a)?o[b]=l.fromTimezone(new Date(a),q.getOption("timezone")):(j&&g.warn("Literal date support has been deprecated, please switch to date object usage"),o[b]=new Date(h(a,"medium"))):o[b]=i[b]?l.fromTimezone(new Date(i[b]),q.getOption("timezone")):null,o.refreshView()});break;case"maxMode":case"minMode":a.datepickerOptions[b]?a.$watch(function(){return a.datepickerOptions[b]},function(c){o[b]=a[b]=angular.isDefined(c)?c:a.datepickerOptions[b],("minMode"===b&&o.modes.indexOf(a.datepickerOptions.datepickerMode)<o.modes.indexOf(o[b])||"maxMode"===b&&o.modes.indexOf(a.datepickerOptions.datepickerMode)>o.modes.indexOf(o[b]))&&(a.datepickerMode=o[b],a.datepickerOptions.datepickerMode=o[b])}):o[b]=a[b]=i[b]||null}}),a.uniqueId="datepicker-"+a.$id+"-"+Math.floor(1e4*Math.random()),a.disabled=angular.isDefined(c.disabled)||!1,angular.isDefined(c.ngDisabled)&&r.push(a.$parent.$watch(c.ngDisabled,function(b){a.disabled=b,o.refreshView()})),a.isActive=function(b){return 0===o.compare(b.date,o.activeDate)?(a.activeDateId=b.uid,!0):!1},this.init=function(b){p=b,q=n(p),a.datepickerOptions.initDate?(o.activeDate=l.fromTimezone(a.datepickerOptions.initDate,q.getOption("timezone"))||new Date,a.$watch("datepickerOptions.initDate",function(a){a&&(p.$isEmpty(p.$modelValue)||p.$invalid)&&(o.activeDate=l.fromTimezone(a,q.getOption("timezone")),o.refreshView())})):o.activeDate=new Date;var c=p.$modelValue?new Date(p.$modelValue):new Date;this.activeDate=isNaN(c)?l.fromTimezone(new Date,q.getOption("timezone")):l.fromTimezone(c,q.getOption("timezone")),p.$render=function(){o.render()}},this.render=function(){if(p.$viewValue){var a=new Date(p.$viewValue),b=!isNaN(a);b?this.activeDate=l.fromTimezone(a,q.getOption("timezone")):k||g.error('Datepicker directive: "ng-model" value must be a Date object')}this.refreshView()},this.refreshView=function(){if(this.element){a.selectedDt=null,this._refreshView(),a.activeDt&&(a.activeDateId=a.activeDt.uid);var b=p.$viewValue?new Date(p.$viewValue):null;b=l.fromTimezone(b,q.getOption("timezone")),p.$setValidity("dateDisabled",!b||this.element&&!this.isDisabled(b))}},this.createDateObject=function(b,c){var d=p.$viewValue?new Date(p.$viewValue):null;d=l.fromTimezone(d,q.getOption("timezone"));var e=new Date;e=l.fromTimezone(e,q.getOption("timezone"));var f=this.compare(b,e),g={date:b,label:l.filter(b,c),selected:d&&0===this.compare(b,d),disabled:this.isDisabled(b),past:0>f,current:0===f,future:f>0,customClass:this.customClass(b)||null};return d&&0===this.compare(b,d)&&(a.selectedDt=g),o.activeDate&&0===this.compare(g.date,o.activeDate)&&(a.activeDt=g),g},this.isDisabled=function(b){return a.disabled||this.minDate&&this.compare(b,this.minDate)<0||this.maxDate&&this.compare(b,this.maxDate)>0||a.dateDisabled&&a.dateDisabled({date:b,mode:a.datepickerMode})},this.customClass=function(b){return a.customClass({date:b,mode:a.datepickerMode})},this.split=function(a,b){for(var c=[];a.length>0;)c.push(a.splice(0,b));return c},a.select=function(b){if(a.datepickerMode===o.minMode){var c=p.$viewValue?l.fromTimezone(new Date(p.$viewValue),q.getOption("timezone")):new Date(0,0,0,0,0,0,0);c.setFullYear(b.getFullYear(),b.getMonth(),b.getDate()),c=l.toTimezone(c,q.getOption("timezone")),p.$setViewValue(c),p.$render()}else o.activeDate=b,m(o.modes[o.modes.indexOf(a.datepickerMode)-1]),a.$emit("uib:datepicker.mode");a.$broadcast("uib:datepicker.focus")},a.move=function(a){var b=o.activeDate.getFullYear()+a*(o.step.years||0),c=o.activeDate.getMonth()+a*(o.step.months||0);o.activeDate.setFullYear(b,c,1),o.refreshView()},a.toggleMode=function(b){b=b||1,a.datepickerMode===o.maxMode&&1===b||a.datepickerMode===o.minMode&&-1===b||(m(o.modes[o.modes.indexOf(a.datepickerMode)+b]),a.$emit("uib:datepicker.mode"))},a.keys={13:"enter",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down"};var s=function(){o.element[0].focus()};a.$on("uib:datepicker.focus",s),a.keydown=function(b){var c=a.keys[b.which];if(c&&!b.shiftKey&&!b.altKey&&!a.disabled)if(b.preventDefault(),o.shortcutPropagation||b.stopPropagation(),"enter"===c||"space"===c){if(o.isDisabled(o.activeDate))return;a.select(o.activeDate)}else!b.ctrlKey||"up"!==c&&"down"!==c?(o.handleKeyDown(c,b),o.refreshView()):a.toggleMode("up"===c?1:-1)},b.on("keydown",function(b){a.$apply(function(){a.keydown(b)})}),a.$on("$destroy",function(){for(;r.length;)r.shift()()})}]).controller("UibDaypickerController",["$scope","$element","dateFilter",function(a,b,c){function d(a,b){return 1!==b||a%4!==0||a%100===0&&a%400!==0?f[b]:29}function e(a){var b=new Date(a);b.setDate(b.getDate()+4-(b.getDay()||7));var c=b.getTime();return b.setMonth(0),b.setDate(1),Math.floor(Math.round((c-b)/864e5)/7)+1}var f=[31,28,31,30,31,30,31,31,30,31,30,31];this.step={months:1},this.element=b,this.init=function(b){angular.extend(b,this),a.showWeeks=b.showWeeks,b.refreshView()},this.getDates=function(a,b){for(var c,d=new Array(b),e=new Date(a),f=0;b>f;)c=new Date(e),d[f++]=c,e.setDate(e.getDate()+1);return d},this._refreshView=function(){var b=this.activeDate.getFullYear(),d=this.activeDate.getMonth(),f=new Date(this.activeDate);f.setFullYear(b,d,1);var g=this.startingDay-f.getDay(),h=g>0?7-g:-g,i=new Date(f);h>0&&i.setDate(-h+1);for(var j=this.getDates(i,42),k=0;42>k;k++)j[k]=angular.extend(this.createDateObject(j[k],this.formatDay),{secondary:j[k].getMonth()!==d,uid:a.uniqueId+"-"+k});a.labels=new Array(7);for(var l=0;7>l;l++)a.labels[l]={abbr:c(j[l].date,this.formatDayHeader),full:c(j[l].date,"EEEE")};if(a.title=c(this.activeDate,this.formatDayTitle),a.rows=this.split(j,7),a.showWeeks){a.weekNumbers=[];for(var m=(11-this.startingDay)%7,n=a.rows.length,o=0;n>o;o++)a.weekNumbers.push(e(a.rows[o][m].date))}},this.compare=function(a,b){var c=new Date(a.getFullYear(),a.getMonth(),a.getDate()),d=new Date(b.getFullYear(),b.getMonth(),b.getDate());return c.setFullYear(a.getFullYear()),d.setFullYear(b.getFullYear()),c-d},this.handleKeyDown=function(a,b){var c=this.activeDate.getDate();if("left"===a)c-=1;else if("up"===a)c-=7;else if("right"===a)c+=1;else if("down"===a)c+=7;else if("pageup"===a||"pagedown"===a){var e=this.activeDate.getMonth()+("pageup"===a?-1:1);this.activeDate.setMonth(e,1),c=Math.min(d(this.activeDate.getFullYear(),this.activeDate.getMonth()),c)}else"home"===a?c=1:"end"===a&&(c=d(this.activeDate.getFullYear(),this.activeDate.getMonth()));this.activeDate.setDate(c)}}]).controller("UibMonthpickerController",["$scope","$element","dateFilter",function(a,b,c){this.step={years:1},this.element=b,this.init=function(a){angular.extend(a,this),a.refreshView()},this._refreshView=function(){for(var b,d=new Array(12),e=this.activeDate.getFullYear(),f=0;12>f;f++)b=new Date(this.activeDate),b.setFullYear(e,f,1),d[f]=angular.extend(this.createDateObject(b,this.formatMonth),{uid:a.uniqueId+"-"+f});a.title=c(this.activeDate,this.formatMonthTitle),a.rows=this.split(d,this.monthColumns),a.yearHeaderColspan=this.monthColumns>3?this.monthColumns-2:1},this.compare=function(a,b){var c=new Date(a.getFullYear(),a.getMonth()),d=new Date(b.getFullYear(),b.getMonth());return c.setFullYear(a.getFullYear()),d.setFullYear(b.getFullYear()),c-d},this.handleKeyDown=function(a,b){var c=this.activeDate.getMonth();if("left"===a)c-=1;else if("up"===a)c-=this.monthColumns;else if("right"===a)c+=1;else if("down"===a)c+=this.monthColumns;else if("pageup"===a||"pagedown"===a){var d=this.activeDate.getFullYear()+("pageup"===a?-1:1);this.activeDate.setFullYear(d)}else"home"===a?c=0:"end"===a&&(c=11);this.activeDate.setMonth(c)}}]).controller("UibYearpickerController",["$scope","$element","dateFilter",function(a,b,c){function d(a){return parseInt((a-1)/f,10)*f+1}var e,f;this.element=b,this.yearpickerInit=function(){e=this.yearColumns,f=this.yearRows*e,this.step={years:f}},this._refreshView=function(){for(var b,c=new Array(f),g=0,h=d(this.activeDate.getFullYear());f>g;g++)b=new Date(this.activeDate),b.setFullYear(h+g,0,1),c[g]=angular.extend(this.createDateObject(b,this.formatYear),{uid:a.uniqueId+"-"+g});a.title=[c[0].label,c[f-1].label].join(" - "),a.rows=this.split(c,e),a.columns=e},this.compare=function(a,b){return a.getFullYear()-b.getFullYear()},this.handleKeyDown=function(a,b){var c=this.activeDate.getFullYear();"left"===a?c-=1:"up"===a?c-=e:"right"===a?c+=1:"down"===a?c+=e:"pageup"===a||"pagedown"===a?c+=("pageup"===a?-1:1)*f:"home"===a?c=d(this.activeDate.getFullYear()):"end"===a&&(c=d(this.activeDate.getFullYear())+f-1),this.activeDate.setFullYear(c)}}]).directive("uibDatepicker",function(){return{templateUrl:function(a,b){return b.templateUrl||"uib/template/datepicker/datepicker.html"},scope:{datepickerOptions:"=?"},require:["uibDatepicker","^ngModel"],restrict:"A",controller:"UibDatepickerController",controllerAs:"datepicker",link:function(a,b,c,d){var e=d[0],f=d[1];e.init(f)}}}).directive("uibDaypicker",function(){return{templateUrl:function(a,b){return b.templateUrl||"uib/template/datepicker/day.html"},
6980require:["^uibDatepicker","uibDaypicker"],restrict:"A",controller:"UibDaypickerController",link:function(a,b,c,d){var e=d[0],f=d[1];f.init(e)}}}).directive("uibMonthpicker",function(){return{templateUrl:function(a,b){return b.templateUrl||"uib/template/datepicker/month.html"},require:["^uibDatepicker","uibMonthpicker"],restrict:"A",controller:"UibMonthpickerController",link:function(a,b,c,d){var e=d[0],f=d[1];f.init(e)}}}).directive("uibYearpicker",function(){return{templateUrl:function(a,b){return b.templateUrl||"uib/template/datepicker/year.html"},require:["^uibDatepicker","uibYearpicker"],restrict:"A",controller:"UibYearpickerController",link:function(a,b,c,d){var e=d[0];angular.extend(e,d[1]),e.yearpickerInit(),e.refreshView()}}}),angular.module("ui.bootstrap.position",[]).factory("$uibPosition",["$document","$window",function(a,b){var c,d,e={normal:/(auto|scroll)/,hidden:/(auto|scroll|hidden)/},f={auto:/\s?auto?\s?/i,primary:/^(top|bottom|left|right)$/,secondary:/^(top|bottom|left|right|center)$/,vertical:/^(top|bottom)$/},g=/(HTML|BODY)/;return{getRawNode:function(a){return a.nodeName?a:a[0]||a},parseStyle:function(a){return a=parseFloat(a),isFinite(a)?a:0},offsetParent:function(c){function d(a){return"static"===(b.getComputedStyle(a).position||"static")}c=this.getRawNode(c);for(var e=c.offsetParent||a[0].documentElement;e&&e!==a[0].documentElement&&d(e);)e=e.offsetParent;return e||a[0].documentElement},scrollbarWidth:function(e){if(e){if(angular.isUndefined(d)){var f=a.find("body");f.addClass("uib-position-body-scrollbar-measure"),d=b.innerWidth-f[0].clientWidth,d=isFinite(d)?d:0,f.removeClass("uib-position-body-scrollbar-measure")}return d}if(angular.isUndefined(c)){var g=angular.element('<div class="uib-position-scrollbar-measure"></div>');a.find("body").append(g),c=g[0].offsetWidth-g[0].clientWidth,c=isFinite(c)?c:0,g.remove()}return c},scrollbarPadding:function(a){a=this.getRawNode(a);var c=b.getComputedStyle(a),d=this.parseStyle(c.paddingRight),e=this.parseStyle(c.paddingBottom),f=this.scrollParent(a,!1,!0),h=this.scrollbarWidth(g.test(f.tagName));return{scrollbarWidth:h,widthOverflow:f.scrollWidth>f.clientWidth,right:d+h,originalRight:d,heightOverflow:f.scrollHeight>f.clientHeight,bottom:e+h,originalBottom:e}},isScrollable:function(a,c){a=this.getRawNode(a);var d=c?e.hidden:e.normal,f=b.getComputedStyle(a);return d.test(f.overflow+f.overflowY+f.overflowX)},scrollParent:function(c,d,f){c=this.getRawNode(c);var g=d?e.hidden:e.normal,h=a[0].documentElement,i=b.getComputedStyle(c);if(f&&g.test(i.overflow+i.overflowY+i.overflowX))return c;var j="absolute"===i.position,k=c.parentElement||h;if(k===h||"fixed"===i.position)return h;for(;k.parentElement&&k!==h;){var l=b.getComputedStyle(k);if(j&&"static"!==l.position&&(j=!1),!j&&g.test(l.overflow+l.overflowY+l.overflowX))break;k=k.parentElement}return k},position:function(c,d){c=this.getRawNode(c);var e=this.offset(c);if(d){var f=b.getComputedStyle(c);e.top-=this.parseStyle(f.marginTop),e.left-=this.parseStyle(f.marginLeft)}var g=this.offsetParent(c),h={top:0,left:0};return g!==a[0].documentElement&&(h=this.offset(g),h.top+=g.clientTop-g.scrollTop,h.left+=g.clientLeft-g.scrollLeft),{width:Math.round(angular.isNumber(e.width)?e.width:c.offsetWidth),height:Math.round(angular.isNumber(e.height)?e.height:c.offsetHeight),top:Math.round(e.top-h.top),left:Math.round(e.left-h.left)}},offset:function(c){c=this.getRawNode(c);var d=c.getBoundingClientRect();return{width:Math.round(angular.isNumber(d.width)?d.width:c.offsetWidth),height:Math.round(angular.isNumber(d.height)?d.height:c.offsetHeight),top:Math.round(d.top+(b.pageYOffset||a[0].documentElement.scrollTop)),left:Math.round(d.left+(b.pageXOffset||a[0].documentElement.scrollLeft))}},viewportOffset:function(c,d,e){c=this.getRawNode(c),e=e!==!1;var f=c.getBoundingClientRect(),g={top:0,left:0,bottom:0,right:0},h=d?a[0].documentElement:this.scrollParent(c),i=h.getBoundingClientRect();if(g.top=i.top+h.clientTop,g.left=i.left+h.clientLeft,h===a[0].documentElement&&(g.top+=b.pageYOffset,g.left+=b.pageXOffset),g.bottom=g.top+h.clientHeight,g.right=g.left+h.clientWidth,e){var j=b.getComputedStyle(h);g.top+=this.parseStyle(j.paddingTop),g.bottom-=this.parseStyle(j.paddingBottom),g.left+=this.parseStyle(j.paddingLeft),g.right-=this.parseStyle(j.paddingRight)}return{top:Math.round(f.top-g.top),bottom:Math.round(g.bottom-f.bottom),left:Math.round(f.left-g.left),right:Math.round(g.right-f.right)}},parsePlacement:function(a){var b=f.auto.test(a);return b&&(a=a.replace(f.auto,"")),a=a.split("-"),a[0]=a[0]||"top",f.primary.test(a[0])||(a[0]="top"),a[1]=a[1]||"center",f.secondary.test(a[1])||(a[1]="center"),b?a[2]=!0:a[2]=!1,a},positionElements:function(a,c,d,e){a=this.getRawNode(a),c=this.getRawNode(c);var g=angular.isDefined(c.offsetWidth)?c.offsetWidth:c.prop("offsetWidth"),h=angular.isDefined(c.offsetHeight)?c.offsetHeight:c.prop("offsetHeight");d=this.parsePlacement(d);var i=e?this.offset(a):this.position(a),j={top:0,left:0,placement:""};if(d[2]){var k=this.viewportOffset(a,e),l=b.getComputedStyle(c),m={width:g+Math.round(Math.abs(this.parseStyle(l.marginLeft)+this.parseStyle(l.marginRight))),height:h+Math.round(Math.abs(this.parseStyle(l.marginTop)+this.parseStyle(l.marginBottom)))};if(d[0]="top"===d[0]&&m.height>k.top&&m.height<=k.bottom?"bottom":"bottom"===d[0]&&m.height>k.bottom&&m.height<=k.top?"top":"left"===d[0]&&m.width>k.left&&m.width<=k.right?"right":"right"===d[0]&&m.width>k.right&&m.width<=k.left?"left":d[0],d[1]="top"===d[1]&&m.height-i.height>k.bottom&&m.height-i.height<=k.top?"bottom":"bottom"===d[1]&&m.height-i.height>k.top&&m.height-i.height<=k.bottom?"top":"left"===d[1]&&m.width-i.width>k.right&&m.width-i.width<=k.left?"right":"right"===d[1]&&m.width-i.width>k.left&&m.width-i.width<=k.right?"left":d[1],"center"===d[1])if(f.vertical.test(d[0])){var n=i.width/2-g/2;k.left+n<0&&m.width-i.width<=k.right?d[1]="left":k.right+n<0&&m.width-i.width<=k.left&&(d[1]="right")}else{var o=i.height/2-m.height/2;k.top+o<0&&m.height-i.height<=k.bottom?d[1]="top":k.bottom+o<0&&m.height-i.height<=k.top&&(d[1]="bottom")}}switch(d[0]){case"top":j.top=i.top-h;break;case"bottom":j.top=i.top+i.height;break;case"left":j.left=i.left-g;break;case"right":j.left=i.left+i.width}switch(d[1]){case"top":j.top=i.top;break;case"bottom":j.top=i.top+i.height-h;break;case"left":j.left=i.left;break;case"right":j.left=i.left+i.width-g;break;case"center":f.vertical.test(d[0])?j.left=i.left+i.width/2-g/2:j.top=i.top+i.height/2-h/2}return j.top=Math.round(j.top),j.left=Math.round(j.left),j.placement="center"===d[1]?d[0]:d[0]+"-"+d[1],j},adjustTop:function(a,b,c,d){return-1!==a.indexOf("top")&&c!==d?{top:b.top-d+"px"}:void 0},positionArrow:function(a,c){a=this.getRawNode(a);var d=a.querySelector(".tooltip-inner, .popover-inner");if(d){var e=angular.element(d).hasClass("tooltip-inner"),g=e?a.querySelector(".tooltip-arrow"):a.querySelector(".arrow");if(g){var h={top:"",bottom:"",left:"",right:""};if(c=this.parsePlacement(c),"center"===c[1])return void angular.element(g).css(h);var i="border-"+c[0]+"-width",j=b.getComputedStyle(g)[i],k="border-";k+=f.vertical.test(c[0])?c[0]+"-"+c[1]:c[1]+"-"+c[0],k+="-radius";var l=b.getComputedStyle(e?d:a)[k];switch(c[0]){case"top":h.bottom=e?"0":"-"+j;break;case"bottom":h.top=e?"0":"-"+j;break;case"left":h.right=e?"0":"-"+j;break;case"right":h.left=e?"0":"-"+j}h[c[1]]=l,angular.element(g).css(h)}}}}}]),angular.module("ui.bootstrap.datepickerPopup",["ui.bootstrap.datepicker","ui.bootstrap.position"]).value("$datepickerPopupLiteralWarning",!0).constant("uibDatepickerPopupConfig",{altInputFormats:[],appendToBody:!1,clearText:"Clear",closeOnDateSelection:!0,closeText:"Done",currentText:"Today",datepickerPopup:"yyyy-MM-dd",datepickerPopupTemplateUrl:"uib/template/datepickerPopup/popup.html",datepickerTemplateUrl:"uib/template/datepicker/datepicker.html",html5Types:{date:"yyyy-MM-dd","datetime-local":"yyyy-MM-ddTHH:mm:ss.sss",month:"yyyy-MM"},onOpenFocus:!0,showButtonBar:!0,placement:"auto bottom-left"}).controller("UibDatepickerPopupController",["$scope","$element","$attrs","$compile","$log","$parse","$
6980window","$document","$rootScope","$uibPosition","dateFilter","uibDateParser","uibDatepickerPopupConfig","$timeout","uibDatepickerConfig","$datepickerPopupLiteralWarning",function(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p){function q(b){var c=l.parse(b,x,a.date);if(isNaN(c))for(var d=0;d<J.length;d++)if(c=l.parse(b,J[d],a.date),!isNaN(c))return c;return c}function r(a){if(angular.isNumber(a)&&(a=new Date(a)),!a)return null;if(angular.isDate(a)&&!isNaN(a))return a;if(angular.isString(a)){var b=q(a);if(!isNaN(b))return l.toTimezone(b,H.getOption("timezone"))}return H.getOption("allowInvalid")?a:void 0}function s(a,b){var d=a||b;return c.ngRequired||d?(angular.isNumber(d)&&(d=new Date(d)),d?angular.isDate(d)&&!isNaN(d)?!0:angular.isString(d)?!isNaN(q(d)):!1:!0):!0}function t(c){if(a.isOpen||!a.disabled){var d=I[0],e=b[0].contains(c.target),f=void 0!==d.contains&&d.contains(c.target);!a.isOpen||e||f||a.$apply(function(){a.isOpen=!1})}}function u(c){27===c.which&&a.isOpen?(c.preventDefault(),c.stopPropagation(),a.$apply(function(){a.isOpen=!1}),b[0].focus()):40!==c.which||a.isOpen||(c.preventDefault(),c.stopPropagation(),a.$apply(function(){a.isOpen=!0}))}function v(){if(a.isOpen){var d=angular.element(I[0].querySelector(".uib-datepicker-popup")),e=c.popupPlacement?c.popupPlacement:m.placement,f=j.positionElements(b,d,e,z);d.css({top:f.top+"px",left:f.left+"px"}),d.hasClass("uib-position-measure")&&d.removeClass("uib-position-measure")}}function w(a){var b;return angular.version.minor<6?(b=angular.isObject(a.$options)?a.$options:{timezone:null},b.getOption=function(a){return b[a]}):b=a.$options,b}var x,y,z,A,B,C,D,E,F,G,H,I,J,K=!1,L=[];this.init=function(e){if(G=e,H=w(G),y=angular.isDefined(c.closeOnDateSelection)?a.$parent.$eval(c.closeOnDateSelection):m.closeOnDateSelection,z=angular.isDefined(c.datepickerAppendToBody)?a.$parent.$eval(c.datepickerAppendToBody):m.appendToBody,A=angular.isDefined(c.onOpenFocus)?a.$parent.$eval(c.onOpenFocus):m.onOpenFocus,B=angular.isDefined(c.datepickerPopupTemplateUrl)?c.datepickerPopupTemplateUrl:m.datepickerPopupTemplateUrl,C=angular.isDefined(c.datepickerTemplateUrl)?c.datepickerTemplateUrl:m.datepickerTemplateUrl,J=angular.isDefined(c.altInputFormats)?a.$parent.$eval(c.altInputFormats):m.altInputFormats,a.showButtonBar=angular.isDefined(c.showButtonBar)?a.$parent.$eval(c.showButtonBar):m.showButtonBar,m.html5Types[c.type]?(x=m.html5Types[c.type],K=!0):(x=c.uibDatepickerPopup||m.datepickerPopup,c.$observe("uibDatepickerPopup",function(a,b){var c=a||m.datepickerPopup;if(c!==x&&(x=c,G.$modelValue=null,!x))throw new Error("uibDatepickerPopup must have a date format specified.")})),!x)throw new Error("uibDatepickerPopup must have a date format specified.");if(K&&c.uibDatepickerPopup)throw new Error("HTML5 date input types do not support custom formats.");D=angular.element("<div uib-datepicker-popup-wrap><div uib-datepicker></div></div>"),D.attr({"ng-model":"date","ng-change":"dateSelection(date)","template-url":B}),E=angular.element(D.children()[0]),E.attr("template-url",C),a.datepickerOptions||(a.datepickerOptions={}),K&&"month"===c.type&&(a.datepickerOptions.datepickerMode="month",a.datepickerOptions.minMode="month"),E.attr("datepicker-options","datepickerOptions"),K?G.$formatters.push(function(b){return a.date=l.fromTimezone(b,H.getOption("timezone")),b}):(G.$$parserName="date",G.$validators.date=s,G.$parsers.unshift(r),G.$formatters.push(function(b){return G.$isEmpty(b)?(a.date=b,b):(angular.isNumber(b)&&(b=new Date(b)),a.date=l.fromTimezone(b,H.getOption("timezone")),l.filter(a.date,x))})),G.$viewChangeListeners.push(function(){a.date=q(G.$viewValue)}),b.on("keydown",u),I=d(D)(a),D.remove(),z?h.find("body").append(I):b.after(I),a.$on("$destroy",function(){for(a.isOpen===!0&&(i.$$phase||a.$apply(function(){a.isOpen=!1})),I.remove(),b.off("keydown",u),h.off("click",t),F&&F.off("scroll",v),angular.element(g).off("resize",v);L.length;)L.shift()()})},a.getText=function(b){return a[b+"Text"]||m[b+"Text"]},a.isDisabled=function(b){"today"===b&&(b=l.fromTimezone(new Date,H.getOption("timezone")));var c={};return angular.forEach(["minDate","maxDate"],function(b){a.datepickerOptions[b]?angular.isDate(a.datepickerOptions[b])?c[b]=new Date(a.datepickerOptions[b]):(p&&e.warn("Literal date support has been deprecated, please switch to date object usage"),c[b]=new Date(k(a.datepickerOptions[b],"medium"))):c[b]=null}),a.datepickerOptions&&c.minDate&&a.compare(b,c.minDate)<0||c.maxDate&&a.compare(b,c.maxDate)>0},a.compare=function(a,b){return new Date(a.getFullYear(),a.getMonth(),a.getDate())-new Date(b.getFullYear(),b.getMonth(),b.getDate())},a.dateSelection=function(c){a.date=c;var d=a.date?l.filter(a.date,x):null;b.val(d),G.$setViewValue(d),y&&(a.isOpen=!1,b[0].focus())},a.keydown=function(c){27===c.which&&(c.stopPropagation(),a.isOpen=!1,b[0].focus())},a.select=function(b,c){if(c.stopPropagation(),"today"===b){var d=new Date;angular.isDate(a.date)?(b=new Date(a.date),b.setFullYear(d.getFullYear(),d.getMonth(),d.getDate())):(b=l.fromTimezone(d,H.getOption("timezone")),b.setHours(0,0,0,0))}a.dateSelection(b)},a.close=function(c){c.stopPropagation(),a.isOpen=!1,b[0].focus()},a.disabled=angular.isDefined(c.disabled)||!1,c.ngDisabled&&L.push(a.$parent.$watch(f(c.ngDisabled),function(b){a.disabled=b})),a.$watch("isOpen",function(d){d?a.disabled?a.isOpen=!1:n(function(){v(),A&&a.$broadcast("uib:datepicker.focus"),h.on("click",t);var d=c.popupPlacement?c.popupPlacement:m.placement;z||j.parsePlacement(d)[2]?(F=F||angular.element(j.scrollParent(b)),F&&F.on("scroll",v)):F=null,angular.element(g).on("resize",v)},0,!1):(h.off("click",t),F&&F.off("scroll",v),angular.element(g).off("resize",v))}),a.$on("uib:datepicker.mode",function(){n(v,0,!1)})}]).directive("uibDatepickerPopup",function(){return{require:["ngModel","uibDatepickerPopup"],controller:"UibDatepickerPopupController",scope:{datepickerOptions:"=?",isOpen:"=?",currentText:"@",clearText:"@",closeText:"@"},link:function(a,b,c,d){var e=d[0],f=d[1];f.init(e)}}}).directive("uibDatepickerPopupWrap",function(){return{restrict:"A",transclude:!0,templateUrl:function(a,b){return b.templateUrl||"uib/template/datepickerPopup/popup.html"}}}),angular.module("ui.bootstrap.debounce",[]).factory("$$debounce",["$timeout",function(a){return function(b,c){var d;return function(){var e=this,f=Array.prototype.slice.call(arguments);d&&a.cancel(d),d=a(function(){b.apply(e,f)},c)}}}]),angular.module("ui.bootstrap.multiMap",[]).factory("$$multiMap",function(){return{createNew:function(){var a={};return{entries:function(){return Object.keys(a).map(function(b){return{key:b,value:a[b]}})},get:function(b){return a[b]},hasKey:function(b){return!!a[b]},keys:function(){return Object.keys(a)},put:function(b,c){a[b]||(a[b]=[]),a[b].push(c)},remove:function(b,c){var d=a[b];if(d){var e=d.indexOf(c);-1!==e&&d.splice(e,1),d.length||delete a[b]}}}}}}),angular.module("ui.bootstrap.dropdown",["ui.bootstrap.multiMap","ui.bootstrap.position"]).constant("uibDropdownConfig",{appendToOpenClass:"uib-dropdown-open",openClass:"open"}).service("uibDropdownService",["$document","$rootScope","$$multiMap",function(a,b,c){var d=null,e=c.createNew();this.isOnlyOpen=function(a,b){var c=e.get(b);if(c){var d=c.reduce(function(b,c){return c.scope===a?c:b},{});if(d)return 1===c.length}return!1},this.open=function(b,c,g){if(d||a.on("click",f),d&&d!==b&&(d.isOpen=!1),d=b,g){var h=e.get(g);if(h){var i=h.map(function(a){return a.scope});-1===i.indexOf(b)&&e.put(g,{scope:b})}else e.put(g,{scope:b})}},this.close=function(b,c,g){if(d===b&&(a.off("click",f),a.off("keydown",this.keybindFilter),d=null),g){var h=e.get(g);if(h){var i=h.reduce(function(a,c){return c.scope===b?c:a},{});i&&e.remove(g,i)}}};
vendor: 9,649 bytes, line 6980
6980var f=function(a){if(d&&d.isOpen&&!(a&&"disabled"===d.getAutoClose()||a&&3===a.which)){var c=d.getToggleElement();if(!(a&&c&&c[0].contains(a.target))){var e=d.getDropdownElement();a&&"outsideClick"===d.getAutoClose()&&e&&e[0].contains(a.target)||(d.focusToggleElement(),d.isOpen=!1,b.$$phase||d.$apply())}}};this.keybindFilter=function(a){if(d){var b=d.getDropdownElement(),c=d.getToggleElement(),e=b&&b[0].contains(a.target),g=c&&c[0].contains(a.target);27===a.which?(a.stopPropagation(),d.focusToggleElement(),f()):d.isKeynavEnabled()&&-1!==[38,40].indexOf(a.which)&&d.isOpen&&(e||g)&&(a.preventDefault(),a.stopPropagation(),d.focusDropdownEntry(a.which))}}}]).controller("UibDropdownController",["$scope","$element","$attrs","$parse","uibDropdownConfig","uibDropdownService","$animate","$uibPosition","$document","$compile","$templateRequest",function(a,b,c,d,e,f,g,h,i,j,k){function l(){b.append(o.dropdownMenu)}var m,n,o=this,p=a.$new(),q=e.appendToOpenClass,r=e.openClass,s=angular.noop,t=c.onToggle?d(c.onToggle):angular.noop,u=!1,v=i.find("body");b.addClass("dropdown"),this.init=function(){c.isOpen&&(n=d(c.isOpen),s=n.assign,a.$watch(n,function(a){p.isOpen=!!a})),u=angular.isDefined(c.keyboardNav)},this.toggle=function(a){return p.isOpen=arguments.length?!!a:!p.isOpen,angular.isFunction(s)&&s(p,p.isOpen),p.isOpen},this.isOpen=function(){return p.isOpen},p.getToggleElement=function(){return o.toggleElement},p.getAutoClose=function(){return c.autoClose||"always"},p.getElement=function(){return b},p.isKeynavEnabled=function(){return u},p.focusDropdownEntry=function(a){var c=o.dropdownMenu?angular.element(o.dropdownMenu).find("a"):b.find("ul").eq(0).find("a");switch(a){case 40:angular.isNumber(o.selectedOption)?o.selectedOption=o.selectedOption===c.length-1?o.selectedOption:o.selectedOption+1:o.selectedOption=0;break;case 38:angular.isNumber(o.selectedOption)?o.selectedOption=0===o.selectedOption?0:o.selectedOption-1:o.selectedOption=c.length-1}c[o.selectedOption].focus()},p.getDropdownElement=function(){return o.dropdownMenu},p.focusToggleElement=function(){o.toggleElement&&o.toggleElement[0].focus()},p.$watch("isOpen",function(e,n){var u=null,w=!1;if(angular.isDefined(c.dropdownAppendTo)){var x=d(c.dropdownAppendTo)(p);x&&(u=angular.element(x))}if(angular.isDefined(c.dropdownAppendToBody)){var y=d(c.dropdownAppendToBody)(p);y!==!1&&(w=!0)}if(w&&!u&&(u=v),u&&o.dropdownMenu&&(e?(u.append(o.dropdownMenu),b.on("$destroy",l)):(b.off("$destroy",l),l())),u&&o.dropdownMenu){var z,A,B,C=h.positionElements(b,o.dropdownMenu,"bottom-left",!0),D=0;if(z={top:C.top+"px",display:e?"block":"none"},A=o.dropdownMenu.hasClass("dropdown-menu-right"),A?(z.left="auto",B=h.scrollbarPadding(u),B.heightOverflow&&B.scrollbarWidth&&(D=B.scrollbarWidth),z.right=window.innerWidth-D-(C.left+b.prop("offsetWidth"))+"px"):(z.left=C.left+"px",z.right="auto"),!w){var E=h.offset(u);z.top=C.top-E.top+"px",A?z.right=window.innerWidth-(C.left-E.left+b.prop("offsetWidth"))+"px":z.left=C.left-E.left+"px"}o.dropdownMenu.css(z)}var F=u?u:b,G=u?q:r,H=F.hasClass(G),I=f.isOnlyOpen(a,u);if(H===!e){var J;J=u?I?"removeClass":"addClass":e?"addClass":"removeClass",g[J](F,G).then(function(){angular.isDefined(e)&&e!==n&&t(a,{open:!!e})})}if(e)o.dropdownMenuTemplateUrl?k(o.dropdownMenuTemplateUrl).then(function(a){m=p.$new(),j(a.trim())(m,function(a){var b=a;o.dropdownMenu.replaceWith(b),o.dropdownMenu=b,i.on("keydown",f.keybindFilter)})}):i.on("keydown",f.keybindFilter),p.focusToggleElement(),f.open(p,b,u);else{if(f.close(p,b,u),o.dropdownMenuTemplateUrl){m&&m.$destroy();var K=angular.element('<ul class="dropdown-menu"></ul>');o.dropdownMenu.replaceWith(K),o.dropdownMenu=K}o.selectedOption=null}angular.isFunction(s)&&s(a,e)})}]).directive("uibDropdown",function(){return{controller:"UibDropdownController",link:function(a,b,c,d){d.init()}}}).directive("uibDropdownMenu",function(){return{restrict:"A",require:"?^uibDropdown",link:function(a,b,c,d){if(d&&!angular.isDefined(c.dropdownNested)){b.addClass("dropdown-menu");var e=c.templateUrl;e&&(d.dropdownMenuTemplateUrl=e),d.dropdownMenu||(d.dropdownMenu=b)}}}}).directive("uibDropdownToggle",function(){return{require:"?^uibDropdown",link:function(a,b,c,d){if(d){b.addClass("dropdown-toggle"),d.toggleElement=b;var e=function(e){e.preventDefault(),b.hasClass("disabled")||c.disabled||a.$apply(function(){d.toggle()})};b.on("click",e),b.attr({"aria-haspopup":!0,"aria-expanded":!1}),a.$watch(d.isOpen,function(a){b.attr("aria-expanded",!!a)}),a.$on("$destroy",function(){b.off("click",e)})}}}}),angular.module("ui.bootstrap.stackedMap",[]).factory("$$stackedMap",function(){return{createNew:function(){var a=[];return{add:function(b,c){a.push({key:b,value:c})},get:function(b){for(var c=0;c<a.length;c++)if(b===a[c].key)return a[c]},keys:function(){for(var b=[],c=0;c<a.length;c++)b.push(a[c].key);return b},top:function(){return a[a.length-1]},remove:function(b){for(var c=-1,d=0;d<a.length;d++)if(b===a[d].key){c=d;break}return a.splice(c,1)[0]},removeTop:function(){return a.pop()},length:function(){return a.length}}}}}),angular.module("ui.bootstrap.modal",["ui.bootstrap.multiMap","ui.bootstrap.stackedMap","ui.bootstrap.position"]).provider("$uibResolve",function(){var a=this;this.resolver=null,this.setResolver=function(a){this.resolver=a},this.$get=["$injector","$q",function(b,c){var d=a.resolver?b.get(a.resolver):null;return{resolve:function(a,e,f,g){if(d)return d.resolve(a,e,f,g);var h=[];return angular.forEach(a,function(a){angular.isFunction(a)||angular.isArray(a)?h.push(c.resolve(b.invoke(a))):angular.isString(a)?h.push(c.resolve(b.get(a))):h.push(c.resolve(a))}),c.all(h).then(function(b){var c={},d=0;return angular.forEach(a,function(a,e){c[e]=b[d++]}),c})}}}]}).directive("uibModalBackdrop",["$animate","$injector","$uibModalStack",function(a,b,c){function d(b,d,e){e.modalInClass&&(a.addClass(d,e.modalInClass),b.$on(c.NOW_CLOSING_EVENT,function(c,f){var g=f();b.modalOptions.animation?a.removeClass(d,e.modalInClass).then(g):g()}))}return{restrict:"A",compile:function(a,b){return a.addClass(b.backdropClass),d}}}]).directive("uibModalWindow",["$uibModalStack","$q","$animateCss","$document",function(a,b,c,d){return{scope:{index:"@"},restrict:"A",transclude:!0,templateUrl:function(a,b){return b.templateUrl||"uib/template/modal/window.html"},link:function(e,f,g){f.addClass(g.windowTopClass||""),e.size=g.size,e.close=function(b){var c=a.getTop();c&&c.value.backdrop&&"static"!==c.value.backdrop&&b.target===b.currentTarget&&(b.preventDefault(),b.stopPropagation(),a.dismiss(c.key,"backdrop click"))},f.on("click",e.close),e.$isRendered=!0;var h=b.defer();e.$$postDigest(function(){h.resolve()}),h.promise.then(function(){var h=null;g.modalInClass&&(h=c(f,{addClass:g.modalInClass}).start(),e.$on(a.NOW_CLOSING_EVENT,function(a,b){var d=b();c(f,{removeClass:g.modalInClass}).start().then(d)})),b.when(h).then(function(){var b=a.getTop();if(b&&a.modalRendered(b.key),!d[0].activeElement||!f[0].contains(d[0].activeElement)){var c=f[0].querySelector("[autofocus]");c?c.focus():f[0].focus()}})})}}}]).directive("uibModalAnimationClass",function(){return{compile:function(a,b){b.modalAnimation&&a.addClass(b.uibModalAnimationClass)}}}).directive("uibModalTransclude",["$animate",function(a){return{link:function(b,c,d,e,f){f(b.$parent,function(b){c.empty(),a.enter(b,c)})}}}]).factory("$uibModalStack",["$animate","$animateCss","$document","$compile","$rootScope","$q","$$multiMap","$$stackedMap","$uibPosition",function(a,b,c,d,e,f,g,h,i){function j(a){var b="-";return a.replace(E,function(a,c){return(c?b:"")+a.toLowerCase()})}function k(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)}function l(){for(var a=-1,b=x.keys(),c=0;c<b.length;c++)x.get(b[c]).value.backdrop&&(a=c);return a>-1&&A>a&&(a=A),a}function m(a,b){var c=x.get(a).value,d=c.appendTo;x.remove(a),B=x.top(),B&&(A=parseInt(B.value.modalDomEl.attr("index"),10)),p(c.modalDomEl,c.modalScope,function(){var b=c.openedClass||w;y.remove(b,a);var e=y.hasKey(b);d.toggleClass(b,e),!e&&v&&v.heightOverflow&&v.scrollbarWidth&&(v.originalRight?d.css({paddingRight:v.originalRight+"px"}):d.css({paddingRight:""}),v=null),n(!0)},c.closedDeferred),o(),b&&b.focus?b.focus():d.focus&&d.focus()}function n(a){var b;x.length()>0&&(b=x.top().value,b.modalDomEl.toggleClass(b.windowTopClass||"",a))}function o(){if(t&&-1===l()){var a=u;p(t,u,function(){a=null}),t=void 0,u=void 0}}function p(b,c,d,e){function g(){g.done||(g.done=!0,a.leave(b).then(function(){d&&d(),b.remove(),e&&e.resolve()}),c.$destroy())}var h,i=null,j=function(){return h||(h=f.defer(),i=h.promise),function(){h.resolve()}};return c.$broadcast(z.NOW_CLOSING_EVENT,j),f.when(i).then(g)}function q(a){if(a.isDefaultPrevented())return a;var b=x.top();if(b)switch(a.which){case 27:b.value.keyboard&&(a.preventDefault(),e.$apply(function(){z.dismiss(b.key,"escape key press")}));break;case 9:var c=z.loadFocusElementList(b),d=!1;a.shiftKey?(z.isFocusInFirstItem(a,c)||z.isModalFocused(a,b))&&(d=z.focusLastFocusableElement(c)):z.isFocusInLastItem(a,c)&&(d=z.focusFirstFocusableElement(c)),d&&(a.preventDefault(),a.stopPropagation())}}function r(a,b,c){return!a.value.modalScope.$broadcast("modal.closing",b,c).defaultPrevented}function s(){Array.prototype.forEach.call(document.querySelectorAll("["+C+"]"),function(a){var b=parseInt(a.getAttribute(C),10),c=b-1;a.setAttribute(C,c),c||(a.removeAttribute(C),a.removeAttribute("aria-hidden"))})}var t,u,v,w="modal-open",x=h.createNew(),y=g.createNew(),z={NOW_CLOSING_EVENT:"modal.stack.now-closing"},A=0,B=null,C="data-bootstrap-modal-aria-hidden-count",D="a[href], area[href], input
6980:not([disabled]):not([tabindex='-1']), button:not([disabled]):not([tabindex='-1']),select:not([disabled]):not([tabindex='-1']), textarea:not([disabled]):not([tabindex='-1']), iframe, object, embed, *[tabindex]:not([tabindex='-1']), *[contenteditable=true]",E=/[A-Z]/g;return e.$watch(l,function(a){u&&(u.index=a)}),c.on("keydown",q),e.$on("$destroy",function(){c.off("keydown",q)}),z.open=function(b,f){function g(a){function b(a){var b=a.parent()?a.parent().children():[];return Array.prototype.filter.call(b,function(b){return b!==a[0]})}if(a&&"BODY"!==a[0].tagName)return b(a).forEach(function(a){var b="true"===a.getAttribute("aria-hidden"),c=parseInt(a.getAttribute(C),10);c||(c=b?1:0),a.setAttribute(C,c+1),a.setAttribute("aria-hidden","true")}),g(a.parent())}var h=c[0].activeElement,k=f.openedClass||w;n(!1),B=x.top(),x.add(b,{deferred:f.deferred,renderDeferred:f.renderDeferred,closedDeferred:f.closedDeferred,modalScope:f.scope,backdrop:f.backdrop,keyboard:f.keyboard,openedClass:f.openedClass,windowTopClass:f.windowTopClass,animation:f.animation,appendTo:f.appendTo}),y.put(k,b);var m=f.appendTo,o=l();o>=0&&!t&&(u=e.$new(!0),u.modalOptions=f,u.index=o,t=angular.element('<div uib-modal-backdrop="modal-backdrop"></div>'),t.attr({"class":"modal-backdrop","ng-style":"{'z-index': 1040 + (index && 1 || 0) + index*10}","uib-modal-animation-class":"fade","modal-in-class":"in"}),f.backdropClass&&t.addClass(f.backdropClass),f.animation&&t.attr("modal-animation","true"),d(t)(u),a.enter(t,m),i.isScrollable(m)&&(v=i.scrollbarPadding(m),v.heightOverflow&&v.scrollbarWidth&&m.css({paddingRight:v.right+"px"})));var p;f.component?(p=document.createElement(j(f.component.name)),p=angular.element(p),p.attr({resolve:"$resolve","modal-instance":"$uibModalInstance",close:"$close($value)",dismiss:"$dismiss($value)"})):p=f.content,A=B?parseInt(B.value.modalDomEl.attr("index"),10)+1:0;var q=angular.element('<div uib-modal-window="modal-window"></div>');q.attr({"class":"modal","template-url":f.windowTemplateUrl,"window-top-class":f.windowTopClass,role:"dialog","aria-labelledby":f.ariaLabelledBy,"aria-describedby":f.ariaDescribedBy,size:f.size,index:A,animate:"animate","ng-style":"{'z-index': 1050 + $$topModalIndex*10, display: 'block'}",tabindex:-1,"uib-modal-animation-class":"fade","modal-in-class":"in"}).append(p),f.windowClass&&q.addClass(f.windowClass),f.animation&&q.attr("modal-animation","true"),m.addClass(k),f.scope&&(f.scope.$$topModalIndex=A),a.enter(d(q)(f.scope),m),x.top().value.modalDomEl=q,x.top().value.modalOpener=h,g(q)},z.close=function(a,b){var c=x.get(a);return s(),c&&r(c,b,!0)?(c.value.modalScope.$$uibDestructionScheduled=!0,c.value.deferred.resolve(b),m(a,c.value.modalOpener),!0):!c},z.dismiss=function(a,b){var c=x.get(a);return s(),c&&r(c,b,!1)?(c.value.modalScope.$$uibDestructionScheduled=!0,c.value.deferred.reject(b),m(a,c.value.modalOpener),!0):!c},z.dismissAll=function(a){for(var b=this.getTop();b&&this.dismiss(b.key,a);)b=this.getTop()},z.getTop=function(){return x.top()},z.modalRendered=function(a){var b=x.get(a);b&&b.value.renderDeferred.resolve()},z.focusFirstFocusableElement=function(a){return a.length>0?(a[0].focus(),!0):!1},z.focusLastFocusableElement=function(a){return a.length>0?(a[a.length-1].focus(),!0):!1},z.isModalFocused=function(a,b){if(a&&b){var c=b.value.modalDomEl;if(c&&c.length)return(a.target||a.srcElement)===c[0]}return!1},z.isFocusInFirstItem=function(a,b){return b.length>0?(a.target||a.srcElement)===b[0]:!1},z.isFocusInLastItem=function(a,b){return b.length>0?(a.target||a.srcElement)===b[b.length-1]:!1},z.loadFocusElementList=function(a){if(a){var b=a.value.modalDomEl;if(b&&b.length){var c=b[0].querySelectorAll(D);return c?Array.prototype.filter.call(c,function(a){return k(a)}):c}}},z}]).provider("$uibModal",function(){var a={options:{animation:!0,backdrop:!0,keyboard:!0},$get:["$rootScope","$q","$document","$templateRequest","$controller","$uibResolve","$uibModalStack",function(b,c,d,e,f,g,h){function i(a){return a.template?c.when(a.template):e(angular.isFunction(a.templateUrl)?a.templateUrl():a.templateUrl)}var j={},k=null;return j.getPromiseChain=function(){return k},j.open=function(e){function j(){return q}var l=c.defer(),m=c.defer(),n=c.defer(),o=c.defer(),p={result:l.promise,opened:m.promise,closed:n.promise,rendered:o.promise,close:function(a){return h.close(p,a)}
vendor: 9,408 bytes, lines 6980-6981
6980,dismiss:function(a){return h.dismiss(p,a)}};if(e=angular.extend({},a.options,e),e.resolve=e.resolve||{},e.appendTo=e.appendTo||d.find("body").eq(0),!e.appendTo.length)throw new Error("appendTo element not found. Make sure that the element passed is in DOM.");if(!e.component&&!e.template&&!e.templateUrl)throw new Error("One of component or template or templateUrl options is required.");var q;q=e.component?c.when(g.resolve(e.resolve,{},null,null)):c.all([i(e),g.resolve(e.resolve,{},null,null)]);var r;return r=k=c.all([k]).then(j,j).then(function(a){function c(b,c,d,e){b.$scope=g,b.$scope.$resolve={},d?b.$scope.$uibModalInstance=p:b.$uibModalInstance=p;var f=c?a[1]:a;angular.forEach(f,function(a,c){e&&(b[c]=a),b.$scope.$resolve[c]=a})}var d=e.scope||b,g=d.$new();g.$close=p.close,g.$dismiss=p.dismiss,g.$on("$destroy",function(){g.$$uibDestructionScheduled||g.$dismiss("$uibUnscheduledDestruction")});var i,j,k={scope:g,deferred:l,renderDeferred:o,closedDeferred:n,animation:e.animation,backdrop:e.backdrop,keyboard:e.keyboard,backdropClass:e.backdropClass,windowTopClass:e.windowTopClass,windowClass:e.windowClass,windowTemplateUrl:e.windowTemplateUrl,ariaLabelledBy:e.ariaLabelledBy,ariaDescribedBy:e.ariaDescribedBy,size:e.size,openedClass:e.openedClass,appendTo:e.appendTo},q={},r={};e.component?(c(q,!1,!0,!1),q.name=e.component,k.component=q):e.controller&&(c(r,!0,!1,!0),j=f(e.controller,r,!0,e.controllerAs),e.controllerAs&&e.bindToController&&(i=j.instance,i.$close=g.$close,i.$dismiss=g.$dismiss,angular.extend(i,{$resolve:r.$scope.$resolve},d)),i=j(),angular.isFunction(i.$onInit)&&i.$onInit()),e.component||(k.content=a[0]),h.open(p,k),m.resolve(!0)},function(a){m.reject(a),l.reject(a)})["finally"](function(){k===r&&(k=null)}),p},j}]};return a}),angular.module("ui.bootstrap.paging",[]).factory("uibPaging",["$parse",function(a){return{create:function(b,c,d){b.setNumPages=d.numPages?a(d.numPages).assign:angular.noop,b.ngModelCtrl={$setViewValue:angular.noop},b._watchers=[],b.init=function(a,e){b.ngModelCtrl=a,b.config=e,a.$render=function(){b.render()},d.itemsPerPage?b._watchers.push(c.$parent.$watch(d.itemsPerPage,function(a){
6981b.itemsPerPage=parseInt(a,10),c.totalPages=b.calculateTotalPages(),b.updatePage()})):b.itemsPerPage=e.itemsPerPage,c.$watch("totalItems",function(a,d){(angular.isDefined(a)||a!==d)&&(c.totalPages=b.calculateTotalPages(),b.updatePage())})},b.calculateTotalPages=function(){var a=b.itemsPerPage<1?1:Math.ceil(c.totalItems/b.itemsPerPage);return Math.max(a||0,1)},b.render=function(){c.page=parseInt(b.ngModelCtrl.$viewValue,10)||1},c.selectPage=function(a,d){d&&d.preventDefault();var e=!c.ngDisabled||!d;e&&c.page!==a&&a>0&&a<=c.totalPages&&(d&&d.target&&d.target.blur(),b.ngModelCtrl.$setViewValue(a),b.ngModelCtrl.$render())},c.getText=function(a){return c[a+"Text"]||b.config[a+"Text"]},c.noPrevious=function(){return 1===c.page},c.noNext=function(){return c.page===c.totalPages},b.updatePage=function(){b.setNumPages(c.$parent,c.totalPages),c.page>c.totalPages?c.selectPage(c.totalPages):b.ngModelCtrl.$render()},c.$on("$destroy",function(){for(;b._watchers.length;)b._watchers.shift()()})}}}]),angular.module("ui.bootstrap.pager",["ui.bootstrap.paging","ui.bootstrap.tabindex"]).controller("UibPagerController",["$scope","$attrs","uibPaging","uibPagerConfig",function(a,b,c,d){a.align=angular.isDefined(b.align)?a.$parent.$eval(b.align):d.align,c.create(this,a,b)}]).constant("uibPagerConfig",{itemsPerPage:10,previousText:"« Previous",nextText:"Next »",align:!0}).directive("uibPager",["uibPagerConfig",function(a){return{scope:{totalItems:"=",previousText:"@",nextText:"@",ngDisabled:"="},require:["uibPager","?ngModel"],restrict:"A",controller:"UibPagerController",controllerAs:"pager",templateUrl:function(a,b){return b.templateUrl||"uib/template/pager/pager.html"},link:function(b,c,d,e){c.addClass("pager");var f=e[0],g=e[1];g&&f.init(g,a)}}}]),angular.module("ui.bootstrap.pagination",["ui.bootstrap.paging","ui.bootstrap.tabindex"]).controller("UibPaginationController",["$scope","$attrs","$parse","uibPaging","uibPaginationConfig",function(a,b,c,d,e){function f(a,b,c){return{number:a,text:b,active:c}}function g(a,b){var c=[],d=1,e=b,g=angular.isDefined(i)&&b>i;g&&(j?(d=Math.max(a-Math.floor(i/2),1),e=d+i-1,e>b&&(e=b,d=e-i+1)):(d=(Math.ceil(a/i)-1)*i+1,e=Math.min(d+i-1,b)));for(var h=d;e>=h;h++){var n=f(h,m(h),h===a);c.push(n)}if(g&&i>0&&(!j||k||l)){if(d>1){if(!l||d>3){var o=f(d-1,"...",!1);c.unshift(o)}if(l){if(3===d){var p=f(2,"2",!1);c.unshift(p)}var q=f(1,"1",!1);c.unshift(q)}}if(b>e){if(!l||b-2>e){var r=f(e+1,"...",!1);c.push(r)}if(l){if(e===b-2){var s=f(b-1,b-1,!1);c.push(s)}var t=f(b,b,!1);c.push(t)}}}return c}var h=this,i=angular.isDefined(b.maxSize)?a.$parent.$eval(b.maxSize):e.maxSize,j=angular.isDefined(b.rotate)?a.$parent.$eval(b.rotate):e.rotate,k=angular.isDefined(b.forceEllipses)?a.$parent.$eval(b.forceEllipses):e.forceEllipses,l=angular.isDefined(b.boundaryLinkNumbers)?a.$parent.$eval(b.boundaryLinkNumbers):e.boundaryLinkNumbers,m=angular.isDefined(b.pageLabel)?function(c){return a.$parent.$eval(b.pageLabel,{$page:c})}:angular.identity;a.boundaryLinks=angular.isDefined(b.boundaryLinks)?a.$parent.$eval(b.boundaryLinks):e.boundaryLinks,a.directionLinks=angular.isDefined(b.directionLinks)?a.$parent.$eval(b.directionLinks):e.directionLinks,b.$set("role","menu"),d.create(this,a,b),b.maxSize&&h._watchers.push(a.$parent.$watch(c(b.maxSize),function(a){i=parseInt(a,10),h.render()}));var n=this.render;this.render=function(){n(),a.page>0&&a.page<=a.totalPages&&(a.pages=g(a.page,a.totalPages))}}]).constant("uibPaginationConfig",{itemsPerPage:10,boundaryLinks:!1,boundaryLinkNumbers:!1,directionLinks:!0,firstText:"First",previousText:"Previous",nextText:"Next",lastText:"Last",rotate:!0,forceEllipses:!1}).directive("uibPagination",["$parse","uibPaginationConfig",function(a,b){return{scope:{totalItems:"=",firstText:"@",previousText:"@",nextText:"@",lastText:"@",ngDisabled:"="},require:["uibPagination","?ngModel"],restrict:"A",controller:"UibPaginationController",controllerAs:"pagination",templateUrl:function(a,b){return b.templateUrl||"uib/template/pagination/pagination.html"},link:function(a,c,d,e){c.addClass("pagination");var f=e[0],g=e[1];g&&f.init(g,b)}}}]),angular.module("ui.bootstrap.tooltip",["ui.bootstrap.position","ui.bootstrap.stackedMap"]).provider("$uibTooltip",function(){function a(a){var b=/[A-Z]/g,c="-";return a.replace(b,function(a,b){return(b?c:"")+a.toLowerCase()})}var b={placement:"top",placementClassPrefix:"",animation:!0,popupDelay:0,popupCloseDelay:0,useContentExp:!1},c={mouseenter:"mouseleave",click:"click",outsideClick:"outsideClick",focus:"blur",none:""},d={};this.options=function(a){angular.extend(d,a)},this.setTriggers=function(a){angular.extend(c,a)},this.$get=["$window","$compile","$timeout","$document","$uibPosition","$interpolate","$rootScope","$parse","$$stackedMap",function(e,f,g,h,i,j,k,l,m){function n(a){if(27===a.which){var b=o.top();b&&(b.value.close(),b=null)}}var o=m.createNew();return h.on("keyup",n),k.$on("$destroy",function(){h.off("keyup",n)}),function(e,k,m,n){function p(a){var b=(a||n.trigger||m).split(" "),d=b.map(function(a){return c[a]||a});return{show:b,hide:d}}n=angular.extend({},b,d,n);var q=a(e),r=j.startSymbol(),s=j.endSymbol(),t="<div "+q+'-popup uib-title="'+r+"title"+s+'" '+(n.useContentExp?'content-exp="contentExp()" ':'content="'+r+"content"+s+'" ')+'origin-scope="origScope" class="uib-position-measure '+k+'" tooltip-animation-class="fade"uib-tooltip-classes ng-class="{ in: isOpen }" ></div>';return{compile:function(a,b){var c=f(t);return function(a,b,d,f){function j(){P.isOpen?q():m()}function m(){O&&!a.$eval(d[k+"Enable"])||(u(),x(),P.popupDelay?H||(H=g(r,P.popupDelay,!1)):r())}function q(){s(),P.popupCloseDelay?I||(I=g(t,P.popupCloseDelay,!1)):t()}function r(){return s(),u(),P.content?(v(),void P.$evalAsync(function(){P.isOpen=!0,y(!0),U()})):angular.noop}function s(){H&&(g.cancel(H),H=null),J&&(g.cancel(J),J=null)}function t(){P&&P.$evalAsync(function(){P&&(P.isOpen=!1,y(!1),P.animation?G||(G=g(w,150,!1)):w())})}function u(){I&&(g.cancel(I),I=null),G&&(g.cancel(G),G=null)}function v(){E||(F=P.$new(),E=c(F,function(a){M?h.find("body").append(a):b.after(a)}),o.add(P,{close:t}),z())}function w(){s(),u(),A(),E&&(E.remove(),E=null,K&&g.cancel(K)),o.remove(P),F&&(F.$destroy(),F=null)}function x(){P.title=d[k+"Title"],S?P.content=S(a):P.content=d[e],P.popupClass=d[k+"Class"],P.placement=angular.isDefined(d[k+"Placement"])?d[k+"Placement"]:n.placement;var b=i.parsePlacement(P.placement);L=b[1]?b[0]+"-"+b[1]:b[0];var c=parseInt(d[k+"PopupDelay"],10),f=parseInt(d[k+"PopupCloseDelay"],10);P.popupDelay=isNaN(c)?n.popupDelay:c,P.popupCloseDelay=isNaN(f)?n.popupCloseDelay:f}function y(b){R&&angular.isFunction(R.assign)&&R.assign(a,b)}function z(){T.length=0,S?(T.push(a.$watch(S,function(a){P.content=a,!a&&P.isOpen&&t()})),T.push(F.$watch(function(){Q||(Q=!0,F.$$postDigest(function(){Q=!1,P&&P.isOpen&&U()}))}))):T.push(d.$observe(e,function(a){P.content=a,!a&&P.isOpen?t():U()})),T.push(d.$observe(k+"Title",function(a){P.title=a,P.isOpen&&U()})),T.push(d.$observe(k+"Placement",function(a){P.placement=a?a:n.placement,P.isOpen&&U()}))}function A(){T.length&&(angular.forEach(T,function(a){a()}),T.length=0)}function B(a){P&&P.isOpen&&E&&(b[0].contains(a.target)||E[0].contains(a.target)||q())}
vendor: 5,491 bytes, line 6981
6981function C(a){27===a.which&&q()}function D(){var c=[],e=[],f=a.$eval(d[k+"Trigger"]);V(),angular.isObject(f)?(Object.keys(f).forEach(function(a){c.push(a),e.push(f[a])}),N={show:c,hide:e}):N=p(f),"none"!==N.show&&N.show.forEach(function(a,c){"outsideClick"===a?(b.on("click",j),h.on("click",B)):a===N.hide[c]?b.on(a,j):a&&(b.on(a,m),b.on(N.hide[c],q)),b.on("keypress",C)})}var E,F,G,H,I,J,K,L,M=angular.isDefined(n.appendToBody)?n.appendToBody:!1,N=p(void 0),O=angular.isDefined(d[k+"Enable"]),P=a.$new(!0),Q=!1,R=angular.isDefined(d[k+"IsOpen"])?l(d[k+"IsOpen"]):!1,S=n.useContentExp?l(d[e]):!1,T=[],U=function(){E&&E.html()&&(J||(J=g(function(){var a=i.positionElements(b,E,P.placement,M),c=angular.isDefined(E.offsetHeight)?E.offsetHeight:E.prop("offsetHeight"),d=M?i.offset(b):i.position(b);E.css({top:a.top+"px",left:a.left+"px"});var e=a.placement.split("-");E.hasClass(e[0])||(E.removeClass(L.split("-")[0]),E.addClass(e[0])),E.hasClass(n.placementClassPrefix+a.placement)||(E.removeClass(n.placementClassPrefix+L),E.addClass(n.placementClassPrefix+a.placement)),K=g(function(){var a=angular.isDefined(E.offsetHeight)?E.offsetHeight:E.prop("offsetHeight"),b=i.adjustTop(e,d,c,a);b&&E.css(b),K=null},0,!1),E.hasClass("uib-position-measure")?(i.positionArrow(E,a.placement),E.removeClass("uib-position-measure")):L!==a.placement&&i.positionArrow(E,a.placement),L=a.placement,J=null},0,!1)))};P.origScope=a,P.isOpen=!1,P.contentExp=function(){return P.content},d.$observe("disabled",function(a){a&&s(),a&&P.isOpen&&t()}),R&&a.$watch(R,function(a){P&&!a===P.isOpen&&j()});var V=function(){N.show.forEach(function(a){"outsideClick"===a?b.off("click",j):(b.off(a,m),b.off(a,j)),b.off("keypress",C)}),N.hide.forEach(function(a){"outsideClick"===a?h.off("click",B):b.off(a,q)})};D();var W=a.$eval(d[k+"Animation"]);P.animation=angular.isDefined(W)?!!W:n.animation;var X,Y=k+"AppendToBody";X=Y in d&&void 0===d[Y]?!0:a.$eval(d[Y]),M=angular.isDefined(X)?X:M,a.$on("$destroy",function(){V(),w(),P=null})}}}}}]}).directive("uibTooltipTemplateTransclude",["$animate","$sce","$compile","$templateRequest",function(a,b,c,d){return{link:function(e,f,g){var h,i,j,k=e.$eval(g.tooltipTemplateTranscludeScope),l=0,m=function(){i&&(i.remove(),i=null),h&&(h.$destroy(),h=null),j&&(a.leave(j).then(function(){i=null}),i=j,j=null)};e.$watch(b.parseAsResourceUrl(g.uibTooltipTemplateTransclude),function(b){var g=++l;b?(d(b,!0).then(function(d){if(g===l){var e=k.$new(),i=d,n=c(i)(e,function(b){m(),a.enter(b,f)});h=e,j=n,h.$emit("$includeContentLoaded",b)}},function(){g===l&&(m(),e.$emit("$includeContentError",b))}),e.$emit("$includeContentRequested",b)):m()}),e.$on("$destroy",m)}}}]).directive("uibTooltipClasses",["$uibPosition",function(a){return{restrict:"A",link:function(b,c,d){if(b.placement){var e=a.parsePlacement(b.placement);c.addClass(e[0])}b.popupClass&&c.addClass(b.popupClass),b.animation&&c.addClass(d.tooltipAnimationClass)}}}]).directive("uibTooltipPopup",function(){return{restrict:"A",scope:{content:"@"},templateUrl:"uib/template/tooltip/tooltip-popup.html"}}).directive("uibTooltip",["$uibTooltip",function(a){return a("uibTooltip","tooltip","mouseenter")}]).directive("uibTooltipTemplatePopup",function(){return{restrict:"A",scope:{contentExp:"&",originScope:"&"},templateUrl:"uib/template/tooltip/tooltip-template-popup.html"}}).directive("uibTooltipTemplate",["$uibTooltip",function(a){return a("uibTooltipTemplate","tooltip","mouseenter",{useContentExp:!0})}]).directive("uibTooltipHtmlPopup",function(){return{restrict:"A",scope:{contentExp:"&"},templateUrl:"uib/template/tooltip/tooltip-html-popup.html"}}).directive("uibTooltipHtml",["$uibTooltip",function(a){return a("uibTooltipHtml","tooltip","mouseenter",{useContentExp:!0})}]),angular.module("ui.bootstrap.popover",["ui.bootstrap.tooltip"]).directive("uibPopoverTemplatePopup",function(){return{restrict:"A",scope:{uibTitle:"@",contentExp:"&",originScope:"&"},templateUrl:"uib/template/popover/popover-template.html"}}).directive("uibPopoverTemplate",["$uibTooltip",function(a){return a("uibPopoverTemplate","popover","click",{useContentExp:!0})}]).directive("uibPopoverHtmlPopup",function(){return{restrict:"A",scope:{contentExp:"&",uibTitle:"@"},templateUrl:"uib/template/popover/popover-html.html"}}).directive("uibPopoverHtml",["$uibTooltip",function(a){return a("uibPopoverHtml","popover","click",{useContentExp:!0})}]).directive("uibPopoverPopup",function(){return{restrict:"A",scope:{uibTitle:"@",content:"@"},templateUrl:"uib/template/popover/popover.html"}}).directive("uibPopover",["$uibTooltip",function(a){return a("uibPopover","popover","click")}]),angular.module("ui.bootstrap.progressbar",[]).constant("uibProgressConfig",{animate:!0,max:100}).controller("UibProgressController",["$scope","$attrs","uibProgressConfig",function(a,b,c){function d(){return angular.isDefined(a.maxParam)?a.maxParam:c.max}var e=this,f=angular.isDefined(b.animate)?a.$parent.$eval(b.animate):c.animate;this.bars=[],a.max=d(),this.addBar=function(a,b,c){f||b.css({transition:"none"}),this.bars.push(a),a.max=d(),a.title=c&&angular.isDefined(c.title)?c.title:"progressbar",a.$watch("value",function(b){a.recalculatePercentage()}),a.recalculatePercentage=function(){var b=e.bars.reduce(function(a,b){return b.percent=+(100*b.value/b.max).toFixed(2),a+b.percent},0);b>100&&(a.percent-=b-100)},a.$on("$destroy",function(){b=null,e.removeBar(a)})},this.removeBar=function(a){this.bars.splice(this.bars.indexOf(a),1),this.bars.forEac
vendor: 17,149 bytes, line 6981
6981h(function(a){a.recalculatePercentage()})},a.$watch("maxParam",function(a){e.bars.forEach(function(a){a.max=d(),a.recalculatePercentage()})})}]).directive("uibProgress",function(){return{replace:!0,transclude:!0,controller:"UibProgressController",require:"uibProgress",scope:{maxParam:"=?max"},templateUrl:"uib/template/progressbar/progress.html"}}).directive("uibBar",function(){return{replace:!0,transclude:!0,require:"^uibProgress",scope:{value:"=",type:"@"},templateUrl:"uib/template/progressbar/bar.html",link:function(a,b,c,d){d.addBar(a,b,c)}}}).directive("uibProgressbar",function(){return{replace:!0,transclude:!0,controller:"UibProgressController",scope:{value:"=",maxParam:"=?max",type:"@"},templateUrl:"uib/template/progressbar/progressbar.html",link:function(a,b,c,d){d.addBar(a,angular.element(b.children()[0]),{title:c.title})}}}),angular.module("ui.bootstrap.rating",[]).constant("uibRatingConfig",{max:5,stateOn:null,stateOff:null,enableReset:!0,titles:["one","two","three","four","five"]}).controller("UibRatingController",["$scope","$attrs","uibRatingConfig",function(a,b,c){var d={$setViewValue:angular.noop},e=this;this.init=function(e){d=e,d.$render=this.render,d.$formatters.push(function(a){return angular.isNumber(a)&&a<<0!==a&&(a=Math.round(a)),a}),this.stateOn=angular.isDefined(b.stateOn)?a.$parent.$eval(b.stateOn):c.stateOn,this.stateOff=angular.isDefined(b.stateOff)?a.$parent.$eval(b.stateOff):c.stateOff,this.enableReset=angular.isDefined(b.enableReset)?a.$parent.$eval(b.enableReset):c.enableReset;var f=angular.isDefined(b.titles)?a.$parent.$eval(b.titles):c.titles;this.titles=angular.isArray(f)&&f.length>0?f:c.titles;var g=angular.isDefined(b.ratingStates)?a.$parent.$eval(b.ratingStates):new Array(angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max);a.range=this.buildTemplateObjects(g)},this.buildTemplateObjects=function(a){for(var b=0,c=a.length;c>b;b++)a[b]=angular.extend({index:b},{stateOn:this.stateOn,stateOff:this.stateOff,title:this.getTitle(b)},a[b]);return a},this.getTitle=function(a){return a>=this.titles.length?a+1:this.titles[a]},a.rate=function(b){if(!a.readonly&&b>=0&&b<=a.range.length){var c=e.enableReset&&d.$viewValue===b?0:b;d.$setViewValue(c),d.$render()}},a.enter=function(b){a.readonly||(a.value=b),a.onHover({value:b})},a.reset=function(){a.value=d.$viewValue,a.onLeave()},a.onKeydown=function(b){/(37|38|39|40)/.test(b.which)&&(b.preventDefault(),b.stopPropagation(),a.rate(a.value+(38===b.which||39===b.which?1:-1)))},this.render=function(){a.value=d.$viewValue,a.title=e.getTitle(a.value-1)}}]).directive("uibRating",function(){return{require:["uibRating","ngModel"],restrict:"A",scope:{readonly:"=?readOnly",onHover:"&",onLeave:"&"},controller:"UibRatingController",templateUrl:"uib/template/rating/rating.html",link:function(a,b,c,d){var e=d[0],f=d[1];e.init(f)}}}),angular.module("ui.bootstrap.tabs",[]).controller("UibTabsetController",["$scope",function(a){function b(a){for(var b=0;b<d.tabs.length;b++)if(d.tabs[b].index===a)return b}var c,d=this;d.tabs=[],d.select=function(a,f){if(!e){var g=b(c),h=d.tabs[g];if(h){if(h.tab.onDeselect({$event:f,$selectedIndex:a}),f&&f.isDefaultPrevented())return;h.tab.active=!1}var i=d.tabs[a];i?(i.tab.onSelect({$event:f}),i.tab.active=!0,d.active=i.index,c=i.index):!i&&angular.isDefined(c)&&(d.active=null,c=null)}},d.addTab=function(a){if(d.tabs.push({tab:a,index:a.index}),d.tabs.sort(function(a,b){return a.index>b.index?1:a.index<b.index?-1:0}),a.index===d.active||!angular.isDefined(d.active)&&1===d.tabs.length){var c=b(a.index);d.select(c)}},d.removeTab=function(a){for(var b,c=0;c<d.tabs.length;c++)if(d.tabs[c].tab===a){b=c;break}if(d.tabs[b].index===d.active){var e=b===d.tabs.length-1?b-1:b+1%d.tabs.length;d.select(e)}d.tabs.splice(b,1)},a.$watch("tabset.active",function(a){angular.isDefined(a)&&a!==c&&d.select(b(a))});var e;a.$on("$destroy",function(){e=!0})}]).directive("uibTabset",function(){return{transclude:!0,replace:!0,scope:{},bindToController:{active:"=?",type:"@"},controller:"UibTabsetController",controllerAs:"tabset",templateUrl:function(a,b){return b.templateUrl||"uib/template/tabs/tabset.html"},link:function(a,b,c){a.vertical=angular.isDefined(c.vertical)?a.$parent.$eval(c.vertical):!1,a.justified=angular.isDefined(c.justified)?a.$parent.$eval(c.justified):!1}}}).directive("uibTab",["$parse",function(a){return{require:"^uibTabset",replace:!0,templateUrl:function(a,b){return b.templateUrl||"uib/template/tabs/tab.html"},transclude:!0,scope:{heading:"@",index:"=?",classes:"@?",onSelect:"&select",onDeselect:"&deselect"},controller:function(){},controllerAs:"tab",link:function(b,c,d,e,f){b.disabled=!1,d.disable&&b.$parent.$watch(a(d.disable),function(a){b.disabled=!!a}),angular.isUndefined(d.index)&&(e.tabs&&e.tabs.length?b.index=Math.max.apply(null,e.tabs.map(function(a){return a.index}))+1:b.index=0),angular.isUndefined(d.classes)&&(b.classes=""),b.select=function(a){if(!b.disabled){for(var c,d=0;d<e.tabs.length;d++)if(e.tabs[d].tab===b){c=d;break}e.select(c,a)}},e.addTab(b),b.$on("$destroy",function(){e.removeTab(b)}),b.$transcludeFn=f}}}]).directive("uibTabHeadingTransclude",function(){return{restrict:"A",require:"^uibTab",link:function(a,b){a.$watch("headingElement",function(a){a&&(b.html(""),b.append(a))})}}}).directive("uibTabContentTransclude",function(){function a(a){return a.tagName&&(a.hasAttribute("uib-tab-heading")||a.hasAttribute("data-uib-tab-heading")||a.hasAttribute("x-uib-tab-heading")||"uib-tab-heading"===a.tagName.toLowerCase()||"data-uib-tab-heading"===a.tagName.toLowerCase()||"x-uib-tab-heading"===a.tagName.toLowerCase()||"uib:tab-heading"===a.tagName.toLowerCase())}return{restrict:"A",require:"^uibTabset",link:function(b,c,d){var e=b.$eval(d.uibTabContentTransclude).tab;e.$transcludeFn(e.$parent,function(b){angular.forEach(b,function(b){a(b)?e.headingElement=b:c.append(b)})})}}}),angular.module("ui.bootstrap.timepicker",[]).constant("uibTimepickerConfig",{hourStep:1,minuteStep:1,secondStep:1,showMeridian:!0,showSeconds:!1,meridians:null,readonlyInput:!1,mousewheel:!0,arrowkeys:!0,showSpinners:!0,templateUrl:"uib/template/timepicker/timepicker.html"}).controller("UibTimepickerController",["$scope","$element","$attrs","$parse","$log","$locale","uibTimepickerConfig",function(a,b,c,d,e,f,g){function h(){var b=+a.hours,c=a.showMeridian?b>0&&13>b:b>=0&&24>b;return c&&""!==a.hours?(a.showMeridian&&(12===b&&(b=0),a.meridian===y[1]&&(b+=12)),b):void 0}function i(){var b=+a.minutes,c=b>=0&&60>b;return c&&""!==a.minutes?b:void 0}function j(){var b=+a.seconds;return b>=0&&60>b?b:void 0}function k(a,b){return null===a?"":angular.isDefined(a)&&a.toString().length<2&&!b?"0"+a:a.toString()}function l(a){m(),x.$setViewValue(new Date(v)),n(a)}function m(){s&&s.$setValidity("hours",!0),t&&t.$setValidity("minutes",!0),u&&u.$setValidity("seconds",!0),x.$setValidity("time",!0),a.invalidHours=!1,a.invalidMinutes=!1,a.invalidSeconds=!1}function n(b){if(x.$modelValue){var c=v.getHours(),d=v.getMinutes(),e=v.getSeconds();a.showMeridian&&(c=0===c||12===c?12:c%12),a.hours="h"===b?c:k(c,!z),"m"!==b&&(a.minutes=k(d)),a.meridian=v.getHours()<12?y[0]:y[1],"s"!==b&&(a.seconds=k(e)),a.meridian=v.getHours()<12?y[0]:y[1]}else a.hours=null,a.minutes=null,a.seconds=null,a.meridian=y[0]}function o(a){v=q(v,a),l()}function p(a,b){return q(a,60*b)}function q(a,b){var c=new Date(a.getTime()+1e3*b),d=new Date(a);return d.setHours(c.getHours(),c.getMinutes(),c.getSeconds()),d}function r(){return(null===a.hours||""===a.hours)&&(null===a.minutes||""===a.minutes)&&(!a.showSeconds||a.showSeconds&&(null===a.seconds||""===a.seconds))}var s,t,u,v=new Date,w=[],x={$setViewValue:angular.noop},y=angular.isDefined(c.meridians)?a.$parent.$eval(c.meridians):g.meridians||f.DATETIME_FORMATS.AMPMS,z=angular.isDefined(c.padHours)?a.$parent.$eval(c.padHours):!0;a.tabindex=angular.isDefined(c.tabindex)?c.tabindex:0,b.removeAttr("tabindex"),this.init=function(b,d){x=b,x.$render=this.render,x.$formatters.unshift(function(a){return a?new Date(a):null});var e=d.eq(0),f=d.eq(1),h=d.eq(2);s=e.controller("ngModel"),t=f.controller("ngModel"),u=h.controller("ngModel");var i=angular.isDefined(c.mousewheel)?a.$parent.$eval(c.mousewheel):g.mousewheel;i&&this.setupMousewheelEvents(e,f,h);var j=angular.isDefined(c.arrowkeys)?a.$parent.$eval(c.arrowkeys):g.arrowkeys;j&&this.setupArrowkeyEvents(e,f,h),a.readonlyInput=angular.isDefined(c.readonlyInput)?a.$parent.$eval(c.readonlyInput):g.readonlyInput,this.setupInputEvents(e,f,h)};var A=g.hourStep;c.hourStep&&w.push(a.$parent.$watch(d(c.hourStep),function(a){A=+a}));var B=g.minuteStep;c.minuteStep&&w.push(a.$parent.$watch(d(c.minuteStep),function(a){B=+a}));var C;w.push(a.$parent.$watch(d(c.min),function(a){var b=new Date(a);C=isNaN(b)?void 0:b}));var D;w.push(a.$parent.$watch(d(c.max),function(a){var b=new Date(a);D=isNaN(b)?void 0:b}));var E=!1;c.ngDisabled&&w.push(a.$parent.$watch(d(c.ngDisabled),function(a){E=a})),a.noIncrementHours=function(){var a=p(v,60*A);return E||a>D||v>a&&C>a},a.noDecrementHours=function(){var a=p(v,60*-A);return E||C>a||a>v&&a>D},a.noIncrementMinutes=function(){var a=p(v,B);return E||a>D||v>a&&C>a},a.noDecrementMinutes=function(){var a=p(v,-B);return E||C>a||a>v&&a>D},a.noIncrementSeconds=function(){var a=q(v,F);return E||a>D||v>a&&C>a},a.noDecrementSeconds=function(){var a=q(v,-F);return E||C>a||a>v&&a>D},a.noToggleMeridian=function(){return v.getHours()<12?E||p(v,720)>D:E||p(v,-720)<C};var F=g.secondStep;c.secondStep&&w.push(a.$parent.$watch(d(c.secondStep),function(a){F=+a})),a.showSeconds=g.showSeconds,c.showSeconds&&w.push(a.$parent.$watch(d(c.showSeconds),function(b){a.showSeconds=!!b})),a.showMeridian=g.showMeridian,c.showMeridian&&w.push(a.$parent.$watch(d(c.showMeridian),function(b){if(a.showMeridian=!!b,x.$error.time){var c=h(),d=i();angular.isDefined(c)&&angular.isDefined(d)&&(v.setHours(c),l())}else n()})),this.setupMousewheelEvents=function(b,c,d){var e=function(a){a.originalEvent&&(a=a.originalEvent);var b=a.wheelDelta?a.wheelDelta:-a.deltaY;return a.detail||b>0};b.on("mousewheel wheel",function(b){E||a.$apply(e(b)?a.incrementHours():a.decrementHours()),b.preventDefault()}),c.on("mousewheel wheel",function(b){E||a.$apply(e(b)?a.incrementMinutes():a.decrementMinutes()),b.preventDefault()}),d.on("mousewheel wheel",function(b){E||a.$apply(e(b)?a.incrementSeconds():a.decrementSeconds()),b.preventDefault()})},this.setupArrowkeyEvents=function(b,c,d){b.on("keydown",function(b){E||(38===b.which?(b.preventDefault(),a.incrementHours(),a.$apply()):40===b.which&&(b.preventDefault(),a.decrementHours(),a.$apply()))}),c.on("keydown",function(b){E||(38===b.which?(b.preventDefault(),a.incrementMinutes(),a.$apply()):40===b.which&&(b.preventDefault(),a.decrementMinutes(),a.$apply()))}),d.on("keydown",function(b){E||(38===b.which?(b.preventDefault(),a.incrementSeconds(),a.$apply()):40===b.which&&(b.preventDefault(),a.decrementSeconds(),a.$apply()))})},this.setupInputEvents=function(b,c,d){if(a.readonlyInput)return a.updateHours=angular.noop,a.updateMinutes=angular.noop,void(a.updateSeconds=angular.noop);var e=function(b,c,d){x.$setViewValue(null),x.$setValidity("time",!1),angular.isDefined(b)&&(a.invalidHours=b,s&&s.$setValidity("hours",!1)),angular.isDefined(c)&&(a.invalidMinutes=c,t&&t.$setValidity("minutes",!1)),angular.isDefined(d)&&(a.invalidSeconds=d,u&&u.$setValidity("seconds",!1))};a.updateHours=function(){var a=h(),b=i();x.$setDirty(),angular.isDefined(a)&&angular.isDefined(b)?(v.setHours(a),v.setMinutes(b),C>v||v>D?e(!0):l("h")):e(!0)},b.on("blur",function(b){x.$setTouched(),r()?m():null===a.hours||""===a.hours?e(!0):!a.invalidHours&&a.hours<10&&a.$apply(function(){a.hours=k(a.hours,!z)})}),a.updateMinutes=function(){var a=i(),b=h();x.$setDirty(),angular.isDefined(a)&&angular.isDefined(b)?(v.setHours(b),v.setMinutes(a),C>v||v>D?e(void 0,!0):l("m")):e(void 0,!0)},c.on("blur",function(b){x.$setTouched(),r()?m():null===a.minutes?e(void 0,!0):!a.invalidMinutes&&a.minutes<10&&a.$apply(function(){a.minutes=k(a.minutes)})}),a.updateSeconds=function(){var a=j();x.$setDirty(),angular.isDefined(a)?(v.setSeconds(a),l("s")):e(void 0,void 0,!0)},d.on("blur",function(b){r()?m():!a.invalidSeconds&&a.seconds<10&&a.$apply(function(){a.seconds=k(a.seconds)})})},this.render=function(){var b=x.$viewValue;isNaN(b)?(x.$setValidity("time",!1),e.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.')):(b&&(v=b),C>v||v>D?(x.$setValidity("time",!1),a.invalidHours=!0,a.invalidMinutes=!0):m(),n())},a.showSpinners=angular.isDefined(c.showSpinners)?a.$parent.$eval(c.showSpinners):g.showSpinners,a.incrementHours=function(){a.noIncrementHours()||o(60*A*60)},a.decrementHours=function(){a.noDecrementHours()||o(60*-A*60)},a.incrementMinutes=function(){a.noIncrementMinutes()||o(60*B)},a.decrementMinutes=function(){a.noDecrementMinutes()||o(60*-B)},a.incrementSeconds=function(){a.noIncrementSeconds()||o(F)},a.decrementSeconds=function(){a.noDecrementSeconds()||o(-F)},a.toggleMeridian=function(){var b=i(),c=h();a.noToggleMeridian()||(angular.isDefined(b)&&angular.isDefined(c)?o(720*(v.getHours()<12?60:-60)):a.meridian=a.meridian===y[0]?y[1]:y[0])},a.blur=function(){x.$setTouched()},a.$on("$destroy",function(){for(;w.length;)w.shift()()})}]).directive("uibTimepicker",["uibTimepickerConfig",function(a){return{require:["uibTimepicker","?^ngModel"],restrict:"A",controller:"UibTimepickerController",controllerAs:"timepicker",scope:{},templateUrl:function(b,c){return c.templateUrl||a.templateUrl},link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f,b.find("input"))}}}]),angular.module("ui.bootstrap.typeahead",["ui.bootstrap.debounce","ui.bootstrap.position"]).factory("uibTypeaheadParser",["$parse",function(a){var b=/^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;return{parse:function(c){var d=c.match(b);if(!d)throw new Error('Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_" but got "'+c+'".');return{itemName:d[3],source:a(d[4]),viewMapper:a(d[2]||d[1]),modelMapper:a(d[1])}}}}]).controller("UibTypeaheadController",["$scope","$element","$attrs","$compile","$parse","$q","$timeout","$document","$window","$rootScope","$$debounce","$uibPosition","uibTypeaheadParser",function(a,b,c,d,e,f,g,h,i,j,k,l,m){function n(){P.moveInProgress||(P.moveInProgress=!0,P.$digest()),$()}function o(){P.position=F?l.offset(b):l.position(b),P.position.top+=b.prop("offsetHeight")}function p(a){var b;return angular.version.minor<6?(b=a.$options||{},b.getOption=function(a){return b[a]}):b=a.$options,b}var q,r,s=[9,13,27,38,40],t=200,u=a.$eval(c.typeaheadMinLength);u||0===u||(u=1),a.$watch(c.typeaheadMinLength,function(a){u=a||0===a?a:1});var v=a.$eval(c.typeaheadWaitMs)||0,w=a.$eval(c.typeaheadEditable)!==!1;a.$watch(c.typeaheadEditable,function(a){w=a!==!1});var x,y,z=e(c.typeaheadLoading).assign||angular.noop,A=c.typeaheadShouldSelect?e(c.typeaheadShouldSelect):function(a,b){var c=b.$event;return 13===c.which||9===c.which},B=e(c.typeaheadOnSelect),C=angular.isDefined(c.typeaheadSelectOnBlur)?a.$eval(c.typeaheadSelectOnBlur):!1,D=e(c.typeaheadNoResults).assign||angular.noop,E=c.typeaheadInputFormatter?e(c.typeaheadInputFormatter):void 0,F=c.typeaheadAppendToBody?a.$eval(c.typeaheadAppendToBody):!1,G=c.typeaheadAppendTo?a.$eval(c.typeaheadAppendTo):null,H=a.$eval(c.typeaheadFocusFirst)!==!1,I=c.typeaheadSelectOnExact?a.$eval(c.typeaheadSelectOnExact):!1,J=e(c.typeaheadIsOpen).assign||angular.noop,K=a.$eval(c.typeaheadShowHint)||!1,L=e(c.ngModel),M=e(c.ngModel+"($$$p)"),N=function(b,c){return angular.isFunction(L(a))&&r.getOption("getterSetter")?M(b,{$$$p:c}):L.assign(b,c)},O=m.parse(c.uibTypeahead),P=a.$new(),Q=a.$on("$destroy",function(){P.$destroy()});P.$on("$destroy",Q);var R="typeahead-"+P.$id+"-"+Math.floor(1e4*Math.random());b.attr({"aria-autocomplete":"list","aria-expanded":!1,"aria-owns":R});var S,T;K&&(S=angular.element("<div></div>"),S.css("position","relative"),b.after(S),T=b.clone(),T.attr("placeholder",""),T.attr("tabindex","-1"),T.val(""),T.css({position:"absolute",top:"0px",left:"0px","border-color":"transparent","box-shadow":"none",opacity:1,background:"none 0% 0% / auto repeat scroll padding-box border-box rgb(255, 255, 255)",color:"#999"}),b.css({position:"relative","vertical-align":"top","background-color":"transparent"}),T.attr("id")&&T.removeAttr("id"),S.append(T),T.after(b));var U=angular.element("<div uib-typeahead-popup></div>");U.attr({id:R,matches:"matches",active:"activeIdx",select:"select(activeIdx, evt)","move-in-progress":"moveInProgress",query:"query",position:"position","assign-is-open":"assignIsOpen(isOpen)",debounce:"debounceUpdate"}),angular.isDefined(c.typeaheadTemplateUrl)&&U.attr("template-url",c.typeaheadTemplateUrl),angular.isDefined(c.typeaheadPopupTemplateUrl)&&U.attr("popup-template-url",c.typeaheadPopupTemplateUrl);var V=function(){K&&T.val("")},W=function(){P.matches=[],P.activeIdx=-1,b.attr("aria-expanded",!1),V()},X=function(a){return R+"-option-"+a};
vendor: 6,584 bytes, lines 6981-6982
6981P.$watch("activeIdx",function(a){0>a?b.removeAttr("aria-activedescendant"):b.attr("aria-activedescendant",X(a))});var Y=function(a,b){return P.matches.length>b&&a?a.toUpperCase()===P.matches[b].label.toUpperCase():!1},Z=function(c,d){var e={$viewValue:c};z(a,!0),D(a,!1),f.when(O.source(a,e)).then(function(f){var g=c===q.$viewValue;if(g&&x)if(f&&f.length>0){P.activeIdx=H?0:-1,D(a,!1),P.matches.length=0;for(var h=0;h<f.length;h++)e[O.itemName]=f[h],P.matches.push({id:X(h),label:O.viewMapper(P,e),model:f[h]});if(P.query=c,o(),b.attr("aria-expanded",!0),I&&1===P.matches.length&&Y(c,0)&&(angular.isNumber(P.debounceUpdate)||angular.isObject(P.debounceUpdate)?k(function(){P.select(0,d)},angular.isNumber(P.debounceUpdate)?P.debounceUpdate:P.debounceUpdate["default"]):P.select(0,d)),K){var i=P.matches[0].label;angular.isString(c)&&c.length>0&&i.slice(0,c.length).toUpperCase()===c.toUpperCase()?T.val(c+i.slice(c.length)):T.val("")}}else W(),D(a,!0);g&&z(a,!1)},function(){W(),z(a,!1),D(a,!0)})};F&&(angular.element(i).on("resize",n),h.find("body").on("scroll",n));var $=k(function(){P.matches.length&&o(),P.moveInProgress=!1},t);P.moveInProgress=!1,P.query=void 0;var _,aa=function(a){_=g(function(){Z(a)},v)},ba=function(){_&&g.cancel(_)};W(),P.assignIsOpen=function(b){J(a,b)},P.select=function(d,e){var f,h,i={};y=!0,i[O.itemName]=h=P.matches[d].model,f=O.modelMapper(a,i),N(a,f),q.$setValidity("editable",!0),q.$setValidity("parse",!0),B(a,{$item:h,$model:f,$label:O.viewMapper(a,i),$event:e}),W(),P.$eval(c.typeaheadFocusOnSelect)!==!1&&g(function(){b[0].focus()},0,!1)},b.on("keydown",function(b){if(0!==P.matches.length&&-1!==s.indexOf(b.which)){var c=A(a,{$event:b});if(-1===P.activeIdx&&c||9===b.which&&b.shiftKey)return W(),void P.$digest();b.preventDefault();var d;switch(b.which){case 27:b.stopPropagation(),W(),a.$digest();break;case 38:P.activeIdx=(P.activeIdx>0?P.activeIdx:P.matches.length)-1,P.$digest(),d=U[0].querySelectorAll(".uib-typeahead-match")[P.activeIdx],d.parentNode.scrollTop=d.offsetTop;break;case 40:P.activeIdx=(P.activeIdx+1)%P.matches.length,P.$digest(),d=U[0].querySelectorAll(".uib-typeahead-match")[P.activeIdx],
6982d.parentNode.scrollTop=d.offsetTop;break;default:c&&P.$apply(function(){angular.isNumber(P.debounceUpdate)||angular.isObject(P.debounceUpdate)?k(function(){P.select(P.activeIdx,b)},angular.isNumber(P.debounceUpdate)?P.debounceUpdate:P.debounceUpdate["default"]):P.select(P.activeIdx,b)})}}}),b.on("focus",function(a){x=!0,0!==u||q.$viewValue||g(function(){Z(q.$viewValue,a)},0)}),b.on("blur",function(a){C&&P.matches.length&&-1!==P.activeIdx&&!y&&(y=!0,P.$apply(function(){angular.isObject(P.debounceUpdate)&&angular.isNumber(P.debounceUpdate.blur)?k(function(){P.select(P.activeIdx,a)},P.debounceUpdate.blur):P.select(P.activeIdx,a)})),!w&&q.$error.editable&&(q.$setViewValue(),P.$apply(function(){q.$setValidity("editable",!0),q.$setValidity("parse",!0)}),b.val("")),x=!1,y=!1});var ca=function(c){b[0]!==c.target&&3!==c.which&&0!==P.matches.length&&(W(),j.$$phase||a.$digest())};h.on("click",ca),a.$on("$destroy",function(){h.off("click",ca),(F||G)&&da.remove(),F&&(angular.element(i).off("resize",n),h.find("body").off("scroll",n)),U.remove(),K&&S.remove()});var da=d(U)(P);F?h.find("body").append(da):G?angular.element(G).eq(0).append(da):b.after(da),this.init=function(b){q=b,r=p(q),P.debounceUpdate=e(r.getOption("debounce"))(a),q.$parsers.unshift(function(b){return x=!0,0===u||b&&b.length>=u?v>0?(ba(),aa(b)):Z(b):(z(a,!1),ba(),W()),w?b:b?void q.$setValidity("editable",!1):(q.$setValidity("editable",!0),null)}),q.$formatters.push(function(b){var c,d,e={};return w||q.$setValidity("editable",!0),E?(e.$model=b,E(a,e)):(e[O.itemName]=b,c=O.viewMapper(a,e),e[O.itemName]=void 0,d=O.viewMapper(a,e),c!==d?c:b)})}}]).directive("uibTypeahead",function(){return{controller:"UibTypeaheadController",require:["ngModel","uibTypeahead"],link:function(a,b,c,d){d[1].init(d[0])}}}).directive("uibTypeaheadPopup",["$$debounce",function(a){return{scope:{matches:"=",query:"=",active:"=",position:"&",moveInProgress:"=",select:"&",assignIsOpen:"&",debounce:"&"},replace:!0,templateUrl:function(a,b){return b.popupTemplateUrl||"uib/template/typeahead/typeahead-popup.html"},link:function(b,c,d){b.templateUrl=d.templateUrl,b.isOpen=function(){var a=b.matches.length>0;return b.assignIsOpen({isOpen:a}),a},b.isActive=function(a){return b.active===a},b.selectActive=function(a){b.active=a},b.selectMatch=function(c,d){var e=b.debounce();angular.isNumber(e)||angular.isObject(e)?a(function(){b.select({activeIdx:c,evt:d})},angular.isNumber(e)?e:e["default"]):b.select({activeIdx:c,evt:d})}}}}]).directive("uibTypeaheadMatch",["$templateRequest","$compile","$parse",function(a,b,c){return{scope:{index:"=",match:"=",query:"="},link:function(d,e,f){var g=c(f.templateUrl)(d.$parent)||"uib/template/typeahead/typeahead-match.html";a(g).then(function(a){var c=angular.element(a.trim());e.replaceWith(c),b(c)(d)})}}}]).filter("uibTypeaheadHighlight",["$sce","$injector","$log",function(a,b,c){function d(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}function e(a){return/<.*>/g.test(a)}var f;return f=b.has("$sanitize"),function(b,g){return!f&&e(b)&&c.warn("Unsafe use of typeahead please use ngSanitize"),b=g?(""+b).replace(new RegExp(d(g),"gi"),"<strong>$&</strong>"):b,f||(b=a.trustAsHtml(b)),b}}]),angular.module("uib/template/accordion/accordion-group.html",[]).run(["$templateCache",function(a){a.put("uib/template/accordion/accordion-group.html",'<div role="tab" id="{{::headingId}}" aria-selected="{{isOpen}}" class="panel-heading" ng-keypress="toggleOpen($event)">\n  <h4 class="panel-title">\n    <a role="button" data-toggle="collapse" href aria-expanded="{{isOpen}}" aria-controls="{{::panelId}}" tabindex="0" class="accordion-toggle" ng-click="toggleOpen()" uib-accordion-transclude="heading" ng-disabled="isDisabled" uib-tabindex-toggle><span uib-accordion-header ng-class="{\'text-muted\': isDisabled}">{{heading}}</span></a>\n  </h4>\n</div>\n<div id="{{::panelId}}" aria-labelledby="{{::headingId}}" aria-hidden="{{!isOpen}}" role="tabpanel" class="panel-collapse collapse" uib-collapse="!isOpen">\n  <div class="panel-body" ng-transclude></div>\n</div>\n')}]),angular.module("uib/template/accordion/accordion.html",[]).run(["$templateCache",function(a){a.put("uib/template/accordion/accordion.html",'<div role="tablist" class="panel-group" ng-transclude></div>')}]),angular.module("uib/template/alert/alert.html",[]).run(["$templateCache",function(a){a.put("uib/template/alert/alert.html",'<button ng-show="closeable" t
6982ype="button" class="close" ng-click="close({$event: $event})">\n  <span aria-hidden="true">&times;</span>\n  <span class="sr-only">Close</span>\n</button>\n<div ng-transclude></div>\n')}]),angular.module("uib/template/carousel/carousel.html",[]).run(["$templateCache",function(a){a.put("uib/template/carousel/carousel.html",'<div class="carousel-inner" ng-transclude></div>\n<a role="button" href class="left carousel-control" ng-click="prev()" ng-class="{ disabled: isPrevDisabled() }" ng-show="slides.length > 1">\n  <span aria-hidden="true" class="glyphicon glyphicon-chevron-left"></span>\n  <span class="sr-only">previous</span>\n</a>\n<a role="button" href class="right carousel-control" ng-click="next()" ng-class="{ disabled: isNextDisabled() }" ng-show="slides.length > 1">\n  <span aria-hidden="true" class="glyphicon glyphicon-chevron-right"></span>\n  <span class="sr-only">next</span>\n</a>\n<ol class="carousel-indicators" ng-show="slides.length > 1">\n  <li ng-repeat="slide in slides | orderBy:indexOfSlide track by $index" ng-class="{ active: isActive(slide) }" ng-click="select(slide)">\n    <span class="sr-only">slide {{ $index + 1 }} of {{ slides.length }}<span ng-if="isActive(slide)">, currently active</span></span>\n  </li>\n</ol>\n')}]),angular.module("uib/template/carousel/slide.html",[]).run(["$templateCache",function(a){a.put("uib/template/carousel/slide.html",'<div class="text-center" ng-transclude></div>\n')}]),angular.module("uib/template/datepicker/datepicker.html",[]).run(["$templateCache",function(a){a.put("uib/template/datepicker/datepicker.html",'<div ng-switch="datepickerMode">\n  <div uib-daypicker ng-switch-when="day" tabindex="0" class="uib-daypicker"></div>\n  <div uib-monthpicker ng-switch-when="month" tabindex="0" class="uib-monthpicker"></div>\n  <div uib-yearpicker ng-switch-when="year" tabindex="0" class="uib-yearpicker"></div>\n</div>\n')}]),angular.module("uib/template/datepicker/day.html",[]).run(["$templateCache",function(a){a.put("uib/template/datepicker/day.html",'<table role="grid" aria-labelledby="{{::uniqueId}}-title" aria-activedescendant="{{activeDateId}}">\n  <thead>\n    <tr>\n      <th><button type="button" class="btn btn-default btn-sm pull-left uib-left" ng-click="move(-1)" tabindex="-1"><i aria-hidden="true" class="glyphicon glyphicon-chevron-left"></i><span class="sr-only">previous</span></button></th>\n      <th colspan="{{::5 + showWeeks}}"><button id="{{::uniqueId}}-title" role="heading" aria-live="assertive" aria-atomic="true" type="button" class="btn btn-default btn-sm uib-title" ng-click="toggleMode()" ng-disabled="datepickerMode === maxMode" tabindex="-1"><strong>{{title}}</strong></button></th>\n      <th><button type="button" class="btn btn-default btn-sm pull-right uib-right" ng-click="move(1)" tabindex="-1"><i aria-hidden="true" class="glyphicon glyphicon-chevron-right"></i><span class="sr-only">next</span></button></th>\n    </tr>\n    <tr>\n      <th ng-if="showWeeks" class="text-center"></th>\n      <th ng-repeat="label in ::labels track by $index" class="text-center"><small aria-label="{{::label.full}}">{{::label.abbr}}</small></th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr class="uib-weeks" ng-repeat="row in rows track by $index" role="row">\n      <td ng-if="showWeeks" class="text-center h6"><em>{{ weekNumbers[$index] }}</em></td>\n      <td ng-repeat="dt in row" class="uib-day text-center" role="gridcell"\n        id="{{::dt.uid}}"\n        ng-class="::dt.customClass">\n        <button type="button" class="btn btn-default btn-sm"\n          uib-is-class="\n            \'btn-info\' for selectedDt,\n            \'active\' for activeDt\n            on dt"\n          ng-click="select(dt.date)"\n          ng-disabled="::dt.disabled"\n          tabindex="-1"><span ng-class="::{\'text-muted\': dt.secondary, \'text-info\': dt.current}">{{::dt.label}}</span></button>\n      </td>\n    </tr>\n  </tbody>\n</table>\n')}]),angular.module("uib/template/datepicker/month.html",[]).run(["$templateCache",function(a){a.put("uib/template/datepicker/month.html",'<table role="grid" aria-labelledby="{{::uniqueId}}-title" aria-activedescendant="{{activeDateId}}">\n  <thead>\n    <tr>\n      <th><button type="button" class="btn btn-default btn-sm pull-left uib-left" ng-click="move(-1)" tabindex="-1"><i aria-hidden="true" class="glyphicon glyphicon-chevron-left"></i><span class="sr-only">previous</span></button></th>\n      <th colspan="{{::yearHeaderColspan}}"><button id="{{::uniqueId}}-title" role="heading" aria-live="assertive" aria-atomic="true" type="button" class="btn btn-default btn-sm uib-title" ng-click="toggleMode()" ng-disabled="datepickerMode === max
6982Mode" tabindex="-1"><strong>{{title}}</strong></button></th>\n      <th><button type="button" class="btn btn-default btn-sm pull-right uib-right" ng-click="move(1)" tabindex="-1"><i aria-hidden="true" class="glyphicon glyphicon-chevron-right"></i><span class="sr-only">next</span></i></button></th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr class="uib-months" ng-repeat="row in rows track by $index" role="row">\n      <td ng-repeat="dt in row" class="uib-month text-center" role="gridcell"\n        id="{{::dt.uid}}"\n        ng-class="::dt.customClass">\n        <button type="button" class="btn btn-default"\n          uib-is-class="\n            \'btn-info\' for selectedDt,\n            \'active\' for activeDt\n            on dt"\n          ng-click="select(dt.date)"\n          ng-disabled="::dt.disabled"\n          tabindex="-1"><span ng-class="::{\'text-info\': dt.current}">{{::dt.label}}</span></button>\n      </td>\n    </tr>\n  </tbody>\n</table>\n')}]),angular.module("uib/template/datepicker/year.html",[]).run(["$templateCache",function(a){a.put("uib/template/datepicker/year.html",'<table role="grid" aria-labelledby="{{::uniqueId}}-title" aria-activedescendant="{{activeDateId}}">\n  <thead>\n    <tr>\n      <th><button type="button" class="btn btn-default btn-sm pull-left uib-left" ng-click="move(-1)" tabindex="-1"><i aria-hidden="true" class="glyphicon glyphicon-chevron-left"></i><span class="sr-only">previous</span></button></th>\n      <th colspan="{{::columns - 2}}"><button id="{{::uniqueId}}-title" role="heading" aria-live="assertive" aria-atomic="true" type="button" class="btn btn-default btn-sm uib-title" ng-click="toggleMode()" ng-disabled="datepickerMode === maxMode" tabindex="-1"><strong>{{title}}</strong></button></th>\n      <th><button type="button" class="btn btn-default btn-sm pull-right uib-right" ng-click="move(1)" tabindex="-1"><i aria-hidden="true" class="glyphicon glyphicon-chevron-right"></i><span class="sr-only">next</span></button></th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr class="uib-years" ng-repeat="row in rows track by $index" role="row">\n      <td ng-repeat="dt in row" class="uib-year text-center" role="gridcell"\n        id="{{::dt.uid}}"\n        ng-class="::dt.customClass">\n        <button type="button" class="btn btn-default"\n          uib-is-class="\n            \'btn-info\' for selectedDt,\n            \'active\' for activeDt\n            on dt"\n          ng-click="select(dt.date)"\n          ng-disabled="::dt.disabled"\n          tabindex="-1"><span ng-class="::{\'text-info\': dt.current}">{{::dt.label}}</span></button>\n      </td>\n    </tr>\n  </tbody>\n</table>\n')}]),angular.module("uib/template/datepickerPopup/popup.html",[]).run(["$templateCache",function(a){a.put("uib/template/datepickerPopup/popup.html",'<ul role="presentation" class="uib-datepicker-popup dropdown-menu uib-position-measure" dropdown-nested ng-if="isOpen" ng-keydown="keydown($event)" ng-click="$event.stopPropagation()">\n  <li ng-transclude></li>\n  <li ng-if="showButtonBar" class="uib-button-bar">\n    <span class="btn-group pull-left">\n      <button type="button" class="btn btn-sm btn-info uib-datepicker-current" ng-click="select(\'today\', $event)" ng-disabled="isDisabled(\'today\')">{{ getText(\'current\') }}</button>\n      <button type="button" class="btn btn-sm btn-danger uib-clear" ng-click="select(null, $event)">{{ getText(\'clear\') }}</button>\n    </span>\n    <button type="button" class="btn btn-sm btn-success pull-right uib-close" ng-click="close($event)">{{ getText(\'close\') }}</button>\n  </li>\n</ul>\n')}]),angular.module("uib/template/modal/window.html",[]).run(["$templateCache",function(a){a.put("uib/template/modal/window.html","<div class=\"modal-dialog {{size ? 'modal-' + size : ''}}\"><div class=\"modal-content\" uib-modal-transclude></div></div>\n")}]),angular.module("uib/template/pager/pager.html",[]).run(["$templateCache",function(a){a.put("uib/template/pager/pager.html",'<li ng-class="{disabled: noPrevious()||ngDisabled, previous: align}"><a href ng-click="selectPage(page - 1, $event)" ng-disabled="noPrevious()||ngDisabled" uib-tabindex-toggle>{{::getText(\'previous\')}}</a></li>\n<li ng-class="{disabled: noNext()||ngDisabled, next: align}"><a href ng-click="selectPage(page + 1, $event)" ng-disabled="noNext()||ngDisabled" uib-tabindex-toggle>{{::getText(\'next\')}}</a></li>\n')}]),angular.module("uib/template/pagination/pagination.html",[]).run(["$templateCache",function(a){a.put("uib/template/pagination/pagination.html",'<li role="menuitem" ng-if="::boundaryLinks" ng-class="{disabled: noPrevious()||ngDisabled}" class="pagination-first"><a href ng-click="selectPage(1, $event)" ng-disabled="noPrevious()||ngDisabled" uib-tabindex-toggle>{{::getText(\'first\')}}</a></li>\n<li role="menuitem" ng-if="::directionLinks" ng-class="{disabled: noPrevious()||ngDisabled}" class="pagination-prev"><a href ng-click="selectPage(page - 1, $event)" ng-disabled="noPrevious()||ngDisabled" uib-tabindex-toggle>{{::getText(\'previous\')}}</a></li>\n<li role="menuitem" ng-repeat="page in pages track by $index" ng-class="{active: page.active,disabled: ngDisabled&&!page.active}" class="pagination-page"><a href ng-click="selectPage(page.number, $event)" ng-disabled="ngDisabled&&!page.active" uib-tabindex-toggle>{{page.text}}</a></li>\n<li role="menuitem" ng-if="::directionLinks" ng-class="{disabled: noNext()||ngDisabled}" class="pagination-next"><a href ng-click="selectPage(page + 1, $event)" ng-disabled="noNext()||ngDisabled" uib-tabindex-toggle>{{::getText(\'next\')}}</a></li>\n<li role="menuitem" ng-if="::boundaryLinks" ng-class="{disabled: noNext()||ngDisabled}" class="pagination-last"><a href ng-click="selectPage(totalPages, $event)" ng-disabled="noNext()||ngDisabled" uib-tabindex-toggle>{{::getText(\'last\')}}</a></li>\n')}]),angular.module("uib/template/tooltip/tooltip-html-popup.html",[]).run(["$templateCache",function(a){a.put("uib/template/tooltip/tooltip-html-popup.html",'<div class="tooltip-arrow"></div>\n<div class="tooltip-inner" ng-bind-html="contentExp()"></div>\n')}]),angular.module("uib/template/tooltip/tooltip-popup.html",[]).run(["$templateCache",function(a){a.put("uib/template/tooltip/tooltip-popup.html",'<div class="tooltip-arrow"></div>\n<div class="tooltip-inner" ng-bind="content"></div>\n')}]),angular.module("uib/template/tooltip/tooltip-template-popup.html",[]).run(["$templateCache",function(a){a.put("uib/template/tooltip/tooltip-template-popup.html",'<div class="tooltip-arrow"></div>\n<div class="tooltip-inner"\n  uib-tooltip-template-transclude="contentExp()"\n  tooltip-template-transclude-scope="originScope()"></div>\n')}]),angular.module("uib/template/popover/popover-html.html",[]).run(["$templateCache",function(a){a.put("uib/template/popover/popover-html.html",'<div class="arrow"></div>\n\n<div class="popover-inner">\n    <h3 class="popover-title" ng-bind="uibTitle" ng-if="uibTitle"></h3>\n    <div class="popover-content" ng-bind-html="contentExp()"></div>\n</div>\n')}]),angular.module("uib/template/popover/popover-template.html",[]).run(["$templateCache",function(a){a.put("uib/template/popover/popover-template.html",'<div class="arrow"></div>\n\n<div class="popover-inner">\n    <h3 class="popover-title" ng-bind="uibTitle" ng-if="uibTitle"></h3>\n    <div class="popover-content"\n      uib-tooltip-template-transclude="contentExp()"\n      tooltip-template-transclude-scope="originScope()"></div>\n</div>\n')}]),angular.module("uib/template/popover/popover.html",[]).run(["$templateCache",function(a){a.put("uib/template/popover/popover.html",'<div class="arrow"></div>\n\n<div class="popover-inner">\n    <h3 class="popover-title" ng-bind="uibTitle" ng-if="uibTitle"></h3>\n    <div class="popover-content" ng-bind="content"></div>\n</div>\n')}]),angular.module("uib/template/progressbar/bar.html",[]).run(["$templateCache",function(a){a.put("uib/template/progressbar/bar.html",'<div class="progress-bar" ng-class="type && \'progress-bar-\' + type" role="progressbar" aria-valuenow="{{value}}" aria-valuemin="0" aria-valuemax="{{max}}" ng-style="{width: (percent < 100 ? percent : 100) + \'%\'}" aria-valuetext="{{percent | number:0}}%" aria-labelledby="{{::title}}" ng-transclude></div>\n')}]),angular.module("uib/template/progressbar/progress.html",[]).run(["$templateCache",function(a){a.put("uib/template/progressbar/progress.html",'<div class="progress" ng-transclude aria-labelledby="{{::title}}"></div>')}]),angular.module("uib/template/progressbar/progressbar.html",[]).run(["$templateCache",function(a){a.put("uib/template/progressbar/progressbar.html",'<div class="progress">\n  <div class="progress-bar" ng-class="type && \'progress-bar-\' + type" role="progressbar" aria-valuenow="{{value}}" aria-valuemin="0" aria-valuemax="{{max}}" ng-style="{width: (percent < 100 ? percent : 100) + \'%\'}" aria-valuetext="{{percent | number:0}}%" aria-labelledby="{{::title}}" ng-transclude></div>\n</div>\n')}]),angular.module("uib/template/rating/rating.html",[]).run(["$templateCache",function(a){a.put("uib/template/rating/rating.html",'<span ng-mouseleave="reset()" ng-keydown="onKeydown($event)" tabindex="0" role="slider" aria-valuemin="0" aria-valuemax="{{range.length}}" aria-valuenow="{{value}}" aria-valuetext="{{title}}">\n    <span ng-repeat-start="r in range track by $index" class="sr-only">({{ $index < value ? \'*\' : \' \' }})</span>\n    <i ng-repeat-end ng-mouseenter="enter($index + 1)" ng-click="rate($index + 1)" class="glyphicon" ng-class="$index < value && (r.stateOn || \'glyphicon-star\') || (r.stateOff || \'glyphicon-star-empty\')" ng-attr-title="{{r.title}}"></i>\n</span>\n')}]),angular.module("uib/template/tabs/tab.html",[]).run(["$templateCache",function(a){a.put("uib/template/tabs/tab.html",'<li ng-class="[{active: active, disabled: disabled}, classes]" class="uib-tab nav-item">\n  <a href ng-click="select($event)" class="nav-link" uib-tab-heading-transclude>{{heading}}</a>\n</li>\n')}]),angular.module("uib/template/tabs/tabset.html",[]).run(["$templateCache",function(a){a.put("uib/template/tabs/tabset.html",'<div>\n  <ul class="nav nav-{{tabset.type || \'tabs\'}}" ng-class="{\'nav-stacked\': vertical, \'nav-justified\': justified}" ng-transclude></ul>\n  <div class="tab-content">\n    <div class="tab-pane"\n         ng-repeat="tab in tabset.tabs"\n         ng-class="{active: tabset.active === tab.index}"\n         uib-tab-content-transclude="tab">\n    </div>\n  </div>\n</div>\n')}]),angular.module("uib/template/timepicker/timepicker.html",[]).run(["$templateCache",function(a){a.put("uib/template/timepicker/timepicker.html",'<table class="uib-timepicker">\n  <tbody>\n    <tr class="text-center" ng-show="::showSpinners">\n      <td class="uib-increment hours"><a ng-click="incrementHours()" ng-class="{disabled: noIncrementHours()}" class="btn btn-link" ng-disabled="noIncrementHours()" tabindex="-1"><span class="glyphicon glyphicon-chevron-up"></span></a></td>\n      <td>&nbsp;</td>\n      <td class="uib-increment minutes"><a ng-click="incrementMinutes()" ng-class="{disabled: noIncrementMinutes()}" class="btn btn-link" ng-disabled="noIncrementMinutes()" tabindex="-1"><span class="glyphicon glyphicon-chevron-up"></span></a></td>\n      <td ng-show="showSeconds">&nbsp;</td>\n      <td ng-show="showSeconds" class="uib-increment seconds"><a ng-click="incrementSeconds()" ng-class="{disabled: noIncrementSeconds()}" class="btn btn-link" ng-disabled="noIncrementSeconds()" tabindex="-1"><span class="glyphicon glyphicon-chevron-up"></span></a></td>\n      <td ng-show="showMeridian"></td>\n    </tr>\n    <tr>\n      <td class="form-group uib-time hours" ng-class="{\'has-error\': invalidHours}">\n        <input type="text" placeholder="HH" ng-model="hours" ng-change="updateHours()" class="form-control text-center" ng-readonly="::readonlyInput" maxlength="2" tabindex="{{::tabindex}}" ng-disabled="noIncrementHours()" ng-blur="blur()">\n      </td>\n      <td class="uib-separator">:</td>\n      <td class="form-group uib-time minutes" ng-class="{\'has-error\': invalidMinutes}">\n        <input type="text" placeholder="MM" ng-model="minutes" ng-change="updateMinutes()" class="form-control text-center" ng-readonly="::readonlyInput" maxlength="2" tabindex="{{::tabindex}}" ng-disabled="noIncrementMinutes()" ng-blur="blur()">\n      </td>\n      <td ng-show="showSeconds" class="uib-separator">:</td>\n      <td class="form-group uib-time seconds" ng-class="{\'has-error\': invalidSeconds}" ng-show="showSeconds">\n        <input type="text" placeholder="SS" ng-model="seconds" ng-change="updateSeconds()" class="form-control text-center" ng-readonly="readonlyInput" maxlength="2" tabindex="{{::tabindex}}" ng-disabled="noIncrementSeconds()" ng-blur="blur()">\n      </td>\n      <td ng-show="showMeridian" class="uib-time am-pm"><button type="button" ng-class="{disabled: noToggleMeridian()}" class="btn btn-default text-center" ng-click="toggleMeridian()" ng-disabled="noToggleMeridian()" tabindex="{{::tabindex}}">{{meridian}}</button></td>\n    </tr>\n    <tr class="text-center" ng-show="::showSpinners">\n      <td class="uib-decrement hours"><a ng-click="decrementHours()" ng-class="{disabled: noDecrementHours()}" class="btn btn-link" ng-disabled="noDecrementHours()" tabindex="-1"><span class="glyphicon glyphicon-chevron-down"></span></a></td>\n      <td>&nbsp;</td>\n      <td class="uib-decrement minutes"><a ng-click="decrementMinutes()" ng-class="{disabled: noDecrementMinutes()}" class="btn btn-link" ng-disabled="noDecrementMinutes()" tabindex="-1"><span class="glyphicon glyphicon-chevron-down"></span></a></td>\n      <td ng-show="showSeconds">&nbsp;</td>\n      <td ng-show="showSeconds" class="uib-decrement seconds"><a ng-click="decrementSeconds()" ng-class="{disabled: noDecrementSeconds()}" class="btn btn-link" ng-disabled="noDecrementSeconds()" tabindex="-1"><span class="glyphicon glyphicon-chevron-down"></span></a></td>\n      <td ng-show="showMeridian"></td>\n    </tr>\n  </tbody>\n</table>\n')}]),angular.module("uib/template/typeahead/typeahead-match.html",[]).run(["$templateCache",function(a){a.put("uib/template/typeahead/typeahead-match.html",'<a href\n   tabindex="-1"\n   ng-bind-html="match.label | uibTypeaheadHighlight:query"\n   ng-attr-title="{{match.label}}"></a>\n')}]),angular.module("uib/template/typeahead/typeahead-popup.html",[]).run(["$templateCache",function(a){a.put("uib/template/typeahead/typeahead-popup.html",'<ul class="dropdown-menu" ng-show="isOpen() && !moveInProgress" ng-style="{top: position().top+\'px\', left: position().left+\'px\'}" role="listbox" aria-hidden="{{!isOpen()}}">\n    <li class="uib-typeahead-match" ng-repeat="match in matches track by $index" ng-class="{active: isActive($index) }" ng-mouseenter="selectActive($index)" ng-click="selectMatch($index, $event)" role="option" id="{{::match.id}}">\n        <div uib-typeahead-match index="$index" match="match" query="query" template-url="templateUrl"></div>\n    </li>\n</ul>\n')}]),angular.module("ui.bootstrap.carousel").run(function(){!angular.$$csp().noInlineStyle&&!
6982angular.$$uibCarouselCss&&angular.element(document).find("head").prepend('<style type="text/css">.ng-animate.item:not(.left):not(.right){-webkit-transition:0s ease-in-out left;transition:0s ease-in-out left}</style>'),angular.$$uibCarouselCss=!0}),angular.module("ui.bootstrap.datepicker").run(function(){!angular.$$csp().noInlineStyle&&!angular.$$uibDatepickerCss&&angular.element(document).find("head").prepend('<style type="text/css">.uib-datepicker .uib-title{width:100%;}.uib-day button,.uib-month button,.uib-year button{min-width:100%;}.uib-left,.uib-right{width:100%}</style>'),angular.$$uibDatepickerCss=!0}),angular.module("ui.bootstrap.position").run(function(){!angular.$$csp().noInlineStyle&&!angular.$$uibPositionCss&&angular.element(document).find("head").prepend('<style type="text/css">.uib-position-measure{display:block !important;visibility:hidden !important;position:absolute !important;top:-9999px !important;left:-9999px !important;}.uib-position-scrollbar-measure{position:absolute !important;top:-9999px !important;width:50px !important;height:50px !important;overflow:scroll !important;}.uib-position-body-scrollbar-measure{overflow:scroll !important;}</style>'),angular.$$uibPositionCss=!0}),angular.module("ui.bootstrap.datepickerPopup").run(function(){!angular.$$csp().noInlineStyle&&!angular.$$uibDatepickerpopupCss&&angular.element(document).find("head").prepend('<style type="text/css">.uib-datepicker-popup.dropdown-menu{display:block;float:none;margin:0;}.uib-button-bar{padding:10px 9px 2px;}</style>'),angular.$$uibDatepickerpopupCss=!0}),angular.module("ui.bootstrap.tooltip").run(function(){!angular.$$csp().noInlineStyle&&!angular.$$uibTooltipCss&&angular.element(document).find("head").prepend('<style type="text/css">[uib-tooltip-popup].tooltip.top-left > .tooltip-arrow,[uib-tooltip-popup].tooltip.top-right > .tooltip-arrow,[uib-tooltip-popup].tooltip.bottom-left > .tooltip-arrow,[uib-tooltip-popup].tooltip.bottom-right > .tooltip-arrow,[uib-tooltip-popup].tooltip.left-top > .tooltip-arrow,[uib-tooltip-popup].tooltip.left-bottom > .tooltip-arrow,[uib-tooltip-popup].tooltip.right-top > .tooltip-arrow,[uib-tooltip-popup].tooltip.right-bottom > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.top-left > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.top-right > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.bottom-left > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.bottom-right > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.left-top > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.left-bottom > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.right-top > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.right-bottom > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.top-left > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.top-right > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.bottom-left > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.bottom-right > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.left-top > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.left-bottom > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.right-top > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.right-bottom > .tooltip-arrow,[uib-popover-popup].popover.top-left > .arrow,[uib-popover-popup].popover.top-right > .arrow,[uib-popover-popup].popover.bottom-left > .arrow,[uib-popover-popup].popover.bottom-right > .arrow,[uib-popover-popup].popover.left-top > .arrow,[uib-popover-popup].popover.left-bottom > .arrow,[uib-popover-popup].popover.right-top > .arrow,[uib-popover-popup].popover.right-bottom > .arrow,[uib-popover-html-popup].popover.top-left > .arrow,[uib-popover-html-popup].popover.top-right > .arrow,[uib-popover-html-popup].popover.bottom-left > .arrow,[uib-popover-html-popup].popover.bottom-right > .arrow,[uib-popover-html-popup].popover.left-top > .arrow,[uib-popover-html-popup].popover.left-bottom > .arrow,[uib-popover-html-popup].popover.right-top > .arrow,[uib-popover-html-popup].popover.right-bottom > .arrow,[uib-popover-template-popup].popover.top-left > .arrow,[uib-popover-template-popup].popover.top-right > .arrow,[uib-popover-template-popup].popover.bottom-left > .arrow,[uib-popover-template-popup].popover.bottom-right > .arrow,[uib-popover-template-popup].popover.left-top > .arrow,[uib-popover-template-popup].popover.left-bottom > .arrow,[uib-popover-template-popup].popover.right-top > .arrow,[uib-popover-template-popup].popover.right-bottom > .arrow{top:auto;bottom:auto;left:auto;right:auto;margin:0;}[uib-popover-popup].popover,[uib-popover-html-popup].popover,[uib-popover-template-popup].popover{display:block !important;}</style>'),angular.$$uibTooltipCss=!0}),angular.module("ui.bootstrap.timepicker").run(function(){!angular.$$csp().noInlineStyle&&!
6982angular.$$uibTimepickerCss&&angular.element(document).find("head").prepend('<style type="text/css">.uib-time input{width:50px;}</style>'),angular.$$uibTimepickerCss=!0}),angular.module("ui.bootstrap.typeahead").run(function(){!angular.$$csp().noInlineStyle&&!angular.$$uibTypeaheadCss&&angular.element(document).find("head").prepend('<style type="text/css">[uib-typeahead-popup].dropdown-menu{display:block;}</style>'),angular.$$uibTypeaheadCss=!0});
6983/**
6984 * angular-ui-sortable - This directive allows you to jQueryUI Sortable.
6985 * @version v0.19.0 - 2018-01-14
vendor: 6,481 bytes, lines 6985-6990
6985
6986 * @link http://angular-ui.github.com
6987 * @license MIT
6988 */
6989
6990!function(a,b,c){"use strict";b.module("ui.sortable",[]).value("uiSortableConfig",{items:"> [ng-repeat],> [data-ng-repeat],> [x-ng-repeat]"}).directive("uiSortable",["uiSortableConfig","$timeout","$log",function(a,d,e){return{require:"?ngModel",scope:{ngModel:"=",uiSortable:"=",create:"&uiSortableCreate",start:"&uiSortableStart",activate:"&uiSortableActivate",beforeStop:"&uiSortableBeforeStop",update:"&uiSortableUpdate",remove:"&uiSortableRemove",receive:"&uiSortableReceive",deactivate:"&uiSortableDeactivate",stop:"&uiSortableStop"},link:function(f,g,h,i){function j(a,b){var c="function"==typeof a,d="function"==typeof b;return c&&d?function(){a.apply(this,arguments),b.apply(this,arguments)}:d?b:a}function k(a){var b=a.data("ui-sortable");return b&&"object"==typeof b&&"ui-sortable"===b.widgetFullName?b:null}function l(a){a.children().each(function(){var a=b.element(this);a.width(a.width())})}function m(a,b){return b}function n(b,c){return E[b]?("stop"===b&&(c=j(c,function(){f.$apply()}),c=j(c,v)),c=j(E[b],c)):F[b]&&(c=F[b](c)),c||"items"!==b&&"ui-model-items"!==b||(c=a.items),c}function o(a,d,e){function f(a,b){b in C||(C[b]=null)}b.forEach(E,f);var g=null;if(d){var h;b.forEach(d,function(d,e){if(!(a&&e in a)){if(e in D)return void("ui-floating"===e?C[e]="auto":C[e]=n(e,c));h||(h=b.element.ui.sortable().options);var f=h[e];f=n(e,f),g||(g={}),g[e]=f,C[e]=f}})}return a=b.extend({},a),b.forEach(a,function(b,c){if(c in D){if("ui-floating"!==c||b!==!1&&b!==!0||!e||(e.floating=b),"ui-preserve-size"===c&&(b===!1||b===!0)){var d=C.helper;a.helper=function(a,b){return C["ui-preserve-size"]===!0&&l(b),(d||m).apply(this,arguments)}}C[c]=n(c,b)}}),b.forEach(a,function(a,b){b in D||(a=n(b,a),g||(g={}),g[b]=a,C[b]=a)}),g}function p(a){var c=a.sortable("option","placeholder");if(c&&c.element&&"function"==typeof c.element){var d=c.element();return d=b.element(d)}return null}function q(a,b){var c=C["ui-model-items"].replace(/[^,]*>/g,""),d=a.find('[class="'+b.attr("class")+'"]:not('+c+")");return d}function r(a,b){var c=a.sortable("option","helper");return"clone"===c||"function"==typeof c&&b.item.sortable.isCustomHelperUsed()}function s(a,b){var c=null;return r(a,b)&&"parent"===a.sortable("option","appendTo")&&(c=B),c}function t(a){return/left|right/.test(a.css("float"))||/inline|table-cell/.test(a.css("display"))}function u(a,b){for(var c=0;c<a.length;c++){var d=a[c];if(d.element[0]===b[0])return d}}function v(a,b){b.item.sortable._destroy()}function w(a){return a.parent().find(C["ui-model-items"]).index(a)}function x(){f.$watchCollection("ngModel",function(){d(function(){k(g)&&g.sortable("refresh")},0,!1)}),E.start=function(a,d){if("auto"===C["ui-floating"]){var e=d.item.siblings(),f=k(b.element(a.target));f.floating=t(e)}var h=w(d.item);d.item.sortable={model:i.$modelValue[h],index:h,source:g,sourceList:d.item.parent(),sourceModel:i.$modelValue,cancel:function(){d.item.sortable._isCanceled=!0},isCanceled:function(){return d.item.sortable._isCanceled},isCustomHelperUsed:function(){return!!d.item.sortable._isCustomHelperUsed},_isCanceled:!1,_isCustomHelperUsed:d.item.sortable._isCustomHelperUsed,_destroy:function(){b.forEach(d.item.sortable,function(a,b){d.item.sortable[b]=c})},_connectedSortables:[],_getElementContext:function(a){return u(this._connectedSortables,a)}}},E.activate=function(a,b){var c=b.item.sortable.source===g,d=c?b.item.sortable.sourceList:g,e={element:g,scope:f,isSourceContext:c,savedNodesOrigin:d};b.item.sortable._connectedSortables.push(e),A=d.contents(),B=b.helper;var h=p(g);if(h&&h.length){var i=q(g,h);A=A.not(i)}},E.update=function(a,b){if(!b.item.sortable.received){b.item.sortable.dropindex=w(b.item);var c=b.item.parent().closest("[ui-sortable], [data-ui-sortable], [x-ui-sortable]");b.item.sortable.droptarget=c,b.item.sortable.droptargetList=b.item.parent();var d=b.item.sortable._getElementContext(c);b.item.sortable.droptargetModel=d.scope.ngModel,g.sortable("cancel")}var e=!b.item.sortable.received&&s(g,b,A);e&&e.length&&(A=A.not(e));var h=b.item.sortable._getElementContext(g);A.appendTo(h.savedNodesOrigin),b.item.sortable.received&&(A=null),b.item.sortable.received&&!b.item.sortable.isCanceled()&&(f.$apply(function(){i.$modelValue.splice(b.item.sortable.dropindex,0,b.item.sortable.moved)}),f.$emit("ui-sortable:moved",b))},E.stop=function(a,c){var d="dropindex"in c.item.sortable&&!c.item.sortable.isCanceled();if(d&&!c.item.sortable.received)f.$apply(function(){i.$modelValue.splice(c.item.sortable.dropindex,0,i.$modelValue.splice(c.item.sortable.index,1)[0])}),f.$emit("ui-sortable:moved",c);else if(!d&&!b.equals(g.contents().toArray(),A.toArray())){var e=s(g,c,A);e&&e.length&&(A=A.not(e));var h=c.item.sortable._getElementContext(g);A.appendTo(h.savedNodesOrigin)}A=null,B=null},E.receive=function(a,b){b.item.sortable.received=!0},E.remove=function(a,b){"dropindex"in b.item.sortable||(g.sortable("cancel"),b.item.sortable.cancel()),b.item.sortable.isCanceled()||f.$apply(function(){b.item.sortable.moved=i.$modelValue.splice(b.item.sortable.index,1)[0]})},b.forEach(E,function(a,b){E[b]=j(E[b],function(){var a,c=f[b];"function"==typeof c&&("uiSortable"+b.substring(0,1).toUpperCase()+b.substring(1)).length&&"function"==typeof(a=c())&&a.apply(this,arguments)})}),F.helper=function(a){return a&&"function"==typeof a?function(d,e){var f=e.sortable,h=w(e);e.sortable={model:i.$modelValue[h],index:h,source:g,sourceList:e.parent(),sourceModel:i.$modelValue,_restore:function(){b.forEach(e.sortable,function(a,b){e.sortable[b]=c}),e.sortable=f}};var j=a.apply(this,arguments);return e.sortable._restore(),e.sortable._isCustomHelperUsed=e!==j,j}:a},f.$watchCollection("uiSortable",function(a,b){var c=k(g);if(c){var d=o(a,b,c);d&&g.sortable("option",d)}},!0),o(C)}function y(){i?x():e.info("ui.sortable: ngModel not provided!",g),g.sortable(C)}function z(){return f.uiSortable&&f.uiSortable.disabled?!1:(y(),z.cancelWatcher(),z.cancelWatcher=b.noop,!0)}var A,B,C={},D={"ui-floating":c,"ui-model-items":a.items,"ui-preserve-size":c},E={create:null,start:null,activate:null,beforeStop:null,update:null,remove:null,receive:null,deactivate:null,stop:null},F={helper:null};return b.extend(C,D,a,f.uiSortable),b.element.fn&&b.element.fn.jquery?(z.cancelWatcher=b.noop,void(z()||(z.cancelWatcher=f.$watch("uiSortable.disabled",z)))):void e.error("ui.sortable: jQuery should be included before AngularJS!")}}}])}(window,window.angular);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.