├── .bowerrc ├── .gitignore ├── .jshintrc ├── .travis.yml ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE ├── README.md ├── bower.json ├── demo ├── app.js ├── bower.json └── index.html ├── docs ├── index.html └── styles.css ├── package.json ├── src └── select2.js └── test ├── karma.conf.js └── select2Spec.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bower_components 3 | demo/bower_components -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": true, 6 | "newcap": true, 7 | "noarg": true, 8 | "sub": true, 9 | "boss": true, 10 | "eqnull": true 11 | } 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.8" 4 | - "0.10" 5 | 6 | before_install: 7 | - export DISPLAY=:99.0 8 | - sh -e /etc/init.d/xvfb start 9 | - npm install -g karma bower grunt-cli 10 | - bower install 11 | - npm install 12 | 13 | script: "grunt" 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | CONTRIBUTING 2 | ============ 3 | 4 | * Open a [Pull Request (PR)](https://github.com/angular-ui/ui-select2/pull/new/master) 5 | * Make sure your PR is on a **new branch** you created off of the latest version of master 6 | * Do **not** open a PR from your master branch 7 | * Open a PR to start a discussion even if the code isn't finished (easier to collect feedback this way) 8 | * Make sure all previous tests pass and add new tests for added behaviors 9 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | 'use strict'; 3 | 4 | var initConfig; 5 | 6 | // Loading external tasks 7 | require('load-grunt-tasks')(grunt); 8 | 9 | // Project configuration. 10 | initConfig = { 11 | bower: 'bower_components', 12 | pkg: grunt.file.readJSON('package.json'), 13 | watch: { 14 | test: { 15 | // Lint & run unit tests in Karma 16 | // Just running `$ grunt watch` will only lint your code; to run tests 17 | // on watch, use `$ grunt watch:karma` to start a Karma server first 18 | files: ['src/select2.js', 'test/select2Spec.js'], 19 | tasks: ['jshint', 'karma:unit:run'] 20 | } 21 | }, 22 | karma: { 23 | options: { 24 | configFile: 'test/karma.conf.js', 25 | browsers: ['Firefox', 'PhantomJS'] 26 | }, 27 | unit: { 28 | singleRun: true 29 | }, 30 | watch: { 31 | autoWatch: true 32 | }, 33 | server: { 34 | background: true 35 | } 36 | }, 37 | jshint: { 38 | all:[ 39 | 'gruntFile.js', 40 | 'src/**/*.js', 41 | 'test/**/*Spec.js' 42 | ], 43 | options: { 44 | jshintrc: '.jshintrc' 45 | } 46 | }, 47 | changelog: { 48 | options: { 49 | dest: 'CHANGELOG.md' 50 | } 51 | } 52 | }; 53 | 54 | // Register tasks 55 | grunt.registerTask('default', ['jshint', 'karma:unit']); 56 | grunt.registerTask('watch', ['jshint', 'karma:watch']); 57 | 58 | grunt.initConfig(initConfig); 59 | }; 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2012 the AngularUI Team, http://angular-ui.github.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ui-select2 (deprecated) [![Build Status](https://travis-ci.org/angular-ui/ui-select2.png)](https://travis-ci.org/angular-ui/ui-select2) 2 | ======================== 3 | 4 | # Annoucement 5 | 6 | 7 | This **directive is now obsolete**. A new initiative, more active, and 100% angular is available at https://github.com/angular-ui/ui-select. 8 | 9 | As development slowed down on ui-select2, it is getting unlikely that bugs will be fixed. So the new alternative should be used as soon as possible. 10 | 11 | # Description 12 | 13 | This directive allows you to enhance your select elements with behaviour from the [select2](http://ivaynberg.github.io/select2/) library. 14 | 15 | # Requirements 16 | 17 | - [AngularJS](http://angularjs.org/) 18 | - [JQuery](http://jquery.com/) 19 | - [Select2](http://ivaynberg.github.io/select2/) 20 | 21 | ## Setup 22 | 23 | 1. Install **Karma**, **Grunt** and **Bower** 24 | `$ npm install -g karma grunt-cli bower` 25 | 2. Install development dependencies 26 | `$ npm install` 27 | 3. Install components 28 | `$ bower install` 29 | 4. ??? 30 | 5. Profit! 31 | 32 | ## Testing 33 | 34 | We use [Grunt](http://gruntjs.com/) to check for JavaScript syntax errors and execute all unit tests. To run Grunt, simply execute: 35 | 36 | `$ grunt` 37 | 38 | This will lint and test the code, then exit. To have Grunt stay open and automatically lint and test your files whenever you make a code change, use: 39 | 40 | `$ grunt karma:server watch` 41 | 42 | This will start a Karma server in the background and run unit tests in Firefox and PhantomJS whenever the source code or spec file is saved. 43 | 44 | # Usage 45 | 46 | We use [bower](https://github.com/bower/bower) for dependency management. Install AngularUI Select2 into your project by running the command 47 | 48 | `$ bower install angular-ui-select2` 49 | 50 | If you use a `bower.json` file in your project, you can have Bower save ui-select2 as a dependency by passing the `--save` or `--save-dev` flag with the above command. 51 | 52 | This will copy the ui-select2 files into your `bower_components` folder, along with its dependencies. Load the script files in your application: 53 | ```html 54 | 55 | 56 | 57 | 58 | 59 | ``` 60 | 61 | (Note that `jquery` must be loaded before `angular` so that it doesn't use `jqLite` internally) 62 | 63 | 64 | Add the select2 module as a dependency to your application module: 65 | 66 | ```javascript 67 | var myAppModule = angular.module('MyApp', ['ui.select2']); 68 | ``` 69 | 70 | Apply the directive to your form elements: 71 | 72 | ```html 73 | 79 | ``` 80 | 81 | ## Options 82 | 83 | All the select2 options can be passed through the directive. You can read more about the supported list of options and what they do on the [Select2 Documentation Page](http://ivaynberg.github.com/select2/) 84 | 85 | ```javascript 86 | myAppModule.controller('MyController', function($scope) { 87 | $scope.select2Options = { 88 | allowClear:true 89 | }; 90 | }); 91 | ``` 92 | 93 | ```html 94 | 99 | ``` 100 | 101 | Some times it may make sense to specify the options in the template file. 102 | 103 | ```html 104 | 109 | ``` 110 | 111 | To define global defaults, you can configure the `uiSelect2Config` injectable: 112 | 113 | ```javascript 114 | myAppModule.run(['uiSelect2Config', function(uiSelect2Config) { 115 | uiSelect2Config.placeholder = "Placeholder text"; 116 | }]); 117 | ``` 118 | 119 | ## Working with ng-model 120 | 121 | The ui-select2 directive plays nicely with ng-model and validation directives such as ng-required. 122 | 123 | If you add the ng-model directive to same the element as ui-select2 then the picked option is automatically synchronized with the model value. 124 | 125 | ## Working with dynamic options 126 | `ui-select2` is incompatible with ` 129 | 130 | 131 | 132 | ``` 133 | 134 | ## Working with placeholder text 135 | In order to properly support the Select2 placeholder, create an empty ` 139 | 140 | 141 | 142 | 143 | ``` 144 | 145 | ## ng-required directive 146 | 147 | If you apply the required directive to element then the form element is invalid until an option is selected. 148 | 149 | Note: Remember that the ng-required directive must be explicitly set, i.e. to "true". This is especially true on divs: 150 | 151 | ```html 152 | 158 | ``` 159 | 160 | ## Using simple tagging mode 161 | 162 | When AngularJS View-Model tags are stored as a list of strings, setting 163 | the ui-select2 specific option `simple_tags` will allow to keep the model 164 | as a list of strings, and not convert it into a list of Select2 tag objects. 165 | 166 | ```html 167 | 172 | ``` 173 | 174 | ```javascript 175 | myAppModule.controller('MyController', function($scope) { 176 | $scope.list_of_string = ['tag1', 'tag2'] 177 | $scope.select2Options = { 178 | 'multiple': true, 179 | 'simple_tags': true, 180 | 'tags': ['tag1', 'tag2', 'tag3', 'tag4'] // Can be empty list. 181 | }; 182 | }); 183 | ``` 184 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "AngularUI", 3 | "name": "angular-ui-select2", 4 | "version": "0.0.5", 5 | "homepage": "http://angular-ui.github.com", 6 | "keywords": [ 7 | "angular", 8 | "angularui", 9 | "select2" 10 | ], 11 | "main": "./src/select2.js", 12 | "dependencies": { 13 | "angular": ">=1.2.0", 14 | "select2": "~3.4", 15 | "jquery": ">=1.6.4" 16 | }, 17 | "devDependencies": { 18 | "angular-mocks": ">=1.0.2" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /demo/app.js: -------------------------------------------------------------------------------- 1 | 2 | var app = angular.module('angular-ui-select2-demo', ['ui.select2']); 3 | 4 | app.controller('MainCtrl', function ($scope, $element) { 5 | 6 | var states = [ 7 | { text: 'Alaskan/Hawaiian Time Zone', children: [ 8 | { id: 'AK', text: 'Alaska' }, 9 | { id: 'HI', text: 'Hawaii' } 10 | ]}, 11 | { text: 'Pacific Time Zone', children: [ 12 | { id: 'CA', text: 'California' }, 13 | { id: 'NV', text: 'Nevada' }, 14 | { id: 'OR', text: 'Oregon' }, 15 | { id: 'WA', text: 'Washington' } 16 | ]}, 17 | { text: 'Mountain Time Zone', children: [ 18 | { id: 'AZ', text: 'Arizona' }, 19 | { id: 'CO', text: 'Colorado' }, 20 | { id: 'ID', text: 'Idaho' }, 21 | { id: 'MT', text: 'Montana' }, 22 | { id: 'NE', text: 'Nebraska' }, 23 | { id: 'NM', text: 'New Mexico' }, 24 | { id: 'ND', text: 'North Dakota' }, 25 | { id: 'UT', text: 'Utah' }, 26 | { id: 'WY', text: 'Wyoming' } 27 | ]}, 28 | { text: 'Central Time Zone', children: [ 29 | { id: 'AL', text: 'Alabama' }, 30 | { id: 'AR', text: 'Arkansas' }, 31 | { id: 'IL', text: 'Illinois' }, 32 | { id: 'IA', text: 'Iowa' }, 33 | { id: 'KS', text: 'Kansas' }, 34 | { id: 'KY', text: 'Kentucky' }, 35 | { id: 'LA', text: 'Louisiana' }, 36 | { id: 'MN', text: 'Minnesota' }, 37 | { id: 'MS', text: 'Mississippi' }, 38 | { id: 'MO', text: 'Missouri' }, 39 | { id: 'OK', text: 'Oklahoma' }, 40 | { id: 'SD', text: 'South Dakota' }, 41 | { id: 'TX', text: 'Texas' }, 42 | { id: 'TN', text: 'Tennessee' }, 43 | { id: 'WI', text: 'Wisconsin' } 44 | ]}, 45 | { text: 'Eastern Time Zone', children: [ 46 | { id: 'CT', text: 'Connecticut' }, 47 | { id: 'DE', text: 'Delaware' }, 48 | { id: 'FL', text: 'Florida' }, 49 | { id: 'GA', text: 'Georgia' }, 50 | { id: 'IN', text: 'Indiana' }, 51 | { id: 'ME', text: 'Maine' }, 52 | { id: 'MD', text: 'Maryland' }, 53 | { id: 'MA', text: 'Massachusetts' }, 54 | { id: 'MI', text: 'Michigan' }, 55 | { id: 'NH', text: 'New Hampshire' }, 56 | { id: 'NJ', text: 'New Jersey' }, 57 | { id: 'NY', text: 'New York' }, 58 | { id: 'NC', text: 'North Carolina' }, 59 | { id: 'OH', text: 'Ohio' }, 60 | { id: 'PA', text: 'Pennsylvania' }, 61 | { id: 'RI', text: 'Rhode Island' }, 62 | { id: 'SC', text: 'South Carolina' }, 63 | { id: 'VT', text: 'Vermont' }, 64 | { id: 'VA', text: 'Virginia' }, 65 | { id: 'WV', text: 'West Virginia' } 66 | ]} 67 | ]; 68 | 69 | function findState(id) { 70 | for (var i=0; i=1.2.0", 6 | "select2": "~3.4", 7 | "jquery": ">=1.6.4", 8 | "bootstrap": "~3.0.3", 9 | "angular-ui-select2": "~0.0.5" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 32 | 33 | 34 | 35 | 36 | 37 | 43 | 44 | 45 |
46 | 47 |
48 |

The Basics

49 |
50 |
51 | 52 | 114 |
115 |
116 |

Value

117 |
{{ basicsValue }}
118 |
119 |
120 |
121 | 122 |
123 |

Multi-Value

124 |
125 |
126 | 127 | 128 |
129 |
130 |

Value

131 |
{{ multiValue }}
132 |
133 |
134 |
135 |
136 | 137 | 138 |
139 |
140 |

Config

141 |
{
142 |     multiple: true,
143 |     query: function (query) {
144 |       query.callback({ results: states });
145 |     },
146 |     initSelection: function(element, callback) {
147 |       var val = $(element).select2('val'),
148 |         results = [];
149 |       for (var i=0; i<val.length; i++) {
150 |         results.push(findState(val[i]));
151 |       }
152 |       callback(results);
153 |     }
154 |   }
155 |

Value

156 |
{{ multi2Value }}
157 |
158 |
159 |
160 | 161 |
162 |

Placeholders

163 |
164 |
165 | 166 | 167 |
168 |
169 |

Value

170 |
{{ placeholdersValue }}
171 |
172 |
173 |
174 |
175 | 176 | 177 |
178 |
179 |

Config

180 |
{{ placeholders }}
181 |

Value

182 |
{{ placeholdersMultiValue }}
183 |
184 |
185 |
186 | 187 |
188 |

Array Data

189 |
190 |
191 | 192 | 193 |
194 |
195 |

Config

196 |
{{ array }}
197 |

Value

198 |
{{ arrayValue }}
199 |
200 |
201 |
202 |
203 | 204 | 205 |
206 |
207 |

Config

208 |
{
209 |     query: function (query) {
210 |       query.callback({ results: states });
211 |     },
212 |     initSelection: function(element, callback) {
213 |       var val = $(element).select2('val');
214 |       return callback(findState(val));
215 |     }
216 |   }
217 |

Value

218 |
{{ arrayAsyncValue }}
219 |
220 |
221 |
222 | 223 |
224 | 225 | 226 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 |
2 | 6 |
7 |
8 |

Demo

9 |
10 |

Value is: {{select2}} (choose second)

11 | 17 |
18 | 19 |
20 |

Value is: {{select2multiple}} (choose second)

21 | 26 |
27 |
28 |
29 |

Options

30 |

You can pass an object to Select2 as the expression: ui-select2="{allowClear:true}" that will be passed directly to $.fn.select2(). You can read more about the supported list of options and what they do on the Select2 Documentation Page. AngularUI will leverage properties passed to Select2 for any complex behavior, there are no parameters necessary for that are specific to AngularUI.

31 |
32 |
33 |

ui-select2 is incompatible with <select ng-options>. For the best results use <option ng-repeat> instead

34 |

In order to properly support the Select2 placeholder, create an empty <option> tag at the top of the <select> and either set a data-placeholder on the select element or pass a placeholder option to Select2.

35 | 36 |

How?

37 |
38 | <p>Value is: {{select2}} <a ng-click="select2='two'">(choose second)</a></p>
39 | <select ui-select2 ng-model="select2">
40 | <option value="">Pick a number</option>
41 | <option value="one">First</option>
42 | <option value="two">Second</option>
43 | <option value="three">Third</option>
44 | </select>
45 | 
46 |

Or try playing around with this sandbox demo to see how AJAX works

47 |
-------------------------------------------------------------------------------- /docs/styles.css: -------------------------------------------------------------------------------- 1 | 2 | #directives-select2 select { 3 | width: 200px; 4 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "https://github.com/angular-ui/ui-select2/graphs/contributors", 3 | "name": "angular-ui-select2", 4 | "keywords": [ 5 | "angular", 6 | "angularui", 7 | "select2" 8 | ], 9 | "description": "AngularUI - The companion suite for AngularJS", 10 | "version": "0.0.5", 11 | "homepage": "http://angular-ui.github.com", 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/angular-ui/ui-select2.git" 15 | }, 16 | "engines": { 17 | "node": ">= 0.8.4" 18 | }, 19 | "dependencies": {}, 20 | "devDependencies": { 21 | "async": "0.1.x", 22 | "grunt": "~0.4.1", 23 | "grunt-contrib-jshint": "~0.6.4", 24 | "grunt-contrib-watch": "~0.5.3", 25 | "grunt-karma": "~0.6.2", 26 | "karma": "~0.10.2", 27 | "grunt-conventional-changelog": "~1.0.0", 28 | "load-grunt-tasks": "~0.2.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/select2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Enhanced Select2 Dropmenus 3 | * 4 | * @AJAX Mode - When in this mode, your value will be an object (or array of objects) of the data used by Select2 5 | * This change is so that you do not have to do an additional query yourself on top of Select2's own query 6 | * @params [options] {object} The configuration options passed to $.fn.select2(). Refer to the documentation 7 | */ 8 | angular.module('ui.select2', []).value('uiSelect2Config', {}).directive('uiSelect2', ['uiSelect2Config', '$timeout', function (uiSelect2Config, $timeout) { 9 | var options = {}; 10 | if (uiSelect2Config) { 11 | angular.extend(options, uiSelect2Config); 12 | } 13 | return { 14 | require: 'ngModel', 15 | priority: 1, 16 | compile: function (tElm, tAttrs) { 17 | var watch, 18 | repeatOption, 19 | repeatAttr, 20 | isSelect = tElm.is('select'), 21 | isMultiple = angular.isDefined(tAttrs.multiple); 22 | 23 | // Enable watching of the options dataset if in use 24 | if (tElm.is('select')) { 25 | repeatOption = tElm.find( 'optgroup[ng-repeat], optgroup[data-ng-repeat], option[ng-repeat], option[data-ng-repeat]'); 26 | 27 | if (repeatOption.length) { 28 | repeatAttr = repeatOption.attr('ng-repeat') || repeatOption.attr('data-ng-repeat'); 29 | watch = jQuery.trim(repeatAttr.split('|')[0]).split(' ').pop(); 30 | } 31 | } 32 | 33 | return function (scope, elm, attrs, controller) { 34 | // instance-specific options 35 | var opts = angular.extend({}, options, scope.$eval(attrs.uiSelect2)); 36 | 37 | /* 38 | Convert from Select2 view-model to Angular view-model. 39 | */ 40 | var convertToAngularModel = function(select2_data) { 41 | var model; 42 | if (opts.simple_tags) { 43 | model = []; 44 | angular.forEach(select2_data, function(value, index) { 45 | model.push(value.id); 46 | }); 47 | } else { 48 | model = select2_data; 49 | } 50 | return model; 51 | }; 52 | 53 | /* 54 | Convert from Angular view-model to Select2 view-model. 55 | */ 56 | var convertToSelect2Model = function(angular_data) { 57 | var model = []; 58 | if (!angular_data) { 59 | return model; 60 | } 61 | 62 | if (opts.simple_tags) { 63 | model = []; 64 | angular.forEach( 65 | angular_data, 66 | function(value, index) { 67 | model.push({'id': value, 'text': value}); 68 | }); 69 | } else { 70 | model = angular_data; 71 | } 72 | return model; 73 | }; 74 | 75 | if (isSelect) { 76 | // Use element', function () { 100 | describe('compiling this directive', function () { 101 | it('should throw an error if we have no model defined', function () { 102 | expect(function(){ 103 | compile(''); 104 | }).toThrow(); 105 | }); 106 | it('should create proper DOM structure', function () { 107 | var element = compile(''); 108 | expect(element.siblings().is('div.select2-container')).toBe(true); 109 | }); 110 | it('should not modify the model if there is no initial value', function(){ 111 | //TODO 112 | }); 113 | }); 114 | describe('when model is changed programmatically', function(){ 115 | describe('for single select', function(){ 116 | it('should set select2 to the value', function(){ 117 | scope.foo = 'First'; 118 | var element = compile(''); 119 | expect(element.select2('val')).toBe('First'); 120 | scope.$apply('foo = "Second"'); 121 | expect(element.select2('val')).toBe('Second'); 122 | }); 123 | it('should handle falsey values', function(){ 124 | scope.foo = 'First'; 125 | var element = compile(''); 126 | expect(element.select2('val')).toBe('First'); 127 | scope.$apply('foo = false'); 128 | expect(element.select2('val')).toBe(null); 129 | scope.$apply('foo = "Second"'); 130 | scope.$apply('foo = null'); 131 | expect(element.select2('val')).toBe(null); 132 | scope.$apply('foo = "Second"'); 133 | scope.$apply('foo = undefined'); 134 | expect(element.select2('val')).toBe(null); 135 | }); 136 | }); 137 | describe('for multiple select', function(){ 138 | it('should set select2 to multiple value', function(){ 139 | scope.foo = ['First']; 140 | var element = compile(''); 141 | expect(element.select2('val')).toEqual(['First']); 142 | scope.$apply('foo = ["Second"]'); 143 | expect(element.select2('val')).toEqual(['Second']); 144 | scope.$apply('foo = ["Second","Third"]'); 145 | expect(element.select2('val')).toEqual(['Second','Third']); 146 | }); 147 | it('should handle falsey values', function(){ 148 | scope.foo = ['First']; 149 | var element = compile(''); 150 | expect(element.val()).toEqual(['First']); 151 | scope.$apply('foo = ["Second"]'); 152 | scope.$apply('foo = false'); 153 | expect(element.select2('val')).toEqual([]); 154 | scope.$apply('foo = ["Second"]'); 155 | scope.$apply('foo = null'); 156 | expect(element.select2('val')).toEqual([]); 157 | scope.$apply('foo = ["Second"]'); 158 | scope.$apply('foo = undefined'); 159 | expect(element.select2('val')).toEqual([]); 160 | }); 161 | }); 162 | }); 163 | it('should observe the disabled attribute', function () { 164 | var element = compile(''); 165 | expect(element.siblings().hasClass('select2-container-disabled')).toBe(false); 166 | scope.$apply('disabled = true'); 167 | expect(element.siblings().hasClass('select2-container-disabled')).toBe(true); 168 | scope.$apply('disabled = false'); 169 | expect(element.siblings().hasClass('select2-container-disabled')).toBe(false); 170 | }); 171 | it('should observe the multiple attribute', function () { 172 | var element = $compile('')(scope); 173 | 174 | expect(element.siblings().hasClass('select2-container-multi')).toBe(false); 175 | scope.$apply('multiple = true'); 176 | expect(element.siblings().hasClass('select2-container-multi')).toBe(true); 177 | scope.$apply('multiple = false'); 178 | expect(element.siblings().hasClass('select2-container-multi')).toBe(false); 179 | }); 180 | it('should observe an option with ng-repeat for changes', function(){ 181 | scope.items = ['first', 'second', 'third']; 182 | scope.foo = 'fourth'; 183 | var element = compile(''); 184 | expect(element.select2('val')).toBe(null); 185 | scope.$apply('foo="fourth";items=["fourth"]'); 186 | $timeout.flush(); 187 | expect(element.select2('val')).toBe('fourth'); 188 | }); 189 | }); 190 | describe('with an element', function () { 191 | describe('compiling this directive', function () { 192 | it('should throw an error if we have no model defined', function () { 193 | expect(function() { 194 | compile(''); 195 | }).toThrow(); 196 | }); 197 | it('should create proper DOM structure', function () { 198 | var element = compile(''); 199 | expect(element.siblings().is('div.select2-container')).toBe(true); 200 | }); 201 | it('should not modify the model if there is no initial value', function(){ 202 | //TODO 203 | }); 204 | }); 205 | describe('when model is changed programmatically', function(){ 206 | describe('for single-select', function(){ 207 | it('should call select2(data, ...) for objects', function(){ 208 | var element = compile(''); 209 | spyOn($.fn, 'select2'); 210 | scope.$apply('foo={ id: 1, text: "first" }'); 211 | expect(element.select2).toHaveBeenCalledWith('data', { id: 1, text: "first" }); 212 | }); 213 | it('should call select2(val, ...) for strings', function(){ 214 | var element = compile(''); 215 | spyOn($.fn, 'select2'); 216 | scope.$apply('foo="first"'); 217 | expect(element.select2).toHaveBeenCalledWith('val', 'first'); 218 | }); 219 | }); 220 | describe('for multi-select', function(){ 221 | it('should call select2(data, ...) for arrays', function(){ 222 | var element = compile(''); 223 | spyOn($.fn, 'select2'); 224 | scope.$apply('foo=[{ id: 1, text: "first" },{ id: 2, text: "second" }]'); 225 | expect(element.select2).toHaveBeenCalledWith('data', [{ id: 1, text: "first" },{ id: 2, text: "second" }]); 226 | }); 227 | it('should call select2(data, []) for falsey values', function(){ 228 | var element = compile(''); 229 | spyOn($.fn, 'select2'); 230 | scope.$apply('foo=[]'); 231 | expect(element.select2).toHaveBeenCalledWith('data', []); 232 | }); 233 | xit('should call select2(val, ...) for strings', function(){ 234 | var element = compile(''); 235 | spyOn($.fn, 'select2'); 236 | scope.$apply('foo="first,second"'); 237 | expect(element.select2).toHaveBeenCalledWith('val', 'first,second'); 238 | }); 239 | }); 240 | }); 241 | describe('consumers of ngModel should correctly use $viewValue', function() { 242 | it('should use any formatters if present (select - single select)', function(){ 243 | scope.foo = 'First'; 244 | var element = compile(''); 245 | expect(element.select2('val')).toBe('First - I\'ve been formatted'); 246 | scope.$apply('foo = "Second"'); 247 | expect(element.select2('val')).toBe('Second - I\'ve been formatted'); 248 | }); 249 | 250 | // isMultiple && falsey 251 | it('should use any formatters if present (input multi select - falsey value)', function() { 252 | // need special function to hit this case 253 | // old code checked modelValue... can't just pass undefined to model value because view value will be the same 254 | scope.transformers.fromModel = function(modelValue) { 255 | if (modelValue === "magic") { 256 | return undefined; 257 | } 258 | 259 | return modelValue; 260 | }; 261 | 262 | var element = compile(''); 263 | spyOn($.fn, 'select2'); 264 | scope.$apply('foo="magic"'); 265 | expect(element.select2).toHaveBeenCalledWith('data', []); 266 | }); 267 | // isMultiple && isArray 268 | it('should use any formatters if present (input multi select)', function() { 269 | var element = compile(''); 270 | spyOn($.fn, 'select2'); 271 | scope.$apply('foo=[{ id: 1, text: "first" },{ id: 2, text: "second" }]'); 272 | expect(element.select2).toHaveBeenCalledWith('data', [{ id: 1, text: "first - I've been formatted" },{ id: 2, text: "second - I've been formatted" }]); 273 | }); 274 | // isMultiple... 275 | xit('should use any formatters if present (input multi select - non array)', function() { 276 | var element = compile(''); 277 | spyOn($.fn, 'select2'); 278 | scope.$apply('foo={ id: 1, text: "first" }'); 279 | expect(element.select2).toHaveBeenCalledWith('val', { id: 1, text: "first - I've been formatted" }); 280 | }); 281 | 282 | // !isMultiple 283 | it('should use any formatters if present (input - single select - object)', function() { 284 | var element = compile(''); 285 | spyOn($.fn, 'select2'); 286 | scope.$apply('foo={ id: 1, text: "first" }'); 287 | expect(element.select2).toHaveBeenCalledWith('data', { id: 1, text: "first - I've been formatted" }); 288 | }); 289 | it('should use any formatters if present (input - single select - non object)', function() { 290 | var element = compile(''); 291 | spyOn($.fn, 'select2'); 292 | scope.$apply('foo="first"'); 293 | expect(element.select2).toHaveBeenCalledWith('val', "first - I've been formatted"); 294 | }); 295 | 296 | it('should not set the default value using scope.$eval', function() { 297 | // testing directive instantiation - change order of test 298 | spyOn($.fn, 'select2'); 299 | spyOn($.fn, 'val'); 300 | scope.$apply('foo=[{ id: 1, text: "first" },{ id: 2, text: "second" }]'); 301 | 302 | var element = compile(''); 303 | expect(element.val).not.toHaveBeenCalledWith([{ id: 1, text: "first" },{ id: 2, text: "second" }]); 304 | }); 305 | it('should expect a default value to be set with a call to the render method', function() { 306 | // this should monitor the events after init, when the timeout callback executes 307 | var opts = angular.copy(scope.options); 308 | opts.multiple = true; 309 | 310 | scope.$apply('foo=[{ id: 1, text: "first" },{ id: 2, text: "second" }]'); 311 | 312 | spyOn($.fn, 'select2'); 313 | var element = compile(''); 314 | 315 | // select 2 init 316 | expect(element.select2).toHaveBeenCalledWith(opts); 317 | 318 | // callback setting 319 | expect(element.select2).toHaveBeenCalledWith('data', [{ id: 1, text: "first - I've been formatted" },{ id: 2, text: "second - I've been formatted" }]); 320 | 321 | // retieve data 322 | expect(element.select2).toHaveBeenCalledWith('data'); 323 | }); 324 | 325 | }); 326 | it('should set the model when the user selects an item', function(){ 327 | var element = compile(''); 328 | // TODO: programmactically select an option 329 | // expect(scope.foo).toBe(/* selected val */) ; 330 | }); 331 | 332 | it('updated the view when model changes with complex object', function(){ 333 | scope.foo = [{'id': '0', 'text': '0'}]; 334 | scope.options['multiple'] = true; 335 | var element = compile(''); 336 | scope.$digest(); 337 | 338 | scope.foo.push({'id': '1', 'text': '1'}); 339 | scope.$digest(); 340 | 341 | expect(element.select2('data')).toEqual( 342 | [{'id': '0', 'text': '0'}, {'id': '1', 'text': '1'}]); 343 | }); 344 | 345 | 346 | describe('simple_tags', function() { 347 | 348 | beforeEach(function() { 349 | scope.options['multiple'] = true; 350 | scope.options['simple_tags'] = true; 351 | scope.options['tags'] = []; 352 | }); 353 | 354 | it('Initialize the select2 view based on list of strings.', function() { 355 | scope.foo = ['tag1', 'tag2']; 356 | 357 | var element = compile(''); 358 | scope.$digest(); 359 | 360 | expect(element.select2('data')).toEqual([ 361 | {'id': 'tag1', 'text': 'tag1'}, 362 | {'id': 'tag2', 'text': 'tag2'} 363 | ]); 364 | }); 365 | 366 | it( 367 | 'When list is empty select2 view model is also initialized as empty', 368 | function() { 369 | scope.foo = []; 370 | 371 | var element = compile(''); 372 | scope.$digest(); 373 | 374 | expect(element.select2('data')).toEqual([]); 375 | }); 376 | 377 | it( 378 | 'Updating the model with a string will update the select2 view model.', 379 | function() { 380 | scope.foo = []; 381 | var element = compile(''); 382 | scope.$digest(); 383 | 384 | scope.foo.push('tag1'); 385 | scope.$digest(); 386 | 387 | expect(element.select2('data')).toEqual([ 388 | {'id': 'tag1', 'text': 'tag1'} 389 | ]); 390 | }); 391 | 392 | it( 393 | 'Updating the select2 model will update AngularJS model with a string.', 394 | function() { 395 | scope.foo = []; 396 | var element = compile(''); 397 | scope.$digest(); 398 | 399 | element.select2('data', [ 400 | {'id':'tag1', 'text': 'tag1'}, 401 | {'id':'tag2', 'text': 'tag2'} 402 | ]); 403 | element.trigger('change'); 404 | 405 | expect(scope.foo).toEqual(['tag1', 'tag2']); 406 | }); 407 | 408 | }); 409 | 410 | }); 411 | }); --------------------------------------------------------------------------------