From 1f8cb735bb1dc3afafa35c385e747cfdb0d9b761 Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Tue, 22 Jan 2013 00:17:56 -0800 Subject: [PATCH 1/3] fix(select2) Can now set ngModel value to simple objects --- modules/directives/select2/select2.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/modules/directives/select2/select2.js b/modules/directives/select2/select2.js index 4531ee7..c9e827a 100644 --- a/modules/directives/select2/select2.js +++ b/modules/directives/select2/select2.js @@ -47,17 +47,24 @@ angular.module('ui.directives').directive('uiSelect2', ['ui.config', '$http', fu if (isSelect) { elm.select2('val', controller.$modelValue); } else { - if (isMultiple && !controller.$modelValue) { - elm.select2('data', []); - } else if (angular.isObject(controller.$modelValue)) { - elm.select2('data', controller.$modelValue); + if (isMultiple) { + if (!controller.$modelValue) { + elm.select2('data', []); + } else if (angular.isArray(controller.$modelValue)) { + elm.select2('data', controller.$modelValue); + } else { + elm.select2('val', controller.$modelValue); + } } else { - elm.select2('val', controller.$modelValue); + if (angular.isObject(controller.$modelValue)) { + elm.select2('data', controller.$modelValue); + } else { + elm.select2('val', controller.$modelValue); + } } } }; - // Watch the options dataset for changes if (watch) { scope.$watch(watch, function (newVal, oldVal, scope) { From d391124292c7051d770d707b3cab0f1a1b3f18c5 Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 6 Feb 2013 13:41:23 -0800 Subject: [PATCH 2/3] fix(select2): Fixes #409 - Updates model upon init when in tagging mode --- modules/directives/select2/select2.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/directives/select2/select2.js b/modules/directives/select2/select2.js index c9e827a..852daf5 100644 --- a/modules/directives/select2/select2.js +++ b/modules/directives/select2/select2.js @@ -5,7 +5,7 @@ * This change is so that you do not have to do an additional query yourself on top of Select2's own query * @params [options] {object} The configuration options passed to $.fn.select2(). Refer to the documentation */ -angular.module('ui.directives').directive('uiSelect2', ['ui.config', '$http', function (uiConfig, $http) { +angular.module('ui.directives').directive('uiSelect2', ['ui.config', '$timeout', function (uiConfig, $timeout) { var options = {}; if (uiConfig.select2) { angular.extend(options, uiConfig.select2); @@ -112,8 +112,11 @@ angular.module('ui.directives').directive('uiSelect2', ['ui.config', '$http', fu elm.val(scope.$eval(attrs.ngModel)); // Initialize the plugin late so that the injected DOM does not disrupt the template compiler - setTimeout(function () { + $timeout(function () { elm.select2(opts); + // 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')); }); }; } From 1146940e366db86fa4c8646dc393d86d87990afa Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Sat, 9 Feb 2013 03:48:09 -0800 Subject: [PATCH 3/3] tests(select2): Fleshing out and fixing a large number of Select2 tests --- modules/directives/select2/select2.js | 4 +- .../directives/select2/test/select2Spec.js | 152 +++++++++++++----- 2 files changed, 110 insertions(+), 46 deletions(-) diff --git a/modules/directives/select2/select2.js b/modules/directives/select2/select2.js index 852daf5..c94f6ea 100644 --- a/modules/directives/select2/select2.js +++ b/modules/directives/select2/select2.js @@ -24,7 +24,7 @@ angular.module('ui.directives').directive('uiSelect2', ['ui.config', '$timeout', repeatOption = tElm.find('option[ng-repeat], option[data-ng-repeat]'); if (repeatOption.length) { - repeatAttr = repeatOption.attr('ng-repeat') || repeatOption.attr('data-ng-repeat'); + repeatAttr = repeatOption.attr('ng-repeat') || repeatOption.attr('data-ng-repeat'); watch = jQuery.trim(repeatAttr.split('|')[0]).split(' ').pop(); } } @@ -70,7 +70,7 @@ angular.module('ui.directives').directive('uiSelect2', ['ui.config', '$timeout', scope.$watch(watch, function (newVal, oldVal, scope) { if (!newVal) return; // Delayed so that the options have time to be rendered - setTimeout(function () { + $timeout(function () { elm.select2('val', controller.$viewValue); // Refresh angular to remove the superfluous option elm.trigger('change'); diff --git a/modules/directives/select2/test/select2Spec.js b/modules/directives/select2/test/select2Spec.js index 5ca0e4d..3e393ae 100644 --- a/modules/directives/select2/test/select2Spec.js +++ b/modules/directives/select2/test/select2Spec.js @@ -2,11 +2,12 @@ describe('uiSelect2', function () { 'use strict'; - var scope, $compile, options; + var scope, $compile, options, $timeout; beforeEach(module('ui.directives')); - beforeEach(inject(function (_$rootScope_, _$compile_, _$window_) { + beforeEach(inject(function (_$rootScope_, _$compile_, _$window_, _$timeout_) { scope = _$rootScope_.$new(); $compile = _$compile_; + $timeout = _$timeout_; scope.options = { query: function (query) { var data = { @@ -17,64 +18,127 @@ describe('uiSelect2', function () { }; })); - describe('with a select element', function () { + /** + * Compile a template synchronously + * @param {String} template The string to compile + * @return {Object} A reference to the compiled template + */ + function compile(template) { + var element = $compile(template)(scope); + scope.$apply(); + $timeout.flush(); + return element; + } + + describe('with a ')(scope); - } - expect(compile).toThrow(); + expect(function(){ + compile(''); + }).toThrow(); }); - it('should proper DOM structure', function () { - scope.foo = 'bar'; - scope.$digest(); - var element = $compile('')(scope); - // DOM is created asynchronously - //expect(element.next().is('div.select2-container')).toBe(true); + it('should create proper DOM structure', function () { + var element = compile(''); + expect(element.siblings().is('div.select2-container')).toBe(true); + }); + }); + describe('when model is changed programmatically', function(){ + it('should set select2 to the value', function(){ + scope.foo = 'First'; + var element = compile(''); + expect(element.select2('val')).toBe('First'); + scope.$apply('foo = "Second"'); + expect(element.select2('val')).toBe('Second'); + }); + it('should set select2 to the value for multiples', function(){ + scope.foo = 'First'; + var element = compile(''); + expect(element.select2('val')).toEqual(['First']); + scope.$apply('foo = ["Second"]'); + expect(element.select2('val')).toEqual(['Second']); + scope.$apply('foo = ["Second","Third"]'); + expect(element.select2('val')).toEqual(['Second','Third']); }); }); it('should observe the disabled attribute', function () { - var element = $compile('')(scope); - - //expect(element.next().hasClass('select2-container-disabled')).toBe(false); - //scope.$apply(function(){ - // scope.disabled = true; - //}); - //expect(element.next().hasClass('select2-container-disabled')).toBe(true); - //scope.$apply(function(){ - // scope.disabled = false; - //}); - //expect(element.next().hasClass('select2-container-disabled')).toBe(false); + var element = compile(''); + expect(element.siblings().hasClass('select2-container-disabled')).toBe(false); + scope.$apply('disabled = true'); + expect(element.siblings().hasClass('select2-container-disabled')).toBe(true); + scope.$apply('disabled = false'); + expect(element.siblings().hasClass('select2-container-disabled')).toBe(false); }); it('should observe the multiple attribute', function () { var element = $compile('')(scope); - //expect(element.next().hasClass('select2-container-multi')).toBe(false); - //scope.$apply(function(){ - // scope.multiple = true; - //}); - //expect(element.next().hasClass('select2-container-multi')).toBe(true); - //scope.$apply(function(){ - // scope.multiple = false; - //}); - //expect(element.next().hasClass('select2-container-multi')).toBe(false); + expect(element.siblings().hasClass('select2-container-multi')).toBe(false); + scope.$apply('multiple = true'); + expect(element.siblings().hasClass('select2-container-multi')).toBe(true); + scope.$apply('multiple = false'); + expect(element.siblings().hasClass('select2-container-multi')).toBe(false); + }); + it('should observe an option with ng-repeat for changes', function(){ + scope.items = ['first', 'second', 'third']; + scope.foo = 'fourth'; + var element = compile(''); + expect(element.select2('val')).toNotBe('fourth'); + scope.$apply('items=["fourth"]'); + $timeout.flush(); + expect(element.select2('val')).toBe('fourth'); }); }); - describe('with an input element', function () { + describe('with an element', function () { describe('compiling this directive', function () { it('should throw an error if we have no model defined', function () { - function compile() { - $compile('')(scope); - } - expect(compile).toThrow(); + expect(function() { + compile(''); + }).toThrow(); + }); + it('should creae proper DOM structure', function () { + var element = compile(''); + expect(element.siblings().is('div.select2-container')).toBe(true); }); - it('should proper DOM structure', function () { - scope.foo = 'bar'; - scope.$digest(); - var element = $compile('')(scope); - // DOM is created asynchronously - // expect(element.next().is('div.select2-container')).toBe(true); + }); + describe('when model is changed programmatically', function(){ + describe('for single-select', function(){ + it('should call select2(data, ...) for objects', function(){ + var element = compile(''); + spyOn($.fn, 'select2'); + scope.$apply('foo={ id: 1, text: "first" }'); + expect(element.select2).toHaveBeenCalledWith('data', { id: 1, text: "first" }); + }); + it('should call select2(val, ...) for strings', function(){ + var element = compile(''); + spyOn($.fn, 'select2'); + scope.$apply('foo="first"'); + expect(element.select2).toHaveBeenCalledWith('val', 'first'); + }); }); + describe('for multi-select', function(){ + it('should call select2(data, ...) for arrays', function(){ + var element = compile(''); + spyOn($.fn, 'select2'); + scope.$apply('foo=[{ id: 1, text: "first" },{ id: 2, text: "second" }]'); + expect(element.select2).toHaveBeenCalledWith('data', [{ id: 1, text: "first" },{ id: 2, text: "second" }]); + }); + it('should call select2(data, []) for falsey values', function(){ + var element = compile(''); + spyOn($.fn, 'select2'); + scope.$apply('foo=[]'); + expect(element.select2).toHaveBeenCalledWith('data', []); + }); + it('should call select2(val, ...) for strings', function(){ + var element = compile(''); + spyOn($.fn, 'select2'); + scope.$apply('foo="first,second"'); + expect(element.select2).toHaveBeenCalledWith('val', 'first,second'); + }); + }); + }); + it('should set the model when the user selects an item', function(){ + var element = compile(''); + // TODO: programmactically select an option + // expect(scope.foo).toBe(/* selected val */); }); }); }); \ No newline at end of file