Permalink
Browse files

Merge pull request #443 from antmt/select2_directive_bug_fix

Select2 Refactor
  • Loading branch information...
2 parents eea2cb1 + 8d64b8f commit 992477a84d41c79b744cc2102a1f38808cc3f1d1 @ProLoser ProLoser committed Feb 23, 2013
Showing with 171 additions and 11 deletions.
  1. +17 −9 modules/directives/select2/select2.js
  2. +154 −2 modules/directives/select2/test/select2Spec.js
@@ -45,21 +45,23 @@ angular.module('ui.directives').directive('uiSelect2', ['ui.config', '$timeout',
// Watch the model for programmatic changes
controller.$render = function () {
if (isSelect) {
- elm.select2('val', controller.$modelValue);
+ elm.select2('val', controller.$viewValue);
} else {
if (isMultiple) {
- if (!controller.$modelValue) {
+ if (!controller.$viewValue) {
elm.select2('data', []);
- } else if (angular.isArray(controller.$modelValue)) {
- elm.select2('data', controller.$modelValue);
+ } else if (angular.isArray(controller.$viewValue)) {
+ elm.select2('data', controller.$viewValue);
} else {
- elm.select2('val', controller.$modelValue);
+ elm.select2('val', controller.$viewValue);
}
} else {
- if (angular.isObject(controller.$modelValue)) {
- elm.select2('data', controller.$modelValue);
+ if (angular.isObject(controller.$viewValue)) {
+ elm.select2('data', controller.$viewValue);
+ } else if (!controller.$viewValue) {
+ elm.select2('data', null);
} else {
- elm.select2('val', controller.$modelValue);
+ elm.select2('val', controller.$viewValue);
}
}
}
@@ -109,11 +111,17 @@ angular.module('ui.directives').directive('uiSelect2', ['ui.config', '$timeout',
}
// Set initial value since Angular doesn't
- elm.val(scope.$eval(attrs.ngModel));
+ //elm.val(scope.$eval(attrs.ngModel));
// Initialize the plugin late so that the injected DOM does not disrupt the template compiler
$timeout(function () {
elm.select2(opts);
+
+ // Set initial value - I'm not sure about this but it seems to need to be there
+ elm.val(controller.$viewValue);
+ // important!
+ controller.$render();
+
// Not sure if I should just check for !isSelect OR if I should check for 'tags' key
if (!opts.initSelection && !isSelect)
controller.$setViewValue(elm.select2('data'));
@@ -1,3 +1,22 @@
+// a helper directive for injecting formatters and parsers
+angular.module('ui.directives').directive('injectTransformers', [ function () {
+ return {
+ restrict: 'A',
+ require: 'ngModel',
+ priority: -1,
+ link: function (scope, element, attr, ngModel) {
+ var local = scope.$eval(attr.injectTransformers);
+
+ if (!angular.isObject(local) || !angular.isFunction(local.fromModel) || !angular.isFunction(local.fromElement)) {
+ throw "The injectTransformers directive must be bound to an object with two functions (`fromModel` and `fromElement`)";
+ }
+
+ ngModel.$parsers.push(local.fromElement);
+ ngModel.$formatters.push(local.fromModel);
+ }
+ };
+}]);
+
/*global describe, beforeEach, module, inject, it, spyOn, expect, $ */
describe('uiSelect2', function () {
'use strict';
@@ -16,6 +35,54 @@ describe('uiSelect2', function () {
query.callback(data);
}
};
+
+ scope.transformers = {
+ fromModel: function (modelValue) {
+ if (!modelValue) {
+ return modelValue;
+ }
+
+ if (angular.isArray(modelValue)) {
+ return modelValue.map(function (val) {
+ val.text += " - I've been formatted";
+ return val;
+ });
+ }
+
+ if (angular.isObject(modelValue)) {
+ modelValue.text += " - I've been formatted";
+ return modelValue;
+ }
+
+ return modelValue + " - I've been formatted";
+ },
+ fromElement: function (elementValue) {
+ var suffix = " - I've been formatted";
+
+ if (!elementValue) {
+ return elementValue;
+ }
+
+ if (angular.isArray(elementValue)) {
+ return elementValue.map(function (val) {
+ val.text += val.text.slice(0, val.text.indexOf(" - I've been formatted"));
+ return val;
+ });
+ }
+
+ if (angular.isObject(elementValue)) {
+
+ elementValue.text = elementValue.text.slice(0, elementValue.text.indexOf(suffix));
+ return elementValue;
+ }
+
+ if (elementValue) {
+ return elementValue.slice(0, elementValue.indexOf(suffix));
+ }
+
+ return undefined;
+ }
+ };
}));
/**
@@ -94,7 +161,7 @@ describe('uiSelect2', function () {
compile('<input ui-select2/>');
}).toThrow();
});
- it('should creae proper DOM structure', function () {
+ it('should create proper DOM structure', function () {
var element = compile('<input ui-select2="options" ng-model="foo"/>');
expect(element.siblings().is('div.select2-container')).toBe(true);
});
@@ -135,10 +202,95 @@ describe('uiSelect2', function () {
});
});
});
+ describe('consumers of ngModel should correctly use $viewValue', function() {
+ it('should use any formatters if present (select - single select)', function(){
+ scope.foo = 'First';
+ var element = compile('<select ui-select2 ng-model="foo" inject-transformers="transformers"><option>First - I\'ve been formatted</option><option>Second - I\'ve been formatted</option></select>');
+ expect(element.select2('val')).toBe('First - I\'ve been formatted');
+ scope.$apply('foo = "Second"');
+ expect(element.select2('val')).toBe('Second - I\'ve been formatted');
+ });
+
+ // isMultiple && falsey
+ it('should use any formatters if present (input multi select - falsey value)', function() {
+ // need special function to hit this case
+ // old code checked modelValue... can't just pass undefined to model value because view value will be the same
+ scope.transformers.fromModel = function(modelValue) {
+ if (modelValue === "magic") {
+ return undefined;
+ }
+
+ return modelValue;
+ };
+
+ var element = compile('<input ng-model="foo" multiple ui-select2="options" inject-transformers="transformers">');
+ spyOn($.fn, 'select2');
+ scope.$apply('foo="magic"');
+ expect(element.select2).toHaveBeenCalledWith('data', []);
+ });
+ // isMultiple && isArray
+ it('should use any formatters if present (input multi select)', function() {
+ var element = compile('<input ng-model="foo" multiple ui-select2="options" inject-transformers="transformers">');
+ spyOn($.fn, 'select2');
+ scope.$apply('foo=[{ id: 1, text: "first" },{ id: 2, text: "second" }]');
+ expect(element.select2).toHaveBeenCalledWith('data', [{ id: 1, text: "first - I've been formatted" },{ id: 2, text: "second - I've been formatted" }]);
+ });
+ // isMultiple...
+ it('should use any formatters if present (input multi select - non array)', function() {
+ var element = compile('<input ng-model="foo" multiple ui-select2="options" inject-transformers="transformers">');
+ spyOn($.fn, 'select2');
+ scope.$apply('foo={ id: 1, text: "first" }');
+ expect(element.select2).toHaveBeenCalledWith('val', { id: 1, text: "first - I've been formatted" });
+ });
+
+ // !isMultiple
+ it('should use any formatters if present (input - single select - object)', function() {
+ var element = compile('<input ng-model="foo" ui-select2="options" inject-transformers="transformers">');
+ spyOn($.fn, 'select2');
+ scope.$apply('foo={ id: 1, text: "first" }');
+ expect(element.select2).toHaveBeenCalledWith('data', { id: 1, text: "first - I've been formatted" });
+ });
+ it('should use any formatters if present (input - single select - non object)', function() {
+ var element = compile('<input ng-model="foo" ui-select2="options" inject-transformers="transformers">');
+ spyOn($.fn, 'select2');
+ scope.$apply('foo="first"');
+ expect(element.select2).toHaveBeenCalledWith('val', "first - I've been formatted");
+ });
+
+ it('should not set the default value using scope.$eval', function() {
+ // testing directive instantiation - change order of test
+ spyOn($.fn, 'select2');
+ spyOn($.fn, 'val');
+ scope.$apply('foo=[{ id: 1, text: "first" },{ id: 2, text: "second" }]');
+
+ var element = compile('<input ng-model="foo" multiple ui-select2="options" inject-transformers="transformers">');
+ expect(element.val).not.toHaveBeenCalledWith([{ id: 1, text: "first" },{ id: 2, text: "second" }]);
+ });
+ it('should expect a default value to be set with a call to the render method', function() {
+ // this should monitor the events after init, when the timeout callback executes
+ var opts = angular.copy(scope.options);
+ opts.multiple = true;
+
+ scope.$apply('foo=[{ id: 1, text: "first" },{ id: 2, text: "second" }]');
+
+ spyOn($.fn, 'select2');
+ var element = compile('<input ng-model="foo" multiple ui-select2="options" inject-transformers="transformers">');
+
+ // select 2 init
+ expect(element.select2).toHaveBeenCalledWith(opts);
+
+ // callback setting
+ expect(element.select2).toHaveBeenCalledWith('data', [{ id: 1, text: "first - I've been formatted" },{ id: 2, text: "second - I've been formatted" }]);
+
+ // retieve data
+ expect(element.select2).toHaveBeenCalledWith('data');
+ });
+
+ });
it('should set the model when the user selects an item', function(){
var element = compile('<input ng-model="foo" multiple ui-select2="options">');
// TODO: programmactically select an option
- // expect(scope.foo).toBe(/* selected val */);
+ // expect(scope.foo).toBe(/* selected val */) ;
});
});
});

0 comments on commit 992477a

Please sign in to comment.