Repository: patosai/tree-multiselect.js Branch: master Commit: 393723ef511a Files: 40 Total size: 155.1 KB Directory structure: gitextract_btu2m2g5/ ├── .babelrc ├── .circleci/ │ └── config.yml ├── .eslintrc.yml ├── .gitignore ├── .npmignore ├── Gruntfile.js ├── LICENSE ├── README.md ├── bower.json ├── conf/ │ └── karma.js ├── dist/ │ ├── jquery.tree-multiselect.css │ └── jquery.tree-multiselect.js ├── package.json ├── release.sh ├── sass/ │ └── style.scss ├── src/ │ ├── tree-multiselect/ │ │ ├── ast/ │ │ │ ├── common.js │ │ │ ├── index.js │ │ │ ├── item.js │ │ │ └── section.js │ │ ├── main.js │ │ ├── search.js │ │ ├── tree.js │ │ ├── ui-builder.js │ │ └── utility/ │ │ ├── array.js │ │ ├── dom.js │ │ └── index.js │ └── tree-multiselect.js └── test/ ├── integration/ │ ├── common.js │ ├── initial-load.test.js │ ├── interactivity.test.js │ ├── options.test.js │ ├── reloading.test.js │ ├── removing.test.js │ ├── search.test.js │ ├── section-checkboxes.test.js │ ├── section-selections.test.js │ └── single-selections.test.js ├── test-performance.html ├── test.html └── unit/ └── utility.test.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .babelrc ================================================ { "presets": ["@babel/preset-env"] } ================================================ FILE: .circleci/config.yml ================================================ version: "2.1" orbs: browser-tools: circleci/browser-tools@1.4.0 jobs: build: docker: - image: cimg/node:18.7.0-browsers working_directory: ~/tree-multiselect steps: - browser-tools/install-chrome - checkout - restore_cache: keys: - v1-dependencies-{{ checksum "package.json" }} # fallback to using the latest cache if no exact match is found - v1-dependencies- - run: npm install - save_cache: paths: - node_modules key: v1-dependencies-{{ checksum "package.json" }} - run: npm test ================================================ FILE: .eslintrc.yml ================================================ env: browser: true es6: true extends: standard globals: jQuery: true module: true exports: true require: true parserOptions: sourceType: module rules: indent: - error - 2 linebreak-style: - error - unix quotes: - error - single semi: - error - always prefer-const: off no-var: off object-curly-spacing: off ================================================ FILE: .gitignore ================================================ bower_components/ node_modules/ coverage/ ================================================ FILE: .npmignore ================================================ * !dist/ !src/ !package.json !LICENSE ================================================ FILE: Gruntfile.js ================================================ const sass = require('node-sass'); module.exports = function(grunt) { grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), eslint: { target: ['src/**/*.js'] }, browserify: { dist: { options: { transform: ['babelify'] }, files: { 'dist/jquery.tree-multiselect.js': ['src/tree-multiselect.js'] } } }, // Karma runner karma: { options: { configFile: 'conf/karma.js', }, local: {}, watch: { autoWatch: true, singleRun: false } }, // SASS compiler sass: { options: { implementation: sass, sourceMap: false }, min: { options: { outputStyle: 'nested' }, files: { 'dist/jquery.tree-multiselect.css': 'sass/style.scss' } }, build: { options: { outputStyle: 'compressed' }, files: { 'dist/jquery.tree-multiselect.min.css': 'sass/style.scss' } } }, // Uglify JS uglify: { dist: { options: { preserveComments: false, }, files: { 'dist/jquery.tree-multiselect.min.js': ['dist/jquery.tree-multiselect.js'] } } }, // Put headers on distributed files usebanner: { dist: { options: { position: 'top', banner: "/* jQuery Tree Multiselect v<%= pkg.version %> | (c) Patrick Tsai | MIT Licensed */", linebreak: true }, files: { src: ['dist/*.js', 'dist/*.css'] } } } }); grunt.loadNpmTasks('grunt-banner'); grunt.loadNpmTasks('grunt-browserify'); grunt.loadNpmTasks('grunt-eslint'); grunt.loadNpmTasks('grunt-karma'); grunt.loadNpmTasks('grunt-sass'); grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.registerTask('lint', ['eslint']); grunt.registerTask('build', ['browserify']); grunt.registerTask('test', ['lint', 'karma:local']); grunt.registerTask('test-watch', ['karma:watch']); grunt.registerTask('release', ['test', 'build', 'uglify', 'sass:build', 'sass:min', 'usebanner']); grunt.registerTask('watch', ['test-watch']); grunt.registerTask('default', 'test'); }; ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2015 Patrick Tsai Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ ## jQuery Tree Multiselect [![CircleCI](https://circleci.com/gh/patosai/tree-multiselect.js.svg?style=svg)](https://circleci.com/gh/patosai/tree-multiselect.js) [![Coverage Status](https://codecov.io/gh/patosai/tree-multiselect.js/branch/master/graph/badge.svg)](https://codecov.io/gh/patosai/tree-multiselect.js) [![devDependency Status](https://david-dm.org/patosai/tree-multiselect.js/dev-status.svg)](https://david-dm.org/patosai/tree-multiselect.js#info=devDependencies) **This plugin allows you to add a sweet treeview frontend to a `` node can be used as it was before. This means you can still use `$("select").val()` or `selectElement.value` to get the value, as if there was no plugin. If you want to add options dynamically, please continue reading, there are some more steps you need to take. * Make sure you've got `` in your `` or some of the symbols may look strange. * Requires jQuery v1.8+ ![demo image](demo.jpg "demo image") ### Demo My website has a simple demo running. ### How To Use 1. Set the `multiple="multiple"` attribute on your `` * Make sure your `` nodes. `params` is optional. ```javascript $("select").treeMultiselect(); ``` ```javascript let params = {searchable: true}; $("select").treeMultiselect(params); ``` ```javascript function treeOnChange(allSelectedItems, addedItems, removedItems) { console.log("something changed!"); } $("select").treeMultiselect({ allowBatchSelection: false, onChange: treeOnChange, startCollapsed: true }); ``` ##### Params Name | Default | Description ----------------------- | -------------- | --------------- `allowBatchSelection` | `true` | Sections have checkboxes which when checked, check everything within them `collapsible` | `true` | Adds collapsibility to sections `enableSelectAll` | `false` | Enables selection of all or no options `selectAllText` | `Select All` | Only used if `enableSelectAll` is active `unselectAllText` | `Unselect All` | Only used if `enableSelectAll` is active `freeze` | `false` | Disables selection/deselection of options; aka display-only `hideSidePanel` | `false` | Hide the right panel showing all the selected items `maxSelections` | `0` | A number that sets the maximum number of options that can be selected. Any positive integer is valid; anything else (such as `0` or `-1`) means no limit `onChange` | `null` | Callback for when select is changed. Called with (allSelectedItems, addedItems, removedItems), each of which is an array of objects with the properties `text`, `value`, `initialIndex`, and `section` `onlyBatchSelection` | `false` | Only sections can be checked, not individual items `sortable` | `false` | Selected options can be sorted by dragging (requires jQuery UI) `searchable` | `false` | Allows searching of options `searchParams` | `['value', 'text', 'description', 'section']` | Set items to be searched. Array must contain `'value'`, `'text'`, or `'description'`, and/or `'section'` `sectionDelimiter` | `/` | Separator between sections in the select option `data-section` attribute `showSectionOnSelected` | `true` | Show section name on the selected items `startCollapsed` | `false` | Activated only if `collapsible` is true; sections are collapsed initially #### Examples #### `.remove()` Removes the tree from the DOM. Leaves the original `` and call `.reload()` to render the new options. User-changed selections will be saved. ```javascript let trees = $("select").treeMultiselect(); let firstTree = trees[0]; // add an option $("select#id").append(""); firstTree.reload(); ``` ### Installation Load `jquery.tree-multiselect.min.js` on to your web page. The css file is optional (but recommended). You can also use bower - `bower install tree-multiselect` ### How to build You need to have grunt-cli installed so you can run the `grunt` command. - Run tests: `grunt` or `grunt test` - Build dist JavaScript file: `grunt build` - Build Sass: `grunt sass` - Build everything: `grunt release` ### FAQ `Help! The first element is selected when I create the tree. How do I make the first element not selected?` You didn't set the `multiple` attribute on your `"); $("select#frozen").append(""); var options = { freeze: true }; $("select#frozen").treeMultiselect(options); var $frozenOption = Common.selection({text: 'Two'}); assert.equal($frozenOption.length, 1); assert($frozenOption.find("input[type=checkbox]").attr('disabled')); var $unfrozenOption = Common.selection({text: 'One'}); assert.equal($unfrozenOption.length, 1); var $checkbox = $unfrozenOption.find("input[type=checkbox]"); assert.notOk($checkbox.attr('disabled')); $checkbox.click(); var $unfrozenSelection = Common.selected({text: 'One'}); assert.equal($unfrozenSelection.length, 1); assert.deepEqual($("select").val(), ['one']); assert.deepEqual($("select#frozen").val(), ['two']); }); it('hides side panel', () => { $("select").append(""); var options = { hideSidePanel: true }; $("select").treeMultiselect(options); assert.equal($("div.selected").length, 0); }); it('onlyBatchSelection gives checkboxes to only sections', () => { $("select").append(""); var options = { onlyBatchSelection: true }; $("select").treeMultiselect(options); assert.equal($("input.section[type=checkbox]").length, 1); assert.equal($("input.option[type=checkbox]").length, 0); }); it('calls onChange with correct arguments when item is added', (done) => { $("select").append(""); $("select").append(""); var options = { onChange: function(all, added, removed) { assert.equal(all.length, 2); assert.equal(added.length, 1); assert.equal(removed.length, 0); var expectedSecondSelections = [all[1], added[0]]; for (var i = 0; i < expectedSecondSelections.length; ++i) { var selection = expectedSecondSelections[i]; assert.equal(selection.text, 'Two'); assert.equal(selection.value, 'two'); assert.isNull(selection.initialIndex); assert.equal(selection.section, 'test'); } assert.equal(all[0].text, 'One'); assert.equal(all[0].value, 'one'); assert.isNull(all[0].initialIndex); assert.equal(all[0].section, 'test'); done(); } }; $("select").treeMultiselect(options); var $item = Common.selection({text: 'Two'}); assert.equal($item.length, 1); $item.find("input[type=checkbox]").click(); }); it('calls onChange with correct arguments when item is removed', (done) => { $("select").append(""); $("select").append(""); var options = { onChange: function(all, added, removed) { assert.equal(all.length, 0); assert.equal(added.length, 0); assert.equal(removed.length, 1); assert.equal(removed[0].text, 'One'); assert.equal(removed[0].value, 'one'); assert.isNull(removed[0].initialIndex); assert.equal(removed[0].section, 'test'); done(); } }; $("select").treeMultiselect(options); var $item = Common.selection({text: 'One'}); assert.equal($item.length, 1); $item.find("input[type=checkbox]").click(); }); it('fixes original select value when sorted', () => { $("select").append(""); $("select").append(""); $("select").treeMultiselect({ sortable: true }); assert.deepEqual($("select").val(), ['one', 'two']); var $selected = Common.selected(); assert.equal($selected.length, 2); var $one = $selected.first(); var $two = $selected.last(); assert($("div.selected").sortable('option', 'start')); $("div.selected").sortable('option', 'start')(null, { item: $one }); $one.insertAfter($two); assert($("div.selected").sortable('option', 'stop')); $("div.selected").sortable('option', 'stop')(null, { item: $one }); assert.deepEqual($("select").val(), ['two', 'one']); }); it('puts selected items in right order when sorted', () => { $("select").append(""); $("select").append(""); $("select").treeMultiselect({ sortable: true }); var $selected = Common.selected(); assert.equal($selected.length, 2); var $one = $selected.first(); var $two = $selected.last(); Common.assertSelected($one, {text: 'One', value: 'one', section: 'test'}) Common.assertSelected($two, {text: 'Two', value: 'two', section: 'test'}) assert($("div.selected").sortable('option', 'start')); $("div.selected").sortable('option', 'start')(null, { item: $one }); $one.insertAfter($two); assert($("div.selected").sortable('option', 'stop')); $("div.selected").sortable('option', 'stop')(null, { item: $one }); $selected = Common.selected(); assert.equal($selected.length, 2); Common.assertSelected($selected.first(), {text: 'Two', value: 'two', section: 'test'}) Common.assertSelected($selected.last(), {text: 'One', value: 'one', section: 'test'}) }); it("doesn't do anything when sorted in same order", () => { $("select").append(""); $("select").append(""); $("select").treeMultiselect({ sortable: true }); var $selected = Common.selected(); assert.equal($selected.length, 2); var $one = $selected.first(); var $two = $selected.last(); Common.assertSelected($one, {text: 'One', value: 'one', section: 'test'}) Common.assertSelected($two, {text: 'Two', value: 'two', section: 'test'}) assert($("div.selected").sortable('option', 'start')); $("div.selected").sortable('option', 'start')(null, { item: $one }); assert($("div.selected").sortable('option', 'stop')); $("div.selected").sortable('option', 'stop')(null, { item: $one }); $selected = Common.selected(); assert.equal($selected.length, 2); Common.assertSelected($selected.first(), {text: 'One', value: 'one', section: 'test'}) Common.assertSelected($selected.last(), {text: 'Two', value: 'two', section: 'test'}) }); it('select all button works', () => { $("select").append(""); $("select").append(""); $("select").treeMultiselect({ enableSelectAll: true }); var $selectAll = $(".select-all"); assert.equal($selectAll.length, 1); var $selectedItems = Common.selected(); assert.equal($selectedItems.length, 0); assert.deepEqual($("select").val(), null); $selectAll.click(); $selectedItems = Common.selected(); assert.equal($selectedItems.length, 2); assert.deepEqual($("select").val(), ['one', 'two']); }); it('unselect button works', () => { $("select").append(""); $("select").append(""); $("select").treeMultiselect({ enableSelectAll: true }); var $unselectAll = $(".unselect-all"); assert.equal($unselectAll.length, 1); var $selectedItems = Common.selected(); assert.equal($selectedItems.length, 2); assert.deepEqual($("select").val(), ['one', 'two']); $unselectAll.click(); $selectedItems = Common.selected(); assert.equal($selectedItems.length, 0); assert.deepEqual($("select").val(), null); }); it('select all text option', () => { $("select").append(""); var selectAllText = "foobar"; $("select").treeMultiselect({ enableSelectAll: true, selectAllText: selectAllText }); var $selectAll = $(".select-all"); assert.equal($selectAll.text(), selectAllText); }); it('unselect all text option', () => { $("select").append(""); var unselectAllText = "foobar"; $("select").treeMultiselect({ enableSelectAll: true, unselectAllText: unselectAllText }); var $unselectAll = $(".unselect-all"); assert.equal($unselectAll.text(), unselectAllText); }); it('can have individual readonly attributes', () => { $("select").append(""); $("select").append(""); $("select").treeMultiselect(); var $firstSelectionCheckbox = Common.selectionCheckbox({value: 'one'}); assert.equal($firstSelectionCheckbox.length, 1); assert.isFalse($firstSelectionCheckbox.prop('disabled')); $firstSelectionCheckbox = Common.selectionCheckbox({value: 'two'}); assert.equal($firstSelectionCheckbox.length, 1); assert.isTrue($firstSelectionCheckbox.prop('disabled')); }); it('has readonly attributes that still appear in select val', () => { $("select").append(""); $("select").treeMultiselect(); assert.deepEqual($("select").val(), ['one']) }); it('cannot remove readonly elements by selected elements', () => { $("select").append(""); $("select").append(""); $("select").treeMultiselect(); var $selected = Common.selected(); assert.equal($selected.length, 2); var $selectedCheckboxes = $selected.children("span.remove-selected"); assert.equal($selectedCheckboxes.length, 1); $selectedCheckboxes.click(); $selected = Common.selected(); assert.equal($selected.length, 1); $selected = Common.selected({value: 'one'}); assert.equal($selected.length, 1); }); it('can set a maximum number of selections', () => { $("select").append(""); $("select").append(""); $("select").append(""); $("select").append(""); $("select").treeMultiselect({maxSelections: 2}); assert.equal(Common.selected().length, 2); assert.deepEqual($("select").val(), ['three', 'four']) var $checkbox = Common.selectionCheckbox(); $checkbox.first().click(); assert.equal(Common.selected().length, 2); assert.deepEqual($("select").val(), ['four', 'one']) }) it('maximum number of selections doesn\'t work with negative numbers', () => { $("select").append(""); $("select").append(""); $("select").append(""); $("select").append(""); $("select").treeMultiselect({maxSelections: -1}); assert.equal(Common.selected().length, 4); }) it('maximum number of selections doesn\'t work with non-numerical truthy values', () => { $("select").append(""); $("select").append(""); $("select").append(""); $("select").append(""); $("select").treeMultiselect({maxSelections: true}); assert.equal(Common.selected().length, 4); }) }); ================================================ FILE: test/integration/reloading.test.js ================================================ var Common = require('./common'); describe('Reloading', () => { it('can reload tree', () => { $("select").append(""); var trees = $("select").treeMultiselect(); var tree = trees[0]; assert.equal(Common.selection().length, 1); assert.equal(Common.selection({text: 'One'}).length, 1); $("select").append(""); tree.reload(); assert.equal(Common.selection().length, 2); assert.equal(Common.selection({text: 'One'}).length, 1); assert.equal(Common.selection({text: 'Two'}).length, 1); }); it('reload saves user-changed options', () => { $("select").append(""); var trees = $("select").treeMultiselect(); var tree = trees[0]; assert.equal(Common.selection().length, 1); assert.equal(Common.selected().length, 1); assert.equal($("select").val().length, 1); $("select").val([]); $("select").change(); $("select").append(""); tree.reload(); assert.equal(Common.selection().length, 2); assert.equal(Common.selected().length, 1); assert.deepEqual($("select").val(), ['one']); }); }); ================================================ FILE: test/integration/removing.test.js ================================================ var Common = require('./common'); describe('Removing', () => { it('can remove tree', () => { $("select").append(""); var trees = $("select").treeMultiselect(); var tree = trees[0]; assert.equal($(".tree-multiselect").length, 1); tree.remove(); assert.equal($(".tree-multiselect").length, 0); }); }); ================================================ FILE: test/integration/search.test.js ================================================ var Common = require('./common'); function getVisibleSelections(props) { return Common.selection(props).filter((_, el) => { return el.getAttribute('searchhit') === 'true'; }) } function getHiddenSelections(props) { return Common.selection(props).filter((_, el) => { return el.getAttribute('searchhit') === 'false'; }) } describe('Search', () => { var $input; describe('default behavior', () => { beforeEach(() => { // value, section $("select").append(""); // section $("select").append(""); // text $("select").append(""); // description $("select").append(""); // description with spaces $("select").append(""); $("select").append(""); $("select").treeMultiselect({searchable: true, enableSelectAll: true}); $input = $("input.search"); assert.equal($input.length, 1); }); it('matches on value', () => { ['a', 'c', 'abc', 'cde', 'bcd', 'abcde', 'abcd'].forEach((searchTerm) => { $input.val(searchTerm).trigger('input'); assert.equal(Common.selection({value: 'abcde'}).attr('searchhit'), 'true'); assert.equal(Common.selection({value: 'fghij'}).attr('searchhit'), 'false'); assert.equal(Common.selection({value: 'KLMNOP'}).attr('searchhit'), 'false'); assert.equal(Common.selection({value: 'QRS'}).attr('searchhit'), 'false'); }); ['fghij', 'ghi', 'fgh'].forEach((searchTerm) => { $input.val(searchTerm).trigger('input'); assert.equal(Common.selection({value: 'abcde'}).attr('searchhit'), 'false'); assert.equal(Common.selection({value: 'fghij'}).attr('searchhit'), 'true'); assert.equal(Common.selection({value: 'KLMNOP'}).attr('searchhit'), 'false'); assert.equal(Common.selection({value: 'QRS'}).attr('searchhit'), 'false'); }); ['q', 'qr', 'qrs', 'rs'].forEach((searchTerm) => { $input.val(searchTerm).trigger('input'); assert.equal(Common.selection({value: 'abcde'}).attr('searchhit'), 'false'); assert.equal(Common.selection({value: 'fghij'}).attr('searchhit'), 'false'); assert.equal(Common.selection({value: 'KLMNOP'}).attr('searchhit'), 'false'); assert.equal(Common.selection({value: 'QRS'}).attr('searchhit'), 'true'); }); }); it('matches on section', () => { $input.val('s1').trigger('input'); assert.equal(Common.selection({value: 'abcde'}).attr('searchhit'), 'true'); assert.equal(Common.selection({value: 'fghij'}).attr('searchhit'), 'true'); assert.equal(Common.selection({value: 'KLMNOP'}).attr('searchhit'), 'false'); assert.equal(Common.selection({value: 'QRS'}).attr('searchhit'), 'false'); }); it('matches on text', () => { $input.val('yyy').trigger('input'); assert.equal(Common.selection({value: 'abcde'}).attr('searchhit'), 'false'); assert.equal(Common.selection({value: 'fghij'}).attr('searchhit'), 'true'); assert.equal(Common.selection({value: 'KLMNOP'}).attr('searchhit'), 'false'); assert.equal(Common.selection({value: 'QRS'}).attr('searchhit'), 'false'); }); it('matches on description', () => { $input.val('fox').trigger('input'); assert.equal(Common.selection({value: 'abcde'}).attr('searchhit'), 'false'); assert.equal(Common.selection({value: 'fghij'}).attr('searchhit'), 'false'); assert.equal(Common.selection({value: 'KLMNOP'}).attr('searchhit'), 'false'); assert.equal(Common.selection({value: 'QRS'}).attr('searchhit'), 'true'); }); it('hides sections with no nodes visible', () => { $input.val('s1').trigger('input'); assert.equal(Common.section({text: 's1'}).attr('searchhit'), 'true'); assert.equal(Common.section({text: 's2'}).attr('searchhit'), 'false'); assert.equal(Common.section({text: 'ttt'}).attr('searchhit'), 'false'); assert.equal(Common.section({text: 'uuu'}).attr('searchhit'), 'false'); assert.equal(Common.section({text: 'vvv'}).attr('searchhit'), 'false'); $input.val('uuu').trigger('input'); assert.equal(Common.section({text: 's1'}).attr('searchhit'), 'false'); assert.equal(Common.section({text: 's2'}).attr('searchhit'), 'false'); assert.equal(Common.section({text: 'ttt'}).attr('searchhit'), 'true'); assert.equal(Common.section({text: 'uuu'}).attr('searchhit'), 'true'); assert.equal(Common.section({text: 'vvv'}).attr('searchhit'), 'true'); }); it('shows all sections when no search term is entered', () => { $input.val('43t#Q%').trigger('input'); // no nodes should be shown assert.equal(getVisibleSelections().length, 0); $input.val('').trigger('input'); // no nodes should be shown assert.equal(getHiddenSelections().length, 0); }); it('only adds filtered selections when using section checkbox', () => { $input.val('abcde').trigger('input'); assert.equal(Common.selected().length, 0); Common.sectionCheckbox({text: 's1'}).click(); assert.equal(Common.selected().length, 1); $input.val('fox').trigger('input'); assert.equal(Common.selected().length, 1); var $tttCheckbox = Common.sectionCheckbox({text: 'ttt'}) $tttCheckbox.click(); assert.equal(Common.selected().length, 2); $tttCheckbox.click(); assert.equal(Common.selected().length, 1); }); it('only adds filtered selections when selecting and unselecting all', () => { $input.val('s1').trigger('input'); var $selectAll = $(".select-all"); var $unselectAll = $(".unselect-all"); assert.equal($selectAll.length, 1); assert.equal($unselectAll.length, 1); assert.equal(Common.selected().length, 0); $selectAll.click(); assert.equal(Common.selected().length, 2); $input.val('abcde').trigger('input'); $unselectAll.click(); assert.equal(Common.selected().length, 1); }) it('handles empty search queries', () => { $input.val(' ').trigger('input'); assert.equal(getVisibleSelections().length, 0); assert.equal(getHiddenSelections().length, 0); }) it('handles search queries with spaces in them', () => { $input.val('passion ').trigger('input'); assert.equal(getVisibleSelections().length, 2); assert.equal(getHiddenSelections().length, 4); $input.val('passion f ').trigger('input'); assert.equal(getVisibleSelections().length, 1); assert.equal(getHiddenSelections().length, 5); }) }); describe('custom search params', () => { beforeEach(() => { $("select").append(""); }); it('section only', () => { $("select").treeMultiselect({searchable: true, searchParams: ['section']}); $input = $("input.search"); $input.val('s1').trigger('input'); assert.equal(getVisibleSelections().length, 1); $input.val('abc').trigger('input'); assert.equal(getVisibleSelections().length, 0); $input.val('ayy').trigger('input'); assert.equal(getVisibleSelections().length, 0); $input.val('xyz').trigger('input'); assert.equal(getVisibleSelections().length, 0); }); it('value only', () => { $("select").treeMultiselect({searchable: true, searchParams: ['value']}); $input = $("input.search"); $input.val('s1').trigger('input'); assert.equal(getVisibleSelections().length, 0); $input.val('abc').trigger('input'); assert.equal(getVisibleSelections().length, 1); $input.val('ayy').trigger('input'); assert.equal(getVisibleSelections().length, 0); $input.val('xyz').trigger('input'); assert.equal(getVisibleSelections().length, 0); }); it('text and description', () => { $("select").treeMultiselect({searchable: true, searchParams: ['text', 'description']}); $input = $("input.search"); $input.val('s1').trigger('input'); assert.equal(getVisibleSelections().length, 0); $input.val('abc').trigger('input'); assert.equal(getVisibleSelections().length, 0); $input.val('ayy').trigger('input'); assert.equal(getVisibleSelections().length, 1); $input.val('xyz').trigger('input'); assert.equal(getVisibleSelections().length, 1); }); }); }); ================================================ FILE: test/integration/section-checkboxes.test.js ================================================ var Common = require('./common'); describe('Section Checkboxes', () => { it('is all checked when all children are selected initially', () => { $("select").append(""); $("select").append(""); $("select").treeMultiselect(); assert.equal($("input[type=checkbox]").length, 3); assert.equal($("input.option[type=checkbox]").length, 2); assert.equal($("input.section[type=checkbox]").length, 1); assert.equal($("input[type=checkbox]:checked").length, 3); }); it('should all be checked when all children are selected', () => { $("select").append(""); $("select").append(""); $("select").treeMultiselect(); assert.equal($("input[type=checkbox]").length, 3); assert.equal($("input.option[type=checkbox]").length, 2); assert.equal($("input.section[type=checkbox]").length, 1); assert.equal($("input.section[type=checkbox]:checked").length, 0); assert.equal($("input.option[type=checkbox]:checked").length, 1); Common.selection().last().children("input[type=checkbox]").click(); assert.equal($("input.section[type=checkbox]:checked").length, 1); assert.equal($("input.option[type=checkbox]:checked").length, 2); }); it('should not check top level parent if only one child section is completely checked', () => { $("select").append(""); $("select").append(""); $("select").treeMultiselect(); var $topLevel = Common.section({text: 'foo'}); assert.notOk($topLevel.find("> div.title > input.section[type=checkbox]").is(":checked")); }); it('should uncheck parent sections when a child is unselected', () => { $("select").append(""); $("select").treeMultiselect(); assert.equal($("input.section[type=checkbox]:checked").length, 1); Common.selection().first().children("input.option[type=checkbox]").click(); assert.equal($("input.section[type=checkbox]:checked").length, 0); }); it('checks nested titles', () => { $("select").append(""); $("select").treeMultiselect(); assert.equal(Common.sectionCheckbox().length, 3); assert.equal(Common.selectionCheckbox().length, 1); assert.equal($("input[type=checkbox]").length, 4); assert.equal($("input[type=checkbox]:checked").length, 0); var $middleSectionCheckbox = Common.sectionCheckbox({text: 'middle'}); $middleSectionCheckbox.click(); assert.equal($("div.title > input[type=checkbox]:checked").length, 3); assert.equal($("div.item > input[type=checkbox]:checked").length, 1); assert.equal($("input[type=checkbox]:checked").length, 4); }); it('only checks relevant titles', () => { $("select").append(""); $("select").append(""); $("select").treeMultiselect(); assert.notOk(Common.sectionCheckbox({text: 'top'}).prop('checked')); assert.notOk(Common.sectionCheckbox({text: 'middle'}).prop('checked')); assert.notOk(Common.sectionCheckbox({text: 'inner'}).prop('checked')); assert.notOk(Common.selectionCheckbox({text: 'One'}).prop('checked')); Common.sectionCheckbox({text: 'middle'}).click(); assert.notOk(Common.sectionCheckbox({text: 'top'}).prop('checked')); assert(Common.sectionCheckbox({text: 'middle'}).prop('checked')); assert(Common.sectionCheckbox({text: 'inner'}).prop('checked')); assert(Common.selectionCheckbox({text: 'One'}).prop('checked')); }); it('checkbox is indeterminate when some children are selected', () => { $("select").append(""); $("select").append(""); $("select").treeMultiselect(); var $topCheckbox = Common.sectionCheckbox({text: 'top'}); assert.notOk($topCheckbox.prop('checked')); assert($topCheckbox.prop('indeterminate')); }); it('checkbox is not indeterminate when all children are selected', () => { $("select").append(""); $("select").append(""); $("select").treeMultiselect(); var $titleCheckbox = Common.sectionCheckbox({text: 'top'}); assert.notOk($titleCheckbox.prop('indeterminate')); }); it('checkbox is not indeterminate when no children are selected', () => { $("select").append(""); $("select").append(""); $("select").treeMultiselect(); var $titleCheckbox = Common.sectionCheckbox({text: 'top'}); assert.notOk($titleCheckbox.prop('indeterminate')); }); it('checkbox doesn\'t select unselected readonly children', () => { $("select").append(""); $("select").append(""); $("select").treeMultiselect(); assert.equal(Common.selected().length, 0); var $titleCheckbox = Common.sectionCheckbox({text: 'top'}); $titleCheckbox.click(); assert.equal(Common.selected().length, 1); assert.equal(Common.selected({value: 'two'}).length, 1); }); it('checkbox doesn\'t unselect selected readonly children', () => { $("select").append(""); $("select").append(""); $("select").treeMultiselect(); assert.equal(Common.selected().length, 2); var $titleCheckbox = Common.sectionCheckbox({text: 'top'}); $titleCheckbox.click(); assert.equal(Common.selected().length, 1); assert.equal(Common.selected({value: 'one'}).length, 1); }); }); ================================================ FILE: test/integration/section-selections.test.js ================================================ var Common = require('./common'); describe('Section Selections', () => { it('adds all child elements when section checkbox clicked', () => { $("select").append(""); $("select").append(""); $("select").treeMultiselect(); assert.deepEqual($("select").val(), null); var $selected = Common.selected(); assert.equal($selected.length, 0); var $checkbox = Common.sectionCheckbox(); assert.equal($checkbox.length, 1); $checkbox.click(); assert.deepEqual($("select").val(), ['one', 'two']); $selected = Common.selected(); assert.equal($selected.length, 2); Common.assertSelected($selected[0], {text: 'One', value: 'one', section: 'foo'}); Common.assertSelected($selected[1], {text: 'Two', value: 'two', section: 'foo'}); }); it("doesn't add child elements twice when other child elements are selected", () => { $("select").append(""); $("select").append(""); $("select").append(""); $("select").append(""); $("select").treeMultiselect(); assert.deepEqual($("select").val(), ['three', 'four']); var $selected = Common.selected(); assert.equal($selected.length, 2); Common.assertSelected($selected[0], {text: 'Three', value: 'three', section: 'foo/bar/baz'}); Common.assertSelected($selected[1], {text: 'Four', value: 'four', section: 'foo/bar/baz'}); var $checkbox = Common.sectionCheckbox(); assert.equal($checkbox.length, 3); $checkbox.first().click(); assert.deepEqual($("select").val(), ['three', 'four', 'one', 'two']); $selected = Common.selected(); assert.equal($selected.length, 4); Common.assertSelected($selected[0], {text: 'Three', value: 'three', section: 'foo/bar/baz'}); Common.assertSelected($selected[1], {text: 'Four', value: 'four', section: 'foo/bar/baz'}); Common.assertSelected($selected[2], {text: 'One', value: 'one', section: 'foo'}); Common.assertSelected($selected[3], {text: 'Two', value: 'two', section: 'foo'}); }); it('removes child elements when section unselected', () => { $("select").append(""); $("select").append(""); $("select").append(""); $("select").append(""); $("select").treeMultiselect(); assert.deepEqual($("select").val(), ['one', 'two', 'three', 'four']); var $selected = Common.selected(); Common.assertSelected($selected[0], {text: 'One', value: 'one', section: 'foo'}); Common.assertSelected($selected[1], {text: 'Two', value: 'two', section: 'foo'}); Common.assertSelected($selected[2], {text: 'Three', value: 'three', section: 'foo/bar/baz'}); Common.assertSelected($selected[3], {text: 'Four', value: 'four', section: 'foo/bar/baz'}); var $checkbox = Common.sectionCheckbox({text: 'baz'}); assert.equal($checkbox.length, 1); $checkbox.click(); assert.deepEqual($("select").val(), ['one', 'two']); $selected = Common.selected(); assert.equal($selected.length, 2); Common.assertSelected($selected[0], {text: 'One', value: 'one', section: 'foo'}); Common.assertSelected($selected[1], {text: 'Two', value: 'two', section: 'foo'}); }); }); ================================================ FILE: test/integration/single-selections.test.js ================================================ var Common = require('./common'); describe('Single Selection', () => { it('can add an item', () => { $("select").append(""); $("select").treeMultiselect(); assert.deepEqual($("select").val(), null); var $checkbox = Common.selectionCheckbox({checked: false}); assert.equal($checkbox.length, 1); $checkbox.click(); var $checkboxChecked = Common.selectionCheckbox({checked: true}); assert.equal($checkboxChecked.length, 1); assert.deepEqual($("select").val(), ['one']); }); it('can remove an item', () => { $("select").append(""); $("select").treeMultiselect(); assert.deepEqual($("select").val(), ['one']); var $checkbox = Common.selectionCheckbox({checked: true}); assert.equal($checkbox.length, 1); $checkbox.click(); var $checkboxChecked = Common.selectionCheckbox({checked: true}); assert.equal($checkboxChecked.length, 0); assert.deepEqual($("select").val(), null); }); it('can add an item with the same text', () => { $("select").append(""); $("select").append(""); $("select").treeMultiselect(); assert.deepEqual($("select").val(), ['two']); var $checkboxChecked = Common.selectionCheckbox({checked: true}); assert.equal($checkboxChecked.length, 1); var $checkbox = Common.selectionCheckbox(); assert.equal($checkbox.length, 2); $checkbox.first().click(); $checkboxChecked = Common.selectionCheckbox({checked: true}); assert.equal($checkboxChecked.length, 2); assert.deepEqual($("select").val(), ['two', 'one']); }); it('can add an item with the same value as another', () => { $("select").append(""); $("select").append(""); $("select").append(""); $("select").treeMultiselect(); assert.deepEqual($("select").val(), ['one']); var $checkboxChecked = Common.selectionCheckbox({checked: true}); assert.equal($checkboxChecked.length, 1); var $checkbox = Common.selectionCheckbox(); assert.equal($checkbox.length, 3); $checkbox.last().click(); $checkboxChecked = Common.selectionCheckbox({checked: true}); assert.equal($checkboxChecked.length, 2); assert.deepEqual($("select").val(), ['one', 'one']); }); it('can remove an item with the same value as another', () => { $("select").append(""); $("select").append(""); $("select").append(""); $("select").treeMultiselect(); assert.deepEqual($("select").val(), ['one', 'one']); var $checkboxChecked = Common.selectionCheckbox({checked: true}); assert.equal($checkboxChecked.length, 2); var $checkbox = Common.selectionCheckbox(); assert.equal($checkbox.length, 3); $checkbox.last().click(); $checkboxChecked = Common.selectionCheckbox({checked: true}); assert.equal($checkboxChecked.length, 1); assert.deepEqual($("select").val(), ['one']); }); it('can remove an item by selected item remove button', () => { $("select").append(""); $("select").treeMultiselect(); assert.deepEqual($("select").val(), ['one']); var $selected = Common.selected(); assert.equal($selected.length, 1); var $removeSpan = $selected.children(".remove-selected"); $removeSpan.click(); assert.equal(Common.selected().length, 0); assert.equal($("select").val(), null); }); it('can remove an item by unchecking selection checkbox', () => { $("select").append(""); $("select").treeMultiselect(); assert.deepEqual($("select").val(), ['one']); var $selections = Common.selection(); var $checkbox = $selections.children("input[type=checkbox]"); $checkbox.click(); assert.deepEqual($("select").val(), null); }); it('removing an item does not remove any others', () => { $("select").append(""); $("select").append(""); $("select").append(""); $("select").treeMultiselect(); assert.deepEqual($("select").val(), ['one', 'two', 'three']); var $selections = Common.selected(); assert.equal($selections.length, 3); var $removeSpan = $selections.first().children(".remove-selected"); $removeSpan.click(); assert.equal(Common.selected().length, 2); assert.deepEqual($("select").val(), ['two', 'three']); }); it('fires change event on original select', (done) => { $("select").append(""); $("select").treeMultiselect(); $("select").change(function() { done(); }); Common.selection().children("input[type=checkbox]").click(); }); }); ================================================ FILE: test/test-performance.html ================================================ Tree Multiselect test ================================================ FILE: test/test.html ================================================ Tree Multiselect test ================================================ FILE: test/unit/utility.test.js ================================================ var Ast = require('ast'); var Util = require('utility'); describe('Utility', () => { it('asserts', () => { var func = () => { Util.assert(true); } assert.doesNotThrow(func); func = () => { Util.assert(false); } assert.throws(func); func = () => { Util.assert(NaN); } assert.throws(func); }); it('getKey', () => { var el = $("
")[0]; assert.equal(Util.getKey(el), 4); el = $("
")[0]; assert.isNaN(Util.getKey(el)); el = $("
")[0]; assert.isNaN(Util.getKey(el)); }); it('isInteger', () => { assert.isTrue(Util.isInteger(1)); assert.isTrue(Util.isInteger(-1)); assert.isTrue(Util.isInteger(0)); assert.isTrue(Util.isInteger(" 3 ")); assert.isFalse(Util.isInteger(true)); assert.isFalse(Util.isInteger(null)); assert.isFalse(Util.isInteger([])); assert.isFalse(Util.isInteger(function () {})); }) describe('array', () => { it('subtract', () => { var arr1 = [1, 2, 5, 7, 0]; var arr2 = [2, 7, 8]; Util.array.subtract(arr1, arr2); assert.deepEqual(arr1, [1, 5, 0]); arr1 = []; arr2 = []; Util.array.subtract(arr1, arr2); assert.deepEqual(arr1, []); arr1 = []; arr2 = [0]; Util.array.subtract(arr1, arr2); assert.deepEqual(arr1, []); arr1 = [6, 8, 1256]; arr2 = []; Util.array.subtract(arr1, arr2); assert.deepEqual(arr1, [6, 8, 1256]); arr1 = ["foo", "bar"]; arr2 = ["baz"]; Util.array.subtract(arr1, arr2); assert.deepEqual(arr1, ["foo", "bar"]); }); it('uniq', () => { var arr = [1, 2, 5, 7, 0]; Util.array.uniq(arr); assert.deepEqual(arr, [1, 2, 5, 7, 0]); arr = []; Util.array.uniq(arr); assert.deepEqual(arr, []); arr = ["abc", "abc", "ghi"]; Util.array.uniq(arr); assert.deepEqual(arr, ["abc", "ghi"]); arr = [123, 678, 900, 123]; Util.array.uniq(arr); assert.deepEqual(arr, [123, 678, 900]); }); it('removeFalseyExceptZero', () => { var arr = [1, 4, 0, null, NaN, undefined, 3]; Util.array.removeFalseyExceptZero(arr); assert.deepEqual(arr, [1, 4, 0, 3]); }); it('moveEl in-place', () => { var arr = [0, 1, 2, 3, 4, 5]; Util.array.moveEl(arr, 5, 1); assert.deepEqual(arr, [0, 5, 1, 2, 3, 4]); arr = [0, 1, 2, 3, 4]; Util.array.moveEl(arr, 4, 0); assert.deepEqual(arr, [4, 0, 1, 2, 3]); arr = [0, 1, 2, 3, 4]; Util.array.moveEl(arr, 0, 4); assert.deepEqual(arr, [1, 2, 3, 4, 0]); }); it('intersect', () => { var arr = [0, 1, 2, 3, 4, 5]; var arr2 = [0, 2, 4, 6]; Util.array.intersect(arr, arr2); assert.deepEqual(arr, [0, 2, 4]); arr = [1, 17, 536, 24]; arr2 = [536, 0, 0, 0]; Util.array.intersect(arr, arr2); assert.deepEqual(arr, [536]); arr = [0, 0, 0, 0, 0, 0]; arr2 = [0, 1]; Util.array.intersect(arr, arr2); assert.deepEqual(arr, [0, 0, 0, 0, 0, 0]); }); it('intersectMany', () => { var arrays = [[1, 3, 5, 7], [2, 3, 6, 7], [3, 9]]; assert.deepEqual(Util.array.intersectMany(arrays), [3]); arrays = [[1, 3, 5, 7], [2, 3, 6, 7], [9]]; assert.deepEqual(Util.array.intersectMany(arrays), []); arrays = [[1, 2, 3, 4], [4, 5, 6, 7], [8, 9]]; assert.deepEqual(Util.array.intersectMany(arrays), []); arrays = [[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]]; assert.deepEqual(Util.array.intersectMany(arrays), [1, 2, 3, 4]); arrays = [[9, 10, 11, 12], [1, 2, 3, 4], [1, 2, 3, 4]]; assert.deepEqual(Util.array.intersectMany(arrays), []); }); it('flatten', () => { assert.deepEqual(Util.array.flatten([[1], [2], [3, [[4], 'foo', 'bar']]]), [1, 2, 3, 4, 'foo', 'bar']); assert.deepEqual(Util.array.flatten([]), []); assert.deepEqual(Util.array.flatten(null), null); assert.deepEqual(Util.array.flatten('foo'), 'foo'); }); }); describe('dom', () => { it('creates nodes with correct tag', () => { var node = Util.dom.createNode('div'); assert.equal(node.tagName, 'DIV'); node = Util.dom.createNode('span'); assert.equal(node.tagName, 'SPAN'); }); it('creates nodes with correct properties', () => { var props = { foo: 'bar', baz: 'over 9000', text: 'foo' } var node = Util.dom.createNode('div', props); assert.equal(node.attributes.length, 2); assert.equal(node.getAttribute('foo'), props.foo); assert.equal(node.getAttribute('baz'), props.baz); assert.equal(node.textContent, props.text); }); it('can tell that AST item is not a section', () => { var option = Ast.createItem({ id: 0, value: 'val', text: 'text', description: 'description' }); assert(option.isItem()); assert.isFalse(option.isSection()); }); it('creates selection node with all properties', () => { var section = Ast.createSection('name'); assert(section.isSection()); assert.isFalse(section.isItem()); }); it('creates selection node with value as text', () => { var option = Ast.createItem({ id: 0, value: 'val', text: null }); var node = Util.dom.createSelection(option, 0, true, true); assert.equal(node.getAttribute('data-value'), 'val'); }); }); });