1 /// crmUi: Sundry UI helpers
2 (function (angular
, $, _
) {
5 angular
.module('crmUi', [])
6 // example: <form name="myForm">...<label crm-ui-label crm-for="myField">My Field</span>...<input name="myField"/>...</form>
8 // Label adapts based on <input required>, <input ng-required>, or any other validation.
10 // Note: This should work in the normal case where <label> and <input> are in roughly the same scope,
11 // but if the scopes are materially different then problems could arise.
12 .directive('crmUiLabel', function($parse
) {
18 template
: '<span ng-class="cssClasses"><span ng-transclude></span> <span ng-show="crmRequired" class="crm-marker" title="This field is required.">*</span></span>',
19 link: function(scope
, element
, attrs
) {
20 if (attrs
.crmFor
== 'name') {
21 throw new Error('Validation monitoring does not work for field name "name"');
24 // 1. Figure out form and input elements
26 var form
= $(element
).closest('form');
27 var formCtrl
= scope
.$parent
.$eval(form
.attr('name'));
28 var input
= $('input[name="' + attrs
.crmFor
+ '"],select[name="' + attrs
.crmFor
+ '"],textarea[name="' + attrs
.crmFor
+ '"]', form
);
29 if (form
.length
!= 1 || input
.length
!= 1) {
30 if (console
.log
) console
.log('Label cannot be matched to input element. Expected to find one form and one input.', form
.length
, input
.length
);
34 // 2. Make sure that inputs are well-defined (with name+id).
36 if (!input
.attr('id')) {
37 input
.attr('id', 'crmUi_' + (++idCount
));
39 $(element
).attr('for', input
.attr('id'));
41 // 3. Monitor is the "required" and "$valid" properties
43 if (input
.attr('ng-required')) {
44 scope
.crmRequired
= scope
.$parent
.$eval(input
.attr('ng-required'));
45 scope
.$parent
.$watch(input
.attr('ng-required'), function(isRequired
) {
46 scope
.crmRequired
= isRequired
;
49 scope
.crmRequired
= input
.prop('required');
52 var inputCtrl
= form
.attr('name') + '.' + input
.attr('name');
53 scope
.cssClasses
= {};
54 scope
.$parent
.$watch(inputCtrl
+ '.$valid', function(newValue
) {
55 //scope.cssClasses['ng-valid'] = newValue;
56 //scope.cssClasses['ng-invalid'] = !newValue;
57 scope
.cssClasses
['crm-error'] = !scope
.$parent
.$eval(inputCtrl
+ '.$valid') && !scope
.$parent
.$eval(inputCtrl
+ '.$pristine');
59 scope
.$parent
.$watch(inputCtrl
+ '.$pristine', function(newValue
) {
60 //scope.cssClasses['ng-pristine'] = newValue;
61 //scope.cssClasses['ng-dirty'] = !newValue;
62 scope
.cssClasses
['crm-error'] = !scope
.$parent
.$eval(inputCtrl
+ '.$valid') && !scope
.$parent
.$eval(inputCtrl
+ '.$pristine');
69 // example: <a crm-ui-lock binding="mymodel.boolfield"></a>
70 // example: <a crm-ui-lock
71 // binding="mymodel.boolfield"
72 // title-locked="ts('Boolfield is locked')"
73 // title-unlocked="ts('Boolfield is unlocked')"></a>
74 .directive('crmUiLock', function ($parse
, $rootScope
) {
75 var defaultVal = function (defaultValue
) {
76 var f = function (scope
) {
79 f
.assign = function (scope
, value
) {
85 // like $parse, but accepts a defaultValue in case expr is undefined
86 var parse = function (expr
, defaultValue
) {
87 return expr
? $parse(expr
) : defaultVal(defaultValue
);
92 link: function (scope
, element
, attrs
) {
93 var binding
= parse(attrs
['binding'], true);
94 var titleLocked
= parse(attrs
['titleLocked'], ts('Locked'));
95 var titleUnlocked
= parse(attrs
['titleUnlocked'], ts('Unlocked'));
97 $(element
).addClass('ui-icon lock-button');
98 var refresh = function () {
99 var locked
= binding(scope
);
102 .removeClass('ui-icon-unlocked')
103 .addClass('ui-icon-locked')
104 .prop('title', titleLocked(scope
))
109 .removeClass('ui-icon-locked')
110 .addClass('ui-icon-unlocked')
111 .prop('title', titleUnlocked(scope
))
116 $(element
).click(function () {
117 binding
.assign(scope
, !binding(scope
));
119 $rootScope
.$digest();
122 scope
.$watch(attrs
.binding
, refresh
);
123 scope
.$watch(attrs
.titleLocked
, refresh
);
124 scope
.$watch(attrs
.titleUnlocked
, refresh
);
130 // Example: <button crm-confirm="{message: ts('Are you sure you want to continue?')}" on-yes="frobnicate(123)">Frobincate</button>
131 // Example: <button crm-confirm="{type: 'disable', obj: myObject}" on-yes="myObject.is_active=0; myObject.save()">Disable</button>
132 .directive('crmConfirm', function () {
133 // Helpers to calculate default options for CRM.confirm()
135 'disable': function (options
) {
137 message
: ts('Are you sure you want to disable this?'),
138 options
: {no
: ts('Cancel'), yes
: ts('Disable')},
140 title
: ts('Disable %1?', {
141 1: options
.obj
.title
|| options
.obj
.label
|| options
.obj
.name
|| ts('the record')
145 'revert': function (options
) {
147 message
: ts('Are you sure you want to revert this?'),
148 options
: {no
: ts('Cancel'), yes
: ts('Revert')},
150 title
: ts('Revert %1?', {
151 1: options
.obj
.title
|| options
.obj
.label
|| options
.obj
.name
|| ts('the record')
155 'delete': function (options
) {
157 message
: ts('Are you sure you want to delete this?'),
158 options
: {no
: ts('Cancel'), yes
: ts('Delete')},
160 title
: ts('Delete %1?', {
161 1: options
.obj
.title
|| options
.obj
.label
|| options
.obj
.name
|| ts('the record')
168 link: function (scope
, element
, attrs
) {
169 $(element
).click(function () {
170 var options
= scope
.$eval(attrs
['crmConfirm']);
171 var defaults
= (options
.type
) ? defaultFuncs
[options
.type
](options
) : {};
172 CRM
.confirm(_
.extend(defaults
, options
))
173 .on('crmConfirm:yes', function () { scope
.$apply(attrs
['onYes']); })
174 .on('crmConfirm:no', function () { scope
.$apply(attrs
['onNo']); });
179 .run(function($rootScope
, $location
) {
180 /// Example: <button ng-click="goto('home')">Go home!</button>
181 $rootScope
.goto = function(path
) {
182 $location
.path(path
);
184 // useful for debugging: $rootScope.log = console.log || function() {};
188 })(angular
, CRM
.$, CRM
._
);