diff --git a/src/uiSelectController.js b/src/uiSelectController.js index 3e4019a90..2c4316b15 100644 --- a/src/uiSelectController.js +++ b/src/uiSelectController.js @@ -265,7 +265,11 @@ uis.controller('uiSelectCtrl', //Remove already selected items (ex: while searching) //TODO Should add a test ctrl.refreshItems(items); - ctrl.ngModel.$modelValue = null; //Force scope model value and ngModel value to be out of sync to re-run formatters + + //update the view value with fresh data from items, if there is a valid model value + if(angular.isDefined(ctrl.ngModel.$modelValue)) { + ctrl.ngModel.$modelValue = null; //Force scope model value and ngModel value to be out of sync to re-run formatters + } } } }); diff --git a/src/uiSelectMultipleDirective.js b/src/uiSelectMultipleDirective.js index 87f8b92d2..6ab6c921b 100644 --- a/src/uiSelectMultipleDirective.js +++ b/src/uiSelectMultipleDirective.js @@ -146,7 +146,10 @@ uis.directive('uiSelectMultiple', ['uiSelectMinErr','$timeout', function(uiSelec //Watch for external model changes scope.$watchCollection(function(){ return ngModel.$modelValue; }, function(newValue, oldValue) { if (oldValue != newValue){ - ngModel.$modelValue = null; //Force scope model value and ngModel value to be out of sync to re-run formatters + //update the view value with fresh data from items, if there is a valid model value + if(angular.isDefined(ngModel.$modelValue)) { + ngModel.$modelValue = null; //Force scope model value and ngModel value to be out of sync to re-run formatters + } $selectMultiple.refreshComponent(); } }); diff --git a/test/select.spec.js b/test/select.spec.js index 25553c20e..4209be93e 100644 --- a/test/select.spec.js +++ b/test/select.spec.js @@ -36,7 +36,37 @@ describe('ui-select tests', function() { }); - beforeEach(module('ngSanitize', 'ui.select', 'wrapperDirective')); + /* Create a directive that can be applied to the ui-select instance to test + * the effects of Angular's validation process on the control. + * + * Does not currently work with single property binding. Looks at the + * selected object or objects for a "valid" property. If all selected objects + * have a "valid" property that is truthy, the validator passes. + */ + angular.module('testValidator', []); + angular.module('testValidator').directive('testValidator', function() { + return { + restrict: 'A', + require: 'ngModel', + link: function(scope, element, attrs, ngModel) { + ngModel.$validators.testValidator = function(modelValue, viewValue) { + if(angular.isUndefined(modelValue) || modelValue === null) { + return true; + } else if(angular.isArray(modelValue)) { + var allValid = true, idx = modelValue.length; + while(idx-- > 0 && allValid) { + allValid = allValid && modelValue[idx].valid; + } + return allValid; + } else { + return !!modelValue.valid; + } + }; + } + } + }); + + beforeEach(module('ngSanitize', 'ui.select', 'wrapperDirective', 'testValidator')); beforeEach(function() { module(function($provide) { @@ -1510,6 +1540,45 @@ describe('ui-select tests', function() { }); + it('should retain an invalid view value after refreshing items', function() { + scope.taggingFunc = function (name) { + return { + name: name, + email: name + '@email.com', + valid: name === "iamvalid" + }; + }; + + var el = compileTemplate( + ' \ + {{$select.selected.email}} \ + \ +
\ +
\ +
\ +
' + ); + + clickMatch(el); + var searchInput = el.find('.ui-select-search'); + + setSearchText(el, 'iamvalid'); + triggerKeydown(searchInput, Key.Tab); + + //model value defined because it's valid, view value defined as expected + var validTag = scope.taggingFunc("iamvalid"); + expect(scope.selection.selected).toEqual(validTag); + expect($(el).scope().$select.selected).toEqual(validTag); + + clickMatch(el); + setSearchText(el, 'notvalid'); + triggerKeydown(searchInput, Key.Tab); + + //model value undefined because it's invalid, view value STILL defined as expected + expect(scope.selection.selected).toEqual(undefined); + expect($(el).scope().$select.selected).toEqual(scope.taggingFunc("notvalid")); + }); + describe('search-enabled option', function() { var el; @@ -2171,6 +2240,45 @@ describe('ui-select tests', function() { }); + it('should retain an invalid view value after refreshing items', function() { + scope.taggingFunc = function (name) { + return { + name: name, + email: name + '@email.com', + valid: name === "iamvalid" + }; + }; + + var el = compileTemplate( + ' \ + {{$select.selected.email}} \ + \ +
\ +
\ +
\ +
' + ); + + clickMatch(el); + var searchInput = el.find('.ui-select-search'); + + setSearchText(el, 'iamvalid'); + triggerKeydown(searchInput, Key.Tab); + + //model value defined because it's valid, view value defined as expected + var validTag = scope.taggingFunc("iamvalid"); + expect(scope.selection.selectedMultiple).toEqual([jasmine.objectContaining(validTag)]); + expect($(el).scope().$select.selected).toEqual([jasmine.objectContaining(validTag)]); + + clickMatch(el); + setSearchText(el, 'notvalid'); + triggerKeydown(searchInput, Key.Tab); + + //model value undefined because it's invalid, view value STILL defined as expected + var invalidTag = scope.taggingFunc("notvalid"); + expect(scope.selection.selected).toEqual(undefined); + expect($(el).scope().$select.selected).toEqual([jasmine.objectContaining(validTag), jasmine.objectContaining(invalidTag)]); + }); it('should run $formatters when changing model directly', function () {