├── .circleci └── config.yml ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── support-question.md ├── renovate.json └── workflows │ ├── autoapprove.yml │ ├── codeql-analysis.yml │ └── tests.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── SECURITY.md ├── docs ├── setup-3rd-party-dependencies.md ├── using-angular-cli.md ├── using-ionic4.md ├── using-rollup.md ├── using-systemjs.md └── using-webpack.md ├── gulpfile.js ├── lerna.json ├── package-lock.json ├── package.json ├── packages ├── devextreme-angular-generator │ ├── gulpfile.js │ ├── package.json │ ├── spec │ │ ├── support │ │ │ └── jasmine.json │ │ └── tests │ │ │ └── metadata-generator.spec.js │ ├── src │ │ ├── common-reexports-generator.ts │ │ ├── component-names-generator.ts │ │ ├── dot-generator.ts │ │ ├── facade-generator.ts │ │ ├── helpers.ts │ │ ├── import-helper.ts │ │ ├── logger.ts │ │ ├── metadata-generator.ts │ │ ├── metadata-model.ts │ │ ├── module.facade-generator.ts │ │ └── templates │ │ │ ├── base-nested-component.tst │ │ │ ├── component.tst │ │ │ └── nested-component.tst │ ├── tsconfig.json │ └── tsconfig.local.json ├── devextreme-angular │ ├── .gitignore │ ├── build.config.js │ ├── gulpfile.js │ ├── karma.common.test.shim.js │ ├── karma.conf.js │ ├── karma.server.test.shim.js │ ├── karma.test.shim.js │ ├── metadata │ │ └── DeprecatedComponentsMetadata.json │ ├── package.json │ ├── src │ │ ├── core │ │ │ ├── component.ts │ │ │ ├── events-strategy.ts │ │ │ ├── index.ts │ │ │ ├── integration.ts │ │ │ ├── iterable-differ-helper.ts │ │ │ ├── nested-option.ts │ │ │ ├── package.json │ │ │ ├── template-host.ts │ │ │ ├── template.ts │ │ │ ├── transfer-state.ts │ │ │ ├── utils.ts │ │ │ └── watcher-helper.ts │ │ ├── schematics │ │ │ └── collection.json │ │ └── server │ │ │ ├── index.ts │ │ │ ├── package.json │ │ │ └── render.ts │ ├── tests │ │ └── src │ │ │ ├── core │ │ │ ├── angular-integration.spec.ts │ │ │ ├── component-extension.spec.ts │ │ │ ├── component.spec.ts │ │ │ ├── events-strategy.spec.ts │ │ │ ├── nested-option.spec.ts │ │ │ └── template.spec.ts │ │ │ ├── server │ │ │ ├── component-names.ts │ │ │ ├── ssr-ajax.spec.ts │ │ │ ├── ssr-components.spec.ts │ │ │ └── ssr-is-platform-server.spec.ts │ │ │ └── ui │ │ │ ├── chart.spec.ts │ │ │ ├── custom-value-accessor-implementation.spec.ts │ │ │ ├── data-grid.spec.ts │ │ │ ├── events.spec.ts │ │ │ ├── form.spec.ts │ │ │ ├── list.spec.ts │ │ │ ├── range-selector.spec.ts │ │ │ ├── responsive-box.spec.ts │ │ │ ├── select-box.spec.ts │ │ │ ├── tab-panel.spec.ts │ │ │ ├── tag-box.spec.ts │ │ │ ├── toolbar.spec.ts │ │ │ └── validator.spec.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── webpack.test.js └── sandbox │ ├── .gitignore │ ├── angular.json │ ├── package.json │ ├── src │ ├── app │ │ ├── app.component.html │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── appointment.service.ts │ │ ├── customer.service.ts │ │ ├── orange.service.ts │ │ └── owner.service.ts │ ├── browserslist │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ └── tsconfig.app.json │ └── tsconfig.json ├── tsconfig.json └── tslint.json /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/node:8-browsers 6 | 7 | steps: 8 | - checkout 9 | 10 | - run: 11 | name: Update npm 12 | command: "sudo npm install -g npm@6.14.11" 13 | - run: 14 | name: Install packages 15 | command: npm install --unsafe-perm 16 | - run: 17 | name: Run tools tests 18 | command: npx lerna run --scope devextreme-angular-generator test 19 | - run: 20 | name: Check code 21 | command: npm run lint 22 | - run: 23 | name: Build with Angular 7 24 | command: npm run build 25 | - run: 26 | name: Run tests with Angular 7 27 | command: npx lerna run --scope devextreme-angular gulp -- run.tests 28 | - run: 29 | name: Install Angular latest 30 | command: | 31 | npx lerna clean --yes 32 | npx lerna add @angular/animations@latest packages/devextreme-angular --dev --exact --no-bootstrap 33 | npx lerna add @angular/core@latest packages/devextreme-angular --dev --exact --no-bootstrap 34 | npx lerna add @angular/common@latest packages/devextreme-angular --dev --exact --no-bootstrap 35 | npx lerna add @angular/compiler@latest packages/devextreme-angular --dev --exact --no-bootstrap 36 | npx lerna add @angular/forms@latest packages/devextreme-angular --dev --exact --no-bootstrap 37 | npx lerna add @angular/platform-browser@latest packages/devextreme-angular --dev --exact --no-bootstrap 38 | npx lerna add @angular/platform-server@latest packages/devextreme-angular --dev --exact --no-bootstrap 39 | npx lerna add @angular/platform-browser-dynamic@latest packages/devextreme-angular --dev --exact --no-bootstrap 40 | npx lerna add @angular/compiler-cli@latest packages/devextreme-angular --dev --exact --no-bootstrap 41 | npx lerna bootstrap --no-ci 42 | - run: 43 | name: Run tests with Angular latest 44 | command: npx lerna run --scope devextreme-angular gulp -- run.tests 45 | - run: 46 | name: Install Angular Next 47 | command: | 48 | npx lerna clean --yes 49 | npx lerna add @angular/animations@next packages/devextreme-angular --dev --exact --no-bootstrap 50 | npx lerna add @angular/core@next packages/devextreme-angular --dev --exact --no-bootstrap 51 | npx lerna add @angular/common@next packages/devextreme-angular --dev --exact --no-bootstrap 52 | npx lerna add @angular/compiler@next packages/devextreme-angular --dev --exact --no-bootstrap 53 | npx lerna add @angular/forms@next packages/devextreme-angular --dev --exact --no-bootstrap 54 | npx lerna add @angular/platform-browser@next packages/devextreme-angular --dev --exact --no-bootstrap 55 | npx lerna add @angular/platform-server@next packages/devextreme-angular --dev --exact --no-bootstrap 56 | npx lerna add @angular/platform-browser-dynamic@next packages/devextreme-angular --dev --exact --no-bootstrap 57 | npx lerna add @angular/compiler-cli@next packages/devextreme-angular --dev --exact --no-bootstrap 58 | npx lerna bootstrap --no-ci 59 | - run: 60 | name: Run tests with Angular Next 61 | command: npx lerna run --scope devextreme-angular gulp -- run.tests 62 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | .circleci text eol=lf 2 | .github text eol=lf 3 | .eslintignore text eol=lf 4 | .eslintrc text eol=lf 5 | .gitattributes text eol=lf 6 | .gitignore text eol=lf 7 | *.js text eol=lf 8 | *.json text eol=lf 9 | *.md text eol=lf 10 | *.yml text eol=lf 11 | *.css text eol=lf 12 | *.ts text eol=lf 13 | *.html text eol=lf 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a bug in devextreme-angular 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 18 | 19 | # Bug Report 20 | 21 | 22 | 23 | **Package versions:** 24 | 25 | devexteme version: 26 | devextreme-angular version: 27 | 28 | **Steps to reproduce:** 29 | 34 | 35 | **Current behavior:** 36 | 37 | 38 | **Expected behavior:** 39 | 40 | 41 | **Screenshots:** 42 | 43 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest a feature for devextreme-angular 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 13 | 14 | # Feature request 15 | 16 | **Package versions you currently use:** 17 | 18 | devexteme version: 19 | devextreme-angular version: 20 | 21 | **Description:** 22 | 23 | 24 | **Preferred Solution:** 25 | 26 | 27 | **Alternatives:** 28 | 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/support-question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Support Question 3 | about: Questions and requests for support. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | # Support Question 11 | 12 | Please do not submit support requests and "How to" questions here. Navigate to our Support Center (https://devexpress.com/sc) instead. 13 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "prConcurrentLimit": 2, 6 | "semanticCommits": "enabled", 7 | "rangeStrategy": "bump", 8 | "baseBranches": [ 9 | "master" 10 | ], 11 | "labels": [ 12 | "dependencies" 13 | ], 14 | "vulnerabilityAlerts": { 15 | "enabled": true, 16 | "automerge": true 17 | }, 18 | "packageRules": [ 19 | { 20 | "matchPackageNames": [ "typescript", "typescript-min" ], 21 | "matchUpdateTypes": [ "major", "minor" ], 22 | "enabled": false 23 | }, 24 | { 25 | "matchPackagePatterns": [ "*" ], 26 | "matchUpdateTypes": [ "minor", "patch" ], 27 | "automerge": true 28 | }, 29 | { 30 | "matchPackagePatterns": [ "*" ], 31 | "matchUpdateTypes": [ "major" ], 32 | "enabled": false 33 | } 34 | ], 35 | "reviewers": [ 36 | "team:devextreme-devops" 37 | ], 38 | "ignorePaths": [ 39 | ".github", 40 | ".circleci" 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /.github/workflows/autoapprove.yml: -------------------------------------------------------------------------------- 1 | name: Auto approve 2 | 3 | on: 4 | pull_request_target 5 | 6 | jobs: 7 | auto-approve: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: hmarr/auto-approve-action@v2 11 | if: github.actor == 'renovate-bot' || github.actor == 'renovate[bot]' 12 | with: 13 | github-token: "${{ secrets.GITHUB_TOKEN }}" 14 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "master", "[0-9][0-9].[0-9]" ] 17 | pull_request: 18 | schedule: 19 | - cron: '0 0 * * 6' 20 | 21 | jobs: 22 | analyze: 23 | name: Analyze 24 | runs-on: ubuntu-latest 25 | permissions: 26 | actions: read 27 | contents: read 28 | security-events: write 29 | 30 | strategy: 31 | fail-fast: false 32 | matrix: 33 | language: [ 'javascript' ] 34 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 35 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 36 | 37 | steps: 38 | - name: Checkout repository 39 | uses: actions/checkout@v3 40 | 41 | # Initializes the CodeQL tools for scanning. 42 | - name: Initialize CodeQL 43 | uses: github/codeql-action/init@v2 44 | with: 45 | languages: ${{ matrix.language }} 46 | # If you wish to specify custom queries, you can do so here or in a config file. 47 | # By default, queries listed here will override any specified in a config file. 48 | # Prefix the list here with "+" to use these queries and those in the config file. 49 | 50 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 51 | # queries: security-extended,security-and-quality 52 | 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v2 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 61 | 62 | # If the Autobuild fails above, remove it and uncomment the following three lines. 63 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 64 | 65 | # - run: | 66 | # echo "Run, Build Application using script" 67 | # ./location_of_script_within_repo/buildscript.sh 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v2 71 | with: 72 | category: "/language:${{matrix.language}}" 73 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - '[12][0-9].[12]' 8 | pull_request: 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | timeout-minutes: 60 14 | 15 | steps: 16 | - name: Get sources 17 | uses: actions/checkout@v3 18 | 19 | - name: Clone devextreme repo from PR author fork 20 | continue-on-error: true 21 | if: github.event_name == 'pull_request' 22 | run: git clone -b ${{github.event.pull_request.head.ref}} https://github.com/${{github.event.pull_request.user.login}}/devextreme ../devextreme 23 | 24 | - name: Clone devextreme repo 25 | run: test -d ../devextreme || git clone -b 23_2 https://github.com/devexpress/devextreme ../devextreme 26 | 27 | - name: Use Node.js 18 28 | uses: actions/setup-node@v3 29 | with: 30 | node-version: '18' 31 | 32 | - name: Get npm cache directory 33 | id: npm-cache-dir 34 | shell: bash 35 | run: echo "dir=$(npm config get cache)" >> ${GITHUB_OUTPUT} 36 | 37 | - name: Restore devextreme npm cache 38 | uses: actions/cache@v3 39 | with: 40 | path: ${{ steps.npm-cache-dir.outputs.dir }} 41 | key: ${{ runner.os }}-node-modules-${{ hashFiles('**/package-lock.json') }} 42 | restore-keys: ${{ runner.os }}-node-modules 43 | 44 | - name: Install packages in devextreme repo 45 | working-directory: ../devextreme 46 | run: npm i --no-audit --no-fund 47 | 48 | - name: Build devextreme repo 49 | working-directory: ../devextreme/packages/devextreme 50 | run: npm run build-npm-devextreme 51 | 52 | - name: Discover declarations 53 | working-directory: ../devextreme/packages/devextreme 54 | run: npm run discover-declarations 55 | 56 | - name: Install devextreme package 57 | continue-on-error: true 58 | run: npm i --save-dev ../devextreme/packages/devextreme/artifacts/npm/devextreme --workspace=devextreme-angular --workspace=devextreme-angular-sandbox 59 | 60 | - name: Run npm install 61 | run: npm i --no-audit --no-fund 62 | 63 | - name: Update metadata 64 | run: npm run update-integration-meta 65 | 66 | - name: Run devextreme-angular-generator tests 67 | run: npm run test --workspace=devextreme-angular-generator 68 | 69 | - name: Run lint 70 | run: npm run lint 71 | 72 | - name: Build with Angular 12 73 | run: npm run build 74 | 75 | - name: Run tests with Angular 12 76 | run: npm run test:dev --workspace=devextreme-angular 77 | 78 | - name: Archive internal-tools artifacts 79 | uses: actions/upload-artifact@v3 80 | with: 81 | name: internal-tools-artifacts 82 | path: artifacts/internal-tools/ 83 | retention-days: 7 84 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | node_modules 4 | bower_components 5 | coverage 6 | dist 7 | npm/dist 8 | npm-debug.log 9 | .vscode/launch.json 10 | artifacts/ 11 | packages/devextreme-angular/metadata/NGMetaData.json 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Developer Express Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CircleCI](https://img.shields.io/circleci/project/github/DevExpress/devextreme-angular/master.svg)](https://circleci.com/gh/DevExpress/devextreme-angular) [![npm version](https://badge.fury.io/js/devextreme-angular.svg)](https://badge.fury.io/js/devextreme-angular) 2 | 3 | 4 | # DevExtreme Angular UI Components # 5 | 6 | This project allows you to use [DevExtreme components](http://js.devexpress.com/Demos/WidgetsGallery/) in [Angular](https://angular.io/) applications. 7 | 8 | * [Documentation](https://js.devexpress.com/Documentation/Guide/Angular_Components/DevExtreme_Angular_Components/) 9 | * [Technical Demos](https://js.devexpress.com/Demos/WidgetsGallery/Demo/DataGrid/Overview/Angular/Light/) 10 | * [Responsive UI Templates](https://js.devexpress.com/Angular/Templates/UITemplates/) 11 | * [Application Template](https://js.devexpress.com/Angular/Documentation/Guide/Angular_Components/Application_Template/) 12 | 13 | ## License ## 14 | 15 | **DevExtreme Angular UI Components are released as a MIT-licensed (free and open-source) add-on to DevExtreme.** 16 | 17 | Familiarize yourself with the [DevExtreme License](https://js.devexpress.com/Licensing/). [Free trial is available!](http://js.devexpress.com/Buy/) 18 | 19 | ## Support & Feedback ## 20 | 21 | If you have questions regarding Angular functionality, consult [Angular docs](https://angular.io/docs). 22 | 23 | If you want to report a bug, request a feature, or ask a question, submit an [issue](https://github.com/DevExpress/devextreme-angular/issues) to this repo. Alternatively, you can contact us at the [DevExpress Support Center](https://www.devexpress.com/Support/Center) if you own an active DevExtreme license. 24 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Security 2 | 3 | Please refer to [DevExpress Security Policy](https://github.com/DevExpress/Shared/security/policy) 4 | -------------------------------------------------------------------------------- /docs/setup-3rd-party-dependencies.md: -------------------------------------------------------------------------------- 1 | This article along with all other DevExtreme Angular articles was moved to the [DevExtreme website](https://js.devexpress.com/Documentation/Guide/Angular_Components/Getting_Started/Add_DevExtreme_to_an_Angular_CLI_Application/#Register_3rd-Party_Dependencies_in_Angular_CLI_6+). 2 | -------------------------------------------------------------------------------- /docs/using-angular-cli.md: -------------------------------------------------------------------------------- 1 | This article along with all other DevExtreme Angular articles was moved to the [DevExtreme website](https://js.devexpress.com/Documentation/Guide/Angular_Components/Getting_Started/Add_DevExtreme_to_an_Angular_CLI_Application/). -------------------------------------------------------------------------------- /docs/using-ionic4.md: -------------------------------------------------------------------------------- 1 | This article along with all other DevExtreme Angular articles was moved to the [DevExtreme website](https://js.devexpress.com/Documentation/Guide/Angular_Components/Getting_Started/Other_Approaches/Using_Ionic/). 2 | -------------------------------------------------------------------------------- /docs/using-rollup.md: -------------------------------------------------------------------------------- 1 | This article along with all other DevExtreme Angular articles was moved to the [DevExtreme website](https://js.devexpress.com/Documentation/Guide/Angular_Components/Getting_Started/Other_Approaches/Using_Rollup/). -------------------------------------------------------------------------------- /docs/using-systemjs.md: -------------------------------------------------------------------------------- 1 | This article along with all other DevExtreme Angular articles was moved to the [DevExtreme website](https://js.devexpress.com/Documentation/Guide/Angular_Components/Getting_Started/Other_Approaches/Using_SystemJS/). -------------------------------------------------------------------------------- /docs/using-webpack.md: -------------------------------------------------------------------------------- 1 | This article along with all other DevExtreme Angular articles was moved to the [DevExtreme website](https://js.devexpress.com/Documentation/Guide/Angular_Components/Getting_Started/Other_Approaches/Using_Webpack/). -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var tslint = require('gulp-tslint'); 3 | 4 | const srcFilesPattern = ['packages/*/src/**/*.ts', '!packages/*/src/ui/**', '!packages/*/src/metadata-model.ts']; 5 | const sandboxSrcFilesPattern = 'packages/sandbox/**/*.ts'; 6 | const testsFilesPattern = ['packages/*/tests/src/**/*.spec.ts', 'packages/*/tests/src/**/component-names.ts']; 7 | const nodeModulesExcludePattern = ['!**/node_modules/**/*']; 8 | 9 | 10 | gulp.task('lint', function() { 11 | return gulp.src(srcFilesPattern 12 | .concat(sandboxSrcFilesPattern) 13 | .concat(testsFilesPattern) 14 | .concat(nodeModulesExcludePattern) 15 | ) 16 | .pipe(tslint({ 17 | formatter: 'prose', 18 | tslint: require('tslint').default, 19 | rulesDirectory: null, 20 | configuration: 'tslint.json' 21 | })) 22 | .pipe(tslint.report()); 23 | }); 24 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "version": "independent", 6 | "npmClient": "npm", 7 | "stream": true, 8 | "nohoist": ["@types/jasmine", "ng-packagr"] 9 | } 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "version": "0.0.0", 5 | "description": "Angular UI and visualization components based on DevExtreme widgets", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/DevExpress/devextreme-angular.git" 9 | }, 10 | "scripts": { 11 | "start": "npm run build && npm run --workspace=devextreme-angular-sandbox start", 12 | "start:dev": "npm run build && npm run --workspace=devextreme-angular-sandbox start:dev", 13 | "lite": "npm run --workspace=devextreme-angular-sandbox lite", 14 | "build": "npm run update-integration-meta && npm run build --ws --if-present", 15 | "test": "npm run test --ws --if-present", 16 | "test:dev": "npm run test:dev --ws --if-present", 17 | "pack": "npm run update-integration-meta && npm run pack --ws --if-present", 18 | "lint": "gulp lint", 19 | "update-integration-meta": "dx-tools generate-ng-smd --artifacts ../devextreme/packages/devextreme/artifacts/internal-tools --output-path ./packages/devextreme-angular/metadata/NGMetaData.json" 20 | }, 21 | "author": "Developer Express Inc.", 22 | "license": "MIT", 23 | "devDependencies": { 24 | "codelyzer": "6.0.2", 25 | "devextreme-internal-tools": "10.0.0-beta.18", 26 | "gulp": "^4.0.2", 27 | "gulp-tslint": "^7.1.0", 28 | "tslint": "6.1.3", 29 | "webpack": "^5.88.2" 30 | }, 31 | "workspaces": [ 32 | "packages/*" 33 | ], 34 | "keywords": [ 35 | "angular", 36 | "devextreme", 37 | "devexpress" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /packages/devextreme-angular-generator/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var path = require('path'); 3 | var typescript = require('gulp-typescript'); 4 | var exec = require('child_process').exec; 5 | var sourcemaps = require('gulp-sourcemaps'); 6 | var jasmine = require('gulp-jasmine'); 7 | var jasmineReporters = require('jasmine-reporters'); 8 | var del = require('del'); 9 | 10 | const SRC_FILES_PATTERN = './src/**/*.ts'; 11 | const TEMPLATES_FILES_PATTERN = './src/templates/*.tst'; 12 | const DIST_PATH = './dist'; 13 | 14 | //------------npm------------ 15 | 16 | gulp.task('npm.pack', gulp.series( 17 | (cb) => { exec('npm pack', (err) => cb(err)); }, 18 | () => gulp.src('./*.tgz').pipe(gulp.dest(DIST_PATH)), 19 | (c) => del('./*.tgz', c) 20 | )); 21 | 22 | //------------Main------------ 23 | 24 | var buildTask = (tsProject = 'tsconfig.json') => gulp.series( 25 | () => gulp.src(TEMPLATES_FILES_PATTERN).pipe(gulp.dest(path.join(DIST_PATH, 'templates'))), 26 | () => gulp.src(SRC_FILES_PATTERN) 27 | .pipe(sourcemaps.init()) 28 | .pipe(typescript(tsProject)) 29 | .pipe(sourcemaps.write('.')) 30 | .pipe(gulp.dest(DIST_PATH)) 31 | ); 32 | 33 | gulp.task('build', buildTask()); 34 | gulp.task('build:local', buildTask('tsconfig.local.json')); 35 | gulp.task('default', buildTask()); 36 | 37 | 38 | //------------Testing------------ 39 | 40 | gulp.task('run.tests', function() { 41 | return gulp.src('./spec/tests/*.spec.js') 42 | .pipe(jasmine({ 43 | errorOnFail: false, 44 | reporter: [ 45 | new jasmineReporters.TerminalReporter({ 46 | verbosity: 1, 47 | color: true, 48 | showStack: true 49 | }) 50 | ] 51 | })); 52 | }); 53 | 54 | gulp.task('test', gulp.series('build', 'run.tests')); 55 | 56 | -------------------------------------------------------------------------------- /packages/devextreme-angular-generator/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Developer Express Inc.", 3 | "name": "devextreme-angular-generator", 4 | "version": "2.1.2", 5 | "description": "Angular UI and visualization components based on DevExtreme widgets", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/DevExpress/devextreme-angular.git" 9 | }, 10 | "main": "dist/dot-generator", 11 | "types": "dist/dot-generator", 12 | "files": [ 13 | "dist" 14 | ], 15 | "scripts": { 16 | "gulp": "npx --node-options='--max_old_space_size=8048' gulp", 17 | "build": "npm run gulp -- build", 18 | "build:local": "npm run gulp -- build:local", 19 | "test": "npm run gulp -- test", 20 | "test:dev": "npm run gulp -- run.tests", 21 | "pack": "npm run gulp -- build && npm run gulp -- npm.pack" 22 | }, 23 | "keywords": [ 24 | "angular", 25 | "devextreme", 26 | "devexpress" 27 | ], 28 | "license": "MIT", 29 | "dependencies": { 30 | "deepmerge": "^2.2.1", 31 | "dot": "^1.1.3", 32 | "inflector-js": "^1.0.1", 33 | "mkdirp": "^0.5.6", 34 | "yargs": "^6.6.0" 35 | }, 36 | "devDependencies": { 37 | "@types/dot": "^1.1.5", 38 | "@types/jasmine": "2.8.19", 39 | "@types/mkdirp": "^0.5.2", 40 | "@types/node": "^18.17.5", 41 | "@types/yargs": "^6.6.0", 42 | "del": "^2.2.2", 43 | "gulp": "^4.0.2", 44 | "gulp-jasmine": "^2.4.2", 45 | "gulp-sourcemaps": "^2.6.5", 46 | "gulp-typescript": "^3.2.4", 47 | "jasmine-reporters": "2.5.2", 48 | "typescript": "~4.2.3" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/devextreme-angular-generator/spec/support/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "spec", 3 | "spec_files": [ 4 | "**/*[sS]pec.js" 5 | ], 6 | "helpers": [ 7 | "helpers/**/*.js" 8 | ], 9 | "stopSpecOnExpectationFailure": false, 10 | "random": false 11 | } 12 | -------------------------------------------------------------------------------- /packages/devextreme-angular-generator/src/common-reexports-generator.ts: -------------------------------------------------------------------------------- 1 | import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs'; 2 | import { join as joinPaths, dirname as getDirName } from 'path'; 3 | import { createTemplateFromString } from './dot-generator'; 4 | 5 | const render: (model: { module: string, reexports: string[] }) => string = createTemplateFromString(` 6 | export {<#~ it.reexports :reExport #> 7 | <#= reExport #>,<#~#> 8 | } from 'devextreme/<#= it.module #>'; 9 | `.trimLeft()); 10 | 11 | export default class CommonReexportsGenerator { 12 | generate(config) { 13 | let metadata = JSON.parse(readFileSync(config.metadataPath).toString()); 14 | 15 | if (!metadata.CommonReexports) { 16 | return; 17 | } 18 | 19 | const commonTargetFolderName = 'common'; 20 | const commonPath = joinPaths(config.outputPath, commonTargetFolderName); 21 | if (!existsSync(commonPath)) { 22 | mkdirSync(commonPath); 23 | } 24 | Object.keys(metadata.CommonReexports).forEach((key) => { 25 | const targetFileName = key === commonTargetFolderName ? 'index.ts' : `${key.replace(`${commonTargetFolderName}/`, '')}.ts`; 26 | const fullPath = joinPaths(commonPath, targetFileName); 27 | mkdirSync(getDirName(fullPath), { recursive: true }); 28 | writeFileSync( 29 | fullPath, 30 | this.generateReexports(key, metadata.CommonReexports[key]), 31 | { encoding: 'utf8' }, 32 | ); 33 | }); 34 | } 35 | private generateReexports(module: string, reexports: string[]): string { 36 | const result = render({ module, reexports }); 37 | return result; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/devextreme-angular-generator/src/component-names-generator.ts: -------------------------------------------------------------------------------- 1 | import fs = require('fs'); 2 | import path = require('path'); 3 | 4 | export default class ComponentNamesGenerator { 5 | private _encoding: BufferEncoding = 'utf8'; 6 | 7 | private _config; 8 | 9 | constructor(config) { 10 | this._config = config; 11 | } 12 | 13 | prepareTagName(fileName: string) { 14 | return ` '${fileName.replace('.ts', '')}'`; 15 | } 16 | 17 | validateFileName(fileName: string) { 18 | return this._config.excludedFileNames.indexOf(fileName) < 0; 19 | } 20 | 21 | generate() { 22 | let directoryPath = this._config.componentFilesPath; 23 | let files = fs.readdirSync(directoryPath); 24 | let fileList = files 25 | .filter(fileName => !fs.lstatSync(path.join(directoryPath, fileName)).isFile() && this.validateFileName(fileName)) 26 | .map(fileName => this.prepareTagName(fileName)) 27 | .join(',\n'); 28 | let resultContent = `export const componentNames = [\n${fileList}\n];\n`; 29 | fs.writeFileSync(this._config.outputFileName, resultContent, { encoding: this._encoding }); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /packages/devextreme-angular-generator/src/dot-generator.ts: -------------------------------------------------------------------------------- 1 | import fs = require('fs'); 2 | import path = require('path'); 3 | import mkdirp = require('mkdirp'); 4 | import logger from './logger'; 5 | let doT = require('dot'); 6 | 7 | doT.templateSettings = { 8 | evaluate: /\<#([\s\S]+?)#\>/g, 9 | interpolate: /\<#=([\s\S]+?)#\>/g, 10 | encode: /\<#!([\s\S]+?)#\>/g, 11 | use: /\<##([\s\S]+?)#\>/g, 12 | define: /\<###\s*([\w\.$]+)\s*(\:|=)([\s\S]+?)##\>/g, 13 | conditional: /\<#\?(\?)?\s*([\s\S]*?)\s*#\>/g, 14 | iterate: /\<#~\s*(?:#\>|([\s\S]+?)\s*\:\s*([\w$]+)\s*(?:\:\s*([\w$]+))?\s*#\>)/g, 15 | varname: 'it', 16 | strip: false, 17 | append: true, 18 | selfcontained: false 19 | }; 20 | 21 | export function createTemplateFromString(templateString: string) { 22 | return doT.template(templateString); 23 | } 24 | 25 | export default class DoTGenerator { 26 | private _encoding: BufferEncoding = 'utf8'; 27 | createTemplate(templateFilePath: string) { 28 | logger('Create doT template from ' + templateFilePath); 29 | let templateString = fs.readFileSync(templateFilePath, this._encoding); 30 | return createTemplateFromString(templateString); 31 | } 32 | generate(config) { 33 | this.generateTemplate(config.templateFilePath || path.join(__dirname, './templates/component.tst'), 34 | config.metadataFolderPath, 35 | config.outputFolderPath, 36 | true); 37 | 38 | this.generateTemplate(config.nestedTemplateFilePath || path.join(__dirname, './templates/nested-component.tst'), 39 | path.join(config.metadataFolderPath, config.nestedPathPart), 40 | path.join(config.outputFolderPath, config.nestedPathPart)); 41 | 42 | this.generateTemplate(config.baseNestedTemplateFilePath || path.join(__dirname, './templates/base-nested-component.tst'), 43 | path.join(config.metadataFolderPath, config.nestedPathPart, config.basePathPart), 44 | path.join(config.outputFolderPath, config.nestedPathPart, config.basePathPart)); 45 | 46 | this.createEntryPoint(config.outputFolderPath, 'nested'); 47 | } 48 | 49 | private generateTemplate( 50 | templateFilePath: string, 51 | metadataFolderPath: string, 52 | outputFolderPath: string, 53 | isSecondaryEntryPoint = false 54 | ) { 55 | let template = this.createTemplate(templateFilePath); 56 | mkdirp.sync(outputFolderPath); 57 | 58 | logger('List directory: ' + metadataFolderPath); 59 | const names = []; 60 | fs.readdirSync(metadataFolderPath) 61 | .filter(fileName => fs.lstatSync(path.join(metadataFolderPath, fileName)).isFile()) 62 | .forEach(fileName => { 63 | let filePath = path.join(metadataFolderPath, fileName); 64 | 65 | logger('Read data from ' + filePath); 66 | let data = fs.readFileSync(filePath, { encoding: this._encoding }); 67 | logger('Apply template'); 68 | let result = template(JSON.parse(data)); 69 | const widgetName = path.parse(filePath).name; 70 | names.push(widgetName); 71 | let resultFilePath; 72 | if (isSecondaryEntryPoint) { 73 | fs.mkdirSync(path.join(outputFolderPath, widgetName)); 74 | resultFilePath = path.join(outputFolderPath, widgetName + '/index.ts'); 75 | 76 | this.createEntryPoint(outputFolderPath, widgetName); 77 | } else { 78 | resultFilePath = path.join(outputFolderPath, widgetName + '.ts'); 79 | } 80 | 81 | logger('Write result to ' + resultFilePath); 82 | fs.writeFileSync(resultFilePath, result, { encoding: this._encoding }); 83 | }); 84 | 85 | if (!isSecondaryEntryPoint) { 86 | fs.writeFileSync( 87 | outputFolderPath + '/index.ts', 88 | names.map(name => `export * from './${name}';\n`).join('') + '\n', 89 | { encoding: this._encoding } 90 | ); 91 | } 92 | } 93 | 94 | private createEntryPoint(outputFolderPath: string, entryPoint: string) { 95 | fs.writeFileSync(path.join(outputFolderPath, entryPoint + '/package.json'), 96 | JSON.stringify({ 97 | ngPackage: { 98 | lib: { 99 | entryFile: 'index.ts' 100 | } 101 | } 102 | }, null, ' '), { encoding: this._encoding }); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /packages/devextreme-angular-generator/src/facade-generator.ts: -------------------------------------------------------------------------------- 1 | import fs = require('fs'); 2 | import path = require('path'); 3 | import logger from './logger'; 4 | import inflector = require('inflector-js'); 5 | 6 | export default class FacadeGenerator { 7 | private _encoding: BufferEncoding = 'utf8'; 8 | generate(config) { 9 | Object.keys(config.facades).forEach(facadeFilePath => { 10 | logger('Generate facade: ' + facadeFilePath); 11 | let facadeConfig = config.facades[facadeFilePath], 12 | resultContent = ''; 13 | 14 | resultContent += `export * from 'devextreme-angular/core';\n`; 15 | resultContent += `export * from './ui/all';\n`; 16 | 17 | config.commonImports.forEach(i => { 18 | resultContent += `import '${i}';\n`; 19 | }); 20 | 21 | fs.readdirSync(facadeConfig.sourceDirectories[0]) 22 | .filter(fileName => fs.lstatSync(path.join(facadeConfig.sourceDirectories[0], fileName)).isFile()) 23 | .forEach(fileName => { 24 | const { name } = path.parse(path.join(facadeConfig.sourceDirectories[0], fileName)); 25 | const formattedName = formatName(name); 26 | const where = `'devextreme-angular/ui/${name}'`; 27 | resultContent += `export { Dx${formattedName}Component, Dx${formattedName}Module } from ${where};\n`; 28 | }); 29 | 30 | logger('Write result to ' + facadeFilePath); 31 | fs.writeFileSync(facadeFilePath, resultContent, { encoding: this._encoding }); 32 | }); 33 | } 34 | } 35 | 36 | 37 | function formatName(name: string): string { 38 | if (!name.includes('-')) { 39 | return inflector.camelize(name); 40 | } 41 | return name.split('-').map((n) => inflector.camelize(n)).join(''); 42 | } 43 | -------------------------------------------------------------------------------- /packages/devextreme-angular-generator/src/helpers.ts: -------------------------------------------------------------------------------- 1 | 2 | export function byKeyComparer(getter: (obj: T) => string): Parameters['sort']>[0] { 3 | return (objA: T, objB: T) => getter(objA).localeCompare(getter(objB)); 4 | } 5 | 6 | export function getValues(obj: Record): T[] { 7 | return obj ? Object.keys(obj).map(k => obj[k]) : undefined; 8 | } 9 | -------------------------------------------------------------------------------- /packages/devextreme-angular-generator/src/import-helper.ts: -------------------------------------------------------------------------------- 1 | import { Import, ImportName, Option } from './metadata-model'; 2 | import { getValues, byKeyComparer } from './helpers'; 3 | 4 | export interface FileImport { 5 | path: string; 6 | importString: string; 7 | } 8 | 9 | export function buildImports(options: Option[], widgetPackageName: string): FileImport[] { 10 | 11 | const importsByPath = extractImportsMeta(options).reduce( 12 | (paths, {Path, Name, Alias}) => { 13 | if (!paths[Path]) { 14 | paths[Path] = {}; 15 | } 16 | 17 | paths[Path][`${Name}+${Alias}`] = { Name, Alias }; 18 | 19 | return paths; 20 | }, {} as Record> 21 | ); 22 | 23 | return Object.keys(importsByPath) 24 | .map(path => { 25 | const {defaultImport, namedImports} = extractDefaultImport(getValues(importsByPath[path])); 26 | const parts = []; 27 | 28 | if (defaultImport) { 29 | parts.push(defaultImport); 30 | } 31 | 32 | if (namedImports.length) { 33 | const namedImportsString = namedImports 34 | .sort(byKeyComparer(i => i.Name)) 35 | .map(({Name, Alias}) => Alias ? `${Name} as ${Alias}` : Name) 36 | .join(', '); 37 | 38 | parts.push(`{ ${namedImportsString} }`); 39 | } 40 | 41 | return { 42 | path: `${widgetPackageName}/${path}`, 43 | importString: parts.join(', ') 44 | } as FileImport; 45 | }) 46 | .sort(byKeyComparer(i => i.path)); 47 | } 48 | 49 | function extractImportsMeta(options: Option[]): Import[] { 50 | if (!options || !options.length) { 51 | return []; 52 | } 53 | 54 | return options.reduce( 55 | (r, option) => { 56 | if (option) { 57 | r.push(...option.TypeImports); 58 | r.push(...extractImportsMeta(getValues(option.Options))); 59 | } 60 | return r; 61 | }, [] as Import[] 62 | ); 63 | } 64 | 65 | function extractDefaultImport(imports: ImportName[]): { defaultImport?: string; namedImports: ImportName[] } { 66 | const result: ReturnType = { defaultImport: undefined, namedImports: [] }; 67 | 68 | for (const entry of imports) { 69 | if (isDefaultImport(entry)) { 70 | if (!entry.Alias) { 71 | throw new Error(`default export must have an alias: ${JSON.stringify(entry)}`); 72 | } 73 | result.defaultImport = entry.Alias; 74 | } else { 75 | result.namedImports.push(entry); 76 | } 77 | } 78 | 79 | return result; 80 | } 81 | 82 | function isDefaultImport(importName: ImportName): boolean { 83 | return importName.Name.toLowerCase() === 'default'; 84 | } 85 | -------------------------------------------------------------------------------- /packages/devextreme-angular-generator/src/logger.ts: -------------------------------------------------------------------------------- 1 | import yargs = require('yargs'); 2 | 3 | let argv = yargs 4 | .default('verbose', false) 5 | .argv; 6 | 7 | export default function(...args) { 8 | if (argv.verbose) { 9 | console.log.apply(console, args); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /packages/devextreme-angular-generator/src/metadata-model.ts: -------------------------------------------------------------------------------- 1 | export interface ImportName { 2 | Name: string; 3 | Alias?: string; 4 | } 5 | 6 | export interface Import extends ImportName { 7 | Path: string; 8 | } 9 | 10 | export interface NestedOptions{ 11 | [optionName: string]: Option; 12 | } 13 | 14 | export interface Option { 15 | PrimitiveTypes?: string[]; 16 | ItemPrimitiveTypes: string[]; 17 | TypeImports?: Import[]; 18 | IsDataSource?: boolean; 19 | IsPromise?: boolean; 20 | IsDeprecated?: boolean; 21 | IsCollection?: boolean; 22 | IsChangeable?: boolean; 23 | IsTemplate?: boolean; 24 | IsReadonly?: boolean; 25 | SingularName?: string; 26 | IsEvent?: boolean; 27 | IsFunc?: boolean; 28 | DocID: string; 29 | TsType: { 30 | Name: string; 31 | File: string; 32 | }, 33 | Options: NestedOptions 34 | } 35 | 36 | export interface Metadata { 37 | Widgets: { 38 | [widgetName: string]: { 39 | DocID: string; 40 | Module: string; 41 | IsTranscludedContent?: boolean; 42 | IsExtensionComponent?: boolean; 43 | IsDeprecated?: boolean; 44 | Options: { 45 | [optionName: string]: Option; 46 | }; 47 | OptionsTypeParams: string[]; 48 | Reexports: string[]; 49 | } 50 | }; 51 | ExtraObjects: any[]; 52 | } -------------------------------------------------------------------------------- /packages/devextreme-angular-generator/src/module.facade-generator.ts: -------------------------------------------------------------------------------- 1 | import fs = require('fs'); 2 | import path = require('path'); 3 | import logger from './logger'; 4 | let inflector = require('inflector-js'); 5 | 6 | export default class FacadeGenerator { 7 | private _encoding: BufferEncoding = 'utf8'; 8 | 9 | prepareModuleName(fileName: string) { 10 | fileName = fileName.replace(/-/g, '_'); 11 | fileName = inflector.camelize(fileName); 12 | fileName = 'Dx' + fileName + 'Module'; 13 | 14 | return fileName; 15 | } 16 | 17 | generate(config) { 18 | Object.keys(config.moduleFacades).forEach(moduleFilePath => { 19 | logger('Generate facade: ' + moduleFilePath); 20 | let facadeConfig = config.moduleFacades[moduleFilePath], 21 | moduleNamesString = '', 22 | importModuleString = ''; 23 | 24 | facadeConfig.sourceComponentDirectories.forEach(directoryPath => { 25 | logger('List directory: ' + directoryPath); 26 | let files = fs.readdirSync(directoryPath); 27 | 28 | files 29 | .filter(fileName => !fs.lstatSync(path.join(directoryPath, fileName)).isFile() && fileName !== 'nested') 30 | .forEach(fileName => { 31 | let moduleName = this.prepareModuleName(fileName); 32 | 33 | moduleNamesString += `\n ${moduleName},`; 34 | importModuleString += `import { ${moduleName} } from 'devextreme-angular/ui/${fileName}';\n`; 35 | }); 36 | }); 37 | 38 | Object.keys(facadeConfig.additionalImports).forEach(importName => { 39 | moduleNamesString += '\n ' + importName + ','; 40 | importModuleString += facadeConfig.additionalImports[importName] + ';\n'; 41 | }); 42 | 43 | moduleNamesString = moduleNamesString.slice(0, -1); 44 | importModuleString = importModuleString.slice(0, -1); 45 | 46 | let resultContent = `import { NgModule } from '@angular/core'; 47 | ` + importModuleString + ` 48 | 49 | @NgModule({ 50 | imports: [` + moduleNamesString + `\n ], 51 | exports: [` + moduleNamesString + `\n ] 52 | }) 53 | export class DevExtremeModule {} 54 | `; 55 | 56 | logger('Write result to ' + moduleFilePath); 57 | fs.writeFileSync(moduleFilePath, resultContent, { encoding: this._encoding }); 58 | }); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/devextreme-angular-generator/src/templates/base-nested-component.tst: -------------------------------------------------------------------------------- 1 | /* tslint:disable:max-line-length */ 2 | 3 | import { <#= it.baseClass #> } from '<#= it.basePath #>'; 4 | import { 5 | Component, 6 | } from '@angular/core'; 7 | 8 | <#? it.imports #><#~ it.imports :file #>import <#= file.importString #> from '<#= file.path #>'; 9 | <#~#><#?#> 10 | @Component({ 11 | template: '' 12 | }) 13 | export abstract class <#= it.className #> extends <#= it.baseClass #> {<#~ it.properties :prop:i #> 14 | get <#= prop.name #>(): <#= prop.type #> { 15 | return this._getOption('<#= prop.name #>'); 16 | } 17 | set <#= prop.name #>(value: <#= prop.type #>) { 18 | this._setOption('<#= prop.name #>', value); 19 | } 20 | <#~#>} 21 | -------------------------------------------------------------------------------- /packages/devextreme-angular-generator/src/templates/component.tst: -------------------------------------------------------------------------------- 1 | /* tslint:disable:max-line-length */ 2 | <# 3 | var collectionProperties = it.properties.filter(item => item.isCollection).map(item => item.name); 4 | var collectionNestedComponents = it.nestedComponents.filter(item => item.isCollection && item.root); 5 | var baseClass = it.isExtension ? 'DxComponentExtension' : 'DxComponent'; 6 | var reExportExplicitTypes = it.optionsTypeParams && it.optionsTypeParams.length; 7 | 8 | var implementedInterfaces = ['OnDestroy']; 9 | 10 | it.isEditor && implementedInterfaces.push('ControlValueAccessor'); 11 | collectionProperties.length && implementedInterfaces.push('OnChanges', 'DoCheck'); 12 | #> 13 | 14 | import { TransferState } from '@angular/platform-browser'; 15 | 16 | import { 17 | Component, 18 | NgModule, 19 | ElementRef, 20 | NgZone, 21 | PLATFORM_ID, 22 | Inject, 23 | 24 | Input, 25 | Output, 26 | OnDestroy,<#? it.isExtension #> 27 | SkipSelf, 28 | Optional, 29 | Host,<#?#> 30 | EventEmitter<#? it.isEditor #>, 31 | forwardRef, 32 | HostListener<#?#><#? collectionProperties.length #>, 33 | OnChanges, 34 | DoCheck, 35 | SimpleChanges<#?#><#? collectionNestedComponents.length #>, 36 | ContentChildren, 37 | QueryList<#?#> 38 | } from '@angular/core'; 39 | 40 | <#? reExportExplicitTypes #>export { ExplicitTypes } from '<#= it.module #>'; 41 | <#?#> 42 | <#? it.imports #><#~ it.imports :file #>import <#= file.importString #> from '<#= file.path #>'; 43 | <#~#><#?#> 44 | import <#= it.className #> from '<#= it.module #>'; 45 | <#? it.isEditor #> 46 | import { 47 | ControlValueAccessor, 48 | NG_VALUE_ACCESSOR 49 | } from '@angular/forms';<#?#> 50 | 51 | import { 52 | <#= baseClass #>, 53 | DxTemplateHost, 54 | DxIntegrationModule, 55 | DxTemplateModule, 56 | NestedOptionHost,<#? collectionProperties.length #> 57 | IterableDifferHelper,<#?#> 58 | WatcherHelper 59 | } from 'devextreme-angular/core'; 60 | 61 | <#~ it.nestedComponents :component:i #>import { <#= component.className #>Module } from '<#= it.packageName #>/ui/nested'; 62 | <#~#> 63 | <#~ collectionNestedComponents :component:i #>import { <#= component.className #>Component } from '<#= it.packageName #>/ui/nested'; 64 | <#~#> 65 | 66 | <#? it.isEditor #> 67 | 68 | const CUSTOM_VALUE_ACCESSOR_PROVIDER = { 69 | provide: NG_VALUE_ACCESSOR, 70 | useExisting: forwardRef(() => <#= it.className #>Component), 71 | multi: true 72 | };<#?#> 73 | /** 74 | * [descr:<#= it.docID #>] 75 | <#? it.isDeprecated #> 76 | * @deprecated [depNote:<#= it.docID #>] 77 | <#?#> 78 | */ 79 | @Component({ 80 | selector: '<#= it.selector #>', 81 | template: '<#? it.isTranscludedContent #><#?#>',<#? it.isViz #> 82 | styles: [ ' :host { display: block; }'],<#?#> 83 | providers: [ 84 | DxTemplateHost, 85 | WatcherHelper,<#? it.isEditor #> 86 | CUSTOM_VALUE_ACCESSOR_PROVIDER,<#?#> 87 | NestedOptionHost<#? collectionProperties.length #>, 88 | IterableDifferHelper<#?#> 89 | ] 90 | }) 91 | export class <#= it.className #>Component extends <#= baseClass #> <#? implementedInterfaces.length #>implements <#= implementedInterfaces.join(', ') #> <#?#>{ 92 | instance: <#= it.className #>; 93 | <#~ it.properties :prop:i #> 94 | /** 95 | * [descr:<#= prop.docID #>] 96 | <#? prop.isDeprecated #> 97 | * @deprecated [depNote:<#= prop.docID #>] 98 | <#?#> 99 | */ 100 | @Input() 101 | get <#= prop.name #>(): <#= prop.type #> { 102 | return this._getOption('<#= prop.name #>'); 103 | } 104 | set <#= prop.name #>(value: <#= prop.type #>) { 105 | this._setOption('<#= prop.name #>', value); 106 | }<#? i < it.properties.length-1 #> 107 | 108 | <#?#><#~#> 109 | <#~ it.events :event:i #> 110 | /** 111 | <#? event.isInternal #> 112 | * This member supports the internal infrastructure and is not intended to be used directly from your code. 113 | <#??#> 114 | * [descr:<#= event.docID #>] 115 | <#? event.isDeprecated #> 116 | * @deprecated [depNote:<#= event.docID #>] 117 | <#?#> 118 | <#?#> 119 | */ 120 | @Output() <#= event.emit #>: <#= event.type #>;<#? i < it.events.length-1 #> 121 | <#?#><#~#> 122 | 123 | <#? it.isEditor #> 124 | @HostListener('valueChange', ['$event']) change(_) { } 125 | @HostListener('onBlur', ['$event']) touched = (_) => {};<#?#> 126 | 127 | <#~ collectionNestedComponents :component:i #> 128 | @ContentChildren(<#= component.className #>Component) 129 | get <#= component.propertyName #>Children(): QueryList<<#= component.className #>Component> { 130 | return this._getOption('<#= component.propertyName #>'); 131 | } 132 | set <#= component.propertyName #>Children(value) { 133 | this.setChildren('<#= component.propertyName #>', value); 134 | } 135 | <#~#> 136 | 137 | <#? it.isExtension #> 138 | parentElement: any; 139 | <#?#> 140 | 141 | constructor(elementRef: ElementRef, ngZone: NgZone, templateHost: DxTemplateHost, 142 | <#? collectionProperties.length #>private <#?#>_watcherHelper: WatcherHelper<#? collectionProperties.length #>, 143 | private _idh: IterableDifferHelper<#?#>,<#? it.isExtension #> 144 | @SkipSelf() @Optional() @Host() parentOptionHost: NestedOptionHost,<#?#> 145 | optionHost: NestedOptionHost, 146 | transferState: TransferState, 147 | @Inject(PLATFORM_ID) platformId: any) { 148 | 149 | super(elementRef, ngZone, templateHost, _watcherHelper, transferState, platformId); 150 | 151 | this._createEventEmitters([ 152 | <#~ it.events :event:i #>{ <#? event.subscribe #>subscribe: '<#= event.subscribe #>', <#?#>emit: '<#= event.emit #>' }<#? i < it.events.length-1 #>, 153 | <#?#><#~#> 154 | ]);<#? it.isExtension #> 155 | this.parentElement = this.getParentElement(parentOptionHost);<#?#><#? collectionProperties.length #> 156 | 157 | this._idh.setHost(this);<#?#> 158 | optionHost.setHost(this); 159 | } 160 | 161 | protected _createInstance(element, options) { 162 | <#? it.isExtension #> 163 | if (this.parentElement) { 164 | return new DxValidator(this.parentElement, options); 165 | } 166 | <#?#> 167 | return new <#= it.className #>(element, options); 168 | } 169 | <#? it.isExtension #> 170 | private getParentElement(host) { 171 | if (host) { 172 | const parentHost = host.getHost(); 173 | return (parentHost as any).element.nativeElement; 174 | } 175 | return; 176 | } 177 | <#?#> 178 | <#? it.isEditor #> 179 | writeValue(value: any): void { 180 | this.eventHelper.lockedValueChangeEvent = true; 181 | this.value = value; 182 | this.eventHelper.lockedValueChangeEvent = false; 183 | } 184 | <#? it.widgetName !== "dxRangeSelector" #> 185 | setDisabledState(isDisabled: boolean): void { 186 | this.disabled = isDisabled; 187 | } 188 | <#?#> 189 | registerOnChange(fn: (_: any) => void): void { this.change = fn; } 190 | registerOnTouched(fn: () => void): void { this.touched = fn; } 191 | 192 | _createWidget(element: any) { 193 | super._createWidget(element); 194 | this.instance.on('focusOut', (e) => { 195 | this.eventHelper.fireNgEvent('onBlur', [e]); 196 | }); 197 | } 198 | <#?#> 199 | ngOnDestroy() { 200 | this._destroyWidget(); 201 | } 202 | <#? collectionProperties.length #> 203 | ngOnChanges(changes: SimpleChanges) { 204 | super.ngOnChanges(changes);<#~ collectionProperties :prop:i #> 205 | this.setupChanges('<#= prop #>', changes);<#~#> 206 | } 207 | 208 | setupChanges(prop: string, changes: SimpleChanges) { 209 | if (!(prop in this._optionsToUpdate)) { 210 | this._idh.setup(prop, changes); 211 | } 212 | } 213 | 214 | ngDoCheck() {<#~ collectionProperties :prop:i #> 215 | this._idh.doCheck('<#= prop #>');<#~#> 216 | this._watcherHelper.checkWatchers(); 217 | super.ngDoCheck(); 218 | super.clearChangedOptions(); 219 | } 220 | 221 | _setOption(name: string, value: any) { 222 | let isSetup = this._idh.setupSingle(name, value); 223 | let isChanged = this._idh.getChanges(name, value) !== null; 224 | 225 | if (isSetup || isChanged) { 226 | super._setOption(name, value); 227 | } 228 | }<#?#> 229 | } 230 | 231 | @NgModule({ 232 | imports: [<#~ it.nestedComponents :component:i #> 233 | <#= component.className #>Module,<#~#> 234 | DxIntegrationModule, 235 | DxTemplateModule 236 | ], 237 | declarations: [ 238 | <#= it.className #>Component 239 | ], 240 | exports: [ 241 | <#= it.className #>Component<#~ it.nestedComponents :component:i #>, 242 | <#= component.className #>Module<#~#>, 243 | DxTemplateModule 244 | ] 245 | }) 246 | export class <#= it.className #>Module { } 247 | <#? it.renderReexports #> 248 | import type * as <#= it.className #>Types from "<#= it.module #>_types"; 249 | export { <#= it.className #>Types }; 250 | <#?#> 251 | 252 | -------------------------------------------------------------------------------- /packages/devextreme-angular-generator/src/templates/nested-component.tst: -------------------------------------------------------------------------------- 1 | /* tslint:disable:max-line-length */ 2 | 3 | <#? it.inputs #>/* tslint:disable:use-input-property-decorator */ 4 | <#?#> 5 | import { 6 | Component,<#? !it.isCollection #> 7 | OnInit, 8 | OnDestroy,<#?#> 9 | NgModule, 10 | Host,<#? it.hasTemplate #> 11 | ElementRef, 12 | Renderer2, 13 | Inject, 14 | AfterViewInit,<#?#> 15 | SkipSelf<#? it.properties #>, 16 | Input<#?#><#? it.events #>, 17 | Output, 18 | EventEmitter<#?#><#? it.collectionNestedComponents.length #>, 19 | ContentChildren, 20 | forwardRef, 21 | QueryList<#?#> 22 | } from '@angular/core'; 23 | 24 | <#? it.hasTemplate #>import { DOCUMENT } from '@angular/common';<#?#> 25 | 26 | 27 | <#? it.imports #><#~ it.imports :file #>import <#= file.importString #> from '<#= file.path #>'; 28 | <#~#><#?#> 29 | import { 30 | NestedOptionHost,<#? it.hasTemplate #> 31 | extractTemplate,<#?#><#? it.hasTemplate #> 32 | DxTemplateDirective, 33 | IDxTemplateHost, 34 | DxTemplateHost<#?#> 35 | } from 'devextreme-angular/core'; 36 | import { <#= it.baseClass #> } from '<#= it.basePath #>'; 37 | <#~ it.collectionNestedComponents :component:i #><#? component.className !== it.className #>import { <#= component.className #>Component } from './<#= component.path #>'; 38 | <#?#><#~#> 39 | 40 | @Component({ 41 | selector: '<#= it.selector #>', 42 | template: '<#? it.hasTemplate #><#?#>', 43 | styles: ['<#? it.hasTemplate #>:host { display: block; }<#?#>'], 44 | providers: [NestedOptionHost<#? it.hasTemplate #>, DxTemplateHost<#?#>]<#? it.inputs #>, 45 | inputs: [<#~ it.inputs :input:i #> 46 | '<#= input.name #>'<#? i < it.inputs.length-1 #>,<#?#><#~#> 47 | ]<#?#> 48 | }) 49 | export class <#= it.className #>Component extends <#= it.baseClass #><#? it.hasTemplate #> implements AfterViewInit,<#? !it.isCollection #> OnDestroy, OnInit,<#?#> 50 | IDxTemplateHost<#?#><#? !it.isCollection && !it.hasTemplate #> implements OnDestroy, OnInit <#?#> {<#~ it.properties :prop:i #> 51 | @Input() 52 | get <#= prop.name #>(): <#= prop.type #> { 53 | return this._getOption('<#= prop.name #>'); 54 | } 55 | set <#= prop.name #>(value: <#= prop.type #>) { 56 | this._setOption('<#= prop.name #>', value); 57 | } 58 | <#~#> 59 | <#~ it.events :event:i #> 60 | /** 61 | <#? event.isInternal #> 62 | * This member supports the internal infrastructure and is not intended to be used directly from your code. 63 | <#??#> 64 | * [descr:<#= event.docID #>] 65 | <#? event.isDeprecated #> 66 | * @deprecated [depNote:<#= event.docID #>] 67 | <#?#> 68 | <#?#> 69 | */ 70 | @Output() <#= event.emit #>: <#= event.type #>;<#? i < it.events.length-1 #> 71 | <#?#><#~#> 72 | protected get _optionPath() { 73 | return '<#= it.optionName #>'; 74 | } 75 | 76 | <#~ it.collectionNestedComponents :component:i #> 77 | @ContentChildren(forwardRef(() => <#= component.className #>Component)) 78 | get <#= component.propertyName #>Children(): QueryList<<#= component.className #>Component> { 79 | return this._getOption('<#= component.propertyName #>'); 80 | } 81 | set <#= component.propertyName #>Children(value) { 82 | this.setChildren('<#= component.propertyName #>', value); 83 | } 84 | <#~#> 85 | constructor(@SkipSelf() @Host() parentOptionHost: NestedOptionHost, 86 | @Host() optionHost: NestedOptionHost<#? it.hasTemplate #>, 87 | private renderer: Renderer2, 88 | @Inject(DOCUMENT) private document: any, 89 | @Host() templateHost: DxTemplateHost, 90 | private element: ElementRef<#?#>) { 91 | super();<#? it.events #> 92 | 93 | this._createEventEmitters([ 94 | <#~ it.events :event:i #>{ emit: '<#= event.emit #>' }<#? i < it.events.length-1 #>, 95 | <#?#><#~#> 96 | ]); 97 | <#?#> 98 | parentOptionHost.setNestedOption(this); 99 | optionHost.setHost(this, this._fullOptionPath.bind(this));<#? it.hasTemplate #> 100 | templateHost.setHost(this);<#?#><#? it.optionName === 'dataSource' #> 101 | if ((console) && (console.warn)) { 102 | console.warn('The nested \'<#= it.selector #>\' component is deprecated in 17.2. ' + 103 | 'Use the \'<#= it.optionName #>\' option instead. ' + 104 | 'See:\nhttps://github.com/DevExpress/devextreme-angular/blob/master/CHANGELOG.md#17.2.3' 105 | ); 106 | }<#?#> 107 | } 108 | <#? it.hasTemplate #> 109 | setTemplate(template: DxTemplateDirective) { 110 | this.template = template; 111 | } 112 | ngAfterViewInit() { 113 | extractTemplate(this, this.element, this.renderer, this.document); 114 | } 115 | <#?#> 116 | <#? !it.isCollection #> 117 | ngOnInit() { 118 | this._addRecreatedComponent(); 119 | } 120 | 121 | ngOnDestroy() { 122 | this._addRemovedOption(this._getOptionPath()); 123 | } 124 | <#?#> 125 | <#? it.isCollection #> 126 | ngOnDestroy() { 127 | this._deleteRemovedOptions(this._fullOptionPath()); 128 | } 129 | <#?#> 130 | } 131 | 132 | @NgModule({ 133 | declarations: [ 134 | <#= it.className #>Component 135 | ], 136 | exports: [ 137 | <#= it.className #>Component 138 | ], 139 | }) 140 | export class <#= it.className #>Module { } 141 | -------------------------------------------------------------------------------- /packages/devextreme-angular-generator/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json" 3 | } -------------------------------------------------------------------------------- /packages/devextreme-angular-generator/tsconfig.local.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "skipLibCheck": true, 5 | "target": "ES5", 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "sourceMap": true, 11 | "inlineSources": true, 12 | "removeComments": false, 13 | "declaration": true, 14 | "noUnusedParameters": true, 15 | "noUnusedLocals": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/devextreme-angular/.gitignore: -------------------------------------------------------------------------------- 1 | metadata/generated 2 | src/ui/ 3 | src/common/ 4 | src/index.ts 5 | -------------------------------------------------------------------------------- /packages/devextreme-angular/build.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | tools: { 3 | metadataGenerator: { 4 | importFrom: 'devextreme-angular-generator/dist/metadata-generator', 5 | sourceMetadataFilePath: './metadata/NGMetaData.json', 6 | deprecatedMetadataFilePath: './metadata/DeprecatedComponentsMetadata.json', 7 | outputFolderPath: './metadata/generated', 8 | nestedPathPart: 'nested', 9 | basePathPart: 'base', 10 | widgetPackageName: "devextreme", 11 | wrapperPackageName: 'devextreme-angular', 12 | generateReexports: true, 13 | }, 14 | componentGenerator: { 15 | importFrom: 'devextreme-angular-generator/dist/dot-generator', 16 | metadataFolderPath: './metadata/generated/', 17 | outputFolderPath: './src/ui/', 18 | nestedPathPart: 'nested', 19 | basePathPart: 'base' 20 | }, 21 | facadeGenerator: { 22 | importFrom: 'devextreme-angular-generator/dist/facade-generator', 23 | facades: { 24 | './src/index.ts': { 25 | sourceDirectories: [ 26 | './metadata/generated' 27 | ] 28 | } 29 | }, 30 | commonImports: [ 31 | './common', 32 | './common/grids', 33 | './common/charts', 34 | ] 35 | }, 36 | moduleFacadeGenerator: { 37 | importFrom: 'devextreme-angular-generator/dist/module.facade-generator', 38 | moduleFacades: { 39 | './src/ui/all.ts': { 40 | sourceComponentDirectories: [ 41 | './src/ui' 42 | ], 43 | additionalImports: { 44 | 'DxTemplateModule': 'import { DxTemplateModule } from \'devextreme-angular/core\'' 45 | } 46 | } 47 | } 48 | }, 49 | componentNamesGenerator: { 50 | importFrom: 'devextreme-angular-generator/dist/component-names-generator', 51 | componentFilesPath: './src/ui/', 52 | excludedFileNames: [ 53 | 'nested', 54 | 'validation-group', 55 | 'validation-summary', 56 | 'validator', 57 | 'button-group', 58 | 'drop-down-button', 59 | 'file-manager' ], 60 | outputFileName: 'tests/src/server/component-names.ts' 61 | }, 62 | commonReexportsGenerator: { 63 | importFrom: 'devextreme-angular-generator/dist/common-reexports-generator', 64 | metadataPath: './metadata/NGMetaData.json', 65 | outputPath: './src/' 66 | }, 67 | 68 | }, 69 | components: { 70 | srcFilesPattern: '**/*.ts', 71 | tsTestSrc: ['tests/src/**/*.spec.ts', 'tests/src/**/component-names.ts'], 72 | testsPath: 'tests/dist', 73 | sourcesGlobs: ['src/**/*.*', './package.json'], 74 | tsSourcesGlob: 'src/**/*.ts', 75 | outputPath: 'dist' 76 | }, 77 | tests: { 78 | tsConfigPath: "tsconfig.json" 79 | }, 80 | npm: { 81 | distPath: "npm/dist", 82 | content: [ "../../LICENSE", "../../README.md" ] 83 | } 84 | }; 85 | -------------------------------------------------------------------------------- /packages/devextreme-angular/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var path = require('path'); 3 | var typescript = require('gulp-typescript'); 4 | var replace = require('gulp-replace'); 5 | var sourcemaps = require('gulp-sourcemaps'); 6 | var del = require('del'); 7 | var karmaServer = require('karma').Server; 8 | var karmaConfig = require('karma').config; 9 | var buildConfig = require('./build.config'); 10 | var header = require('gulp-header'); 11 | var ngPackagr = require('ng-packagr'); 12 | var exec = require('child_process').exec; 13 | 14 | const argv = require('yargs') 15 | .default('with-descriptions', false) 16 | .argv; 17 | 18 | //------------Components------------ 19 | 20 | gulp.task('clean.metadata', gulp.series(function() { 21 | var outputFolderPath = buildConfig.tools.metadataGenerator.outputFolderPath; 22 | 23 | return del([outputFolderPath]); 24 | })); 25 | 26 | gulp.task('generate.metadata', gulp.series('clean.metadata', function(done) { 27 | var MetadataGenerator = require(buildConfig.tools.metadataGenerator.importFrom).default, 28 | generator = new MetadataGenerator(); 29 | 30 | generator.generate(buildConfig.tools.metadataGenerator); 31 | done(); 32 | })); 33 | 34 | gulp.task('clean.generatedComponents', function(done) { 35 | var { outputFolderPath } = buildConfig.tools.componentGenerator; 36 | del.sync([outputFolderPath + "/**"]); 37 | done(); 38 | }); 39 | 40 | gulp.task('generate.components', gulp.series('generate.metadata', 'clean.generatedComponents', function(done) { 41 | var DoTGenerator = require(buildConfig.tools.componentGenerator.importFrom).default, 42 | generator = new DoTGenerator(); 43 | 44 | generator.generate(buildConfig.tools.componentGenerator); 45 | done(); 46 | })); 47 | 48 | gulp.task('generate.moduleFacades', gulp.series('generate.components', function(done) { 49 | var ModuleFacadeGenerator = require(buildConfig.tools.moduleFacadeGenerator.importFrom).default, 50 | moduleFacadeGenerator = new ModuleFacadeGenerator(); 51 | 52 | moduleFacadeGenerator.generate(buildConfig.tools.moduleFacadeGenerator); 53 | done(); 54 | })); 55 | 56 | gulp.task('generate.facades', gulp.series('generate.moduleFacades', function(done) { 57 | var FacadeGenerator = require(buildConfig.tools.facadeGenerator.importFrom).default, 58 | facadeGenerator = new FacadeGenerator(); 59 | 60 | facadeGenerator.generate(buildConfig.tools.facadeGenerator); 61 | done(); 62 | })); 63 | 64 | gulp.task('generate.common-reexports', function(done) { 65 | var CommonReexportsGenerator = require(buildConfig.tools.commonReexportsGenerator.importFrom).default, 66 | commonReexportsGenerator = new CommonReexportsGenerator(); 67 | 68 | commonReexportsGenerator.generate(buildConfig.tools.commonReexportsGenerator); 69 | done(); 70 | }); 71 | 72 | gulp.task('build.license-headers', function() { 73 | var config = buildConfig.components, 74 | pkg = require('./package.json'), 75 | now = new Date(), 76 | data = { 77 | pkg: pkg, 78 | date: now.toDateString(), 79 | year: now.getFullYear() 80 | }; 81 | 82 | var banner = [ 83 | '/*!', 84 | ' * <%= pkg.name %>', 85 | ' * Version: <%= pkg.version %>', 86 | ' * Build date: <%= date %>', 87 | ' *', 88 | ' * Copyright (c) 2012 - <%= year %> Developer Express Inc. ALL RIGHTS RESERVED', 89 | ' *', 90 | ' * This software may be modified and distributed under the terms', 91 | ' * of the MIT license. See the LICENSE file in the root of the project for details.', 92 | ' *', 93 | ' * https://github.com/DevExpress/devextreme-angular', 94 | ' */', 95 | '\n' // This new line is necessary to keep the header after TS compilation 96 | ].join('\n'); 97 | 98 | return gulp.src(`${config.outputPath}/${config.srcFilesPattern}`) 99 | .pipe(header(banner, data)) 100 | .pipe(gulp.dest(config.outputPath)); 101 | }); 102 | 103 | gulp.task('clean.dist', function() { 104 | del.sync([buildConfig.components.outputPath + "/*.*"]); 105 | return del([buildConfig.components.outputPath]); 106 | }); 107 | 108 | gulp.task('build.ngc', function() { 109 | var config = buildConfig.components; 110 | 111 | return ngPackagr 112 | .ngPackagr() 113 | .forProject(path.join(config.outputPath, 'package.json')) 114 | .withTsConfig('tsconfig.lib.json') 115 | .build() 116 | }); 117 | 118 | gulp.task('build.copy-sources', gulp.series('clean.dist', function() { 119 | var config = buildConfig.components; 120 | return gulp.src(config.sourcesGlobs) 121 | .pipe(gulp.dest(config.outputPath)); 122 | })); 123 | 124 | // Note: workaround for https://github.com/angular/angular-cli/issues/4874 125 | gulp.task('build.remove-unusable-variable', function() { 126 | var config = buildConfig.npm; 127 | 128 | return gulp.src(config.distPath + '/**/*.js') 129 | .pipe(replace(/DevExpress\.[\w\.]+/g, 'Object')) 130 | .pipe(gulp.dest(config.distPath)); 131 | }); 132 | 133 | gulp.task('build.components', gulp.series('generate.facades', 134 | 'generate.common-reexports', 135 | 'build.copy-sources', 136 | 'build.license-headers', 137 | 'build.ngc', 138 | 'build.remove-unusable-variable' 139 | )); 140 | 141 | //------------npm------------ 142 | 143 | gulp.task('npm.content', gulp.series('build.components', function() { 144 | var npmConfig = buildConfig.npm, 145 | cmpConfig = buildConfig.components; 146 | 147 | return gulp.src([cmpConfig.outputPath + '/**/collection.json', ...npmConfig.content]) 148 | .pipe(gulp.dest(npmConfig.distPath)); 149 | })); 150 | 151 | gulp.task('npm.pack', gulp.series( 152 | 'npm.content', 153 | (cb) => { 154 | argv.withDescriptions ? exec('npm run inject-descriptions', (err) => cb(err)) : cb(); 155 | }, 156 | (cb) => { exec('npm pack', { cwd: buildConfig.npm.distPath }, (err) => cb(err)) } 157 | )); 158 | 159 | //------------Main------------ 160 | 161 | var buildTask = gulp.series( 162 | 'build.components' 163 | ); 164 | 165 | gulp.task('build', buildTask); 166 | gulp.task('default', buildTask); 167 | 168 | 169 | //------------Testing------------ 170 | 171 | gulp.task('clean.tests', function() { 172 | var outputFolderPath = buildConfig.components.testsPath; 173 | 174 | return del([outputFolderPath]); 175 | }); 176 | 177 | gulp.task('generate-component-names', function(done) { 178 | var ComponentNamesGenerator = require(buildConfig.tools.componentNamesGenerator.importFrom).default; 179 | var generator = new ComponentNamesGenerator(buildConfig.tools.componentNamesGenerator); 180 | 181 | generator.generate(); 182 | 183 | done(); 184 | }); 185 | 186 | gulp.task('build.tests', gulp.series('clean.tests', 'generate-component-names', function() { 187 | var config = buildConfig.components, 188 | testConfig = buildConfig.tests; 189 | 190 | return gulp.src(config.tsTestSrc) 191 | .pipe(sourcemaps.init()) 192 | .pipe(typescript(testConfig.tsConfigPath)) 193 | .pipe(sourcemaps.write('.')) 194 | .pipe(gulp.dest(config.testsPath)); 195 | })); 196 | 197 | gulp.task('watch.spec', function() { 198 | gulp.watch(buildConfig.components.tsTestSrc, ['build.tests']); 199 | }); 200 | 201 | var getKarmaConfig = function(testsPath) { 202 | const preprocessors = {}; 203 | preprocessors[testsPath] = [ 'webpack' ]; 204 | return karmaConfig.parseConfig(path.resolve('./karma.conf.js'), { 205 | files: [{ pattern: testsPath, watched: false }], 206 | preprocessors: preprocessors 207 | }); 208 | }; 209 | 210 | gulp.task('test.components.client', gulp.series('build.tests', function(done) { 211 | new karmaServer(getKarmaConfig('./karma.test.shim.js'), done).start(); 212 | })); 213 | 214 | gulp.task('test.components.server', gulp.series('build.tests', function(done) { 215 | new karmaServer(getKarmaConfig('./karma.server.test.shim.js'), done).start(); 216 | })); 217 | 218 | gulp.task('test.components.client.debug', function(done) { 219 | var config = getKarmaConfig('./karma.test.shim.js'); 220 | config.browsers = [ 'Chrome' ]; 221 | config.singleRun = false; 222 | 223 | new karmaServer(config, done).start(); 224 | }); 225 | 226 | gulp.task('test.components.server.debug', function(done) { 227 | var config = getKarmaConfig('./karma.server.test.shim.js'); 228 | config.browsers = [ 'Chrome' ]; 229 | config.singleRun = false; 230 | 231 | new karmaServer(config, done).start(); 232 | }); 233 | 234 | gulp.task('run.tests', gulp.series('test.components.server', 'test.components.client')); 235 | 236 | gulp.task('test', gulp.series('build', 'run.tests')); 237 | 238 | gulp.task('watch.test', function(done) { 239 | new karmaServer({ 240 | configFile: __dirname + '/karma.conf.js' 241 | }, done).start(); 242 | }); 243 | -------------------------------------------------------------------------------- /packages/devextreme-angular/karma.common.test.shim.js: -------------------------------------------------------------------------------- 1 | Error.stackTraceLimit = Infinity; 2 | 3 | require("core-js/es6"); 4 | require("reflect-metadata"); 5 | 6 | require("zone.js/dist/zone"); 7 | require("zone.js/dist/long-stack-trace-zone"); 8 | require("zone.js/dist/proxy"); 9 | require("zone.js/dist/sync-test"); 10 | require("zone.js/dist/jasmine-patch"); 11 | require("zone.js/dist/async-test"); 12 | require("zone.js/dist/fake-async-test"); 13 | 14 | __karma__.loaded = function () {}; 15 | -------------------------------------------------------------------------------- /packages/devextreme-angular/karma.conf.js: -------------------------------------------------------------------------------- 1 | /* global process */ 2 | process.env.CHROME_BIN = require('puppeteer').executablePath(); 3 | 4 | var webpackConfig = require('./webpack.test'); 5 | 6 | module.exports = function(config) { 7 | config.set({ 8 | 9 | basePath: './', 10 | 11 | frameworks: ['jasmine'], 12 | 13 | port: 9876, 14 | 15 | logLevel: config.LOG_ERROR, 16 | 17 | colors: true, 18 | 19 | autoWatch: true, 20 | 21 | browsers: ['ChromeHeadless'], 22 | 23 | reporters: [ 24 | 'progress', 25 | 'junit' 26 | ], 27 | 28 | client: { 29 | jasmine: { 30 | random: false, 31 | } 32 | }, 33 | 34 | junitReporter: { 35 | outputFile: 'test-results.xml' 36 | }, 37 | 38 | // Karma plugins loaded 39 | plugins: [ 40 | require('karma-jasmine'), 41 | require('karma-chrome-launcher'), 42 | require('karma-junit-reporter'), 43 | require('karma-webpack') 44 | ], 45 | 46 | webpack: webpackConfig, 47 | 48 | webpackMiddleware: { 49 | stats: 'errors-only' 50 | }, 51 | 52 | singleRun: true, 53 | 54 | concurrency: Infinity 55 | }); 56 | }; 57 | -------------------------------------------------------------------------------- /packages/devextreme-angular/karma.server.test.shim.js: -------------------------------------------------------------------------------- 1 | require("./karma.common.test.shim"); 2 | 3 | const testing = require("@angular/core/testing"); 4 | const server = require("@angular/platform-server/testing"); 5 | 6 | var windowUtils = require("devextreme/core/utils/window"); 7 | 8 | var windowMock = {}; 9 | windowMock.window = windowMock; 10 | windowUtils.setWindow(windowMock); 11 | 12 | testing.TestBed.initTestEnvironment( 13 | server.ServerTestingModule, 14 | server.platformServerTesting() 15 | ); 16 | 17 | const context = require.context('./tests/dist/server', true, /\.spec\.js$/); 18 | context.keys().map(context); 19 | __karma__.start(); 20 | -------------------------------------------------------------------------------- /packages/devextreme-angular/karma.test.shim.js: -------------------------------------------------------------------------------- 1 | require("./karma.common.test.shim"); 2 | 3 | const testing = require("@angular/core/testing"); 4 | const browser = require("@angular/platform-browser-dynamic/testing"); 5 | 6 | testing.TestBed.initTestEnvironment( 7 | browser.BrowserDynamicTestingModule, 8 | browser.platformBrowserDynamicTesting() 9 | ); 10 | 11 | const context = require.context('./tests/dist', true, /^.\/(?!.*\/ssr-components.spec.js$).*\.spec\.js$/); 12 | context.keys().map(context); 13 | __karma__.start(); -------------------------------------------------------------------------------- /packages/devextreme-angular/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "devextreme-angular", 3 | "version": "23.2.0", 4 | "description": "Angular UI and visualization components based on DevExtreme widgets", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/DevExpress/devextreme-angular.git" 8 | }, 9 | "scripts": { 10 | "gulp": "npx --node-options='--max_old_space_size=8192' gulp", 11 | "build": "npm run gulp -- build", 12 | "test": "npm run gulp -- test", 13 | "test:dev": "npm run gulp -- run.tests", 14 | "pack": "npm run gulp -- npm.pack", 15 | "inject-descriptions": "dx-tools inject-descriptions-to-bundle --js-scripts ./npm/dist --artifacts ../../artifacts/internal-tools" 16 | }, 17 | "author": "Developer Express Inc.", 18 | "license": "MIT", 19 | "peerDependencies": { 20 | "@angular/common": ">12.0.0", 21 | "@angular/core": ">12.0.0", 22 | "@angular/forms": ">12.0.0", 23 | "devextreme": "23.2-next" 24 | }, 25 | "devDependencies": { 26 | "@angular/animations": "~12.2.17", 27 | "@angular/common": "~12.2.17", 28 | "@angular/compiler": "~12.2.17", 29 | "@angular/compiler-cli": "~12.2.17", 30 | "@angular/core": "~12.2.17", 31 | "@angular/forms": "~12.2.17", 32 | "@angular/platform-browser": "~12.2.17", 33 | "@angular/platform-browser-dynamic": "~12.2.17", 34 | "@angular/platform-server": "~12.2.17", 35 | "@types/jasmine": "2.8.19", 36 | "@types/node": "~8.10.66", 37 | "codelyzer": "6.0.2", 38 | "core-js": "^2.6.12", 39 | "del": "^2.2.2", 40 | "devextreme": "23.2-next", 41 | "devextreme-angular-generator": "^2.1.1", 42 | "devextreme-internal-tools": "10.0.0-beta.18", 43 | "gulp": "^4.0.2", 44 | "gulp-header": "^1.8.12", 45 | "gulp-replace": "^0.6.1", 46 | "gulp-sourcemaps": "^2.6.5", 47 | "gulp-typescript": "^3.2.4", 48 | "jasmine": "2.99.0", 49 | "karma": "^6.4.2", 50 | "karma-chrome-launcher": "^3.2.0", 51 | "karma-jasmine": "^5.1.0", 52 | "karma-junit-reporter": "^2.0.1", 53 | "karma-webpack": "^5.0.0", 54 | "ng-packagr": "12.2.7", 55 | "puppeteer": "^19.11.1", 56 | "rxjs": "^6.6.7", 57 | "stream-browserify": "^3.0.0", 58 | "typescript": "~4.2.3", 59 | "webpack": "^5.88.2", 60 | "yargs": "^6.6.0", 61 | "zone.js": "^0.13.1" 62 | }, 63 | "main": "./src/index.ts", 64 | "keywords": [ 65 | "angular", 66 | "devextreme", 67 | "devexpress" 68 | ], 69 | "dependencies": { 70 | "@angular-devkit/schematics": "^12.2.18", 71 | "devextreme-schematics": "latest", 72 | "inferno-server": "7.4.11" 73 | }, 74 | "schematics": "./schematics/collection.json", 75 | "ngPackage": { 76 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 77 | "lib": { 78 | "entryFile": "index.ts" 79 | }, 80 | "dest": "../npm/dist", 81 | "whitelistedNonPeerDependencies": [ 82 | "." 83 | ] 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /packages/devextreme-angular/src/core/component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | ElementRef, 4 | NgZone, 5 | QueryList, 6 | SimpleChanges, 7 | PLATFORM_ID, 8 | Inject, 9 | EventEmitter, 10 | 11 | OnChanges, 12 | OnInit, 13 | DoCheck, 14 | AfterContentChecked, 15 | AfterViewInit, 16 | AfterViewChecked 17 | } from '@angular/core'; 18 | 19 | import { isPlatformServer } from '@angular/common'; 20 | import { TransferState, makeStateKey } from '@angular/platform-browser'; 21 | 22 | import { DxTemplateDirective } from './template'; 23 | import { IDxTemplateHost, DxTemplateHost } from './template-host'; 24 | import { EmitterHelper, NgEventsStrategy } from './events-strategy'; 25 | import { WatcherHelper } from './watcher-helper'; 26 | import domAdapter from 'devextreme/core/dom_adapter'; 27 | import { triggerHandler } from 'devextreme/events'; 28 | 29 | import { 30 | INestedOptionContainer, 31 | ICollectionNestedOption, 32 | ICollectionNestedOptionContainer, 33 | CollectionNestedOptionContainerImpl 34 | } from './nested-option'; 35 | 36 | let serverStateKey; 37 | export const getServerStateKey = () => { 38 | if (!serverStateKey) { 39 | serverStateKey = makeStateKey('DX_isPlatformServer'); 40 | } 41 | 42 | return serverStateKey; 43 | }; 44 | 45 | @Component({ 46 | template: '' 47 | }) 48 | export abstract class DxComponent implements OnChanges, OnInit, DoCheck, AfterContentChecked, AfterViewInit, AfterViewChecked, 49 | INestedOptionContainer, ICollectionNestedOptionContainer, IDxTemplateHost { 50 | private _initialOptions: any = {}; 51 | protected _optionsToUpdate: any = {}; 52 | private _collectionContainerImpl: ICollectionNestedOptionContainer; 53 | eventHelper: EmitterHelper; 54 | optionChangedHandlers: EventEmitter = new EventEmitter(); 55 | templates: DxTemplateDirective[]; 56 | instance: any; 57 | isLinked = true; 58 | changedOptions = {}; 59 | removedNestedComponents = []; 60 | recreatedNestedComponents: any[]; 61 | widgetUpdateLocked = false; 62 | templateUpdateRequired = false; 63 | 64 | private _updateTemplates() { 65 | if (this.templates.length && this.templateUpdateRequired) { 66 | let updatedTemplates = {}; 67 | this.templates.forEach(template => { 68 | updatedTemplates[template.name] = template; 69 | }); 70 | this.instance.option('integrationOptions.templates', updatedTemplates); 71 | this.templates = Object.values(updatedTemplates); 72 | this.templateUpdateRequired = false; 73 | } 74 | } 75 | 76 | private _initEvents() { 77 | this.instance.on('optionChanged', (e) => { 78 | this.changedOptions[e.name] = e.value; 79 | 80 | const value = e.name === e.fullName ? e.value : e.component.option(e.name); 81 | this.eventHelper.fireNgEvent(e.name + 'Change', [value]); 82 | this.optionChangedHandlers.emit(e); 83 | }); 84 | } 85 | 86 | private _initOptions() { 87 | this._initialOptions.integrationOptions.watchMethod = this.watcherHelper.getWatchMethod(); 88 | } 89 | 90 | private _initPlatform() { 91 | if (this.transferState.hasKey(getServerStateKey())) { 92 | this._initialOptions.integrationOptions.renderedOnServer = this.transferState.get(getServerStateKey(), null); 93 | } else if (isPlatformServer(this.platformId)) { 94 | this.transferState.set(getServerStateKey(), true); 95 | } 96 | } 97 | 98 | protected _createEventEmitters(events) { 99 | const zone = this.ngZone; 100 | this.eventHelper.createEmitters(events); 101 | 102 | this._initialOptions.eventsStrategy = (instance) => { 103 | let strategy = new NgEventsStrategy(instance, zone); 104 | 105 | events.filter(event => event.subscribe).forEach(event => { 106 | strategy.addEmitter(event.subscribe, this[event.emit]); 107 | }); 108 | 109 | return strategy; 110 | }; 111 | 112 | this._initialOptions.nestedComponentOptions = function (component) { 113 | return { 114 | eventsStrategy: (instance) => { return new NgEventsStrategy(instance, zone); }, 115 | nestedComponentOptions: component.option('nestedComponentOptions') 116 | }; 117 | }; 118 | } 119 | 120 | _shouldOptionChange(name: string, value: any) { 121 | if (this.changedOptions.hasOwnProperty(name)) { 122 | const prevValue = this.changedOptions[name]; 123 | delete this.changedOptions[name]; 124 | 125 | return value !== prevValue; 126 | } 127 | return true; 128 | } 129 | 130 | clearChangedOptions() { 131 | this.changedOptions = {}; 132 | } 133 | 134 | protected _getOption(name: string) { 135 | return this.instance ? 136 | this.instance.option(name) : 137 | this._initialOptions[name]; 138 | } 139 | 140 | lockWidgetUpdate() { 141 | if (!this.widgetUpdateLocked && this.instance) { 142 | this.instance.beginUpdate(); 143 | this.widgetUpdateLocked = true; 144 | } 145 | } 146 | 147 | unlockWidgetUpdate() { 148 | if (this.widgetUpdateLocked) { 149 | this.widgetUpdateLocked = false; 150 | this.instance.endUpdate(); 151 | } 152 | } 153 | 154 | protected _setOption(name: string, value: any) { 155 | this.lockWidgetUpdate(); 156 | 157 | if (!this._shouldOptionChange(name, value)) { 158 | return; 159 | } 160 | 161 | if (this.instance) { 162 | this.instance.option(name, value); 163 | } else { 164 | this._initialOptions[name] = value; 165 | } 166 | } 167 | 168 | protected abstract _createInstance(element, options) 169 | 170 | protected _createWidget(element: any) { 171 | this._initialOptions.integrationOptions = {}; 172 | this._initPlatform(); 173 | this._initOptions(); 174 | 175 | this._initialOptions.onInitializing = function () { 176 | this.beginUpdate(); 177 | }; 178 | this.instance = this._createInstance(element, this._initialOptions); 179 | this._initEvents(); 180 | this._initialOptions = {}; 181 | } 182 | 183 | protected _destroyWidget() { 184 | this.removedNestedComponents = []; 185 | if (this.instance) { 186 | let element = this.instance.element(); 187 | triggerHandler(element, 'dxremove', { _angularIntegration: true }); 188 | this.instance.dispose(); 189 | domAdapter.removeElement(element); 190 | } 191 | } 192 | 193 | constructor(protected element: ElementRef, 194 | private ngZone: NgZone, 195 | templateHost: DxTemplateHost, 196 | private watcherHelper: WatcherHelper, 197 | private transferState: TransferState, 198 | @Inject(PLATFORM_ID) private platformId: any) { 199 | this.templates = []; 200 | templateHost.setHost(this); 201 | this._collectionContainerImpl = new CollectionNestedOptionContainerImpl(this._setOption.bind(this)); 202 | this.eventHelper = new EmitterHelper(ngZone, this); 203 | } 204 | 205 | ngOnChanges(changes: SimpleChanges) { 206 | for (let key in changes) { 207 | let change = changes[key]; 208 | if (change.currentValue !== this[key]) { 209 | this._optionsToUpdate[key] = changes[key].currentValue; 210 | } 211 | } 212 | } 213 | 214 | ngOnInit() { 215 | this._createWidget(this.element.nativeElement); 216 | } 217 | 218 | ngDoCheck() { 219 | this.applyOptions(); 220 | } 221 | 222 | ngAfterContentChecked() { 223 | this.applyOptions(); 224 | this.resetOptions(); 225 | this.unlockWidgetUpdate(); 226 | } 227 | 228 | ngAfterViewInit() { 229 | this._updateTemplates(); 230 | this.instance.endUpdate(); 231 | this.recreatedNestedComponents = []; 232 | } 233 | 234 | ngAfterViewChecked(): void { 235 | this._updateTemplates(); 236 | } 237 | 238 | applyOptions() { 239 | if (Object.keys(this._optionsToUpdate).length) { 240 | if (this.instance) { 241 | this.instance.option(this._optionsToUpdate); 242 | } 243 | this._optionsToUpdate = {}; 244 | } 245 | } 246 | 247 | resetOptions(collectionName?: string) { 248 | if (this.instance) { 249 | this.removedNestedComponents.filter(option => option && 250 | !this.isRecreated(option) && 251 | collectionName ? option.startsWith(collectionName) : true) 252 | .forEach(option => { 253 | this.instance.resetOption(option); 254 | }); 255 | 256 | this.removedNestedComponents = []; 257 | this.recreatedNestedComponents = []; 258 | } 259 | } 260 | 261 | isRecreated(name: string): boolean { 262 | return this.recreatedNestedComponents && 263 | this.recreatedNestedComponents.some(nestedComponent => nestedComponent.getOptionPath() === name); 264 | } 265 | 266 | setTemplate(template: DxTemplateDirective) { 267 | this.templates.push(template); 268 | this.templateUpdateRequired = true; 269 | } 270 | 271 | setChildren(propertyName: string, items: QueryList) { 272 | this.resetOptions(propertyName); 273 | return this._collectionContainerImpl.setChildren(propertyName, items); 274 | } 275 | } 276 | 277 | @Component({ 278 | template: '' 279 | }) 280 | export abstract class DxComponentExtension extends DxComponent implements OnInit, AfterViewInit { 281 | createInstance(element: any) { 282 | this._createWidget(element); 283 | } 284 | 285 | ngOnInit() { 286 | } 287 | 288 | ngAfterViewInit() { 289 | this._createWidget(this.element.nativeElement); 290 | this.instance.endUpdate(); 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /packages/devextreme-angular/src/core/events-strategy.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter, NgZone } from '@angular/core'; 2 | import { DxComponent } from './component'; 3 | 4 | interface IEventSubscription { 5 | handler: any; 6 | unsubscribe: () => void; 7 | } 8 | export class NgEventsStrategy { 9 | private subscriptions: { [key: string]: IEventSubscription[] } = {}; 10 | private events: { [key: string]: EventEmitter } = {}; 11 | 12 | constructor(private instance: any, private zone: NgZone) { } 13 | 14 | hasEvent(name: string) { 15 | return this.getEmitter(name).observers.length !== 0; 16 | } 17 | 18 | fireEvent(name, args) { 19 | let emitter = this.getEmitter(name); 20 | if (emitter.observers.length) { 21 | const internalSubs = this.subscriptions[name] || []; 22 | if (internalSubs.length === emitter.observers.length) { 23 | emitter.next(args && args[0]); 24 | } else { 25 | this.zone.run(() => emitter.next(args && args[0])); 26 | } 27 | } 28 | } 29 | 30 | on(name: string | Object, handler?: Function) { 31 | if (typeof name === 'string') { 32 | let eventSubscriptions = this.subscriptions[name] || [], 33 | subcription = this.getEmitter(name).subscribe(handler.bind(this.instance)), 34 | unsubscribe = subcription.unsubscribe.bind(subcription); 35 | 36 | eventSubscriptions.push({ handler, unsubscribe }); 37 | this.subscriptions[name] = eventSubscriptions; 38 | } else { 39 | let handlersObj = name; 40 | 41 | Object.keys(handlersObj).forEach(event => this.on(event, handlersObj[event])); 42 | } 43 | } 44 | 45 | off(name, handler) { 46 | let eventSubscriptions = this.subscriptions[name] || []; 47 | 48 | if (handler) { 49 | eventSubscriptions.some((subscription, i) => { 50 | if (subscription.handler === handler) { 51 | subscription.unsubscribe(); 52 | eventSubscriptions.splice(i, 1); 53 | return true; 54 | } 55 | }); 56 | } else { 57 | eventSubscriptions.forEach(subscription => { 58 | subscription.unsubscribe(); 59 | }); 60 | eventSubscriptions.splice(0, eventSubscriptions.length); 61 | } 62 | } 63 | 64 | dispose() {} 65 | 66 | public addEmitter(eventName: string, emitter: EventEmitter) { 67 | this.events[eventName] = emitter; 68 | } 69 | 70 | private getEmitter(eventName: string): EventEmitter { 71 | if (!this.events[eventName]) { 72 | this.events[eventName] = new EventEmitter(); 73 | } 74 | return this.events[eventName]; 75 | } 76 | } 77 | 78 | export class EmitterHelper { 79 | lockedValueChangeEvent = false; 80 | 81 | constructor(private zone: NgZone, private component: DxComponent) { } 82 | 83 | fireNgEvent(eventName: string, eventArgs: any) { 84 | if (this.lockedValueChangeEvent && eventName === 'valueChange') { 85 | return; 86 | } 87 | let emitter = this.component[eventName]; 88 | if (emitter && emitter.observers.length) { 89 | this.zone.run(() => { 90 | emitter.next(eventArgs && eventArgs[0]); 91 | }); 92 | } 93 | } 94 | 95 | createEmitters(events: any[]) { 96 | events.forEach(event => { 97 | this.component[event.emit] = new EventEmitter(); 98 | }); 99 | } 100 | } 101 | 102 | -------------------------------------------------------------------------------- /packages/devextreme-angular/src/core/index.ts: -------------------------------------------------------------------------------- 1 | export * from './component'; 2 | export * from './events-strategy'; 3 | export * from './integration'; 4 | export * from './iterable-differ-helper'; 5 | export * from './nested-option'; 6 | export * from './template-host'; 7 | export * from './template'; 8 | export * from './transfer-state'; 9 | export * from './utils'; 10 | export * from './watcher-helper'; 11 | -------------------------------------------------------------------------------- /packages/devextreme-angular/src/core/integration.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:max-line-length */ 2 | import { NgModule, Inject, NgZone, Optional, VERSION } from '@angular/core'; 3 | import { DOCUMENT, XhrFactory } from '@angular/common'; 4 | import httpRequest from 'devextreme/core/http_request'; 5 | 6 | import domAdapter from 'devextreme/core/dom_adapter'; 7 | import readyCallbacks from 'devextreme/core/utils/ready_callbacks'; 8 | import eventsEngine from 'devextreme/events/core/events_engine'; 9 | 10 | const outsideZoneEvents = ['mousemove', 'mouseover', 'mouseout']; 11 | const insideZoneEvents = ['mouseup', 'click', 'mousedown', 'transitionend', 'wheel']; 12 | 13 | let originalAdd; 14 | let callbacks = []; 15 | let readyCallbackAdd = function(callback) { 16 | if (!originalAdd) { 17 | originalAdd = this.callBase.bind(this); 18 | } 19 | callbacks.push(callback); 20 | }; 21 | 22 | readyCallbacks.inject({ 23 | add: function(callback) { 24 | return readyCallbackAdd.call(this, callback); 25 | } 26 | }); 27 | 28 | let doInjections = (document: any, ngZone: NgZone, xhrFactory: XhrFactory) => { 29 | if (Number(VERSION.major) < 12) { 30 | console.warn(`Your version of Angular is not supported. Please update your project to version 12 or later. Please refer to the Angular Update Guide for more information: https://update.angular.io`); 31 | } 32 | 33 | domAdapter.inject({ 34 | _document: document, 35 | 36 | listen: function(...args) { 37 | const eventName = args[1]; 38 | if (outsideZoneEvents.indexOf(eventName) !== -1) { 39 | return ngZone.runOutsideAngular(() => { 40 | return this.callBase.apply(this, args); 41 | }); 42 | } 43 | 44 | if (ngZone.isStable && insideZoneEvents.indexOf(eventName) !== -1) { 45 | return ngZone.run(() => { 46 | return this.callBase.apply(this, args); 47 | }); 48 | } 49 | 50 | return this.callBase.apply(this, args); 51 | }, 52 | 53 | isElementNode: function(element) { 54 | return element && element.nodeType === 1; 55 | }, 56 | 57 | isTextNode: function(element) { 58 | return element && element.nodeType === 3; 59 | }, 60 | 61 | isDocument: function(element) { 62 | return element && element.nodeType === 9; 63 | } 64 | }); 65 | 66 | httpRequest.inject({ 67 | getXhr: function() { 68 | if (!xhrFactory) { 69 | return this.callBase.apply(this); 70 | } 71 | let _xhr = xhrFactory.build(); 72 | if (!('withCredentials' in _xhr)) { 73 | (_xhr as any)['withCredentials'] = false; 74 | } 75 | 76 | return _xhr; 77 | } 78 | }); 79 | 80 | const runReadyCallbacksInZone = () => { 81 | ngZone.run(() => { 82 | eventsEngine.set({}); 83 | callbacks.forEach(callback => originalAdd.call(null, callback)); 84 | callbacks = []; 85 | readyCallbacks.fire(); 86 | }); 87 | }; 88 | 89 | runReadyCallbacksInZone(); 90 | 91 | readyCallbackAdd = (callback) => ngZone.run(() => callback()); 92 | doInjections = runReadyCallbacksInZone; 93 | }; 94 | 95 | @NgModule({}) 96 | export class DxIntegrationModule { 97 | constructor(@Inject(DOCUMENT) document: any, ngZone: NgZone, @Optional() xhrFactory: XhrFactory) { 98 | doInjections(document, ngZone, xhrFactory); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /packages/devextreme-angular/src/core/iterable-differ-helper.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | SimpleChanges, 4 | IterableDiffers 5 | } from '@angular/core'; 6 | 7 | import { 8 | DxComponent 9 | } from './component'; 10 | 11 | @Injectable() 12 | export class IterableDifferHelper { 13 | 14 | private _host: DxComponent; 15 | private _propertyDiffers: { [id: string]: any; } = {}; 16 | 17 | constructor(private _differs: IterableDiffers) { } 18 | 19 | setHost(host: DxComponent) { 20 | this._host = host; 21 | } 22 | 23 | setup(prop: string, changes: SimpleChanges) { 24 | if (prop in changes) { 25 | const value = changes[prop].currentValue; 26 | this.setupSingle(prop, value); 27 | } 28 | } 29 | 30 | setupSingle(prop: string, value: any) { 31 | if (value && Array.isArray(value)) { 32 | if (!this._propertyDiffers[prop]) { 33 | try { 34 | this._propertyDiffers[prop] = this._differs.find(value).create(null); 35 | return true; 36 | } catch (e) { } 37 | } 38 | } else { 39 | delete this._propertyDiffers[prop]; 40 | } 41 | 42 | return false; 43 | } 44 | 45 | getChanges(prop: string, value: any) { 46 | if (this._propertyDiffers[prop]) { 47 | return this._propertyDiffers[prop].diff(value); 48 | } 49 | } 50 | 51 | checkChangedOptions(propName: string, hostValue: any) { 52 | return this._host.changedOptions[propName] === hostValue; 53 | }; 54 | 55 | doCheck(prop: string) { 56 | if (this._propertyDiffers[prop]) { 57 | let hostValue = this._host[prop], 58 | isChangedOption = this.checkChangedOptions(prop, hostValue); 59 | 60 | const changes = this.getChanges(prop, hostValue); 61 | if (changes && this._host.instance && !isChangedOption) { 62 | this._host.lockWidgetUpdate(); 63 | this._host.instance.option(prop, hostValue); 64 | } 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /packages/devextreme-angular/src/core/nested-option.ts: -------------------------------------------------------------------------------- 1 | import { Component, QueryList, ElementRef, Renderer2, EventEmitter } from '@angular/core'; 2 | 3 | import { DX_TEMPLATE_WRAPPER_CLASS } from './template'; 4 | import { getElement } from './utils'; 5 | 6 | import render from 'devextreme/core/renderer'; 7 | import { triggerHandler } from 'devextreme/events'; 8 | import domAdapter from 'devextreme/core/dom_adapter'; 9 | 10 | const VISIBILITY_CHANGE_SELECTOR = 'dx-visibility-change-handler'; 11 | 12 | export interface INestedOptionContainer { 13 | instance: any; 14 | isLinked: boolean; 15 | removedNestedComponents: string[]; 16 | optionChangedHandlers: EventEmitter; 17 | recreatedNestedComponents: any[]; 18 | resetOptions: (collectionName?: string) => void; 19 | isRecreated: (name: string) => boolean; 20 | } 21 | 22 | export interface IOptionPathGetter { (): string; } 23 | 24 | @Component({ 25 | template: '' 26 | }) 27 | export abstract class BaseNestedOption implements INestedOptionContainer, ICollectionNestedOptionContainer { 28 | protected _host: INestedOptionContainer; 29 | protected _hostOptionPath: IOptionPathGetter; 30 | private _collectionContainerImpl: ICollectionNestedOptionContainer; 31 | protected _initialOptions = {}; 32 | 33 | protected abstract get _optionPath(): string; 34 | protected abstract _fullOptionPath(): string; 35 | 36 | constructor() { 37 | this._collectionContainerImpl = new CollectionNestedOptionContainerImpl(this._setOption.bind(this), this._filterItems.bind(this)); 38 | } 39 | 40 | protected _optionChangedHandler(e: any) { 41 | let fullOptionPath = this._fullOptionPath(); 42 | 43 | if (e.fullName.indexOf(fullOptionPath) === 0) { 44 | let optionName = e.fullName.slice(fullOptionPath.length); 45 | let emitter = this[optionName + 'Change']; 46 | 47 | if (emitter) { 48 | emitter.next(e.value); 49 | } 50 | } 51 | } 52 | 53 | protected _createEventEmitters(events) { 54 | events.forEach(event => { 55 | this[event.emit] = new EventEmitter(); 56 | }); 57 | } 58 | 59 | protected _getOption(name: string): any { 60 | if (this.isLinked) { 61 | return this.instance.option(this._fullOptionPath() + name); 62 | } else { 63 | return this._initialOptions[name]; 64 | } 65 | } 66 | 67 | protected _setOption(name: string, value: any) { 68 | if (this.isLinked) { 69 | const fullPath = this._fullOptionPath() + name; 70 | this.instance.option(fullPath, value); 71 | } else { 72 | this._initialOptions[name] = value; 73 | } 74 | } 75 | 76 | protected _addRemovedOption(name: string) { 77 | if (this.instance && this.removedNestedComponents) { 78 | this.removedNestedComponents.push(name); 79 | } 80 | } 81 | 82 | protected _deleteRemovedOptions(name: string) { 83 | if (this.instance && this.removedNestedComponents) { 84 | this.removedNestedComponents = this.removedNestedComponents.filter((x) => !x.startsWith(name)); 85 | } 86 | } 87 | 88 | protected _addRecreatedComponent() { 89 | if (this.instance && this.recreatedNestedComponents) { 90 | this.recreatedNestedComponents.push({ getOptionPath: () => this._getOptionPath() }); 91 | } 92 | } 93 | 94 | protected _getOptionPath() { 95 | return this._hostOptionPath() + this._optionPath; 96 | } 97 | 98 | setHost(host: INestedOptionContainer, optionPath: IOptionPathGetter) { 99 | this._host = host; 100 | this._hostOptionPath = optionPath; 101 | this.optionChangedHandlers.subscribe(this._optionChangedHandler.bind(this)); 102 | } 103 | 104 | setChildren(propertyName: string, items: QueryList) { 105 | this.resetOptions(propertyName); 106 | return this._collectionContainerImpl.setChildren(propertyName, items); 107 | } 108 | 109 | _filterItems(items: QueryList) { 110 | return items.filter((item) => { return item !== this; }); 111 | } 112 | 113 | get instance() { 114 | return this._host && this._host.instance; 115 | } 116 | 117 | get resetOptions() { 118 | return this._host && this._host.resetOptions; 119 | } 120 | 121 | get isRecreated() { 122 | return this._host && this._host.isRecreated; 123 | } 124 | 125 | get removedNestedComponents() { 126 | return this._host && this._host.removedNestedComponents; 127 | } 128 | 129 | set removedNestedComponents(value) { 130 | this._host.removedNestedComponents = value; 131 | } 132 | 133 | get recreatedNestedComponents() { 134 | return this._host && this._host.recreatedNestedComponents; 135 | } 136 | 137 | set recreatedNestedComponents(value) { 138 | this._host.recreatedNestedComponents = value; 139 | } 140 | 141 | get isLinked() { 142 | return !!this.instance && this._host.isLinked; 143 | } 144 | 145 | get optionChangedHandlers() { 146 | return this._host && this._host.optionChangedHandlers; 147 | } 148 | } 149 | 150 | export interface ICollectionNestedOptionContainer { 151 | setChildren(propertyName: string, items: QueryList); 152 | } 153 | 154 | export class CollectionNestedOptionContainerImpl implements ICollectionNestedOptionContainer { 155 | private _activatedQueries = {}; 156 | 157 | constructor(private _setOption: Function, private _filterItems?: Function) { } 158 | 159 | setChildren(propertyName: string, items: QueryList) { 160 | if (this._filterItems) { 161 | items = this._filterItems(items); 162 | } 163 | if (items.length) { 164 | this._activatedQueries[propertyName] = true; 165 | } 166 | if (this._activatedQueries[propertyName]) { 167 | let widgetItems = items.map((item, index) => { 168 | item._index = index; 169 | return item._value; 170 | }); 171 | this._setOption(propertyName, widgetItems); 172 | } 173 | } 174 | } 175 | 176 | @Component({ 177 | template: '' 178 | }) 179 | export abstract class NestedOption extends BaseNestedOption { 180 | setHost(host: INestedOptionContainer, optionPath: IOptionPathGetter) { 181 | super.setHost(host, optionPath); 182 | 183 | this._host[this._optionPath] = this._initialOptions; 184 | } 185 | 186 | protected _fullOptionPath() { 187 | return this._getOptionPath() + '.'; 188 | } 189 | } 190 | 191 | export interface ICollectionNestedOption { 192 | _index: number; 193 | _value: Object; 194 | } 195 | 196 | @Component({ 197 | template: '' 198 | }) 199 | export abstract class CollectionNestedOption extends BaseNestedOption implements ICollectionNestedOption { 200 | _index: number; 201 | 202 | protected _fullOptionPath() { 203 | return `${this._getOptionPath()}[${this._index}].`; 204 | } 205 | 206 | get _value() { 207 | return this._initialOptions; 208 | } 209 | 210 | get isLinked() { 211 | return this._index !== undefined && !!this.instance && this._host.isLinked; 212 | } 213 | } 214 | 215 | export interface IOptionWithTemplate extends BaseNestedOption { 216 | template: any; 217 | } 218 | 219 | let triggerShownEvent = function(element) { 220 | let changeHandlers = []; 221 | 222 | if (!render(element).hasClass(VISIBILITY_CHANGE_SELECTOR)) { 223 | changeHandlers.push(element); 224 | } 225 | 226 | changeHandlers.push.apply(changeHandlers, element.querySelectorAll('.' + VISIBILITY_CHANGE_SELECTOR)); 227 | 228 | for (let i = 0; i < changeHandlers.length; i++) { 229 | triggerHandler(changeHandlers[i], 'dxshown'); 230 | } 231 | }; 232 | 233 | export function extractTemplate(option: IOptionWithTemplate, element: ElementRef, renderer: Renderer2, document: any) { 234 | if (!option.template === undefined || !element.nativeElement.hasChildNodes()) { 235 | return; 236 | } 237 | 238 | let childNodes = [].slice.call(element.nativeElement.childNodes); 239 | let userContent = childNodes.filter((n) => { 240 | if (n.tagName) { 241 | let tagNamePrefix = n.tagName.toLowerCase().substr(0, 3); 242 | return !(tagNamePrefix === 'dxi' || tagNamePrefix === 'dxo'); 243 | } else { 244 | return n.nodeName !== '#comment' && n.textContent.replace(/\s/g, '').length; 245 | } 246 | }); 247 | if (!userContent.length) { 248 | return; 249 | } 250 | 251 | option.template = { 252 | render: (renderData) => { 253 | let result = element.nativeElement; 254 | 255 | domAdapter.setClass(result, DX_TEMPLATE_WRAPPER_CLASS, true); 256 | 257 | if (renderData.container) { 258 | let container = getElement(renderData.container); 259 | let resultInContainer = container.contains(element.nativeElement); 260 | 261 | renderer.appendChild(container, element.nativeElement); 262 | 263 | if (!resultInContainer) { 264 | let resultInBody = document.body.contains(container); 265 | 266 | if (resultInBody) { 267 | triggerShownEvent(result); 268 | } 269 | } 270 | } 271 | 272 | return result; 273 | } 274 | }; 275 | } 276 | 277 | export class NestedOptionHost { 278 | private _host: INestedOptionContainer; 279 | private _optionPath: IOptionPathGetter; 280 | 281 | getHost(): INestedOptionContainer { 282 | return this._host; 283 | } 284 | 285 | setHost(host: INestedOptionContainer, optionPath?: IOptionPathGetter) { 286 | this._host = host; 287 | this._optionPath = optionPath || (() => ''); 288 | } 289 | 290 | setNestedOption(nestedOption: BaseNestedOption) { 291 | nestedOption.setHost(this._host, this._optionPath); 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /packages/devextreme-angular/src/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "ngPackage": { 3 | "lib": { 4 | "entryFile": "index.ts" 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/devextreme-angular/src/core/template-host.ts: -------------------------------------------------------------------------------- 1 | import { DxTemplateDirective } from './template'; 2 | 3 | export interface IDxTemplateHost { 4 | setTemplate(template: DxTemplateDirective); 5 | }; 6 | 7 | export class DxTemplateHost { 8 | host: IDxTemplateHost; 9 | 10 | setHost(host: IDxTemplateHost) { 11 | this.host = host; 12 | } 13 | 14 | setTemplate(template: DxTemplateDirective) { 15 | this.host.setTemplate(template); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/devextreme-angular/src/core/template.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:use-input-property-decorator */ 2 | 3 | import { 4 | Directive, 5 | NgModule, 6 | TemplateRef, 7 | ViewContainerRef, 8 | Input, 9 | Renderer2, 10 | NgZone, 11 | EmbeddedViewRef 12 | } from '@angular/core'; 13 | 14 | import { DxTemplateHost } from './template-host'; 15 | import { getElement } from './utils'; 16 | import { one } from 'devextreme/events'; 17 | import domAdapter from 'devextreme/core/dom_adapter'; 18 | 19 | export const DX_TEMPLATE_WRAPPER_CLASS = 'dx-template-wrapper'; 20 | 21 | export class RenderData { 22 | model: any; 23 | index: number; 24 | container: any; 25 | } 26 | 27 | @Directive({ 28 | selector: '[dxTemplate]' 29 | }) 30 | export class DxTemplateDirective { 31 | @Input() 32 | set dxTemplateOf(value) { 33 | this.name = value; 34 | }; 35 | name: string; 36 | 37 | constructor(private templateRef: TemplateRef, 38 | private viewContainerRef: ViewContainerRef, 39 | templateHost: DxTemplateHost, 40 | private renderer: Renderer2, 41 | private zone: NgZone) { 42 | templateHost.setTemplate(this); 43 | } 44 | 45 | private renderTemplate(renderData: RenderData): EmbeddedViewRef { 46 | const childView = this.viewContainerRef.createEmbeddedView(this.templateRef, { 47 | '$implicit': renderData.model, 48 | index: renderData.index 49 | }); 50 | 51 | const container = getElement(renderData.container); 52 | if (renderData.container) { 53 | childView.rootNodes.forEach((element) => { 54 | this.renderer.appendChild(container, element); 55 | }); 56 | } 57 | 58 | return childView; 59 | } 60 | 61 | render(renderData: RenderData) { 62 | let childView; 63 | if (this.zone.isStable) { 64 | childView = this.zone.run(() => { 65 | return this.renderTemplate(renderData); 66 | }); 67 | } else { 68 | childView = this.renderTemplate(renderData); 69 | } 70 | // =========== WORKAROUND ============= 71 | // https://github.com/angular/angular/issues/12243 72 | childView['detectChanges'](); 73 | // =========== /WORKAROUND ============= 74 | 75 | childView.rootNodes.forEach((element) => { 76 | if (element.nodeType === 1) { 77 | domAdapter.setClass(element, DX_TEMPLATE_WRAPPER_CLASS, true); 78 | } 79 | 80 | one(element, 'dxremove', ({}, params) => { 81 | if (!params || !params._angularIntegration) { 82 | childView.destroy(); 83 | } 84 | }); 85 | }); 86 | 87 | return childView.rootNodes; 88 | } 89 | } 90 | 91 | @NgModule({ 92 | declarations: [DxTemplateDirective], 93 | exports: [DxTemplateDirective] 94 | }) 95 | export class DxTemplateModule { } 96 | -------------------------------------------------------------------------------- /packages/devextreme-angular/src/core/transfer-state.ts: -------------------------------------------------------------------------------- 1 | import { PLATFORM_ID, Inject, NgModule } from '@angular/core'; 2 | import { isPlatformServer } from '@angular/common'; 3 | import ajax from 'devextreme/core/utils/ajax'; 4 | import { Deferred } from 'devextreme/core/utils/deferred'; 5 | import { TransferState, makeStateKey } from '@angular/platform-browser'; 6 | 7 | @NgModule({}) 8 | 9 | export class DxServerTransferStateModule { 10 | constructor(private state: TransferState, @Inject(PLATFORM_ID) private platformId: any) { 11 | let that = this; 12 | 13 | ajax.inject({ 14 | sendRequest: function(...args) { 15 | let key = makeStateKey(that.generateKey(args)), 16 | cachedData = that.state.get(key, null as any); 17 | 18 | if (isPlatformServer(that.platformId)) { 19 | let result = this.callBase.apply(this, args); 20 | result.always((data, status) => { 21 | let dataForCache = { 22 | data: data, 23 | status: status 24 | }; 25 | that.state.set(key, dataForCache as any); 26 | }); 27 | return result; 28 | } else { 29 | if (cachedData) { 30 | let d = (Deferred as any)(); 31 | d.resolve(cachedData.data, cachedData.status); 32 | that.state.set(key, null as any); 33 | 34 | return d.promise(); 35 | } 36 | return this.callBase.apply(this, args); 37 | } 38 | } 39 | }); 40 | } 41 | 42 | generateKey(args) { 43 | let keyValue = ''; 44 | for (let key in args) { 45 | if (typeof args[key] === 'object') { 46 | let objKey = this.generateKey(args[key]); 47 | keyValue += key + objKey; 48 | } else { 49 | keyValue += key + args[key]; 50 | } 51 | } 52 | 53 | return keyValue; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/devextreme-angular/src/core/utils.ts: -------------------------------------------------------------------------------- 1 | export function getElement(element: any) { 2 | return element.get ? element.get(0) : element; 3 | }; 4 | -------------------------------------------------------------------------------- /packages/devextreme-angular/src/core/watcher-helper.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable 3 | } from '@angular/core'; 4 | 5 | import { equalByValue } from 'devextreme/core/utils/common'; 6 | 7 | @Injectable() 8 | export class WatcherHelper { 9 | private _watchers: any[] = []; 10 | 11 | getWatchMethod() { 12 | let watchMethod = (valueGetter, valueChangeCallback, options) => { 13 | let oldValue = valueGetter(); 14 | options = options || {}; 15 | 16 | if (!options.skipImmediate) { 17 | valueChangeCallback(oldValue); 18 | } 19 | 20 | let watcher = () => { 21 | let newValue = valueGetter(); 22 | 23 | if (this._isDifferentValues(oldValue, newValue, options.deep)) { 24 | valueChangeCallback(newValue); 25 | oldValue = newValue; 26 | } 27 | }; 28 | 29 | this._watchers.push(watcher); 30 | 31 | return () => { 32 | let index = this._watchers.indexOf(watcher); 33 | 34 | if (index !== -1) { 35 | this._watchers.splice(index, 1); 36 | } 37 | }; 38 | }; 39 | 40 | return watchMethod; 41 | } 42 | 43 | private _isDifferentValues(oldValue: any, newValue: any, deepCheck: boolean) { 44 | let comparableNewValue = this._toComparable(newValue); 45 | let comparableOldValue = this._toComparable(oldValue); 46 | let isObjectValues = comparableNewValue instanceof Object && comparableOldValue instanceof Object; 47 | 48 | if (deepCheck && isObjectValues) { 49 | return this._checkObjectsFields(newValue, oldValue); 50 | } 51 | return comparableNewValue !== comparableOldValue; 52 | } 53 | 54 | private _toComparable(value) { 55 | if (value instanceof Date) { 56 | return value.getTime(); 57 | } 58 | 59 | return value; 60 | } 61 | 62 | private _checkObjectsFields(checkingFromObject: Object, checkingToObject: Object) { 63 | for (let field in checkingFromObject) { 64 | let oldValue = this._toComparable(checkingFromObject[field]); 65 | let newValue = this._toComparable(checkingToObject[field]); 66 | let isEqualObjects = false; 67 | 68 | if (typeof oldValue === 'object' && typeof newValue === 'object') { 69 | isEqualObjects = equalByValue(oldValue, newValue); 70 | } 71 | if (oldValue !== newValue && !isEqualObjects) { 72 | return true; 73 | } 74 | } 75 | } 76 | 77 | checkWatchers() { 78 | for (let watcher of this._watchers) { 79 | watcher(); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /packages/devextreme-angular/src/schematics/collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/@angular-devkit/schematics/collection-schema.json", 3 | "extends": [ 4 | "devextreme-schematics" 5 | ], 6 | "schematics": [] 7 | } 8 | -------------------------------------------------------------------------------- /packages/devextreme-angular/src/server/index.ts: -------------------------------------------------------------------------------- 1 | export * from './render'; 2 | -------------------------------------------------------------------------------- /packages/devextreme-angular/src/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "ngPackage": { 3 | "lib": { 4 | "entryFile": "index.ts" 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/devextreme-angular/src/server/render.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, Inject, PLATFORM_ID } from '@angular/core'; 2 | import { isPlatformServer } from '@angular/common'; 3 | 4 | import infernoRenderer from 'devextreme/core/inferno_renderer'; 5 | import { renderToString } from 'inferno-server'; 6 | 7 | @NgModule({ 8 | exports: [], 9 | imports: [], 10 | providers: [] 11 | }) 12 | export class DxServerModule { 13 | constructor(@Inject(PLATFORM_ID) platformId: any) { 14 | if (isPlatformServer(platformId)) { 15 | infernoRenderer.inject({ 16 | render: ( 17 | component, 18 | props, 19 | container, 20 | ) => { 21 | const el = infernoRenderer.createElement(component, props); 22 | const document = container.ownerDocument; 23 | const temp = document.createElement(container.tagName); 24 | temp.innerHTML = renderToString(el); 25 | const mainElement = temp.childNodes[0]; 26 | const childString = mainElement.innerHTML; 27 | 28 | for (let i = 0; i < mainElement.attributes.length; i++) { 29 | temp.setAttribute(mainElement.attributes[i].name, mainElement.attributes[i].value); 30 | } 31 | temp.innerHTML = childString; 32 | container.outerHTML = temp.outerHTML; 33 | }}); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/devextreme-angular/tests/src/core/angular-integration.spec.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, NgZone } from '@angular/core'; 2 | import { TestBed } from '@angular/core/testing'; 3 | import domAdapter from 'devextreme/core/dom_adapter'; 4 | import { DxIntegrationModule } from 'devextreme-angular/core'; 5 | 6 | @NgModule({ 7 | imports: [ 8 | DxIntegrationModule 9 | ] 10 | }) 11 | class SingletonModule { 12 | constructor(ngZone: NgZone) { 13 | new DxIntegrationModule({}, ngZone, null); 14 | } 15 | } 16 | 17 | describe('Integration module', () => { 18 | it('domAdapter should be injected once', () => { 19 | const spy = spyOn(domAdapter, 'inject').and.callThrough(); 20 | 21 | TestBed.configureTestingModule({ 22 | imports: [ SingletonModule ] 23 | }); 24 | TestBed.get(SingletonModule); 25 | 26 | expect(spy.calls.count()).toBe(1); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/devextreme-angular/tests/src/core/component-extension.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:component-selector */ 2 | 3 | import { 4 | Component, 5 | ElementRef, 6 | ViewChild, 7 | NgZone, 8 | PLATFORM_ID, 9 | Inject 10 | } from '@angular/core'; 11 | 12 | import { TransferState } from '@angular/platform-browser'; 13 | import { BrowserTransferStateModule } from '@angular/platform-browser'; 14 | 15 | import { 16 | TestBed 17 | } from '@angular/core/testing'; 18 | 19 | import { 20 | DxComponentExtension, 21 | DxTemplateHost, 22 | WatcherHelper 23 | } from 'devextreme-angular'; 24 | 25 | import DxButton from 'devextreme/ui/button'; 26 | let DxTestExtension = DxButton; 27 | 28 | DxTestExtension.defaultOptions({ 29 | options: { 30 | elementAttr: { class: 'dx-test-extension' } 31 | } 32 | }); 33 | 34 | @Component({ 35 | selector: 'dx-test-extension', 36 | template: '', 37 | providers: [DxTemplateHost, WatcherHelper] 38 | }) 39 | export class DxTestExtensionComponent extends DxComponentExtension { 40 | constructor(elementRef: ElementRef, 41 | ngZone: NgZone, 42 | templateHost: DxTemplateHost, 43 | _watcherHelper: WatcherHelper, 44 | transferState: TransferState, 45 | @Inject(PLATFORM_ID) platformId: any) { 46 | super(elementRef, ngZone, templateHost, _watcherHelper, transferState, platformId); 47 | } 48 | 49 | protected _createInstance(element, options) { 50 | return new DxTestExtension(element, options); 51 | } 52 | } 53 | 54 | @Component({ 55 | selector: 'test-container-component', 56 | template: '' 57 | }) 58 | export class TestContainerComponent { 59 | @ViewChild(DxTestExtensionComponent) innerWidget: DxTestExtensionComponent; 60 | } 61 | 62 | 63 | describe('DevExtreme Angular component extension', () => { 64 | 65 | beforeEach(() => { 66 | TestBed.configureTestingModule( 67 | { 68 | imports: [BrowserTransferStateModule], 69 | declarations: [TestContainerComponent, DxTestExtensionComponent] 70 | }); 71 | }); 72 | 73 | function getWidget(element) { 74 | return DxTestExtension.getInstance(element); 75 | } 76 | // spec 77 | it('should not create widget instance by itself', () => { 78 | TestBed.overrideComponent(TestContainerComponent, { 79 | set: { 80 | template: '' 81 | } 82 | }); 83 | let fixture = TestBed.createComponent(TestContainerComponent); 84 | fixture.detectChanges(); 85 | 86 | let instance = getWidget(fixture.nativeElement); 87 | expect(instance).toBe(undefined); 88 | }); 89 | 90 | it('should instantiate widget with the createInstance() method', () => { 91 | TestBed.overrideComponent(TestContainerComponent, { 92 | set: { 93 | template: '' 94 | } 95 | }); 96 | let fixture = TestBed.createComponent(TestContainerComponent); 97 | fixture.detectChanges(); 98 | 99 | let outerComponent = fixture.componentInstance, 100 | innerComponent = outerComponent.innerWidget, 101 | targetElement = document.createElement('div'); 102 | 103 | innerComponent.createInstance(targetElement); 104 | let instance = getWidget(targetElement); 105 | expect(instance).not.toBe(undefined); 106 | expect(innerComponent.instance).not.toBe(undefined); 107 | }); 108 | 109 | }); 110 | -------------------------------------------------------------------------------- /packages/devextreme-angular/tests/src/core/events-strategy.spec.ts: -------------------------------------------------------------------------------- 1 | import { NgEventsStrategy } from 'devextreme-angular'; 2 | 3 | describe('Events strategy API', () => { 4 | it('should allow to pass object with event handlers in the "on" method', () => { 5 | const eventsStrategy = new NgEventsStrategy({}, null); 6 | const handlers = [ 7 | jasmine.createSpy('first'), 8 | jasmine.createSpy('second'), 9 | jasmine.createSpy('third') 10 | ]; 11 | 12 | eventsStrategy.on({ 13 | firstEvent: handlers[0], 14 | secondEvent: handlers[1], 15 | thirdEvent: handlers[2] 16 | }); 17 | 18 | eventsStrategy.fireEvent('firstEvent', []); 19 | expect(handlers[0]).toHaveBeenCalled(); 20 | 21 | eventsStrategy.fireEvent('secondEvent', []); 22 | expect(handlers[1]).toHaveBeenCalled(); 23 | 24 | eventsStrategy.fireEvent('thirdEvent', []); 25 | expect(handlers[2]).toHaveBeenCalled(); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /packages/devextreme-angular/tests/src/core/template.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:component-selector */ 2 | 3 | import { 4 | Component, 5 | ElementRef, 6 | EventEmitter, 7 | ViewChild, 8 | NgZone, 9 | Input, 10 | Output, 11 | AfterViewInit, 12 | PLATFORM_ID, 13 | Inject 14 | } from '@angular/core'; 15 | 16 | import { TransferState } from '@angular/platform-browser'; 17 | import { BrowserTransferStateModule } from '@angular/platform-browser'; 18 | 19 | import { 20 | TestBed 21 | } from '@angular/core/testing'; 22 | 23 | import { 24 | DxComponent, 25 | DxTemplateHost, 26 | DxTemplateModule, 27 | DxTemplateDirective, 28 | WatcherHelper 29 | } from 'devextreme-angular'; 30 | 31 | // TODO: Try to replace dxButton to Widget ('require' required) 32 | import DxButton from 'devextreme/ui/button'; 33 | let DxTestWidget = DxButton; 34 | DxTestWidget.defaultOptions({ 35 | options: { 36 | elementAttr: { class: 'dx-test-widget' } 37 | } 38 | }); 39 | 40 | @Component({ 41 | selector: 'dx-test-widget', 42 | template: '', 43 | providers: [DxTemplateHost, WatcherHelper] 44 | }) 45 | export class DxTestWidgetComponent extends DxComponent { 46 | @Input() 47 | get testTemplate(): any { 48 | return this._getOption('testTemplate'); 49 | } 50 | set testTemplate(value: any) { 51 | this._setOption('testTemplate', value); 52 | }; 53 | 54 | @Output() onOptionChanged = new EventEmitter(); 55 | @Output() testTemplateChange = new EventEmitter(); 56 | 57 | constructor(elementRef: ElementRef, 58 | ngZone: NgZone, 59 | templateHost: DxTemplateHost, 60 | _watcherHelper: WatcherHelper, 61 | transferState: TransferState, 62 | @Inject(PLATFORM_ID) platformId: any) { 63 | super(elementRef, ngZone, templateHost, _watcherHelper, transferState, platformId); 64 | 65 | this._createEventEmitters([ 66 | { subscribe: 'optionChanged', emit: 'onOptionChanged' }, 67 | { subscribe: 'initialized', emit: 'onInitialized' } 68 | ]); 69 | } 70 | 71 | protected _createInstance(element, options) { 72 | return new DxTestWidget(element, options); 73 | } 74 | } 75 | 76 | @Component({ 77 | selector: 'dx-test', 78 | template: '', 79 | providers: [DxTemplateHost, WatcherHelper] 80 | }) 81 | export class DxTestComponent extends DxComponent implements AfterViewInit { 82 | templates: DxTemplateDirective[]; 83 | 84 | constructor(elementRef: ElementRef, 85 | ngZone: NgZone, 86 | templateHost: DxTemplateHost, 87 | _watcherHelper: WatcherHelper, 88 | transferState: TransferState, 89 | @Inject(PLATFORM_ID) platformId: any) { 90 | super(elementRef, ngZone, templateHost, _watcherHelper, transferState, platformId); 91 | } 92 | 93 | protected _createInstance(element, options) { 94 | return new DxTestWidget(element, options); 95 | } 96 | 97 | renderTemplate(model) { 98 | const element = this.element.nativeElement; 99 | element.textContent = ''; 100 | this.templates[0].render({ 101 | model: model, 102 | container: element, 103 | index: 5 104 | }); 105 | } 106 | 107 | ngAfterViewInit() { 108 | this.renderTemplate({ 109 | value: () => '' 110 | }); 111 | } 112 | } 113 | 114 | @Component({ 115 | selector: 'test-container-component', 116 | template: '', 117 | providers: [DxTemplateHost] 118 | }) 119 | export class TestContainerComponent { 120 | @ViewChild(DxTestWidgetComponent) widget: DxTestWidgetComponent; 121 | @ViewChild(DxTestComponent) testComponent: DxTestComponent; 122 | 123 | @Output() onInnerElementClicked = new EventEmitter(); 124 | 125 | dynamicTemplateName: string; 126 | 127 | constructor() { 128 | this.dynamicTemplateName = 'start'; 129 | } 130 | 131 | testFunction() { 132 | this.onInnerElementClicked.next(); 133 | } 134 | 135 | switchTemplateName() { 136 | this.dynamicTemplateName = this.dynamicTemplateName === 'start' ? 'end' : 'start'; 137 | } 138 | } 139 | 140 | @Component({ 141 | selector: 'dx-imitation', 142 | template: ` 143 |
144 |
123213
145 |
146 | ` 147 | }) 148 | export class ImitateImportComponent { 149 | constructor() { 150 | } 151 | } 152 | 153 | 154 | describe('DevExtreme Angular widget\'s template', () => { 155 | 156 | beforeEach(() => { 157 | TestBed.configureTestingModule( 158 | { 159 | declarations: [TestContainerComponent, DxTestWidgetComponent, DxTestComponent], 160 | imports: [DxTemplateModule, BrowserTransferStateModule] 161 | }); 162 | }); 163 | 164 | // spec 165 | it('should initialize named templates #17', () => { 166 | TestBed.overrideComponent(TestContainerComponent, { 167 | set: { 168 | template: ` 169 | 170 |
Template content
171 |
172 | `} 173 | }); 174 | let fixture = TestBed.createComponent(TestContainerComponent); 175 | fixture.detectChanges(); 176 | 177 | let instance = fixture.componentInstance.widget.instance, 178 | templatesHash = instance.option('integrationOptions.templates'); 179 | 180 | expect(templatesHash['templateName']).not.toBeUndefined(); 181 | expect(typeof templatesHash['templateName'].render).toBe('function'); 182 | 183 | }); 184 | 185 | it('should be able to load template imported from another component', () => { 186 | TestBed.configureTestingModule( 187 | { 188 | declarations: [TestContainerComponent, DxTestWidgetComponent, DxTestComponent, ImitateImportComponent], 189 | imports: [DxTemplateModule, BrowserTransferStateModule] 190 | }); 191 | TestBed.overrideComponent(TestContainerComponent, { 192 | set: { 193 | template: ` 194 | 195 | 196 | 197 | ` 198 | } 199 | }); 200 | let fixture = TestBed.createComponent(TestContainerComponent); 201 | fixture.detectChanges(); 202 | 203 | let testComponent = fixture.componentInstance, 204 | innerComponent = testComponent.widget, 205 | templatesHash = innerComponent.instance.option('integrationOptions.templates'), 206 | template = innerComponent.testTemplate, 207 | container = document.createElement('div'); 208 | 209 | expect(template).not.toBeUndefined; 210 | 211 | templatesHash[template].render({ container: container }); 212 | fixture.detectChanges(); 213 | 214 | expect(container.children[0].classList.contains('dx-template-wrapper')).toBe(true); 215 | }); 216 | 217 | 218 | it('should add template wrapper class as template has root container', () => { 219 | TestBed.overrideComponent(TestContainerComponent, { 220 | set: { 221 | template: ` 222 | 223 |
Template content: {{d}}
224 |
225 | `} 226 | }); 227 | let fixture = TestBed.createComponent(TestContainerComponent); 228 | fixture.detectChanges(); 229 | 230 | let testComponent = fixture.componentInstance, 231 | innerComponent = testComponent.widget, 232 | templatesHash = innerComponent.instance.option('integrationOptions.templates'), 233 | template = innerComponent.testTemplate, 234 | container = document.createElement('div'); 235 | 236 | expect(template).not.toBeUndefined; 237 | 238 | templatesHash[template].render({ container: container }); 239 | fixture.detectChanges(); 240 | 241 | expect(container.children[0].classList.contains('dx-template-wrapper')).toBe(true); 242 | 243 | }); 244 | 245 | it('should have item index', () => { 246 | TestBed.overrideComponent(TestContainerComponent, { 247 | set: { 248 | template: ` 249 | 250 |
index: {{i}}
251 |
252 | `} 253 | }); 254 | let fixture = TestBed.createComponent(TestContainerComponent); 255 | fixture.detectChanges(); 256 | 257 | let element = fixture.nativeElement.querySelector('div'); 258 | expect(element.textContent).toBe('index: 5'); 259 | }); 260 | 261 | it('should be created within Angular Zone', () => { 262 | TestBed.overrideComponent(TestContainerComponent, { 263 | set: { 264 | template: ` 265 | 266 |
267 |
268 |
269 |
270 | `} 271 | }); 272 | 273 | let fixture = TestBed.createComponent(TestContainerComponent); 274 | fixture.detectChanges(); 275 | 276 | fixture.ngZone.runOutsideAngular(() => { 277 | fixture.componentInstance.testComponent.renderTemplate({ 278 | value: () => { 279 | expect(fixture.ngZone.isStable).toBe(false); 280 | } 281 | }); 282 | }); 283 | 284 | fixture.nativeElement.querySelector('.elem').click(); 285 | }); 286 | 287 | it('should work with dynamic template name', () => { 288 | TestBed.overrideComponent(TestContainerComponent, { 289 | set: { 290 | template: ` 291 | 292 |
293 |
294 | Template content: {{dynamicTemplateName}} 295 |
296 |
297 |
298 | `} 299 | }); 300 | let fixture = TestBed.createComponent(TestContainerComponent); 301 | fixture.detectChanges(); 302 | 303 | let testComponent = fixture.componentInstance, 304 | innerComponent = testComponent.widget, 305 | template = innerComponent.testTemplate, 306 | templatesHash = innerComponent.instance.option('integrationOptions.templates'), 307 | container = document.createElement('div'); 308 | 309 | expect(template).not.toBeUndefined; 310 | 311 | templatesHash[template].render({ container: container }); 312 | fixture.detectChanges(); 313 | 314 | expect(container.querySelector('.start')).not.toBeNull(); 315 | 316 | testComponent.switchTemplateName(); 317 | fixture.detectChanges(); 318 | expect(template).not.toBeUndefined; 319 | 320 | templatesHash[template].render({ container: container }); 321 | fixture.detectChanges(); 322 | 323 | expect(container.querySelector('.end')).not.toBeNull(); 324 | }); 325 | }); 326 | 327 | -------------------------------------------------------------------------------- /packages/devextreme-angular/tests/src/server/component-names.ts: -------------------------------------------------------------------------------- 1 | export const componentNames = [ 2 | 'accordion', 3 | 'action-sheet', 4 | 'autocomplete', 5 | 'bar-gauge', 6 | 'box', 7 | 'bullet', 8 | 'button', 9 | 'calendar', 10 | 'chart', 11 | 'check-box', 12 | 'circular-gauge', 13 | 'color-box', 14 | 'context-menu', 15 | 'data-grid', 16 | 'date-box', 17 | 'defer-rendering', 18 | 'diagram', 19 | 'draggable', 20 | 'drawer', 21 | 'drop-down-box', 22 | 'file-uploader', 23 | 'filter-builder', 24 | 'form', 25 | 'funnel', 26 | 'gallery', 27 | 'gantt', 28 | 'html-editor', 29 | 'linear-gauge', 30 | 'list', 31 | 'load-indicator', 32 | 'load-panel', 33 | 'lookup', 34 | 'map', 35 | 'menu', 36 | 'multi-view', 37 | 'number-box', 38 | 'pie-chart', 39 | 'pivot-grid', 40 | 'pivot-grid-field-chooser', 41 | 'polar-chart', 42 | 'popover', 43 | 'popup', 44 | 'progress-bar', 45 | 'radio-group', 46 | 'range-selector', 47 | 'range-slider', 48 | 'recurrence-editor', 49 | 'resizable', 50 | 'responsive-box', 51 | 'sankey', 52 | 'scheduler', 53 | 'scroll-view', 54 | 'select-box', 55 | 'slider', 56 | 'sortable', 57 | 'sparkline', 58 | 'speed-dial-action', 59 | 'switch', 60 | 'tab-panel', 61 | 'tabs', 62 | 'tag-box', 63 | 'text-area', 64 | 'text-box', 65 | 'tile-view', 66 | 'toast', 67 | 'toolbar', 68 | 'tooltip', 69 | 'tree-list', 70 | 'tree-map', 71 | 'tree-view', 72 | 'vector-map' 73 | ]; 74 | -------------------------------------------------------------------------------- /packages/devextreme-angular/tests/src/server/ssr-ajax.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:component-selector */ 2 | 3 | import { Component, PLATFORM_ID } from '@angular/core'; 4 | 5 | import { isPlatformServer } from '@angular/common'; 6 | 7 | import { DxServerTransferStateModule } from 'devextreme-angular'; 8 | 9 | import { DxServerModule } from 'devextreme-angular/server'; 10 | 11 | import { Deferred } from 'devextreme/core/utils/deferred'; 12 | import ajax from 'devextreme/core/utils/ajax'; 13 | 14 | import { ServerModule, ServerTransferStateModule } from '@angular/platform-server'; 15 | import { BrowserModule, TransferState, makeStateKey, BrowserTransferStateModule } from '@angular/platform-browser'; 16 | 17 | import { 18 | TestBed 19 | } from '@angular/core/testing'; 20 | 21 | let mockSendRequest = { 22 | callBase: function() { 23 | let d = (Deferred as any)(); 24 | d.resolve('test', 'success'); 25 | 26 | return d.promise(); 27 | } 28 | }; 29 | 30 | @Component({ 31 | selector: 'test-container-component', 32 | template: '' 33 | }) 34 | class TestContainerComponent { 35 | } 36 | 37 | describe('Universal', () => { 38 | let sendRequest: any; 39 | let ajaxInject = ajax.inject; 40 | beforeEach(() => { 41 | ajax.inject = function(obj) { 42 | sendRequest = obj['sendRequest']; 43 | 44 | }; 45 | TestBed.configureTestingModule( 46 | { 47 | declarations: [TestContainerComponent], 48 | imports: [ 49 | DxServerModule, 50 | ServerModule, 51 | DxServerTransferStateModule, 52 | ServerTransferStateModule, 53 | BrowserTransferStateModule, 54 | BrowserModule.withServerTransition({appId: 'appid'})] 55 | }); 56 | }); 57 | 58 | afterEach(function() { 59 | ajax.inject = ajaxInject; 60 | }); 61 | // spec 62 | it('should set state and remove data from the state when the request is repeated', () => { 63 | const platformId = TestBed.get(PLATFORM_ID); 64 | if (isPlatformServer(platformId)) { 65 | sendRequest.apply(mockSendRequest, [{url: 'someurl'}]); 66 | const transferState: TransferState = TestBed.get(TransferState); 67 | let key = makeStateKey('0urlsomeurl'); 68 | 69 | expect(transferState.hasKey(key)).toBe(true); 70 | expect(transferState.get(key, null as any)).toEqual(Object({ data: 'test', status: 'success' })); 71 | } 72 | }); 73 | 74 | it('should generate complex key', () => { 75 | const platformId = TestBed.get(PLATFORM_ID); 76 | if (isPlatformServer(platformId)) { 77 | sendRequest.apply(mockSendRequest, [{url: 'someurl', data: { filter: { name: 'test'}, select: ['name']}}]); 78 | let key = makeStateKey('0urlsomeurldatafilternametestselect0name'); 79 | const transferState: TransferState = TestBed.get(TransferState); 80 | 81 | expect(transferState.hasKey(key)).toBe(true); 82 | } 83 | }); 84 | 85 | }); 86 | -------------------------------------------------------------------------------- /packages/devextreme-angular/tests/src/server/ssr-components.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:component-selector */ 2 | 3 | import { 4 | Component 5 | } from '@angular/core'; 6 | import { 7 | TestBed 8 | } from '@angular/core/testing'; 9 | 10 | import { 11 | DevExtremeModule 12 | } from 'devextreme-angular'; 13 | 14 | import { BrowserTransferStateModule } from '@angular/platform-browser'; 15 | 16 | import { DxServerModule } from 'devextreme-angular/server'; 17 | 18 | import { 19 | componentNames 20 | } from './component-names'; 21 | 22 | @Component({ 23 | selector: 'test-container-component', 24 | template: '' 25 | }) 26 | class TestContainerComponent { 27 | } 28 | 29 | describe('Universal', () => { 30 | 31 | beforeEach(() => { 32 | TestBed.configureTestingModule({ 33 | declarations: [TestContainerComponent], 34 | imports: [ 35 | DxServerModule, 36 | DevExtremeModule, 37 | BrowserTransferStateModule 38 | ] 39 | }); 40 | }); 41 | 42 | // spec 43 | it('should render all components', () => { 44 | TestBed.overrideComponent(TestContainerComponent, { 45 | set: { 46 | template: ` 47 | ${componentNames.map((name) => ``).join('')} 48 | ` 49 | } 50 | }); 51 | 52 | let fixture = TestBed.createComponent(TestContainerComponent); 53 | expect(fixture.detectChanges.bind(fixture)).not.toThrow(); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /packages/devextreme-angular/tests/src/server/ssr-is-platform-server.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:component-selector */ 2 | 3 | import { 4 | Component, 5 | PLATFORM_ID 6 | } from '@angular/core'; 7 | 8 | import { isPlatformServer } from '@angular/common'; 9 | 10 | import { TransferState, BrowserTransferStateModule } from '@angular/platform-browser'; 11 | 12 | import { DxServerModule } from 'devextreme-angular/server'; 13 | 14 | import { 15 | TestBed 16 | } from '@angular/core/testing'; 17 | 18 | import { 19 | DxDataGridModule, 20 | getServerStateKey 21 | } from 'devextreme-angular'; 22 | 23 | @Component({ 24 | selector: 'test-container-component', 25 | template: '' 26 | }) 27 | class TestContainerComponent { 28 | renderedOnServer = false; 29 | initializedHandler(e) { 30 | this.renderedOnServer = e.component.option('integrationOptions.renderedOnServer'); 31 | } 32 | } 33 | 34 | describe('Universal', () => { 35 | 36 | beforeEach(() => { 37 | TestBed.configureTestingModule({ 38 | declarations: [TestContainerComponent], 39 | imports: [ 40 | DxServerModule, 41 | DxDataGridModule, 42 | BrowserTransferStateModule 43 | ] 44 | }); 45 | }); 46 | 47 | // spec 48 | it('should set transfer state for rendererdOnServer option of integration', () => { 49 | TestBed.overrideComponent(TestContainerComponent, { 50 | set: { 51 | template: `` 52 | } 53 | }); 54 | let platformID = TestBed.get(PLATFORM_ID); 55 | if (isPlatformServer(platformID)) { 56 | let fixture = TestBed.createComponent(TestContainerComponent); 57 | fixture.detectChanges(); 58 | 59 | const transferState: TransferState = TestBed.get(TransferState); 60 | 61 | expect(transferState.hasKey(getServerStateKey())).toBe(true); 62 | expect(transferState.get(getServerStateKey(), null as any)).toEqual(true); 63 | } 64 | }); 65 | 66 | it('should set rendererdOnServer option of integration', () => { 67 | TestBed.overrideComponent(TestContainerComponent, { 68 | set: { 69 | template: `` 70 | } 71 | }); 72 | 73 | let fixture = TestBed.createComponent(TestContainerComponent); 74 | const transferState: TransferState = TestBed.get(TransferState); 75 | 76 | transferState.set(getServerStateKey(), true as any); 77 | 78 | fixture.detectChanges(); 79 | 80 | expect(fixture.componentInstance.renderedOnServer).toBe(true); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /packages/devextreme-angular/tests/src/ui/chart.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:component-selector */ 2 | 3 | import { 4 | Component, 5 | ViewChild 6 | } from '@angular/core'; 7 | 8 | import { BrowserTransferStateModule } from '@angular/platform-browser'; 9 | 10 | import { 11 | TestBed 12 | } from '@angular/core/testing'; 13 | 14 | import { 15 | DxChartModule, DxChartComponent, DxScrollViewModule 16 | } from 'devextreme-angular'; 17 | import dxChart from 'devextreme/viz/chart'; 18 | 19 | @Component({ 20 | selector: 'test-container-component', 21 | template: '' 22 | }) 23 | class TestContainerComponent { 24 | strips: any[] = [{ 25 | label: 'label1' 26 | }]; 27 | @ViewChild(DxChartComponent) chart: DxChartComponent; 28 | dataSource = []; 29 | disposed = false; 30 | commonSeriesSettings = { 31 | argumentField: undefined 32 | }; 33 | seriesAsArray = []; 34 | seriesAsObject = { 35 | valueField: undefined 36 | }; 37 | 38 | onDisposing() { 39 | this.disposed = true; 40 | } 41 | } 42 | 43 | describe('DxChart', () => { 44 | 45 | beforeEach(() => { 46 | TestBed.configureTestingModule( 47 | { 48 | declarations: [TestContainerComponent], 49 | imports: [DxChartModule, DxScrollViewModule, BrowserTransferStateModule] 50 | }); 51 | }); 52 | 53 | // spec 54 | it('visualizsation widgets should have display:"block" style', () => { 55 | TestBed.overrideComponent(TestContainerComponent, { 56 | set: { 57 | template: `` 58 | } 59 | }); 60 | let fixture = TestBed.createComponent(TestContainerComponent); 61 | fixture.detectChanges(); 62 | 63 | let outerComponent = fixture.componentInstance, 64 | chart = outerComponent.chart, 65 | element: any = chart.instance.element(); 66 | 67 | expect(window.getComputedStyle(element).display).toBe('block'); 68 | }); 69 | 70 | it('should not repainting twice in change detection cycle after applying options directly', () => { 71 | TestBed.overrideComponent(TestContainerComponent, { 72 | set: { 73 | template: `` 78 | } 79 | }); 80 | let fixture = TestBed.createComponent(TestContainerComponent); 81 | fixture.detectChanges(); 82 | 83 | let testComponent = fixture.componentInstance, 84 | chart = testComponent.chart, 85 | spy = spyOn(chart.instance, '_applyChanges'); 86 | 87 | testComponent.dataSource = [{ 88 | name: 1, 89 | value: 2 90 | }]; 91 | testComponent.seriesAsArray = [ 92 | { valueField: 'value'} 93 | ]; 94 | 95 | fixture.detectChanges(); 96 | 97 | expect(spy.calls.count()).toBe(1); 98 | }); 99 | 100 | it('should not repainting twice in change detection cycle after detect changes in arrays', () => { 101 | TestBed.overrideComponent(TestContainerComponent, { 102 | set: { 103 | template: `` 108 | } 109 | }); 110 | let fixture = TestBed.createComponent(TestContainerComponent); 111 | fixture.detectChanges(); 112 | 113 | let testComponent = fixture.componentInstance, 114 | chart = testComponent.chart, 115 | spy = spyOn(chart.instance, '_applyChanges'); 116 | 117 | testComponent.dataSource.push({ 118 | name: 1, 119 | value: 2 120 | }); 121 | testComponent.seriesAsArray.push({ 122 | valueField: 'value' 123 | }); 124 | 125 | fixture.detectChanges(); 126 | 127 | expect(spy.calls.count()).toBe(1); 128 | }); 129 | 130 | it('should change strip', () => { 131 | TestBed.overrideComponent(TestContainerComponent, { 132 | set: { 133 | template: ` 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | ` 142 | } 143 | }); 144 | let fixture = TestBed.createComponent(TestContainerComponent); 145 | fixture.detectChanges(); 146 | 147 | let instance = fixture.componentInstance.chart.instance; 148 | 149 | expect(instance.option('argumentAxis.strips[0].label.text')).toBe('label1'); 150 | 151 | fixture.componentInstance.strips[0].label = 'label2'; 152 | fixture.detectChanges(); 153 | 154 | expect(instance.option('argumentAxis.strips[0].label.text')).toBe('label2'); 155 | }); 156 | 157 | it('should remove component by dispose method', () => { 158 | TestBed.overrideComponent(TestContainerComponent, { 159 | set: { 160 | template: ` 161 | 162 | 163 | 164 | 165 | ` 166 | } 167 | }); 168 | 169 | jasmine.clock().uninstall(); 170 | jasmine.clock().install(); 171 | let fixture = TestBed.createComponent(TestContainerComponent); 172 | fixture.detectChanges(); 173 | 174 | const testComponent = fixture.componentInstance; 175 | const instance = testComponent.chart.instance; 176 | instance.dispose(); 177 | fixture.detectChanges(); 178 | fixture.destroy(); 179 | expect(testComponent.disposed); 180 | jasmine.clock().uninstall(); 181 | 182 | }); 183 | }); 184 | -------------------------------------------------------------------------------- /packages/devextreme-angular/tests/src/ui/custom-value-accessor-implementation.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:component-selector */ 2 | 3 | import { 4 | Component, 5 | OnInit 6 | } from '@angular/core'; 7 | 8 | import { 9 | ReactiveFormsModule, 10 | AbstractControl, 11 | FormGroup, 12 | FormControl 13 | } from '@angular/forms'; 14 | 15 | import { BrowserTransferStateModule } from '@angular/platform-browser'; 16 | 17 | import { 18 | TestBed 19 | } from '@angular/core/testing'; 20 | 21 | import DxTextBox from 'devextreme/ui/text_box'; 22 | 23 | import { 24 | DxTextBoxModule 25 | } from 'devextreme-angular'; 26 | 27 | @Component({ 28 | selector: 'test-container-component', 29 | template: ` 30 |
31 |
32 | 33 |
34 |
35 | ` 36 | }) 37 | class TestContainerComponent implements OnInit { 38 | form: FormGroup; 39 | value = ''; 40 | formControl: AbstractControl; 41 | 42 | ngOnInit() { 43 | this.form = new FormGroup({ 44 | formControl: new FormControl(''), 45 | }); 46 | this.formControl = this.form.controls['formControl']; 47 | } 48 | } 49 | 50 | describe('DxTextBox value accessor', () => { 51 | 52 | beforeEach(() => { 53 | TestBed.configureTestingModule( 54 | { 55 | declarations: [TestContainerComponent], 56 | imports: [DxTextBoxModule, ReactiveFormsModule, BrowserTransferStateModule] 57 | }); 58 | }); 59 | 60 | function getWidget(fixture) { 61 | let widgetElement = fixture.nativeElement.querySelector('.dx-textbox') || fixture.nativeElement; 62 | return DxTextBox['getInstance'](widgetElement) as any; 63 | } 64 | 65 | // spec 66 | it('should process disable/enable methods', () => { 67 | let fixture = TestBed.createComponent(TestContainerComponent); 68 | fixture.detectChanges(); 69 | 70 | let instance = getWidget(fixture); 71 | 72 | fixture.componentInstance.formControl.disable(); 73 | fixture.detectChanges(); 74 | 75 | expect(instance.option('disabled')).toBe(true); 76 | 77 | fixture.componentInstance.formControl.enable(); 78 | fixture.detectChanges(); 79 | 80 | expect(instance.option('disabled')).toBe(false); 81 | }); 82 | 83 | it('should change the value', () => { 84 | let fixture = TestBed.createComponent(TestContainerComponent); 85 | fixture.detectChanges(); 86 | 87 | let instance = getWidget(fixture); 88 | 89 | fixture.componentInstance.value = 'text'; 90 | fixture.detectChanges(); 91 | 92 | expect(instance.option('value')).toBe('text'); 93 | }); 94 | 95 | it('should change touched option', () => { 96 | let fixture = TestBed.createComponent(TestContainerComponent); 97 | fixture.detectChanges(); 98 | 99 | let instance = getWidget(fixture); 100 | 101 | expect(fixture.componentInstance.formControl.touched).toBe(false); 102 | 103 | instance.focus(); 104 | instance.blur(); 105 | 106 | expect(fixture.componentInstance.formControl.touched).toBe(true); 107 | }); 108 | 109 | it('should not fire valueChanges event when patchValue method is used with emitEvent=false (T614207)', () => { 110 | let fixture = TestBed.createComponent(TestContainerComponent); 111 | fixture.detectChanges(); 112 | 113 | let component = fixture.componentInstance, 114 | form = component.form, 115 | testSpy = jasmine.createSpy('testSpy'); 116 | 117 | form.valueChanges.subscribe(testSpy); 118 | 119 | form.controls['formControl'].patchValue('text', { emitEvent: false }); 120 | fixture.detectChanges(); 121 | expect(testSpy).toHaveBeenCalledTimes(0); 122 | 123 | form.controls['formControl'].patchValue('text2'); 124 | fixture.detectChanges(); 125 | expect(testSpy).toHaveBeenCalledTimes(1); 126 | }); 127 | }); 128 | -------------------------------------------------------------------------------- /packages/devextreme-angular/tests/src/ui/events.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:component-selector */ 2 | 3 | import { 4 | Component, NgZone 5 | } from '@angular/core'; 6 | 7 | import { 8 | TestBed 9 | } from '@angular/core/testing'; 10 | 11 | import { BrowserTransferStateModule } from '@angular/platform-browser'; 12 | 13 | import { 14 | DxDataGridModule 15 | } from 'devextreme-angular'; 16 | 17 | import readyCallbacks from 'devextreme/core/utils/ready_callbacks'; 18 | import { on } from 'devextreme/events'; 19 | 20 | @Component({ 21 | selector: 'test-container-component', 22 | template: '' 23 | }) 24 | class TestContainerComponent { 25 | } 26 | 27 | 28 | describe('global events', () => { 29 | 30 | it('should be subscribed within Angular Zone', () => { 31 | let readyCallbacksCalls = 0; 32 | readyCallbacks.fire(); 33 | 34 | readyCallbacks.add(() => { 35 | readyCallbacksCalls++; 36 | NgZone.assertInAngularZone(); 37 | }); 38 | 39 | TestBed.configureTestingModule({ 40 | declarations: [TestContainerComponent], 41 | imports: [DxDataGridModule, BrowserTransferStateModule] 42 | }); 43 | 44 | TestBed.overrideComponent(TestContainerComponent, { 45 | set: { template: `` } 46 | }); 47 | 48 | TestBed.createComponent(TestContainerComponent); 49 | expect(readyCallbacksCalls).toBe(1); 50 | 51 | readyCallbacks.add(() => { 52 | readyCallbacksCalls++; 53 | NgZone.assertInAngularZone(); 54 | }); 55 | expect(readyCallbacksCalls).toBe(2); 56 | }); 57 | 58 | }); 59 | 60 | describe('events', () => { 61 | 62 | it('should be fired within Angular Zone', () => { 63 | TestBed.configureTestingModule({ 64 | declarations: [TestContainerComponent], 65 | imports: [DxDataGridModule] 66 | }); 67 | 68 | TestBed.overrideComponent(TestContainerComponent, { 69 | set: { template: ` 70 |
71 | ` } 72 | }); 73 | 74 | const fixture = TestBed.createComponent(TestContainerComponent); 75 | fixture.detectChanges(); 76 | 77 | const element = fixture.nativeElement.querySelector('.elem'); 78 | let counter = 0; 79 | fixture.ngZone.runOutsideAngular(() => { 80 | on(element, 'click', () => { 81 | expect(NgZone.isInAngularZone()).toBe(true); 82 | counter++; 83 | }); 84 | }); 85 | 86 | element.click(); 87 | expect(counter).toBe(1); 88 | }); 89 | 90 | }); 91 | -------------------------------------------------------------------------------- /packages/devextreme-angular/tests/src/ui/range-selector.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:component-selector */ 2 | 3 | import { 4 | Component, 5 | ViewChild 6 | } from '@angular/core'; 7 | 8 | import { 9 | TestBed 10 | } from '@angular/core/testing'; 11 | 12 | import { BrowserTransferStateModule } from '@angular/platform-browser'; 13 | 14 | import { 15 | DxRangeSelectorModule, DxRangeSelectorComponent 16 | } from 'devextreme-angular'; 17 | 18 | @Component({ 19 | selector: 'test-container-component', 20 | template: '' 21 | }) 22 | class TestContainerComponent { 23 | value = { startValue: 0, endValue: 10 }; 24 | scale = { startValue: 0, endValue: 10 }; 25 | count = 0; 26 | @ViewChild('rangeSelector') rangeSelector: DxRangeSelectorComponent; 27 | onChanged() { 28 | this.count++; 29 | } 30 | } 31 | 32 | describe('DxRangeSelector', () => { 33 | beforeEach(() => { 34 | TestBed.configureTestingModule( 35 | { 36 | declarations: [TestContainerComponent], 37 | imports: [DxRangeSelectorModule, BrowserTransferStateModule] 38 | }); 39 | }); 40 | 41 | it('value binding should work', () => { 42 | TestBed.overrideComponent(TestContainerComponent, { 43 | set: { 44 | template: ` 45 | 46 | ` 47 | } 48 | }); 49 | let fixture = TestBed.createComponent(TestContainerComponent); 50 | fixture.detectChanges(); 51 | 52 | let component: TestContainerComponent = fixture.componentInstance; 53 | component.rangeSelector.value = {startValue: 0, endValue: 6}; 54 | fixture.detectChanges(); 55 | component.rangeSelector.value = {startValue: 0, endValue: 5}; 56 | fixture.detectChanges(); 57 | expect(fixture.componentInstance.count).toBe(4); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /packages/devextreme-angular/tests/src/ui/responsive-box.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:component-selector */ 2 | 3 | import { 4 | Component, 5 | ViewChild 6 | } from '@angular/core'; 7 | 8 | import { 9 | TestBed 10 | } from '@angular/core/testing'; 11 | 12 | import { BrowserTransferStateModule } from '@angular/platform-browser'; 13 | 14 | import { 15 | DxResponsiveBoxModule, 16 | DxResponsiveBoxComponent 17 | } from 'devextreme-angular'; 18 | 19 | @Component({ 20 | selector: 'test-container-component', 21 | template: '' 22 | }) 23 | class TestContainerComponent { 24 | @ViewChild(DxResponsiveBoxComponent) innerWidget: DxResponsiveBoxComponent; 25 | screenByWidth() { 26 | return 'sm'; 27 | } 28 | } 29 | 30 | describe('DxResponsiveBox', () => { 31 | 32 | beforeEach(() => { 33 | TestBed.configureTestingModule( 34 | { 35 | declarations: [TestContainerComponent], 36 | imports: [DxResponsiveBoxModule, BrowserTransferStateModule] 37 | }); 38 | }); 39 | 40 | // spec 41 | it('should be able to accept item locations as dxi components', () => { 42 | TestBed.overrideComponent(TestContainerComponent, { 43 | set: { 44 | template: ` 45 | 51 | 52 | 57 | 58 |

