`, otherwise you'll encounter strange off-by-one errors:
```html
```
> This annoying behavior is alluded to in the examples in the [documentation for ngOptions](http://docs.angularjs.org/api/ng.directive:select).
#### Works well with other AngularJS directives
```html
```
### Loading from a remote data source
Include `chosen-spinner.css` and `spinner.gif` to show an Ajax spinner icon while your data is loading. If the collection comes back empty, the directive will disable the element and show a default
"No values available" message. You can customize this message by passing in `noResultsText` in your options.
##### app.js
```js
angular.module('App', ['ngResource', 'localytics.directives'])
.controller('BeerCtrl', function($scope, $resource) {
$scope.beers = $resource('api/beers').query()
});
```
##### index.html
```html
```
Image of select defined above in loading state:
` `
> Note: Assigning promises directly to scope is now deprecated in Angular 1.2+. Assign the results of the promise to scope
once the promise returns. The loader animation will still work as long as the collection expression
evaluates to `undefined` while your data is loading!
### Default values with chosenProvider (thanks @zlodes) [since 1.6.0]
```javascript
angular.module('chosenExampleApp', ['localytics.directives'])
.config(['chosenProvider', function (chosenProvider) {
chosenProvider.setOption({
no_results_text: 'There is no results!',
placeholder_text_multiple: 'Choose one or more!'
});
}]);
```
================================================
FILE: bower.json
================================================
{
"name": "angular-chosen-localytics",
"repository": {
"type": "git",
"url": "git://github.com/leocaseiro/angular-chosen.git"
},
"main": "dist/angular-chosen.js",
"ignore": [
"gulpfile.js",
"src",
"test"
],
"dependencies": {
"jquery": "^2.0.3",
"chosen": "^1.5.1",
"angular": "^1.4.9"
},
"license": "MIT"
}
================================================
FILE: chosen-spinner.css
================================================
/* Additional styles to display a spinner image while options are loading */
.localytics-chosen.loading+.chosen-container-multi .chosen-choices {
background-image: url('spinner.gif');
background-repeat: no-repeat;
background-position: 95%;
}
.localytics-chosen.loading+.chosen-container-single .chosen-single span {
background: url('spinner.gif') no-repeat right;
}
.localytics-chosen.loading+.chosen-container-single .chosen-single .search-choice-close {
display: none;
}
================================================
FILE: chosen.js
================================================
console.warn('the file ./chosen.js is deprecated, you must include ./dist/angular-chosen.js or ./dist/angular-chosen.min.js instead');
================================================
FILE: dist/angular-chosen.js
================================================
/**
* angular-chosen-localytics - Angular Chosen directive is an AngularJS Directive that brings the Chosen jQuery in a Angular way
* @version v1.9.2
* @link http://github.com/leocaseiro/angular-chosen
* @license MIT
*/
(function() {
var chosenModule,
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
angular.module('localytics.directives', []);
chosenModule = angular.module('localytics.directives');
chosenModule.provider('chosen', function() {
var options;
options = {};
return {
setOption: function(newOpts) {
angular.extend(options, newOpts);
},
$get: function() {
return options;
}
};
});
chosenModule.directive('chosen', [
'chosen', '$timeout', '$parse', function(config, $timeout, $parse) {
var CHOSEN_OPTION_WHITELIST, NG_OPTIONS_REGEXP, isEmpty, snakeCase;
NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/;
CHOSEN_OPTION_WHITELIST = ['allowSingleDeselect', 'disableSearch', 'disableSearchThreshold', 'enableSplitWordSearch', 'inheritSelectClasses', 'maxSelectedOptions', 'noResultsText', 'placeholderTextMultiple', 'placeholderTextSingle', 'searchContains', 'groupSearch', 'singleBackstrokeDelete', 'width', 'displayDisabledOptions', 'displaySelectedOptions', 'includeGroupLabelInSelected', 'maxShownResults', 'caseSensitiveSearch', 'hideResultsOnSelect', 'rtl'];
snakeCase = function(input) {
return input.replace(/[A-Z]/g, function($1) {
return "_" + ($1.toLowerCase());
});
};
isEmpty = function(value) {
var key;
if (angular.isArray(value)) {
return value.length === 0;
} else if (angular.isObject(value)) {
for (key in value) {
if (value.hasOwnProperty(key)) {
return false;
}
}
}
return true;
};
return {
restrict: 'A',
require: ['select', '?ngModel'],
priority: 1,
link: function(scope, element, attr, ctrls) {
var $render, chosen, directiveOptions, disableIfEmpty, empty, initIfNotInitialized, match, ngModel, ngSelect, options, startLoading, stopLoading, timer, trackBy, valuesExpr, viewWatch;
scope.disabledValuesHistory = scope.disabledValuesHistory ? scope.disabledValuesHistory : [];
element = $(element);
element.addClass('localytics-chosen');
ngSelect = ctrls[0];
ngModel = ctrls[1];
match = attr.ngOptions && attr.ngOptions.match(NG_OPTIONS_REGEXP);
valuesExpr = match && $parse(match[7]);
trackBy = match && match[8];
directiveOptions = scope.$eval(attr.chosen) || {};
options = angular.copy(config);
angular.extend(options, directiveOptions);
angular.forEach(attr, function(value, key) {
if (indexOf.call(CHOSEN_OPTION_WHITELIST, key) >= 0) {
return attr.$observe(key, function(value) {
var prefix;
prefix = String(element.attr(attr.$attr[key])).slice(0, 2);
options[snakeCase(key)] = prefix === '{{' ? value : scope.$eval(value);
return disableIfEmpty();
});
}
});
startLoading = function() {
return element.addClass('loading').attr('disabled', true).trigger('chosen:updated');
};
stopLoading = function() {
element.removeClass('loading');
if (angular.isDefined(attr.disabled)) {
element.attr('disabled', attr.disabled);
} else {
element.attr('disabled', false);
}
return element.trigger('chosen:updated');
};
chosen = null;
empty = false;
initIfNotInitialized = function() {
if (!chosen) {
return scope.$evalAsync(function() {
if (!chosen) {
return chosen = element.chosen(options).data('chosen');
}
});
}
};
disableIfEmpty = function() {
if (chosen && empty) {
element.attr('disabled', true);
}
return element.trigger('chosen:updated');
};
if (ngModel) {
$render = ngModel.$render;
ngModel.$render = function() {
var isPrimitive, nextValue, previousValue, valueChanged;
initIfNotInitialized();
try {
previousValue = ngSelect.readValue();
} catch (error) {}
$render();
try {
nextValue = ngSelect.readValue();
} catch (error) {}
isPrimitive = !trackBy && !attr.multiple;
valueChanged = isPrimitive ? previousValue !== nextValue : !angular.equals(previousValue, nextValue);
if (valueChanged) {
return element.trigger('chosen:updated');
}
};
element.on('chosen:hiding_dropdown', function() {
return scope.$applyAsync(function() {
return ngModel.$setTouched();
});
});
if (attr.multiple) {
viewWatch = function() {
return ngModel.$viewValue;
};
scope.$watch(viewWatch, ngModel.$render, true);
}
} else {
initIfNotInitialized();
}
attr.$observe('disabled', function() {
return element.trigger('chosen:updated');
});
if (attr.ngOptions && ngModel) {
timer = null;
scope.$watchCollection(valuesExpr, function(newVal, oldVal) {
return timer = $timeout(function() {
if (angular.isUndefined(newVal)) {
return startLoading();
} else {
empty = isEmpty(newVal);
stopLoading();
return disableIfEmpty();
}
});
});
return scope.$on('$destroy', function(event) {
if (timer != null) {
return $timeout.cancel(timer);
}
});
}
}
};
}
]);
}).call(this);
================================================
FILE: example/index.html
================================================
Chosen Directive Example Usage
Chosen with a promise: {{foo | json}}
Chosen with a promised hash: {{baz | json}}
Chosen with static options: {{ bar }}
Hi
This is fun
I like Chosen so much
I also like bunny rabbits
Passing options in as a hash: {{ woo }}
Disabling the element with a message when the collection is empty: {{ whatever }}
Empty?
Changing the ngModel externally: {{ myPets }}
Disabling the selection:
Disabled
Great
fun
Great fun .. indeed
Form validation
Shows error after user selects nothing (works with Angular 1.3+, uses $touched + $invalid)
#179 - inherit-select-class inside ng-if:
Ng If {{ngIfInherit}}
#59 - Don't scroll to top when selecting multiple items with ctrl
================================================
FILE: example/index.js
================================================
// Generated by CoffeeScript 1.6.2
(function() {
angular.module('chosenExampleApp', ['localytics.directives'])
.config(['chosenProvider', function (chosenProvider) {
chosenProvider.setOption({
no_results_text: 'Haha! There is no results!',
placeholder_text_multiple: 'Choose a few!'
});
}])
.controller('IndexCtrl', [
'$scope', '$q', '$timeout', function($scope, $q, $timeout) {
var simulateAjax;
simulateAjax = function(result) {
var deferred, fn;
deferred = $q.defer();
fn = function() {
return deferred.resolve(result);
};
$timeout(fn, 3000);
return deferred.promise;
};
simulateAjax(['grooo', 'wowowowow', 'lakakalakakl', 'yadayada', 'insight', 'delve', 'synergy']).then(function(result) {
return $scope.optionsFromQuery = result;
});
$scope.optionsFromQueryAsHash = (function() {
var result;
result = {
win: "Brilliant Escape",
fail: "Untimely Demise"
};
return simulateAjax(result);
})();
$scope.$watch('emptyCollection', function(empty) {
return $scope.emptyOptions = simulateAjax(empty ? [] : ['look', 'i', 'have', 'data']);
});
$scope.directiveOptions = {
no_results_text: "SO SORRY"
};
$scope.ngIfInherit = true;
$scope.myPets = ['cat'];
$scope.pets = {
cat: 'Cat',
dog: 'Dog',
hamster: 'Hamster'
};
$scope.state = ['California', 'Arizona'];
$scope.states = {
"AL": "Alabama",
"AK": "Alaska",
"AS": "American Samoa",
"AZ": "Arizona",
"AR": "Arkansas",
"CA": "California",
"CO": "Colorado",
"CT": "Connecticut",
"DE": "Delaware",
"DC": "District Of Columbia",
"FM": "Federated States Of Micronesia",
"FL": "Florida",
"GA": "Georgia",
"GU": "Guam",
"HI": "Hawaii",
"ID": "Idaho",
"IL": "Illinois",
"IN": "Indiana",
"IA": "Iowa",
"KS": "Kansas",
"KY": "Kentucky",
"LA": "Louisiana",
"ME": "Maine",
"MH": "Marshall Islands",
"MD": "Maryland",
"MA": "Massachusetts",
"MI": "Michigan",
"MN": "Minnesota",
"MS": "Mississippi",
"MO": "Missouri",
"MT": "Montana",
"NE": "Nebraska",
"NV": "Nevada",
"NH": "New Hampshire",
"NJ": "New Jersey",
"NM": "New Mexico",
"NY": "New York",
"NC": "North Carolina",
"ND": "North Dakota",
"MP": "Northern Mariana Islands",
"OH": "Ohio",
"OK": "Oklahoma",
"OR": "Oregon",
"PW": "Palau",
"PA": "Pennsylvania",
"PR": "Puerto Rico",
"RI": "Rhode Island",
"SC": "South Carolina",
"SD": "South Dakota",
"TN": "Tennessee",
"TX": "Texas",
"UT": "Utah",
"VT": "Vermont",
"VI": "Virgin Islands",
"VA": "Virginia",
"WA": "Washington",
"WV": "West Virginia",
"WI": "Wisconsin",
"WY": "Wyoming"
};
$timeout(function() {
return $scope.$apply(function() {
return $scope.myPets.push('hamster');
});
}, 1000);
return $scope.disabled = true;
}
]);
}).call(this);
================================================
FILE: gulpfile.js
================================================
var config = {
test: './test',
src: './src/',
dist: './dist/',
file: 'angular-chosen'
};
var banner = ['/**',
' * <%= pkg.name %> - <%= pkg.description %>',
' * @version v<%= pkg.version %>',
' * @link <%= pkg.homepage %>',
' * @license <%= pkg.license %>',
' */',
''].join('\n');
var args = require('yargs').argv,
gulp = require('gulp'),
karma = require('karma'),
del = require('del'),
$ = require('gulp-load-plugins')({ lazy: true }),
pkg = require('./package.json');
// List Tasks by default
gulp.task('default', $.taskListing.withFilters(null, ['build-hint']));
gulp.task('build-hint', function() {
return gulp.src(config.src + '/*.coffee')
.pipe($.if(args.debug, $.debug()))
.pipe($.plumber())
.pipe($.coffeelint('./src/coffeelint.json'))
.pipe($.coffeelint.reporter());
});
/**
* Compile CoffeeScript into ./dist/angular-chose.js
*/
gulp.task('build-coffee-script', ['build-hint'], function() {
return gulp.src(config.src + '/*.coffee')
.pipe($.if(args.debug, $.debug()))
.pipe($.plumber())
.pipe($.coffee().on('error', $.util.log))
.pipe($.rename(config.file + '.js'))
.pipe($.header(banner, { pkg : pkg }))
.pipe(gulp.dest(config.dist));
});
/**
* Minify ./dist/angular-chose.js into ./dist/angular-chose.min.js
*/
gulp.task('build-minify', ['build-coffee-script'], function() {
return gulp.src(config.dist + '/angular-chosen.js')
.pipe($.if(args.debug, $.debug()))
.pipe($.plumber())
.pipe($.uglify({mangle: true}))
.pipe($.rename(config.file + '.min.js'))
.pipe($.header(banner, {pkg : pkg}))
.pipe(gulp.dest(config.dist));
});
/**
* Run Clean Javascripts, than minify(coffee-script)
*/
gulp.task('build', ['build-clean-javascripts'], function() {
gulp.start('build-minify');
});
/**
* Clean Javascripts from .dist/
*/
gulp.task('build-clean-javascripts', function() {
return del(config.dist);
});
/**
* Watch files and compile Coffee Script in real-time
*/
gulp.task('watcher', ['tests'], function() {
gulp.watch([config.src + '*.coffee', config.dist + '*.js'], ['tests']);
});
gulp.task('test', ['build'], function (done) {
new karma.Server({
configFile: __dirname + '/test/support/karma.conf.js',
singleRun: true
}, done).start();
});
gulp.task('tests', ['build'], function (done) {
new karma.Server({
configFile: __dirname + '/test/support/karma.conf.js',
singleRun: false
}, done).start();
});
================================================
FILE: package.json
================================================
{
"name": "angular-chosen-localytics",
"filename": "chosen.js",
"main": "dist/angular-chosen.js",
"version": "1.9.2",
"description": "Angular Chosen directive is an AngularJS Directive that brings the Chosen jQuery in a Angular way",
"homepage": "http://github.com/leocaseiro/angular-chosen",
"repository": {
"type": "git",
"url": "git://github.com/leocaseiro/angular-chosen"
},
"keywords": [
"angularjs",
"select",
"multiselect",
"dropdown",
"form",
"input",
"ui"
],
"license": "MIT",
"author": "jr314159",
"contributors": [
"abuggia",
"abyx",
"charandas",
"dariusriggins",
"darlanalves",
"dougludlow",
"failpunk",
"frnan",
"gaui",
"iamnewspecies",
"ilychkov",
"kfarst",
"leocaseiro",
"lpsBetty",
"lukeMason",
"nike-17",
"odi55555",
"paulpflug",
"simison",
"slobo",
"stefanvermaas",
"vantanev",
"vstene",
"zlodes"
],
"dependencies": {
"angular": "^1.5.7",
"chosen-js": "^1.6.1",
"jquery": "^3.0.0"
},
"bugs": {
"url": "https://github.com/leocaseiro/angular-chosen/issues"
},
"devDependencies": {
"angular-mocks": "^1.4.9",
"del": "^2.2.0",
"gulp": "^3.9.1",
"gulp-coffee": "^2.3.1",
"gulp-coffeelint": "^0.6.0",
"gulp-debug": "^2.1.2",
"gulp-header": "^1.7.1",
"gulp-if": "^2.0.0",
"gulp-load-plugins": "^1.2.0",
"gulp-plumber": "^1.1.0",
"gulp-rename": "^1.2.2",
"gulp-task-listing": "^1.0.1",
"gulp-uglify": "^1.5.3",
"gulp-util": "^3.0.7",
"jasmine-core": "^2.4.1",
"karma": "^0.13.22",
"karma-coffee-preprocessor": "^0.3.0",
"karma-jasmine": "^0.3.7",
"karma-jasmine-matchers": "^2.0.2",
"karma-mocha-reporter": "^2.0.0",
"karma-phantomjs-launcher": "^1.0.0",
"phantomjs-prebuilt": "^2.1.5",
"yargs": "^4.2.0"
},
"scripts": {
"test": "gulp test"
}
}
================================================
FILE: src/.editorconfig
================================================
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = false
================================================
FILE: src/chosen.coffee
================================================
angular.module('localytics.directives', [])
chosenModule = angular.module('localytics.directives')
chosenModule.provider 'chosen', ->
options = {}
{
setOption: (newOpts) ->
angular.extend options, newOpts
return
$get: ->
options
}
chosenModule.directive 'chosen', ['chosen', '$timeout', '$parse', (config, $timeout, $parse) ->
# coffeelint: disable=max_line_length
# This is stolen from Angular...
NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/
# coffeelint: enable=max_line_length
# Whitelist of options that will be parsed from the element's attributes and passed into Chosen
#
# Can be updated by running the following script on the options page
# https://harvesthq.github.io/chosen/options.html
#
# Array.from(
# document.querySelectorAll('table:first-of-type tr td:first-of-type')
# )
# .map(node => node.textContent)
# .map(option => option.replace(/_(\w)/g, (_, letter) => letter.toUpperCase()))
#
CHOSEN_OPTION_WHITELIST = [
'allowSingleDeselect'
'disableSearch'
'disableSearchThreshold'
'enableSplitWordSearch'
'inheritSelectClasses'
'maxSelectedOptions'
'noResultsText'
'placeholderTextMultiple'
'placeholderTextSingle'
'searchContains'
'groupSearch'
'singleBackstrokeDelete'
'width'
'displayDisabledOptions'
'displaySelectedOptions'
'includeGroupLabelInSelected'
'maxShownResults'
'caseSensitiveSearch'
'hideResultsOnSelect'
'rtl'
]
snakeCase = (input) -> input.replace /[A-Z]/g, ($1) -> "_#{$1.toLowerCase()}"
isEmpty = (value) ->
if angular.isArray(value)
return value.length is 0
else if angular.isObject(value)
return false for key of value when value.hasOwnProperty(key)
true
restrict: 'A'
require: ['select', '?ngModel']
priority: 1
link: (scope, element, attr, ctrls) ->
scope.disabledValuesHistory = if scope.disabledValuesHistory then scope.disabledValuesHistory else []
element = $ element # Use real JQuery if it wasn't loaded before Angular.
element.addClass('localytics-chosen')
ngSelect = ctrls[0]
ngModel = ctrls[1]
match = attr.ngOptions && attr.ngOptions.match(NG_OPTIONS_REGEXP)
valuesExpr = match && $parse(match[7])
trackBy = match && match[8]
# Take a hash of options from the chosen directive
directiveOptions = scope.$eval(attr.chosen) or {}
# Clone options from configProvider
options = angular.copy(config)
# Merge options from directive with options from configProvider
angular.extend(options, directiveOptions)
# Options defined as attributes take precedence
angular.forEach attr, (value, key) ->
if key in CHOSEN_OPTION_WHITELIST
# Observe attributes
# Update the value in options. Set the default texts again. Update message.
attr.$observe key, (value) ->
prefix = String(element.attr(attr.$attr[key])).slice(0, 2)
options[snakeCase(key)] = if prefix is '{{' then value else scope.$eval(value)
disableIfEmpty()
startLoading = -> element.addClass('loading').attr('disabled', true).trigger('chosen:updated')
stopLoading = ->
element.removeClass('loading')
if angular.isDefined attr.disabled
element.attr 'disabled', attr.disabled
else
element.attr 'disabled', false
element.trigger('chosen:updated')
chosen = null
empty = false
# async initialize chosen if it's not initialized yet
initIfNotInitialized = ->
if !chosen
scope.$evalAsync ->
if !chosen then chosen = element.chosen(options).data('chosen')
# Use Chosen's placeholder or no results found text depending on whether there are options available
disableIfEmpty = ->
if chosen && empty
element.attr('disabled', true)
element.trigger('chosen:updated')
# Watch the underlying ngModel for updates and trigger an update when they occur.
if ngModel
$render = ngModel.$render
ngModel.$render = ->
initIfNotInitialized()
# We need to try and detect if the select value was changed from outside of chosen
try previousValue = ngSelect.readValue()
$render()
try nextValue = ngSelect.readValue()
isPrimitive = !trackBy && !attr.multiple
valueChanged = if isPrimitive
then previousValue != nextValue
else !angular.equals(previousValue, nextValue)
# If it was changed, then we trigger a chosen re-render
if (valueChanged)
element.trigger('chosen:updated')
element.on 'chosen:hiding_dropdown', ->
scope.$applyAsync -> ngModel.$setTouched()
# This is basically taken from angular ngOptions source. ngModel watches reference, not value,
# so when values are added or removed from array ngModels, $render won't be fired.
if attr.multiple
viewWatch = -> ngModel.$viewValue
scope.$watch viewWatch, ngModel.$render, true
else
initIfNotInitialized()
# Watch the disabled attribute (could be set by ngDisabled)
attr.$observe 'disabled', -> element.trigger('chosen:updated')
# Watch the collection in ngOptions and update chosen when it changes. This works with promises!
# ngOptions doesn't do anything unless there is an ngModel, so neither do we.
if attr.ngOptions and ngModel
timer = null
scope.$watchCollection valuesExpr, (newVal, oldVal) ->
# Defer execution until DOM is loaded
timer = $timeout(->
if angular.isUndefined(newVal)
startLoading()
else
empty = isEmpty(newVal)
stopLoading()
disableIfEmpty()
)
scope.$on '$destroy', (event) ->
$timeout.cancel timer if timer?
]
================================================
FILE: src/coffeelint.json
================================================
{
"coffeescript_error": {
"level": "error"
},
"arrow_spacing": {
"name": "arrow_spacing",
"level": "warn"
},
"no_tabs": {
"name": "no_tabs",
"level": "error"
},
"no_trailing_whitespace": {
"name": "no_trailing_whitespace",
"level": "warn",
"allowed_in_comments": false,
"allowed_in_empty_lines": false
},
"max_line_length": {
"name": "max_line_length",
"value": 120,
"level": "error",
"limitComments": true
},
"line_endings": {
"name": "line_endings",
"level": "ignore",
"value": "unix"
},
"no_trailing_semicolons": {
"name": "no_trailing_semicolons",
"level": "error"
},
"indentation": {
"name": "indentation",
"value": 2,
"level": "error"
},
"camel_case_classes": {
"name": "camel_case_classes",
"level": "error"
},
"colon_assignment_spacing": {
"name": "colon_assignment_spacing",
"level": "warn",
"spacing": {
"left": 0,
"right": 1
}
},
"no_implicit_braces": {
"name": "no_implicit_braces",
"level": "ignore",
"strict": true
},
"no_plusplus": {
"name": "no_plusplus",
"level": "ignore"
},
"no_throwing_strings": {
"name": "no_throwing_strings",
"level": "error"
},
"no_backticks": {
"name": "no_backticks",
"level": "error"
},
"no_implicit_parens": {
"name": "no_implicit_parens",
"level": "ignore"
},
"no_empty_param_list": {
"name": "no_empty_param_list",
"level": "warn"
},
"no_stand_alone_at": {
"name": "no_stand_alone_at",
"level": "ignore"
},
"space_operators": {
"name": "space_operators",
"level": "warn"
},
"duplicate_key": {
"name": "duplicate_key",
"level": "error"
},
"empty_constructor_needs_parens": {
"name": "empty_constructor_needs_parens",
"level": "ignore"
},
"cyclomatic_complexity": {
"name": "cyclomatic_complexity",
"value": 10,
"level": "ignore"
},
"newlines_after_classes": {
"name": "newlines_after_classes",
"value": 3,
"level": "ignore"
},
"no_unnecessary_fat_arrows": {
"name": "no_unnecessary_fat_arrows",
"level": "warn"
},
"missing_fat_arrows": {
"name": "missing_fat_arrows",
"level": "ignore"
},
"non_empty_constructor_needs_parens": {
"name": "non_empty_constructor_needs_parens",
"level": "ignore"
}
}
================================================
FILE: test/base.spec.js
================================================
describe('base functionality', function() {
var element;
it('should work without ngModel', function() {
element = $compile('One ')($scope);
$scope.$apply();
var chosenSelected = element.next().find('.chosen-single span');
expect(chosenSelected.length).toBe(1)
})
beforeEach(function() {
$scope.currentLanguage = 'german';
$scope.languages = ['german', 'english'];
// Compile a piece of HTML containing the directive
element = $compile(' ')($scope);
$scope.$apply();
$timeout.flush();
element.trigger('chosen:open.chosen'); // fills dropdown (triggers chosen:showing_dropdown when finished)
element.trigger('chosen:close.chosen');
});
it('should add chosen dropdown', function() {
var chosenContainer = element.next();
var chosenSelected = chosenContainer.find('.chosen-single span');
var chosenList = chosenContainer.find('.chosen-drop ul li');
expect(chosenSelected.text()).toBe($scope.currentLanguage);
expect(chosenList.length).toBe(2);
expect(chosenList.first().text()).toBe($scope.languages[0]);
expect(chosenList.last().text()).toBe($scope.languages[1]);
});
it('should work when current model updates', function() {
var chosenContainer = element.next();
var chosenSelected = chosenContainer.find('.chosen-single span');
expect(chosenSelected.text()).toBe('german');
$scope.currentLanguage = 'english';
$scope.$apply();
expect(chosenSelected.text()).toBe('english');
});
it('should trigger ngChange function when selecting a chosen result', function() {
$scope.changed = false;
// Compile a piece of HTML containing the directive
element = $compile(' ')($scope);
$scope.$apply();
$timeout.flush();
element.trigger('chosen:open.chosen');
var chosenContainer = element.next();
var chosenSelected = chosenContainer.find('.chosen-single span');
var chosenList = chosenContainer.find('.chosen-drop ul li');
expect(chosenSelected.text()).toBe('german');
chosenList.last().trigger('mouseup'); // select english = last item
expect(chosenSelected.text()).toBe('english');
expect($scope.changed).toBe(true);
});
it('should disable chosen with ngDisabled', function() {
$scope.disabled = true;
// Compile a piece of HTML containing the directive
element = $compile(' ')($scope);
$scope.$apply();
$timeout.flush();
var chosenContainer = element.next();
expect(chosenContainer.hasClass('chosen-disabled')).toBe(true);
$scope.disabled = false;
$scope.$apply();
expect(chosenContainer.hasClass('chosen-disabled')).toBe(false);
});
});
================================================
FILE: test/chosenAttrSpec.js
================================================
describe('chosen attributes', function() {
var element;
it('should inherit class customclass from select', function() {
var customClass = 'customclass';
var chosenContainer;
var select = function(inheritSelectClasses, customClass) {
return ' ';
};
//inherit-select-classes = true
element = $compile(select(true, customClass))($scope);
$scope.$apply();
chosenContainer = element.next();
expect(chosenContainer.hasClass(customClass)).toBe(true);
//inherit-select-classes = false
element = $compile(select(false, customClass))($scope);
$scope.$apply();
chosenContainer = element.next();
expect(chosenContainer.hasClass(customClass)).toBe(false);
});
});
================================================
FILE: test/chosenOptions/allowSingleDeselected.spec.js
================================================
describe('Chosen options: allow-single-deselect', function () {
beforeEach(function () {
chosenSelectHelper.compileWithAttributes({
allowSingleDeselect: true
});
});
it('removes the model value when deselected', function () {
var removeButton = chosenSelectHelper.chosenContainer().find('abbr.search-choice-close');
removeButton.trigger('mouseup.chosen');
expect($scope.currentLanguage).toBeNull();
});
});
================================================
FILE: test/chosenOptions/disableSearch.spec.js
================================================
describe('Chosen options: disable-search', function () {
beforeEach(function () {
chosenSelectHelper.compileWithAttributes({
disableSearch: true
});
});
it('hides the search input', function () {
expect(chosenSelectHelper.searchInput().attr('readOnly')).toBeTruthy();
});
});
================================================
FILE: test/chosenOptions/disableSearchThreshold.spec.js
================================================
describe('Chosen options: disable-search-threshold', function () {
it('disables the search when the results are lower than the threshold', function () {
chosenSelectHelper.compileWithAttributes({
disableSearchThreshold: 3
});
expect(chosenSelectHelper.searchInput().attr('readOnly')).toBeTruthy();
});
it('does not disable the search when the results are higher than the threshold', function () {
chosenSelectHelper.compileWithAttributes({
disableSearchThreshold: 1
});
expect(chosenSelectHelper.searchInput().attr('readOnly')).toBeFalsy();
});
});
================================================
FILE: test/chosenOptions/inheritSelectClasses.spec.js
================================================
describe('Chosen options: inherit-select-classes', function () {
it('passes classes from the select tag to the chosen container', function () {
chosenSelectHelper.compileWithAttributes({
inheritSelectClasses: true,
'class': 'inherited-class'
});
expect(chosenSelectHelper.chosenContainer().hasClass('inherited-class')).toBeTruthy();
});
it('dont pass classes from the select tag to the chosen container', function () {
chosenSelectHelper.compileWithAttributes({
inheritSelectClasses: false,
'class': 'inherited-class'
});
expect(chosenSelectHelper.chosenContainer().hasClass('inherited-class')).toBeFalsy();
});
});
================================================
FILE: test/chosenOptions/maxShownResults.spec.js
================================================
describe('Chosen options: max-shown-results', function () {
beforeEach(function () {
chosenSelectHelper.compileWithAttributes({
maxShownResults: 1
});
chosenSelectHelper.selectTag.trigger('chosen:open.chosen');
chosenSelectHelper.chosenResults().last().trigger('mouseup.chosen');
});
it('displays only the maximum number of results specified', function () {
expect(chosenSelectHelper.chosenResults().length).toBe(1);
});
});
================================================
FILE: test/chosenOptions/noResultsText.spec.js
================================================
describe('Chosen options: no-results-text', function () {
beforeEach(function () {
chosenSelectHelper.compileWithAttributes({
noResultsText: "'testing no results text'"
});
chosenSelectHelper.searchInput().val('foo');
chosenSelectHelper.searchInput().trigger('keyup.chosen');
});
it('displays the specified message when a search returns no results', function () {
expect(chosenSelectHelper.chosenResults().html()).toMatch('testing no results text');
});
});
================================================
FILE: test/chosenOptions/placeholderTextMultiple.spec.js
================================================
describe('Chosen options: placeholder-text-multiple', function () {
beforeEach(function () {
$scope.emptyModel = [];
chosenSelectHelper.compileWithAttributes({
multiple: true,
placeholderTextMultiple: "'testing multi placeholder text'",
ngModel: 'emptyModel'
});
});
it('shows the specified placeholder text for a multi-select chosen tag', function () {
expect(chosenSelectHelper.chosenContainer().find('.search-field input').attr('value')).toMatch('testing multi placeholder text');
});
});
================================================
FILE: test/chosenOptions/placeholderTextSingle.spec.js
================================================
describe('Chosen options: placeholder-text-single', function () {
beforeEach(function () {
chosenSelectHelper.compileWithAttributes({
placeholderTextSingle: "'testing single placeholder text'",
ngModel: 'emptyModel'
});
});
it('shows the specified placeholder text for a single-select chosen tag', function () {
expect(chosenSelectHelper.chosenContainer().find('.chosen-default span').text()).toMatch('testing single placeholder text');
});
});
================================================
FILE: test/chosenOptions/searchContains.spec.js
================================================
describe('Chosen options: search-contains', function () {
beforeEach(function () {
chosenSelectHelper.compileWithAttributes({
searchContains: true
});
chosenSelectHelper.searchInput().val('erma');
chosenSelectHelper.searchInput().trigger('keyup.chosen');
});
it('finds a match in the search result that is not at the beginning of the option text', function () {
expect(chosenSelectHelper.chosenResults().length).toBeTruthy();
});
});
================================================
FILE: test/chosenOptions/width.spec.js
================================================
describe('Chosen options: width', function () {
beforeEach(function () {
chosenSelectHelper.compileWithAttributes({
width: 250
});
});
it('sets the dropdown width', function () {
expect(chosenSelectHelper.chosenContainer().attr('style')).toContain('width: 250');
});
});
================================================
FILE: test/form.spec.js
================================================
describe('form validations', function() {
var element, ngModel;
beforeEach(function() {
$scope.currentLanguage = null;
$scope.languages = ['german', 'english'];
// Compile a piece of HTML containing the directive
var form = $compile('')($scope);
element = form.find('select');
ngModel = $scope.form.language;
$scope.$apply();
});
it('should work with required form validation', function() {
expect(ngModel.$valid).toBe(false);
expect(ngModel.$error.required).toBe(true);
$scope.currentLanguage = 'german';
$scope.$apply();
expect(ngModel.$valid).toBe(true);
});
it('should set $touched of ngModel to true (for e.g. form validation)', function() {
expect(ngModel.$touched).toBe(false);
element.trigger('chosen:open.chosen');
$scope.$apply();
element.trigger('chosen:hiding_dropdown');
$scope.$apply();
expect(ngModel.$touched).toBe(true);
});
});
================================================
FILE: test/issues/179-ng-if-breaks-inherit-select-classes.spec.js
================================================
describe('#179 inherit-select-classes inside ng-if', function () {
it('passes classes from the select tag to the chosen container inside ng-if', function () {
var element;
var customClass = 'customclass';
var chosenContainer;
$scope.test = true;
var select = function(inheritSelectClasses, customClass) {
return '{{test}}
';
};
//inherit-select-classes = true
element = $compile(select(true, customClass))($scope);
$scope.$apply();
chosenContainer = element.find('select').next();
expect(chosenContainer.hasClass(customClass)).toBe(true);
});
});
================================================
FILE: test/support/caseConvertFilter.js
================================================
angular.module('caseConvertFilter', [])
.filter('caseConvert', function () {
var SNAKE_CASE_REGEXP = /[A-Z]/g;
return function (name, separator) {
var separator = separator || '_';
return name.replace(SNAKE_CASE_REGEXP, function (letter, pos) {
return (pos ? separator : '') + letter.toLowerCase();
});
};
});
================================================
FILE: test/support/chosenSelectHelper.js
================================================
angular.module('chosenSelectHelper', [])
.service('chosenSelectHelper', ['$filter', function ($filter) {
return {
compileWithAttributes: function (attributes) {
var attributes = attributes || {};
if (!attributes['ngModel']) {
$scope.currentLanguage = 'german';
attributes['ngModel'] = 'currentLanguage';
}
if (!attributes['ngOptions']) {
$scope.languages = ['german', 'english'];
attributes['ngOptions'] = 'lang for lang in languages';
}
for (attrName in attributes) {
this.selectTag.attr($filter('caseConvert')(attrName, '-'), attributes[attrName]);
}
$compile(this.selectTag)($scope);
$scope.$apply();
$timeout.flush();
},
selectTag: angular.element(' '),
chosenContainer: function () {
return this.selectTag.next();
},
chosenResults: function () {
return this.chosenContainer().find('.chosen-results li');
},
searchInput: function () {
return this.chosenContainer().find('.chosen-search input');
}
};
}]);
================================================
FILE: test/support/karma.conf.js
================================================
'use strict';
module.exports = function(config) {
config.set({
basePath: '../../',
frameworks: [
'jasmine',
'jasmine-matchers'
],
files: [
'node_modules/jquery/dist/jquery.js',
'node_modules/chosen-js/chosen.jquery.js',
'node_modules/angular/angular.js',
'node_modules/angular-mocks/angular-mocks.js',
'src/**/*.coffee',
'test/support/*.js',
'test/**/*.spec.js'
],
preprocessors: {
'src/**/*.coffee': ['coffee']
},
reporters: ['mocha'],
// Start these browsers, currently available:
// - Chrome
// - ChromeCanary
// - Firefox
// - Opera
// - Safari (only Mac)
// - PhantomJS
// - IE (only Windows)
browsers: [
// 'PhantomJS', 'Chrome', 'Firefox', 'Safari'
'PhantomJS'
],
// Which plugins to enable
plugins: [
'karma-mocha-reporter',
'karma-phantomjs-launcher',
// 'karma-chrome-launcher',
// 'karma-firefox-launcher',
// 'karma-safari-launcher',
'karma-jasmine',
'karma-jasmine-matchers',
'karma-coffee-preprocessor'
],
singleRun: true
});
};
================================================
FILE: test/support/specHelper.js
================================================
beforeEach(function() {
angular.module('testApp', [
'localytics.directives',
'caseConvertFilter',
'chosenSelectHelper'
]);
angular.mock.module('testApp');
});
var $scope;
var $timeout;
var $compile;
var chosenSelectHelper;
/**
* Assigns $scope, $timeout and $compile, these will be used in every test.
*/
beforeEach(inject(function(_$rootScope_, _$timeout_, _$compile_, _chosenSelectHelper_) {
$scope = _$rootScope_.$new();
$timeout = _$timeout_;
$compile = _$compile_;
chosenSelectHelper = _chosenSelectHelper_;
}));