Repository: NileshPatel17/ng-multiselect-dropdown Branch: main Commit: 7e45686d51de Files: 66 Total size: 121.4 KB Directory structure: gitextract_nsiqwz_z/ ├── .all-contributorsrc ├── .editorconfig ├── .github/ │ └── workflows/ │ └── nodejs.yml ├── .gitignore ├── CHANGELOG.md ├── ISSUE_TEMPLATE.md ├── README.md ├── angular.json ├── custom-theme.md ├── e2e/ │ ├── app.e2e-spec.ts │ ├── app.po.ts │ └── tsconfig.e2e.json ├── jest.config.js ├── karma.conf.js ├── ng-multiselect-dropdown.theme.scss ├── ng-package.json ├── package-lib-template.json ├── package.json ├── protractor.conf.js ├── publish-package.md ├── src/ │ ├── app/ │ │ ├── app.component.html │ │ ├── app.component.scss │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── components/ │ │ │ ├── sample-section.component.html │ │ │ ├── sample-section.component.ts │ │ │ ├── select/ │ │ │ │ ├── multiple-demo.html │ │ │ │ ├── multiple-demo.ts │ │ │ │ ├── single-demo.html │ │ │ │ └── single-demo.ts │ │ │ └── select-section.ts │ │ └── index.ts │ ├── assets/ │ │ └── .gitkeep │ ├── code-viewer/ │ │ ├── code-viewer.module.ts │ │ └── code-viewer.ts │ ├── environments/ │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── index.html │ ├── jest-global-mocks.ts │ ├── main.ts │ ├── ng-multiselect-dropdown/ │ │ ├── src/ │ │ │ ├── click-outside.directive.ts │ │ │ ├── index.ts │ │ │ ├── list-filter.pipe.ts │ │ │ ├── multi-select.component.html │ │ │ ├── multi-select.component.scss │ │ │ ├── multiselect.component.ts │ │ │ ├── multiselect.model.ts │ │ │ ├── ng-multiselect-dropdown.module.ts │ │ │ └── public_api.ts │ │ └── test/ │ │ ├── helper.ts │ │ ├── list-filter.pipe.spec.ts │ │ ├── multi-select-disabled-item.component.spec.ts │ │ ├── multi-select.component.spec.ts │ │ ├── multi-select.component1.spec.ts │ │ ├── multi-select.component2.spec.ts │ │ └── various-input-data.spec.ts │ ├── polyfills.ts │ ├── setup-jest.ts │ ├── styles.scss │ ├── test.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ ├── tsconfig.spec.json │ └── typings.d.ts ├── tsconfig.json └── tslint.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .all-contributorsrc ================================================ { "projectName": "ng-multiselect-dropdown", "projectOwner": "Nilesh Patel", "repoType": "github", "repoHost": "https://github.com", "files": [ "README.md" ], "imageSize": 100, "commit": false, "commitConvention": "angular", "contributors": [ { "login": "tomsaleeba", "name": "Tom Saleeba", "avatar_url": "https://avatars0.githubusercontent.com/u/1773838?v=4", "profile": "http://blog.techotom.com", "contributions": [ "code" ] }, { "login": "synap5e", "name": "Simon Pinfold", "avatar_url": "https://avatars0.githubusercontent.com/u/2515062?v=4", "profile": "https://overtrack.gg", "contributions": [ "code" ] }, { "login": "sushil-suthar", "name": "Sushil Suthar", "avatar_url": "https://avatars0.githubusercontent.com/u/32981723?v=4", "profile": "http://helpfordeveloper.blogspot.in", "contributions": [ "code" ] }, { "login": "sacgrover", "name": "Sachin Grover", "avatar_url": "https://avatars1.githubusercontent.com/u/1292182?v=4", "profile": "http://sacgro.com", "contributions": [ "code" ] }, { "login": "WWL-MikeRoberts", "name": "Mike Roberts", "avatar_url": "https://avatars3.githubusercontent.com/u/9750056?v=4", "profile": "https://github.com/WWL-MikeRoberts", "contributions": [ "code" ] }, { "login": "DsosaV", "name": "David Sosa", "avatar_url": "https://avatars2.githubusercontent.com/u/3926475?v=4", "profile": "https://github.com/DsosaV", "contributions": [ "code" ] } ], "contributorsPerLine": 7 } ================================================ FILE: .editorconfig ================================================ # Editor configuration, see http://editorconfig.org root = true [*] charset = utf-8 indent_style = space indent_size = 2 insert_final_newline = true trim_trailing_whitespace = true [*.md] max_line_length = off trim_trailing_whitespace = false ================================================ FILE: .github/workflows/nodejs.yml ================================================ name: Node CI - run test on: [pull_request] jobs: build: runs-on: ubuntu-latest strategy: matrix: node-version: [16.x] steps: - uses: actions/checkout@v1 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - name: npm install and test run: | npm install npm run test:ci ================================================ FILE: .gitignore ================================================ # See http://help.github.com/ignore-files/ for more about ignoring files. # compiled output /dist /dist-lib /tmp /out-tsc /.angular dist-lib.tgz # dependencies /node_modules # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json .vscode # misc /.sass-cache /connect.lock /coverage /libpeerconnection.log npm-debug.log testem.log /typings yarn-error.log # e2e /e2e/*.js /e2e/*.map # System Files .DS_Store Thumbs.db package-lock.json settings.json TODO.md ng-multiselect-dropdown - Copy ================================================ FILE: CHANGELOG.md ================================================ ================================================ FILE: ISSUE_TEMPLATE.md ================================================ **Angular version**: **ng-multiselect-dropdown version**: **Description of issue**: **Steps to reproduce**: **Expected result**: **Actual result**: **Demo**: Please share sample code link using StackBlitz or codesandbox Any relevant code: ``` ``` ================================================ FILE: README.md ================================================ # Angular Multiselect Dropdown [![All Contributors](https://img.shields.io/badge/all_contributors-6-orange.svg?style=flat-square)](#contributors-) [![npm version](https://img.shields.io/npm/v/ng-multiselect-dropdown.svg)](https://www.npmjs.com/package/ng-multiselect-dropdown) [![downloads](https://img.shields.io/npm/dt/ng-multiselect-dropdown.svg)](https://www.npmjs.com/package/ng-multiselect-dropdown) [![downloads](https://img.shields.io/npm/dm/ng-multiselect-dropdown.svg)](https://www.npmjs.com/package/ng-multiselect-dropdown) Angular multiselect dropdown component for web applications. Easy to integrate and use. It can be bind to any custom data source. # [Demo](https://nileshpatel17.github.io/ng-multiselect-dropdown/) ![demo](Screenshots/ng-multiselect-dropdown_v0.1.6.gif) ## Getting Started ## Features - dropdown with single/multiple selction option - bind to any custom data source - search item with custom placeholder text - limit selection - select/de-select all items - custom theme ### Installation ``` npm install ng-multiselect-dropdown ``` And then include it in your module (see [app.module.ts](https://github.com/NileshPatel17/ng-multiselect-dropdown/blob/master/src/app/app.module.ts)): ```ts import { NgMultiSelectDropDownModule } from 'ng-multiselect-dropdown'; // ... @NgModule({ imports: [ NgMultiSelectDropDownModule.forRoot() // ... ] // ... }) export class AppModule {} ``` ### Usage ```ts import { Component, OnInit } from '@angular/core'; import { IDropdownSettings } from 'ng-multiselect-dropdown'; export class AppComponent implements OnInit { dropdownList = []; selectedItems = []; dropdownSettings = {}; ngOnInit() { this.dropdownList = [ { item_id: 1, item_text: 'Mumbai' }, { item_id: 2, item_text: 'Bangaluru' }, { item_id: 3, item_text: 'Pune' }, { item_id: 4, item_text: 'Navsari' }, { item_id: 5, item_text: 'New Delhi' } ]; this.selectedItems = [ { item_id: 3, item_text: 'Pune' }, { item_id: 4, item_text: 'Navsari' } ]; this.dropdownSettings:IDropdownSettings = { singleSelection: false, idField: 'item_id', textField: 'item_text', selectAllText: 'Select All', unSelectAllText: 'UnSelect All', itemsShowLimit: 3, allowSearchFilter: true }; } onItemSelect(item: any) { console.log(item); } onSelectAll(items: any) { console.log(items); } } ``` ```html ``` ### Settings | Setting | Type | Description | Default Value | | :----------------------------- | :--------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------ | | singleSelection | Boolean | Mode of this component. If set `true` user can select more than one option. | false | | placeholder | String | Text to be show in the dropdown, when no items are selected. | 'Select' | | disabled | Boolean | Disable the dropdown | false | | data | Array | Array of items from which to select. Should be an array of objects with id and `text` properties. You can also use custom properties. In that case you need to map idField and `textField` properties. As convenience, you may also pass an array of strings, in which case the same string is used for both the ID and the text(no mapping is required) | n/a | | idField | String | map id field in case of custom array of object | 'id' | | textField | String | map text field in case of custom array of object | 'text' | | enableCheckAll | Boolean | Enable the option to select all items in list | false | | selectAllText | String | Text to display as the label of select all option | Select All | | unSelectAllText | String | Text to display as the label of unSelect option | UnSelect All | | allowSearchFilter | Boolean | Enable filter option for the list. | false | | searchPlaceholderText | String | custom search placeholder | Search | | clearSearchFilter | Boolean | clear search filter on dropdown close | true | | maxHeight | Number | Set maximum height of the dropdown list in px. | 197 | | itemsShowLimit | Number | Limit the number of items to show in the input field. If not set will show all selected. | All | | limitSelection | Number | Limit the selection of number of items from the dropdown list. Once the limit is reached, all unselected items gets disabled. | none | | searchPlaceholderText | String | Custom text for the search placeholder text. Default value would be 'Search' | 'Search' | | noDataAvailablePlaceholderText | String | Custom text when no data is available. | 'No data available' | | closeDropDownOnSelection | Boolean | Closes the dropdown when item is selected. applicable only in cas of single selection | false | | defaultOpen | Boolean | open state of dropdown | false | | allowRemoteDataSearch | Boolean | allow search remote api if no data is present. | false | ### Callback Methods - `onSelect` - Return the selected item when an item is checked. Example : (onSelect)="onItemSelect($event)" - `onSelectAll` - Return the all items. Example : (onSelectAll)="onSelectAll($event)". - `onDeSelect` - Return the unselected item when an item is unchecked. Example : (onDeSelect)="onItemDeSelect($event)" - `onFilterChange` - Return the key press. Example : (onFilterChange)="onFilterChange($event)" - `onDropDownClose`- Example : (onDropDownClose)="onDropDownClose()" ### Custom Theme - The component package has a themes folder in node_modules at `ng-multiselet-dropdown\themes\ng-multiselect-dropdown.theme.scss` - Include the `ng-multiselet-dropdown.theme.css` in `angular-cli.json` (for versions below angular 6) and `angular.json` (for version 6 or more). - [Refer this file](https://github.com/NileshPatel17/ng-multiselect-dropdown/blob/master/custom-theme.md) on how to add the css file to your angular project. ## Custom Template(in beta): ### Variables can be used in template 1. id: return id as number 2. option: return option text. return string 3. isSelected: determine if item is selected or not. returns boolean Template for each item ``` {{option}} ``` Template for selected item ``` {{option}} ``` [Demo](https://codesandbox.io/s/custom-template-uyo0o?file=/src/app/app.component.html) ### Run locally - Clone the repository or downlod the .zip,.tar files. - Run `npm install` - Run `ng serve` for a dev server - Navigate to `http://localhost:4200/` ### Library Build / NPM Package Run `yarn build:lib` to build the library and generate an NPM package. The build artifacts will be stored in the dist-lib/ folder. ## Running unit tests Run `yarn test` to execute the unit tests. ## Development This project was generated with Angular CLI version 1.7.1. ## Contributions Contributions are welcome, please open an issue and preferrably file a pull request. ### Opening Issue Please share sample code using codesandbox.com or stackblitz.com to help me re-produce the issue. ## License MIT License. ## Contributors ✨ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):

Tom Saleeba