Header

59 |
60 |
61 | ` 62 | } 63 | }); 64 | let fixture = TestBed.createComponent(TestContainerComponent); 65 | fixture.detectChanges(); 66 | 67 | let instance = fixture.componentInstance.innerWidget.instance; 68 | fixture.detectChanges(); 69 | 70 | expect(instance.option('items')[0].location.length).toBe(1); 71 | expect(instance.option('items')[0].location[0].row).toBe(0); 72 | expect(instance.option('items')[0].location[0].col).toBe(0); 73 | expect(instance.option('items')[0].location[0].colspan).toBe(1); 74 | expect(instance.option('items')[0].location[0].screen).toBe('lg'); 75 | }); 76 | 77 | }); 78 | -------------------------------------------------------------------------------- /packages/devextreme-angular/tests/src/ui/select-box.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:component-selector */ 2 | 3 | import { 4 | Component, 5 | ViewChild 6 | } from '@angular/core'; 7 | 8 | import { 9 | TestBed 10 | } from '@angular/core/testing'; 11 | 12 | import { BrowserTransferStateModule } from '@angular/platform-browser'; 13 | 14 | import { 15 | DxSelectBoxModule, 16 | DxTextBoxModule, 17 | DxSelectBoxComponent 18 | } from 'devextreme-angular'; 19 | 20 | @Component({ 21 | selector: 'test-container-component', 22 | template: '' 23 | }) 24 | class TestContainerComponent { 25 | @ViewChild(DxSelectBoxComponent) selectBox: DxSelectBoxComponent; 26 | 27 | changeOption(e) { 28 | if (e.value === 2) { 29 | this.selectBox.value = 1; 30 | } 31 | } 32 | } 33 | 34 | describe('DxSelectBox', () => { 35 | 36 | beforeEach(() => { 37 | TestBed.configureTestingModule( 38 | { 39 | declarations: [TestContainerComponent], 40 | imports: [DxSelectBoxModule, DxTextBoxModule, BrowserTransferStateModule] 41 | }); 42 | }); 43 | 44 | // spec 45 | it('field template should work', () => { 46 | TestBed.overrideComponent(TestContainerComponent, { 47 | set: { 48 | template: ` 49 | 50 |
51 | 52 |
53 |
54 | ` 55 | } 56 | }); 57 | let fixture = TestBed.createComponent(TestContainerComponent); 58 | fixture.detectChanges(); 59 | 60 | let instance = fixture.componentInstance.selectBox.instance; 61 | 62 | expect(instance.element().querySelectorAll('.dx-textbox').length).toBe(1); 63 | }); 64 | 65 | // spec 66 | it('should update value on action', () => { 67 | TestBed.overrideComponent(TestContainerComponent, { 68 | set: { 69 | template: ` 70 | 71 | 72 | ` 73 | } 74 | }); 75 | 76 | let fixture = TestBed.createComponent(TestContainerComponent); 77 | fixture.detectChanges(); 78 | 79 | let selectBox = fixture.componentInstance.selectBox; 80 | let instance = selectBox.instance; 81 | 82 | instance.option('value', 2); 83 | // @ts-ignore 84 | expect(instance.option('text')).toBe(1); 85 | 86 | instance.option('value', 2); 87 | // @ts-ignore 88 | expect(instance.option('text')).toBe(1); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /packages/devextreme-angular/tests/src/ui/tab-panel.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:component-selector */ 2 | 3 | import { 4 | Component, 5 | ViewChild 6 | } from '@angular/core'; 7 | 8 | import { 9 | TestBed 10 | } from '@angular/core/testing'; 11 | 12 | import { BrowserTransferStateModule } from '@angular/platform-browser'; 13 | 14 | import { 15 | DxTabPanelModule, 16 | DxTabPanelComponent 17 | } from 'devextreme-angular'; 18 | 19 | @Component({ 20 | selector: 'test-container-component', 21 | template: ` 22 | 25 | 26 | ` 27 | }) 28 | class TestContainerComponent { 29 | @ViewChild(DxTabPanelComponent) tabPanel: DxTabPanelComponent; 30 | 31 | tabPanelItems: number[] = [0, 1, 2]; 32 | selectedIndex = 0; 33 | } 34 | 35 | describe('DxTabPanel', () => { 36 | 37 | beforeEach(() => { 38 | TestBed.configureTestingModule( 39 | { 40 | declarations: [TestContainerComponent], 41 | imports: [DxTabPanelModule, BrowserTransferStateModule] 42 | }); 43 | }); 44 | 45 | // spec 46 | it('option, dependenced on dataSource, should be applied', () => { 47 | let fixture = TestBed.createComponent(TestContainerComponent); 48 | fixture.detectChanges(); 49 | 50 | let component: TestContainerComponent = fixture.componentInstance; 51 | component.tabPanelItems.push(3); 52 | 53 | let index = component.tabPanelItems.length - 1; 54 | component.selectedIndex = index; 55 | fixture.detectChanges(); 56 | 57 | let instance: any = component.tabPanel.instance; 58 | expect(instance.option('items').length).toBe(4); 59 | expect(instance.option('selectedIndex')).toBe(index); 60 | }); 61 | 62 | it('option, binded to another widget, should update second widget before AfterViewChecked lifecycle hook', () => { 63 | TestBed.overrideComponent(TestContainerComponent, { 64 | set: { 65 | template: ` 66 | 68 | 69 | 70 | ` 71 | } 72 | }); 73 | 74 | let fixture = TestBed.createComponent(TestContainerComponent); 75 | fixture.detectChanges(); 76 | 77 | let component: TestContainerComponent = fixture.componentInstance; 78 | component.tabPanel.visible = false; 79 | 80 | expect(fixture.detectChanges.bind(fixture)).not.toThrow(); 81 | }); 82 | 83 | it('dxi-item nested component should render own content', () => { 84 | TestBed.overrideComponent(TestContainerComponent, { 85 | set: { 86 | template: ` 87 | 88 | 89 |
Page1
90 |
91 |
92 | ` 93 | } 94 | }); 95 | let fixture = TestBed.createComponent(TestContainerComponent); 96 | fixture.detectChanges(); 97 | 98 | let content = document.getElementById('content'); 99 | 100 | expect(content).not.toBe(null); 101 | expect(content.innerText).toBe('Page1'); 102 | }); 103 | }); 104 | -------------------------------------------------------------------------------- /packages/devextreme-angular/tests/src/ui/tag-box.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:component-selector */ 2 | 3 | import { 4 | Component, 5 | ViewChild 6 | } from '@angular/core'; 7 | 8 | import { 9 | TestBed 10 | } from '@angular/core/testing'; 11 | 12 | import { BrowserTransferStateModule } from '@angular/platform-browser'; 13 | 14 | import { 15 | DxTagBoxModule, 16 | DxTagBoxComponent 17 | } from 'devextreme-angular'; 18 | 19 | @Component({ 20 | selector: 'test-container-component', 21 | template: '' 22 | }) 23 | class TestContainerComponent { 24 | @ViewChild(DxTagBoxComponent) tagBox: DxTagBoxComponent; 25 | 26 | value: number[] = []; 27 | testMethod() {} 28 | } 29 | 30 | describe('DxTagBox', () => { 31 | 32 | beforeEach(() => { 33 | TestBed.configureTestingModule( 34 | { 35 | declarations: [TestContainerComponent], 36 | imports: [DxTagBoxModule, BrowserTransferStateModule] 37 | }); 38 | }); 39 | 40 | // spec 41 | it('value change should be fired once', () => { 42 | let testSpy = spyOn(TestContainerComponent.prototype, 'testMethod'); 43 | TestBed.overrideComponent(TestContainerComponent, { 44 | set: { 45 | template: ` 46 | 47 | 48 | ` 49 | } 50 | }); 51 | let fixture = TestBed.createComponent(TestContainerComponent); 52 | fixture.detectChanges(); 53 | 54 | expect(testSpy).toHaveBeenCalledTimes(0); 55 | 56 | let instance: any = fixture.componentInstance.tagBox.instance; 57 | instance.option('value', [2]); 58 | 59 | fixture.detectChanges(); 60 | expect(testSpy).toHaveBeenCalledTimes(1); 61 | }); 62 | 63 | it('value change should be fired once after remove tag', () => { 64 | let testSpy = spyOn(TestContainerComponent.prototype, 'testMethod'); 65 | TestBed.overrideComponent(TestContainerComponent, { 66 | set: { 67 | template: ` 68 | 69 | 70 | ` 71 | } 72 | }); 73 | let fixture = TestBed.createComponent(TestContainerComponent); 74 | fixture.detectChanges(); 75 | expect(testSpy).toHaveBeenCalledTimes(0); 76 | let instance: any = fixture.componentInstance.tagBox.instance, 77 | removeButton = instance.element().querySelector('.dx-tag-remove-button'); 78 | removeButton.click(); 79 | fixture.detectChanges(); 80 | expect(testSpy).toHaveBeenCalledTimes(1); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /packages/devextreme-angular/tests/src/ui/toolbar.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:component-selector */ 2 | 3 | import { 4 | Component, 5 | ViewChild 6 | } from '@angular/core'; 7 | 8 | import { 9 | TestBed 10 | } from '@angular/core/testing'; 11 | 12 | import { BrowserTransferStateModule } from '@angular/platform-browser'; 13 | 14 | import { 15 | DxToolbarModule, 16 | DxToolbarComponent 17 | } from 'devextreme-angular'; 18 | 19 | @Component({ 20 | selector: 'test-container-component', 21 | template: '' 22 | }) 23 | class TestContainerComponent { 24 | @ViewChild(DxToolbarComponent) innerWidget: DxToolbarComponent; 25 | } 26 | 27 | describe('DxToolbar', () => { 28 | 29 | beforeEach(() => { 30 | TestBed.configureTestingModule( 31 | { 32 | declarations: [TestContainerComponent], 33 | imports: [DxToolbarModule, BrowserTransferStateModule] 34 | }); 35 | }); 36 | 37 | // spec 38 | it('should not initialize the "items" property of an item if no children are declared inside the item (T472434)', () => { 39 | TestBed.overrideComponent(TestContainerComponent, { 40 | set: { 41 | template: ` 42 | 43 | Item1 44 | 45 | ` 46 | } 47 | }); 48 | let fixture = TestBed.createComponent(TestContainerComponent); 49 | fixture.detectChanges(); 50 | 51 | let instance = fixture.componentInstance.innerWidget.instance; 52 | expect(instance.option('items')[0].items).toBe(undefined); 53 | expect(instance.element().querySelector('.dx-toolbar-item').textContent).toBe('Item1'); 54 | }); 55 | 56 | }); 57 | -------------------------------------------------------------------------------- /packages/devextreme-angular/tests/src/ui/validator.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:component-selector */ 2 | 3 | import { 4 | Component, 5 | ViewChild 6 | } from '@angular/core'; 7 | 8 | import { 9 | TestBed 10 | } from '@angular/core/testing'; 11 | 12 | import { BrowserTransferStateModule } from '@angular/platform-browser'; 13 | 14 | import { 15 | DxTextBoxModule, 16 | DxTextBoxComponent, 17 | DxValidatorModule, 18 | DxValidatorComponent 19 | } from 'devextreme-angular'; 20 | 21 | @Component({ 22 | selector: 'test-container-component', 23 | template: '' 24 | }) 25 | class TestContainerComponent { 26 | @ViewChild(DxTextBoxComponent) textBox: DxTextBoxComponent; 27 | @ViewChild(DxValidatorComponent) validator: DxValidatorComponent; 28 | 29 | text = 'Some text'; 30 | validationRules = [ 31 | { 32 | type: 'required', 33 | message: 'Report number is required.' 34 | } 35 | ]; 36 | isValid = true; 37 | testAdapter = { 38 | getValue: () => { 39 | return this.text; 40 | }, 41 | applyValidationResults: (result: any) => { 42 | this.isValid = result.isValid; 43 | } 44 | }; 45 | } 46 | 47 | 48 | describe('DxValidator', () => { 49 | 50 | beforeEach(() => { 51 | TestBed.configureTestingModule( 52 | { 53 | declarations: [TestContainerComponent], 54 | imports: [DxTextBoxModule, DxValidatorModule, BrowserTransferStateModule] 55 | }); 56 | }); 57 | 58 | // spec 59 | it('should work with dx-validator', () => { 60 | TestBed.overrideComponent(TestContainerComponent, { 61 | set: { 62 | template: ` 63 | 64 | 65 | 66 | ` 67 | } 68 | }); 69 | let fixture = TestBed.createComponent(TestContainerComponent); 70 | fixture.detectChanges(); 71 | 72 | let testComponent = fixture.componentInstance; 73 | let instance: any = testComponent.textBox.instance; 74 | 75 | fixture.detectChanges(); 76 | expect(instance.element().classList.contains('dx-invalid')).toBe(false); 77 | 78 | testComponent.text = ''; 79 | fixture.detectChanges(); 80 | expect(instance.element().classList.contains('dx-invalid')).toBe(true); 81 | 82 | testComponent.text = 'Some text'; 83 | fixture.detectChanges(); 84 | expect(instance.element().classList.contains('dx-invalid')).toBe(false); 85 | }); 86 | 87 | it('should work with custom editor', () => { 88 | TestBed.overrideComponent(TestContainerComponent, { 89 | set: { 90 | template: ` 91 | 92 | 93 | ` 94 | } 95 | }); 96 | 97 | let fixture = TestBed.createComponent(TestContainerComponent); 98 | fixture.detectChanges(); 99 | 100 | let testComponent = fixture.componentInstance; 101 | 102 | testComponent.validator.instance.validate(); 103 | expect(testComponent.isValid).toBe(true); 104 | 105 | testComponent.text = ''; 106 | fixture.detectChanges(); 107 | testComponent.validator.instance.validate(); 108 | expect(testComponent.isValid).toBe(false); 109 | 110 | testComponent.text = 'Some text'; 111 | fixture.detectChanges(); 112 | testComponent.validator.instance.validate(); 113 | expect(testComponent.isValid).toBe(true); 114 | }); 115 | }); 116 | -------------------------------------------------------------------------------- /packages/devextreme-angular/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "target": "es2015", 5 | "typeRoots": [ 6 | "./node_modules/@types", 7 | "../../node_modules/@types" 8 | ], 9 | "types": ["jasmine"] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/devextreme-angular/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../node_modules/ng-packagr/lib/ts/conf/tsconfig.ngc.json", 3 | "angularCompilerOptions": { 4 | "enableIvy": true, 5 | "annotateForClosureCompiler": true, 6 | "fullTemplateTypeCheck": true, 7 | }, 8 | "compilerOptions": { 9 | "typeRoots": [ 10 | "./node_modules/@types", 11 | "../../node_modules/@types" 12 | ] 13 | }, 14 | "files": [], 15 | } 16 | -------------------------------------------------------------------------------- /packages/devextreme-angular/webpack.test.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var path = require('path'); 3 | 4 | module.exports = { 5 | devtool: 'inline-source-map', 6 | mode: 'development', 7 | plugins: [ 8 | new webpack.ContextReplacementPlugin( 9 | /angular(\\|\/)core(\\|\/)(@angular|esm5)/, 10 | path.join(__dirname, '../tests'), 11 | {} 12 | ) 13 | ], 14 | resolve: { 15 | alias: { 16 | 'devextreme-angular': path.resolve(__dirname, 'npm/dist') 17 | }, 18 | fallback: { "stream": require.resolve("stream-browserify")} 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /packages/sandbox/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # profiling files 12 | chrome-profiler-events.json 13 | speed-measure-plugin.json 14 | 15 | # IDEs and editors 16 | /.idea 17 | .project 18 | .classpath 19 | .c9/ 20 | *.launch 21 | .settings/ 22 | *.sublime-workspace 23 | 24 | # IDE - VSCode 25 | .vscode/* 26 | !.vscode/settings.json 27 | !.vscode/tasks.json 28 | !.vscode/launch.json 29 | !.vscode/extensions.json 30 | .history/* 31 | 32 | # misc 33 | /.sass-cache 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | npm-debug.log 38 | yarn-error.log 39 | testem.log 40 | /typings 41 | 42 | # System Files 43 | .DS_Store 44 | Thumbs.db 45 | -------------------------------------------------------------------------------- /packages/sandbox/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "sandbox": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "app", 11 | "schematics": {}, 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "outputPath": "dist/sandbox", 17 | "index": "src/index.html", 18 | "main": "src/main.ts", 19 | "polyfills": "src/polyfills.ts", 20 | "tsConfig": "src/tsconfig.app.json", 21 | "styles": [ 22 | "../../node_modules/devextreme/dist/css/dx.common.css", 23 | "../../node_modules/devextreme/dist/css/dx.light.css" 24 | ], 25 | "scripts": [] 26 | }, 27 | "configurations": { 28 | "production": { 29 | "fileReplacements": [ 30 | { 31 | "replace": "src/environments/environment.ts", 32 | "with": "src/environments/environment.prod.ts" 33 | } 34 | ], 35 | "optimization": true, 36 | "outputHashing": "all", 37 | "sourceMap": false, 38 | "extractCss": true, 39 | "namedChunks": false, 40 | "aot": true, 41 | "extractLicenses": true, 42 | "vendorChunk": false, 43 | "buildOptimizer": true, 44 | "budgets": [ 45 | { 46 | "type": "initial", 47 | "maximumWarning": "2mb", 48 | "maximumError": "5mb" 49 | } 50 | ] 51 | }, 52 | "dev": { 53 | "optimization": false, 54 | "outputHashing": "none", 55 | "sourceMap": true, 56 | "extractCss": true, 57 | "namedChunks": false, 58 | "aot": true, 59 | "extractLicenses": true, 60 | "vendorChunk": false, 61 | "buildOptimizer": false 62 | } 63 | } 64 | }, 65 | "serve": { 66 | "builder": "@angular-devkit/build-angular:dev-server", 67 | "options": { 68 | "browserTarget": "sandbox:build" 69 | }, 70 | "configurations": { 71 | "production": { 72 | "browserTarget": "sandbox:build:production" 73 | }, 74 | "dev": { 75 | "browserTarget": "sandbox:build:dev" 76 | } 77 | } 78 | } 79 | } 80 | } 81 | }, 82 | "defaultProject": "sandbox" 83 | } 84 | -------------------------------------------------------------------------------- /packages/sandbox/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "devextreme-angular-sandbox", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "start:dev": "ng serve --open -c dev" 8 | }, 9 | "private": true, 10 | "dependencies": { 11 | "@angular/animations": "~12.2.17", 12 | "@angular/common": "~12.2.17", 13 | "@angular/compiler": "~12.2.17", 14 | "@angular/core": "~12.2.17", 15 | "@angular/forms": "~12.2.17", 16 | "@angular/platform-browser": "~12.2.17", 17 | "@angular/platform-browser-dynamic": "~12.2.17", 18 | "@angular/router": "~12.2.17", 19 | "core-js": "^2.6.12", 20 | "devextreme-angular": "~23.2.0", 21 | "rxjs": "^6.6.7", 22 | "tslib": "^2.6.1", 23 | "zone.js": "^0.13.1" 24 | }, 25 | "devDependencies": { 26 | "@angular-devkit/build-angular": "~12.2.18", 27 | "@angular/cli": "~12.2.18", 28 | "@angular/compiler-cli": "~12.2.17", 29 | "@angular/language-service": "~12.2.17", 30 | "@types/node": "~8.10.66", 31 | "devextreme": "23.2-next", 32 | "ts-node": "~7.0.1", 33 | "typescript": "~4.2.3" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/sandbox/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:component-selector */ 2 | 3 | import { 4 | Component, 5 | OnInit, 6 | AfterViewInit, 7 | ViewChild 8 | } from '@angular/core'; 9 | import { 10 | AbstractControl, 11 | FormGroup, 12 | FormControl, 13 | Validators 14 | } from '@angular/forms'; 15 | import { 16 | Orange, 17 | OrangeService 18 | } from './orange.service'; 19 | import { 20 | Customer, 21 | CustomerService 22 | } from './customer.service'; 23 | import { 24 | Appointment, 25 | AppointmentService 26 | } from './appointment.service'; 27 | import { 28 | OwnerService 29 | } from './owner.service'; 30 | import { 31 | DxPopoverComponent 32 | } from 'devextreme-angular'; 33 | 34 | @Component({ 35 | selector: 'my-app', 36 | styles: [` 37 | h1, h2, h3 { 38 | font-family: 'Helvetica Neue','Segoe UI',Helvetica,Verdana,sans-serif; 39 | } 40 | .demo-container { 41 | width: 400px; 42 | } 43 | .demo-container > .dx-widget { 44 | margin-bottom: 20px; 45 | -display: block; 46 | } 47 | .float-right { 48 | float: right; 49 | } 50 | .full-width { 51 | width: 100%; 52 | display: block; 53 | } 54 | .scroll-view-height { 55 | height: 200px; 56 | display: block; 57 | } 58 | .resizable { 59 | display: block; 60 | background-color: #ccc; 61 | } 62 | .tab-content { 63 | text-align: justify; 64 | margin-top: 25px; 65 | } 66 | #tabs { 67 | margin-top: 60px; 68 | } 69 | .tabpanel-item { 70 | height: 200px; 71 | -webkit-touch-callout: none; 72 | -webkit-user-select: none; 73 | -khtml-user-select: none; 74 | -moz-user-select: none; 75 | -ms-user-select: none; 76 | user-select: none; 77 | padding-left: 25px; 78 | padding-top: 55px; 79 | display: block; 80 | } 81 | .tabpanel-item > div { 82 | float: left; 83 | padding: 0 85px 10px 10px 84 | } 85 | .tabpanel-item p { 86 | font-size: 16px; 87 | } 88 | .form-group { 89 | margin-bottom: 10px; 90 | } 91 | `], 92 | templateUrl: 'app.component.html', 93 | providers: [ 94 | OrangeService, 95 | CustomerService, 96 | AppointmentService, 97 | OwnerService 98 | ] 99 | }) 100 | export class AppComponent implements OnInit, AfterViewInit { 101 | @ViewChild(DxPopoverComponent) popover: DxPopoverComponent; 102 | text = 'Initial text'; 103 | formData = { email: '', password: '' }; 104 | emailControl: AbstractControl; 105 | passwordControl: AbstractControl; 106 | emailControlIsChanged = false; 107 | passwordControlIsChanged = false; 108 | form: FormGroup; 109 | boolValue: boolean; 110 | numberValue: number; 111 | dateValue: Date; 112 | currentDate: Date; 113 | demoItems: string[]; 114 | popupVisible = false; 115 | chartSeriesTypes = ['bar', 'line', 'spline']; 116 | oranges: Orange[]; 117 | customers: Customer[]; 118 | appointments: Appointment[]; 119 | resources: any[]; 120 | products = [ 121 | { 122 | key: 'Notebook', 123 | items: ['Supernote JG867', 'Ultranote VP334', 'Meganote LS952'] 124 | }, { 125 | key: 'Netbook', 126 | items: ['Supernet HY834', 'Ultranet KN354', 'Meganet ME830'] 127 | } 128 | ]; 129 | tabs = [ 130 | { 131 | id: 0, 132 | text: 'user', 133 | icon: 'user', 134 | content: 'User tab content' 135 | }, { 136 | id: 1, 137 | text: 'comment', 138 | icon: 'comment', 139 | content: 'Comment tab content' 140 | }, { 141 | id: 2, 142 | text: 'find', 143 | icon: 'find', 144 | content: 'Find tab content' 145 | } 146 | ]; 147 | chartItem: string; 148 | tabPanelItems: Customer[]; 149 | tabContent: string; 150 | constructor(private orangeService: OrangeService, 151 | private customerService: CustomerService, 152 | private appointmentService: AppointmentService, 153 | private ownerService: OwnerService) { 154 | this.text = 'Text in textbox'; 155 | this.boolValue = true; 156 | this.numberValue = 10; 157 | this.dateValue = new Date(); 158 | this.currentDate = new Date(2015, 4, 25); 159 | this.demoItems = [ 160 | 'item1', 161 | 'item2', 162 | 'item3' 163 | ]; 164 | this.tabContent = this.tabs[0].content; 165 | } 166 | helloWorld() { 167 | alert('Hello world'); 168 | } 169 | buy(model) { 170 | alert(model + ' has been added to order'); 171 | } 172 | callNumber(number) { 173 | alert(number + ' is being called...'); 174 | } 175 | updateEmailControl(e) { 176 | this.form.controls['emailControl'].setValue(e.value); 177 | } 178 | updatePasswordControl(e) { 179 | this.form.controls['passwordControl'].setValue(e.value); 180 | } 181 | toggleFormControlsState(e) { 182 | if (e.value) { 183 | this.emailControl.disable(); 184 | this.passwordControl.disable(); 185 | } else { 186 | this.emailControl.enable(); 187 | this.passwordControl.enable(); 188 | } 189 | } 190 | onSubmit() { 191 | this.form.updateValueAndValidity(); 192 | console.log('submitted'); 193 | return false; 194 | } 195 | validate(params) { 196 | let result = params.validationGroup.validate(); 197 | if (result.isValid) { 198 | alert('Form data is valid'); 199 | } else { 200 | alert('Form data is invalid'); 201 | } 202 | } 203 | ngOnInit() { 204 | this.form = new FormGroup({ 205 | emailControl: new FormControl('', Validators.compose([Validators.required, CustomValidator.mailFormat])), 206 | passwordControl: new FormControl('', Validators.compose([Validators.required, Validators.minLength(6)])) 207 | }); 208 | this.emailControl = this.form.controls['emailControl']; 209 | this.passwordControl = this.form.controls['passwordControl']; 210 | this.oranges = this.orangeService.getOranges(); 211 | this.customers = this.customerService.getCustomers(); 212 | this.appointments = this.appointmentService.getAppointments(); 213 | this.resources = [{ 214 | field: 'OwnerId', 215 | allowMultiple: true, 216 | dataSource: this.ownerService.getOwners(), 217 | label: 'Owner' 218 | }]; 219 | this.tabPanelItems = this.customers.slice(0, 4); 220 | } 221 | ngAfterViewInit() { 222 | this.form.controls['emailControl'].valueChanges.subscribe((value) => { 223 | this.emailControlIsChanged = true; 224 | this.formData.email = value; 225 | }); 226 | this.form.controls['passwordControl'].valueChanges.subscribe((value) => { 227 | this.passwordControlIsChanged = true; 228 | this.formData.password = value; 229 | }); 230 | } 231 | showPopover() { 232 | this.popover.instance.show(); 233 | } 234 | selectTab(e) { 235 | this.tabContent = this.tabs[e.itemIndex].content; 236 | } 237 | seriesHoverChanged(e) { 238 | this.chartItem = e.target.argument; 239 | } 240 | } 241 | 242 | export class CustomValidator { 243 | static mailFormat(control: FormControl) { 244 | let EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i; 245 | 246 | if (control.value && control.value.length && (control.value.length <= 5 || !EMAIL_REGEXP.test(control.value))) { 247 | return { 'incorrectMailFormat': true }; 248 | } 249 | 250 | return null; 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /packages/sandbox/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { ReactiveFormsModule } from '@angular/forms'; 4 | import { AppComponent } from './app.component'; 5 | 6 | import { DevExtremeModule } from 'devextreme-angular'; 7 | 8 | @NgModule({ 9 | declarations: [ 10 | AppComponent 11 | ], 12 | imports: [ 13 | BrowserModule, 14 | ReactiveFormsModule, 15 | DevExtremeModule 16 | ], 17 | providers: [], 18 | bootstrap: [AppComponent] 19 | }) 20 | export class AppModule { } 21 | -------------------------------------------------------------------------------- /packages/sandbox/src/app/appointment.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | export class Appointment { 4 | Text: string; 5 | OwnerId: number[]; 6 | StartDate: Date; 7 | EndDate: Date; 8 | } 9 | 10 | let Appointments: Appointment[] = [ 11 | { 12 | Text: 'Website Re-Design Plan', 13 | OwnerId: [4], 14 | StartDate: new Date(2015, 4, 25, 9, 30), 15 | EndDate: new Date(2015, 4, 25, 11, 30) 16 | }, { 17 | Text: 'Book Flights to San Fran for Sales Trip', 18 | OwnerId: [2], 19 | StartDate: new Date(2015, 4, 25, 12, 0), 20 | EndDate: new Date(2015, 4, 25, 13, 0) 21 | }, { 22 | Text: 'Install New Router in Dev Room', 23 | OwnerId: [1], 24 | StartDate: new Date(2015, 4, 25, 14, 30), 25 | EndDate: new Date(2015, 4, 25, 15, 30) 26 | }, { 27 | Text: 'Approve Personal Computer Upgrade Plan', 28 | OwnerId: [3], 29 | StartDate: new Date(2015, 4, 26, 10, 0), 30 | EndDate: new Date(2015, 4, 26, 11, 0) 31 | }, { 32 | Text: 'Final Budget Review', 33 | OwnerId: [1], 34 | StartDate: new Date(2015, 4, 26, 12, 0), 35 | EndDate: new Date(2015, 4, 26, 13, 35) 36 | }, { 37 | Text: 'New Brochures', 38 | OwnerId: [4], 39 | StartDate: new Date(2015, 4, 26, 14, 30), 40 | EndDate: new Date(2015, 4, 26, 15, 45) 41 | }, { 42 | Text: 'Install New Database', 43 | OwnerId: [2], 44 | StartDate: new Date(2015, 4, 27, 9, 45), 45 | EndDate: new Date(2015, 4, 27, 11, 15) 46 | }, { 47 | Text: 'Approve New Online Marketing Strategy', 48 | OwnerId: [3, 4], 49 | StartDate: new Date(2015, 4, 27, 12, 0), 50 | EndDate: new Date(2015, 4, 27, 14, 0) 51 | }, { 52 | Text: 'Upgrade Personal Computers', 53 | OwnerId: [2], 54 | StartDate: new Date(2015, 4, 27, 15, 15), 55 | EndDate: new Date(2015, 4, 27, 16, 30) 56 | }, { 57 | Text: 'Prepare 2015 Marketing Plan', 58 | OwnerId: [1, 3], 59 | StartDate: new Date(2015, 4, 28, 11, 0), 60 | EndDate: new Date(2015, 4, 28, 13, 30) 61 | }, { 62 | Text: 'Brochure Design Review', 63 | OwnerId: [4], 64 | StartDate: new Date(2015, 4, 28, 14, 0), 65 | EndDate: new Date(2015, 4, 28, 15, 30) 66 | }, { 67 | Text: 'Create Icons for Website', 68 | OwnerId: [3], 69 | StartDate: new Date(2015, 4, 29, 10, 0), 70 | EndDate: new Date(2015, 4, 29, 11, 30) 71 | }, { 72 | Text: 'Upgrade Server Hardware', 73 | OwnerId: [4], 74 | StartDate: new Date(2015, 4, 29, 14, 30), 75 | EndDate: new Date(2015, 4, 29, 16, 0) 76 | }, { 77 | Text: 'Submit New Website Design', 78 | OwnerId: [1], 79 | StartDate: new Date(2015, 4, 29, 16, 30), 80 | EndDate: new Date(2015, 4, 29, 18, 0) 81 | }, { 82 | Text: 'Launch New Website', 83 | OwnerId: [2], 84 | StartDate: new Date(2015, 4, 29, 12, 20), 85 | EndDate: new Date(2015, 4, 29, 14, 0) 86 | } 87 | ]; 88 | 89 | @Injectable() 90 | export class AppointmentService { 91 | getAppointments() { 92 | return Appointments; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /packages/sandbox/src/app/customer.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | export class Customer { 4 | ID: number; 5 | CompanyName: string; 6 | Address: string; 7 | City: string; 8 | State: string; 9 | Zipcode: number; 10 | Phone: string; 11 | Fax: string; 12 | Website: string; 13 | } 14 | 15 | let CUSTOMERS: Customer[] = [{ 16 | ID: 1, 17 | CompanyName: 'Super Mart of the West', 18 | Address: '702 SW 8th Street', 19 | City: 'Bentonville', 20 | State: 'Arkansas', 21 | Zipcode: 72716, 22 | Phone: '(800) 555-2797', 23 | Fax: '(800) 555-2171', 24 | Website: 'http://www.nowebsitesupermart.com' 25 | }, { 26 | ID: 2, 27 | CompanyName: 'Electronics Depot', 28 | Address: '2455 Paces Ferry Road NW', 29 | City: 'Atlanta', 30 | State: 'Georgia', 31 | Zipcode: 30339, 32 | Phone: '(800) 595-3232', 33 | Fax: '(800) 595-3231', 34 | Website: 'http://www.nowebsitedepot.com' 35 | }, { 36 | ID: 3, 37 | CompanyName: 'K&S Music', 38 | Address: '1000 Nicllet Mall', 39 | City: 'Minneapolis', 40 | State: 'Minnesota', 41 | Zipcode: 55403, 42 | Phone: '(612) 304-6073', 43 | Fax: '(612) 304-6074', 44 | Website: 'http://www.nowebsitemusic.com' 45 | }, { 46 | ID: 4, 47 | CompanyName: 'Tom\'s Club', 48 | Address: '999 Lake Drive', 49 | City: 'Issaquah', 50 | State: 'Washington', 51 | Zipcode: 98027, 52 | Phone: '(800) 955-2292', 53 | Fax: '(800) 955-2293', 54 | Website: 'http://www.nowebsitetomsclub.com' 55 | }, { 56 | ID: 5, 57 | CompanyName: 'E-Mart', 58 | Address: '3333 Beverly Rd', 59 | City: 'Hoffman Estates', 60 | State: 'Illinois', 61 | Zipcode: 60179, 62 | Phone: '(847) 286-2500', 63 | Fax: '(847) 286-2501', 64 | Website: 'http://www.nowebsiteemart.com' 65 | }, { 66 | ID: 6, 67 | CompanyName: 'Walters', 68 | Address: '200 Wilmot Rd', 69 | City: 'Deerfield', 70 | State: 'Illinois', 71 | Zipcode: 60015, 72 | Phone: '(847) 940-2500', 73 | Fax: '(847) 940-2501', 74 | Website: 'http://www.nowebsitewalters.com' 75 | }, { 76 | ID: 7, 77 | CompanyName: 'StereoShack', 78 | Address: '400 Commerce S', 79 | City: 'Fort Worth', 80 | State: 'Texas', 81 | Zipcode: 76102, 82 | Phone: '(817) 820-0741', 83 | Fax: '(817) 820-0742', 84 | Website: 'http://www.nowebsiteshack.com' 85 | }, { 86 | ID: 8, 87 | CompanyName: 'Circuit Town', 88 | Address: '2200 Kensington Court', 89 | City: 'Oak Brook', 90 | State: 'Illinois', 91 | Zipcode: 60523, 92 | Phone: '(800) 955-2929', 93 | Fax: '(800) 955-9392', 94 | Website: 'http://www.nowebsitecircuittown.com' 95 | }, { 96 | ID: 9, 97 | CompanyName: 'Premier Buy', 98 | Address: '7601 Penn Avenue South', 99 | City: 'Richfield', 100 | State: 'Minnesota', 101 | Zipcode: 55423, 102 | Phone: '(612) 291-1000', 103 | Fax: '(612) 291-2001', 104 | Website: 'http://www.nowebsitepremierbuy.com' 105 | }, { 106 | ID: 10, 107 | CompanyName: 'ElectrixMax', 108 | Address: '263 Shuman Blvd', 109 | City: 'Naperville', 110 | State: 'Illinois', 111 | Zipcode: 60563, 112 | Phone: '(630) 438-7800', 113 | Fax: '(630) 438-7801', 114 | Website: 'http://www.nowebsiteelectrixmax.com' 115 | }, { 116 | ID: 11, 117 | CompanyName: 'Video Emporium', 118 | Address: '1201 Elm Street', 119 | City: 'Dallas', 120 | State: 'Texas', 121 | Zipcode: 75270, 122 | Phone: '(214) 854-3000', 123 | Fax: '(214) 854-3001', 124 | Website: 'http://www.nowebsitevideoemporium.com' 125 | }, { 126 | ID: 12, 127 | CompanyName: 'Screen Shop', 128 | Address: '1000 Lowes Blvd', 129 | City: 'Mooresville', 130 | State: 'North Carolina', 131 | Zipcode: 28117, 132 | Phone: '(800) 445-6937', 133 | Fax: '(800) 445-6938', 134 | Website: 'http://www.nowebsitescreenshop.com' 135 | }, { 136 | ID: 13, 137 | CompanyName: 'Braeburn', 138 | Address: '1 Infinite Loop', 139 | City: 'Cupertino', 140 | State: 'California', 141 | Zipcode: 95014, 142 | Phone: '(408) 996-1010', 143 | Fax: '(408) 996-1012', 144 | Website: 'http://www.nowebsitebraeburn.com' 145 | }, { 146 | ID: 14, 147 | CompanyName: 'PriceCo', 148 | Address: '30 Hunter Lane', 149 | City: 'Camp Hill', 150 | State: 'Pennsylvania', 151 | Zipcode: 17011, 152 | Phone: '(717) 761-2633', 153 | Fax: '(717) 761-2334', 154 | Website: 'http://www.nowebsitepriceco.com' 155 | }, { 156 | ID: 15, 157 | CompanyName: 'Ultimate Gadget', 158 | Address: '1557 Watson Blvd', 159 | City: 'Warner Robbins', 160 | State: 'Georgia', 161 | Zipcode: 31093, 162 | Phone: '(995) 623-6785', 163 | Fax: '(995) 623-6786', 164 | Website: 'http://www.nowebsiteultimategadget.com' 165 | }, { 166 | ID: 16, 167 | CompanyName: 'EZ Stop', 168 | Address: '618 Michillinda Ave.', 169 | City: 'Arcadia', 170 | State: 'California', 171 | Zipcode: 91007, 172 | Phone: '(626) 265-8632', 173 | Fax: '(626) 265-8633', 174 | Website: 'http://www.nowebsiteezstop.com' 175 | }, { 176 | ID: 17, 177 | CompanyName: 'Clicker', 178 | Address: '1100 W. Artesia Blvd.', 179 | City: 'Compton', 180 | State: 'California', 181 | Zipcode: 90220, 182 | Phone: '(310) 884-9000', 183 | Fax: '(310) 884-9001', 184 | Website: 'http://www.nowebsiteclicker.com' 185 | }, { 186 | ID: 18, 187 | CompanyName: 'Store of America', 188 | Address: '2401 Utah Ave. South', 189 | City: 'Seattle', 190 | State: 'Washington', 191 | Zipcode: 98134, 192 | Phone: '(206) 447-1575', 193 | Fax: '(206) 447-1576', 194 | Website: 'http://www.nowebsiteamerica.com' 195 | }, { 196 | ID: 19, 197 | CompanyName: 'Zone Toys', 198 | Address: '1945 S Cienega Boulevard', 199 | City: 'Los Angeles', 200 | State: 'California', 201 | Zipcode: 90034, 202 | Phone: '(310) 237-5642', 203 | Fax: '(310) 237-5643', 204 | Website: 'http://www.nowebsitezonetoys.com' 205 | }, { 206 | ID: 20, 207 | CompanyName: 'ACME', 208 | Address: '2525 E El Segundo Blvd', 209 | City: 'El Segundo', 210 | State: 'California', 211 | Zipcode: 90245, 212 | Phone: '(310) 536-0611', 213 | Fax: '(310) 536-0612', 214 | Website: 'http://www.nowebsiteacme.com' 215 | }]; 216 | 217 | @Injectable() 218 | export class CustomerService { 219 | getCustomers() { 220 | return CUSTOMERS; 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /packages/sandbox/src/app/orange.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | export class Orange { 4 | day: string; 5 | oranges: number; 6 | } 7 | 8 | let ORANGES: Orange[] = [{ 9 | day: 'Monday', 10 | oranges: 3 11 | }, { 12 | day: 'Tuesday', 13 | oranges: 2 14 | }, { 15 | day: 'Wednesday', 16 | oranges: 3 17 | }, { 18 | day: 'Thursday', 19 | oranges: 4 20 | }, { 21 | day: 'Friday', 22 | oranges: 6 23 | }, { 24 | day: 'Saturday', 25 | oranges: 11 26 | }, { 27 | day: 'Sunday', 28 | oranges: 4 29 | }]; 30 | 31 | @Injectable() 32 | export class OrangeService { 33 | getOranges() { 34 | return ORANGES; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/sandbox/src/app/owner.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | export class Owner { 4 | text: string; 5 | id: number; 6 | color: string; 7 | } 8 | 9 | let Owners: Owner[] = [ 10 | { 11 | text: 'Samantha Bright', 12 | id: 1, 13 | color: '#cb6bb2' 14 | }, { 15 | text: 'John Heart', 16 | id: 2, 17 | color: '#56ca85' 18 | }, { 19 | text: 'Todd Hoffman', 20 | id: 3, 21 | color: '#1e90ff' 22 | }, { 23 | text: 'Sandra Johnson', 24 | id: 4, 25 | color: '#ff9747' 26 | } 27 | ]; 28 | 29 | @Injectable() 30 | export class OwnerService { 31 | getOwners() { 32 | return Owners; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/sandbox/src/browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # 5 | # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed 6 | 7 | > 0.5% 8 | last 2 versions 9 | Firefox ESR 10 | not dead 11 | not IE 9-11 -------------------------------------------------------------------------------- /packages/sandbox/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /packages/sandbox/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /packages/sandbox/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DevExtreme Angular Sandbox 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/sandbox/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | import config from 'devextreme/core/config'; 8 | config({ defaultCurrency: 'EUR' }); 9 | 10 | if (environment.production) { 11 | enableProdMode(); 12 | } 13 | 14 | platformBrowserDynamic().bootstrapModule(AppModule) 15 | .catch(err => console.error(err)); 16 | -------------------------------------------------------------------------------- /packages/sandbox/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10, IE11, and Chrome <55 requires all of the following polyfills. 22 | * This also includes Android Emulators with older versions of Chrome and Google Search/Googlebot 23 | */ 24 | 25 | // import 'core-js/es6/symbol'; 26 | // import 'core-js/es6/object'; 27 | // import 'core-js/es6/function'; 28 | // import 'core-js/es6/parse-int'; 29 | // import 'core-js/es6/parse-float'; 30 | // import 'core-js/es6/number'; 31 | // import 'core-js/es6/math'; 32 | // import 'core-js/es6/string'; 33 | // import 'core-js/es6/date'; 34 | // import 'core-js/es6/array'; 35 | // import 'core-js/es6/regexp'; 36 | // import 'core-js/es6/map'; 37 | // import 'core-js/es6/weak-map'; 38 | // import 'core-js/es6/set'; 39 | 40 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 41 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 42 | 43 | /** IE10 and IE11 requires the following for the Reflect API. */ 44 | // import 'core-js/es6/reflect'; 45 | 46 | /** 47 | * Web Animations `@angular/platform-browser/animations` 48 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 49 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 50 | */ 51 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 52 | 53 | /** 54 | * By default, zone.js will patch all possible macroTask and DomEvents 55 | * user can disable parts of macroTask/DomEvents patch by setting following flags 56 | * because those flags need to be set before `zone.js` being loaded, and webpack 57 | * will put import in the top of bundle, so user need to create a separate file 58 | * in this directory (for example: zone-flags.ts), and put the following flags 59 | * into that file, and then add the following code before importing zone.js. 60 | * import './zone-flags.ts'; 61 | * 62 | * The flags allowed in zone-flags.ts are listed here. 63 | * 64 | * The following flags will work for all browsers. 65 | * 66 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 67 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 68 | * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 69 | * 70 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 71 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 72 | * 73 | * (window as any).__Zone_enable_cross_context_check = true; 74 | * 75 | */ 76 | 77 | /*************************************************************************************************** 78 | * Zone JS is required by default for Angular itself. 79 | */ 80 | import 'zone.js/dist/zone'; // Included with Angular CLI. 81 | 82 | 83 | /*************************************************************************************************** 84 | * APPLICATION IMPORTS 85 | */ 86 | -------------------------------------------------------------------------------- /packages/sandbox/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "exclude": [ 8 | "test.ts", 9 | "**/*.spec.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/sandbox/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "module": "es2015", 9 | "moduleResolution": "node", 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "importHelpers": true, 13 | "target": "es5", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2018", 19 | "dom" 20 | ], 21 | "paths": { 22 | "devextreme-angular": ["../../node_modules/devextreme-angular/npm/dist"], 23 | "devextreme-angular/*": ["../../node_modules/devextreme-angular/npm/dist/*"] 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "target": "ES5", 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "emitDecoratorMetadata": true, 8 | "experimentalDecorators": true, 9 | "sourceMap": true, 10 | "inlineSources": true, 11 | "removeComments": false, 12 | "declaration": true, 13 | "noUnusedParameters": true, 14 | "noUnusedLocals": true, 15 | "lib": ["es2015", "es2015.iterable", "dom"], 16 | "paths": { 17 | "devextreme-angular": ["packages/devextreme-angular/src"], 18 | "devextreme-angular/*": ["packages/devextreme-angular/src/*"] 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "class-name": true, 7 | "comment-format": [ 8 | true, 9 | "check-space" 10 | ], 11 | "curly": true, 12 | "eofline": true, 13 | "indent": [ 14 | true, 15 | "spaces" 16 | ], 17 | "label-position": true, 18 | "max-line-length": [ 19 | true, 20 | 140 21 | ], 22 | "member-access": false, 23 | "member-ordering": [ 24 | true, 25 | "static-before-instance", 26 | "variables-before-functions" 27 | ], 28 | "no-arg": true, 29 | "no-bitwise": true, 30 | "no-console": [ 31 | true, 32 | "debug", 33 | "info", 34 | "time", 35 | "timeEnd", 36 | "trace" 37 | ], 38 | "no-construct": true, 39 | "no-debugger": true, 40 | "no-duplicate-variable": true, 41 | "no-empty": false, 42 | "no-eval": true, 43 | "no-inferrable-types": [true], 44 | "no-shadowed-variable": true, 45 | "no-string-literal": false, 46 | "no-switch-case-fall-through": true, 47 | "no-trailing-whitespace": true, 48 | "no-unused-expression": true, 49 | "no-use-before-declare": true, 50 | "no-var-keyword": true, 51 | "object-literal-sort-keys": false, 52 | "one-line": [ 53 | true, 54 | "check-open-brace", 55 | "check-catch", 56 | "check-else", 57 | "check-whitespace" 58 | ], 59 | "quotemark": [ 60 | true, 61 | "single" 62 | ], 63 | "radix": true, 64 | "semicolon": [ 65 | "always" 66 | ], 67 | "triple-equals": [ 68 | true, 69 | "allow-null-check" 70 | ], 71 | "typedef-whitespace": [ 72 | true, 73 | { 74 | "call-signature": "nospace", 75 | "index-signature": "nospace", 76 | "parameter": "nospace", 77 | "property-declaration": "nospace", 78 | "variable-declaration": "nospace" 79 | } 80 | ], 81 | "variable-name": false, 82 | "whitespace": [ 83 | true, 84 | "check-branch", 85 | "check-decl", 86 | "check-operator", 87 | "check-separator", 88 | "check-type" 89 | ], 90 | "directive-selector": [true, "attribute", ["dx"], "camelCase"], 91 | "component-selector": [true, "element", ["dxi", "dxo", "dx"], "kebab-case"], 92 | "no-inputs-metadata-property": true, 93 | "no-outputs-metadata-property": true, 94 | "no-host-metadata-property": true, 95 | "no-input-rename": true, 96 | "no-output-rename": true, 97 | "use-life-cycle-interface": true, 98 | "use-pipe-transform-interface": true, 99 | "directive-class-suffix": true, 100 | "import-destructuring-spacing": true 101 | } 102 | } 103 | --------------------------------------------------------------------------------