From daa7dd039775c105dc46be869003cd6c8e086107 Mon Sep 17 00:00:00 2001 From: Philippe Lhoste Date: Thu, 3 Mar 2016 10:38:01 +0100 Subject: [PATCH 1/5] Treat user entered options differently from original options Or programmatically changed options. Fixes issue #69. Also fixes #110... Demo at http://plnkr.co/edit/2352rt --- dist/angular-selectize.js | 58 ++++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/dist/angular-selectize.js b/dist/angular-selectize.js index a009975..77f0e68 100755 --- a/dist/angular-selectize.js +++ b/dist/angular-selectize.js @@ -17,12 +17,12 @@ angular.module('selectize', []).value('selectizeConfig', {}).directive("selectiz scope.config = scope.config || {}; var isEmpty = function(val) { - return val === undefined || val === null || !val.length; //support checking empty arrays + return val === undefined || val === null || val.length === 0; // support checking empty arrays / strings }; var toggle = function(disabled) { disabled ? selectize.disable() : selectize.enable(); - } + }; var validate = function() { var isInvalid = (scope.ngRequired() || attrs.required || settings.required) && isEmpty(scope.ngModel); @@ -30,18 +30,24 @@ angular.module('selectize', []).value('selectizeConfig', {}).directive("selectiz }; var setSelectizeOptions = function(curr, prev) { - angular.forEach(prev, function(opt){ - if(curr.indexOf(opt) === -1){ + if (scope._noUpdate) { // Internal changes to scope.options, eschew the watch mechanism + scope._noUpdate = false; + return; + } + angular.forEach(prev, function(opt) { + if (curr.indexOf(opt) === -1) { var value = opt[settings.valueField]; selectize.removeOption(value, true); } }); + angular.forEach(curr, function(opt) { + selectize.registerOption(opt); + }); + selectize.lastQuery = undefined; // Hack because of a Selectize bug... + selectize.refreshOptions(false); // Update the content of the drop-down - selectize.addOption(curr, true); - - selectize.refreshOptions(false); // updates results if user has entered a query setSelectizeValue(); - } + }; var setSelectizeValue = function() { validate(); @@ -54,28 +60,48 @@ angular.module('selectize', []).value('selectizeConfig', {}).directive("selectiz if (!angular.equals(selectize.items, scope.ngModel)) { selectize.setValue(scope.ngModel, true); } - } + }; settings.onChange = function(value) { - var value = angular.copy(selectize.items); - if (settings.maxItems == 1) { - value = value[0] + var items = angular.copy(selectize.items); + if (settings.maxItems === 1) { + items = items[0]; } - modelCtrl.$setViewValue( value ); + modelCtrl.$setViewValue(items); if (scope.config.onChange) { scope.config.onChange.apply(this, arguments); } }; + // User entered a new tag. settings.onOptionAdd = function(value, data) { - if( scope.options.indexOf(data) === -1 ) { + if (scope.options.indexOf(data) === -1) { + scope._noUpdate = true; scope.options.push(data); + } + if (scope.config.onOptionAdd) { + scope.config.onOptionAdd.apply(this, arguments); + } + }; - if (scope.config.onOptionAdd) { - scope.config.onOptionAdd.apply(this, arguments); + // User removed a tag they entered. + // Note: it is not called if persist is true. + settings.onOptionRemove = function(value) { + var idx = -1; + for (var i = 0; i < scope.options.length; i++) { + if (scope.options[i][scope.config.valueField] === value) { + idx = i; + break; } } + if (idx !== -1 ) { + scope._noUpdate = true; + scope.options.splice(idx, 1); + } + if (scope.config.onOptionRemove) { + scope.config.onOptionRemove.apply(this, arguments); + } }; settings.onInitialize = function() { From 59df05876d44dcb8909d886827c3cad8973b8581 Mon Sep 17 00:00:00 2001 From: Philippe Lhoste Date: Fri, 4 Mar 2016 18:01:01 +0100 Subject: [PATCH 2/5] Avoid call to onOptionRemove if removeOption is called --- dist/angular-selectize.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/dist/angular-selectize.js b/dist/angular-selectize.js index 77f0e68..15972dd 100755 --- a/dist/angular-selectize.js +++ b/dist/angular-selectize.js @@ -30,16 +30,20 @@ angular.module('selectize', []).value('selectizeConfig', {}).directive("selectiz }; var setSelectizeOptions = function(curr, prev) { + if (!curr) + return; if (scope._noUpdate) { // Internal changes to scope.options, eschew the watch mechanism scope._noUpdate = false; return; } + scope._skipRemove = true; angular.forEach(prev, function(opt) { if (curr.indexOf(opt) === -1) { var value = opt[settings.valueField]; selectize.removeOption(value, true); } }); + scope._skipRemove = false; angular.forEach(curr, function(opt) { selectize.registerOption(opt); }); @@ -85,9 +89,11 @@ angular.module('selectize', []).value('selectizeConfig', {}).directive("selectiz } }; - // User removed a tag they entered. + // User removed a tag they entered, or we called removeOption(). // Note: it is not called if persist is true. settings.onOptionRemove = function(value) { + if (scope._skipRemove) + return; var idx = -1; for (var i = 0; i < scope.options.length; i++) { if (scope.options[i][scope.config.valueField] === value) { From 6b1c858dd7827e1ca3f407e4defe9ed5e8d45222 Mon Sep 17 00:00:00 2001 From: Philippe Lhoste Date: Tue, 8 Mar 2016 10:28:54 +0100 Subject: [PATCH 3/5] Items in model and not in options are created as user options Updated Plunker: http://plnkr.co/edit/2352rt --- dist/angular-selectize.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/dist/angular-selectize.js b/dist/angular-selectize.js index 15972dd..3ed398b 100755 --- a/dist/angular-selectize.js +++ b/dist/angular-selectize.js @@ -62,7 +62,14 @@ angular.module('selectize', []).value('selectizeConfig', {}).directive("selectiz selectize.$control.toggleClass('ng-pristine', modelCtrl.$pristine); if (!angular.equals(selectize.items, scope.ngModel)) { - selectize.setValue(scope.ngModel, true); + if (scope.config.create && angular.isArray(scope.ngModel)) { + // Items might be in model but not in options: we create them in both, as user options + for (var i = 0; i < scope.ngModel.length; i++) { + selectize.createItem(scope.ngModel[i], false); + } + } else { + selectize.setValue(scope.ngModel, true); + } } }; From dcc47e51666d1bb3f41ef54bd92fc21814ee536f Mon Sep 17 00:00:00 2001 From: Philippe Lhoste Date: Thu, 10 Mar 2016 16:57:11 +0100 Subject: [PATCH 4/5] Avoid unnecessary createItem, do it silently. Had subtle race condition with multiple updates of modelCtrl --- dist/angular-selectize.js | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/dist/angular-selectize.js b/dist/angular-selectize.js index 3ed398b..c3fa921 100755 --- a/dist/angular-selectize.js +++ b/dist/angular-selectize.js @@ -49,8 +49,6 @@ angular.module('selectize', []).value('selectizeConfig', {}).directive("selectiz }); selectize.lastQuery = undefined; // Hack because of a Selectize bug... selectize.refreshOptions(false); // Update the content of the drop-down - - setSelectizeValue(); }; var setSelectizeValue = function() { @@ -63,10 +61,25 @@ angular.module('selectize', []).value('selectizeConfig', {}).directive("selectiz if (!angular.equals(selectize.items, scope.ngModel)) { if (scope.config.create && angular.isArray(scope.ngModel)) { + scope._silentChanges = true; // addOption / createItem has no silent option! // Items might be in model but not in options: we create them in both, as user options for (var i = 0; i < scope.ngModel.length; i++) { - selectize.createItem(scope.ngModel[i], false); + var item = scope.ngModel[i]; + var found = false; + for (var j = 0; j < scope.options.length; j++) { + if (scope.options[j][settings.valueField] === item) { + found = true; + break; + } + } + if (found) { // Existing option, just add it to the item list + selectize.addItem(item, true); + } else { // Not a known option, create it along with the item + selectize.createItem(item, false); + } } + scope._silentChanges = false; + settings.onChange(scope.ngModel); } else { selectize.setValue(scope.ngModel, true); } @@ -74,6 +87,8 @@ angular.module('selectize', []).value('selectizeConfig', {}).directive("selectiz }; settings.onChange = function(value) { + if (scope._silentChanges) + return; // Avoid intermediary updates and side effects var items = angular.copy(selectize.items); if (settings.maxItems === 1) { items = items[0]; @@ -108,7 +123,7 @@ angular.module('selectize', []).value('selectizeConfig', {}).directive("selectiz break; } } - if (idx !== -1 ) { + if (idx !== -1) { scope._noUpdate = true; scope.options.splice(idx, 1); } From 35c307fafd3f5a4deb2e040c761723b62b883bab Mon Sep 17 00:00:00 2001 From: Philippe Lhoste Date: Thu, 10 Mar 2016 19:52:03 +0100 Subject: [PATCH 5/5] Fix update of model when removing items --- dist/angular-selectize.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dist/angular-selectize.js b/dist/angular-selectize.js index c3fa921..eb41aa0 100755 --- a/dist/angular-selectize.js +++ b/dist/angular-selectize.js @@ -62,6 +62,7 @@ angular.module('selectize', []).value('selectizeConfig', {}).directive("selectiz if (!angular.equals(selectize.items, scope.ngModel)) { if (scope.config.create && angular.isArray(scope.ngModel)) { scope._silentChanges = true; // addOption / createItem has no silent option! + selectize.clear(true); // Items might be in model but not in options: we create them in both, as user options for (var i = 0; i < scope.ngModel.length; i++) { var item = scope.ngModel[i]; @@ -144,7 +145,7 @@ angular.module('selectize', []).value('selectizeConfig', {}).directive("selectiz } scope.$watchCollection('options', setSelectizeOptions); - scope.$watch('ngModel', setSelectizeValue); + scope.$watchCollection('ngModel', setSelectizeValue); scope.$watch('ngDisabled', toggle); };