Merge pull request #4757 from davecivicrm/CRM-15409
[civicrm-core.git] / js / angular-crm-ui.js
CommitLineData
685acae4 1/// crmUi: Sundry UI helpers
2(function (angular, $, _) {
3
f8601d61
TO
4 var uidCount = 0;
5
6717e4b9
TO
6 var partialUrl = function (relPath) {
7 return CRM.resourceUrls['civicrm'] + '/partials/crmUi/' + relPath;
8 };
9
685acae4 10 angular.module('crmUi', [])
030dce01 11
0cbed02c
TO
12 // example <div crm-ui-accordion crm-title="ts('My Title')" crm-collapsed="true">...content...</div>
13 // WISHLIST: crmCollapsed should support two-way/continous binding
030dce01
TO
14 .directive('crmUiAccordion', function() {
15 return {
16 scope: {
0cbed02c
TO
17 crmTitle: '@',
18 crmCollapsed: '@'
030dce01 19 },
0cbed02c 20 template: '<div class="crm-accordion-wrapper" ng-class="cssClasses"><div class="crm-accordion-header">{{$parent.$eval(crmTitle)}}</div><div class="crm-accordion-body" ng-transclude></div></div>',
030dce01 21 transclude: true,
0cbed02c
TO
22 link: function (scope, element, attrs) {
23 scope.cssClasses = {
24 collapsed: scope.$parent.$eval(attrs.crmCollapsed)
25 };
26 }
030dce01
TO
27 };
28 })
29
510d515d
TO
30 // example: <input crm-ui-date="myobj.datefield" />
31 // example: <input crm-ui-date="myobj.datefield" crm-ui-date-format="yy-mm-dd" />
3074caa9 32 .directive('crmUiDate', function ($parse, $timeout) {
510d515d
TO
33 return {
34 restrict: 'AE',
35 scope: {
36 crmUiDate: '@', // expression, model binding
37 crmUiDateFormat: '@' // expression, date format (default: "yy-mm-dd")
38 },
39 link: function (scope, element, attrs) {
40 var fmt = attrs.crmUiDateFormat ? $parse(attrs.crmUiDateFormat)() : "yy-mm-dd";
41 var model = $parse(attrs.crmUiDate);
510d515d
TO
42
43 element.addClass('dateplugin');
44 $(element).datepicker({
45 dateFormat: fmt
46 });
47
48 var updateChildren = (function() {
3074caa9
TO
49 element.off('change', updateParent);
50 $(element).datepicker('setDate', model(scope.$parent));
51 element.on('change', updateParent);
510d515d
TO
52 });
53 var updateParent = (function() {
54 $timeout(function () {
55 model.assign(scope.$parent, $(element).val());
56 });
57 });
58
59 updateChildren();
3074caa9
TO
60 scope.$parent.$watch(attrs.crmUiDate, updateChildren);
61 element.on('change', updateParent);
510d515d
TO
62 }
63 };
64 })
65
66 // example: <div crm-ui-date-time="myobj.mydatetimefield"></div>
3074caa9 67 .directive('crmUiDateTime', function ($parse) {
510d515d
TO
68 return {
69 restrict: 'AE',
70 scope: {
71 crmUiDateTime: '@'
72 },
73 template: '<input crm-ui-date="dtparts.date" placeholder="{{dateLabel}}"/> <input crm-ui-time="dtparts.time" placeholder="{{timeLabel}}"/>',
74 link: function (scope, element, attrs) {
75 var model = $parse(attrs.crmUiDateTime);
510d515d
TO
76 scope.dateLabel = ts('Date');
77 scope.timeLabel = ts('Time');
78
79 var updateChildren = (function () {
80 var value = model(scope.$parent);
81 if (value) {
82 var dtparts = value.split(/ /);
83 scope.dtparts = {date: dtparts[0], time: dtparts[1]};
84 }
85 else {
86 scope.dtparts = {date: '', time: ''};
87 }
88 });
89 var updateParent = (function () {
3074caa9 90 model.assign(scope.$parent, scope.dtparts.date + " " + scope.dtparts.time);
510d515d
TO
91 });
92
93 updateChildren();
3074caa9
TO
94 scope.$parent.$watch(attrs.crmUiDateTime, updateChildren);
95 scope.$watch('dtparts.date', updateParent),
96 scope.$watch('dtparts.time', updateParent)
510d515d
TO
97 }
98 };
99 })
100
489c2674
TO
101 // Display a field/row in a field list
102 // example: <div crm-ui-field crm-title="My Field"> {{mydata}} </div>
f8601d61
TO
103 // example: <div crm-ui-field="subform.myfield" crm-title="'My Field'"> <input crm-ui-id="subform.myfield" name="myfield" /> </div>
104 // example: <div crm-ui-field="subform.myfield" crm-title="'My Field'"> <input crm-ui-id="subform.myfield" name="myfield" required /> </div>
3cc9c048 105 .directive('crmUiField', function() {
489c2674
TO
106 // Note: When writing new templates, the "label" position is particular. See/patch "var label" below.
107 var templateUrls = {
108 default: partialUrl('field.html'),
109 checkbox: partialUrl('field-cb.html')
110 };
111
112 return {
f8601d61
TO
113 require: '^crmUiIdScope',
114 restrict: 'EA',
489c2674 115 scope: {
f8601d61
TO
116 crmUiField: '@',
117 crmTitle: '@'
489c2674
TO
118 },
119 templateUrl: function(tElement, tAttrs){
120 var layout = tAttrs.crmLayout ? tAttrs.crmLayout : 'default';
121 return templateUrls[layout];
122 },
123 transclude: true,
f8601d61 124 link: function (scope, element, attrs, crmUiIdCtrl) {
489c2674 125 $(element).addClass('crm-section');
489c2674 126 scope.crmUiField = attrs.crmUiField;
f8601d61
TO
127 scope.crmTitle = attrs.crmTitle;
128 }
129 };
130 })
131
132 // example: <div ng-form="subform" crm-ui-id-scope><label crm-ui-for="subform.foo">Foo:</label><input crm-ui-id="subform.foo" name="foo"/></div>
133 .directive('crmUiId', function () {
134 return {
135 require: '^crmUiIdScope',
136 restrict: 'EA',
3cc9c048
TO
137 link: {
138 pre: function (scope, element, attrs, crmUiIdCtrl) {
139 var id = crmUiIdCtrl.get(attrs.crmUiId);
140 element.attr('id', id);
141 }
f8601d61
TO
142 }
143 };
144 })
489c2674 145
f8601d61
TO
146 // example: <div ng-form="subform" crm-ui-id-scope><label crm-ui-for="subform.foo">Foo:</label><input crm-ui-id="subform.foo" name="foo"/></div>
147 .directive('crmUiFor', function ($parse, $timeout) {
148 return {
149 require: '^crmUiIdScope',
150 restrict: 'EA',
151 template: '<span ng-class="cssClasses"><span ng-transclude/><span crm-ui-visible="crmIsRequired" class="crm-marker" title="This field is required.">*</span></span>',
152 transclude: true,
153 link: function (scope, element, attrs, crmUiIdCtrl) {
154 scope.crmIsRequired = false;
155 scope.cssClasses = {};
489c2674 156
f8601d61 157 if (!attrs.crmUiFor) return;
489c2674 158
f8601d61
TO
159 var id = crmUiIdCtrl.get(attrs.crmUiFor);
160 element.attr('for', id);
161 var ngModel = null;
489c2674 162
f8601d61
TO
163 var updateCss = function () {
164 scope.cssClasses['crm-error'] = !ngModel.$valid && !ngModel.$pristine;
165 };
489c2674 166
f8601d61
TO
167 // Note: if target element is dynamically generated (eg via ngInclude), then it may not be available
168 // immediately for initialization. Use retries/retryDelay to initialize such elements.
169 var init = function (retries, retryDelay) {
170 var input = $('#' + id);
171 if (input.length == 0) {
172 if (retries) {
173 $timeout(function(){
174 init(retries-1, retryDelay);
175 }, retryDelay);
176 }
177 return;
178 }
489c2674 179
f8601d61
TO
180 var tgtScope = scope;//.$parent;
181 if (attrs.crmDepth) {
182 for (var i = attrs.crmDepth; i > 0; i--) {
183 tgtScope = tgtScope.$parent;
184 }
185 }
489c2674 186
f8601d61
TO
187 if (input.attr('ng-required')) {
188 scope.crmIsRequired = scope.$parent.$eval(input.attr('ng-required'));
189 scope.$parent.$watch(input.attr('ng-required'), function (isRequired) {
190 scope.crmIsRequired = isRequired;
191 });
192 }
193 else {
194 scope.crmIsRequired = input.prop('required')
195 }
489c2674 196
f8601d61
TO
197 ngModel = $parse(attrs.crmUiFor)(tgtScope);
198 if (ngModel) {
199 ngModel.$viewChangeListeners.push(updateCss);
200 }
201 };
489c2674 202
f8601d61
TO
203 $timeout(function(){
204 init(3, 100);
489c2674
TO
205 });
206 }
207 };
208 })
209
f8601d61
TO
210 // example: <div ng-form="subform" crm-ui-id-scope><label crm-ui-for="subform.foo">Foo:</label><input crm-ui-id="subform.foo" name="foo"/></div>
211 .directive('crmUiIdScope', function () {
212 return {
213 restrict: 'EA',
214 scope: {},
215 controllerAs: 'crmUiIdCtrl',
216 controller: function($scope) {
217 var ids = {};
218 this.get = function(name) {
219 if (!ids[name]) {
220 ids[name] = "crmUiId_" + (++uidCount);
221 }
222 return ids[name];
223 }
224 },
225 link: function (scope, element, attrs) {}
226 };
227 })
228
107c5cc7
TO
229 // example: <iframe crm-ui-iframe="getHtmlContent()"></iframe>
230 .directive('crmUiIframe', function ($parse) {
231 return {
232 scope: {
233 crmUiIframe: '@' // expression which evalutes to HTML content
234 },
235 link: function (scope, elm, attrs) {
236 var iframe = $(elm)[0];
237 iframe.setAttribute('width', '100%');
238 iframe.setAttribute('frameborder', '0');
239
240 var refresh = function () {
241 // var iframeHtml = '<html><head><base target="_blank"></head><body onload="parent.document.getElementById(\'' + iframe.id + '\').style.height=document.body.scrollHeight + \'px\'"><scr' + 'ipt type="text/javascript" src="https://gist.github.com/' + iframeId + '.js"></sc' + 'ript></body></html>';
242 var iframeHtml = scope.$parent.$eval(attrs.crmUiIframe);
243
244 var doc = iframe.document;
245 if (iframe.contentDocument) {
246 doc = iframe.contentDocument;
247 }
248 else if (iframe.contentWindow) {
249 doc = iframe.contentWindow.document;
250 }
251
252 doc.open();
253 doc.writeln(iframeHtml);
254 doc.close();
255 }
256
257 scope.$parent.$watch(attrs.crmUiIframe, refresh);
258 //setTimeout(function () { refresh(); }, 50);
259 }
260 };
261 })
262
3cc9c048
TO
263 // example: <textarea crm-ui-id="myForm.body_html" crm-ui-richtext name="body_html" ng-model="mailing.body_html"></textarea>
264 .directive('crmUiRichtext', function ($timeout) {
38737af8
TO
265 return {
266 require: '?ngModel',
267 link: function (scope, elm, attr, ngModel) {
38737af8
TO
268 var ck = CKEDITOR.replace(elm[0]);
269
270 if (!ngModel) {
271 return;
272 }
273
274 ck.on('pasteState', function () {
275 scope.$apply(function () {
276 ngModel.$setViewValue(ck.getData());
277 });
278 });
279
280 ck.on('insertText', function () {
281 $timeout(function () {
282 ngModel.$setViewValue(ck.getData());
283 });
284 });
285
286 ngModel.$render = function (value) {
287 ck.setData(ngModel.$viewValue);
288 };
289 }
290 };
291 })
292
685acae4 293 // example: <a crm-ui-lock binding="mymodel.boolfield"></a>
294 // example: <a crm-ui-lock
295 // binding="mymodel.boolfield"
296 // title-locked="ts('Boolfield is locked')"
297 // title-unlocked="ts('Boolfield is unlocked')"></a>
298 .directive('crmUiLock', function ($parse, $rootScope) {
299 var defaultVal = function (defaultValue) {
300 var f = function (scope) {
301 return defaultValue;
302 }
303 f.assign = function (scope, value) {
304 // ignore changes
305 }
306 return f;
307 };
308
309 // like $parse, but accepts a defaultValue in case expr is undefined
310 var parse = function (expr, defaultValue) {
311 return expr ? $parse(expr) : defaultVal(defaultValue);
312 };
313
314 return {
315 template: '',
316 link: function (scope, element, attrs) {
317 var binding = parse(attrs['binding'], true);
318 var titleLocked = parse(attrs['titleLocked'], ts('Locked'));
319 var titleUnlocked = parse(attrs['titleUnlocked'], ts('Unlocked'));
320
321 $(element).addClass('ui-icon lock-button');
322 var refresh = function () {
323 var locked = binding(scope);
324 if (locked) {
325 $(element)
326 .removeClass('ui-icon-unlocked')
327 .addClass('ui-icon-locked')
328 .prop('title', titleLocked(scope))
329 ;
330 }
331 else {
332 $(element)
333 .removeClass('ui-icon-locked')
334 .addClass('ui-icon-unlocked')
335 .prop('title', titleUnlocked(scope))
336 ;
337 }
338 };
339
340 $(element).click(function () {
341 binding.assign(scope, !binding(scope));
342 //scope.$digest();
343 $rootScope.$digest();
344 });
345
346 scope.$watch(attrs.binding, refresh);
347 scope.$watch(attrs.titleLocked, refresh);
348 scope.$watch(attrs.titleUnlocked, refresh);
349
350 refresh();
351 }
352 };
353 })
030dce01 354
ebfe3efb
TO
355 // usage: <select crm-ui-select="{placeholder:'Something',allowClear:true,...}" ng-model="myobj.field"><option...></select>
356 .directive('crmUiSelect', function ($parse, $timeout) {
8c632f2b 357 return {
ebfe3efb 358 require: '?ngModel',
8c632f2b 359 scope: {
ebfe3efb 360 crmUiSelect: '@'
8c632f2b 361 },
ebfe3efb 362 link: function (scope, element, attrs, ngModel) {
8c632f2b
TO
363 // In cases where UI initiates update, there may be an extra
364 // call to refreshUI, but it doesn't create a cycle.
365
ebfe3efb
TO
366 ngModel.$render = function () {
367 $timeout(function () {
368 // ex: msg_template_id adds new item then selects it; use $timeout to ensure that
369 // new item is added before selection is made
370 $(element).select2('val', ngModel.$viewValue);
371 });
372 };
8c632f2b 373 function refreshModel() {
ebfe3efb 374 var oldValue = ngModel.$viewValue, newValue = $(element).select2('val');
8c632f2b 375 if (oldValue != newValue) {
ebfe3efb
TO
376 scope.$parent.$apply(function () {
377 ngModel.$setViewValue(newValue);
8c632f2b 378 });
8c632f2b
TO
379 }
380 }
ebfe3efb 381
8c632f2b
TO
382 function init() {
383 // TODO watch select2-options
384 var options = attrs.crmUiSelect ? scope.$parent.$eval(attrs.crmUiSelect) : {};
385 $(element).select2(options);
386 $(element).on('change', refreshModel);
ebfe3efb 387 $timeout(ngModel.$render);
8c632f2b 388 }
ebfe3efb 389
8c632f2b
TO
390 init();
391 }
392 };
393 })
394
030dce01 395 // example <div crm-ui-tab crm-title="ts('My Title')">...content...</div>
5f3568fd 396 // WISHLIST: use a full Angular component instead of an incomplete jQuery wrapper
030dce01
TO
397 .directive('crmUiTab', function($parse) {
398 return {
5f3568fd
TO
399 require: '^crmUiTabSet',
400 restrict: 'EA',
030dce01 401 scope: {
5f3568fd
TO
402 crmTitle: '@',
403 id: '@'
030dce01 404 },
5f3568fd 405 template: '<div ng-transclude></div>',
030dce01 406 transclude: true,
5f3568fd
TO
407 link: function (scope, element, attrs, crmUiTabSetCtrl) {
408 crmUiTabSetCtrl.add(scope);
409 }
030dce01
TO
410 };
411 })
412
413 // example: <div crm-ui-tab-set><div crm-ui-tab crm-title="Tab 1">...</div><div crm-ui-tab crm-title="Tab 2">...</div></div>
414 .directive('crmUiTabSet', function() {
415 return {
5f3568fd
TO
416 restrict: 'EA',
417 scope: {
418 crmUiTabSet: '@'
419 },
420 templateUrl: partialUrl('tabset.html'),
030dce01 421 transclude: true,
5f3568fd
TO
422 controllerAs: 'crmUiTabSetCtrl',
423 controller: function($scope, $parse) {
424 var tabs = $scope.tabs = []; // array<$scope>
425 this.add = function(tab) {
426 if (!tab.id) throw "Tab is missing 'id'";
427 tabs.push(tab);
428 };
429 },
030dce01
TO
430 link: function (scope, element, attrs) {}
431 };
432 })
433
510d515d 434 // example: <input crm-ui-time="myobj.mytimefield" />
3074caa9 435 .directive('crmUiTime', function ($parse, $timeout) {
510d515d
TO
436 return {
437 restrict: 'AE',
438 scope: {
439 crmUiTime: '@'
440 },
441 link: function (scope, element, attrs) {
442 var model = $parse(attrs.crmUiTime);
510d515d
TO
443
444 element.addClass('crm-form-text six');
445 $(element).timeEntry({show24Hours: true});
446
447 var updateChildren = (function() {
3074caa9
TO
448 element.off('change', updateParent);
449 $(element).timeEntry('setTime', model(scope.$parent));
450 element.on('change', updateParent);
510d515d
TO
451 });
452 var updateParent = (function () {
453 $timeout(function () {
454 model.assign(scope.$parent, element.val());
455 });
456 });
457
458 updateChildren();
3074caa9
TO
459 scope.$parent.$watch(attrs.crmUiTime, updateChildren);
460 element.on('change', updateParent);
510d515d
TO
461 }
462 }
463 })
464
e84a11d8
TO
465 // like ng-show, but hides/displays elements using "visibility" which maintains positioning
466 // example <div crm-ui-visible="false">...content...</div>
467 .directive('crmUiVisible', function($parse) {
468 return {
469 restrict: 'EA',
470 scope: {
471 crmUiVisible: '@'
472 },
473 link: function (scope, element, attrs) {
474 var model = $parse(attrs.crmUiVisible);
475 function updatecChildren() {
476 element.css('visibility', model(scope.$parent) ? 'inherit' : 'hidden');
477 }
478 updatecChildren();
479 scope.$parent.$watch(attrs.crmUiVisible, updatecChildren);
480 }
481 };
482 })
483
6717e4b9
TO
484 // example: <div crm-ui-wizard="myWizardCtrl"><div crm-ui-wizard-step crm-title="ts('Step 1')">...</div><div crm-ui-wizard-step crm-title="ts('Step 2')">...</div></div>
485 // Note: "myWizardCtrl" has various actions/properties like next() and $first().
486 // WISHLIST: Allow each step to determine if it is "complete" / "valid" / "selectable"
487 // WISHLIST: Allow each step to enable/disable (show/hide) itself
030dce01
TO
488 .directive('crmUiWizard', function() {
489 return {
6717e4b9
TO
490 restrict: 'EA',
491 scope: {
492 crmUiWizard: '@'
493 },
494 templateUrl: partialUrl('wizard.html'),
030dce01 495 transclude: true,
6717e4b9
TO
496 controllerAs: 'crmUiWizardCtrl',
497 controller: function($scope, $parse) {
498 var steps = $scope.steps = []; // array<$scope>
499 var crmUiWizardCtrl = this;
500 var maxVisited = 0;
501 var selectedIndex = null;
502
503 var findIndex = function() {
504 var found = null;
505 angular.forEach(steps, function(step, stepKey) {
506 if (step.selected) found = stepKey;
507 });
508 return found;
509 };
510
511 /// @return int the index of the current step
512 this.$index = function() { return selectedIndex; };
513 /// @return bool whether the currentstep is first
514 this.$first = function() { return this.$index() === 0; };
515 /// @return bool whether the current step is last
516 this.$last = function() { return this.$index() === steps.length -1; };
517 this.$maxVisit = function() { return maxVisited; }
518 this.iconFor = function(index) {
519 if (index < this.$index()) return '√';
520 if (index === this.$index()) return '»';
521 return ' ';
522 }
523 this.isSelectable = function(step) {
524 if (step.selected) return false;
525 var result = false;
526 angular.forEach(steps, function(otherStep, otherKey) {
527 if (step === otherStep && otherKey <= maxVisited) result = true;
528 });
529 return result;
530 };
531
532 /*** @param Object step the $scope of the step */
533 this.select = function(step) {
534 angular.forEach(steps, function(otherStep, otherKey) {
535 otherStep.selected = (otherStep === step);
536 if (otherStep === step && maxVisited < otherKey) maxVisited = otherKey;
537 });
538 selectedIndex = findIndex();
539 };
540 /*** @param Object step the $scope of the step */
541 this.add = function(step) {
542 if (steps.length === 0) {
543 step.selected = true;
544 selectedIndex = 0;
545 }
546 steps.push(step);
8688b463
TO
547 steps.sort(function(a,b){
548 return a.crmUiWizardStep - b.crmUiWizardStep;
549 });
550 selectedIndex = findIndex();
551 };
552 this.remove = function(step) {
553 var key = null;
554 angular.forEach(steps, function(otherStep, otherKey) {
555 if (otherStep === step) key = otherKey;
556 });
557 if (key != null) {
558 steps.splice(key, 1);
559 }
6717e4b9
TO
560 };
561 this.goto = function(index) {
562 if (index < 0) index = 0;
563 if (index >= steps.length) index = steps.length-1;
564 this.select(steps[index]);
565 };
566 this.previous = function() { this.goto(this.$index()-1); };
567 this.next = function() { this.goto(this.$index()+1); };
568 if ($scope.crmUiWizard) {
569 $parse($scope.crmUiWizard).assign($scope.$parent, this)
570 }
571 },
030dce01
TO
572 link: function (scope, element, attrs) {}
573 };
574 })
575
6717e4b9
TO
576 // Use this to add extra markup to wizard
577 .directive('crmUiWizardButtons', function() {
030dce01 578 return {
6717e4b9
TO
579 require: '^crmUiWizard',
580 restrict: 'EA',
581 scope: {},
582 template: '<span ng-transclude></span>',
030dce01 583 transclude: true,
6717e4b9
TO
584 link: function (scope, element, attrs, crmUiWizardCtrl) {
585 var realButtonsEl = $(element).closest('.crm-wizard').find('.crm-wizard-buttons');
586 $(element).appendTo(realButtonsEl);
587 }
030dce01
TO
588 };
589 })
590
8688b463
TO
591 // example: <div crm-ui-wizard-step crm-title="ts('My Title')">...content...</div>
592 // If there are any conditional steps, then be sure to set a weight explicitly on *all* steps to maintain ordering.
593 // example: <div crm-ui-wizard-step="100" crm-title="..." ng-if="...">...content...</div>
030dce01 594 .directive('crmUiWizardStep', function() {
8688b463 595 var nextWeight = 1;
030dce01 596 return {
6717e4b9
TO
597 require: '^crmUiWizard',
598 restrict: 'EA',
030dce01 599 scope: {
8688b463
TO
600 crmTitle: '@', // expression, evaluates to a printable string
601 crmUiWizardStep: '@' // int, a weight which determines the ordering of the steps
030dce01 602 },
6717e4b9 603 template: '<div class="crm-wizard-step" ng-show="selected" ng-transclude/></div>',
030dce01 604 transclude: true,
6717e4b9 605 link: function (scope, element, attrs, crmUiWizardCtrl) {
8688b463
TO
606 if (scope.crmUiWizardStep) {
607 scope.crmUiWizardStep = parseInt(scope.crmUiWizardStep);
608 } else {
609 scope.crmUiWizardStep = nextWeight++;
610 }
6717e4b9 611 crmUiWizardCtrl.add(scope);
8688b463
TO
612 element.on('$destroy', function(){
613 crmUiWizardCtrl.remove(scope);
614 });
6717e4b9 615 }
030dce01
TO
616 };
617 })
618
5fb5b3cf 619 // Example: <button crm-confirm="{message: ts('Are you sure you want to continue?')}" on-yes="frobnicate(123)">Frobincate</button>
4b8c8b42
TO
620 // Example: <button crm-confirm="{type: 'disable', obj: myObject}" on-yes="myObject.is_active=0; myObject.save()">Disable</button>
621 .directive('crmConfirm', function () {
88fcc9f1 622 // Helpers to calculate default options for CRM.confirm()
4b8c8b42
TO
623 var defaultFuncs = {
624 'disable': function (options) {
625 return {
626 message: ts('Are you sure you want to disable this?'),
627 options: {no: ts('Cancel'), yes: ts('Disable')},
628 width: 300,
629 title: ts('Disable %1?', {
630 1: options.obj.title || options.obj.label || options.obj.name || ts('the record')
631 })
632 };
633 },
470a458e
TO
634 'revert': function (options) {
635 return {
636 message: ts('Are you sure you want to revert this?'),
637 options: {no: ts('Cancel'), yes: ts('Revert')},
638 width: 300,
639 title: ts('Revert %1?', {
640 1: options.obj.title || options.obj.label || options.obj.name || ts('the record')
641 })
642 };
643 },
4b8c8b42
TO
644 'delete': function (options) {
645 return {
646 message: ts('Are you sure you want to delete this?'),
647 options: {no: ts('Cancel'), yes: ts('Delete')},
648 width: 300,
649 title: ts('Delete %1?', {
650 1: options.obj.title || options.obj.label || options.obj.name || ts('the record')
651 })
652 };
653 }
654 };
655 return {
656 template: '',
657 link: function (scope, element, attrs) {
658 $(element).click(function () {
659 var options = scope.$eval(attrs['crmConfirm']);
660 var defaults = (options.type) ? defaultFuncs[options.type](options) : {};
661 CRM.confirm(_.extend(defaults, options))
662 .on('crmConfirm:yes', function () { scope.$apply(attrs['onYes']); })
663 .on('crmConfirm:no', function () { scope.$apply(attrs['onNo']); });
664 });
665 }
666 };
667 })
02308c07
TO
668 .run(function($rootScope, $location) {
669 /// Example: <button ng-click="goto('home')">Go home!</button>
670 $rootScope.goto = function(path) {
671 $location.path(path);
672 };
4b8c8b42 673 // useful for debugging: $rootScope.log = console.log || function() {};
02308c07 674 })
685acae4 675 ;
676
5fb5b3cf 677})(angular, CRM.$, CRM._);