diff --git a/CHANGELOG.md b/CHANGELOG.md index c6ab167..f7c67dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ CHANGELOG ========= +## 0.5.2 (2017-04-19) + +- Update `bootstrap-switch` to `~3.3.4` +- Fix `jquery` import in tests +- Fix `npm` dependencies +- Fix `README` headings + ## 0.5.1 (2016-06-04) - Make `switch-change` trigger when model changes @@ -19,7 +26,7 @@ CHANGELOG **BREAKING CHANGES:** - Applications relying on `undefined` as the only indeterminate state - may break if they consider `null` a falsy value. `null` is now an + may break if they consider `null` a falsy value. `null` is now an indeterminate value. ## 0.4.1 (2015-06-15) diff --git a/README.md b/README.md index a10f7d7..1108f55 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ angular-bootstrap-switch AngularJS directive for the [bootstrap-switch](https://github.com/nostalgiaz/bootstrap-switch) jQuery plugin. -##Usage +## Usage -###Installation +### Installation ```shell $ bower install angular-bootstrap-switch ``` @@ -23,7 +23,7 @@ $ npm install angular-bootstrap-switch This will install AngularJS, jQuery, and the original bootstrap-switch. -###Registration +### Registration To be able to use the directive, you need to register the `angular-bootstrap-switch` module as a dependency: @@ -33,7 +33,7 @@ angular.module('yourModule', ['frapontillo.bootstrap-switch' ]); ``` -###Directive +### Directive The directive can work on both element and attribute levels. The following example contains all of the supported attributes: ```html @@ -63,9 +63,9 @@ The directive can work on both element and attribute levels. The following examp Short doc for all of the attributes: * `ng-model`, the value to bind the switch to -* `type`, has to be one of `checkbox` and `radio`. +* `type`, has to be one of `checkbox` and `radio`. This value is mandatory and must be a string, as it cannot be changed once set (see [this answer on StackOverflow](http://stackoverflow.com/a/15155407/801065)). -If you choose `radio`, be sure to follow the [AngularJS radio specs](https://docs.angularjs.org/api/ng/input/input%5Bradio%5D), +If you choose `radio`, be sure to follow the [AngularJS radio specs](https://docs.angularjs.org/api/ng/input/input%5Bradio%5D), meaning you have to specify the same `ngModel` and a different `value` or `ng-value` attribute for each radio * `switch-active`, determines if the switch is enabled or not (changes the inner input's `disabled` attribute) * `switch-readonly`, determines if the switch is read-only or not (changes the inner input's `readonly` attribute) @@ -84,21 +84,21 @@ meaning you have to specify the same `ngModel` and a different `value` or `ng-va * `switch-inverse`, inverts the on/off handles * `switch-change`, evaluates an expression whenever the model value changes. Instead, `ng-change` will fire when view value changes (e.g from a click) -###Migrating from bootstrap-switch~2 +### Migrating from bootstrap-switch~2 Read the [CHANGELOG](CHANGELOG.md#030-alpha1-2014-02-22) information to learn what's different in `0.3.0`. -###Examples +### Examples The `example` folder shows a simple working demo of the switch. -###Compatibility +### Compatibility IE8 requires you to attach the directive to an `` or ``. Due to some incompatibilities it is not possible to use a custom tag or `div` instead. -##Development +## Development -###Test and build +### Test and build To build the directive yourself you need to have NodeJS. Then do the following: @@ -110,18 +110,18 @@ $ grunt test-travis $ grunt build ``` -###Contribute +### Contribute To contribute, please follow the generic [AngularJS Contributing Guidelines](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md), with the only exception to send the PR to the `develop` branch instead of `master`. -##Author +## Author Francesco Pontillo () -##License +## License ``` - Copyright 2014-2015-2016 Francesco Pontillo + Copyright 2014-2017 Francesco Pontillo Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/bower.json b/bower.json index 356c9eb..204ae4f 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "angular-bootstrap-switch", - "version": "0.5.1", + "version": "0.5.2", "author": { "name": "Francesco Pontillo", "email": "francescopontillo@gmail.com", @@ -22,7 +22,7 @@ "angular": ">=1.4.0", "jquery": ">=1.9.0", "bootstrap": ">=2.3.2", - "bootstrap-switch": "3.3.2" + "bootstrap-switch": "~3.3.4" }, "devDependencies": { "angular-mocks": ">=1.4.0", diff --git a/dist/angular-bootstrap-switch.js b/dist/angular-bootstrap-switch.js index 1533ff9..70756d8 100644 --- a/dist/angular-bootstrap-switch.js +++ b/dist/angular-bootstrap-switch.js @@ -1,6 +1,6 @@ /** * angular-bootstrap-switch - * @version v0.5.1 - 2016-06-04 + * @version v0.5.2 - 2017-04-19 * @author Francesco Pontillo (francescopontillo@gmail.com) * @link https://github.com/frapontillo/angular-bootstrap-switch * @license Apache License 2.0(http://www.apache.org/licenses/LICENSE-2.0.html) diff --git a/dist/angular-bootstrap-switch.min.js b/dist/angular-bootstrap-switch.min.js index 6d639b8..e1a26ea 100644 --- a/dist/angular-bootstrap-switch.min.js +++ b/dist/angular-bootstrap-switch.min.js @@ -1,6 +1,6 @@ /** * angular-bootstrap-switch - * @version v0.5.1 - 2016-06-04 + * @version v0.5.2 - 2017-04-19 * @author Francesco Pontillo (francescopontillo@gmail.com) * @link https://github.com/frapontillo/angular-bootstrap-switch * @license Apache License 2.0(http://www.apache.org/licenses/LICENSE-2.0.html) diff --git a/karma-chrome.conf.js b/karma-chrome.conf.js index f438fec..48cbb21 100644 --- a/karma-chrome.conf.js +++ b/karma-chrome.conf.js @@ -15,7 +15,7 @@ module.exports = function(config) { // list of files / patterns to load in the browser files: [ - 'bower_components/jquery/dist/jquery.js', + 'bower_components/jquery/jquery.js', 'bower_components/angular/angular.js', 'bower_components/angular-mocks/angular-mocks.js', 'bower_components/bootstrap-switch/dist/js/bootstrap-switch.js', diff --git a/karma.conf.js b/karma.conf.js index d4d4789..04746db 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -15,7 +15,7 @@ module.exports = function(config) { // list of files / patterns to load in the browser files: [ - 'bower_components/jquery/dist/jquery.js', + 'bower_components/jquery/jquery.js', 'bower_components/angular/angular.js', 'bower_components/angular-mocks/angular-mocks.js', 'bower_components/bootstrap-switch/dist/js/bootstrap-switch.js', diff --git a/package.json b/package.json index 217ea47..86b032b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angular-bootstrap-switch", - "version": "0.5.1", + "version": "0.5.2", "main": "dist/angular-bootstrap-switch.js", "author": { "name": "Francesco Pontillo", @@ -13,7 +13,12 @@ "url": "git@github.com:frapontillo/angular-bootstrap-switch.git" }, "license": "Apache-2.0", - "dependencies": {}, + "dependencies": { + "angular": ">=1.4.0", + "jquery": ">=1.9.0", + "bootstrap": ">=2.3.2", + "bootstrap-switch": "3.3.2" + }, "devDependencies": { "grunt": "~0.4.5", "grunt-contrib-clean": "~0.7.0", diff --git a/src/directives/bsSwitch.js b/src/directives/bsSwitch.js index 7e1f7f8..210e2d0 100644 --- a/src/directives/bsSwitch.js +++ b/src/directives/bsSwitch.js @@ -150,6 +150,12 @@ angular.module('frapontillo.bootstrap-switch') controller.$setViewValue(controller.$modelValue); } else { controller.$setViewValue(viewValue); + controller.$formatters[0] = function(value) { + if (value === undefined || value === null) { + return value; + } + return angular.equals(value, getTrueValue()); + }; } } }; @@ -176,6 +182,12 @@ angular.module('frapontillo.bootstrap-switch') // When the model changes controller.$render = function () { initMaybe(); + + // WORKAROUND for https://github.com/Bttstrp/bootstrap-switch/issues/540 + // to update model value when bootstrapSwitch is disabled we should + // re-enable it and only then update 'state' + element.bootstrapSwitch('disabled', ''); + var newValue = controller.$modelValue; if (newValue !== undefined && newValue !== null) { element.bootstrapSwitch('state', newValue === getTrueValue(), true); @@ -183,6 +195,10 @@ angular.module('frapontillo.bootstrap-switch') element.bootstrapSwitch('indeterminate', true, true); controller.$setViewValue(undefined); } + + // return initial value for "disabled" + setActive(); + switchChange(); }; diff --git a/test/spec/directives/bsSwitchSpec.js b/test/spec/directives/bsSwitchSpec.js index 41bdcdb..07cd1f0 100644 --- a/test/spec/directives/bsSwitchSpec.js +++ b/test/spec/directives/bsSwitchSpec.js @@ -391,6 +391,34 @@ describe('Directive: bsSwitch', function () { it('should change the model, then deactivate the switch', inject(makeTestChangeModelThenDeactivate())); it('should change the model, deactivate the switch (input)', inject(makeTestChangeModelThenDeactivate(true))); + // Test a model change when switch is deactivated + function makeTestChangeModelWhenSwitchIsDeactivated() { + return function () { + var element = compileDirective('active'); + scope.model = false; + scope.isActive = false; + scope.$apply(); + $timeout.flush(); + // test the active state, should be false + expect(element.hasClass(CONST.SWITCH_DISABLED_CLASS)).toBeTruthy(); + expect(element.find(CONST.INPUT_SELECTOR).attr('disabled')).toBeTruthy(); + // test the model, should be false + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeTruthy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeFalsy(); + + scope.model = true; + scope.$apply(); + + // test the active state, should be false + expect(element.hasClass(CONST.SWITCH_DISABLED_CLASS)).toBeTruthy(); + expect(element.find(CONST.INPUT_SELECTOR).attr('disabled')).toBeTruthy(); + // test the model, should be true + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeFalsy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeTruthy(); + }; + } + it('should deactivate the switch, then change the model', inject(makeTestChangeModelWhenSwitchIsDeactivated())); + // Test the activation function makeTestActivate(input) { return function () { @@ -723,4 +751,140 @@ describe('Directive: bsSwitch', function () { } it('should evaluate change expression when model changes', inject(makeTestModelSwitchChange())); it('should evaluate change expression when model changes', inject(makeTestModelSwitchChange(true))); + + // Test the null model from true state + function makeTestToIndeterminateNullFromTrue(input) { + return function () { + var element = compileDirective(undefined, input); + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeFalsy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeTruthy(); + scope.model = true; + scope.$apply(); + expect(element.hasClass(CONST.SWITCH_INDETERMINATE_CLASS)).toBeFalsy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeTruthy(); + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeFalsy(); + scope.model = null; + scope.$apply(); + expect(element.hasClass(CONST.SWITCH_INDETERMINATE_CLASS)).toBeTruthy(); + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeFalsy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeTruthy(); + }; + } + it('should change from true to the indeterminate state when the model is null', inject(makeTestToIndeterminateNullFromTrue())); + it('should change from true to the indeterminate state when the model is null (input)', inject(makeTestToIndeterminateNullFromTrue(true))); + + // Test the null model from false state + function makeTestToIndeterminateNullFromFalse(input) { + return function () { + var element = compileDirective(undefined, input); + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeFalsy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeTruthy(); + scope.model = false; + scope.$apply(); + expect(element.hasClass(CONST.SWITCH_INDETERMINATE_CLASS)).toBeFalsy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeFalsy(); + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeTruthy(); + scope.model = null; + scope.$apply(); + expect(element.hasClass(CONST.SWITCH_INDETERMINATE_CLASS)).toBeTruthy(); + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeTruthy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeFalsy(); + }; + } + it('should change from false to the indeterminate state when the model is null', inject(makeTestToIndeterminateNullFromFalse())); + it('should change from false to the indeterminate state when the model is null (input)', inject(makeTestToIndeterminateNullFromFalse(true))); + + // Test the undefined model from true state + function makeTestToIndeterminateUndefinedFromTrue(input) { + return function () { + var element = compileDirective(undefined, input); + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeFalsy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeTruthy(); + scope.model = true; + scope.$apply(); + expect(element.hasClass(CONST.SWITCH_INDETERMINATE_CLASS)).toBeFalsy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeTruthy(); + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeFalsy(); + scope.model = undefined; + scope.$apply(); + expect(element.hasClass(CONST.SWITCH_INDETERMINATE_CLASS)).toBeTruthy(); + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeFalsy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeTruthy(); + }; + } + it('should change from true to the indeterminate state when the model is null', inject(makeTestToIndeterminateUndefinedFromTrue())); + it('should change from true to the indeterminate state when the model is null (input)', inject(makeTestToIndeterminateUndefinedFromTrue(true))); + + // Test the undefined model from false state + function makeTestToIndeterminateUndefinedFromFalse(input) { + return function () { + var element = compileDirective(undefined, input); + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeFalsy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeTruthy(); + scope.model = false; + scope.$apply(); + expect(element.hasClass(CONST.SWITCH_INDETERMINATE_CLASS)).toBeFalsy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeFalsy(); + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeTruthy(); + scope.model = undefined; + scope.$apply(); + expect(element.hasClass(CONST.SWITCH_INDETERMINATE_CLASS)).toBeTruthy(); + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeTruthy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeFalsy(); + }; + } + it('should change from false to the indeterminate state when the model is null', inject(makeTestToIndeterminateUndefinedFromFalse())); + it('should change from false to the indeterminate state when the model is null (input)', inject(makeTestToIndeterminateUndefinedFromFalse(true))); + + // Test the changing multiple state + function makeTestMultipleChangeOfStateIndeterminate(input) { + return function () { + var element = compileDirective(undefined, input); + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeFalsy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeTruthy(); + scope.model = false; + scope.$apply(); + expect(element.hasClass(CONST.SWITCH_INDETERMINATE_CLASS)).toBeFalsy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeFalsy(); + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeTruthy(); + scope.model = undefined; + scope.$apply(); + expect(element.hasClass(CONST.SWITCH_INDETERMINATE_CLASS)).toBeTruthy(); + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeTruthy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeFalsy(); + scope.model = true; + scope.$apply(); + expect(element.hasClass(CONST.SWITCH_INDETERMINATE_CLASS)).toBeFalsy(); + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeFalsy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeTruthy(); + }; + } + it('should change from false to the indeterminate state and to true', inject(makeTestMultipleChangeOfStateIndeterminate())); + it('should change from false to the indeterminate state and to true (input)', inject(makeTestMultipleChangeOfStateIndeterminate(true))); + + // Test the changing multiple state other way round + function makeTestMultipleChangeOfStateIndeterminateReverse(input) { + return function () { + var element = compileDirective(undefined, input); + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeFalsy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeTruthy(); + scope.model = true; + scope.$apply(); + expect(element.hasClass(CONST.SWITCH_INDETERMINATE_CLASS)).toBeFalsy(); + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeFalsy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeTruthy(); + scope.model = undefined; + scope.$apply(); + expect(element.hasClass(CONST.SWITCH_INDETERMINATE_CLASS)).toBeTruthy(); + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeFalsy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeTruthy(); + scope.model = false; + scope.$apply(); + expect(element.hasClass(CONST.SWITCH_INDETERMINATE_CLASS)).toBeFalsy(); + expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeFalsy(); + expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeTruthy(); + }; + } + it('should change from false to the indeterminate state and to false', inject(makeTestMultipleChangeOfStateIndeterminateReverse())); + it('should change from false to the indeterminate state and to false (input)', inject(makeTestMultipleChangeOfStateIndeterminateReverse(true))); });