💻

Simon Pinfold

💻

Sushil Suthar

💻

Sachin Grover

💻

Mike Roberts

💻

David Sosa

💻

Sergiy Gedeon

💻
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! ================================================ FILE: angular.json ================================================ { "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, "newProjectRoot": "projects", "projects": { "ng-multiselect-dropdown-base": { "root": "", "sourceRoot": "src", "projectType": "application", "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { "aot": true, "outputPath": "dist", "index": "src/index.html", "main": "src/main.ts", "tsConfig": "src/tsconfig.app.json", "polyfills": "src/polyfills.ts", "assets": [ "src/assets", "src/favicon.ico" ], "styles": [ "src/styles.scss" ], "scripts": [] }, "configurations": { "production": { "budgets": [ { "type": "anyComponentStyle", "maximumWarning": "6kb" } ], "optimization": true, "outputHashing": "all", "sourceMap": false, "extractCss": true, "namedChunks": false, "aot": true, "extractLicenses": true, "vendorChunk": false, "buildOptimizer": true, "fileReplacements": [ { "replace": "src/environments/environment.ts", "with": "src/environments/environment.prod.ts" } ] } } }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { "browserTarget": "ng-multiselect-dropdown-base:build" }, "configurations": { "production": { "browserTarget": "ng-multiselect-dropdown-base:build:production" } } }, "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { "browserTarget": "ng-multiselect-dropdown-base:build" } }, "test": { "builder": "@angular-devkit/build-angular:karma", "options": { "main": "src/test.ts", "karmaConfig": "./karma.conf.js", "polyfills": "src/polyfills.ts", "tsConfig": "src/tsconfig.spec.json", "scripts": [], "styles": [ "src/styles.scss" ], "assets": [ "src/assets", "src/favicon.ico" ] } }, "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ "src/tsconfig.app.json", "src/tsconfig.spec.json" ], "exclude": [ "**/node_modules/**" ] } } } }, "ng-multiselect-dropdown-base-e2e": { "root": "", "sourceRoot": "e2e", "projectType": "application", "architect": { "e2e": { "builder": "@angular-devkit/build-angular:protractor", "options": { "protractorConfig": "./protractor.conf.js", "devServerTarget": "ng-multiselect-dropdown-base:serve" } }, "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ "e2e/tsconfig.e2e.json" ], "exclude": [ "**/node_modules/**" ] } } } } }, "schematics": { "@schematics/angular:component": { "prefix": "ng", "style": "scss" }, "@schematics/angular:directive": { "prefix": "ng" } } } ================================================ FILE: custom-theme.md ================================================ # Custom Theme #### Step 1: 1. copy 'ng-multiselect-dropdown.theme.scss' file located at `node_modules\ng-multiselet-dropdown\themes\ng-multiselect-dropdown.theme.scss` ** if you are using version below 0.2.11, you need to get it from https://github.com/NileshPatel17/ng-multiselect-dropdown/themes/ng-multiselet-dropdown.theme.css #### Step 2: 1. paste this file in your project wherever you want. Include this file `ng-multiselet-dropdown.theme.css` in `angular-cli.json` (for versions below angular 6) and `angular.json` (for version 6 or more). file name can be anything. ![](Screenshots/theme-step-2.png) #### Step 3: 1. Change $base-color in 'ng-multiselect-dropdown.theme.scss' ![](Screenshots/theme-step-3.png) ### You can also checkout [sample code](https://codesandbox.io/s/custom-theme-p1556), i created in codesandbox. ================================================ FILE: e2e/app.e2e-spec.ts ================================================ import { NgTest2Page } from './app.po'; describe('ng-test2 App', () => { let page: NgTest2Page; beforeEach(() => { page = new NgTest2Page(); }); it('should display welcome message', () => { page.navigateTo(); expect(page.getParagraphText()).toEqual('Welcome to app!'); }); }); ================================================ FILE: e2e/app.po.ts ================================================ import { browser, by, element } from 'protractor'; export class NgTest2Page { navigateTo() { return browser.get('/'); } getParagraphText() { return element(by.css('app-root h1')).getText(); } } ================================================ FILE: e2e/tsconfig.e2e.json ================================================ { "extends": "../tsconfig.json", "compilerOptions": { "outDir": "../out-tsc/e2e", "baseUrl": "./", "module": "commonjs", "target": "es5", "types": [ "jasmine", "jasminewd2", "node" ] } } ================================================ FILE: jest.config.js ================================================ const jestConfig = { "preset": "jest-preset-angular", "setupFilesAfterEnv": [ "/src/setup-jest.ts" ], "testPathIgnorePatterns": [ "/node_modules/", "/dist/", "/dist-lib/", "/src/test.ts" ], "globals": { "ts-jest": { "tsConfig": "/src/tsconfig.spec.json", "stringifyContentPathRegex": "\\.html$", "astTransformers": [ "/node_modules/jest-preset-angular/InlineHtmlStripStylesTransformer" ] } } } module.exports = jestConfig; ================================================ FILE: karma.conf.js ================================================ // Karma configuration file, see link for more information // https://karma-runner.github.io/0.13/config/configuration-file.html module.exports = function (config) { config.set({ basePath: '', frameworks: ['jasmine', '@angular-devkit/build-angular'], plugins: [ require('karma-jasmine'), require('karma-chrome-launcher'), require('karma-jasmine-html-reporter'), require('karma-coverage-istanbul-reporter'), require('@angular-devkit/build-angular/plugins/karma') ], client:{ clearContext: false // leave Jasmine Spec Runner output visible in browser }, coverageIstanbulReporter: { dir: require('path').join(__dirname, 'coverage'), reports: [ 'html', 'lcovonly' ], fixWebpackSourcePaths: true }, reporters: ['progress', 'kjhtml'], port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: true, browsers: ['Chrome'], singleRun: false }); }; ================================================ FILE: ng-multiselect-dropdown.theme.scss ================================================ $base-color: #337ab7; $disable-background-color: #eceeef; .multiselect-dropdown { position: relative; width: 100%; font-size: inherit; font-family: inherit; .dropdown-btn { display: inline-block; border: 1px solid #adadad; width: 100%; padding: 6px 12px; margin-bottom: 0; font-weight: normal; line-height: 1.52857143; text-align: left; vertical-align: middle; cursor: pointer; background-image: none; border-radius: 4px; .selected-item { border: 1px solid $base-color; margin-right: 4px; background: $base-color; padding: 0px 5px; color: #fff; border-radius: 2px; float: left; a { text-decoration: none; } } .selected-item:hover { box-shadow: 1px 1px #959595; } .dropdown-down { display: inline-block; top: 10px; width: 0; height: 0; border-top: 10px solid #adadad; border-left: 10px solid transparent; border-right: 10px solid transparent; } .dropdown-up { display: inline-block; width: 0; height: 0; border-bottom: 10px solid #adadad; border-left: 10px solid transparent; border-right: 10px solid transparent; } } .disabled { & > span { background-color: $disable-background-color; } } } .dropdown-list { position: absolute; padding-top: 6px; width: 100%; z-index: 9999; border: 1px solid #ccc; border-radius: 3px; background: #fff; margin-top: 10px; box-shadow: 0px 1px 5px #959595; ul { padding: 0px; list-style: none; overflow: auto; margin: 0px; } li { padding: 6px 10px; cursor: pointer; text-align: left; } .filter-textbox { border-bottom: 1px solid #ccc; position: relative; padding: 10px; input { border: 0px; width: 100%; padding: 0px 0px 0px 26px; } input:focus { outline: none; } } } .multiselect-item-checkbox input[type='checkbox'] { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; } .multiselect-item-checkbox input[type='checkbox']:focus + div:before, .multiselect-item-checkbox input[type='checkbox']:hover + div:before { border-color: $base-color; background-color: #f2f2f2; } .multiselect-item-checkbox input[type='checkbox']:active + div:before { transition-duration: 0s; } .multiselect-item-checkbox input[type='checkbox'] + div { position: relative; padding-left: 2em; vertical-align: middle; user-select: none; cursor: pointer; margin: 0px; color: #000; } .multiselect-item-checkbox input[type='checkbox'] + div:before { box-sizing: content-box; content: ''; color: $base-color; position: absolute; top: 50%; left: 0; width: 14px; height: 14px; margin-top: -9px; border: 2px solid $base-color; text-align: center; transition: all 0.4s ease; } .multiselect-item-checkbox input[type='checkbox'] + div:after { box-sizing: content-box; content: ''; background-color: $base-color; position: absolute; top: 50%; left: 4px; width: 10px; height: 10px; margin-top: -5px; transform: scale(0); transform-origin: 50%; transition: transform 200ms ease-out; } .multiselect-item-checkbox input[type='checkbox']:disabled + div:before { border-color: #cccccc; } .multiselect-item-checkbox input[type='checkbox']:disabled:focus + div:before .multiselect-item-checkbox input[type='checkbox']:disabled:hover + div:before { background-color: inherit; } .multiselect-item-checkbox input[type='checkbox']:disabled:checked + div:before { background-color: #cccccc; } .multiselect-item-checkbox input[type='checkbox'] + div:after { background-color: transparent; top: 50%; left: 4px; width: 8px; height: 3px; margin-top: -4px; border-style: solid; border-color: #ffffff; border-width: 0 0 3px 3px; border-image: none; transform: rotate(-45deg) scale(0); } .multiselect-item-checkbox input[type='checkbox']:checked + div:after { content: ''; transform: rotate(-45deg) scale(1); transition: transform 200ms ease-out; } .multiselect-item-checkbox input[type='checkbox']:checked + div:before { animation: borderscale 200ms ease-in; background: $base-color; } .multiselect-item-checkbox input[type='checkbox']:checked + div:after { transform: rotate(-45deg) scale(1); } @keyframes borderscale { 50% { box-shadow: 0 0 0 2px $base-color; } } ================================================ FILE: ng-package.json ================================================ { "dest": "dist-lib", "lib": { "entryFile": "src/ng-multiselect-dropdown/src/public_api.ts" } } ================================================ FILE: package-lib-template.json ================================================ { "name": "ng-multiselect-dropdown", "version": "0.1.0", "description": "Angular Multi-Select Dropdown", "keywords": [ "angular2", "angular4", "angular multiselect dropdown", "angular2 multiselect dropdown", "angular4 multiselect dropdown", "ng2 multiselect dropdown", "ng4 multiselect dropdown" ], "author": "Nilesh Patel", "license": "MIT", "repository": { "type": "git", "url": "git+ssh://git@github.com/nileshpatel17/ng-multiselect-dropdown.git" }, "bugs": { "url": "https://github.com/nileshpatel17/ng-multiselect-dropdown/issues" }, "homepage": "nileshpatel17/ng-multiselect-dropdown#readme", "peerDependencies": { "@angular/common": "^2.3.1 || >=4.0.0", "@angular/core": "^2.3.1 || >=4.0.0", "@angular/forms": "^2.3.1 || >=4.0.0" } } ================================================ FILE: package.json ================================================ { "name": "ng-multiselect-dropdown", "version": "1.0.0", "private": true, "description": "Angular Multi-Select Dropdown", "author": "Nilesh Patel", "license": "MIT", "scripts": { "ng": "ng", "start": "ng serve --port 4201", "build": "ng build", "ng:test": "ng test", "lint": "ng lint", "test": "jest --watch", "test:ci": "jest --runInBand", "test:coverage": "jest --coverage", "build:prod": "ng build --prod --base-href https://nileshpatel17.github.io/ng-multiselect-dropdown/", "clear:lib": "rimraf dist-lib", "postcopyfiles": "copyfiles -u 1 ./dist-lib/**/*.* node_modules/ng-multiselect-dropdown", "copyfiles": "mkdir dist-lib/themes && copyfiles ng-multiselect-dropdown.theme.scss dist-lib/themes", "build:lib": "npm run clear:lib && ng-packagr -p ng-package.json", "postbuild:lib": "npm run copyfiles", "prepublish": "npm run build:prod", "publish": "ngh --no-silent false --name=\"nileshpatel17\" --email=\"nilesh.nvs@hotmail.com\"", "deploy": "ng build --prod --base-href /ng-multiselect-dropdown/ && npm run deployOnly", "deployOnly": "angular-cli-ghpages --no-silent --repo=https://github.com/NileshPatel17/ng-multiselect-dropdown.git --name=\"Nilesh Patel\" --email=nilesh.nvs@hotmail.com", "contributors:add": "all-contributors add", "contributors:generate": "all-contributors generate" }, "keywords": [ "angular4", "angular8", "angular multiselect dropdown", "angular4 multiselect dropdown", "angular8 multiselect dropdown", "ng multiselect dropdown", "ng4 multiselect dropdown", "ng8 multiselect dropdown" ], "repository": { "type": "git", "url": "https://github.com/nileshpatel17/ng-multiselect-dropdown.git" }, "bugs": { "url": "https://github.com/nileshpatel17/ng-multiselect-dropdown/issues" }, "homepage": "https://github.com/nileshpatel17/ng-multiselect-dropdown#readme", "peerDependencies": {}, "devDependencies": { "@angular-devkit/build-angular": "~16.1.6", "@angular/animations": "16.1.7", "@angular/cli": "^16.1.6", "@angular/common": "16.1.7", "@angular/compiler": "16.1.7", "@angular/compiler-cli": "16.1.7", "@angular/core": "16.1.7", "@angular/forms": "16.1.7", "@angular/http": "7.2.15", "@angular/language-service": "16.1.7", "@angular/platform-browser": "16.1.7", "@angular/platform-browser-dynamic": "16.1.7", "@angular/router": "16.1.7", "@types/jasmine": "~3.4.0", "@types/jasminewd2": "~2.0.2", "@types/node": "^12.11.1", "angular-cli-ghpages": "^0.6.0-rc.2", "angular2-markdown": "^2.2.3", "codelyzer": "^5.1.2", "copyfiles": "^2.0.0", "core-js": "^3.2.1", "jasmine-core": "~3.5.0", "jasmine-spec-reporter": "~5.0.0", "jest": "^29.6.2", "jest-preset-angular": "^7.1.1", "karma": "~6.4.2", "karma-chrome-launcher": "~3.1.0", "karma-cli": "~2.0.0", "karma-coverage-istanbul-reporter": "~3.0.2", "karma-jasmine": "~4.0.0", "karma-jasmine-html-reporter": "^1.5.0", "ng-multiselect-dropdown": "^0.2.11", "ng-packagr": "^16.1.0", "ngx-bootstrap": "^5.1.1", "protractor": "~7.0.0", "rimraf": "^3.0.0", "rxjs": "^6.2.1", "rxjs-compat": "^6.2.1", "ts-node": "~8.3.0", "tslint": "~6.1.0", "typescript": "4.9.5", "zone.js": "~0.13.1" }, "dependencies": { "tslib": "^2.0.0" } } ================================================ FILE: protractor.conf.js ================================================ // Protractor configuration file, see link for more information // https://github.com/angular/protractor/blob/master/lib/config.ts const { SpecReporter } = require('jasmine-spec-reporter'); exports.config = { allScriptsTimeout: 11000, specs: [ './e2e/**/*.e2e-spec.ts' ], capabilities: { 'browserName': 'chrome' }, directConnect: true, baseUrl: 'http://localhost:4200/', framework: 'jasmine', jasmineNodeOpts: { showColors: true, defaultTimeoutInterval: 30000, print: function() {} }, onPrepare() { require('ts-node').register({ project: 'e2e/tsconfig.e2e.json' }); jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); } }; ================================================ FILE: publish-package.md ================================================ ## step to publish 1. yarn build:lib 2. navigate to dist-lib folder 3. mark private to false 4. login to npm registry (npm login) 5. yarn publish 6. yarn deployOnly ## Angular Multiselect Dropdown [![npm version](https://img.shields.io/npm/v/ng-multiselect-dropdown.svg)](https://www.npmjs.com/package/ng-multiselect-dropdown) [![downloads](https://img.shields.io/npm/dt/ng-multiselect-dropdown.svg)](https://www.npmjs.com/package/ng-multiselect-dropdown) [![npm](https://img.shields.io/npm/dm/localeval.svg)](https://www.npmjs.com/package/ng-multiselect-dropdown) [![npm](https://img.shields.io/npm/dw/localeval.svg)](https://www.npmjs.com/package/ng-multiselect-dropdown) ================================================ FILE: src/app/app.component.html ================================================

