1 /// crmUi: Sundry UI helpers
2 (function (angular
, $, _
) {
6 angular
.module('crmUi', [])
8 // example <div crm-ui-accordion crm-title="ts('My Title')" crm-collapsed="true">...content...</div>
9 // WISHLIST: crmCollapsed should support two-way/continous binding
10 .directive('crmUiAccordion', function() {
16 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>',
18 link: function (scope
, element
, attrs
) {
20 collapsed
: scope
.$parent
.$eval(attrs
.crmCollapsed
)
27 // crmUiAlert({text: 'My text', title: 'My title', type: 'error'});
28 // crmUiAlert({template: '<a ng-click="ok()">Hello</a>', scope: $scope.$new()});
29 // var h = crmUiAlert({templateUrl: '~/crmFoo/alert.html', scope: $scope.$new()});
31 .service('crmUiAlert', function($compile
, $rootScope
, $templateRequest
, $q
) {
33 return function crmUiAlert(params
) {
34 var id
= 'crmUiAlert_' + (++count
);
36 if (params
.templateUrl
) {
37 tpl
= $templateRequest(params
.templateUrl
);
39 else if (params
.template
) {
40 tpl
= params
.template
;
43 params
.text
= '<div id="' + id
+ '"></div>'; // temporary stub
45 var result
= CRM
.alert(params
.text
, params
.title
, params
.type
, params
.options
);
47 $q
.when(tpl
, function(html
) {
48 var scope
= params
.scope
|| $rootScope
.$new();
49 var linker
= $compile(html
);
50 $('#' + id
).append($(linker(scope
)));
57 // Display a date widget.
58 // example: <input crm-ui-date ng-model="myobj.datefield" />
59 // example: <input crm-ui-date ng-model="myobj.datefield" crm-ui-date-format="yy-mm-dd" />
60 .directive('crmUiDate', function ($parse
, $timeout
) {
65 crmUiDateFormat
: '@' // expression, date format (default: "yy-mm-dd")
67 link: function (scope
, element
, attrs
, ngModel
) {
68 var fmt
= attrs
.crmUiDateFormat
? $parse(attrs
.crmUiDateFormat
)() : "yy-mm-dd";
70 element
.addClass('dateplugin');
71 $(element
).datepicker({
75 ngModel
.$render
= function $render() {
76 $(element
).datepicker('setDate', ngModel
.$viewValue
);
78 var updateParent
= (function() {
79 $timeout(function () {
80 ngModel
.$setViewValue(element
.val());
84 element
.on('change', updateParent
);
89 // Display a date-time widget.
90 // example: <div crm-ui-date-time ng-model="myobj.mydatetimefield"></div>
91 .directive('crmUiDateTime', function ($parse
) {
98 templateUrl
: '~/crmUi/datetime.html',
99 link: function (scope
, element
, attrs
, ngModel
) {
100 var ts
= scope
.ts
= CRM
.ts(null);
101 scope
.dateLabel
= ts('Date');
102 scope
.timeLabel
= ts('Time');
103 element
.addClass('crm-ui-datetime');
105 ngModel
.$render
= function $render() {
106 if (!_
.isEmpty(ngModel
.$viewValue
)) {
107 var dtparts
= ngModel
.$viewValue
.split(/ /);
108 scope
.dtparts
= {date
: dtparts
[0], time
: dtparts
[1]};
111 scope
.dtparts
= {date
: '', time
: ''};
116 function updateParent() {
119 if (_
.isEmpty(scope
.dtparts
.date
) && _
.isEmpty(scope
.dtparts
.time
)) {
120 ngModel
.$setViewValue(' ');
123 //ngModel.$setViewValue(scope.dtparts.date + ' ' + scope.dtparts.time);
124 ngModel
.$setViewValue((scope
.dtparts
.date
? scope
.dtparts
.date
: '') + ' ' + (scope
.dtparts
.time
? scope
.dtparts
.time
: ''));
128 scope
.$watch('dtparts.date', updateParent
);
129 scope
.$watch('dtparts.time', updateParent
);
131 function validate() {
132 var incompleteDateTime
= _
.isEmpty(scope
.dtparts
.date
) ^ _
.isEmpty(scope
.dtparts
.time
);
133 ngModel
.$setValidity('incompleteDateTime', !incompleteDateTime
);
136 function updateRequired() {
137 scope
.required
= scope
.$parent
.$eval(attrs
.ngRequired
);
140 if (attrs
.ngRequired
) {
142 scope
.$parent
.$watch(attrs
.ngRequired
, updateRequired
);
145 scope
.reset
= function reset() {
146 scope
.dtparts
= {date
: '', time
: ''};
147 ngModel
.$setViewValue('');
153 // Display a field/row in a field list
154 // example: <div crm-ui-field crm-title="My Field"> {{mydata}} </div>
155 // example: <div crm-ui-field="subform.myfield" crm-title="'My Field'"> <input crm-ui-id="subform.myfield" name="myfield" /> </div>
156 // example: <div crm-ui-field="subform.myfield" crm-title="'My Field'"> <input crm-ui-id="subform.myfield" name="myfield" required /> </div>
157 .directive('crmUiField', function() {
158 // Note: When writing new templates, the "label" position is particular. See/patch "var label" below.
160 default: '~/crmUi/field.html',
161 checkbox
: '~/crmUi/field-cb.html'
165 require
: '^crmUiIdScope',
171 templateUrl: function(tElement
, tAttrs
){
172 var layout
= tAttrs
.crmLayout
? tAttrs
.crmLayout
: 'default';
173 return templateUrls
[layout
];
176 link: function (scope
, element
, attrs
, crmUiIdCtrl
) {
177 $(element
).addClass('crm-section');
178 scope
.crmUiField
= attrs
.crmUiField
;
179 scope
.crmTitle
= attrs
.crmTitle
;
184 // 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>
185 .directive('crmUiId', function () {
187 require
: '^crmUiIdScope',
190 pre: function (scope
, element
, attrs
, crmUiIdCtrl
) {
191 var id
= crmUiIdCtrl
.get(attrs
.crmUiId
);
192 element
.attr('id', id
);
198 // 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>
199 .directive('crmUiFor', function ($parse
, $timeout
) {
201 require
: '^crmUiIdScope',
203 template
: '<span ng-class="cssClasses"><span ng-transclude/><span crm-ui-visible="crmIsRequired" class="crm-marker" title="This field is required.">*</span></span>',
205 link: function (scope
, element
, attrs
, crmUiIdCtrl
) {
206 scope
.crmIsRequired
= false;
207 scope
.cssClasses
= {};
209 if (!attrs
.crmUiFor
) return;
211 var id
= crmUiIdCtrl
.get(attrs
.crmUiFor
);
212 element
.attr('for', id
);
215 var updateCss = function () {
216 scope
.cssClasses
['crm-error'] = !ngModel
.$valid
&& !ngModel
.$pristine
;
219 // Note: if target element is dynamically generated (eg via ngInclude), then it may not be available
220 // immediately for initialization. Use retries/retryDelay to initialize such elements.
221 var init = function (retries
, retryDelay
) {
222 var input
= $('#' + id
);
223 if (input
.length
=== 0) {
226 init(retries
-1, retryDelay
);
232 var tgtScope
= scope
;//.$parent;
233 if (attrs
.crmDepth
) {
234 for (var i
= attrs
.crmDepth
; i
> 0; i
--) {
235 tgtScope
= tgtScope
.$parent
;
239 if (input
.attr('ng-required')) {
240 scope
.crmIsRequired
= scope
.$parent
.$eval(input
.attr('ng-required'));
241 scope
.$parent
.$watch(input
.attr('ng-required'), function (isRequired
) {
242 scope
.crmIsRequired
= isRequired
;
246 scope
.crmIsRequired
= input
.prop('required');
249 ngModel
= $parse(attrs
.crmUiFor
)(tgtScope
);
251 ngModel
.$viewChangeListeners
.push(updateCss
);
262 // Define a scope in which a name like "subform.foo" maps to a unique ID.
263 // 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>
264 .directive('crmUiIdScope', function () {
268 controllerAs
: 'crmUiIdCtrl',
269 controller: function($scope
) {
271 this.get = function(name
) {
273 ids
[name
] = "crmUiId_" + (++uidCount
);
278 link: function (scope
, element
, attrs
) {}
282 // Display an HTML blurb inside an IFRAME.
283 // example: <iframe crm-ui-iframe="getHtmlContent()"></iframe>
284 .directive('crmUiIframe', function ($parse
) {
287 crmUiIframe
: '@' // expression which evalutes to HTML content
289 link: function (scope
, elm
, attrs
) {
290 var iframe
= $(elm
)[0];
291 iframe
.setAttribute('width', '100%');
292 iframe
.setAttribute('frameborder', '0');
294 var refresh = function () {
295 // 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>';
296 var iframeHtml
= scope
.$parent
.$eval(attrs
.crmUiIframe
);
298 var doc
= iframe
.document
;
299 if (iframe
.contentDocument
) {
300 doc
= iframe
.contentDocument
;
302 else if (iframe
.contentWindow
) {
303 doc
= iframe
.contentWindow
.document
;
307 doc
.writeln(iframeHtml
);
311 // If the iframe is in a dialog, respond to resize events
312 $(elm
).parent().on('dialogresize dialogopen', function(e
, ui
) {
313 $(this).css({padding
: '0', margin
: '0', overflow
: 'hidden'});
314 iframe
.setAttribute('height', '' + $(this).innerHeight() + 'px');
317 scope
.$parent
.$watch(attrs
.crmUiIframe
, refresh
);
323 // <a ng-click="$broadcast('my-insert-target', 'some new text')>Insert</a>
324 // <textarea crm-ui-insert-rx='my-insert-target'></textarea>
325 // TODO Consider ways to separate the plain-text/rich-text implementations
326 .directive('crmUiInsertRx', function() {
328 link: function(scope
, element
, attrs
) {
329 scope
.$on(attrs
.crmUiInsertRx
, function(e
, tokenName
) {
330 var id
= element
.attr('id');
331 if (CKEDITOR
.instances
[id
]) {
332 CKEDITOR
.instances
[id
].insertText(tokenName
);
333 $(element
).select2('close').select2('val', '');
334 CKEDITOR
.instances
[id
].focus();
337 var crmForEl
= $('#' + id
);
338 var origVal
= crmForEl
.val();
339 var origPos
= crmForEl
[0].selectionStart
;
340 var newVal
= origVal
.substring(0, origPos
) + tokenName
+ origVal
.substring(origPos
, origVal
.length
);
341 crmForEl
.val(newVal
);
342 var newPos
= (origPos
+ tokenName
.length
);
343 crmForEl
[0].selectionStart
= newPos
;
344 crmForEl
[0].selectionEnd
= newPos
;
346 $(element
).select2('close').select2('val', '');
347 crmForEl
.triggerHandler('change');
355 // Define a rich text editor.
356 // example: <textarea crm-ui-id="myForm.body_html" crm-ui-richtext name="body_html" ng-model="mailing.body_html"></textarea>
357 .directive('crmUiRichtext', function ($timeout
) {
360 link: function (scope
, elm
, attr
, ngModel
) {
361 var ck
= CKEDITOR
.replace(elm
[0]);
368 ck
.on('blur', function(){
370 scope
.$eval(attr
.ngBlur
);
375 ck
.on('pasteState', function () {
376 scope
.$apply(function () {
377 ngModel
.$setViewValue(ck
.getData());
381 ck
.on('insertText', function () {
382 $timeout(function () {
383 ngModel
.$setViewValue(ck
.getData());
387 ngModel
.$render = function (value
) {
388 ck
.setData(ngModel
.$viewValue
);
394 // Display a lock icon (based on a boolean).
395 // example: <a crm-ui-lock binding="mymodel.boolfield"></a>
396 // example: <a crm-ui-lock
397 // binding="mymodel.boolfield"
398 // title-locked="ts('Boolfield is locked')"
399 // title-unlocked="ts('Boolfield is unlocked')"></a>
400 .directive('crmUiLock', function ($parse
, $rootScope
) {
401 var defaultVal = function (defaultValue
) {
402 var f = function (scope
) {
405 f
.assign = function (scope
, value
) {
411 // like $parse, but accepts a defaultValue in case expr is undefined
412 var parse = function (expr
, defaultValue
) {
413 return expr
? $parse(expr
) : defaultVal(defaultValue
);
418 link: function (scope
, element
, attrs
) {
419 var binding
= parse(attrs
.binding
, true);
420 var titleLocked
= parse(attrs
.titleLocked
, ts('Locked'));
421 var titleUnlocked
= parse(attrs
.titleUnlocked
, ts('Unlocked'));
423 $(element
).addClass('ui-icon lock-button');
424 var refresh = function () {
425 var locked
= binding(scope
);
428 .removeClass('ui-icon-unlocked')
429 .addClass('ui-icon-locked')
430 .prop('title', titleLocked(scope
))
435 .removeClass('ui-icon-locked')
436 .addClass('ui-icon-unlocked')
437 .prop('title', titleUnlocked(scope
))
442 $(element
).click(function () {
443 binding
.assign(scope
, !binding(scope
));
445 $rootScope
.$digest();
448 scope
.$watch(attrs
.binding
, refresh
);
449 scope
.$watch(attrs
.titleLocked
, refresh
);
450 scope
.$watch(attrs
.titleUnlocked
, refresh
);
457 // CrmUiOrderCtrl is a controller class which manages sort orderings.
459 // JS: $scope.myOrder = new CrmUiOrderCtrl(['+field1', '-field2]);
460 // $scope.myOrder.toggle('field1');
461 // $scope.myOrder.setDir('field2', '');
462 // HTML: <tr ng-repeat="... | order:myOrder.get()">...</tr>
463 .service('CrmUiOrderCtrl', function(){
465 function CrmUiOrderCtrl(defaults
){
466 this.values
= defaults
;
468 angular
.extend(CrmUiOrderCtrl
.prototype, {
469 get: function get() {
472 getDir
: function getDir(name
) {
473 if (this.values
.indexOf(name
) >= 0 || this.values
.indexOf('+' + name
) >= 0) {
476 if (this.values
.indexOf('-' + name
) >= 0) {
481 // @return bool TRUE if something is removed
482 remove
: function remove(name
) {
483 var idx
= this.values
.indexOf(name
);
485 this.values
.splice(idx
, 1);
492 setDir
: function setDir(name
, dir
) {
493 return this.toggle(name
, dir
);
495 // Toggle sort order on a field.
496 // To set a specific order, pass optional parameter 'next' ('+', '-', or '').
497 toggle
: function toggle(name
, next
) {
498 if (!next
&& next
!== '') {
500 if (this.remove(name
) || this.remove('+' + name
)) {
503 if (this.remove('-' + name
)) {
509 this.values
.unshift('+' + name
);
511 else if (next
== '-') {
512 this.values
.unshift('-' + name
);
516 return CrmUiOrderCtrl
;
519 // Define a controller which manages sort order. You may interact with the controller
520 // directly ("myOrder.toggle('fieldname')") order using the helper, crm-ui-order-by.
522 // <span crm-ui-order="{var: 'myOrder', defaults: {'-myField'}}"></span>
523 // <th><a crm-ui-order-by="[myOrder,'myField']">My Field</a></th>
524 // <tr ng-repeat="... | order:myOrder.get()">...</tr>
525 // <button ng-click="myOrder.toggle('myField')">
526 .directive('crmUiOrder', function(CrmUiOrderCtrl
) {
528 link: function(scope
, element
, attrs
){
529 var options
= angular
.extend({var: 'crmUiOrderBy'}, scope
.$eval(attrs
.crmUiOrder
));
530 scope
[options
.var] = new CrmUiOrderCtrl(options
.defaults
);
535 // For usage, see crmUiOrder (above)
536 .directive('crmUiOrderBy', function() {
538 link: function(scope
, element
, attrs
) {
539 function updateClass(crmUiOrderCtrl
, name
) {
540 var dir
= crmUiOrderCtrl
.getDir(name
);
542 .toggleClass('sorting_asc', dir
=== '+')
543 .toggleClass('sorting_desc', dir
=== '-')
544 .toggleClass('sorting', dir
=== '');
547 element
.on('click', function(e
){
548 var tgt
= scope
.$eval(attrs
.crmUiOrderBy
);
549 tgt
[0].toggle(tgt
[1]);
550 updateClass(tgt
[0], tgt
[1]);
555 var tgt
= scope
.$eval(attrs
.crmUiOrderBy
);
556 updateClass(tgt
[0], tgt
[1]);
561 // Display a fancy SELECT (based on select2).
562 // usage: <select crm-ui-select="{placeholder:'Something',allowClear:true,...}" ng-model="myobj.field"><option...></select>
563 .directive('crmUiSelect', function ($parse
, $timeout
) {
569 link: function (scope
, element
, attrs
, ngModel
) {
570 // In cases where UI initiates update, there may be an extra
571 // call to refreshUI, but it doesn't create a cycle.
573 ngModel
.$render = function () {
574 $timeout(function () {
575 // ex: msg_template_id adds new item then selects it; use $timeout to ensure that
576 // new item is added before selection is made
577 element
.select2('val', ngModel
.$viewValue
);
580 function refreshModel() {
581 var oldValue
= ngModel
.$viewValue
, newValue
= element
.select2('val');
582 if (oldValue
!= newValue
) {
583 scope
.$parent
.$apply(function () {
584 ngModel
.$setViewValue(newValue
);
590 // TODO watch select2-options
591 var options
= attrs
.crmUiSelect
? scope
.$parent
.$eval(attrs
.crmUiSelect
) : {};
592 element
.select2(options
);
593 element
.on('change', refreshModel
);
594 $timeout(ngModel
.$render
);
602 // example <div crm-ui-tab crm-title="ts('My Title')">...content...</div>
603 // WISHLIST: use a full Angular component instead of an incomplete jQuery wrapper
604 .directive('crmUiTab', function($parse
) {
606 require
: '^crmUiTabSet',
612 template
: '<div ng-transclude></div>',
614 link: function (scope
, element
, attrs
, crmUiTabSetCtrl
) {
615 crmUiTabSetCtrl
.add(scope
);
620 // 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>
621 .directive('crmUiTabSet', function() {
627 templateUrl
: '~/crmUi/tabset.html',
629 controllerAs
: 'crmUiTabSetCtrl',
630 controller: function($scope
, $parse
) {
631 var tabs
= $scope
.tabs
= []; // array<$scope>
632 this.add = function(tab
) {
633 if (!tab
.id
) throw "Tab is missing 'id'";
637 link: function (scope
, element
, attrs
) {}
641 // Display a time-entry field.
642 // example: <input crm-ui-time ng-model="myobj.mytimefield" />
643 .directive('crmUiTime', function ($parse
, $timeout
) {
649 link: function (scope
, element
, attrs
, ngModel
) {
650 element
.addClass('crm-form-text six');
651 element
.timeEntry({show24Hours
: true});
653 ngModel
.$render
= function $render() {
654 element
.timeEntry('setTime', ngModel
.$viewValue
);
657 var updateParent
= (function () {
658 $timeout(function () {
659 ngModel
.$setViewValue(element
.val());
662 element
.on('change', updateParent
);
667 // Generic, field-independent form validator.
668 // example: <span ng-model="placeholder" crm-ui-validate="foo && bar || whiz" />
669 // example: <span ng-model="placeholder" crm-ui-validate="foo && bar || whiz" crm-ui-validate-name="myError" />
670 .directive('crmUiValidate', function() {
674 link: function(scope
, element
, attrs
, ngModel
) {
675 var validationKey
= attrs
.crmUiValidateName
? attrs
.crmUiValidateName
: 'crmUiValidate';
676 scope
.$watch(attrs
.crmUiValidate
, function(newValue
){
677 ngModel
.$setValidity(validationKey
, !!newValue
);
683 // like ng-show, but hides/displays elements using "visibility" which maintains positioning
684 // example <div crm-ui-visible="false">...content...</div>
685 .directive('crmUiVisible', function($parse
) {
691 link: function (scope
, element
, attrs
) {
692 var model
= $parse(attrs
.crmUiVisible
);
693 function updatecChildren() {
694 element
.css('visibility', model(scope
.$parent
) ? 'inherit' : 'hidden');
697 scope
.$parent
.$watch(attrs
.crmUiVisible
, updatecChildren
);
702 // 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>
703 // Note: "myWizardCtrl" has various actions/properties like next() and $first().
704 // WISHLIST: Allow each step to determine if it is "complete" / "valid" / "selectable"
705 // WISHLIST: Allow each step to enable/disable (show/hide) itself
706 .directive('crmUiWizard', function() {
712 templateUrl
: '~/crmUi/wizard.html',
714 controllerAs
: 'crmUiWizardCtrl',
715 controller: function($scope
, $parse
) {
716 var steps
= $scope
.steps
= []; // array<$scope>
717 var crmUiWizardCtrl
= this;
719 var selectedIndex
= null;
721 var findIndex = function() {
723 angular
.forEach(steps
, function(step
, stepKey
) {
724 if (step
.selected
) found
= stepKey
;
729 /// @return int the index of the current step
730 this.$index = function() { return selectedIndex
; };
731 /// @return bool whether the currentstep is first
732 this.$first = function() { return this.$index() === 0; };
733 /// @return bool whether the current step is last
734 this.$last = function() { return this.$index() === steps
.length
-1; };
735 this.$maxVisit = function() { return maxVisited
; };
736 this.$validStep = function() {
737 return steps
[selectedIndex
].isStepValid();
739 this.iconFor = function(index
) {
740 if (index
< this.$index()) return '√';
741 if (index
=== this.$index()) return '»';
744 this.isSelectable = function(step
) {
745 if (step
.selected
) return false;
747 angular
.forEach(steps
, function(otherStep
, otherKey
) {
748 if (step
=== otherStep
&& otherKey
<= maxVisited
) result
= true;
753 /*** @param Object step the $scope of the step */
754 this.select = function(step
) {
755 angular
.forEach(steps
, function(otherStep
, otherKey
) {
756 otherStep
.selected
= (otherStep
=== step
);
757 if (otherStep
=== step
&& maxVisited
< otherKey
) maxVisited
= otherKey
;
759 selectedIndex
= findIndex();
761 /*** @param Object step the $scope of the step */
762 this.add = function(step
) {
763 if (steps
.length
=== 0) {
764 step
.selected
= true;
768 steps
.sort(function(a
,b
){
769 return a
.crmUiWizardStep
- b
.crmUiWizardStep
;
771 selectedIndex
= findIndex();
773 this.remove = function(step
) {
775 angular
.forEach(steps
, function(otherStep
, otherKey
) {
776 if (otherStep
=== step
) key
= otherKey
;
779 steps
.splice(key
, 1);
782 this.goto = function(index
) {
783 if (index
< 0) index
= 0;
784 if (index
>= steps
.length
) index
= steps
.length
-1;
785 this.select(steps
[index
]);
787 this.previous = function() { this.goto(this.$index()-1); };
788 this.next = function() { this.goto(this.$index()+1); };
789 if ($scope
.crmUiWizard
) {
790 $parse($scope
.crmUiWizard
).assign($scope
.$parent
, this);
793 link: function (scope
, element
, attrs
) {
794 scope
.ts
= CRM
.ts(null);
799 // Use this to add extra markup to wizard
800 .directive('crmUiWizardButtons', function() {
802 require
: '^crmUiWizard',
805 template
: '<span ng-transclude></span>',
807 link: function (scope
, element
, attrs
, crmUiWizardCtrl
) {
808 var realButtonsEl
= $(element
).closest('.crm-wizard').find('.crm-wizard-buttons');
809 $(element
).appendTo(realButtonsEl
);
814 // Example: <button crm-icon="check">Save</button>
815 .directive('crmIcon', function() {
819 link: function (scope
, element
, attrs
) {
820 $(element
).prepend('<span class="icon ui-icon-' + attrs
.crmIcon
+ '"></span> ');
821 if ($(element
).is('button')) {
822 $(element
).addClass('crm-button');
828 // example: <div crm-ui-wizard-step crm-title="ts('My Title')" ng-form="mySubForm">...content...</div>
829 // If there are any conditional steps, then be sure to set a weight explicitly on *all* steps to maintain ordering.
830 // example: <div crm-ui-wizard-step="100" crm-title="..." ng-if="...">...content...</div>
831 .directive('crmUiWizardStep', function() {
834 require
: ['^crmUiWizard', 'form'],
837 crmTitle
: '@', // expression, evaluates to a printable string
838 crmUiWizardStep
: '@' // int, a weight which determines the ordering of the steps
840 template
: '<div class="crm-wizard-step" ng-show="selected" ng-transclude/></div>',
842 link: function (scope
, element
, attrs
, ctrls
) {
843 var crmUiWizardCtrl
= ctrls
[0], form
= ctrls
[1];
844 if (scope
.crmUiWizardStep
) {
845 scope
.crmUiWizardStep
= parseInt(scope
.crmUiWizardStep
);
847 scope
.crmUiWizardStep
= nextWeight
++;
849 scope
.isStepValid = function() {
852 crmUiWizardCtrl
.add(scope
);
853 element
.on('$destroy', function(){
854 crmUiWizardCtrl
.remove(scope
);
860 // Example: <button crm-confirm="{message: ts('Are you sure you want to continue?')}" on-yes="frobnicate(123)">Frobincate</button>
861 // Example: <button crm-confirm="{type: 'disable', obj: myObject}" on-yes="myObject.is_active=0; myObject.save()">Disable</button>
862 .directive('crmConfirm', function () {
863 // Helpers to calculate default options for CRM.confirm()
865 'disable': function (options
) {
867 message
: ts('Are you sure you want to disable this?'),
868 options
: {no
: ts('Cancel'), yes
: ts('Disable')},
870 title
: ts('Disable %1?', {
871 1: options
.obj
.title
|| options
.obj
.label
|| options
.obj
.name
|| ts('the record')
875 'revert': function (options
) {
877 message
: ts('Are you sure you want to revert this?'),
878 options
: {no
: ts('Cancel'), yes
: ts('Revert')},
880 title
: ts('Revert %1?', {
881 1: options
.obj
.title
|| options
.obj
.label
|| options
.obj
.name
|| ts('the record')
885 'delete': function (options
) {
887 message
: ts('Are you sure you want to delete this?'),
888 options
: {no
: ts('Cancel'), yes
: ts('Delete')},
890 title
: ts('Delete %1?', {
891 1: options
.obj
.title
|| options
.obj
.label
|| options
.obj
.name
|| ts('the record')
897 link: function (scope
, element
, attrs
) {
898 $(element
).click(function () {
899 var options
= scope
.$eval(attrs
.crmConfirm
);
900 if (attrs
.title
&& !options
.title
) {
901 options
.title
= attrs
.title
;
903 var defaults
= (options
.type
) ? defaultFuncs
[options
.type
](options
) : {};
904 CRM
.confirm(_
.extend(defaults
, options
))
905 .on('crmConfirm:yes', function () { scope
.$apply(attrs
.onYes
); })
906 .on('crmConfirm:no', function () { scope
.$apply(attrs
.onNo
); });
911 .run(function($rootScope
, $location
) {
912 /// Example: <button ng-click="goto('home')">Go home!</button>
913 $rootScope
.goto = function(path
) {
914 $location
.path(path
);
916 // useful for debugging: $rootScope.log = console.log || function() {};
920 })(angular
, CRM
.$, CRM
._
);