Commit | Line | Data |
---|---|---|
685acae4 | 1 | /// crmUi: Sundry UI helpers |
2 | (function (angular, $, _) { | |
438f2b52 | 3 | var idCount = 0; |
685acae4 | 4 | |
5 | angular.module('crmUi', []) | |
438f2b52 TO |
6 | // example: <form name="myForm">...<label crm-ui-label crm-for="myField">My Field</span>...<input name="myField"/>...</form> |
7 | // | |
8 | // Label adapts based on <input required>, <input ng-required>, or any other validation. | |
9 | // | |
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) { | |
13 | return { | |
14 | scope: { | |
15 | name: '@' | |
16 | }, | |
17 | transclude: true, | |
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"'); | |
22 | } | |
23 | ||
24 | // 1. Figure out form and input elements | |
25 | ||
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); | |
31 | return; | |
32 | } | |
33 | ||
34 | // 2. Make sure that inputs are well-defined (with name+id). | |
35 | ||
36 | if (!input.attr('id')) { | |
37 | input.attr('id', 'crmUi_' + (++idCount)); | |
38 | } | |
39 | $(element).attr('for', input.attr('id')); | |
40 | ||
41 | // 3. Monitor is the "required" and "$valid" properties | |
42 | ||
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; | |
47 | }); | |
48 | } else { | |
49 | scope.crmRequired = input.prop('required'); | |
50 | } | |
51 | ||
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'); | |
58 | }); | |
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'); | |
63 | }); | |
64 | ||
65 | } | |
66 | }; | |
67 | }) | |
685acae4 | 68 | |
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) { | |
77 | return defaultValue; | |
78 | } | |
79 | f.assign = function (scope, value) { | |
80 | // ignore changes | |
81 | } | |
82 | return f; | |
83 | }; | |
84 | ||
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); | |
88 | }; | |
89 | ||
90 | return { | |
91 | template: '', | |
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')); | |
96 | ||
97 | $(element).addClass('ui-icon lock-button'); | |
98 | var refresh = function () { | |
99 | var locked = binding(scope); | |
100 | if (locked) { | |
101 | $(element) | |
102 | .removeClass('ui-icon-unlocked') | |
103 | .addClass('ui-icon-locked') | |
104 | .prop('title', titleLocked(scope)) | |
105 | ; | |
106 | } | |
107 | else { | |
108 | $(element) | |
109 | .removeClass('ui-icon-locked') | |
110 | .addClass('ui-icon-unlocked') | |
111 | .prop('title', titleUnlocked(scope)) | |
112 | ; | |
113 | } | |
114 | }; | |
115 | ||
116 | $(element).click(function () { | |
117 | binding.assign(scope, !binding(scope)); | |
118 | //scope.$digest(); | |
119 | $rootScope.$digest(); | |
120 | }); | |
121 | ||
122 | scope.$watch(attrs.binding, refresh); | |
123 | scope.$watch(attrs.titleLocked, refresh); | |
124 | scope.$watch(attrs.titleUnlocked, refresh); | |
125 | ||
126 | refresh(); | |
127 | } | |
128 | }; | |
129 | }) | |
5fb5b3cf | 130 | // Example: <button crm-confirm="{message: ts('Are you sure you want to continue?')}" on-yes="frobnicate(123)">Frobincate</button> |
4b8c8b42 TO |
131 | // Example: <button crm-confirm="{type: 'disable', obj: myObject}" on-yes="myObject.is_active=0; myObject.save()">Disable</button> |
132 | .directive('crmConfirm', function () { | |
88fcc9f1 | 133 | // Helpers to calculate default options for CRM.confirm() |
4b8c8b42 TO |
134 | var defaultFuncs = { |
135 | 'disable': function (options) { | |
136 | return { | |
137 | message: ts('Are you sure you want to disable this?'), | |
138 | options: {no: ts('Cancel'), yes: ts('Disable')}, | |
139 | width: 300, | |
140 | title: ts('Disable %1?', { | |
141 | 1: options.obj.title || options.obj.label || options.obj.name || ts('the record') | |
142 | }) | |
143 | }; | |
144 | }, | |
470a458e TO |
145 | 'revert': function (options) { |
146 | return { | |
147 | message: ts('Are you sure you want to revert this?'), | |
148 | options: {no: ts('Cancel'), yes: ts('Revert')}, | |
149 | width: 300, | |
150 | title: ts('Revert %1?', { | |
151 | 1: options.obj.title || options.obj.label || options.obj.name || ts('the record') | |
152 | }) | |
153 | }; | |
154 | }, | |
4b8c8b42 TO |
155 | 'delete': function (options) { |
156 | return { | |
157 | message: ts('Are you sure you want to delete this?'), | |
158 | options: {no: ts('Cancel'), yes: ts('Delete')}, | |
159 | width: 300, | |
160 | title: ts('Delete %1?', { | |
161 | 1: options.obj.title || options.obj.label || options.obj.name || ts('the record') | |
162 | }) | |
163 | }; | |
164 | } | |
165 | }; | |
166 | return { | |
167 | template: '', | |
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']); }); | |
175 | }); | |
176 | } | |
177 | }; | |
178 | }) | |
02308c07 TO |
179 | .run(function($rootScope, $location) { |
180 | /// Example: <button ng-click="goto('home')">Go home!</button> | |
181 | $rootScope.goto = function(path) { | |
182 | $location.path(path); | |
183 | }; | |
4b8c8b42 | 184 | // useful for debugging: $rootScope.log = console.log || function() {}; |
02308c07 | 185 | }) |
685acae4 | 186 | ; |
187 | ||
5fb5b3cf | 188 | })(angular, CRM.$, CRM._); |