ng-multiselect-dropdown

Native Angular component for Multiple Select

View on GitHub
================================================ FILE: src/app/app.component.scss ================================================ ================================================ FILE: src/app/app.component.ts ================================================ import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent implements OnInit { ngOnInit() { } } ================================================ FILE: src/app/app.module.ts ================================================ import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { BrowserModule } from '@angular/platform-browser'; import { TabsModule } from 'ngx-bootstrap/tabs'; import { ButtonsModule } from 'ngx-bootstrap/buttons'; import { NgMultiSelectDropDownModule } from '../ng-multiselect-dropdown/src'; // import { NgMultiSelectDropDownModule } from 'ng-multiselect-dropdown'; import { SelectSectionComponent } from './components/select-section'; import { SampleSectionComponent } from './components/sample-section.component'; import { SingleDemoComponent } from './components/select/single-demo'; import { MultipleDemoComponent } from './components/select/multiple-demo'; import { ShCodeViewer } from '../code-viewer/code-viewer.module'; import { AppComponent } from './app.component'; @NgModule({ declarations: [SelectSectionComponent, SampleSectionComponent, SingleDemoComponent, MultipleDemoComponent, AppComponent], imports: [ FormsModule, ReactiveFormsModule, BrowserModule, TabsModule.forRoot(), ButtonsModule.forRoot(), NgMultiSelectDropDownModule.forRoot(), ShCodeViewer ], providers: [], bootstrap: [AppComponent] }) export class AppModule {} ================================================ FILE: src/app/components/sample-section.component.html ================================================ ================================================ FILE: src/app/components/sample-section.component.ts ================================================ import { Component, Input } from '@angular/core'; @Component({ selector: 'sample-section', templateUrl: './sample-section.component.html' }) export class SampleSectionComponent{ @Input() public desc: any; } ================================================ FILE: src/app/components/select/multiple-demo.html ================================================

Select Multiple Cities

Option

Settings
       {{dropdownSettings | json}}
      
================================================ FILE: src/app/components/select/multiple-demo.ts ================================================ import { FormBuilder, FormGroup } from '@angular/forms'; import { Component, OnInit } from '@angular/core'; import { IDropdownSettings } from '../../../ng-multiselect-dropdown/src'; @Component({ selector: 'multiple-demo', templateUrl: './multiple-demo.html' }) export class MultipleDemoComponent implements OnInit { myForm: FormGroup; disabled = false; ShowFilter = true; showAll = true; limitSelection = false; limitShow = false; disableBangalore = true; cities: Array = []; selectedItems: Array = []; dropdownSettings: IDropdownSettings = {}; htmlCode = ` <form [formGroup]="myForm"> <ng-multiselect-dropdown name="city" [placeholder]="'Select City'" [data]="cities" formControlName="city" [disabled]="disabled" [settings]="dropdownSettings" (onSelect)="onItemSelect($event)" (onDeSelect)="onItemDeSelect($event)"> </ng-multiselect-dropdown> </form> `; typescriptCode = ` import { FormBuilder, FormGroup } from '@angular/forms'; import { Component, OnInit } from '@angular/core'; @Component({ selector: 'multiple-demo', templateUrl: './multiple-demo.html' }) export class MultipleDemoComponent implements OnInit { myForm:FormGroup; disabled = false; ShowFilter = false; limitSelection = false; limitShow = false; cities: Array = []; selectedItems: Array = []; dropdownSettings: any = {}; constructor(private fb: FormBuilder) {} ngOnInit() { this.cities = [ { item_id: 1, item_text: 'New Delhi' }, { item_id: 2, item_text: 'Mumbai' }, { item_id: 3, item_text: 'Bangalore' }, { item_id: 4, item_text: 'Pune' }, { item_id: 5, item_text: 'Chennai' }, { item_id: 6, item_text: 'Navsari' } ]; this.selectedItems = [{ item_id: 4, item_text: 'Pune' }, { item_id: 6, item_text: 'Navsari' }]; this.dropdownSettings = { singleSelection: false, idField: 'item_id', textField: 'item_text', selectAllText: 'Select All', unSelectAllText: 'UnSelect All', itemsShowLimit: 99999, allowSearchFilter: this.ShowFilter }; this.myForm = this.fb.group({ city: [this.selectedItems] }); } onItemSelect(item: any) { console.log('onItemSelect', item); } onSelectAll(items: any) { console.log('onSelectAll', items); } toogleShowFilter() { this.ShowFilter = !this.ShowFilter; this.dropdownSettings = Object.assign({}, this.dropdownSettings, { allowSearchFilter: this.ShowFilter }); } handleLimitSelection() { if (this.limitSelection) { this.dropdownSettings = Object.assign({}, this.dropdownSettings, { limitSelection: 2 }); } else { this.dropdownSettings = Object.assign({}, this.dropdownSettings, { limitSelection: null }); } } handleLimitShow() { if (this.limitShow) { this.dropdownSettings = Object.assign({}, this.dropdownSettings, { itemsShowLimit: 3 }); } else { this.dropdownSettings = Object.assign({}, this.dropdownSettings, { itemsShowLimit: 999999 }); } console.log() } } `; constructor(private fb: FormBuilder) {} ngOnInit() { this.cities = [ { item_id: 1, item_text: 'New Delhi' }, { item_id: 2, item_text: 'Mumbai' }, { item_id: 3, item_text: 'Bangalore', isDisabled: this.disableBangalore }, { item_id: 4, item_text: 'Pune' }, { item_id: 5, item_text: 'Chennai' }, { item_id: 6, item_text: 'Navsari' } ]; this.selectedItems = [ { item_id: 4, item_text: 'Pune' }, { item_id: 6, item_text: 'Navsari' } ]; this.dropdownSettings = { singleSelection: false, defaultOpen: false, idField: 'item_id', textField: 'item_text', selectAllText: 'Select All', unSelectAllText: 'UnSelect All', enableCheckAll: this.showAll, itemsShowLimit: 999999, allowSearchFilter: this.ShowFilter }; this.myForm = this.fb.group({ city: [this.selectedItems] }); } onItemSelect(item: any) { console.log('onItemSelect', item); console.log('form model', this.myForm.get('city').value); } onItemDeSelect(item: any) { console.log('onItem DeSelect', item); console.log('form model', this.myForm.get('city').value); } onSelectAll(items: any) { console.log('onSelectAll', items); } onDropDownClose() { console.log('dropdown closed'); } toogleShowAll() { this.showAll = !this.showAll; this.dropdownSettings = Object.assign({}, this.dropdownSettings, { enableCheckAll: this.showAll }); } toogleShowFilter() { this.ShowFilter = !this.ShowFilter; this.dropdownSettings = Object.assign({}, this.dropdownSettings, { allowSearchFilter: this.ShowFilter }); } handleLimitSelection() { if (this.limitSelection) { this.dropdownSettings = Object.assign({}, this.dropdownSettings, { limitSelection: 2 }); } else { this.dropdownSettings = Object.assign({}, this.dropdownSettings, { limitSelection: -1 }); } } handleLimitShow() { if (this.limitShow) { this.dropdownSettings = Object.assign({}, this.dropdownSettings, { itemsShowLimit: 3 }); } else { this.dropdownSettings = Object.assign({}, this.dropdownSettings, { itemsShowLimit: 999999 }); } console.log() } handleDisableBangalore() { this.cities[2].isDisabled = this.disableBangalore; this.cities = [...this.cities]; } handleReset() { this.myForm.get('city').setValue([]); } } ================================================ FILE: src/app/components/select/single-demo.html ================================================

Select a single city

Option

Settings
        {{dropdownSettings | json}}
      
