├── tests
├── helpers
│ └── .gitkeep
├── unit
│ └── .gitkeep
├── dummy
│ ├── app
│ │ ├── styles
│ │ │ └── app.css
│ │ ├── resolver.js
│ │ ├── locales
│ │ │ ├── es
│ │ │ │ └── translations.js
│ │ │ └── fr
│ │ │ │ └── translations.js
│ │ ├── controllers
│ │ │ ├── primitives-test.js
│ │ │ ├── plain-objects-test.js
│ │ │ ├── long-option-list-test.js
│ │ │ ├── block-params-test.js
│ │ │ ├── immutability-test.js
│ │ │ └── ember-objects-test.js
│ │ ├── templates
│ │ │ ├── primitives-test.hbs
│ │ │ ├── long-option-list-test.hbs
│ │ │ ├── plain-objects-test.hbs
│ │ │ ├── application.hbs
│ │ │ ├── block-params-test.hbs
│ │ │ ├── ember-objects-test.hbs
│ │ │ └── immutability-test.hbs
│ │ ├── app.js
│ │ ├── router.js
│ │ └── index.html
│ ├── public
│ │ ├── robots.txt
│ │ └── crossdomain.xml
│ └── config
│ │ ├── optional-features.json
│ │ ├── ember-cli-update.json
│ │ ├── targets.js
│ │ └── environment.js
├── .eslintrc.js
├── test-helper.js
├── index.html
└── integration
│ └── multiselect-checkboxes-test.js
├── .watchmanconfig
├── .bowerrc
├── .prettierrc.js
├── .template-lintrc.js
├── index.js
├── config
├── environment.js
└── ember-try.js
├── app
└── components
│ └── multiselect-checkboxes.js
├── .ember-cli
├── .prettierignore
├── .eslintignore
├── addon
├── templates
│ └── components
│ │ └── multiselect-checkboxes.hbs
└── components
│ └── multiselect-checkboxes.js
├── .editorconfig
├── .gitignore
├── .npmignore
├── testem.js
├── CONTRIBUTING.md
├── ember-cli-build.js
├── LICENSE.md
├── .eslintrc.js
├── .travis.yml
├── package.json
├── README.md
└── CHANGELOG.md
/tests/helpers/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/unit/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {
2 | "ignore_dirs": ["tmp", "dist"]
3 | }
4 |
--------------------------------------------------------------------------------
/tests/dummy/app/styles/app.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | margin: 20px;
3 | }
4 |
--------------------------------------------------------------------------------
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "bower_components",
3 | "analytics": false
4 | }
5 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | singleQuote: true,
5 | };
6 |
--------------------------------------------------------------------------------
/tests/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | embertest: true
4 | }
5 | };
6 |
--------------------------------------------------------------------------------
/tests/dummy/public/robots.txt:
--------------------------------------------------------------------------------
1 | # http://www.robotstxt.org
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/.template-lintrc.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | extends: 'recommended',
5 | };
6 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | name: require('./package').name,
5 | };
6 |
--------------------------------------------------------------------------------
/tests/dummy/app/resolver.js:
--------------------------------------------------------------------------------
1 | import Resolver from 'ember-resolver';
2 |
3 | export default Resolver;
4 |
--------------------------------------------------------------------------------
/config/environment.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function (/* environment, appConfig */) {
4 | return {};
5 | };
6 |
--------------------------------------------------------------------------------
/app/components/multiselect-checkboxes.js:
--------------------------------------------------------------------------------
1 | export { default } from 'ember-multiselect-checkboxes/components/multiselect-checkboxes';
2 |
--------------------------------------------------------------------------------
/tests/dummy/app/locales/es/translations.js:
--------------------------------------------------------------------------------
1 | export default {
2 | "Lisa": "Luisa",
3 | "Bob": "Roberto",
4 | "John": "Juan"
5 | };
6 |
--------------------------------------------------------------------------------
/tests/dummy/app/locales/fr/translations.js:
--------------------------------------------------------------------------------
1 | export default {
2 | "Lisa": "Louise",
3 | "Bob": "Robert",
4 | "John": "Jean"
5 | };
6 |
--------------------------------------------------------------------------------
/tests/dummy/config/optional-features.json:
--------------------------------------------------------------------------------
1 | {
2 | "application-template-wrapper": false,
3 | "default-async-observers": true,
4 | "jquery-integration": false,
5 | "template-only-glimmer-components": true
6 | }
7 |
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/primitives-test.js:
--------------------------------------------------------------------------------
1 | import Controller from '@ember/controller';
2 |
3 | export default Controller.extend({
4 | init() {
5 | this._super(...arguments);
6 | this.fruits = ["apple", "banana", "orange"];
7 | this.selectedFruits = [];
8 | }
9 | });
10 |
--------------------------------------------------------------------------------
/.ember-cli:
--------------------------------------------------------------------------------
1 | {
2 | /**
3 | Ember CLI sends analytics information by default. The data is completely
4 | anonymous, but there are times when you might want to disable this behavior.
5 |
6 | Setting `disableAnalytics` to true will prevent any data from being sent.
7 | */
8 | "disableAnalytics": false
9 | }
10 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/primitives-test.hbs:
--------------------------------------------------------------------------------
1 |
Test with array of primitives
2 |
3 |
4 |
5 | Selected fruits:
6 |
7 | {{#each this.selectedFruits as |fruit|}}
8 | {{fruit}}
9 | {{/each}}
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # unconventional js
2 | /blueprints/*/files/
3 | /vendor/
4 |
5 | # compiled output
6 | /dist/
7 | /tmp/
8 |
9 | # dependencies
10 | /bower_components/
11 | /node_modules/
12 |
13 | # misc
14 | /coverage/
15 | !.*
16 | .eslintcache
17 |
18 | # ember-try
19 | /.node_modules.ember-try/
20 | /bower.json.ember-try
21 | /package.json.ember-try
22 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | # unconventional js
2 | /blueprints/*/files/
3 | /vendor/
4 |
5 | # compiled output
6 | /dist/
7 | /tmp/
8 |
9 | # dependencies
10 | /bower_components/
11 | /node_modules/
12 |
13 | # misc
14 | /coverage/
15 | !.*
16 | .*/
17 | .eslintcache
18 |
19 | # ember-try
20 | /.node_modules.ember-try/
21 | /bower.json.ember-try
22 | /package.json.ember-try
23 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/long-option-list-test.hbs:
--------------------------------------------------------------------------------
1 | Test with long option list
2 |
3 | Selected options:
4 |
5 | {{#each this.selectedOptions as |selectedOption|}}
6 | {{selectedOption}}
7 | {{/each}}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/tests/test-helper.js:
--------------------------------------------------------------------------------
1 | import Application from 'dummy/app';
2 | import config from 'dummy/config/environment';
3 | import * as QUnit from 'qunit';
4 | import { setApplication } from '@ember/test-helpers';
5 | import { setup } from 'qunit-dom';
6 | import { start } from 'ember-qunit';
7 |
8 | setApplication(Application.create(config.APP));
9 |
10 | setup(QUnit.assert);
11 |
12 | start();
13 |
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/plain-objects-test.js:
--------------------------------------------------------------------------------
1 | import Controller from '@ember/controller';
2 |
3 | export default Controller.extend({
4 | init() {
5 | this._super(...arguments);
6 | this.cars = [
7 | { make: "BMW", color: "black"},
8 | { make: "Ferari", color: "red"},
9 | { make: "Volvo", color: "blue"}
10 | ];
11 | this.selectedCars = [];
12 | }
13 | });
14 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/plain-objects-test.hbs:
--------------------------------------------------------------------------------
1 | Test with array of plain js objects
2 |
3 |
4 |
5 | Selected cars:
6 |
7 | {{#each this.selectedCars as |car|}}
8 | make: {{car.make}}, color: {{car.color}}
9 | {{/each}}
10 |
11 |
12 |
--------------------------------------------------------------------------------
/addon/templates/components/multiselect-checkboxes.hbs:
--------------------------------------------------------------------------------
1 | {{#each this.checkboxes as |checkbox index|}}
2 | {{#if (has-block)}}
3 | {{yield checkbox.option checkbox.isSelected index}}
4 | {{else}}
5 |
6 |
7 |
8 | {{checkbox.label}}
9 |
10 |
11 | {{/if}}
12 | {{/each}}
13 |
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/long-option-list-test.js:
--------------------------------------------------------------------------------
1 | import Controller from '@ember/controller';
2 | import { A } from '@ember/array';
3 |
4 | let options = [];
5 |
6 | for (let i = 0; i < 1000; i++) {
7 | options.push(i);
8 | }
9 |
10 | export default Controller.extend({
11 | init() {
12 | this._super(...arguments);
13 | this.options = A(options);
14 | this.selectedOptions = A();
15 | }
16 | });
17 |
--------------------------------------------------------------------------------
/tests/dummy/app/app.js:
--------------------------------------------------------------------------------
1 | import Application from '@ember/application';
2 | import Resolver from 'ember-resolver';
3 | import loadInitializers from 'ember-load-initializers';
4 | import config from 'dummy/config/environment';
5 |
6 | export default class App extends Application {
7 | modulePrefix = config.modulePrefix;
8 | podModulePrefix = config.podModulePrefix;
9 | Resolver = Resolver;
10 | }
11 |
12 | loadInitializers(App, config.modulePrefix);
13 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 | end_of_line = lf
9 | charset = utf-8
10 | trim_trailing_whitespace = true
11 | insert_final_newline = true
12 | indent_style = space
13 | indent_size = 2
14 |
15 | [*.hbs]
16 | insert_final_newline = false
17 |
18 | [*.{diff,md}]
19 | trim_trailing_whitespace = false
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist/
5 | /tmp/
6 |
7 | # dependencies
8 | /bower_components/
9 | /node_modules/
10 |
11 | # misc
12 | /.env*
13 | /.pnp*
14 | /.sass-cache
15 | /.eslintcache
16 | /connect.lock
17 | /coverage/
18 | /libpeerconnection.log
19 | /npm-debug.log*
20 | /testem.log
21 | /yarn-error.log
22 |
23 | # ember-try
24 | /.node_modules.ember-try/
25 | /bower.json.ember-try
26 | /package.json.ember-try
27 |
--------------------------------------------------------------------------------
/tests/dummy/config/ember-cli-update.json:
--------------------------------------------------------------------------------
1 | {
2 | "schemaVersion": "1.0.0",
3 | "packages": [
4 | {
5 | "name": "ember-cli",
6 | "version": "3.28.6",
7 | "blueprints": [
8 | {
9 | "name": "addon",
10 | "outputRepo": "https://github.com/ember-cli/ember-addon-output",
11 | "codemodsSource": "ember-addon-codemods-manifest@1",
12 | "isBaseBlueprint": true,
13 | "options": [
14 | "--welcome"
15 | ]
16 | }
17 | ]
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/application.hbs:
--------------------------------------------------------------------------------
1 |
2 | Ember multiselect-checkboxes tests
3 |
4 | {{#link-to 'primitives-test'}}Primivites test{{/link-to}} |
5 | {{#link-to 'plain-objects-test'}}Plain JS objects test{{/link-to}} |
6 | {{#link-to 'ember-objects-test'}}Ember objects test{{/link-to}} |
7 | {{#link-to 'block-params-test'}}Block params test{{/link-to}} |
8 | {{#link-to 'long-option-list-test'}}Long option list test{{/link-to}}
9 | {{#link-to 'immutability-test'}}Immutability test{{/link-to}}
10 |
11 |
12 | {{outlet}}
13 |
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/block-params-test.js:
--------------------------------------------------------------------------------
1 | import EmberObject from '@ember/object';
2 | import Controller from '@ember/controller';
3 |
4 | var Person = EmberObject.extend({
5 | name: null,
6 |
7 | gender: null
8 | });
9 |
10 | export default Controller.extend({
11 | init() {
12 | this._super(...arguments);
13 | this.persons = [
14 | Person.create({ name: "Lisa", gender: "Female" }),
15 | Person.create({ name: "Bob", gender: "Male" }),
16 | Person.create({ name: "John", gender: "Male"})
17 | ];
18 | this.selectedPersons = [];
19 | }
20 | });
21 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/block-params-test.hbs:
--------------------------------------------------------------------------------
1 | Block Params Test
2 |
3 |
4 |
5 |
6 |
7 | {{index}} --{{person.name}}--
8 |
9 |
10 |
11 |
12 | Selected persons:
13 |
14 | {{#each this.selectedPersons as |person|}}
15 | name: {{person.name}}, gender: {{person.gender}}
16 | {{/each}}
17 |
18 |
19 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # compiled output
2 | /dist/
3 | /tmp/
4 |
5 | # dependencies
6 | /bower_components/
7 |
8 | # misc
9 | /.bowerrc
10 | /.editorconfig
11 | /.ember-cli
12 | /.env*
13 | /.eslintcache
14 | /.eslintignore
15 | /.eslintrc.js
16 | /.git/
17 | /.gitignore
18 | /.prettierignore
19 | /.prettierrc.js
20 | /.template-lintrc.js
21 | /.travis.yml
22 | /.watchmanconfig
23 | /bower.json
24 | /config/ember-try.js
25 | /CONTRIBUTING.md
26 | /ember-cli-build.js
27 | /testem.js
28 | /tests/
29 | /yarn-error.log
30 | /yarn.lock
31 | .gitkeep
32 |
33 | # ember-try
34 | /.node_modules.ember-try/
35 | /bower.json.ember-try
36 | /package.json.ember-try
37 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/ember-objects-test.hbs:
--------------------------------------------------------------------------------
1 | Test with array of Ember objects
2 |
3 |
4 |
5 |
6 | Select all
7 | Clear all
8 | Toggle Enabled/Disabled
9 |
10 |
11 | Selected persons:
12 |
13 | {{#each this.selectedPersons as |person|}}
14 | name: {{person.name}}, gender: {{person.gender}}
15 | {{/each}}
16 |
17 |
18 |
--------------------------------------------------------------------------------
/tests/dummy/public/crossdomain.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
15 |
16 |
--------------------------------------------------------------------------------
/testem.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | test_page: 'tests/index.html?hidepassed',
5 | disable_watching: true,
6 | launch_in_ci: ['Chrome'],
7 | launch_in_dev: ['Chrome'],
8 | browser_start_timeout: 120,
9 | browser_args: {
10 | Chrome: {
11 | ci: [
12 | // --no-sandbox is needed when running Chrome inside a container
13 | process.env.CI ? '--no-sandbox' : null,
14 | '--headless',
15 | '--disable-dev-shm-usage',
16 | '--disable-software-rasterizer',
17 | '--mute-audio',
18 | '--remote-debugging-port=0',
19 | '--window-size=1440,900',
20 | ].filter(Boolean),
21 | },
22 | },
23 | };
24 |
--------------------------------------------------------------------------------
/tests/dummy/app/router.js:
--------------------------------------------------------------------------------
1 | import EmberRouter from '@ember/routing/router';
2 | import config from 'dummy/config/environment';
3 |
4 | export default class Router extends EmberRouter {
5 | location = config.locationType;
6 | rootURL = config.rootURL;
7 | }
8 |
9 | Router.map(function () {
10 | this.route('primitives-test', { path: '/primitives-test' });
11 | this.route('plain-objects-test', { path: '/plain-objects-test' });
12 | this.route('ember-objects-test', { path: '/ember-objects-test' });
13 | this.route('block-params-test', { path: '/block-params-test' });
14 | this.route('long-option-list-test', { path: '/long-option-list-test' });
15 | this.route('immutability-test', { path: '/immutability-test' });
16 | });
17 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How To Contribute
2 |
3 | ## Installation
4 |
5 | * `git clone `
6 | * `cd ember-multiselect-checkboxes`
7 | * `npm install`
8 |
9 | ## Linting
10 |
11 | * `npm run lint`
12 | * `npm run lint:fix`
13 |
14 | ## Running tests
15 |
16 | * `ember test` – Runs the test suite on the current Ember version
17 | * `ember test --server` – Runs the test suite in "watch mode"
18 | * `ember try:each` – Runs the test suite against multiple Ember versions
19 |
20 | ## Running the dummy application
21 |
22 | * `ember serve`
23 | * Visit the dummy application at [http://localhost:4200](http://localhost:4200).
24 |
25 | For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/).
26 |
--------------------------------------------------------------------------------
/ember-cli-build.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const EmberAddon = require('ember-cli/lib/broccoli/ember-addon');
4 |
5 | module.exports = function (defaults) {
6 | let app = new EmberAddon(defaults, {
7 | // Add options here
8 | });
9 |
10 | /*
11 | This build file specifies the options for the dummy test app of this
12 | addon, located in `/tests/dummy`
13 | This build file does *not* influence how the addon or the app using it
14 | behave. You most likely want to be modifying `./index.js` or app's build file
15 | */
16 |
17 | const { maybeEmbroider } = require('@embroider/test-setup');
18 | return maybeEmbroider(app, {
19 | skipBabel: [
20 | {
21 | package: 'qunit',
22 | },
23 | ],
24 | });
25 | };
26 |
--------------------------------------------------------------------------------
/tests/dummy/config/targets.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const browsers = [
4 | 'last 1 Chrome versions',
5 | 'last 1 Firefox versions',
6 | 'last 1 Safari versions',
7 | ];
8 |
9 | // Ember's browser support policy is changing, and IE11 support will end in
10 | // v4.0 onwards.
11 | //
12 | // See https://deprecations.emberjs.com/v3.x#toc_3-0-browser-support-policy
13 | //
14 | // If you need IE11 support on a version of Ember that still offers support
15 | // for it, uncomment the code block below.
16 | //
17 | // const isCI = Boolean(process.env.CI);
18 | // const isProduction = process.env.EMBER_ENV === 'production';
19 | //
20 | // if (isCI || isProduction) {
21 | // browsers.push('ie 11');
22 | // }
23 |
24 | module.exports = {
25 | browsers,
26 | };
27 |
--------------------------------------------------------------------------------
/tests/dummy/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Dummy
7 |
8 |
9 |
10 | {{content-for "head"}}
11 |
12 |
13 |
14 |
15 | {{content-for "head-footer"}}
16 |
17 |
18 | {{content-for "body"}}
19 |
20 |
21 |
22 |
23 | {{content-for "body-footer"}}
24 |
25 |
26 |
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/immutability-test.js:
--------------------------------------------------------------------------------
1 | import EmberObject from '@ember/object';
2 | import Controller from '@ember/controller';
3 | import { A } from '@ember/array';
4 |
5 | let Person = EmberObject.extend({
6 | name: null,
7 |
8 | gender: null
9 | });
10 |
11 | export default Controller.extend({
12 | init() {
13 | this._super(...arguments);
14 | this.persons = A([
15 | Person.create({ name: "Lisa", gender: "Female" }),
16 | Person.create({ name: "Bob", gender: "Male" }),
17 | Person.create({ name: "John", gender: "Male"})
18 | ]);
19 | this.activeSelection = A();
20 | this.selectionHistory = A();
21 | },
22 |
23 | actions: {
24 | updateSelection: function (newSelection) {
25 | this.get('selectionHistory').addObject(newSelection);
26 | this.set('activeSelection', newSelection);
27 | }
28 | }
29 | });
30 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/immutability-test.hbs:
--------------------------------------------------------------------------------
1 | Test with an immutable array of Ember objects
2 |
3 |
9 |
10 | Active selection:
11 |
12 | {{#each this.activeSelection as |person|}}
13 | name: {{person.name}}, gender: {{person.gender}}
14 | {{/each}}
15 |
16 |
17 |
18 | Selection history:
19 |
20 | {{#each this.selectionHistory as |selectionState|}}
21 |
22 |
23 | {{#each selectionState as |person|}}
24 | name: {{person.name}}, gender: {{person.gender}}
25 | {{/each}}
26 |
27 |
28 | {{/each}}
29 |
30 |
31 |
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/ember-objects-test.js:
--------------------------------------------------------------------------------
1 | import EmberObject from '@ember/object';
2 | import Controller from '@ember/controller';
3 |
4 | var Person = EmberObject.extend({
5 | name: null,
6 |
7 | gender: null
8 | });
9 |
10 | export default Controller.extend({
11 | init() {
12 | this._super(...arguments);
13 | this.persons = [
14 | Person.create({ name: "Lisa", gender: "Female" }),
15 | Person.create({ name: "Bob", gender: "Male" }),
16 | Person.create({ name: "John", gender: "Male"})
17 | ];
18 | this.selectedPersons = [];
19 | },
20 |
21 | personsDisabled: false,
22 |
23 | actions: {
24 | selectAllPersons: function() {
25 | this.set("selectedPersons", this.get('persons').slice());
26 | },
27 |
28 | clearPersons: function() {
29 | this.set("selectedPersons", []);
30 | },
31 |
32 | toggleDisabled: function() {
33 | this.toggleProperty("personsDisabled");
34 | }
35 | }
36 | });
37 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # The MIT License (MIT)
2 |
3 | Copyright (c) 2014 R.S. Schermer
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | root: true,
5 | parser: 'babel-eslint',
6 | parserOptions: {
7 | ecmaVersion: 2018,
8 | sourceType: 'module',
9 | ecmaFeatures: {
10 | legacyDecorators: true,
11 | },
12 | },
13 | plugins: ['ember'],
14 | extends: [
15 | 'eslint:recommended',
16 | 'plugin:ember/recommended',
17 | 'plugin:prettier/recommended',
18 | ],
19 | env: {
20 | browser: true,
21 | },
22 | rules: {},
23 | overrides: [
24 | // node files
25 | {
26 | files: [
27 | './.eslintrc.js',
28 | './.prettierrc.js',
29 | './.template-lintrc.js',
30 | './ember-cli-build.js',
31 | './index.js',
32 | './testem.js',
33 | './blueprints/*/index.js',
34 | './config/**/*.js',
35 | './tests/dummy/config/**/*.js',
36 | ],
37 | parserOptions: {
38 | sourceType: 'script',
39 | },
40 | env: {
41 | browser: false,
42 | node: true,
43 | },
44 | plugins: ['node'],
45 | extends: ['plugin:node/recommended'],
46 | },
47 | {
48 | // Test files:
49 | files: ['tests/**/*-test.{js,ts}'],
50 | extends: ['plugin:qunit/recommended'],
51 | },
52 | ],
53 | };
54 |
--------------------------------------------------------------------------------
/tests/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Dummy Tests
7 |
8 |
9 |
10 | {{content-for "head"}}
11 | {{content-for "test-head"}}
12 |
13 |
14 |
15 |
16 |
17 | {{content-for "head-footer"}}
18 | {{content-for "test-head-footer"}}
19 |
20 |
21 | {{content-for "body"}}
22 | {{content-for "test-body"}}
23 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | {{content-for "body-footer"}}
38 | {{content-for "test-body-footer"}}
39 |
40 |
41 |
--------------------------------------------------------------------------------
/tests/dummy/config/environment.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function(environment) {
4 | let ENV = {
5 | modulePrefix: 'dummy',
6 | environment,
7 | rootURL: '/',
8 | locationType: 'auto',
9 | i18n: {
10 | defaultLocale: 'es'
11 | },
12 | EmberENV: {
13 | FEATURES: {
14 | // Here you can enable experimental features on an ember canary build
15 | // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true
16 | },
17 | EXTEND_PROTOTYPES: {
18 | // Prevent Ember Data from overriding Date.parse.
19 | Date: false,
20 | }
21 | },
22 |
23 | APP: {
24 | // Here you can pass flags/options to your application instance
25 | // when it is created
26 | },
27 | };
28 |
29 | if (environment === 'development') {
30 | // ENV.APP.LOG_RESOLVER = true;
31 | // ENV.APP.LOG_ACTIVE_GENERATION = true;
32 | // ENV.APP.LOG_TRANSITIONS = true;
33 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
34 | // ENV.APP.LOG_VIEW_LOOKUPS = true;
35 | }
36 |
37 | if (environment === 'test') {
38 | // Testem prefers this...
39 | ENV.locationType = 'none';
40 |
41 | // keep test console output quieter
42 | ENV.APP.LOG_ACTIVE_GENERATION = false;
43 | ENV.APP.LOG_VIEW_LOOKUPS = false;
44 |
45 | ENV.APP.rootElement = '#ember-testing';
46 | ENV.APP.autoboot = false;
47 | }
48 |
49 | if (environment === 'production') {
50 | // here you can enable a production-specific feature
51 | }
52 |
53 | return ENV;
54 | };
55 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | ---
2 | language: node_js
3 | node_js:
4 | # we recommend testing addons with the same minimum supported node version as Ember CLI
5 | # so that your addon works for all apps
6 | - "12"
7 |
8 | dist: xenial
9 |
10 | addons:
11 | chrome: stable
12 |
13 | cache:
14 | directories:
15 | - $HOME/.npm
16 |
17 | env:
18 | global:
19 | # See https://git.io/vdao3 for details.
20 | - JOBS=1
21 |
22 | branches:
23 | only:
24 | - master
25 | # npm version tags
26 | - /^v\d+\.\d+\.\d+/
27 |
28 | jobs:
29 | fast_finish: true
30 | allow_failures:
31 | - env: EMBER_TRY_SCENARIO=ember-canary
32 |
33 | include:
34 | # runs linting and tests with current locked deps
35 | - stage: "Tests"
36 | name: "Tests"
37 | script:
38 | - npm run lint
39 | - npm run test:ember
40 |
41 | - stage: "Additional Tests"
42 | name: "Floating Dependencies"
43 | install:
44 | - npm install --no-package-lock
45 | script:
46 | - npm run test:ember
47 |
48 | # we recommend new addons test the current and previous LTS
49 | # as well as latest stable release (bonus points to beta/canary)
50 | - env: EMBER_TRY_SCENARIO=ember-lts-3.24
51 | - env: EMBER_TRY_SCENARIO=ember-lts-3.28
52 | - env: EMBER_TRY_SCENARIO=ember-lts-3.28-with-i18n
53 | - env: EMBER_TRY_SCENARIO=ember-release
54 | - env: EMBER_TRY_SCENARIO=ember-beta
55 | - env: EMBER_TRY_SCENARIO=ember-canary
56 | - env: EMBER_TRY_SCENARIO=ember-default-with-jquery
57 | - env: EMBER_TRY_SCENARIO=ember-classic
58 | - env: EMBER_TRY_SCENARIO=embroider-safe
59 | - env: EMBER_TRY_SCENARIO=embroider-optimized
60 |
61 | script:
62 | - node_modules/.bin/ember try:one $EMBER_TRY_SCENARIO
63 |
--------------------------------------------------------------------------------
/config/ember-try.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const getChannelURL = require('ember-source-channel-url');
4 | const { embroiderSafe, embroiderOptimized } = require('@embroider/test-setup');
5 |
6 | module.exports = async function () {
7 | return {
8 | scenarios: [
9 | {
10 | name: 'ember-lts-3.24',
11 | npm: {
12 | devDependencies: {
13 | 'ember-source': '~3.24.3',
14 | },
15 | },
16 | },
17 | {
18 | name: 'ember-lts-3.28',
19 | npm: {
20 | devDependencies: {
21 | 'ember-source': '~3.28.0',
22 | },
23 | },
24 | },
25 | {
26 | name: 'ember-lts-3.28-with-i18n',
27 | npm: {
28 | devDependencies: {
29 | 'ember-source': '~3.28.0',
30 | 'ember-i18n': '5.3.1',
31 | },
32 | },
33 | },
34 | {
35 | name: 'ember-release',
36 | npm: {
37 | devDependencies: {
38 | 'ember-source': await getChannelURL('release'),
39 | },
40 | },
41 | },
42 | {
43 | name: 'ember-beta',
44 | npm: {
45 | devDependencies: {
46 | 'ember-source': await getChannelURL('beta'),
47 | },
48 | },
49 | },
50 | {
51 | name: 'ember-canary',
52 | npm: {
53 | devDependencies: {
54 | 'ember-source': await getChannelURL('canary'),
55 | },
56 | },
57 | },
58 | {
59 | name: 'ember-default-with-jquery',
60 | env: {
61 | EMBER_OPTIONAL_FEATURES: JSON.stringify({
62 | 'jquery-integration': true,
63 | }),
64 | },
65 | npm: {
66 | devDependencies: {
67 | '@ember/jquery': '^1.1.0',
68 | },
69 | },
70 | },
71 | {
72 | name: 'ember-classic',
73 | env: {
74 | EMBER_OPTIONAL_FEATURES: JSON.stringify({
75 | 'application-template-wrapper': true,
76 | 'default-async-observers': false,
77 | 'template-only-glimmer-components': false,
78 | }),
79 | },
80 | npm: {
81 | devDependencies: {
82 | 'ember-source': '~3.28.0',
83 | },
84 | ember: {
85 | edition: 'classic',
86 | },
87 | },
88 | },
89 | embroiderSafe(),
90 | embroiderOptimized(),
91 | ],
92 | };
93 | };
94 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ember-multiselect-checkboxes",
3 | "version": "0.12.0",
4 | "description": "Simple Ember component for allowing multiple selection from a certain collection (a hasMany property for example) using checkboxes.",
5 | "keywords": [
6 | "ember-addon",
7 | "ember-component",
8 | "multiselect-checkboxes",
9 | "multiselect",
10 | "checkboxes"
11 | ],
12 | "license": "MIT",
13 | "author": "Roland Schermer",
14 | "directories": {
15 | "doc": "doc",
16 | "test": "tests"
17 | },
18 | "repository": "https://github.com/rsschermer/ember-multiselect-checkboxes",
19 | "scripts": {
20 | "build": "ember build --environment=production",
21 | "lint": "npm-run-all --aggregate-output --continue-on-error --parallel \"lint:!(fix)\"",
22 | "lint:fix": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*:fix",
23 | "lint:hbs": "ember-template-lint .",
24 | "lint:hbs:fix": "ember-template-lint . --fix",
25 | "lint:js": "eslint . --cache",
26 | "lint:js:fix": "eslint . --fix",
27 | "start": "ember serve",
28 | "test": "npm-run-all lint test:*",
29 | "test:ember": "ember test",
30 | "test:ember-compatibility": "ember try:each"
31 | },
32 | "dependencies": {
33 | "ember-cli-babel": "^7.26.10",
34 | "ember-cli-htmlbars": "^5.7.2"
35 | },
36 | "devDependencies": {
37 | "@ember/jquery": "^2.0.0",
38 | "@ember/optional-features": "^2.0.0",
39 | "@ember/test-helpers": "^2.6.0",
40 | "@embroider/test-setup": "^0.48.1",
41 | "@glimmer/component": "^1.0.4",
42 | "@glimmer/tracking": "^1.0.4",
43 | "babel-eslint": "^10.1.0",
44 | "broccoli-asset-rev": "^3.0.0",
45 | "ember-auto-import": "^1.12.0",
46 | "ember-cli": "~3.28.6",
47 | "ember-cli-dependency-checker": "^3.2.0",
48 | "ember-cli-inject-live-reload": "^2.1.0",
49 | "ember-cli-sri": "^2.1.1",
50 | "ember-cli-terser": "^4.0.2",
51 | "ember-disable-prototype-extensions": "^1.1.3",
52 | "ember-export-application-global": "^2.0.1",
53 | "ember-load-initializers": "^2.1.2",
54 | "ember-maybe-import-regenerator": "^0.1.6",
55 | "ember-page-title": "^6.2.2",
56 | "ember-qunit": "^5.1.5",
57 | "ember-resolver": "^8.0.3",
58 | "ember-source": "https://s3.amazonaws.com/builds.emberjs.com/release/shas/e12dbe8fd866777275b633f1f108e09352d9509d.tgz",
59 | "ember-source-channel-url": "^3.0.0",
60 | "ember-template-lint": "^3.15.0",
61 | "ember-try": "^1.4.0",
62 | "ember-welcome-page": "^4.1.0",
63 | "eslint": "^7.32.0",
64 | "eslint-config-prettier": "^8.3.0",
65 | "eslint-plugin-ember": "^10.5.8",
66 | "eslint-plugin-node": "^11.1.0",
67 | "eslint-plugin-prettier": "^3.4.1",
68 | "eslint-plugin-qunit": "^6.2.0",
69 | "loader.js": "^4.7.0",
70 | "npm-run-all": "^4.1.5",
71 | "prettier": "^2.5.1",
72 | "qunit": "^2.17.2",
73 | "qunit-dom": "^1.6.0"
74 | },
75 | "engines": {
76 | "node": "12.* || 14.* || >= 16"
77 | },
78 | "ember": {
79 | "edition": "octane"
80 | },
81 | "ember-addon": {
82 | "configPath": "tests/dummy/config"
83 | },
84 | "bugs": {
85 | "url": "https://github.com/rsschermer/ember-multiselect-checkboxes/issues"
86 | },
87 | "homepage": "https://github.com/rsschermer/ember-multiselect-checkboxes"
88 | }
--------------------------------------------------------------------------------
/addon/components/multiselect-checkboxes.js:
--------------------------------------------------------------------------------
1 | import EmberObject from '@ember/object';
2 | import Component from '@ember/component';
3 | import { computed } from '@ember/object';
4 | import { A } from '@ember/array';
5 | import { getOwner } from '@ember/application';
6 | import layout from '../templates/components/multiselect-checkboxes';
7 |
8 | let Checkbox = EmberObject.extend({
9 | isSelected: computed('value', 'selection.[]', {
10 | get() {
11 | return this.get('selection').includes(this.get('value'));
12 | },
13 |
14 | set(_, checked) {
15 | let selection = this.get('selection');
16 | let selected = selection.includes(this.get('value'));
17 | let onchange = this.get('onchange');
18 | let updateSelectionValue = this.get('updateSelectionValue');
19 | let isMutable = typeof selection.addObject === 'function' && typeof selection.removeObject === 'function';
20 |
21 | // Dispatch onchange event to handler with updated selection if handler is specified
22 | if (onchange) {
23 | let updated = A(selection.slice());
24 | let operation;
25 |
26 | if (checked && !selected) {
27 | operation = 'added';
28 | updated.addObject(this.get('value'));
29 | } else if (!checked && selected) {
30 | operation = 'removed';
31 | updated.removeObject(this.get('value'));
32 | }
33 |
34 | onchange(updated, this.get('value'), operation);
35 | }
36 |
37 | // Mutate selection if updateSelectionValue is true and selection is mutable
38 | if (updateSelectionValue !== false && isMutable) {
39 | if (checked && !selected) {
40 | selection.addObject(this.get('value'));
41 | } else if (!checked && selected) {
42 | selection.removeObject(this.get('value'));
43 | }
44 |
45 | return checked;
46 | } else {
47 |
48 | // Only change the checked status of the checkbox when selection is mutated, because if
49 | // it is not mutated and the onchange handler does not update the bound selection value the
50 | // displayed checkboxes would be out of sync with bound selection value.
51 | return !checked;
52 | }
53 | }
54 | })
55 | });
56 |
57 | export default Component.extend({
58 | layout,
59 | classNames: ['multiselect-checkboxes'],
60 |
61 | tagName: 'ul',
62 |
63 | i18n: computed(function () {
64 | return getOwner(this).lookup('service:i18n');
65 | }),
66 |
67 | checkboxes: computed('options.[]', 'labelProperty', 'valueProperty', 'selection', 'translate', 'i18n.locale', function () {
68 | let labelProperty = this.get('labelProperty');
69 | let valueProperty = this.get('valueProperty');
70 | let selection = A(this.get('selection'));
71 | let onchange = this.get('onchange');
72 | let updateSelectionValue = this.get('updateSelectionValue') !== undefined ? this.get('updateSelectionValue') : true;
73 | let options = A(this.get('options'));
74 | let translate = this.get('translate');
75 |
76 | let checkboxes = options.map((option) => {
77 | let label, value;
78 |
79 | if (labelProperty) {
80 | if (typeof option.get === 'function') {
81 | label = option.get(labelProperty);
82 | } else {
83 | label = option[labelProperty];
84 | }
85 | } else {
86 | label = String(option);
87 | }
88 |
89 | if (translate && label && this.get('i18n')) {
90 | label = this.get('i18n').t(label);
91 | }
92 |
93 | if (valueProperty) {
94 | if (typeof option.get === 'function') {
95 | value = option.get(valueProperty);
96 | } else {
97 | value = option[valueProperty];
98 | }
99 | } else {
100 | value = option;
101 | }
102 |
103 | return Checkbox.create({
104 | option: option,
105 | label: label,
106 | value: value,
107 | selection: selection,
108 | onchange: onchange,
109 | updateSelectionValue: updateSelectionValue
110 | });
111 | });
112 |
113 | return A(checkboxes);
114 | })
115 | });
116 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ember-multiselect-checkboxes [](https://travis-ci.org/RSSchermer/ember-multiselect-checkboxes)
2 |
3 | Simple Ember component for allowing multiple selection from a certain collection (a `hasMany` property for example)
4 | using checkboxes.
5 |
6 | ## Demo
7 | Demo available [here](https://rsschermer.github.io/ember-multiselect-checkboxes/).
8 |
9 | ## Installation
10 |
11 | `ember install ember-multiselect-checkboxes`
12 |
13 | ## Usage
14 |
15 | Example:
16 |
17 | ``` handlebars
18 |
19 | ```
20 |
21 | This component can be used with an array of primitives as the options, an array of plain javascript objects as the
22 | options, or an array of Ember objects as the options. The following attributes should always be set:
23 |
24 | * `options`: a collection of Ember objects that can be selected.
25 | * `selection`: the subset of the options that is currently selected. The selection will automatically be updated when
26 | the user checks or unchecks options through Ember's two-way bindings.
27 |
28 | When using this component with an array of javascript objects or an array of Ember objects you should also set the
29 | `labelProperty` attribute:
30 |
31 | * `labelProperty`: the property on the plain javascript object or the Ember object that will be used as a label for the
32 | checkbox. By default this property will render as plain text. If translation is desired, set `translate` to true.
33 |
34 | ```handlebars
35 |
40 | ```
41 |
42 | When using this component with an array of javascript objects or an array of Ember objects you may optionally specify
43 | the `valueProperty` attribute:
44 |
45 | * `valueProperty`: the property on the plain javascript object or the Ember object that will be used to represent this
46 | object in the selection. Example: when using an array of car objects as the options, if you set the `valueProperty`
47 | as their "color" property, the selection will be an array of color strings (not an array of cars).
48 |
49 | [This controller for the demo application](https://github.com/RSSchermer/ember-multiselect-checkboxes/blob/gh-pages/demo-app/app/controllers/application.js)
50 | provides an example of what your controller code could look like for each type of options collection.
51 |
52 | An action can be bound to the `onchange` attribute:
53 |
54 | ```handlebars
55 |
60 | ```
61 |
62 | When a checkbox is checked or unchecked, this action will be triggered. The action handler will receive the following
63 | parameters:
64 |
65 | * `newSelection`: the subset of the options that is currently selected.
66 | * `value`: the corresponding value of the checkbox that was checked or unchecked.
67 | * `operation`: a string describing the operation performed on the selection. There are two possible values: 'added' when
68 | the value was added to the selection and 'removed' when the value was removed from the selection.
69 |
70 | ```js
71 | actions: {
72 | updateSelection: function (newSelection, value, operation) {
73 | ...
74 | }
75 | }
76 | ```
77 |
78 | By default, the component will update the value bound to the `selection` attribute automatically. If you prefer to
79 | update the value bound to the `selection` attribute yourself, this can be disabled by setting the `updateSelectionValue`
80 | attribute to `false`:
81 |
82 | ```handlebars
83 |
89 | ```
90 |
91 | You should then update the value bound to the `selection` property in the action bound to `onchange`, e.g.:
92 |
93 | ```js
94 | actions: {
95 | updateSelection: function (newSelection, value, operation) {
96 | this.set('selection', newSelection);
97 |
98 | ...
99 | }
100 | }
101 | ```
102 |
103 | Note that for long option lists, allowing the component to automatically update the value bound to the `selection`
104 | attribute may result in significantly better performance.
105 |
106 | It's also possible to pass a custom template block should you want to customize the option list in some way (requires
107 | Ember 1.13 or newer). This template block will receive 3 block parameters: the option itself, a boolean value indicating
108 | whether or not the option is selected, and the option's index:
109 |
110 | ```handlebars
111 |
112 |
113 |
114 | ```
115 |
116 | The initial example without a custom template block is essentially equivalent to the following example with a custom
117 | template block:
118 |
119 | ```handlebars
120 |
121 |
122 |
123 |
124 | {{user.name}}
125 |
126 |
127 |
128 | ```
129 |
130 | Note that the `labelProperty` attribute is superfluous when using a custom template block; instead, `{{user.name}}` is
131 | used directly in the template block.
132 |
133 | By default the `multiselect-checkboxes` tag will render as an `ul` element. This can be customized by specifying the
134 | `tagName` attribute:
135 |
136 | ```handlebars
137 |
138 | ...
139 |
140 | ```
141 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Ember-multiselect-checkboxes change log
2 |
3 | ## 0.12.0
4 |
5 | Upgraded for Ember 3.28.
6 |
7 | ## 0.11.0
8 |
9 | Upgraded for Ember 3, no changes to the public API.
10 |
11 | ## 0.10.0
12 |
13 | A custom template block new receives the index of an option as a third parameter:
14 |
15 | ```handlebars
16 | {{#multiselect-checkboxes options=users selection=selectedUsers as |user isSelected index|}}
17 | ...
18 | {{/multiselect-checkboxes}}
19 | ```
20 |
21 | ## 0.9.0
22 |
23 | Action handlers bound to the `onchange` attribute now receive additional parameters. Previously only the updated
24 | selection was passed to the handler. It now receives 3 parameters:
25 |
26 | * `newSelection`: the subset of the options that is currently selected.
27 | * `value`: the corresponding value of the checkbox that was checked or unchecked.
28 | * `operation`: a string describing the operation performed on the selection. There are two possible values: 'added' when
29 | the value was added to the selection and 'removed' when the value was removed from the selection.
30 |
31 | ```js
32 | actions: {
33 | updateSelection: function (newSelection, value, operation) {
34 | ...
35 | }
36 | }
37 | ```
38 |
39 | ## 0.8.0
40 |
41 | Added `translate` attribute. If `translate` is set to true and the [Ember-i18n addon](https://www.npmjs.com/package/ember-i18n)
42 | is installed, then the labels will be used as keys for translation lookup and the translations are displayed instead.
43 |
44 | ```handlebars
45 | {{multiselect-checkboxes
46 | options=users
47 | labelProperty='name'
48 | selection=selectedUsers
49 | translate=true}}}
50 | ```
51 |
52 | ## 0.7.0
53 |
54 | Added `onchange` attribute. An action can be bound to the `onchange` attribute:
55 |
56 | ```handlebars
57 | {{multiselect-checkboxes
58 | options=users
59 | labelProperty='name'
60 | selection=selectedUsers
61 | onchange=(action 'updateSelection')}}
62 | ```
63 |
64 | When a checkbox is checked or unchecked, this action will be triggered and it will receive the new selection as a
65 | parameter.
66 |
67 | Added `updateSelectionValue` attribute. By default, the component will update the value bound to the `selection`
68 | attribute automatically. If you prefer to update the value bound to the `selection` attribute yourself, this can be
69 | disabled by setting the `updateSelectionValue` attribute to `false`:
70 |
71 | ```handlebars
72 | {{multiselect-checkboxes
73 | options=users
74 | labelProperty='name'
75 | selection=selectedUsers
76 | onchange=(action 'updateSelection')
77 | updateSelectionValue=false}}
78 | ```
79 |
80 | You should then update the value bound to the `selection` property in the action bound to `onchange`, e.g.:
81 |
82 | ```js
83 | actions: {
84 | updateSelection: function (newSelection) {
85 | this.set('selection', newSelection);
86 |
87 | ...
88 | }
89 | }
90 | ```
91 |
92 | Note that for long option lists, allowing the component to automatically update the value bound to the `selection`
93 | attribute may result in significantly better performance.
94 |
95 | ## 0.6.0
96 |
97 | BC break:
98 |
99 | As suggested by @nadnoslen, passing the option itself as a block param to a custom template block should allow more
100 | flexibility than passing the label and option value. In previous versions, a custom template block received 3 block
101 | parameters: the option label, a boolean value indicating whether or not the option is selected, and the option value. As
102 | of this version, a custom template block now receives 2 parameters: the option itself and a boolean value indicating
103 | whether or not the option is selected.
104 |
105 | The following is an example of the old version with a custom template block:
106 |
107 | ```handlebars
108 | {{#multiselect-checkboxes options=users labelProperty='name' selection=selectedUsers as |label isSelected value|}}
109 |
110 |
111 | {{input type="checkbox" checked=isSelected}}
112 | {{label}}
113 |
114 |
115 | {{/multiselect-checkboxes}}
116 | ```
117 |
118 | This should now be replaced with the following:
119 |
120 | ```handlebars
121 | {{#multiselect-checkboxes options=users selection=selectedUsers as |user isSelected|}}
122 |
123 |
124 | {{input type="checkbox" checked=isSelected}}
125 | {{user.name}}
126 |
127 |
128 | {{/multiselect-checkboxes}}
129 | ```
130 |
131 | Note that the `labelProperty` attribute is now superfluous when using a custom template block; instead, `{{user.name}}`
132 | is referenced directly in the template block.
133 |
134 | ## 0.5.0
135 |
136 | Thanks to @techthumb, the options and checkboxes should now properly update when updating the bound options array or the
137 | bound selection array externally. Also adds the value as an optional third block param that can be used in a custom
138 | template block for displaying options.
139 |
140 | ## 0.4.0
141 |
142 | This release requires Ember 1.13 or newer.
143 |
144 | It's now possible to pass a custom template block should you want to customize the option list is some way. The
145 | following example without a template block:
146 |
147 | ```handlebars
148 | {{multiselect-checkboxes options=users labelProperty='name' selection=selectedUsers}}
149 | ```
150 |
151 | Is equivalent to this example with a template block:
152 |
153 | ```handlebars
154 | {{#multiselect-checkboxes options=users labelProperty='name' selection=selectedUsers as |label isSelected|}}
155 |
156 |
157 | {{input type="checkbox" checked=isSelected}}
158 | {{label}}
159 |
160 |
161 | {{/multiselect-checkboxes}}
162 | ```
163 |
164 | ## 0.3.0
165 |
166 | Adds `valueProperty` option. This option can be used to change how plain js objects or Ember js objects are represented
167 | in the selection. If for example, you specify the options to be an array of car objects, setting the `valueProperty` to
168 | be their "color" property, will result in a selection of color strings, instead of a selection of car objects.
169 |
170 | This version also removed the `multiselect-checkbox-option` component. This helper component was never intended to be
171 | part of the public API of this addon. If you were using it to create a customized checkbox list, stick on 0.2.x for a
172 | while; when Ember 1.13 is released this addon will be updated to provide better customization options with the help of
173 | block params.
174 |
175 | ## 0.2.0
176 |
177 | Upgraded to Ember CLI 0.2.0.
178 |
179 | ## 0.1.0
180 |
181 | Added option to disable a checkbox group, courtesy of @rafaelsales (see #6):
182 |
183 | ```hbs
184 | {{multiselect-checkboxes options=persons selection=selectedPersons labelProperty="name" disabled=personsDisabled}}
185 | ```
186 |
--------------------------------------------------------------------------------
/tests/integration/multiselect-checkboxes-test.js:
--------------------------------------------------------------------------------
1 | import { click, fillIn, render } from '@ember/test-helpers';
2 | import hbs from 'htmlbars-inline-precompile';
3 | import { module, test } from 'qunit';
4 | import { setupRenderingTest } from 'ember-qunit';
5 | import { has } from 'require';
6 | import { A } from '@ember/array';
7 | import Object from '@ember/object';
8 | import { run } from '@ember/runloop';
9 | import $ from 'jquery';
10 |
11 | module('Integration | Component | Multiselect-checkboxes', function(hooks) {
12 | setupRenderingTest(hooks);
13 |
14 | let fruits = A(['apple', 'orange', 'strawberry']);
15 |
16 | let cars = A([
17 | { make: "BMW", color: "black"},
18 | { make: "Ferari", color: "red"},
19 | { make: "Volvo", color: "blue"}
20 | ]);
21 |
22 | let Person = Object.extend({
23 | name: null,
24 |
25 | gender: null
26 | });
27 |
28 | let persons = A([
29 | Person.create({ name: "Lisa", gender: "Female" }),
30 | Person.create({ name: "Bob", gender: "Male" }),
31 | Person.create({ name: "John", gender: "Male"})
32 | ]);
33 |
34 | test('uses the correct labels with primitive values and no label property', async function (assert) {
35 | this.set('options', fruits);
36 |
37 | await render(hbs`
38 |
39 | `);
40 |
41 | let labels = this.element.querySelectorAll('label');
42 |
43 | assert.equal($(labels[0]).text().trim(), 'apple');
44 | assert.equal($(labels[1]).text().trim(), 'orange');
45 | assert.equal($(labels[2]).text().trim(), 'strawberry');
46 | });
47 |
48 | test('uses the correct labels with plain js values and a label property', async function (assert) {
49 | this.set('options', cars);
50 |
51 | await render(hbs`
52 |
53 | `);
54 |
55 | let labels = this.element.querySelectorAll('label');
56 |
57 | assert.equal($(labels[0]).text().trim(), 'BMW');
58 | assert.equal($(labels[1]).text().trim(), 'Ferari');
59 | assert.equal($(labels[2]).text().trim(), 'Volvo');
60 | });
61 |
62 | if(has('ember-i18n')) {
63 | test('labels are translated when translate is true and i18n addon is present', async function (assert) {
64 | this.set('options', persons);
65 |
66 | await render(hbs`
67 |
68 | `);
69 |
70 | let labels = this.element.querySelectorAll('label');
71 |
72 | assert.equal($(labels[0]).text().trim(), 'Luisa');
73 | assert.equal($(labels[1]).text().trim(), 'Roberto');
74 | assert.equal($(labels[2]).text().trim(), 'Juan');
75 | });
76 |
77 | test('labels are translated correctly when translate is true and i18n addon is present after switching locale', async function (assert) {
78 | this.set('options', persons);
79 |
80 | await render(hbs`
81 |
82 | `);
83 |
84 | run(() => this.owner.lookup('service:i18n').set('locale', 'fr'));
85 |
86 | let labels = this.element.querySelectorAll('label');
87 |
88 | assert.equal($(labels[0]).text().trim(), 'Louise');
89 | assert.equal($(labels[1]).text().trim(), 'Robert');
90 | assert.equal($(labels[2]).text().trim(), 'Jean');
91 | });
92 | }
93 |
94 | test('labels are not translated when translate is true and i18n addon is not present', async function (assert) {
95 | this.set('options', persons);
96 |
97 | await render(hbs`
98 |
99 | `);
100 |
101 | let labels = this.element.querySelectorAll('label');
102 |
103 | assert.equal($(labels[0]).text().trim(), 'Lisa');
104 | assert.equal($(labels[1]).text().trim(), 'Bob');
105 | assert.equal($(labels[2]).text().trim(), 'John');
106 | });
107 |
108 | test('uses the correct labels with Ember object values and a label property', async function (assert) {
109 | this.set('options', persons);
110 |
111 | await render(hbs`
112 |
113 | `);
114 |
115 | let labels = this.element.querySelectorAll('label');
116 |
117 | assert.equal($(labels[0]).text().trim(), 'Lisa');
118 | assert.equal($(labels[1]).text().trim(), 'Bob');
119 | assert.equal($(labels[2]).text().trim(), 'John');
120 | });
121 |
122 | test('checks the checkboxes that represent a value currently in the selection', async function (assert) {
123 | this.setProperties({
124 | 'options': persons,
125 | 'selection': A([persons[0], persons[2]])
126 | });
127 |
128 | await render(hbs`
129 |
130 | `);
131 |
132 | let checkboxes = this.element.querySelectorAll('input[type="checkbox"]');
133 |
134 | assert.equal($(checkboxes[0]).prop('checked'), true);
135 | assert.equal($(checkboxes[1]).prop('checked'), false);
136 | assert.equal($(checkboxes[2]).prop('checked'), true);
137 | });
138 |
139 | test('adds the value a checkbox represents to the selection when that checkbox is checked', async function (assert) {
140 | this.setProperties({
141 | 'options': persons,
142 | 'selection': A([persons[0]])
143 | });
144 |
145 | await render(hbs`
146 |
147 | `);
148 |
149 | let checkboxes = this.element.querySelectorAll('input[type="checkbox"]');
150 |
151 | assert.equal($(checkboxes[2]).prop('checked'), false);
152 |
153 | $(checkboxes[2]).click();
154 |
155 | assert.equal($(checkboxes[2]).prop('checked'), true);
156 | assert.equal(this.get('selection.length'), 2);
157 | assert.equal(this.get('selection').includes(persons[2]), true);
158 | });
159 |
160 | test('removes the value a checkbox represents from the selection when that checkbox is unchecked', async function (assert) {
161 | this.setProperties({
162 | 'options': persons,
163 | 'selection': A([persons[0]])
164 | });
165 |
166 | await render(hbs`
167 |
168 | `);
169 |
170 | let checkboxes = this.element.querySelectorAll('input[type="checkbox"]');
171 |
172 | assert.equal($(checkboxes[0]).prop('checked'), true);
173 |
174 | $(checkboxes[0]).click();
175 |
176 | assert.equal($(checkboxes[0]).prop('checked'), false);
177 | assert.equal(this.get('selection.length'), 0);
178 | assert.equal(this.get('selection').includes(persons[0]), false);
179 | });
180 |
181 | test('triggers the onchange action with the correct arguments when the selection changes', async function (assert) {
182 | this.setProperties({
183 | 'options': persons,
184 | 'selection': A(),
185 | 'actions': {
186 | updateSelection: (newSelection, value, operation) => {
187 | assert.equal(newSelection.length, 1);
188 | assert.equal(newSelection.includes(persons[1]), true);
189 | assert.equal(value, persons[1]);
190 | assert.equal(operation, 'added');
191 | }
192 | }
193 | });
194 |
195 | await render(hbs`
196 |
197 | `);
198 |
199 | let checkboxes = this.element.querySelectorAll('input[type="checkbox"]');
200 |
201 | $(checkboxes[1]).click();
202 | });
203 |
204 | test('does not update the bound selection value when updateSelectionValue is set to false', async function (assert) {
205 | this.setProperties({
206 | 'options': persons,
207 | 'selection': A([persons[0]])
208 | });
209 |
210 | await render(hbs`
211 |
212 | `);
213 |
214 | let checkboxes = this.element.querySelectorAll('input[type="checkbox"]');
215 |
216 | $(checkboxes[1]).click();
217 |
218 | assert.equal(this.get('selection.length'), 1);
219 | assert.equal(this.get('selection').includes(persons[0]), true);
220 | assert.equal(this.get('selection').includes(persons[1]), false);
221 | });
222 |
223 | test('checks the correct options with plain js values and a value property', async function (assert) {
224 | this.setProperties({
225 | 'options': cars,
226 | 'selection': A(['red'])
227 | });
228 |
229 | await render(hbs`
230 |
231 | `);
232 |
233 | let checkboxes = this.element.querySelectorAll('input[type="checkbox"]');
234 |
235 | assert.equal($(checkboxes[0]).prop('checked'), false);
236 | assert.equal($(checkboxes[1]).prop('checked'), true);
237 | assert.equal($(checkboxes[2]).prop('checked'), false);
238 | });
239 |
240 | test('updates the selection correctly with plain js values and a value property', async function (assert) {
241 | this.setProperties({
242 | 'options': cars,
243 | 'selection': A(['red'])
244 | });
245 |
246 | await render(hbs`
247 |
248 | `);
249 |
250 | let checkboxes = this.element.querySelectorAll('input[type="checkbox"]');
251 |
252 | assert.equal($(checkboxes[0]).prop('checked'), false);
253 |
254 | $(checkboxes[0]).click();
255 |
256 | assert.equal($(checkboxes[0]).prop('checked'), true);
257 | assert.equal(this.get('selection.length'), 2);
258 | assert.equal(this.get('selection').includes('black'), true);
259 | assert.equal(this.get('selection').includes('red'), true);
260 | });
261 |
262 | test('checks the correct options with Ember object values and a value property', async function (assert) {
263 | this.setProperties({
264 | 'options': persons,
265 | 'selection': A(['Bob'])
266 | });
267 |
268 | await render(hbs`
269 |
270 | `);
271 |
272 | let checkboxes = this.element.querySelectorAll('input[type="checkbox"]');
273 |
274 | assert.equal($(checkboxes[0]).prop('checked'), false);
275 | assert.equal($(checkboxes[1]).prop('checked'), true);
276 | assert.equal($(checkboxes[2]).prop('checked'), false);
277 | });
278 |
279 | test('updates the selection correctly with Ember object values and a value property', async function (assert) {
280 | this.setProperties({
281 | 'options': persons,
282 | 'selection': A(['Bob'])
283 | });
284 |
285 | await render(hbs`
286 |
287 | `);
288 |
289 | let checkboxes = this.element.querySelectorAll('input[type="checkbox"]');
290 |
291 | assert.equal($(checkboxes[0]).prop('checked'), false);
292 |
293 | $(checkboxes[0]).click();
294 |
295 | assert.equal($(checkboxes[0]).prop('checked'), true);
296 |
297 | assert.equal(this.get('selection.length'), 2);
298 | assert.equal(this.get('selection').includes('Lisa'), true);
299 | assert.equal(this.get('selection').includes('Bob'), true);
300 | });
301 |
302 | test('disables all checkboxes when disabled is set to true', async function (assert) {
303 | this.setProperties({
304 | 'options': persons,
305 | 'selection': A([persons[0]])
306 | });
307 |
308 | await render(hbs`
309 |
310 | `);
311 |
312 | let checkboxes = this.element.querySelectorAll('input[type="checkbox"]');
313 |
314 | $(checkboxes).each((index, checkbox) => {
315 | assert.equal($(checkbox).prop('disabled'), true);
316 | });
317 |
318 | $(checkboxes).each((index, checkbox) => {
319 | $(checkbox).click();
320 | });
321 |
322 | assert.equal(this.get('selection.length'), 1);
323 | assert.equal(this.get('selection').includes(persons[0]), true);
324 | });
325 |
326 | test('updates the displayed options when the bound options change', async function (assert) {
327 | this.set('options', fruits);
328 |
329 | await render(hbs`
330 |
331 | `);
332 |
333 | let labels = this.element.querySelectorAll('label');
334 |
335 | assert.equal($(labels[0]).text().trim(), 'apple');
336 | assert.equal($(labels[1]).text().trim(), 'orange');
337 | assert.equal($(labels[2]).text().trim(), 'strawberry');
338 |
339 | run(() => fruits.reverseObjects());
340 |
341 | labels = this.element.querySelectorAll('label');
342 |
343 | assert.equal($(labels[0]).text().trim(), 'strawberry');
344 | assert.equal($(labels[1]).text().trim(), 'orange');
345 | assert.equal($(labels[2]).text().trim(), 'apple');
346 | });
347 |
348 | test('updates checkboxes when the bound selection changes', async function (assert) {
349 | let selection = A([persons[0], persons[2]]);
350 |
351 | this.setProperties({
352 | 'options': persons,
353 | 'selection': selection
354 | });
355 |
356 | await render(hbs`
357 |
358 | `);
359 |
360 | let checkboxes = this.element.querySelectorAll('input[type="checkbox"]');
361 |
362 | assert.equal($(checkboxes[0]).prop('checked'), true);
363 | assert.equal($(checkboxes[1]).prop('checked'), false);
364 | assert.equal($(checkboxes[2]).prop('checked'), true);
365 |
366 | run(() => selection.removeObject(persons[0]));
367 |
368 | checkboxes = this.element.querySelectorAll('input[type="checkbox"]');
369 |
370 | assert.equal($(checkboxes[0]).prop('checked'), false);
371 | assert.equal($(checkboxes[1]).prop('checked'), false);
372 | assert.equal($(checkboxes[2]).prop('checked'), true);
373 | });
374 |
375 | test('with a template block displays the correct custom labels for each person', async function (assert) {
376 | this.set('options', persons);
377 |
378 | await render(hbs`
379 |
380 |
381 |
382 |
383 | --{{person.name}}--
384 |
385 |
386 |
387 | `);
388 |
389 | let labels = this.element.querySelectorAll('label');
390 |
391 | assert.equal($(labels[0]).text().trim(), '--Lisa--');
392 | assert.equal($(labels[1]).text().trim(), '--Bob--');
393 | assert.equal($(labels[2]).text().trim(), '--John--');
394 | });
395 |
396 | test('with a template block adds the value a checkbox represents to the selection when that checkbox is checked', async function (assert) {
397 | this.setProperties({
398 | 'options': persons,
399 | 'selection': A()
400 | });
401 |
402 | await render(hbs`
403 |
404 |
405 |
406 |
407 | --{{person.name}}--
408 |
409 |
410 |
411 | `);
412 |
413 | let checkboxes = this.element.querySelectorAll('input[type="checkbox"]');
414 |
415 | assert.equal($(checkboxes[2]).prop('checked'), false);
416 |
417 | $(checkboxes[2]).click();
418 |
419 | assert.equal($(checkboxes[2]).prop('checked'), true);
420 | assert.equal(this.get('selection.length'), 1);
421 | assert.equal(this.get('selection').includes(persons[2]), true);
422 | });
423 | });
424 |
--------------------------------------------------------------------------------