Repository: frapontillo/angular-bootstrap-switch Branch: develop Commit: e9616e34524a Files: 28 Total size: 95.6 KB Directory structure: gitextract_zpg3whn3/ ├── .bowerrc ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE ├── README.md ├── bower.json ├── bsSwitch.prefix ├── bsSwitch.suffix ├── common/ │ └── module.js ├── dist/ │ └── angular-bootstrap-switch.js ├── example/ │ ├── index.html │ ├── scripts/ │ │ ├── app.js │ │ └── controllers/ │ │ └── main.js │ └── styles/ │ └── main.css ├── karma-chrome.conf.js ├── karma.conf.js ├── package.json ├── src/ │ └── directives/ │ └── bsSwitch.js └── test/ ├── .jshintrc ├── runner.html └── spec/ └── directives/ └── bsSwitchSpec.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .bowerrc ================================================ { "directory": "bower_components" } ================================================ FILE: .editorconfig ================================================ # EditorConfig helps developers define and maintain consistent # coding styles between different editors and IDEs # editorconfig.org root = true [*] # Change these settings to your own preference indent_style = space indent_size = 2 # We recommend you to keep these unchanged end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.md] trim_trailing_whitespace = false ================================================ FILE: .gitattributes ================================================ * text=auto ================================================ FILE: .gitignore ================================================ node_modules .tmp .sass-cache bower_components .idea/ ================================================ FILE: .jshintrc ================================================ { "node": true, "browser": true, "esnext": true, "bitwise": true, "camelcase": true, "curly": true, "eqeqeq": true, "immed": true, "indent": 2, "latedef": true, "newcap": true, "noarg": true, "quotmark": "single", "regexp": true, "undef": true, "unused": true, "strict": true, "trailing": true, "smarttabs": true, "globals": { "angular": false, "$": false, "jQuery": false } } ================================================ FILE: .npmignore ================================================ .git/ .idea/ .tmp/ bower_components/ common/ src/ node_modules/ .editorconfig .gitattributes .gitignore .jshintrc .travis.yml ================================================ FILE: .travis.yml ================================================ language: node_js node_js: - "4.1" env: - CXX=g++-4.8 addons: apt: sources: - ubuntu-toolchain-r-test packages: - g++-4.8 before_script: - 'npm install -g bower grunt-cli' - 'npm install' - 'bower install' script: - grunt ================================================ FILE: CHANGELOG.md ================================================ 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 - Use `ng-change` for triggers only on view changes - Test `ng-change` and `switch-change` behaviors ## 0.5.0 (2016-03-10) - Use `ngAnnotate` instead of `ngMin` - Update dev dependencies - Add `switch-change` attribute - Improve digest cycle - Use `null` for indeterminate state - Update to Angular 1.5.0 **BREAKING CHANGES:** - Applications relying on `undefined` as the only indeterminate state may break if they consider `null` a falsy value. `null` is now an indeterminate value. ## 0.4.1 (2015-06-15) - Update to `angular` 1.4.0 - Add test support for IE - Enable indeterminate state - Enable generic true value (not just strings) ## 0.4.0 (2015-04-13) - Alpha to stable with no changes ## 0.4.0-alpha.2 (2015-04-01) - Add new parameters - `switch-inverse` - `switch-readonly` - Fix for radio switches - Handle models using getterSetter option - `'use strict'` to module-level only - Update to `angular` 1.3.15 ## 0.4.0-alpha.1 (2014-11-21) - Update to `bootstrap-switch` 3.2.2 - Update to `angular` 1.3.3 - Add new parameters - `switch-label-width` - `switch-handle-width` - Multiple bug fixes - Code optimization improvements ## 0.3.0 (2014-06-27) - Update to `bootstrap-switch` 3.0.2 - Update to `angular` 1.2.18 - Support for `jquery` > 1.9.0 - Promotion to stable ## 0.3.0-alpha.2 (2014-03-30) - Update to `bootstrap-switch` 3.0.0 stable - Update to `angular` 1.2.15 - **bsSwitch**: add `switch-wrapper` property ## 0.3.0-alpha.1 (2014-02-22) ### Breaking changes This is an alpha release based on the `HEAD` of the `bootstrap-switch` `3.0` branch. Therefore, specifications have slightly changed in order to reflect the original API. Use in production environment is discouraged, since the API may change unexpectedly. - Handle text: - Use `switch-on-text` instead of `switch-on-label` - Use `switch-off-text` instead of `switch-off-label` - Handle color: - Use `switch-on-color` instead of `switch-on` - Use `switch-off-color` instead of `switch-off` - When setting `switch-icon`, `bootstrap-switch~2` used to inject an `` tag with a predefined `icon` class, while it now injects a `` tag without any additional classes other than the ones you specify. ### Other changes - Update to `angular` 1.2.13 - Update to `bootstrap-switch#3.0.0` - Update to `jquery` 2.1.0 ## 0.2.1 (2013-12-31) - Update to `angular` 1.2.6 - Update to `bootstrap-switch` 2.0.0 - **bsSwitch**: fix for `type` enforcing - **bsSwitchSpec**: fix tests - Add `CHANGELOG.md` ## 0.2.0 (2013-12-16) - Improve build process - **bsSwitchSpec**: fix stop test-travis - **bsSwitch**: fix `$apply already in progress`, default active state - Update to `angular` 1.2.5 - Update to `bootstrap-switch` 1.9.0 - Add example page - Add contribution guidelines - **bsSwitchSpec**: Add test file (24 tests) - **bsSwitch**: fix class size (thanks to [@bardusco](https://github.com/bardusco)) ## 2.0.0 (2013-09-24) - Update to `angular` 1.2.0-rc.1 - Update to `develop` branch of `bootstrap-switch` - **bsSwitch**: handle undefined `ngModel` ## 0.1.0 (2013-08-12) - First release ================================================ FILE: CONTRIBUTING.md ================================================ Contributing ============ ## Report an Issue If you have found an issue with `angular-bootstrap-switch` and want to report it, **please make a live demo** first so that the misbehaviour can be reproduced. If you don't know how to do it, simply fork and edit **[this plnkr template](http://plnkr.co/edit/SWy8YmrVi8IsTa4FuqSZ)**. Issues with no live demo can get automatically closed. Also, make sure to: - look for **similar issues** in the repository bug tracker - specify the `angular-bootstrap-switch` **version** showing the issue - check if the issue was already fixed in an `alpha`/`beta` release or in the latest commit of the `develop` branch (commits on the `develop` branch don't generate a single file in the `build` directory, you need to check against files in the `src` directory) - clearly describe how the plugin should be changed to address your request ## Submit a Pull Request If you want to submit a Pull Request, please follow the same rules as in [Report an Issue](#report-issue), plus all the **[submission guidelines](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#submitting-a-pull-request)**, **[coding rules](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#rules)** and **[commit message rules](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#commit)** that apply to the main angular.js project. **IMPORTANT**: Before submitting your PR, write new tests for it (where applicable) and test everything by running: ```shell $ grunt test-travis ``` Previously existing tests *should* never break. ================================================ FILE: Gruntfile.js ================================================ 'use strict'; module.exports = function (grunt) { // load all grunt tasks require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); // configurable paths var yeomanConfig = { src: 'src', dist: 'dist', test: 'test', temp: '.temp' }; try { yeomanConfig.src = require('./bower.json').appPath || yeomanConfig.src; } catch (e) {} grunt.initConfig({ yeoman: yeomanConfig, pkg: grunt.file.readJSON('bower.json'), meta: { banner: '/**\n' + ' * <%= pkg.name %>\n' + ' * @version v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>\n' + ' * @author <%= pkg.author.name %> (<%= pkg.author.email %>)\n' + ' * @link <%= pkg.homepage %>\n' + ' * @license <%= _.map(pkg.licenses, function(l) { return l.type + "(" + l.url + ")"; }).join(", ") %>\n' + '**/\n\n' }, jshint: { options: { jshintrc: '.jshintrc' }, all: [ 'Gruntfile.js', '<%= yeoman.src %>/**/*.js' ], test: { src: ['<%= yeoman.test %>/spec/**/*.js'], options: { jshintrc: '<%= yeoman.test %>/.jshintrc' } } }, karma: { options: { configFile: 'karma.conf.js' }, unit: { options: { singleRun: false } }, final: { options: { singleRun: true } }, travis: { browsers: ['PhantomJS'], options: { singleRun: true } } }, clean: { dist: { files: [{ dot: true, src: [ '<%= yeoman.dist %>/*', '!<%= yeoman.dist %>/.git*' ] }] }, temp: { src: ['<%= yeoman.dist %>/<%= yeoman.temp %>'] } }, ngAnnotate: { dist: { expand: true, cwd: '<%= yeoman.src %>', src: ['**/*.js'], dest: '<%= yeoman.dist %>/<%= yeoman.temp %>' } }, concat: { options: { banner: '<%= meta.banner %>', process: function(src, filepath) { // don't strip 'use strict' in the prefix if (filepath === 'bsSwitch.prefix') { return src; } return '// Source: ' + filepath + '\n' + src.replace(/(^|\n)[ \t]*('use strict'|"use strict");?\s*/g, '$1'); } }, dist: { src: ['bsSwitch.prefix', 'common/*.js', '<%= yeoman.dist %>/<%= yeoman.temp %>/**/*.js', 'bsSwitch.suffix'], dest: '<%= yeoman.dist %>/<%= pkg.name %>.js' } }, uglify: { options: { banner: '<%= meta.banner %>' }, min: { files: { '<%= yeoman.dist %>/<%= pkg.name %>.min.js': '<%= concat.dist.dest %>' } } } }); // Test the directive grunt.registerTask('test', ['jshint', 'karma:unit']); grunt.registerTask('test-travis', ['jshint', 'karma:travis']); // Build the directive // - clean, cleans the output directory // - ngAnnotate, prepares the angular files // - concat, concatenates and adds a banner to the debug file // - uglify, minifies and adds a banner to the minified file // - clean:temp, cleans the ngAnnotate-ified directory grunt.registerTask('build', ['clean', 'ngAnnotate', 'concat', 'uglify', 'clean:temp']); // Default task, do everything grunt.registerTask('default', ['test-travis', 'build']); }; ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: You must give any other recipients of the Work or Derivative Works a copy of this License; and You must cause any modified files to carry prominent notices stating that You changed the files; and You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ angular-bootstrap-switch ======================== [![Bower version][bower-version-image]][bower-url] [![NPM version][npm-version-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Apache License][license-image]][license-url] AngularJS directive for the [bootstrap-switch](https://github.com/nostalgiaz/bootstrap-switch) jQuery plugin. ## Usage ### Installation ```shell $ bower install angular-bootstrap-switch ``` or ```shell $ npm install angular-bootstrap-switch ``` This will install AngularJS, jQuery, and the original bootstrap-switch. ### Registration To be able to use the directive, you need to register the `angular-bootstrap-switch` module as a dependency: ```javascript angular.module('yourModule', ['frapontillo.bootstrap-switch' // other dependencies ]); ``` ### Directive The directive can work on both element and attribute levels. The following example contains all of the supported attributes: ```html ``` 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`. 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), 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) * `switch-size`, can be the empty string as default, `mini`, `small`, `large` * `switch-animate`, determines if the switch animates when toggled * `switch-on-text`, sets the positive (checked) text * `switch-off-text`, sets the negative (unchecked) text * `switch-on-color`, sets the positive (checked) class, can be `primary` (as default), `default`, `info`, `success`, `warning`, `danger` * `switch-off-color`, sets the negative (unchecked) class, can be `default` (as default), `primary`, `info`, `success`, `warning`, `danger` * `switch-label`, sets the toggle label * `switch-icon`, sets the toggle icon (e.g. `icon-save`) * `switch-wrapper`, sets the main container class, use a falsy value to fall back to the default one * `switch-radio-off`, allows a radio button to be unchecked by the user (from `true` to `false`) * `switch-label-width`, sets the width of the middle label * `switch-handle-width`, sets the width of both handles * `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 Read the [CHANGELOG](CHANGELOG.md#030-alpha1-2014-02-22) information to learn what's different in `0.3.0`. ### Examples The `example` folder shows a simple working demo of the switch. ### 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 ### Test and build To build the directive yourself you need to have NodeJS. Then do the following: ```shell $ npm install -g grunt-cli bower karma $ npm install $ bower install $ grunt test-travis $ grunt build ``` ### 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 Francesco Pontillo () ## License ``` 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. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ``` [license-image]: http://img.shields.io/badge/license-Apache_2.0-blue.svg?style=flat [license-url]: LICENSE [bower-version-image]: http://img.shields.io/bower/v/angular-bootstrap-switch.svg?style=flat [bower-url]: http://bower.io/search/?q=angular-bootstrap-switch [npm-url]: https://npmjs.org/package/angular-bootstrap-switch [npm-version-image]: http://img.shields.io/npm/v/angular-bootstrap-switch.svg?style=flat [travis-image]: http://img.shields.io/travis/frapontillo/angular-bootstrap-switch/develop.svg?style=flat [travis-url]: https://travis-ci.org/frapontillo/angular-bootstrap-switch ================================================ FILE: bower.json ================================================ { "name": "angular-bootstrap-switch", "version": "0.5.2", "author": { "name": "Francesco Pontillo", "email": "francescopontillo@gmail.com", "url": "https://github.com/frapontillo" }, "homepage": "https://github.com/frapontillo/angular-bootstrap-switch", "repository": { "type": "git", "url": "git@github.com:frapontillo/angular-bootstrap-switch.git" }, "licenses": [ { "type": "Apache License 2.0", "url": "http://www.apache.org/licenses/LICENSE-2.0.html" } ], "main": "./dist/angular-bootstrap-switch.js", "dependencies": { "angular": ">=1.4.0", "jquery": ">=1.9.0", "bootstrap": ">=2.3.2", "bootstrap-switch": "~3.3.4" }, "devDependencies": { "angular-mocks": ">=1.4.0", "angular-scenario": ">=1.4.0" } } ================================================ FILE: bsSwitch.prefix ================================================ (function() { 'use strict'; ================================================ FILE: bsSwitch.suffix ================================================ })(); ================================================ FILE: common/module.js ================================================ 'use strict'; angular.module('frapontillo.bootstrap-switch', []); ================================================ FILE: dist/angular-bootstrap-switch.js ================================================ /** * angular-bootstrap-switch * @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) **/ (function() { 'use strict'; // Source: common/module.js angular.module('frapontillo.bootstrap-switch', []); // Source: dist/.temp/directives/bsSwitch.js angular.module('frapontillo.bootstrap-switch') .directive('bsSwitch', ["$parse", "$timeout", function ($parse, $timeout) { return { restrict: 'A', require: 'ngModel', link: function link(scope, element, attrs, controller) { var isInit = false; /** * Return the true value for this specific checkbox. * @returns {Object} representing the true view value; if undefined, returns true. */ var getTrueValue = function() { if (attrs.type === 'radio') { return attrs.value || $parse(attrs.ngValue)(scope) || true; } var trueValue = ($parse(attrs.ngTrueValue)(scope)); if (angular.isUndefined(trueValue)) { trueValue = true; } return trueValue; }; /** * Get a boolean value from a boolean-like string, evaluating it on the current scope. * @param value The input object * @returns {boolean} A boolean value */ var getBooleanFromString = function(value) { return scope.$eval(value) === true; }; /** * Get a boolean value from a boolean-like string, defaulting to true if undefined. * @param value The input object * @returns {boolean} A boolean value */ var getBooleanFromStringDefTrue = function(value) { return (value === true || value === 'true' || !value); }; /** * Returns the value if it is truthy, or undefined. * * @param value The value to check. * @returns the original value if it is truthy, {@link undefined} otherwise. */ var getValueOrUndefined = function (value) { return (value ? value : undefined); }; /** * Returns a function that executes the provided expression * * @param value The string expression * @return a function that evaluates the expression */ var getExprFromString = function (value) { if (angular.isUndefined(value)) { return angular.noop; } return function () { scope.$evalAsync(value); }; }; /** * Get the value of the angular-bound attribute, given its name. * The returned value may or may not equal the attribute value, as it may be transformed by a function. * * @param attrName The angular-bound attribute name to get the value for * @returns {*} The attribute value */ var getSwitchAttrValue = function(attrName) { var map = { 'switchRadioOff': getBooleanFromStringDefTrue, 'switchActive': function(value) { return !getBooleanFromStringDefTrue(value); }, 'switchAnimate': getBooleanFromStringDefTrue, 'switchLabel': function(value) { return value ? value : ' '; }, 'switchIcon': function(value) { if (value) { return ''; } }, 'switchWrapper': function(value) { return value || 'wrapper'; }, 'switchInverse': getBooleanFromString, 'switchReadonly': getBooleanFromString, 'switchChange': getExprFromString }; var transFn = map[attrName] || getValueOrUndefined; return transFn(attrs[attrName]); }; /** * Set a bootstrapSwitch parameter according to the angular-bound attribute. * The parameter will be changed only if the switch has already been initialized * (to avoid creating it before the model is ready). * * @param element The switch to apply the parameter modification to * @param attr The name of the switch parameter * @param modelAttr The name of the angular-bound parameter */ var setSwitchParamMaybe = function(element, attr, modelAttr) { if (!isInit) { return; } var newValue = getSwitchAttrValue(modelAttr); element.bootstrapSwitch(attr, newValue); }; var setActive = function() { setSwitchParamMaybe(element, 'disabled', 'switchActive'); }; /** * If the directive has not been initialized yet, do so. */ var initMaybe = function() { // if it's the first initialization if (!isInit) { var viewValue = (controller.$modelValue === getTrueValue()); isInit = !isInit; // Bootstrap the switch plugin element.bootstrapSwitch({ radioAllOff: getSwitchAttrValue('switchRadioOff'), disabled: getSwitchAttrValue('switchActive'), state: viewValue, onText: getSwitchAttrValue('switchOnText'), offText: getSwitchAttrValue('switchOffText'), onColor: getSwitchAttrValue('switchOnColor'), offColor: getSwitchAttrValue('switchOffColor'), animate: getSwitchAttrValue('switchAnimate'), size: getSwitchAttrValue('switchSize'), labelText: attrs.switchLabel ? getSwitchAttrValue('switchLabel') : getSwitchAttrValue('switchIcon'), wrapperClass: getSwitchAttrValue('switchWrapper'), handleWidth: getSwitchAttrValue('switchHandleWidth'), labelWidth: getSwitchAttrValue('switchLabelWidth'), inverse: getSwitchAttrValue('switchInverse'), readonly: getSwitchAttrValue('switchReadonly') }); if (attrs.type === 'radio') { controller.$setViewValue(controller.$modelValue); } else { controller.$setViewValue(viewValue); } } }; var switchChange = getSwitchAttrValue('switchChange'); /** * Listen to model changes. */ var listenToModel = function () { attrs.$observe('switchActive', function (newValue) { var active = getBooleanFromStringDefTrue(newValue); // if we are disabling the switch, delay the deactivation so that the toggle can be switched if (!active) { $timeout(setActive); } else { // if we are enabling the switch, set active right away setActive(); } }); // When the model changes controller.$render = function () { initMaybe(); var newValue = controller.$modelValue; if (newValue !== undefined && newValue !== null) { element.bootstrapSwitch('state', newValue === getTrueValue(), true); } else { element.bootstrapSwitch('indeterminate', true, true); controller.$setViewValue(undefined); } switchChange(); }; // angular attribute to switch property bindings var bindings = { 'switchRadioOff': 'radioAllOff', 'switchOnText': 'onText', 'switchOffText': 'offText', 'switchOnColor': 'onColor', 'switchOffColor': 'offColor', 'switchAnimate': 'animate', 'switchSize': 'size', 'switchLabel': 'labelText', 'switchIcon': 'labelText', 'switchWrapper': 'wrapperClass', 'switchHandleWidth': 'handleWidth', 'switchLabelWidth': 'labelWidth', 'switchInverse': 'inverse', 'switchReadonly': 'readonly' }; var observeProp = function(prop, bindings) { return function() { attrs.$observe(prop, function () { setSwitchParamMaybe(element, bindings[prop], prop); }); }; }; // for every angular-bound attribute, observe it and trigger the appropriate switch function for (var prop in bindings) { attrs.$observe(prop, observeProp(prop, bindings)); } }; /** * Listen to view changes. */ var listenToView = function () { if (attrs.type === 'radio') { // when the switch is clicked element.on('change.bootstrapSwitch', function (e) { // discard not real change events if ((controller.$modelValue === controller.$viewValue) && (e.target.checked !== $(e.target).bootstrapSwitch('state'))) { // $setViewValue --> $viewValue --> $parsers --> $modelValue // if the switch is indeed selected if (e.target.checked) { // set its value into the view controller.$setViewValue(getTrueValue()); } else if (getTrueValue() === controller.$viewValue) { // otherwise if it's been deselected, delete the view value controller.$setViewValue(undefined); } switchChange(); } }); } else { // When the checkbox switch is clicked, set its value into the ngModel element.on('switchChange.bootstrapSwitch', function (e) { // $setViewValue --> $viewValue --> $parsers --> $modelValue controller.$setViewValue(e.target.checked); switchChange(); }); } }; // Listen and respond to view changes listenToView(); // Listen and respond to model changes listenToModel(); // On destroy, collect ya garbage scope.$on('$destroy', function () { element.bootstrapSwitch('destroy'); }); } }; }]) .directive('bsSwitch', function () { return { restrict: 'E', require: 'ngModel', template: '', replace: true }; }); // Source: bsSwitch.suffix })(); ================================================ FILE: example/index.html ================================================ AngularJS Bootstrap Switch example
{{ isSelected }}
================================================ FILE: example/scripts/app.js ================================================ 'use strict'; angular.module('bsSwitchApp', ['frapontillo.bootstrap-switch']); ================================================ FILE: example/scripts/controllers/main.js ================================================ 'use strict'; angular.module('bsSwitchApp') .controller('MainCtrl', function ($scope, $log) { $scope.isSelected = 'nope'; $scope.onText = 'Y'; $scope.offText = 'N'; $scope.isActive = true; $scope.size = 'normal'; $scope.animate = true; $scope.radioOff = true; $scope.handleWidth = "auto"; $scope.labelWidth = "auto"; $scope.inverse = true; $scope.$watch('isSelected', function() { $log.info('Selection changed.'); }); $scope.toggle = function() { $scope.isSelected = $scope.isSelected === 'yep' ? 'nope' : 'yep'; }; $scope.setUndefined = function() { $scope.isSelected = undefined; }; $scope.toggleActivation = function() { $scope.isActive = !$scope.isActive; } }); ================================================ FILE: example/styles/main.css ================================================ body { background: #fafafa; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; color: #333; } .hero-unit { margin: 50px auto 0 auto; width: 300px; font-size: 18px; font-weight: 200; line-height: 30px; background-color: #eee; border-radius: 6px; padding: 60px; } .hero-unit h1 { font-size: 60px; line-height: 1; letter-spacing: -1px; } ================================================ FILE: karma-chrome.conf.js ================================================ // Karma configuration // Generated on Thu Mar 27 2014 09:18:43 GMT+0100 (ora solare Europa occidentale) module.exports = function(config) { config.set({ // base path that will be used to resolve all patterns (eg. files, exclude) basePath: '', // frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter frameworks: ['jasmine'], // list of files / patterns to load in the browser files: [ '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', 'common/module.js', 'src/**/*.js', 'test/**/*.js' ], // list of files to exclude exclude: [ ], // preprocess matching files before serving them to the browser // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor preprocessors: { }, // test results reporter to use // possible values: 'dots', 'progress' // available reporters: https://npmjs.org/browse/keyword/karma-reporter reporters: ['progress'], // web server port port: 9876, // enable / disable colors in the output (reporters and logs) colors: true, // level of logging // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG logLevel: config.LOG_INFO, // enable / disable watching file and executing tests whenever any file changes autoWatch: true, // start these browsers // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher browsers: ['Chrome'], // Continuous Integration mode // if true, Karma captures browsers, runs the tests and exits singleRun: false }); }; ================================================ FILE: karma.conf.js ================================================ // Karma configuration // Generated on Thu Mar 27 2014 09:18:43 GMT+0100 (ora solare Europa occidentale) module.exports = function(config) { config.set({ // base path that will be used to resolve all patterns (eg. files, exclude) basePath: '', // frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter frameworks: ['jasmine'], // list of files / patterns to load in the browser files: [ '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', 'common/module.js', 'src/**/*.js', 'test/**/*.js' ], // list of files to exclude exclude: [ ], // preprocess matching files before serving them to the browser // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor preprocessors: { }, // test results reporter to use // possible values: 'dots', 'progress' // available reporters: https://npmjs.org/browse/keyword/karma-reporter reporters: ['progress'], // web server port port: 9876, // enable / disable colors in the output (reporters and logs) colors: true, // level of logging // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG logLevel: config.LOG_INFO, // enable / disable watching file and executing tests whenever any file changes autoWatch: true, // start these browsers // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher browsers: ['Chrome', 'Firefox', 'PhantomJS'], // Continuous Integration mode // if true, Karma captures browsers, runs the tests and exits singleRun: false }); }; ================================================ FILE: package.json ================================================ { "name": "angular-bootstrap-switch", "version": "0.5.2", "main": "dist/angular-bootstrap-switch.js", "author": { "name": "Francesco Pontillo", "email": "francescopontillo@gmail.com", "url": "https://github.com/frapontillo" }, "homepage": "https://github.com/frapontillo/angular-bootstrap-switch", "repository": { "type": "git", "url": "git@github.com:frapontillo/angular-bootstrap-switch.git" }, "license": "Apache-2.0", "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", "grunt-contrib-concat": "~0.5.1", "grunt-contrib-jshint": "~0.11.0", "grunt-contrib-uglify": "~0.11.0", "grunt-karma": "~0.12.1", "grunt-ng-annotate": "^1.0.1", "jasmine-core": "^2.4.1", "karma": "^0.13.19", "karma-chrome-launcher": "~0.2.2", "karma-firefox-launcher": "~0.1.7", "karma-jasmine": "~0.3.6", "karma-phantomjs-launcher": "~1.0.0", "matchdep": "~1.0.0", "phantomjs-prebuilt": "^2.1.3" }, "engines": { "node": ">=4.0.0" }, "scripts": { "test": "grunt test-travis" } } ================================================ FILE: src/directives/bsSwitch.js ================================================ 'use strict'; angular.module('frapontillo.bootstrap-switch') .directive('bsSwitch', function ($parse, $timeout) { return { restrict: 'A', require: 'ngModel', link: function link(scope, element, attrs, controller) { var isInit = false; /** * Return the true value for this specific checkbox. * @returns {Object} representing the true view value; if undefined, returns true. */ var getTrueValue = function() { if (attrs.type === 'radio') { return attrs.value || $parse(attrs.ngValue)(scope) || true; } var trueValue = ($parse(attrs.ngTrueValue)(scope)); if (angular.isUndefined(trueValue)) { trueValue = true; } return trueValue; }; /** * Get a boolean value from a boolean-like string, evaluating it on the current scope. * @param value The input object * @returns {boolean} A boolean value */ var getBooleanFromString = function(value) { return scope.$eval(value) === true; }; /** * Get a boolean value from a boolean-like string, defaulting to true if undefined. * @param value The input object * @returns {boolean} A boolean value */ var getBooleanFromStringDefTrue = function(value) { return (value === true || value === 'true' || !value); }; /** * Returns the value if it is truthy, or undefined. * * @param value The value to check. * @returns the original value if it is truthy, {@link undefined} otherwise. */ var getValueOrUndefined = function (value) { return (value ? value : undefined); }; /** * Returns a function that executes the provided expression * * @param value The string expression * @return a function that evaluates the expression */ var getExprFromString = function (value) { if (angular.isUndefined(value)) { return angular.noop; } return function () { scope.$evalAsync(value); }; }; /** * Get the value of the angular-bound attribute, given its name. * The returned value may or may not equal the attribute value, as it may be transformed by a function. * * @param attrName The angular-bound attribute name to get the value for * @returns {*} The attribute value */ var getSwitchAttrValue = function(attrName) { var map = { 'switchRadioOff': getBooleanFromStringDefTrue, 'switchActive': function(value) { return !getBooleanFromStringDefTrue(value); }, 'switchAnimate': getBooleanFromStringDefTrue, 'switchLabel': function(value) { return value ? value : ' '; }, 'switchIcon': function(value) { if (value) { return ''; } }, 'switchWrapper': function(value) { return value || 'wrapper'; }, 'switchInverse': getBooleanFromString, 'switchReadonly': getBooleanFromString, 'switchChange': getExprFromString }; var transFn = map[attrName] || getValueOrUndefined; return transFn(attrs[attrName]); }; /** * Set a bootstrapSwitch parameter according to the angular-bound attribute. * The parameter will be changed only if the switch has already been initialized * (to avoid creating it before the model is ready). * * @param element The switch to apply the parameter modification to * @param attr The name of the switch parameter * @param modelAttr The name of the angular-bound parameter */ var setSwitchParamMaybe = function(element, attr, modelAttr) { if (!isInit) { return; } var newValue = getSwitchAttrValue(modelAttr); element.bootstrapSwitch(attr, newValue); }; var setActive = function() { setSwitchParamMaybe(element, 'disabled', 'switchActive'); }; /** * If the directive has not been initialized yet, do so. */ var initMaybe = function() { // if it's the first initialization if (!isInit) { var viewValue = (controller.$modelValue === getTrueValue()); isInit = !isInit; // Bootstrap the switch plugin element.bootstrapSwitch({ radioAllOff: getSwitchAttrValue('switchRadioOff'), disabled: getSwitchAttrValue('switchActive'), state: viewValue, onText: getSwitchAttrValue('switchOnText'), offText: getSwitchAttrValue('switchOffText'), onColor: getSwitchAttrValue('switchOnColor'), offColor: getSwitchAttrValue('switchOffColor'), animate: getSwitchAttrValue('switchAnimate'), size: getSwitchAttrValue('switchSize'), labelText: attrs.switchLabel ? getSwitchAttrValue('switchLabel') : getSwitchAttrValue('switchIcon'), wrapperClass: getSwitchAttrValue('switchWrapper'), handleWidth: getSwitchAttrValue('switchHandleWidth'), labelWidth: getSwitchAttrValue('switchLabelWidth'), inverse: getSwitchAttrValue('switchInverse'), readonly: getSwitchAttrValue('switchReadonly') }); if (attrs.type === 'radio') { 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()); }; } } }; var switchChange = getSwitchAttrValue('switchChange'); /** * Listen to model changes. */ var listenToModel = function () { attrs.$observe('switchActive', function (newValue) { var active = getBooleanFromStringDefTrue(newValue); // if we are disabling the switch, delay the deactivation so that the toggle can be switched if (!active) { $timeout(setActive); } else { // if we are enabling the switch, set active right away setActive(); } }); // 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); } else { element.bootstrapSwitch('indeterminate', true, true); controller.$setViewValue(undefined); } // return initial value for "disabled" setActive(); switchChange(); }; // angular attribute to switch property bindings var bindings = { 'switchRadioOff': 'radioAllOff', 'switchOnText': 'onText', 'switchOffText': 'offText', 'switchOnColor': 'onColor', 'switchOffColor': 'offColor', 'switchAnimate': 'animate', 'switchSize': 'size', 'switchLabel': 'labelText', 'switchIcon': 'labelText', 'switchWrapper': 'wrapperClass', 'switchHandleWidth': 'handleWidth', 'switchLabelWidth': 'labelWidth', 'switchInverse': 'inverse', 'switchReadonly': 'readonly' }; var observeProp = function(prop, bindings) { return function() { attrs.$observe(prop, function () { setSwitchParamMaybe(element, bindings[prop], prop); }); }; }; // for every angular-bound attribute, observe it and trigger the appropriate switch function for (var prop in bindings) { attrs.$observe(prop, observeProp(prop, bindings)); } }; /** * Listen to view changes. */ var listenToView = function () { if (attrs.type === 'radio') { // when the switch is clicked element.on('change.bootstrapSwitch', function (e) { // discard not real change events if ((controller.$modelValue === controller.$viewValue) && (e.target.checked !== $(e.target).bootstrapSwitch('state'))) { // $setViewValue --> $viewValue --> $parsers --> $modelValue // if the switch is indeed selected if (e.target.checked) { // set its value into the view controller.$setViewValue(getTrueValue()); } else if (getTrueValue() === controller.$viewValue) { // otherwise if it's been deselected, delete the view value controller.$setViewValue(undefined); } switchChange(); } }); } else { // When the checkbox switch is clicked, set its value into the ngModel element.on('switchChange.bootstrapSwitch', function (e) { // $setViewValue --> $viewValue --> $parsers --> $modelValue controller.$setViewValue(e.target.checked); switchChange(); }); } }; // Listen and respond to view changes listenToView(); // Listen and respond to model changes listenToModel(); // On destroy, collect ya garbage scope.$on('$destroy', function () { element.bootstrapSwitch('destroy'); }); } }; }) .directive('bsSwitch', function () { return { restrict: 'E', require: 'ngModel', template: '', replace: true }; }); ================================================ FILE: test/.jshintrc ================================================ { "node": true, "browser": true, "esnext": true, "bitwise": true, "camelcase": true, "curly": true, "eqeqeq": true, "immed": true, "indent": 2, "latedef": true, "newcap": true, "noarg": true, "quotmark": "single", "regexp": true, "undef": true, "unused": true, "strict": true, "trailing": true, "smarttabs": true, "globals": { "jQuery": false, "after": false, "afterEach": false, "angular": false, "before": false, "beforeEach": false, "browser": false, "describe": false, "expect": false, "inject": false, "it": false, "spyOn": false, "jasmine": false } } ================================================ FILE: test/runner.html ================================================ End2end Test Runner ================================================ FILE: test/spec/directives/bsSwitchSpec.js ================================================ 'use strict'; describe('Directive: bsSwitch', function () { var scope, $sandbox, $compile, $timeout; beforeEach(module('frapontillo.bootstrap-switch')); /* jshint camelcase: false */ beforeEach(inject(function ($injector, $rootScope, _$compile_, _$timeout_) { scope = $rootScope; $compile = _$compile_; $timeout = _$timeout_; $sandbox = angular.element('
').appendTo(angular.element.find('body')); })); /* jshint camelcase: true */ afterEach(function() { $sandbox.remove(); scope.$destroy(); }); var templates = { 'default': { scope: {model:true}, element: 'ng-model="model" type="checkbox"' }, 'multipleRadios': { scope: {model:''}, element: [ 'ng-model="model" name="radio" type="radio" value="uno"', 'ng-model="model" name="radio" type="radio" value="dos"', 'ng-model="model" name="radio" type="radio" value="tres"' ] }, 'radio': { scope: {model:true}, element: 'ng-model="model" name="radio" type="radio"' }, 'radioOff': { scope: {model:true, radioOff:false}, element: 'ng-model="model" name="radio" type="radio" switch-radio-off="{{ radioOff }}"' }, 'active': { scope: {model:true, isActive:true}, element: 'ng-model="model" type="checkbox" switch-active="{{ isActive }}"' }, 'unactivated': { scope: {model:true, isActive:false}, element: 'ng-model="model" type="checkbox" switch-active="{{ isActive }}"' }, 'readonly': { scope: {model:true}, element: 'ng-model="model" type="checkbox" switch-readonly="{{ isReadonly }}"' }, 'size': { scope: {model:true, size:'large'}, element: 'ng-model="model" type="checkbox" switch-size="{{ size }}" switch-label-width="{{ labelWidth }}" switch-handle-width="{{ handleWidth }}"' }, 'color': { scope: {model:true, on:'info', off:'warning'}, element: 'ng-model="model" type="checkbox" switch-on-color="{{ on }}" switch-off-color="{{ off }}"' }, 'label': { scope: {model:true}, element: 'ng-model="model" type="checkbox" switch-on-text="{{ on }}" switch-off-text="{{ off }}" switch-label="{{ label }}"' }, 'icon': { scope: {model:true, icon:'icon-youtube'}, element: 'ng-model="model" type="checkbox" switch-icon="{{ icon }}"' }, 'animation': { scope: {model:true}, element: 'ng-model="model" type="checkbox" switch-animate="{{ animate }}"' }, 'modifier': { scope: {model:true}, element: 'ng-model="model" type="checkbox" switch-wrapper="{{ modifier }}"' }, 'customValues': { scope: {model:'something'}, element: 'ng-model="model" type="checkbox" ng-true-value="\'yep\'" ng-false-value="\'nope\'"' }, 'customObjectsValues': { scope: {model:1}, element: 'ng-model="model" type="checkbox" ng-true-value="{{ 0 | json }}" ng-false-value="{{ 1 | json }}"' }, 'inverse': { scope: {model:true}, element: 'ng-model="model" type="checkbox" switch-inverse="{{ inverse }}"' }, 'getterSetter': { scope: {}, element: 'ng-model="modelGetterSetter" ng-model-options="{getterSetter: true}" type="checkbox"' }, 'change': { scope: {}, element: 'ng-model="model" type="checkbox" switch-change="switchChange()"' }, 'ngChange': { scope: {}, element: 'ng-model="model" type="checkbox" ng-change="ngChange()"' } }; var CONST = { SWITCH_CLASS: 'bootstrap-switch', SWITCH_WRAPPER_CLASS: 'bootstrap-switch-wrapper', SWITCH_CONTAINER_CLASS: 'bootstrap-switch-container', SWITCH_INVERSE_CLASS: 'bootstrap-switch-inverse', SWITCH_INDETERMINATE_CLASS: 'bootstrap-switch-indeterminate', SWITCH_ON_CLASS: 'bootstrap-switch-on', SWITCH_OFF_CLASS: 'bootstrap-switch-off', SWITCH_DISABLED_CLASS: 'bootstrap-switch-disabled', SWITCH_READONLY_CLASS: 'bootstrap-switch-readonly', SWITCH_MINI_CLASS: 'bootstrap-switch-mini', SWITCH_INFO_CLASS: 'bootstrap-switch-info', SWITCH_WARNING_CLASS: 'bootstrap-switch-warning', SWITCH_SUCCESS_CLASS: 'bootstrap-switch-success', SWITCH_ERROR_CLASS: 'bootstrap-switch-error', SWITCH_ANIMATED_CLASS: 'bootstrap-switch-animate', SWITCH_LEFT_SELECTOR: '.bootstrap-switch-handle-on', SWITCH_RIGHT_SELECTOR: '.bootstrap-switch-handle-off', LABEL_SELECTOR: '.bootstrap-switch-label', INPUT_SELECTOR: 'input', ICON_SELECTOR: '.bootstrap-switch-label span', DEFAULT_TRUE_TEXT: 'ON', DEFAULT_FALSE_TEXT: 'OFF' }; /** * Build an element string. * @param template The template element to be used * @param input true if the element must be an `input` tag, anything falsy for `bs-switch` * @returns {string} The HTML element as a string */ function buildElement(template, input) { var elementContent = template.element; var realElement; if (angular.isArray(elementContent)) { realElement = '
'; for (var c in elementContent) { realElement += buildSingleElement(elementContent[c], input); } realElement += '
'; return realElement; } return buildSingleElement(elementContent, input); } function buildSingleElement(content, isInput) { var singleElement = (isInput ? ''; if (!isInput) { singleElement += ''; } return singleElement; } /** * Compile a given template object as an `input` or a `bs-switch`. * @param template The template object * @param input true if the element must be an `input` tag, anything falsy for `bs-switch` * @returns {*} compiled angular element */ function compileDirective(template, input) { template = template ? templates[template] : templates['default']; angular.extend(scope, template.scope || templates['default'].scope); var content = buildElement(template, input); var $element = angular.element(content).appendTo($sandbox); $compile($element)(scope); scope.$apply(); $element = $sandbox.find('> *:first-child'); return $element; } // Test the widget creation and defaults function makeTestCreateSwitch(input) { return function() { var element = compileDirective(undefined, input); expect(element).not.toBe(undefined); expect(element.hasClass(CONST.SWITCH_CLASS)).toBe(true); expect(element.find(CONST.SWITCH_LEFT_SELECTOR).html()).toBe(CONST.DEFAULT_TRUE_TEXT); expect(element.find(CONST.SWITCH_RIGHT_SELECTOR).html()).toBe(CONST.DEFAULT_FALSE_TEXT); expect(element.find(CONST.LABEL_SELECTOR).html()).toBe(' '); expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeTruthy(); expect(element.hasClass(CONST.SWITCH_DISABLED_CLASS)).toBeFalsy(); expect(element.find(CONST.INPUT_SELECTOR).attr('disabled')).toBeFalsy(); }; } it('should create a switch', inject(makeTestCreateSwitch())); it('should create a switch (input)', inject(makeTestCreateSwitch(true))); // Test the switch type function makeTestRadio(input) { return function () { var element = compileDirective('radio', input); expect(element.find(CONST.INPUT_SELECTOR).attr('type')).toBe('radio'); }; } it('should create a radio switch', inject(makeTestRadio())); it('should create a radio switch (input)', inject(makeTestRadio(true))); // Test the change of a radio switch from true to false function makeTestRadioOffFalse(input) { return function () { var element = compileDirective('radioOff', 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_OFF_CLASS)).toBeFalsy(); expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeTruthy(); }; } it('should not change a radio from true to false', inject(makeTestRadioOffFalse())); it('should not change a radio from true to false (input)', inject(makeTestRadioOffFalse(true))); // Test the change of a radio switch from true to false function makeTestRadioOffTrue(input) { return function () { var element = compileDirective('radioOff', input); expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeFalsy(); expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeTruthy(); scope.radioOff = true; scope.$apply(); scope.model = false; scope.$apply(); expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeTruthy(); expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeFalsy(); }; } it('should change a radio from true to false', inject(makeTestRadioOffTrue())); it('should change a radio from true to false (input)', inject(makeTestRadioOffTrue(true))); function expectNothing(el1, el2, el3) { expect(el1.hasClass(CONST.SWITCH_OFF_CLASS)).toBeTruthy(); expect(el1.hasClass(CONST.SWITCH_ON_CLASS)).toBeFalsy(); expect(el2.hasClass(CONST.SWITCH_OFF_CLASS)).toBeTruthy(); expect(el2.hasClass(CONST.SWITCH_ON_CLASS)).toBeFalsy(); expect(el3.hasClass(CONST.SWITCH_OFF_CLASS)).toBeTruthy(); expect(el3.hasClass(CONST.SWITCH_ON_CLASS)).toBeFalsy(); } function makeTestMultipleRadios(input) { return function () { var element = compileDirective('multipleRadios', input); var elements = element.find('.bootstrap-switch'); var el1 = angular.element(elements[0]); var el2 = angular.element(elements[1]); var el3 = angular.element(elements[2]); expectNothing(el1, el2, el3); scope.model = 'wat'; scope.$apply(); expectNothing(el1, el2, el3); scope.model = 'dos'; scope.$apply(); expect(el2.hasClass(CONST.SWITCH_OFF_CLASS)).toBeFalsy(); expect(el2.hasClass(CONST.SWITCH_ON_CLASS)).toBeTruthy(); expect(scope.model).toEqual('dos'); expect(el1.hasClass(CONST.SWITCH_OFF_CLASS)).toBeTruthy(); expect(el1.hasClass(CONST.SWITCH_ON_CLASS)).toBeFalsy(); expect(el3.hasClass(CONST.SWITCH_OFF_CLASS)).toBeTruthy(); expect(el3.hasClass(CONST.SWITCH_ON_CLASS)).toBeFalsy(); }; } it('should set the proper model with multiple radios', inject(makeTestMultipleRadios())); it('should set the proper model with multiple radios (input)', inject(makeTestMultipleRadios(true))); function makeTestMultipleRadiosOff(input) { return function () { var element = compileDirective('multipleRadios', input); var elements = element.find('.bootstrap-switch'); expect(scope.model).toEqual(''); var el1 = angular.element(elements[0]); var el2 = angular.element(elements[1]); var el3 = angular.element(elements[2]); expectNothing(el1, el2, el3); jQuery(el3).find('input').bootstrapSwitch('toggleState'); scope.$apply(); expect(scope.model).toEqual('tres'); jQuery(el3).find('input').bootstrapSwitch('toggleState'); scope.$apply(); expect(scope.model).toEqual(undefined); expectNothing(el1, el2, el3); }; } it('should set the proper model to undefined when a radio is turned off', inject(makeTestMultipleRadiosOff())); it('should set the proper model to undefined when a radio is turned off (input)', inject(makeTestMultipleRadiosOff(true))); // Test the model change function makeTestChangeModel(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_OFF_CLASS)).toBeTruthy(); expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeFalsy(); }; } it('should move the switch when the model changes', inject(makeTestChangeModel())); it('should move the switch when the model changes (input)', inject(makeTestChangeModel(true))); // Test the undefined model (the on/off class is untouched when the indeterminate class is added) function makeTestIndeterminateUndefinedModel(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 = 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 set the indeterminate state when the model is undefined', inject(makeTestIndeterminateUndefinedModel())); it('should set the indeterminate state when the model is undefined (input)', inject(makeTestIndeterminateUndefinedModel(true))); // Test the null model (the on/off class is untouched when the indeterminate class is added) function makeTestIndeterminateNullModel(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 = 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 set the indeterminate state when the model is null', inject(makeTestIndeterminateNullModel())); it('should set the indeterminate state when the model is null (input)', inject(makeTestIndeterminateNullModel(true))); // Test the view change function makeTestChangeView(input) { return function () { var element = compileDirective(undefined, input); expect(scope.model).toBeTruthy(); element.find('input').bootstrapSwitch('toggleState'); scope.$apply(); expect(scope.model).toBeFalsy(); element.find('input').bootstrapSwitch('toggleState'); scope.$apply(); expect(scope.model).toBeTruthy(); }; } it('should change the model when the switch is clicked', inject(makeTestChangeView())); it('should change the model when the switch is clicked (input)', inject(makeTestChangeView(true))); // Test the deactivation function makeTestDeactivate(input) { return function () { var element = compileDirective('active', input); expect(element.hasClass(CONST.SWITCH_DISABLED_CLASS)).toBeFalsy(); expect(element.find(CONST.INPUT_SELECTOR).attr('disabled')).toBeFalsy(); scope.isActive = false; scope.$apply(); $timeout.flush(); expect(element.hasClass(CONST.SWITCH_DISABLED_CLASS)).toBeTruthy(); expect(element.find(CONST.INPUT_SELECTOR).attr('disabled')).toBeTruthy(); }; } it('should deactivate the switch', inject(makeTestDeactivate())); it('should deactivate the switch (input)', inject(makeTestDeactivate(true))); // Test a model change followed by a deactivation function makeTestChangeModelThenDeactivate(input) { return function () { var element = compileDirective('active', input); // test the active state, should be true expect(element.hasClass(CONST.SWITCH_DISABLED_CLASS)).toBeFalsy(); expect(element.find(CONST.INPUT_SELECTOR).attr('disabled')).toBeFalsy(); // test the model, should be false expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeFalsy(); expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeTruthy(); 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(); }; } 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 () { var element = compileDirective('unactivated', input); // need to flush since the element starts as deactivated $timeout.flush(); expect(element.hasClass(CONST.SWITCH_DISABLED_CLASS)).toBeTruthy(); scope.isActive = true; scope.$apply(); // no need to flush here since we are activating the switch expect(element.hasClass(CONST.SWITCH_DISABLED_CLASS)).toBeFalsy(); }; } it('should activate the switch', inject(makeTestActivate())); it('should activate the switch (input)', inject(makeTestActivate(true))); // Test the readonly function makeTestReadonly(input) { return function () { var element = compileDirective('readonly', input); expect(element.hasClass(CONST.SWITCH_READONLY_CLASS)).toBeFalsy(); expect(element.find(CONST.INPUT_SELECTOR).attr('readonly')).toBeFalsy(); scope.isReadonly = true; scope.$apply(); expect(element.hasClass(CONST.SWITCH_READONLY_CLASS)).toBeTruthy(); expect(element.find(CONST.INPUT_SELECTOR).attr('readonly')).toBeTruthy(); }; } it('should set the switch as read only', inject(makeTestReadonly())); it('should set the switch as read only (input)', inject(makeTestReadonly(true))); // Test the size change function makeTestChangeSize(input) { return function () { var element = compileDirective('size', input); expect(element.hasClass(CONST.SWITCH_MINI_CLASS)).toBeFalsy(); scope.size = 'mini'; scope.$apply(); expect(element.hasClass(CONST.SWITCH_MINI_CLASS)).toBeTruthy(); }; } it('should change the switch size', inject(makeTestChangeSize())); it('should change the switch size (input)', inject(makeTestChangeSize(true))); // Test the label width change function makeTestChangeLabelWidth(input) { return function () { var element = compileDirective('size', input); scope.labelWidth = '600'; scope.$apply(); expect(element.find(CONST.INPUT_SELECTOR).bootstrapSwitch('labelWidth')).toEqual('600'); }; } it('should change the label width', inject(makeTestChangeLabelWidth())); it('should change the label width (input)', inject(makeTestChangeLabelWidth(true))); // Test the handle width change function makeTestChangeHandleWidth(input) { return function () { var element = compileDirective('size', input); scope.handleWidth = '600'; scope.$apply(); expect(element.find(CONST.INPUT_SELECTOR).bootstrapSwitch('handleWidth')).toEqual('600'); }; } it('should change the handle width', inject(makeTestChangeHandleWidth())); it('should change the handle width (input)', inject(makeTestChangeHandleWidth(true))); // Test the "on" and "off" color change function makeTestChangeColor(input) { return function () { var element = compileDirective('color', input); expect(element.find(CONST.SWITCH_LEFT_SELECTOR).hasClass(CONST.SWITCH_INFO_CLASS)).toBeTruthy(); expect(element.find(CONST.SWITCH_RIGHT_SELECTOR).hasClass(CONST.SWITCH_WARNING_CLASS)).toBeTruthy(); scope.on = 'success'; scope.off = 'error'; scope.$apply(); expect(element.find(CONST.SWITCH_LEFT_SELECTOR).hasClass(CONST.SWITCH_SUCCESS_CLASS)).toBeTruthy(); expect(element.find(CONST.SWITCH_RIGHT_SELECTOR).hasClass(CONST.SWITCH_ERROR_CLASS)).toBeTruthy(); }; } it('should change the switch colors', inject(makeTestChangeColor())); it('should change the switch colors (input)', inject(makeTestChangeColor(true))); // Test the "on" and "off" label change function makeTestChangeLabel(input) { return function () { var element = compileDirective('label', input); expect(element.find(CONST.SWITCH_LEFT_SELECTOR).html()).toBe(CONST.DEFAULT_TRUE_TEXT); expect(element.find(CONST.SWITCH_RIGHT_SELECTOR).html()).toBe(CONST.DEFAULT_FALSE_TEXT); scope.on = 'Yay'; scope.off = 'Nay'; scope.$apply(); expect(element.find(CONST.SWITCH_LEFT_SELECTOR).html()).toBe('Yay'); expect(element.find(CONST.SWITCH_RIGHT_SELECTOR).html()).toBe('Nay'); }; } it('should change the switch labels', inject(makeTestChangeLabel())); it('should change the switch labels (input)', inject(makeTestChangeLabel(true))); // Test the middle label change function makeTestChangeMiddleLabel(input) { return function () { var element = compileDirective('label', input); expect(element.find(CONST.LABEL_SELECTOR).html()).toBe(' '); scope.label = 'XYZ'; scope.$apply(); expect(element.find(CONST.LABEL_SELECTOR).html()).toBe('XYZ'); }; } it('should change the switch middle label', inject(makeTestChangeMiddleLabel())); it('should change the switch middle label (input)', inject(makeTestChangeMiddleLabel(true))); // Test the middle icon change function makeTestChangeMiddleIcon(input) { return function () { var element = compileDirective('icon', input); expect(element.find(CONST.ICON_SELECTOR).hasClass('icon-youtube')).toBeTruthy(); scope.icon = 'icon-fullscreen'; scope.$apply(); expect(element.find(CONST.ICON_SELECTOR).hasClass('icon-youtube')).toBeFalsy(); expect(element.find(CONST.ICON_SELECTOR).hasClass('icon-fullscreen')).toBe(true); }; } it('should change the switch middle icon', inject(makeTestChangeMiddleIcon())); it('should change the switch middle icon (input)', inject(makeTestChangeMiddleIcon(true))); // Test the animation deactivation and reactivation function makeTestAnimation(input) { return function () { jasmine.clock().install(); var element = compileDirective('animation', input); jasmine.clock().tick(50); expect(element.hasClass(CONST.SWITCH_ANIMATED_CLASS)).toBeTruthy(); scope.animate = false; scope.$apply(); jasmine.clock().tick(50); expect(element.hasClass(CONST.SWITCH_ANIMATED_CLASS)).toBeFalsy(); scope.animate = true; scope.$apply(); jasmine.clock().tick(50); expect(element.hasClass(CONST.SWITCH_ANIMATED_CLASS)).toBeTruthy(); jasmine.clock().uninstall(); }; } it('should change the switch animation mode', inject(makeTestAnimation())); it('should change the switch animation mode (input)', inject(makeTestAnimation(true))); // Test the custom class modifiers function makeTestClassModifiers(input) { return function () { var element = compileDirective('modifier', input); expect(element.hasClass(CONST.SWITCH_WRAPPER_CLASS)).toBeTruthy(); scope.modifier = 'flat-switch'; scope.$apply(); expect(element.hasClass(CONST.SWITCH_WRAPPER_CLASS)).toBeFalsy(); expect(element.hasClass('bootstrap-switch-flat-switch')).toBeTruthy(); scope.modifier = ''; scope.$apply(); expect(element.hasClass(CONST.SWITCH_WRAPPER_CLASS)).toBeTruthy(); scope.modifier = undefined; scope.$apply(); expect(element.hasClass(CONST.SWITCH_WRAPPER_CLASS)).toBeTruthy(); }; } it('should change the custom wrapper class', inject(makeTestClassModifiers())); it('should change the custom wrapper class (input)', inject(makeTestClassModifiers(true))); // Test the non-replacement if already an input element given // to ensure IE8 compatibility function makeTestReplacement(useInputElement) { return function () { var beforeCompile, afterCompile, content, template = templates['default']; angular.extend(scope, template.scope); content = buildElement(template, useInputElement); beforeCompile = angular.element(content).appendTo($sandbox); $compile(beforeCompile)(scope); afterCompile = $sandbox.find('input'); scope.$apply(); expect(beforeCompile.length).toBe(1); expect(afterCompile.length).toBe(1); expect(beforeCompile[0] === afterCompile[0]).toBe(true); }; } it('should replace non-input elements', inject(makeTestReplacement())); it('should not replace input elements', inject(makeTestReplacement(true))); // Test the custom true/false values function makeTestCustomValues(input) { return function () { var element = compileDirective('customValues', input); expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeTruthy(); expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeFalsy(); scope.model = 'yep'; scope.$apply(); expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeFalsy(); expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeTruthy(); }; } it('should use "yep" and "nope" instead of true and false', inject(makeTestCustomValues())); it('should use "yep" and "nope" instead of true and false (input)', inject(makeTestCustomValues(true))); // Test the custom true/false values as generic objects function makeTestCustomObjectsValues(input) { return function () { var element = compileDirective('customObjectsValues', input); expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeTruthy(); expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeFalsy(); scope.model = 0; scope.$apply(); expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeFalsy(); expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeTruthy(); }; } it('should use 0 and 1 instead of true and false', inject(makeTestCustomObjectsValues())); it('should use 0 and 1 instead of true and false (input)', inject(makeTestCustomObjectsValues(true))); // Test the inverse default option function makeTestInverseUndefined(input) { return function () { var element = compileDirective('inverse', input); expect(element.hasClass(CONST.SWITCH_INVERSE_CLASS)).toBeFalsy(); var children = element.find('.' + CONST.SWITCH_CONTAINER_CLASS).find('*[class^=\'bootstrap-switch-handle-\']'); expect(children.length).toBe(2); expect(angular.element(children[0]).hasClass(CONST.SWITCH_LEFT_SELECTOR.substr(1))).toBeTruthy(); expect(angular.element(children[1]).hasClass(CONST.SWITCH_RIGHT_SELECTOR.substr(1))).toBeTruthy(); }; } it('should default to inverse false when not defined', inject(makeTestInverseUndefined())); it('should default to inverse false when not defined (input)', inject(makeTestInverseUndefined(true))); // Test the inverse option function makeTestInverse(input) { return function () { var element = compileDirective('inverse', input); expect(element.hasClass(CONST.SWITCH_INVERSE_CLASS)).toBeFalsy(); // invert scope.inverse = true; scope.$apply(); expect(element.hasClass(CONST.SWITCH_INVERSE_CLASS)).toBeTruthy(); var children = element.find('.' + CONST.SWITCH_CONTAINER_CLASS).find('*[class^=\'bootstrap-switch-handle-\']'); expect(children.length).toBe(2); expect(angular.element(children[1]).hasClass(CONST.SWITCH_LEFT_SELECTOR.substr(1))).toBeTruthy(); expect(angular.element(children[0]).hasClass(CONST.SWITCH_RIGHT_SELECTOR.substr(1))).toBeTruthy(); // reset scope.inverse = false; scope.$apply(); expect(element.hasClass(CONST.SWITCH_INVERSE_CLASS)).toBeFalsy(); children = element.find('.' + CONST.SWITCH_CONTAINER_CLASS).find('*[class^=\'bootstrap-switch-handle-\']'); expect(children.length).toBe(2); expect(angular.element(children[0]).hasClass(CONST.SWITCH_LEFT_SELECTOR.substr(1))).toBeTruthy(); expect(angular.element(children[1]).hasClass(CONST.SWITCH_RIGHT_SELECTOR.substr(1))).toBeTruthy(); }; } it('should invert the on and off switches and then reset them', inject(makeTestInverse())); it('should invert the on and off switches and then reset them (input)', inject(makeTestInverse(true))); // Test the getterSetter ng-model option function makeTestGetterSetter(input) { return function () { var element = compileDirective('getterSetter', input); var localValue = false; scope.modelGetterSetter = function() { return localValue; }; scope.$apply(); expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeTruthy(); expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeFalsy(); localValue = true; scope.$apply(); expect(element.hasClass(CONST.SWITCH_OFF_CLASS)).toBeFalsy(); expect(element.hasClass(CONST.SWITCH_ON_CLASS)).toBeTruthy(); }; } it('should watch updates in getterSetter', inject(makeTestGetterSetter())); it('should watch updates in getterSetter', inject(makeTestGetterSetter(true))); function makeTestViewNgChange(input) { return function () { var element = compileDirective('ngChange', input); scope.ngChange = jasmine.createSpy(); // On - model change scope.model = true; scope.$apply(); expect(scope.ngChange).not.toHaveBeenCalled(); // Indeterminate - model change scope.model = undefined; scope.$apply(); expect(scope.ngChange).not.toHaveBeenCalled(); // Off - view change element.find('input').click(); expect(scope.ngChange).toHaveBeenCalled(); scope.ngChange.calls.reset(); // On - view change element.find('input').click(); expect(scope.ngChange).toHaveBeenCalled(); }; } it('should evaluate ngChange expression only when view changes', inject(makeTestViewNgChange())); it('should evaluate ngChange expression only when view changes', inject(makeTestViewNgChange(true))); function makeTestModelSwitchChange(input) { return function () { var element = compileDirective('change', input); scope.switchChange = jasmine.createSpy(); // On - model change scope.model = true; scope.$apply(); expect(scope.switchChange).toHaveBeenCalled(); scope.switchChange.calls.reset(); // Off - view change element.find('input').click(); expect(scope.switchChange).toHaveBeenCalled(); }; } 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))); });