|
43 | 43 | * Original discussion about parsing "repeat" attribute instead of fully relying on ng-repeat:
|
44 | 44 | * https://github.com/angular-ui/ui-select/commit/5dd63ad#commitcomment-5504697
|
45 | 45 | */
|
46 |
| - .service('RepeatParser', ['uiSelectMinErr', function(uiSelectMinErr) { |
| 46 | + .service('RepeatParser', ['uiSelectMinErr','$parse', function(uiSelectMinErr, $parse) { |
47 | 47 | var self = this;
|
48 | 48 |
|
49 | 49 | /**
|
50 | 50 | * Example:
|
51 | 51 | * expression = "address in addresses | filter: {street: $select.search} track by $index"
|
52 |
| - * lhs = "address", |
53 |
| - * rhs = "addresses | filter: {street: $select.search}", |
| 52 | + * itemName = "address", |
| 53 | + * source = "addresses | filter: {street: $select.search}", |
54 | 54 | * trackByExp = "$index",
|
55 |
| - * valueIdentifier = "address", |
56 |
| - * keyIdentifier = undefined |
57 | 55 | */
|
58 | 56 | self.parse = function(expression) {
|
59 |
| - if (!expression) { |
60 |
| - throw uiSelectMinErr('repeat', "Expected 'repeat' expression."); |
61 |
| - } |
62 | 57 |
|
63 |
| - var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/); |
| 58 | + var match = expression.match(/^\s*(?:([\s\S]+?)\s+as\s+)?([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/); |
64 | 59 |
|
65 | 60 | if (!match) {
|
66 | 61 | throw uiSelectMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.",
|
67 |
| - expression); |
68 |
| - } |
69 |
| - |
70 |
| - var lhs = match[1]; // Left-hand side |
71 |
| - var rhs = match[2]; // Right-hand side |
72 |
| - var trackByExp = match[3]; |
73 |
| - |
74 |
| - match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/); |
75 |
| - if (!match) { |
76 |
| - throw uiSelectMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.", |
77 |
| - lhs); |
| 62 | + expression); |
78 | 63 | }
|
79 | 64 |
|
80 |
| - // Unused for now |
81 |
| - // var valueIdentifier = match[3] || match[1]; |
82 |
| - // var keyIdentifier = match[2]; |
83 |
| - |
84 | 65 | return {
|
85 |
| - lhs: lhs, |
86 |
| - rhs: rhs, |
87 |
| - trackByExp: trackByExp |
| 66 | + itemName: match[2], // (lhs) Left-hand side, |
| 67 | + source: match[3], // (rhs) Right-hand side, |
| 68 | + trackByExp: match[4], |
| 69 | + modelMapper: $parse(match[1] || match[2]) |
88 | 70 | };
|
| 71 | + |
89 | 72 | };
|
90 | 73 |
|
91 | 74 | self.getGroupNgRepeatExpression = function() {
|
92 | 75 | return '($group, $items) in $select.groups';
|
93 | 76 | };
|
94 | 77 |
|
95 |
| - self.getNgRepeatExpression = function(lhs, rhs, trackByExp, grouped) { |
96 |
| - var expression = lhs + ' in ' + (grouped ? '$items' : rhs); |
| 78 | + self.getNgRepeatExpression = function(itemName, source, trackByExp, grouped) { |
| 79 | + var expression = itemName + ' in ' + (grouped ? '$items' : source); |
97 | 80 | if (trackByExp) {
|
98 | 81 | expression += ' track by ' + trackByExp;
|
99 | 82 | }
|
|
180 | 163 | ctrl.items = items;
|
181 | 164 | }
|
182 | 165 |
|
183 |
| - var repeat = RepeatParser.parse(repeatAttr), |
184 |
| - setItemsFn = groupByExp ? updateGroups : setPlainItems; |
| 166 | + var setItemsFn = groupByExp ? updateGroups : setPlainItems; |
| 167 | + |
| 168 | + ctrl.parserResult = RepeatParser.parse(repeatAttr); |
185 | 169 |
|
186 | 170 | ctrl.isGrouped = !!groupByExp;
|
187 |
| - ctrl.itemProperty = repeat.lhs; |
| 171 | + ctrl.itemProperty = ctrl.parserResult.itemName; |
188 | 172 |
|
189 | 173 | // See https://github.com/angular/angular.js/blob/v1.2.15/src/ng/directive/ngRepeat.js#L259
|
190 |
| - $scope.$watchCollection(repeat.rhs, function(items) { |
| 174 | + $scope.$watchCollection(ctrl.parserResult.source, function(items) { |
191 | 175 |
|
192 | 176 | if (items === undefined || items === null) {
|
193 | 177 | // If the user specifies undefined or null => reset the collection
|
|
204 | 188 | }
|
205 | 189 |
|
206 | 190 | });
|
| 191 | + |
207 | 192 | };
|
208 | 193 |
|
209 | 194 | var _refreshDelayPromise;
|
|
355 | 340 | var $select = ctrls[0];
|
356 | 341 | var ngModel = ctrls[1];
|
357 | 342 |
|
| 343 | + //From view --> model |
| 344 | + ngModel.$parsers.unshift(function (inputValue) { |
| 345 | + var locals = {}; |
| 346 | + locals[$select.parserResult.itemName] = inputValue; |
| 347 | + var result = $select.parserResult.modelMapper(scope, locals); |
| 348 | + return result; |
| 349 | + }); |
| 350 | + |
| 351 | + //From model --> view |
| 352 | + ngModel.$formatters.unshift(function (inputValue) { |
| 353 | + var match = $select.parserResult.source.match(/^\s*([\S]+).*$/); |
| 354 | + var data = scope[match[1]]; |
| 355 | + if (data){ |
| 356 | + for (var i = data.length - 1; i >= 0; i--) { |
| 357 | + var locals = {}; |
| 358 | + locals[$select.parserResult.itemName] = data[i]; |
| 359 | + var result = $select.parserResult.modelMapper(scope, locals); |
| 360 | + if (result == inputValue){ |
| 361 | + return data[i]; |
| 362 | + } |
| 363 | + } |
| 364 | + } |
| 365 | + return inputValue; |
| 366 | + }); |
| 367 | + |
| 368 | + |
358 | 369 | //Idea from: https://github.com/ivaynberg/select2/blob/79b5bf6db918d7560bdd959109b7bcfb47edaf43/select2.js#L1954
|
359 | 370 | var focusser = angular.element("<input ng-disabled='$select.disabled' class='ui-select-focusser ui-select-offscreen' type='text' aria-haspopup='true' role='button' />");
|
360 | 371 | $compile(focusser)(scope);
|
|
533 | 544 | },
|
534 | 545 |
|
535 | 546 | compile: function(tElement, tAttrs) {
|
536 |
| - var repeat = RepeatParser.parse(tAttrs.repeat); |
537 |
| - var groupByExp = tAttrs.groupBy; |
| 547 | + |
| 548 | + if (!tAttrs.repeat) throw uiSelectMinErr('repeat', "Expected 'repeat' expression."); |
| 549 | + |
538 | 550 | return function link(scope, element, attrs, $select, transcludeFn) {
|
| 551 | + |
| 552 | + // var repeat = RepeatParser.parse(attrs.repeat); |
| 553 | + var groupByExp = attrs.groupBy; |
| 554 | + |
| 555 | + $select.parseRepeatAttr(attrs.repeat, groupByExp); //Result ready at $select.parserResult |
539 | 556 |
|
540 | 557 | if(groupByExp) {
|
541 | 558 | var groups = element.querySelectorAll('.ui-select-choices-group');
|
|
548 | 565 | throw uiSelectMinErr('rows', "Expected 1 .ui-select-choices-row but got '{0}'.", choices.length);
|
549 | 566 | }
|
550 | 567 |
|
551 |
| - choices.attr('ng-repeat', RepeatParser.getNgRepeatExpression(repeat.lhs, '$select.items', repeat.trackByExp, groupByExp)) |
552 |
| - .attr('ng-mouseenter', '$select.setActiveItem('+repeat.lhs+')') |
553 |
| - .attr('ng-click', '$select.select(' + repeat.lhs + ')'); |
554 |
| - |
| 568 | + choices.attr('ng-repeat', RepeatParser.getNgRepeatExpression($select.parserResult.itemName, '$select.items', $select.parserResult.trackByExp, groupByExp)) |
| 569 | + .attr('ng-mouseenter', '$select.setActiveItem('+$select.parserResult.itemName +')') |
| 570 | + .attr('ng-click', '$select.select(' + $select.parserResult.itemName + ')'); |
555 | 571 |
|
556 | 572 | transcludeFn(function(clone) {
|
557 | 573 | var rowsInner = element.querySelectorAll('.ui-select-choices-row-inner');
|
|
562 | 578 | $compile(element)(scope);
|
563 | 579 | });
|
564 | 580 |
|
565 |
| - $select.parseRepeatAttr(attrs.repeat, groupByExp); |
566 |
| - |
567 | 581 | scope.$watch('$select.search', function() {
|
568 | 582 | $select.activeIndex = 0;
|
569 | 583 | $select.refresh(attrs.refresh);
|
|
0 commit comments