================================================ FILE: src/app/components/select/single-demo.ts ================================================ import { Component, OnInit } from '@angular/core'; @Component({ selector: 'single-demo', templateUrl: './single-demo.html' }) export class SingleDemoComponent implements OnInit { cities: Array = []; selectedItem: Array = []; dropdownSettings: any = {}; closeDropdownSelection = false; disabled = false; htmlCode = ` <ng-multiselect-dropdown name="city" [data]="cities" [(ngModel)]="selectedItem" [settings]="dropdownSettings" (onSelect)="onItemSelect($event)" [disabled]="disabled" </ng-multiselect-dropdown> `; typescriptCode = ` import { Component, OnInit } from '@angular/core'; @Component({ selector: 'single-demo', templateUrl: './single-demo.html' }) export class SingleDemoComponent implements OnInit { cities: Array = []; selectedItem: Array = []; dropdownSettings: any = {}; closeDropdownSelection=false; disabled=false; ngOnInit() { this.cities = ['Mumbai', 'New Delhi', 'Bangaluru', 'Pune', 'Navsari']; this.selectedItem = ['Pune']; this.dropdownSettings = { singleSelection: true, selectAllText: 'Select All', unSelectAllText: 'UnSelect All', allowSearchFilter: true, closeDropDownOnSelection: this.closeDropdownSelection }; } onItemSelect(item: any) { console.log('onItemSelect', item); } toggleCloseDropdownSelection() { this.closeDropdownSelection = !this.closeDropdownSelection; this.dropdownSettings = Object.assign({}, this.dropdownSettings,{closeDropDownOnSelection: this.closeDropdownSelection}); } } `; ngOnInit() { this.cities = ['Mumbai', 'New Delhi', 'Bangaluru', 'Pune', 'Navsari']; this.dropdownSettings = { singleSelection: true, selectAllText: 'Select All', unSelectAllText: 'UnSelect All', allowSearchFilter: true, closeDropDownOnSelection: this.closeDropdownSelection }; this.selectedItem = ['Mumbai']; } onItemSelect(item: any) { console.log('onItemSelect', item); console.log('selectedItem', this.selectedItem); } toggleCloseDropdownSelection() { this.closeDropdownSelection = !this.closeDropdownSelection; this.dropdownSettings = Object.assign({}, this.dropdownSettings, { closeDropDownOnSelection: this.closeDropdownSelection }); } handleReset() { this.selectedItem = []; } } ================================================ FILE: src/app/components/select-section.ts ================================================ import { Component } from '@angular/core'; const tabDesc: any = { single: { heading: 'Single' } , multiple1: { heading: 'Multiple-Example1' } }; @Component({ selector: 'select-section', template: `
` }) export class SelectSectionComponent { public currentHeading = 'Single'; public tabDesc: any = tabDesc; } ================================================ FILE: src/app/index.ts ================================================ export * from './app.component'; export * from './app.module'; ================================================ FILE: src/assets/.gitkeep ================================================ ================================================ FILE: src/code-viewer/code-viewer.module.ts ================================================ import {NgModule, ModuleWithProviders} from '@angular/core'; import {CommonModule} from '@angular/common'; import {CodeViewerComponent} from './code-viewer'; @NgModule({ imports: [ CommonModule ], declarations: [ CodeViewerComponent ], exports: [CodeViewerComponent] }) export class ShCodeViewer { static forRoot(): ModuleWithProviders { return { ngModule: ShCodeViewer, providers: [] }; } } ================================================ FILE: src/code-viewer/code-viewer.ts ================================================ import { ElementRef, Input, OnInit, OnChanges, Component, ViewEncapsulation, ViewChild, AfterViewChecked, SimpleChanges } from '@angular/core'; declare let hljs: any; @Component({ selector: 'sh-code-viewer', template: `
        
    
`, encapsulation: ViewEncapsulation.None, styles: [ ` pre{ padding: 0; margin: 0; } code{ margin: 0; padding-top: 0; } /* Monokai Sublime style. Derived from Monokai by noformnocontent http://nn.mit-license.org/ */ .hljs { display: block; overflow-x: auto; padding: 0.5em; background: #23241f; } .hljs, .hljs-tag, .hljs-subst { color: #f8f8f2; } .hljs-strong, .hljs-emphasis { color: #a8a8a2; } .hljs-bullet, .hljs-quote, .hljs-number, .hljs-regexp, .hljs-literal, .hljs-link { color: #ae81ff; } .hljs-code, .hljs-title, .hljs-section, .hljs-selector-class { color: #a6e22e; } .hljs-strong { font-weight: bold; } .hljs-emphasis { font-style: italic; } .hljs-keyword, .hljs-selector-tag, .hljs-name, .hljs-attr { color: #f92672; } .hljs-symbol, .hljs-attribute { color: #66d9ef; } .hljs-params, .hljs-class .hljs-title { color: #f8f8f2; } .hljs-string, .hljs-type, .hljs-built_in, .hljs-builtin-name, .hljs-selector-id, .hljs-selector-attr, .hljs-selector-pseudo, .hljs-addition, .hljs-variable, .hljs-template-variable { color: #e6db74; } .hljs-comment, .hljs-deletion, .hljs-meta { color: #75715e; } ` ] }) export class CodeViewerComponent implements OnInit, OnChanges, AfterViewChecked { @Input() useBr: boolean; @Input() code: string; @Input() language: string; @ViewChild('codeView') codeView: ElementRef; private needUpdate: boolean; constructor(private elementRef: ElementRef) {} ngOnInit() { if (this.useBr) { hljs.configure({ useBR: true }); } } ngOnChanges(changes: SimpleChanges) { if (changes['code'] && changes['code'].currentValue) { this.needUpdate = true; } } ngAfterViewChecked() { if (!this.needUpdate) { return; } this.needUpdate = false; if (this.codeView.nativeElement.innerHTML) { hljs.highlightBlock(this.codeView.nativeElement); } } } ================================================ FILE: src/environments/environment.prod.ts ================================================ export const environment = { production: true }; ================================================ FILE: src/environments/environment.ts ================================================ // The file contents for the current environment will overwrite these during build. // The build system defaults to the dev environment which uses `environment.ts`, but if you do // `ng build --env=prod` then `environment.prod.ts` will be used instead. // The list of which env maps to which file can be found in `.angular-cli.json`. export const environment = { production: false }; ================================================ FILE: src/index.html ================================================ Angular Multi-Select Dropdown

LOADING..

