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