1 | /**
|
---|
2 | * Enhanced Select2 Dropmenus
|
---|
3 | *
|
---|
4 | * @AJAX Mode - When in this mode, your value will be an object (or array of objects) of the data used by Select2
|
---|
5 | * This change is so that you do not have to do an additional query yourself on top of Select2's own query
|
---|
6 | * @params [options] {object} The configuration options passed to $.fn.select2(). Refer to the documentation
|
---|
7 | */
|
---|
8 | angular.module('ui.select2', []).value('uiSelect2Config', {}).directive('uiSelect2', ['uiSelect2Config', '$timeout', function (uiSelect2Config, $timeout) {
|
---|
9 | var options = {};
|
---|
10 | if (uiSelect2Config) {
|
---|
11 | angular.extend(options, uiSelect2Config);
|
---|
12 | }
|
---|
13 | return {
|
---|
14 | require: 'ngModel',
|
---|
15 | priority: 1,
|
---|
16 | compile: function (tElm, tAttrs) {
|
---|
17 | var watch,
|
---|
18 | repeatOption,
|
---|
19 | repeatAttr,
|
---|
20 | isSelect = tElm.is('select'),
|
---|
21 | isMultiple = angular.isDefined(tAttrs.multiple);
|
---|
22 |
|
---|
23 | // Enable watching of the options dataset if in use
|
---|
24 | if (tElm.is('select')) {
|
---|
25 | repeatOption = tElm.find('optgroup[ng-repeat], optgroup[data-ng-repeat], option[ng-repeat], option[data-ng-repeat]');
|
---|
26 |
|
---|
27 | if (repeatOption.length) {
|
---|
28 | repeatAttr = repeatOption.attr('ng-repeat') || repeatOption.attr('data-ng-repeat');
|
---|
29 | watch = jQuery.trim(repeatAttr.split('|')[0]).split(' ').pop();
|
---|
30 | }
|
---|
31 | }
|
---|
32 |
|
---|
33 | return function (scope, elm, attrs, controller) {
|
---|
34 | // instance-specific options
|
---|
35 | var opts = angular.extend({}, options, scope.$eval(attrs.uiSelect2));
|
---|
36 |
|
---|
37 | /*
|
---|
38 | Convert from Select2 view-model to Angular view-model.
|
---|
39 | */
|
---|
40 | var convertToAngularModel = function (select2_data) {
|
---|
41 | var model;
|
---|
42 | if (opts.simple_tags) {
|
---|
43 | model = [];
|
---|
44 | angular.forEach(select2_data, function (value, index) {
|
---|
45 | model.push(value.id);
|
---|
46 | });
|
---|
47 | } else {
|
---|
48 | model = select2_data;
|
---|
49 | }
|
---|
50 | return model;
|
---|
51 | };
|
---|
52 |
|
---|
53 | /*
|
---|
54 | Convert from Angular view-model to Select2 view-model.
|
---|
55 | */
|
---|
56 | var convertToSelect2Model = function (angular_data) {
|
---|
57 | var model = [];
|
---|
58 | if (!angular_data) {
|
---|
59 | return model;
|
---|
60 | }
|
---|
61 |
|
---|
62 | if (opts.simple_tags) {
|
---|
63 | model = [];
|
---|
64 | angular.forEach(
|
---|
65 | angular_data,
|
---|
66 | function (value, index) {
|
---|
67 | model.push({ 'id': value, 'text': value });
|
---|
68 | });
|
---|
69 | } else {
|
---|
70 | model = angular_data;
|
---|
71 | }
|
---|
72 | return model;
|
---|
73 | };
|
---|
74 |
|
---|
75 | if (isSelect) {
|
---|
76 | // Use <select multiple> instead
|
---|
77 | delete opts.multiple;
|
---|
78 | delete opts.initSelection;
|
---|
79 | } else if (isMultiple) {
|
---|
80 | opts.multiple = true;
|
---|
81 | }
|
---|
82 |
|
---|
83 | if (controller) {
|
---|
84 | // Watch the model for programmatic changes
|
---|
85 | scope.$watch(tAttrs.ngModel, function (current, old) {
|
---|
86 | if (!current) {
|
---|
87 | return;
|
---|
88 | }
|
---|
89 | if (current === old) {
|
---|
90 | return;
|
---|
91 | }
|
---|
92 | controller.$render();
|
---|
93 | }, true);
|
---|
94 | controller.$render = function () {
|
---|
95 | if (isSelect) {
|
---|
96 | elm.select2('val', controller.$viewValue);
|
---|
97 | } else {
|
---|
98 | if (opts.multiple) {
|
---|
99 | var viewValue = controller.$viewValue;
|
---|
100 | if (angular.isString(viewValue)) {
|
---|
101 | viewValue = viewValue.split(opts.separator);
|
---|
102 | }
|
---|
103 | elm.select2(
|
---|
104 | 'data', convertToSelect2Model(viewValue));
|
---|
105 | } else {
|
---|
106 | if (angular.isObject(controller.$viewValue)) {
|
---|
107 | elm.select2('data', controller.$viewValue);
|
---|
108 | } else if (!controller.$viewValue) {
|
---|
109 | elm.select2('data', null);
|
---|
110 | } else {
|
---|
111 | elm.select2('val', controller.$viewValue);
|
---|
112 | }
|
---|
113 | }
|
---|
114 | }
|
---|
115 | };
|
---|
116 |
|
---|
117 | // Watch the options dataset for changes
|
---|
118 | if (watch) {
|
---|
119 | scope.$watch(watch, function (newVal, oldVal, scope) {
|
---|
120 | if (angular.equals(newVal, oldVal)) {
|
---|
121 | return;
|
---|
122 | }
|
---|
123 | // Delayed so that the options have time to be rendered
|
---|
124 | $timeout(function () {
|
---|
125 | elm.select2('val', controller.$viewValue);
|
---|
126 | // Refresh angular to remove the superfluous option
|
---|
127 | elm.trigger('change');
|
---|
128 | if (newVal && !oldVal && controller.$setPristine) {
|
---|
129 | controller.$setPristine(true);
|
---|
130 | }
|
---|
131 | });
|
---|
132 | });
|
---|
133 | }
|
---|
134 |
|
---|
135 | // Update valid and dirty statuses
|
---|
136 | controller.$parsers.push(function (value) {
|
---|
137 | var div = elm.prev();
|
---|
138 | div
|
---|
139 | .toggleClass('ng-invalid', !controller.$valid)
|
---|
140 | .toggleClass('ng-valid', controller.$valid)
|
---|
141 | .toggleClass('ng-invalid-required', !controller.$valid)
|
---|
142 | .toggleClass('ng-valid-required', controller.$valid)
|
---|
143 | .toggleClass('ng-dirty', controller.$dirty)
|
---|
144 | .toggleClass('ng-pristine', controller.$pristine);
|
---|
145 | return value;
|
---|
146 | });
|
---|
147 |
|
---|
148 | if (!isSelect) {
|
---|
149 | // Set the view and model value and update the angular template manually for the ajax/multiple select2.
|
---|
150 | elm.bind("change", function (e) {
|
---|
151 | e.stopImmediatePropagation();
|
---|
152 |
|
---|
153 | if (scope.$$phase || scope.$root.$$phase) {
|
---|
154 | return;
|
---|
155 | }
|
---|
156 | scope.$apply(function () {
|
---|
157 | controller.$setViewValue(
|
---|
158 | convertToAngularModel(elm.select2('data')));
|
---|
159 | });
|
---|
160 | });
|
---|
161 |
|
---|
162 | if (opts.initSelection) {
|
---|
163 | var initSelection = opts.initSelection;
|
---|
164 | opts.initSelection = function (element, callback) {
|
---|
165 | initSelection(element, function (value) {
|
---|
166 | var isPristine = controller.$pristine;
|
---|
167 | controller.$setViewValue(convertToAngularModel(value));
|
---|
168 | callback(value);
|
---|
169 | if (isPristine) {
|
---|
170 | controller.$setPristine();
|
---|
171 | }
|
---|
172 | elm.prev().toggleClass('ng-pristine', controller.$pristine);
|
---|
173 | });
|
---|
174 | };
|
---|
175 | }
|
---|
176 | }
|
---|
177 | }
|
---|
178 |
|
---|
179 | elm.bind("$destroy", function () {
|
---|
180 | elm.select2("destroy");
|
---|
181 | });
|
---|
182 |
|
---|
183 | attrs.$observe('disabled', function (value) {
|
---|
184 | elm.select2('enable', !value);
|
---|
185 | });
|
---|
186 |
|
---|
187 | attrs.$observe('readonly', function (value) {
|
---|
188 | elm.select2('readonly', !!value);
|
---|
189 | });
|
---|
190 |
|
---|
191 | if (attrs.ngMultiple) {
|
---|
192 | scope.$watch(attrs.ngMultiple, function (newVal) {
|
---|
193 | attrs.$set('multiple', !!newVal);
|
---|
194 | elm.select2(opts);
|
---|
195 | });
|
---|
196 | }
|
---|
197 |
|
---|
198 | // Initialize the plugin late so that the injected DOM does not disrupt the template compiler
|
---|
199 | $timeout(function () {
|
---|
200 | elm.select2(opts);
|
---|
201 |
|
---|
202 | // Set initial value - I'm not sure about this but it seems to need to be there
|
---|
203 | elm.select2('data', controller.$modelValue);
|
---|
204 | // important!
|
---|
205 | controller.$render();
|
---|
206 |
|
---|
207 | // Not sure if I should just check for !isSelect OR if I should check for 'tags' key
|
---|
208 | if (!opts.initSelection && !isSelect) {
|
---|
209 | var isPristine = controller.$pristine;
|
---|
210 | controller.$setViewValue(
|
---|
211 | convertToAngularModel(elm.select2('data'))
|
---|
212 | );
|
---|
213 | if (isPristine) {
|
---|
214 | controller.$setPristine();
|
---|
215 | }
|
---|
216 | elm.prev().toggleClass('ng-pristine', controller.$pristine);
|
---|
217 | }
|
---|
218 | });
|
---|
219 | };
|
---|
220 | }
|
---|
221 | };
|
---|
222 | }]); |
---|