================================================ FILE: src/jest-global-mocks.ts ================================================ // @ts-ignore global.CSS = null; const webStorageMock = () => { let storage: Record = {}; return { getItem: (key: string) => (key in storage ? storage[key] : null), setItem: (key: string, value: any) => (storage[key] = value || ''), removeItem: (key: string) => delete storage[key], clear: () => (storage = {}), }; }; Object.defineProperty(window, 'localStorage', { value: webStorageMock() }); Object.defineProperty(window, 'sessionStorage', { value: webStorageMock() }); Object.defineProperty(document, 'doctype', { value: '', }); Object.defineProperty(window, 'getComputedStyle', { value: () => { return { display: 'none', appearance: ['-webkit-appearance'], }; }, }); /** * ISSUE: https://github.com/angular/material2/issues/7101 * Workaround for JSDOM missing transform property */ Object.defineProperty(document.body.style, 'transform', { value: () => { return { enumerable: true, configurable: true, }; }, }); ================================================ FILE: src/main.ts ================================================ import { enableProdMode } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; if (environment.production) { enableProdMode(); } platformBrowserDynamic().bootstrapModule(AppModule); ================================================ FILE: src/ng-multiselect-dropdown/src/click-outside.directive.ts ================================================ import {Directive, ElementRef, Output, EventEmitter, HostListener} from '@angular/core'; @Directive({ selector: '[clickOutside]' }) export class ClickOutsideDirective { constructor(private _elementRef: ElementRef) { } @Output() public clickOutside = new EventEmitter(); @HostListener('document:click', ['$event', '$event.target']) public onClick(event: MouseEvent, targetElement: HTMLElement): void { if (!targetElement) { return; } const clickedInside = this._elementRef.nativeElement.contains(targetElement); if (!clickedInside) { this.clickOutside.emit(event); } } } ================================================ FILE: src/ng-multiselect-dropdown/src/index.ts ================================================ export { MultiSelectComponent } from './multiselect.component'; export { ClickOutsideDirective } from './click-outside.directive'; export { ListFilterPipe } from './list-filter.pipe'; export { NgMultiSelectDropDownModule } from './ng-multiselect-dropdown.module'; export { IDropdownSettings } from './multiselect.model' ================================================ FILE: src/ng-multiselect-dropdown/src/list-filter.pipe.ts ================================================ import { Pipe, PipeTransform } from '@angular/core'; import { ListItem } from './multiselect.model'; @Pipe({ name: 'multiSelectFilter', pure: false }) export class ListFilterPipe implements PipeTransform { transform(items: ListItem[], filter: ListItem): ListItem[] { if (!items || !filter) { return items; } return items.filter((item: ListItem) => this.applyFilter(item, filter)); } applyFilter(item: ListItem, filter: ListItem): boolean { if (typeof item.text === 'string' && typeof filter.text === 'string') { return !(filter.text && item.text && item.text.toLowerCase().indexOf(filter.text.toLowerCase()) === -1); } else { return !(filter.text && item.text && item.text.toString().toLowerCase().indexOf(filter.text.toString().toLowerCase()) === -1); } } } ================================================ FILE: src/ng-multiselect-dropdown/src/multi-select.component.html ================================================
{{_placeholder}} {{item.text}}  x +{{itemShowRemaining()}}
================================================ FILE: src/ng-multiselect-dropdown/src/multi-select.component.scss ================================================ $base-color: #337ab7; $disable-background-color: #eceeef; .multiselect-dropdown { position: relative; width: 100%; font-size: inherit; font-family: inherit; .dropdown-btn { display: inline-block; border: 1px solid #adadad; width: 100%; padding: 6px 12px; margin-bottom: 0; font-weight: normal; line-height: 1.52857143; text-align: left; vertical-align: middle; cursor: pointer; background-image: none; border-radius: 4px; .selected-item-container { display: flex; float: left; .selected-item{ border: 1px solid $base-color; margin-right: 4px; background: $base-color; padding: 0px 5px; color: #fff; border-radius: 2px; float: left; max-width: 100px; span { overflow: hidden; text-overflow: ellipsis; } a { text-decoration: none; } } } .selected-item:hover { box-shadow: 1px 1px #959595; } .dropdown-multiselect__caret { line-height: 16px; display: block; position: absolute; box-sizing: border-box; width: 40px; height: 38px; right: 1px; top: 0px; padding: 4px 8px; margin: 0; text-decoration: none; text-align: center; cursor: pointer; transition: transform 0.2s ease; } .dropdown-multiselect__caret:before { position: relative; right: 0; top: 65%; color: #999; margin-top: 4px; border-style: solid; border-width: 8px 8px 0 8px; border-color: #999999 transparent; content: ""; } .dropdown-multiselect--active .dropdown-multiselect__caret { transform: rotateZ(180deg); } } .disabled { & > span { background-color: $disable-background-color; } } } .dropdown-list { position: absolute; padding-top: 6px; width: 100%; z-index: 9999; border: 1px solid #ccc; border-radius: 3px; background: #fff; margin-top: 10px; box-shadow: 0px 1px 5px #959595; ul { padding: 0px; list-style: none; overflow: auto; margin: 0px; } li { padding: 6px 10px; cursor: pointer; text-align: left; } .filter-textbox { border-bottom: 1px solid #ccc; position: relative; padding: 10px; input { border: 0px; width: 100%; padding: 0px 0px 0px 26px; } input:focus { outline: none; } } } .multiselect-item-checkbox:hover{ background-color: #e4e3e3; } .multiselect-item-checkbox input[type='checkbox'] { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; } .multiselect-item-checkbox input[type='checkbox']:focus + div:before, .multiselect-item-checkbox input[type='checkbox']:hover + div:before { border-color: $base-color; background-color: #f2f2f2; } .multiselect-item-checkbox input[type='checkbox']:active + div:before { transition-duration: 0s; } .multiselect-item-checkbox input[type='checkbox'] + div { position: relative; padding-left: 2em; vertical-align: middle; user-select: none; cursor: pointer; margin: 0px; color: #000; } .multiselect-item-checkbox input[type='checkbox'] + div:before { box-sizing: content-box; content: ''; color: $base-color; position: absolute; top: 50%; left: 0; width: 14px; height: 14px; margin-top: -9px; border: 2px solid $base-color; text-align: center; transition: all 0.4s ease; } .multiselect-item-checkbox input[type='checkbox'] + div:after { box-sizing: content-box; content: ''; background-color: $base-color; position: absolute; top: 50%; left: 4px; width: 10px; height: 10px; margin-top: -5px; transform: scale(0); transform-origin: 50%; transition: transform 200ms ease-out; } .multiselect-item-checkbox input[type='checkbox']:disabled + div:before { border-color: #cccccc; } .multiselect-item-checkbox input[type='checkbox']:disabled:focus + div:before .multiselect-item-checkbox input[type='checkbox']:disabled:hover + div:before { background-color: inherit; } .multiselect-item-checkbox input[type='checkbox']:disabled:checked + div:before { background-color: #cccccc; } .multiselect-item-checkbox input[type='checkbox'] + div:after { background-color: transparent; top: 50%; left: 4px; width: 8px; height: 3px; margin-top: -4px; border-style: solid; border-color: #ffffff; border-width: 0 0 3px 3px; border-image: none; transform: rotate(-45deg) scale(0); } .multiselect-item-checkbox input[type='checkbox']:checked + div:after { content: ''; transform: rotate(-45deg) scale(1); transition: transform 200ms ease-out; } .multiselect-item-checkbox input[type='checkbox']:checked + div:before { animation: borderscale 200ms ease-in; background: $base-color; } .multiselect-item-checkbox input[type='checkbox']:checked + div:after { transform: rotate(-45deg) scale(1); } @keyframes borderscale { 50% { box-shadow: 0 0 0 2px $base-color; } } ================================================ FILE: src/ng-multiselect-dropdown/src/multiselect.component.ts ================================================ import { Component, HostListener, forwardRef, Input, Output, EventEmitter, ChangeDetectionStrategy, ChangeDetectorRef } from "@angular/core"; import { NG_VALUE_ACCESSOR, ControlValueAccessor } from "@angular/forms"; import { ListItem, IDropdownSettings } from "./multiselect.model"; import { ListFilterPipe } from "./list-filter.pipe"; export const DROPDOWN_CONTROL_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => MultiSelectComponent), multi: true }; const noop = () => {}; @Component({ selector: "ng-multiselect-dropdown", templateUrl: "./multi-select.component.html", styleUrls: ["./multi-select.component.scss"], providers: [DROPDOWN_CONTROL_VALUE_ACCESSOR], changeDetection: ChangeDetectionStrategy.OnPush }) export class MultiSelectComponent implements ControlValueAccessor { public _settings: IDropdownSettings; public _data: Array = []; public selectedItems: Array = []; public isDropdownOpen = true; _placeholder = "Select"; private _sourceDataType = null; // to keep note of the source data type. could be array of string/number/object private _sourceDataFields: Array = []; // store source data fields names filter: ListItem = new ListItem(this.data); defaultSettings: IDropdownSettings = { singleSelection: false, idField: "id", textField: "text", disabledField: "isDisabled", enableCheckAll: true, selectAllText: "Select All", unSelectAllText: "UnSelect All", allowSearchFilter: false, limitSelection: -1, clearSearchFilter: true, maxHeight: 197, itemsShowLimit: 999999999999, searchPlaceholderText: "Search", noDataAvailablePlaceholderText: "No data available", noFilteredDataAvailablePlaceholderText: "No filtered data available", closeDropDownOnSelection: false, showSelectedItemsAtTop: false, defaultOpen: false, allowRemoteDataSearch: false }; @Input() public set placeholder(value: string) { if (value) { this._placeholder = value; } else { this._placeholder = "Select"; } } @Input() disabled = false; @Input() public set settings(value: IDropdownSettings) { if (value) { this._settings = Object.assign(this.defaultSettings, value); } else { this._settings = Object.assign(this.defaultSettings); } } @Input() public set data(value: Array) { if (!value) { this._data = []; } else { const firstItem = value[0]; this._sourceDataType = typeof firstItem; this._sourceDataFields = this.getFields(firstItem); this._data = value.map((item: any) => typeof item === "string" || typeof item === "number" ? new ListItem(item) : new ListItem({ id: item[this._settings.idField], text: item[this._settings.textField], isDisabled: item[this._settings.disabledField] }) ); } } @Output("onFilterChange") onFilterChange: EventEmitter = new EventEmitter(); @Output("onDropDownClose") onDropDownClose: EventEmitter = new EventEmitter(); @Output("onSelect") onSelect: EventEmitter = new EventEmitter(); @Output("onDeSelect") onDeSelect: EventEmitter = new EventEmitter(); @Output("onSelectAll") onSelectAll: EventEmitter> = new EventEmitter>(); @Output("onDeSelectAll") onDeSelectAll: EventEmitter> = new EventEmitter>(); private onTouchedCallback: () => void = noop; private onChangeCallback: (_: any) => void = noop; onFilterTextChange($event) { this.onFilterChange.emit($event); } constructor( private listFilterPipe:ListFilterPipe, private cdr: ChangeDetectorRef ) {} onItemClick($event: any, item: ListItem) { if (this.disabled || item.isDisabled) { return false; } const found = this.isSelected(item); const allowAdd = this._settings.limitSelection === -1 || (this._settings.limitSelection > 0 && this.selectedItems.length < this._settings.limitSelection); if (!found) { if (allowAdd) { this.addSelected(item); } } else { this.removeSelected(item); } if (this._settings.singleSelection && this._settings.closeDropDownOnSelection) { this.closeDropdown(); } } writeValue(value: any) { if (value !== undefined && value !== null && value.length > 0) { if (this._settings.singleSelection) { try { if (value.length >= 1) { const firstItem = value[0]; this.selectedItems = [ typeof firstItem === "string" || typeof firstItem === "number" ? new ListItem(firstItem) : new ListItem({ id: firstItem[this._settings.idField], text: firstItem[this._settings.textField], isDisabled: firstItem[this._settings.disabledField] }) ]; } } catch (e) { // console.error(e.body.msg); } } else { const _data = value.map((item: any) => typeof item === "string" || typeof item === "number" ? new ListItem(item) : new ListItem({ id: item[this._settings.idField], text: item[this._settings.textField], isDisabled: item[this._settings.disabledField] }) ); if (this._settings.limitSelection > 0) { this.selectedItems = _data.splice(0, this._settings.limitSelection); } else { this.selectedItems = _data; } } } else { this.selectedItems = []; } this.onChangeCallback(value); this.cdr.markForCheck(); } // From ControlValueAccessor interface registerOnChange(fn: any) { this.onChangeCallback = fn; } // From ControlValueAccessor interface registerOnTouched(fn: any) { this.onTouchedCallback = fn; } // Set touched on blur @HostListener("blur") public onTouched() { // this.closeDropdown(); this.onTouchedCallback(); } trackByFn(index, item) { return item.id; } isSelected(clickedItem: ListItem) { let found = false; this.selectedItems.forEach(item => { if (clickedItem.id === item.id) { found = true; } }); return found; } isLimitSelectionReached(): boolean { return this._settings.limitSelection === this.selectedItems.length; } isAllItemsSelected(): boolean { // get disabld item count let filteredItems = this.listFilterPipe.transform(this._data,this.filter); const itemDisabledCount = filteredItems.filter(item => item.isDisabled).length; // take disabled items into consideration when checking if ((!this.data || this.data.length === 0) && this._settings.allowRemoteDataSearch) { return false; } return filteredItems.length === this.selectedItems.length + itemDisabledCount; } showButton(): boolean { if (!this._settings.singleSelection) { if (this._settings.limitSelection > 0) { return false; } // this._settings.enableCheckAll = this._settings.limitSelection === -1 ? true : false; return true; // !this._settings.singleSelection && this._settings.enableCheckAll && this._data.length > 0; } else { // should be disabled in single selection mode return false; } } itemShowRemaining(): number { return this.selectedItems.length - this._settings.itemsShowLimit; } addSelected(item: ListItem) { if (this._settings.singleSelection) { this.selectedItems = []; this.selectedItems.push(item); } else { this.selectedItems.push(item); } this.onChangeCallback(this.emittedValue(this.selectedItems)); this.onSelect.emit(this.emittedValue(item)); } removeSelected(itemSel: ListItem) { this.selectedItems.forEach(item => { if (itemSel.id === item.id) { this.selectedItems.splice(this.selectedItems.indexOf(item), 1); } }); this.onChangeCallback(this.emittedValue(this.selectedItems)); this.onDeSelect.emit(this.emittedValue(itemSel)); } emittedValue(val: any): any { const selected = []; if (Array.isArray(val)) { val.map(item => { selected.push(this.objectify(item)); }); } else { if (val) { return this.objectify(val); } } return selected; } objectify(val: ListItem) { if (this._sourceDataType === 'object') { const obj = {}; obj[this._settings.idField] = val.id; obj[this._settings.textField] = val.text; if (this._sourceDataFields.includes(this._settings.disabledField)) { obj[this._settings.disabledField] = val.isDisabled; } return obj; } if (this._sourceDataType === 'number') { return Number(val.id); } else { return val.text; } } toggleDropdown(evt) { evt.preventDefault(); if (this.disabled && this._settings.singleSelection) { return; } this._settings.defaultOpen = !this._settings.defaultOpen; if (!this._settings.defaultOpen) { this.onDropDownClose.emit(); } } closeDropdown() { this._settings.defaultOpen = false; // clear search text if (this._settings.clearSearchFilter) { this.filter.text = ""; } this.onDropDownClose.emit(); } toggleSelectAll() { if (this.disabled) { return false; } if (!this.isAllItemsSelected()) { // filter out disabled item first before slicing this.selectedItems = this.listFilterPipe.transform(this._data,this.filter).filter(item => !item.isDisabled).slice(); this.onSelectAll.emit(this.emittedValue(this.selectedItems)); } else { this.selectedItems = []; this.onDeSelectAll.emit(this.emittedValue(this.selectedItems)); } this.onChangeCallback(this.emittedValue(this.selectedItems)); } getFields(inputData) { const fields = []; if (typeof inputData !== "object") { return fields; } // tslint:disable-next-line:forin for (const prop in inputData) { fields.push(prop); } return fields; } } ================================================ FILE: src/ng-multiselect-dropdown/src/multiselect.model.ts ================================================ export interface IDropdownSettings { singleSelection?: boolean; idField?: string; textField?: string; disabledField?: string; enableCheckAll?: boolean; selectAllText?: string; unSelectAllText?: string; allowSearchFilter?: boolean; clearSearchFilter?: boolean; maxHeight?: number; itemsShowLimit?: number; limitSelection?: number; searchPlaceholderText?: string; noDataAvailablePlaceholderText?: string; noFilteredDataAvailablePlaceholderText?: string; closeDropDownOnSelection?: boolean; showSelectedItemsAtTop?: boolean; defaultOpen?: boolean; allowRemoteDataSearch?: boolean; } export class ListItem { id: String | number; text: String | number; isDisabled?: boolean; public constructor(source: any) { if (typeof source === 'string' || typeof source === 'number') { this.id = this.text = source; this.isDisabled = false; } if (typeof source === 'object') { this.id = source.id; this.text = source.text; this.isDisabled = source.isDisabled; } } } ================================================ FILE: src/ng-multiselect-dropdown/src/ng-multiselect-dropdown.module.ts ================================================ import { NgModule, ModuleWithProviders } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { MultiSelectComponent } from './multiselect.component'; import { ClickOutsideDirective } from './click-outside.directive'; import { ListFilterPipe } from './list-filter.pipe'; @NgModule({ imports: [CommonModule, FormsModule], declarations: [MultiSelectComponent, ClickOutsideDirective, ListFilterPipe], providers: [ListFilterPipe], exports: [MultiSelectComponent] }) export class NgMultiSelectDropDownModule { static forRoot(): ModuleWithProviders { return { ngModule: NgMultiSelectDropDownModule }; } } ================================================ FILE: src/ng-multiselect-dropdown/src/public_api.ts ================================================ export { MultiSelectComponent } from './multiselect.component'; export { NgMultiSelectDropDownModule } from './ng-multiselect-dropdown.module'; export { IDropdownSettings } from './multiselect.model'; ================================================ FILE: src/ng-multiselect-dropdown/test/helper.ts ================================================ import { Type } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { TestBed, ComponentFixture, tick } from '@angular/core/testing'; import { NgMultiSelectDropDownModule } from './../src/ng-multiselect-dropdown.module'; export function newEvent(eventName: string, bubbles = false, cancelable = false) { let evt = document.createEvent('CustomEvent'); // MUST be 'CustomEvent' evt.initCustomEvent(eventName, bubbles, cancelable, null); return evt; } export function createTestingModule(cmp: Type, template: string): ComponentFixture { TestBed.configureTestingModule({ imports: [FormsModule, NgMultiSelectDropDownModule], declarations: [cmp] }) .overrideComponent(cmp, { set: { template: template } }) .compileComponents(); const fixture = TestBed.createComponent(cmp); fixture.detectChanges(); return fixture; } export function tickAndDetectChanges(fixture) { fixture.detectChanges(); tick(); } ================================================ FILE: src/ng-multiselect-dropdown/test/list-filter.pipe.spec.ts ================================================ import { ListFilterPipe } from '../src/list-filter.pipe' describe('multiSelectFilter', () => { let pipe: ListFilterPipe; beforeEach(() => { pipe = new ListFilterPipe(); }) it('should create an instance', () => { expect(pipe).toBeTruthy(); }); it('bad inputs:should return source data if filter data is null', () => { const sourceData = [ { id: 1, text: 'Navsari' }, { id: 2, text: 'Pune' }]; expect(pipe.transform(sourceData, null)).toEqual(sourceData); }) it('transform string data', () => { const sourceData = [ { id: 1, text: 'Navsari' }, { id: 2, text: 'Pune' }]; const filterData = { id: 1, text: 'Nav' }; const expectedData = [{ id: 1, text: 'Navsari' }] expect(pipe.transform(sourceData, filterData)).toEqual(expectedData); }) it('transform numbers data', () => { const sourceData = [ { id: 1111, text: 1111 }, { id: 2222, text: 2222 }]; const filterData = { id: 1111, text: 1111 }; const expectedData = [{ id: 1111, text: 1111 }] expect(pipe.transform(sourceData, filterData)).toEqual(expectedData); }) }) ================================================ FILE: src/ng-multiselect-dropdown/test/multi-select-disabled-item.component.spec.ts ================================================ import { Component, Type, ViewChild, DebugElement } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { ComponentFixture, fakeAsync } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { MultiSelectComponent } from './../src/multiselect.component'; import { createTestingModule, tickAndDetectChanges } from './helper'; @Component({ template: `` }) class Ng2MultiSelectDropdownMultipleSelectWithDisableItemComponent { @ViewChild(MultiSelectComponent) select: MultiSelectComponent; cities = [ { item_id: 1, item_text: 'Mumbai' }, { item_id: 2, item_text: 'Bangalore' }, { item_id: 3, item_text: 'Pune', isDisabled: true }, { item_id: 4, item_text: 'Navsari' }, { item_id: 5, item_text: 'New Delhi' } ]; selectedItem = [{ item_id: 1, item_text: 'Mumbai' }, { item_id: 4, item_text: 'Navsari' }]; dropdownSettings = { singleSelection: false, idField: 'item_id', textField: 'item_text', selectAllText: 'Select All', unSelectAllText: 'UnSelect All', badgeShowLimit: 3, disabled: false, allowSearchFilter: true, closeDropDownOnSelection: true }; } describe('Multiple Selection:disable item', () => { let fixture: ComponentFixture; beforeEach(fakeAsync(() => { fixture = createTestingModule( Ng2MultiSelectDropdownMultipleSelectWithDisableItemComponent, `
` ); })); it('should have 4 items enabled and 1 item disabled', fakeAsync(() => { let selCheckBoxes: HTMLLIElement[]; const sel = fixture.nativeElement.querySelectorAll('.multiselect-item-checkbox'); selCheckBoxes = Array.from(sel); // tickAndDetectChanges(fixture); // Mumbai expect(selCheckBoxes[1].querySelector('div').textContent).toContain('Mumbai'); expect(selCheckBoxes[1].querySelector('input').disabled).toBe(false); // Bangalore expect(selCheckBoxes[2].querySelector('div').textContent).toContain('Bangalore'); expect(selCheckBoxes[2].querySelector('input').disabled).toBe(false); expect(selCheckBoxes[3].querySelector('div').textContent).toContain('Pune'); expect(selCheckBoxes[3].querySelector('input').disabled).toBe(true); // Pune should have disable attribute // Navsari expect(selCheckBoxes[4].querySelector('div').textContent).toContain('Navsari'); expect(selCheckBoxes[4].querySelector('input').disabled).toBe(false); // New Delhi expect(selCheckBoxes[5].querySelector('div').textContent).toContain('New Delhi'); expect(selCheckBoxes[5].querySelector('input').disabled).toBe(false); expect(fixture.componentInstance.selectedItem.length).toEqual(2); })); it('should not select disabled item-Pune', fakeAsync(() => { const index = 3; // 0 is select all checkbox let selCheckBoxes: HTMLLIElement[]; const sel = fixture.nativeElement.querySelectorAll('.multiselect-item-checkbox'); selCheckBoxes = Array.from(sel); selCheckBoxes[index].click(); selCheckBoxes[index - 1].click(); // select Bangalore tickAndDetectChanges(fixture); expect(selCheckBoxes[index - 1].querySelector('input').disabled).toBe(false); // Bangalore should not have disable attribute expect(selCheckBoxes[index].querySelector('input').disabled).toBe(true); // Pune should have disable attribute expect(selCheckBoxes[index].querySelector('div').textContent).toContain('Pune'); expect(fixture.componentInstance.selectedItem.length).toEqual(3); })); it('should not select disabled item when selecting all items', fakeAsync(() => { const index = 0; // 0 is select all checkbox let selCheckBoxes: HTMLLIElement[]; const sel = fixture.nativeElement.querySelectorAll('.multiselect-item-checkbox'); selCheckBoxes = Array.from(sel); selCheckBoxes[index].click(); // click on "select all" tickAndDetectChanges(fixture); expect(selCheckBoxes[3].querySelector('div').textContent).toContain('Pune'); expect(selCheckBoxes[3].querySelector('input').disabled).toBe(true); // Pune should have disable attribute expect(fixture.componentInstance.selectedItem.length).toEqual(4); })); }); ================================================ FILE: src/ng-multiselect-dropdown/test/multi-select.component.spec.ts ================================================ import { Component, Type, ViewChild, DebugElement } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { ComponentFixture, fakeAsync } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { MultiSelectComponent } from './../src/multiselect.component'; import { createTestingModule, tickAndDetectChanges } from './helper'; @Component({ template: `` }) class Ng2MultiSelectDropdownSingleSelect { @ViewChild(MultiSelectComponent) select: MultiSelectComponent; cities = [ { item_id: 1, item_text: 'Mumbai' }, { item_id: 2, item_text: 'Bangalore' }, { item_id: 3, item_text: 'Pune', isDisabled: true }, { item_id: 4, item_text: 'Navsari' }, { item_id: 5, item_text: 'New Delhi' } ]; selectedItem = [{ item_id: 4, item_text: 'Navsari' }]; dropdownSettings = { singleSelection: true, idField: 'item_id', textField: 'item_text', selectAllText: 'Select All', unSelectAllText: 'UnSelect All', badgeShowLimit: 3, disabled: false, allowSearchFilter: false, closeDropDownOnSelection: true }; } @Component({ template: `` }) class Ng2MultiSelectDropdownMultipleSelect { @ViewChild(MultiSelectComponent) select: MultiSelectComponent; cities = [ { item_id: 1, item_text: 'Mumbai' }, { item_id: 2, item_text: 'Bangalore' }, { item_id: 3, item_text: 'Pune' }, { item_id: 4, item_text: 'Navsari' }, { item_id: 5, item_text: 'New Delhi' } ]; selectedItem = [ { item_id: 1, item_text: 'Mumbai' }, { item_id: 4, item_text: 'Navsari' } ]; dropdownSettings = { singleSelection: false, idField: 'item_id', textField: 'item_text', selectAllText: 'Select All', unSelectAllText: 'UnSelect All', badgeShowLimit: 3, disabled: false, allowSearchFilter: true, closeDropDownOnSelection: true }; } describe('ng-multiselect-component', function() { describe('Single Selection', () => { let fixture: ComponentFixture; beforeEach(fakeAsync(() => { fixture = createTestingModule( Ng2MultiSelectDropdownSingleSelect, `
` ); })); it('should update internal model on select an item', fakeAsync(() => { let index = 4; let selCheckBoxes: HTMLLIElement[]; const sel = fixture.nativeElement.querySelectorAll( '.multiselect-item-checkbox' ); selCheckBoxes = Array.from(sel); selCheckBoxes[index].click(); tickAndDetectChanges(fixture); expect(fixture.componentInstance.selectedItem.length).toBe(1); let selItem = fixture.componentInstance.cities[index]; expect(fixture.componentInstance.selectedItem[0]).toEqual(selItem); index = 3; selCheckBoxes[index].click(); tickAndDetectChanges(fixture); expect(fixture.componentInstance.selectedItem.length).toBe(1); selItem = fixture.componentInstance.cities[index]; expect(fixture.componentInstance.selectedItem[0]).toEqual(selItem); index = 4; selCheckBoxes[index].click(); tickAndDetectChanges(fixture); expect(fixture.componentInstance.selectedItem.length).toBe(1); selItem = fixture.componentInstance.cities[index]; expect(fixture.componentInstance.selectedItem[0]).toEqual(selItem); })); it('should dropdown gets close once item is selected', fakeAsync(() => { tickAndDetectChanges(fixture); const selDropdown: HTMLElement = fixture.nativeElement.querySelector( '.multiselect-dropdown' ); selDropdown.click(); tickAndDetectChanges(fixture); expect(fixture.componentInstance.select._settings.defaultOpen).toBe( false ); })); it('selected item should be correct', fakeAsync(() => { expect(fixture.componentInstance.selectedItem.length).toBe(1); const selItem = fixture.componentInstance.cities[3]; expect(fixture.componentInstance.selectedItem[0]).toEqual(selItem); })); it('should have default placeholder as "Select"', () => { const de: DebugElement = fixture.debugElement.query( By.css('.dropdown-btn>span') ); const el = de.nativeElement; expect(el.textContent).toContain('Select'); }); it('close dropdown if opened and clicked outside dropdown container', fakeAsync(() => { fixture.componentInstance.select.isDropdownOpen = true; const de: DebugElement = fixture.debugElement.query(By.css('.container')); const el = de.nativeElement; el.click(); tickAndDetectChanges(fixture); expect(fixture.componentInstance.select._settings.defaultOpen).toBe( false ); })); // it('search filter should work', () => { // const inputSearch = fixture.nativeElement.query(By.css('input[type=text]')) as HTMLInputElement; // inputSearch.value = 'navsari'; // inputSearch.dispatchEvent(newEvent('input')); // tickAndDetectChanges(fixture); // const selItems: HTMLLIElement[] = Array.from(document.querySelectorAll('.multiselect-item-checkbox')); // expect(selItems.length).toBe(1); // }); it('dropdown should not open when component is disabled', fakeAsync(() => { fixture.componentInstance.select.isDropdownOpen = false; fixture.componentInstance.dropdownSettings.disabled = true; const de: DebugElement = fixture.debugElement.query( By.css('.dropdown-btn') ); const el = de.nativeElement; tickAndDetectChanges(fixture); expect(fixture.componentInstance.select.isDropdownOpen).toBe(false); })); it('should not select disabled item', fakeAsync(() => { const selectedItems = [...fixture.componentInstance.selectedItem]; // currently selected items const index = 2; let selCheckBoxes: HTMLLIElement[]; const sel = fixture.nativeElement.querySelectorAll( '.multiselect-item-checkbox' ); selCheckBoxes = Array.from(sel); selCheckBoxes[index].click(); tickAndDetectChanges(fixture); expect(fixture.componentInstance.select.isDropdownOpen).toBeTruthy(); expect(fixture.componentInstance.selectedItem).toEqual(selectedItems); // selected items must've not changed })); }); describe('Multiple Selection', () => { let fixture: ComponentFixture; beforeEach(fakeAsync(() => { fixture = createTestingModule( Ng2MultiSelectDropdownMultipleSelect, `
` ); })); // it('should update internal model on select an item', fakeAsync(() => { // let index = 4; // let selCheckBoxes: HTMLLIElement[]; // const sel = fixture.nativeElement.querySelectorAll('.multiselect-item-checkbox'); // selCheckBoxes = Array.from(sel); // selCheckBoxes[index].click(); // tickAndDetectChanges(fixture); // expect(fixture.componentInstance.selectedItem.length).toBe(1); // let selItem = fixture.componentInstance.cities[index]; // expect(fixture.componentInstance.selectedItem[0]).toEqual(selItem); // index = 3; // selCheckBoxes[index].click(); // tickAndDetectChanges(fixture); // expect(fixture.componentInstance.selectedItem.length).toBe(1); // selItem = fixture.componentInstance.cities[index]; // expect(fixture.componentInstance.selectedItem[0]).toEqual(selItem); // index = 4; // selCheckBoxes[index].click(); // tickAndDetectChanges(fixture); // expect(fixture.componentInstance.selectedItem.length).toBe(1); // selItem = fixture.componentInstance.cities[index]; // expect(fixture.componentInstance.selectedItem[0]).toEqual(selItem); // })); it('should dropdown gets close once item is selected', fakeAsync(() => { tickAndDetectChanges(fixture); const selDropdown: HTMLElement = fixture.nativeElement.querySelector( '.multiselect-dropdown' ); selDropdown.click(); tickAndDetectChanges(fixture); expect(fixture.componentInstance.select._settings.defaultOpen).toBe( false ); })); it('selected item should be correct', fakeAsync(() => { expect(fixture.componentInstance.selectedItem.length).toBe(2); // const selItem = fixture.componentInstance.cities[3]; // expect(fixture.componentInstance.selectedItem[0]).toEqual(selItem); })); it('should have default placeholder as "Select"', () => { const de: DebugElement = fixture.debugElement.query( By.css('.dropdown-btn>span') ); const el = de.nativeElement; expect(el.textContent).toContain('Select'); }); it('should have default placeholder for search textbox as "Search"', () => { const de: DebugElement = fixture.debugElement.query( By.css('.filter-textbox>input') ); const el = de.nativeElement; expect(el.placeholder).toBe('Search'); }); it('close dropdown if opened and clicked outside dropdown container', fakeAsync(() => { fixture.componentInstance.select.isDropdownOpen = true; const de: DebugElement = fixture.debugElement.query(By.css('.container')); const el = de.nativeElement; el.click(); tickAndDetectChanges(fixture); expect(fixture.componentInstance.select._settings.defaultOpen).toBe( false ); })); it('should have custom placeholder for "select all text" button', () => { const de: DebugElement = fixture.debugElement.query( By.css('.item1>li>div') ); const el = de.nativeElement; expect(el.textContent).toContain('Select All'); }); }); }); ================================================ FILE: src/ng-multiselect-dropdown/test/multi-select.component1.spec.ts ================================================ import { Component, Type, ViewChild, DebugElement } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { ComponentFixture, fakeAsync } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { MultiSelectComponent, IDropdownSettings } from './../src'; import { createTestingModule, tickAndDetectChanges } from './helper' @Component({ template: `` }) class Ng2MultiSelectDropdownMultipleSelect_defaultPlaceHolderText { @ViewChild(MultiSelectComponent) select: MultiSelectComponent; cities = []; selectedItem = []; dropdownSettings: IDropdownSettings = { singleSelection: false, idField: 'item_id', textField: 'item_text', selectAllText: 'Select All', unSelectAllText: 'UnSelect All', allowSearchFilter: true, closeDropDownOnSelection: true, }; } const NO_DATA_AVAILABLE = 'NO DATA AVAILABLE' @Component({ template: `` }) class Ng2MultiSelectDropdownMultipleSelect_CustomPlaceHolderText { @ViewChild(MultiSelectComponent) select: MultiSelectComponent; cities = []; selectedItem = []; dropdownSettings: IDropdownSettings = { singleSelection: false, idField: 'item_id', textField: 'item_text', selectAllText: 'Select All', unSelectAllText: 'UnSelect All', allowSearchFilter: true, closeDropDownOnSelection: true, noDataAvailablePlaceholderText: NO_DATA_AVAILABLE }; } describe('ng-multiselect-component: default placeholder when no data is available to show', function () { let fixture: ComponentFixture; beforeEach( fakeAsync(() => { fixture = createTestingModule( Ng2MultiSelectDropdownMultipleSelect_defaultPlaceHolderText, `
` ); }) ); it('should have default placeholder when no data is available to show', () => { const de: DebugElement = fixture.debugElement.query(By.css('.no-data')); const el = de.nativeElement; expect(el.textContent).toContain('No data available') }) }); describe('ng-multiselect-component: custom placeholder when no data is available to show', function () { let fixture: ComponentFixture; beforeEach( fakeAsync(() => { fixture = createTestingModule( Ng2MultiSelectDropdownMultipleSelect_CustomPlaceHolderText, `
` ); }) ); it('should have custom placeholder when no data is available to show', () => { const de: DebugElement = fixture.debugElement.query(By.css('.no-data')); const el = de.nativeElement; expect(el.textContent).toContain(NO_DATA_AVAILABLE) }) }); ================================================ FILE: src/ng-multiselect-dropdown/test/multi-select.component2.spec.ts ================================================ import { Component, Type, ViewChild, DebugElement } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { ComponentFixture, fakeAsync } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { MultiSelectComponent, IDropdownSettings } from './../src'; import { createTestingModule, tickAndDetectChanges } from './helper' @Component({ template: `` }) class Ng2MultiSelectDropdownMultipleSelect { @ViewChild(MultiSelectComponent) select: MultiSelectComponent; cities = [ { item_id: 0, item_text: 'Navsari' }, { item_id: 1, item_text: 'Mumbai' }, { item_id: 2, item_text: 'Bangalore' }, { item_id: 3, item_text: 'Pune' }, { item_id: 5, item_text: 'New Delhi' } ]; selectedItem = [{ item_id: 0, item_text: 'Navsari' }]; dropdownSettings: IDropdownSettings = { singleSelection: false, idField: 'item_id', textField: 'item_text', selectAllText: 'Select All', unSelectAllText: 'UnSelect All', allowSearchFilter: true, closeDropDownOnSelection: true, }; } // https://github.com/NileshPatel17/ng-multiselect-dropdown/issues/67 describe('ng-multiselect-component: Issue No: 67( Option with value = 0 does not work)', function () { let fixture: ComponentFixture; beforeEach( fakeAsync(() => { fixture = createTestingModule( Ng2MultiSelectDropdownMultipleSelect, `
` ); }) ); it('should have 5 total items', () => { let selCheckBoxes: HTMLLIElement[]; const de: DebugElement[] = fixture.debugElement.queryAll(By.css('.item2>li')); expect(fixture.componentInstance.cities.length).toBe(5) expect(de.length).toBe(5) expect(fixture.componentInstance.selectedItem.length).toBe(1) }) }); ================================================ FILE: src/ng-multiselect-dropdown/test/various-input-data.spec.ts ================================================ import { Component, ViewChild } from '@angular/core'; import { ComponentFixture, fakeAsync } from '@angular/core/testing'; import { MultiSelectComponent } from './../src/multiselect.component'; import { createTestingModule, tickAndDetectChanges } from './helper'; // #region String Data const ALL_CITIES = ['New Delhi', 'Mumbai', 'Bangalore', 'Pune', 'Chennai', 'Navsari']; const SELECTED_CITIES = ['Mumbai', 'Navsari']; @Component({ template: `` }) class SingleDimensionString { @ViewChild(MultiSelectComponent) select: MultiSelectComponent; cities: Array = ALL_CITIES; selectedItem: Array = SELECTED_CITIES; dropdownSettings = { singleSelection: false, selectAllText: 'Select All', unSelectAllText: 'UnSelect All', badgeShowLimit: 3, disabled: false, allowSearchFilter: true, closeDropDownOnSelection: true }; } describe('Multiple Selection:String Data', () => { let fixture: ComponentFixture; beforeEach(fakeAsync(() => { fixture = createTestingModule( SingleDimensionString, `
` ); })); it('selected item should be array of string', fakeAsync(() => { const index = 0; // 0 is select all checkbox let selCheckBoxes: HTMLLIElement[]; const sel = fixture.nativeElement.querySelectorAll('.multiselect-item-checkbox'); selCheckBoxes = Array.from(sel); selCheckBoxes[index].click(); tickAndDetectChanges(fixture); expect(fixture.componentInstance.selectedItem.length).toEqual(ALL_CITIES.length); expect(fixture.componentInstance.selectedItem).toEqual(ALL_CITIES); })); }); // #endregion // #region Numeric data const NUMBERS = [1, 3, 5, 6]; @Component({ template: `` }) class SingleDimensionNumber { @ViewChild(MultiSelectComponent) select: MultiSelectComponent; cities: Array = NUMBERS; selectedItem: Array = [3]; dropdownSettings = { singleSelection: false, selectAllText: 'Select All', unSelectAllText: 'UnSelect All', badgeShowLimit: 3, disabled: false, allowSearchFilter: true, closeDropDownOnSelection: true }; } describe('Multiple Selection:Numeric Data', () => { let fixture: ComponentFixture; beforeEach(fakeAsync(() => { fixture = createTestingModule( SingleDimensionNumber, `
` ); })); it('selected item should be array of number', fakeAsync(() => { const index = 0; // 0 is select all checkbox let selCheckBoxes: HTMLLIElement[]; const sel = fixture.nativeElement.querySelectorAll('.multiselect-item-checkbox'); selCheckBoxes = Array.from(sel); selCheckBoxes[index].click(); tickAndDetectChanges(fixture); expect(fixture.componentInstance.selectedItem.length).toEqual(NUMBERS.length); expect(fixture.componentInstance.selectedItem).toEqual(NUMBERS); })); }); // #endregion // #region Object data interface ICity { item_id: number; item_text: string; } const CITIES: Array = [ { item_id: 1, item_text: 'New Delhi' }, { item_id: 2, item_text: 'Mumbai' }, { item_id: 3, item_text: 'Bangalore' }, { item_id: 4, item_text: 'Pune' }, { item_id: 5, item_text: 'Chennai' }, { item_id: 6, item_text: 'Navsari' } ]; const SEL_CITIES: Array = [ { item_id: 2, item_text: 'Mumbai' }, { item_id: 4, item_text: 'Pune' } ]; @Component({ template: `` }) class SingleDimensionObjectComponent { @ViewChild(MultiSelectComponent) select: MultiSelectComponent; cities: Array = CITIES; selectedItem: Array = SEL_CITIES; dropdownSettings = { singleSelection: false, idField: 'item_id', textField: 'item_text', selectAllText: 'Select All', unSelectAllText: 'UnSelect All', badgeShowLimit: 3, disabled: false, allowSearchFilter: true, closeDropDownOnSelection: true }; } describe('Multiple Selection:Object Data', () => { let fixture: ComponentFixture; beforeEach(fakeAsync(() => { fixture = createTestingModule( SingleDimensionObjectComponent, `
` ); })); it('selected item should be array of Object', fakeAsync(() => { const index = 0; // 0 is select all checkbox let selCheckBoxes: HTMLLIElement[]; const sel = fixture.nativeElement.querySelectorAll('.multiselect-item-checkbox'); selCheckBoxes = Array.from(sel); selCheckBoxes[index].click(); tickAndDetectChanges(fixture); expect(fixture.componentInstance.selectedItem.length).toEqual(CITIES.length); expect(fixture.componentInstance.selectedItem).toEqual(CITIES); })); }); // #endregion ================================================ FILE: src/polyfills.ts ================================================ /** * This file includes polyfills needed by Angular and is loaded before the app. * You can add your own extra polyfills to this file. * * This file is divided into 2 sections: * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. * 2. Application imports. Files imported after ZoneJS that should be loaded before your main * file. * * The current setup is for so-called "evergreen" browsers; the last versions of browsers that * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. * * Learn more in https://angular.io/guide/browser-support */ /*************************************************************************************************** * BROWSER POLYFILLS */ /** IE10 and IE11 requires the following for NgClass support on SVG elements */ // import 'classlist.js'; // Run `npm install --save classlist.js`. /** * Web Animations `@angular/platform-browser/animations` * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). */ // import 'web-animations-js'; // Run `npm install --save web-animations-js`. /** * By default, zone.js will patch all possible macroTask and DomEvents * user can disable parts of macroTask/DomEvents patch by setting following flags * because those flags need to be set before `zone.js` being loaded, and webpack * will put import in the top of bundle, so user need to create a separate file * in this directory (for example: zone-flags.ts), and put the following flags * into that file, and then add the following code before importing zone.js. * import './zone-flags.ts'; * * The flags allowed in zone-flags.ts are listed here. * * The following flags will work for all browsers. * * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames * * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js * with the following flag, it will bypass `zone.js` patch for IE/Edge * * (window as any).__Zone_enable_cross_context_check = true; * */ /*************************************************************************************************** * Zone JS is required by default for Angular itself. */ import 'zone.js/dist/zone'; // Included with Angular CLI. /*************************************************************************************************** * APPLICATION IMPORTS */ ================================================ FILE: src/setup-jest.ts ================================================ import 'jest-preset-angular'; import './jest-global-mocks'; // browser mocks globally available for every test ================================================ FILE: src/styles.scss ================================================ /* You can add global styles to this file, and also import other style files */ .h1, .h2, .h3, h1, h2, h3 { margin-top: 20px; margin-bottom: 10px; } .h1, h1 { font-size: 36px; } .btn-group-lg > .btn, .btn-lg { font-size: 18px; } section { padding-top: 30px; } .bd-pageheader { // margin-top: 51px; } .page-header { padding-bottom: 9px; margin: 40px 0 20px; border-bottom: 1px solid #eee; } .navbar-default .navbar-nav > li > a { color: #777; } .navbar { padding: 0; } .navbar-nav .nav-item { margin-left: 0 !important; } .nav > li > a { position: relative; display: block; padding: 10px 15px; } .nav .navbar-brand { float: left; height: 50px; padding: 15px 15px; font-size: 18px; line-height: 20px; margin-right: 0 !important; } .navbar-brand { color: #777; float: left; height: 50px; padding: 15px 15px; font-size: 18px; line-height: 20px; } .navbar-toggler { margin-top: 8px; margin-right: 15px; } .navbar-default .navbar-nav > li > a:focus, .navbar-default .navbar-nav > li > a:hover { color: #333; background-color: transparent; } .bd-pageheader, .bs-docs-masthead { position: relative; padding: 30px 0; color: #cdbfe3; text-align: center; text-shadow: 0 1px 0 rgba(0, 0, 0, .1); background-color: #6f5499; background-image: -webkit-gradient(linear, left top, left bottom, from(#563d7c), to(#6f5499)); background-image: -webkit-linear-gradient(top, #563d7c 0, #6f5499 100%); background-image: -o-linear-gradient(top, #563d7c 0, #6f5499 100%); background-image: linear-gradient(to bottom, #563d7c 0, #6f5499 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#563d7c', endColorstr='#6F5499', GradientType=0); background-repeat: repeat-x; } .bd-pageheader { // margin-bottom: 40px; font-size: 20px; } .bd-pageheader h1 { margin-top: 0; color: #fff; } .bd-pageheader p { margin-bottom: 0; font-weight: 300; line-height: 1.4; } .bd-pageheader .btn { margin: 10px 0; } .scrollable-menu .nav-link { color: #337ab7; font-size: 14px; } .scrollable-menu .nav-link:hover { color: #23527c; background-color: #eee; } @media (min-width: 992px) { .bd-pageheader h1, .bd-pageheader p { margin-right: 380px; } } @media (min-width: 768px) { .bd-pageheader { // padding-top: 60px; // padding-bottom: 60px; font-size: 24px; text-align: left; } .bd-pageheader h1 { font-size: 60px; line-height: 1; } .navbar-nav > li > a.nav-link { padding-top: 15px; padding-bottom: 15px; font-size: 14px; } .navbar > .container .navbar-brand, .navbar > .container-fluid .navbar-brand { margin-left: -15px; } } @media (max-width: 767px) { .hidden-xs { display: none !important; } .navbar .container { width: 100%; max-width: 100%; } .navbar .container, .navbar .container .navbar-header { padding: 0; margin: 0; } } @media (max-width: 400px) { code, kbd { font-size: 60%; } } .scrollable-menu { height: 90vh !important; width: 100vw; overflow-x: hidden; padding: 0 0 20px; } /** * iPad with portrait orientation. */ @media all and (device-width: 768px) and (device-height: 1024px) and (orientation: portrait) { .scrollable-menu { height: 1024px !important; } } /** * iPad with landscape orientation. */ @media all and (device-width: 768px) and (device-height: 1024px) and (orientation: landscape) { .scrollable-menu { height: 768px !important; } } /** * iPhone 5 * You can also target devices with aspect ratio. */ @media screen and (device-aspect-ratio: 40/71) { .scrollable-menu { height: 500px !important; } } .navbar-default .navbar-toggle .icon-bar { background-color: #888; } .navbar-toggle:focus { outline: 0 } .navbar-toggle .icon-bar { display: block; width: 22px; height: 2px; border-radius: 1px } .navbar-toggle .icon-bar + .icon-bar { margin-top: 4px } pre { white-space: pre-wrap; /* CSS 3 */ white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ white-space: -pre-wrap; /* Opera 4-6 */ white-space: -o-pre-wrap; /* Opera 7 */ word-wrap: break-word; /* Internet Explorer 5.5+ */ } .chart-legend, .bar-legend, .line-legend, .pie-legend, .radar-legend, .polararea-legend, .doughnut-legend { list-style-type: none; margin-top: 5px; text-align: center; -webkit-padding-start: 0; -moz-padding-start: 0; padding-left: 0 } .chart-legend li, .bar-legend li, .line-legend li, .pie-legend li, .radar-legend li, .polararea-legend li, .doughnut-legend li { display: inline-block; white-space: nowrap; position: relative; margin-bottom: 4px; border-radius: 5px; padding: 2px 8px 2px 28px; font-size: smaller; cursor: default } .chart-legend li span, .bar-legend li span, .line-legend li span, .pie-legend li span, .radar-legend li span, .polararea-legend li span, .doughnut-legend li span { display: block; position: absolute; left: 0; top: 0; width: 20px; height: 20px; border-radius: 5px } ================================================ FILE: src/test.ts ================================================ // This file is required by karma.conf.js and loads recursively all the .spec and framework files import 'zone.js/dist/long-stack-trace-zone'; import 'zone.js/dist/proxy.js'; import 'zone.js/dist/sync-test'; import 'zone.js/dist/jasmine-patch'; import 'zone.js/dist/async-test'; import 'zone.js/dist/fake-async-test'; import { getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. declare const __karma__: any; declare const require: any; // Prevent Karma from running prematurely. __karma__.loaded = function () {}; // First, initialize the Angular testing environment. getTestBed().initTestEnvironment( BrowserDynamicTestingModule, platformBrowserDynamicTesting() ); // Then we find all the tests. const context = require.context('./', true, /\.spec\.ts$/); // And load the modules. context.keys().map(context); // Finally, start Karma to run the tests. __karma__.start(); ================================================ FILE: src/tsconfig.app.json ================================================ { "extends": "../tsconfig.json", "compilerOptions": { "outDir": "../out-tsc/app", "baseUrl": "./", "types": [] }, "files": [ "main.ts", "polyfills.ts" ], "include": [ "src/**/*.d.ts" ] } ================================================ FILE: src/tsconfig.json ================================================ { "compileOnSave": false, "compilerOptions": { "outDir": "./temp/out-tsc", "sourceMap": true, "declaration": false, "moduleResolution": "node", "emitDecoratorMetadata": true, "experimentalDecorators": true, "target": "es5", "mapRoot": "./", "typeRoots": ["../node_modules/@types"], "lib": ["es2015", "dom"] } } ================================================ FILE: src/tsconfig.spec.json ================================================ { "extends": "../tsconfig.json", "compilerOptions": { "outDir": "../out-tsc/spec", "baseUrl": "./", "target": "es5", "types": [ "jasmine", "node" ] }, "files": [ "test.ts", "polyfills.ts" ], "include": [ "**/*.spec.ts", "**/*.d.ts" ] } ================================================ FILE: src/typings.d.ts ================================================ /* SystemJS module definition */ declare var module: NodeModule; interface NodeModule { id: string; } ================================================ FILE: tsconfig.json ================================================ { "compileOnSave": false, "compilerOptions": { "module": "es2020", "outDir": "./dist/out-tsc", "sourceMap": true, "declaration": false, "moduleResolution": "node", "emitDecoratorMetadata": true, "experimentalDecorators": true, "target": "es2018", "typeRoots": ["node_modules/@types"], "lib": ["es2016","es2015", "dom"] } } ================================================ FILE: tslint.json ================================================ { "rulesDirectory": [ "node_modules/codelyzer" ], "rules": { "arrow-return-shorthand": true, "callable-types": true, "class-name": true, "comment-format": [ true, "check-space" ], "curly": true, "eofline": true, "forin": true, "deprecation": { "severity": "warning" }, "import-blacklist": [ true ], "import-spacing": true, "indent": [ true, "spaces" ], "interface-over-type-literal": true, "label-position": true, "max-line-length": [ true, 140 ], "member-access": false, "member-ordering": [ true, { "order": [ "static-field", "instance-field", "static-method", "instance-method" ] } ], "no-arg": true, "no-bitwise": true, "no-console": [ true, "debug", "info", "time", "timeEnd", "trace" ], "no-construct": true, "no-debugger": true, "no-duplicate-super": true, "no-empty": false, "no-empty-interface": true, "no-eval": true, "no-inferrable-types": [ true, "ignore-params" ], "no-misused-new": true, "no-non-null-assertion": true, "no-shadowed-variable": true, "no-string-literal": false, "no-string-throw": true, "no-switch-case-fall-through": true, "no-trailing-whitespace": true, "no-unnecessary-initializer": true, "no-unused-expression": true, "no-var-keyword": true, "object-literal-sort-keys": false, "one-line": [ true, "check-open-brace", "check-catch", "check-else", "check-whitespace" ], "prefer-const": true, "quotemark": [ true, "single" ], "radix": true, "semicolon": [ true, "always" ], "triple-equals": [ true, "allow-null-check" ], "typedef-whitespace": [ true, { "call-signature": "nospace", "index-signature": "nospace", "parameter": "nospace", "property-declaration": "nospace", "variable-declaration": "nospace" } ], "typeof-compare": true, "unified-signatures": true, "variable-name": false, "whitespace": [ true, "check-branch", "check-decl", "check-operator", "check-separator", "check-type" ], "directive-selector": [ true, "attribute", "app", "camelCase" ], "component-selector": [ true, "element", "app", "kebab-case" ], "use-input-property-decorator": true, "use-output-property-decorator": true, "use-host-property-decorator": true, "no-input-rename": true, "no-output-rename": true, "use-life-cycle-interface": true, "use-pipe-transform-interface": true, "component-class-suffix": true, "directive-class-suffix": true, "no-access-missing-member": true, "templates-use-public": true, "invoke-injectable": true } }