diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml
index 4983ea6a6f3..453c2f4c631 100644
--- a/.github/workflows/nodejs.yml
+++ b/.github/workflows/nodejs.yml
@@ -37,6 +37,7 @@ jobs:
npm run test:lib:grid
npm run test:lib:hgrid
npm run test:lib:tgrid
+ npm run test:styles
npm run test:schematics
env:
NODE_OPTIONS: --max_old_space_size=4096
diff --git a/.travis.yml b/.travis.yml
index 9203261226d..93d25c2665f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -24,6 +24,7 @@ script:
- if [ -z "${TRAVIS_TAG}" ]; then npm run test:lib:grid; fi
- if [ -z "${TRAVIS_TAG}" ]; then npm run test:lib:tgrid; fi
- if [ -z "${TRAVIS_TAG}" ]; then npm run test:lib:hgrid; fi
+- if [ -z "${TRAVIS_TAG}" ]; then npm run test:styles; fi
- if [ -z "${TRAVIS_TAG}" ]; then npm run test:schematics; fi
## Use only Github actions for publishing coveralls.io status
#- if [ -z "${TRAVIS_TAG}" ]; then istanbul-combine -d coverage -p none -r lcov -r html coverage/hierarchical-grid/coverage-final.json coverage/tree-grid/coverage-final.json coverage/non-grid/coverage-final.json coverage/grid/coverage-final.json; fi
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a1913931d18..dea33b64d44 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,8 @@ All notable changes for each version of this project will be documented in this
### New Features
- `IgxDropDown`
- The `igx-drop-down-item` now allows for `igxPrefix`, `igxSuffix` and `igx-divider` directives to be passed as `ng-content` and they will be renderer accordingly in the item's content.
+- `IgxGrid`
+ - Added support for exporting grouped data.
### General
- `IgxDialog`
- The dialog content has been moved inside the dialog window container in the template. This means that if you have added something in-between the opening and closing tags of the dialog, you may have to adjust its styling a bit since that content is now rendered inside a container that has padding on it.
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 841216f0f0c..00ed238c85f 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -38,6 +38,9 @@ steps:
env:
NODE_OPTIONS: "--max_old_space_size=4096"
+- script: npm run test:styles
+ displayName: 'Run styling library tests'
+
## Use only Github actions for publishing coveralls.io status
# - script: istanbul-combine -d coverage -p none -r lcov -r cobertura coverage/hierarchical-grid/coverage-final.json coverage/tree-grid/coverage-final.json coverage/non-grid/coverage-final.json coverage/grid/coverage-final.json
# displayName: 'Combine coverage results'
diff --git a/package-lock.json b/package-lock.json
index 8a4c2fb6fb4..b554bfbc4bd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4558,7 +4558,7 @@
},
"callsites": {
"version": "2.0.0",
- "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz",
+ "resolved": "http://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz",
"integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=",
"dev": true
},
@@ -5363,7 +5363,7 @@
"dependencies": {
"bluebird": {
"version": "2.11.0",
- "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz",
+ "resolved": "http://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz",
"integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=",
"dev": true
}
@@ -9550,7 +9550,7 @@
},
"kind-of": {
"version": "1.1.0",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz",
+ "resolved": "http://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz",
"integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=",
"dev": true
},
@@ -12642,6 +12642,18 @@
"lodash._root": "^3.0.0"
}
},
+ "lodash.find": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/lodash.find/-/lodash.find-4.6.0.tgz",
+ "integrity": "sha1-ywcE1Hq3F4n/oN6Ll92Sb7iLE7E=",
+ "dev": true
+ },
+ "lodash.foreach": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz",
+ "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=",
+ "dev": true
+ },
"lodash.identity": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/lodash.identity/-/lodash.identity-2.4.1.tgz",
@@ -12692,6 +12704,12 @@
"lodash.isarray": "^3.0.0"
}
},
+ "lodash.last": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/lodash.last/-/lodash.last-3.0.0.tgz",
+ "integrity": "sha1-JC9mMRLdTG5jcoxgo8kJ0b2tvUw=",
+ "dev": true
+ },
"lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@@ -13737,7 +13755,7 @@
},
"ncp": {
"version": "1.0.1",
- "resolved": "https://registry.npmjs.org/ncp/-/ncp-1.0.1.tgz",
+ "resolved": "http://registry.npmjs.org/ncp/-/ncp-1.0.1.tgz",
"integrity": "sha1-0VNn5cuHQyuhF9K/gP30Wuz7QkY=",
"dev": true
},
@@ -13940,7 +13958,7 @@
},
"semver": {
"version": "5.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
+ "resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
"integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
"dev": true
},
@@ -15436,7 +15454,7 @@
"dependencies": {
"ansi-colors": {
"version": "1.1.0",
- "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz",
+ "resolved": "http://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz",
"integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==",
"dev": true,
"requires": {
@@ -15522,7 +15540,7 @@
"dependencies": {
"async": {
"version": "1.5.2",
- "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
+ "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz",
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
"dev": true
}
@@ -17906,6 +17924,19 @@
}
}
},
+ "sass-true": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/sass-true/-/sass-true-5.0.0.tgz",
+ "integrity": "sha512-Q2HONu8QJcx8Lsdn8RqSHRGGB0aPlqCpXMDONtuXedaNIecz6QD3NCwkZiR4mprr5ZDiFcZwYT6fHDbyBXmDLQ==",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.4.2",
+ "css": "^2.2.1",
+ "lodash.find": "^4.6.0",
+ "lodash.foreach": "^4.5.0",
+ "lodash.last": "^3.0.0"
+ }
+ },
"sassdoc": {
"version": "2.7.3",
"resolved": "https://registry.npmjs.org/sassdoc/-/sassdoc-2.7.3.tgz",
@@ -18150,7 +18181,7 @@
},
"jsonfile": {
"version": "2.4.0",
- "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
+ "resolved": "http://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
"integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=",
"dev": true,
"requires": {
@@ -18798,7 +18829,7 @@
},
"engine.io-client": {
"version": "3.2.1",
- "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz",
+ "resolved": "http://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz",
"integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==",
"dev": true,
"requires": {
@@ -18882,7 +18913,7 @@
},
"socket.io-parser": {
"version": "3.2.0",
- "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz",
+ "resolved": "http://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz",
"integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==",
"dev": true,
"requires": {
@@ -20631,7 +20662,7 @@
},
"jsonfile": {
"version": "2.4.0",
- "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
+ "resolved": "http://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
"integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=",
"dev": true,
"requires": {
diff --git a/package.json b/package.json
index 8be2b28e22f..07ab9c05579 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,7 @@
"test:lib:azure:others": "ng test igniteui-angular --watch=false --no-progress --karma-config=./projects/igniteui-angular/karma.azure.non-grid.conf.js --tsConfig=./projects/igniteui-angular/tsconfig.ivy.false.spec.json",
"test:lib:watch": "ng test igniteui-angular",
"test:schematics": "ts-node --project projects/igniteui-angular/migrations/tsconfig.json ./node_modules/jasmine/bin/jasmine.js ./projects/igniteui-angular/migrations/**/*.spec.ts ./projects/igniteui-angular/schematics/**/*.spec.ts",
+ "test:styles": "ts-node --skip-project ./node_modules/jasmine/bin/jasmine.js ./projects/igniteui-angular/src/lib/core/styles/spec/tests.ts",
"build:lib": "ng build igniteui-angular --prod && gulp buildStyle",
"build:style": "gulp buildStyle",
"build:migration": "gulp copyMigrations && tsc --listEmittedFiles --project ./projects/igniteui-angular/migrations/tsconfig.json",
@@ -73,6 +74,11 @@
"devDependencies": {
"@angular-devkit/build-angular": "~0.1100.0",
"@angular-devkit/schematics": "^11.0.0",
+ "@angular-eslint/builder": "0.8.0-beta.5",
+ "@angular-eslint/eslint-plugin": "0.8.0-beta.5",
+ "@angular-eslint/eslint-plugin-template": "0.8.0-beta.5",
+ "@angular-eslint/schematics": "^0.8.0-beta.5",
+ "@angular-eslint/template-parser": "0.8.0-beta.5",
"@angular/cli": "~11.0.0",
"@angular/compiler-cli": "^11.0.0",
"@angular/language-service": "^11.0.0",
@@ -81,9 +87,15 @@
"@types/jasminewd2": "^2.0.8",
"@types/node": "^12.12.39",
"@types/webpack-env": "^1.15.2",
+ "@typescript-eslint/eslint-plugin": "4.3.0",
+ "@typescript-eslint/parser": "4.3.0",
"browser-sync": "^2.26.12",
"codelyzer": "^6.0.0",
"coveralls": "^3.1.0",
+ "eslint": "^7.6.0",
+ "eslint-plugin-import": "2.22.1",
+ "eslint-plugin-jsdoc": "30.7.6",
+ "eslint-plugin-prefer-arrow": "1.2.2",
"gulp": "^4.0.2",
"gulp-cached": "^1.1.1",
"gulp-concat": "^2.6.1",
@@ -112,6 +124,7 @@
"ng-packagr": "^11.0.3",
"pngcrush": "^2.0.1",
"protractor": "~7.0.0",
+ "sass-true": "^5.0.0",
"sassdoc": "^2.7.3",
"sassdoc-plugin-localization": "^1.4.1",
"stylelint": "^11.1.1",
@@ -122,17 +135,6 @@
"typedoc": "^0.17.7",
"typedoc-plugin-localization": "^2.2.1",
"typescript": "~4.0.2",
- "webpack-sources": "1.3.0",
- "eslint": "^7.6.0",
- "eslint-plugin-import": "2.22.1",
- "eslint-plugin-jsdoc": "30.7.6",
- "eslint-plugin-prefer-arrow": "1.2.2",
- "@angular-eslint/builder": "0.8.0-beta.5",
- "@angular-eslint/eslint-plugin": "0.8.0-beta.5",
- "@angular-eslint/eslint-plugin-template": "0.8.0-beta.5",
- "@angular-eslint/schematics": "^0.8.0-beta.5",
- "@angular-eslint/template-parser": "0.8.0-beta.5",
- "@typescript-eslint/eslint-plugin": "4.3.0",
- "@typescript-eslint/parser": "4.3.0"
+ "webpack-sources": "1.3.0"
}
}
diff --git a/projects/igniteui-angular/src/lib/carousel/carousel.component.html b/projects/igniteui-angular/src/lib/carousel/carousel.component.html
index 906322b8429..a6d02e485a2 100644
--- a/projects/igniteui-angular/src/lib/carousel/carousel.component.html
+++ b/projects/igniteui-angular/src/lib/carousel/carousel.component.html
@@ -21,30 +21,32 @@
-
-
+
- {{getCarouselLabel}}
+ {{getCarouselLabel}}
-
+
-
+
-
+
diff --git a/projects/igniteui-angular/src/lib/carousel/carousel.component.spec.ts b/projects/igniteui-angular/src/lib/carousel/carousel.component.spec.ts
index 9f40710d0f1..e09fc22f903 100644
--- a/projects/igniteui-angular/src/lib/carousel/carousel.component.spec.ts
+++ b/projects/igniteui-angular/src/lib/carousel/carousel.component.spec.ts
@@ -464,6 +464,41 @@ describe('Carousel', () => {
});
+ it('should apply correctly aria attributes to carousel component', () => {
+ const expectedRole = 'region';
+ const expectedRoleDescription = 'carousel';
+ const tabIndex = carousel.nativeElement.getAttribute('tabindex');
+
+ expect(tabIndex).toBeNull();
+ expect(carousel.nativeElement.getAttribute('role')).toEqual(expectedRole);
+ expect(carousel.nativeElement.getAttribute('aria-roledescription')).toEqual(expectedRoleDescription);
+
+ const indicators = carousel.nativeElement.querySelector(HelperTestFunctions.INDICATORS_BOTTOM_CLASS);
+
+ expect(indicators).toBeDefined();
+ expect(indicators.getAttribute('role')).toEqual('tablist');
+
+ const tabs = carousel.nativeElement.querySelectorAll('[role="tab"]');
+ expect(tabs.length).toEqual(4);
+ });
+
+ it('should apply correctly aria attributes to slide components', () => {
+ carousel.loop = false;
+ carousel.select(carousel.get(1));
+ fixture.detectChanges();
+
+ const expectedRole = 'tabpanel';
+ const slide = carousel.slides.find(s => s.active);
+ const tabIndex = slide.nativeElement.getAttribute('tabindex');
+
+ expect(+tabIndex).toBe(0);
+ expect(slide.nativeElement.getAttribute('role')).toEqual(expectedRole);
+
+ const tabs = carousel.nativeElement.querySelectorAll('[role="tab"]');
+ const slides = carousel.nativeElement.querySelectorAll('[role="tabpanel"]');
+
+ expect(slides.length).toEqual(tabs.length);
+ });
});
describe('Templates Tests: ', () => {
@@ -887,7 +922,7 @@ class HelperTestFunctions {
deltaY: 0,
duration: 100,
velocity,
- preventDefault: ( ( e: any ) => { })
+ preventDefault: ( () => { })
};
carouselElement.triggerEventHandler(event, panOptions);
@@ -1013,7 +1048,7 @@ class CarouselDynamicSlidesComponent {
this.addNewSlide();
}
- addNewSlide() {
+ public addNewSlide() {
this.slides.push(
{ text: 'Slide 1', active: false },
{ text: 'Slide 2', active: false },
diff --git a/projects/igniteui-angular/src/lib/carousel/carousel.component.ts b/projects/igniteui-angular/src/lib/carousel/carousel.component.ts
index 6122699400c..e4f086a0684 100644
--- a/projects/igniteui-angular/src/lib/carousel/carousel.component.ts
+++ b/projects/igniteui-angular/src/lib/carousel/carousel.component.ts
@@ -96,15 +96,6 @@ export class CarouselHammerConfig extends HammerGestureConfig {
})
export class IgxCarouselComponent implements OnDestroy, AfterContentInit {
- /**
- * Returns the `role` attribute of the carousel.
- * ```typescript
- * let carouselRole = this.carousel.role;
- * ```
- *
- * @memberof IgxCarouselComponent
- */
- @HostBinding('attr.role') public role = 'region';
/**
* Sets the `id` of the carousel.
@@ -118,29 +109,24 @@ export class IgxCarouselComponent implements OnDestroy, AfterContentInit {
@HostBinding('attr.id')
@Input()
public id = `igx-carousel-${NEXT_ID++}`;
-
/**
- * Returns the `aria-label` of the carousel.
- *
- * ```typescript
- * let carousel = this.carousel.ariaLabel;
- * ```
- *
- */
- @HostBinding('attr.aria-label')
- public ariaLabel = 'carousel';
-
- /**
- * Returns the `tabIndex` of the carousel component.
+ * Returns the `role` attribute of the carousel.
* ```typescript
- * let tabIndex = this.carousel.tabIndex;
+ * let carouselRole = this.carousel.role;
* ```
*
* @memberof IgxCarouselComponent
*/
- @HostBinding('attr.tabindex')
- get tabIndex() {
- return 0;
+ @HostBinding('attr.role') public role = 'region';
+
+ /** @hidden */
+ @HostBinding('attr.aria-roledescription')
+ public roleDescription = 'carousel';
+
+ /** @hidden */
+ @HostBinding('attr.aria-labelledby')
+ public get labelId() {
+ return this.showIndicatorsLabel ? `${this.id}-label` : null;
}
/**
@@ -161,7 +147,7 @@ export class IgxCarouselComponent implements OnDestroy, AfterContentInit {
* ```
*/
@HostBinding('style.touch-action')
- get touchAction() {
+ public get touchAction() {
return this.gesturesSupport ? 'pan-y' : 'auto';
}
@@ -398,11 +384,15 @@ export class IgxCarouselComponent implements OnDestroy, AfterContentInit {
@ViewChild('defaultPrevButton', { read: TemplateRef, static: true })
private defaultPrevButton: TemplateRef
;
+ /**
+ * @hidden
+ * @internal
+ */
+ public stoppedByInteraction: boolean;
private _interval: number;
private _resourceStrings = CurrentResourceStrings.CarouselResStrings;
private lastInterval: any;
private playing: boolean;
- private stoppedByInteraction: boolean;
private destroyed: boolean;
private destroy$ = new Subject();
private differ: IterableDiffer | null = null;
@@ -420,14 +410,14 @@ export class IgxCarouselComponent implements OnDestroy, AfterContentInit {
* By default it uses EN resources.
*/
@Input()
- set resourceStrings(value: ICarouselResourceStrings) {
+ public set resourceStrings(value: ICarouselResourceStrings) {
this._resourceStrings = Object.assign({}, this._resourceStrings, value);
}
/**
* An accessor that returns the resource strings.
*/
- get resourceStrings(): ICarouselResourceStrings {
+ public get resourceStrings(): ICarouselResourceStrings {
return this._resourceStrings;
}
@@ -484,7 +474,7 @@ export class IgxCarouselComponent implements OnDestroy, AfterContentInit {
* @memberOf IgxCarouselComponent
*/
public get total(): number {
- return this.slides.length;
+ return this.slides?.length;
}
/**
@@ -530,7 +520,7 @@ export class IgxCarouselComponent implements OnDestroy, AfterContentInit {
*
* @memberof IgxCarouselComponent
*/
- get nativeElement(): any {
+ public get nativeElement(): any {
return this.element.nativeElement;
}
@@ -543,7 +533,7 @@ export class IgxCarouselComponent implements OnDestroy, AfterContentInit {
* @memberof IgxCarouselComponent
*/
@Input()
- get interval(): number {
+ public get interval(): number {
return this._interval;
}
@@ -556,7 +546,7 @@ export class IgxCarouselComponent implements OnDestroy, AfterContentInit {
*
* @memberof IgxCarouselComponent
*/
- set interval(value: number) {
+ public set interval(value: number) {
this._interval = +value;
this.restartInterval();
}
@@ -566,13 +556,14 @@ export class IgxCarouselComponent implements OnDestroy, AfterContentInit {
this.differ = this.iterableDiffers.find([]).create(null);
}
+
/** @hidden */
@HostListener('keydown.arrowright', ['$event'])
public onKeydownArrowRight(event) {
if (this.keyboardSupport) {
event.preventDefault();
this.next();
- requestAnimationFrame(() => this.nativeElement.focus());
+ requestAnimationFrame(() => this.slides.find(s => s.active).nativeElement.focus());
}
}
@@ -582,7 +573,7 @@ export class IgxCarouselComponent implements OnDestroy, AfterContentInit {
if (this.keyboardSupport) {
event.preventDefault();
this.prev();
- requestAnimationFrame(() => this.nativeElement.focus());
+ requestAnimationFrame(() => this.slides.find(s => s.active).nativeElement.focus());
}
}
@@ -608,7 +599,7 @@ export class IgxCarouselComponent implements OnDestroy, AfterContentInit {
if (this.keyboardSupport && this.slides.length > 0) {
event.preventDefault();
this.slides.first.active = true;
- requestAnimationFrame(() => this.nativeElement.focus());
+ requestAnimationFrame(() => this.slides.find(s => s.active).nativeElement.focus());
}
}
@@ -618,7 +609,7 @@ export class IgxCarouselComponent implements OnDestroy, AfterContentInit {
if (this.keyboardSupport && this.slides.length > 0) {
event.preventDefault();
this.slides.last.active = true;
- requestAnimationFrame(() => this.nativeElement.focus());
+ requestAnimationFrame(() => this.slides.find(s => s.active).nativeElement.focus());
}
}
@@ -713,11 +704,6 @@ export class IgxCarouselComponent implements OnDestroy, AfterContentInit {
}
}
- /** @hidden */
- public setAriaLabel(slide) {
- return `Item ${slide.index + 1} of ${this.total}`;
- }
-
/**
* Returns the slide corresponding to the provided `index` or null.
* ```typescript
@@ -1104,6 +1090,7 @@ export class IgxCarouselComponent implements OnDestroy, AfterContentInit {
this.slides.reduce((any, c, ind) => c.index = ind, 0); // reset slides indexes
diff.forEachAddedItem((record: IterableChangeRecord) => {
const slide = record.item;
+ slide.total = this.total;
this.onSlideAdded.emit({ carousel: this, slide });
if (slide.active) {
this.currentSlide = slide;
diff --git a/projects/igniteui-angular/src/lib/carousel/carousel.directives.ts b/projects/igniteui-angular/src/lib/carousel/carousel.directives.ts
index ea26c4a5e93..366d6fcc03e 100644
--- a/projects/igniteui-angular/src/lib/carousel/carousel.directives.ts
+++ b/projects/igniteui-angular/src/lib/carousel/carousel.directives.ts
@@ -1,4 +1,4 @@
-import { Directive, TemplateRef } from '@angular/core';
+import { Directive } from '@angular/core';
@Directive({
selector: '[igxCarouselIndicator]'
diff --git a/projects/igniteui-angular/src/lib/carousel/slide.component.ts b/projects/igniteui-angular/src/lib/carousel/slide.component.ts
index 2747627e76b..dc8ef6d28b2 100644
--- a/projects/igniteui-angular/src/lib/carousel/slide.component.ts
+++ b/projects/igniteui-angular/src/lib/carousel/slide.component.ts
@@ -1,4 +1,4 @@
-import { Component, OnDestroy, Input, HostBinding, Output, EventEmitter, ElementRef, ChangeDetectorRef } from '@angular/core';
+import { Component, OnDestroy, Input, HostBinding, Output, EventEmitter, ElementRef, AfterContentChecked } from '@angular/core';
import { Subject } from 'rxjs';
export enum Direction { NONE, NEXT, PREV }
@@ -20,7 +20,7 @@ export enum Direction { NONE, NEXT, PREV }
templateUrl: 'slide.component.html'
})
-export class IgxSlideComponent implements OnDestroy {
+export class IgxSlideComponent implements AfterContentChecked, OnDestroy {
/**
* Gets/sets the `index` of the slide inside the carousel.
* ```html
@@ -45,6 +45,9 @@ export class IgxSlideComponent implements OnDestroy {
*/
@Input() public direction: Direction;
+ @Input()
+ public total: number;
+
/**
* Returns the `tabIndex` of the slide component.
* ```typescript
@@ -54,35 +57,28 @@ export class IgxSlideComponent implements OnDestroy {
* @memberof IgxSlideComponent
*/
@HostBinding('attr.tabindex')
- get tabIndex() {
+ public get tabIndex() {
return this.active ? 0 : null;
}
/**
- * Returns the `aria-selected` of the slide.
- *
- * ```typescript
- * let slide = this.slide.ariaSelected;
- * ```
- *
+ * @hidden
*/
- @HostBinding('attr.aria-selected')
- public get ariaSelected(): boolean {
- return this.active;
- }
+ @HostBinding('attr.id')
+ public id: string;
/**
- * Returns the `aria-live` of the slide.
- *
- * ```typescript
- * let slide = this.slide.ariaLive;
- * ```
+ * Returns the `role` of the slide component.
+ * By default is set to `tabpanel`
*
+ * @memberof IgxSlideComponent
*/
- @HostBinding('attr.aria-selected')
- public get ariaLive() {
- return this.active ? 'polite' : null;
- }
+ @HostBinding('attr.role')
+ public tab = 'tabpanel';
+
+ /** @hidden */
+ @HostBinding('attr.aria-labelledby')
+ public ariaLabelledBy;
/**
* Returns the class of the slide component.
@@ -155,6 +151,11 @@ export class IgxSlideComponent implements OnDestroy {
return this._destroy$;
}
+ public ngAfterContentChecked() {
+ this.id = `panel-${this.index}`;
+ this.ariaLabelledBy = `tab-${this.index}-${this.total}`;
+ }
+
/**
* @hidden
*/
diff --git a/projects/igniteui-angular/src/lib/core/i18n/carousel-resources.ts b/projects/igniteui-angular/src/lib/core/i18n/carousel-resources.ts
index 3ddc7899040..4745b4ecc88 100644
--- a/projects/igniteui-angular/src/lib/core/i18n/carousel-resources.ts
+++ b/projects/igniteui-angular/src/lib/core/i18n/carousel-resources.ts
@@ -1,7 +1,13 @@
export interface ICarouselResourceStrings {
igx_carousel_of?: string;
+ igx_carousel_slide?: string;
+ igx_carousel_previous_slide?: string;
+ igx_carousel_next_slide?: string;
}
export const CarouselResourceStringsEN: ICarouselResourceStrings = {
- igx_carousel_of: 'of'
+ igx_carousel_of: 'of',
+ igx_carousel_slide: 'slide',
+ igx_carousel_previous_slide: 'previous slide',
+ igx_carousel_next_slide: 'next slide'
};
diff --git a/projects/igniteui-angular/src/lib/core/styles/spec/_functions.spec.scss b/projects/igniteui-angular/src/lib/core/styles/spec/_functions.spec.scss
new file mode 100644
index 00000000000..20f39477b46
--- /dev/null
+++ b/projects/igniteui-angular/src/lib/core/styles/spec/_functions.spec.scss
@@ -0,0 +1,51 @@
+@include describe('Unit conversions') {
+ @include it('should convert pixel numbers to rem') {
+ @include assert-equal(rem(16px, 16px), 1rem);
+ }
+
+ @include it('should convert unitless numbers to rem') {
+ @include assert-equal(rem(16, 16), 1rem);
+ }
+
+ @include it('should convert pixel numbers to em') {
+ @include assert-equal(em(16px, 16px), 1em);
+ }
+
+ @include it('should convert unitless numbers to em') {
+ @include assert-equal(em(16px, 16px), 1em);
+ }
+
+ @include it('should convert relative unit numbers to pixels') {
+ @include assert-equal(px(1em, 16px), 16px);
+ @include assert-equal(px(1rem, 16px), 16px);
+ }
+
+ @include it('should convert unitless numbers to pixels') {
+ @include assert-equal(px(1, 16), 16px);
+ }
+}
+
+@include describe('Luminance') {
+ @include it('should return a number') {
+ @include assert-true(type-of(luminance(blue)), 'number');
+ }
+
+ @include it('should retrieve luminance for a given color value') {
+ @include assert-equal(luminance(red), .2126);
+ }
+
+ @include it('should pass through non-color values') {
+ @include assert-equal(luminance('bozo'), 'bozo');
+ }
+}
+
+@include describe('Contrast') {
+ @include it('should return a number') {
+ @include assert-true(type-of(contrast(white, black)), 'number');
+ }
+
+ @include it('should return the contrast ratio between two colors') {
+ @include assert-equal(contrast(white, black), 21);
+ @include assert-equal(round(contrast(#09f, black)), 7);
+ }
+}
diff --git a/projects/igniteui-angular/src/lib/core/styles/spec/_index.scss b/projects/igniteui-angular/src/lib/core/styles/spec/_index.scss
new file mode 100644
index 00000000000..3df95537655
--- /dev/null
+++ b/projects/igniteui-angular/src/lib/core/styles/spec/_index.scss
@@ -0,0 +1,5 @@
+@import 'node_modules/sass-true/sass/true';
+@import '../base/index';
+
+@import 'functions.spec';
+
diff --git a/projects/igniteui-angular/src/lib/core/styles/spec/tests.ts b/projects/igniteui-angular/src/lib/core/styles/spec/tests.ts
new file mode 100644
index 00000000000..b311df0c588
--- /dev/null
+++ b/projects/igniteui-angular/src/lib/core/styles/spec/tests.ts
@@ -0,0 +1,5 @@
+import * as path from 'path';
+import * as sassTrue from 'sass-true';
+
+const file = path.join(__dirname, '_index.scss');
+sassTrue.runSass({ file }, { describe, it });
diff --git a/projects/igniteui-angular/src/lib/expansion-panel/expansion-panel.component.ts b/projects/igniteui-angular/src/lib/expansion-panel/expansion-panel.component.ts
index bfe08c815e7..6324a084c8e 100644
--- a/projects/igniteui-angular/src/lib/expansion-panel/expansion-panel.component.ts
+++ b/projects/igniteui-angular/src/lib/expansion-panel/expansion-panel.component.ts
@@ -196,6 +196,7 @@ export class IgxExpansionPanelComponent implements IgxExpansionPanelBase, AfterC
() => {
this.onCollapsed.emit({ event: evt, panel: this, owner: this });
this.collapsed = true;
+ this.cdr.markForCheck();
}
);
}
diff --git a/projects/igniteui-angular/src/lib/services/csv/csv-exporter.ts b/projects/igniteui-angular/src/lib/services/csv/csv-exporter.ts
index de7d505abbb..6655b104c28 100644
--- a/projects/igniteui-angular/src/lib/services/csv/csv-exporter.ts
+++ b/projects/igniteui-angular/src/lib/services/csv/csv-exporter.ts
@@ -1,5 +1,5 @@
import { EventEmitter, Injectable, Output } from '@angular/core';
-import { IgxBaseExporter } from '../exporter-common/base-export-service';
+import { IExportRecord, IgxBaseExporter } from '../exporter-common/base-export-service';
import { ExportUtilities } from '../exporter-common/export-utilities';
import { CharSeparatedValueData } from './char-separated-value-data';
import { CsvFileTypes, IgxCsvExporterOptions } from './csv-exporter-options';
@@ -48,8 +48,8 @@ export class IgxCsvExporterService extends IgxBaseExporter {
private _stringData: string;
- protected exportDataImplementation(data: any[], options: IgxCsvExporterOptions) {
- data = data.map((item) => item.rowData);
+ protected exportDataImplementation(data: IExportRecord[], options: IgxCsvExporterOptions) {
+ data = data.map((item) => item.data);
const csvData = new CharSeparatedValueData(data, options.valueDelimiter);
csvData.prepareDataAsync((r) => {
this._stringData = r;
diff --git a/projects/igniteui-angular/src/lib/services/excel/excel-exporter-grid.spec.ts b/projects/igniteui-angular/src/lib/services/excel/excel-exporter-grid.spec.ts
index 8ce1c764b34..0c49ed04c82 100644
--- a/projects/igniteui-angular/src/lib/services/excel/excel-exporter-grid.spec.ts
+++ b/projects/igniteui-angular/src/lib/services/excel/excel-exporter-grid.spec.ts
@@ -14,7 +14,8 @@ import {
GridIDNameJobTitleComponent,
ProductsComponent,
GridIDNameJobTitleHireDataPerformanceComponent,
- GridHireDateComponent
+ GridHireDateComponent,
+ GridExportGroupedDataComponent
} from '../../test-utils/grid-samples.spec';
import { SampleTestData } from '../../test-utils/sample-test-data.spec';
import { first } from 'rxjs/operators';
@@ -43,9 +44,10 @@ describe('Excel Exporter', () => {
GridIDNameJobTitleComponent,
IgxTreeGridPrimaryForeignKeyComponent,
ProductsComponent,
- GridWithEmtpyColumnsComponent,
+ GridWithEmptyColumnsComponent,
GridIDNameJobTitleHireDataPerformanceComponent,
- GridHireDateComponent
+ GridHireDateComponent,
+ GridExportGroupedDataComponent
],
imports: [IgxGridModule, IgxTreeGridModule, NoopAnimationsModule]
}).compileComponents();
@@ -256,7 +258,7 @@ describe('Excel Exporter', () => {
wrapper = await getExportedData(grid, options);
await wrapper.verifyDataFilesContent(
- actualData.simpleGridSortByNameDesc(true), 'Descending sorted data should have been exported.');
+ actualData.simpleGridSortByNameDesc(), 'Descending sorted data should have been exported.');
grid.clearSort();
grid.sort({fieldName: 'ID', dir: SortingDirection.Asc, ignoreCase: true, strategy: DefaultSortingStrategy.instance()});
@@ -458,7 +460,7 @@ describe('Excel Exporter', () => {
});
it('should export columns without header', async () => {
- const fix = TestBed.createComponent(GridWithEmtpyColumnsComponent);
+ const fix = TestBed.createComponent(GridWithEmptyColumnsComponent);
fix.detectChanges();
await wait();
@@ -539,6 +541,97 @@ describe('Excel Exporter', () => {
await exportAndVerify(grid, options, actualData.hireDate);
});
+
+ it('Should export grouped grid', async () => {
+ const fix = TestBed.createComponent(GridExportGroupedDataComponent);
+ fix.detectChanges();
+ await wait();
+
+ const grid = fix.componentInstance.grid;
+
+ grid.groupBy({ fieldName: 'Brand', dir: SortingDirection.Asc, ignoreCase: false });
+ grid.groupBy({ fieldName: 'Price', dir: SortingDirection.Desc, ignoreCase: false });
+
+ fix.detectChanges();
+
+ await exportAndVerify(grid, options, actualData.exportGroupedData);
+ });
+
+ it('Should export grouped grid with collapsed rows', async () => {
+ const fix = TestBed.createComponent(GridExportGroupedDataComponent);
+ fix.detectChanges();
+ await wait();
+
+ const grid = fix.componentInstance.grid;
+
+ grid.groupBy({ fieldName: 'Brand', dir: SortingDirection.Asc, ignoreCase: false });
+ grid.groupBy({ fieldName: 'Price', dir: SortingDirection.Desc, ignoreCase: false });
+
+ fix.detectChanges();
+
+ const groupRows = grid.groupsRowList.toArray();
+
+ grid.toggleGroup(groupRows[2].groupRow);
+ grid.toggleGroup(groupRows[3].groupRow);
+ grid.toggleGroup(groupRows[6].groupRow);
+
+ fix.detectChanges();
+
+
+ await exportAndVerify(grid, options, actualData.exportGroupedDataWithCollapsedRows);
+ });
+
+ it('Should export grouped grid with ignored sorting', async () => {
+ const fix = TestBed.createComponent(GridExportGroupedDataComponent);
+ fix.detectChanges();
+ await wait();
+
+ const grid = fix.componentInstance.grid;
+
+ grid.groupBy({ fieldName: 'Brand', dir: SortingDirection.Asc, ignoreCase: false });
+ grid.sort({fieldName: 'Price', dir: SortingDirection.Desc});
+
+ options.ignoreSorting = true;
+
+ fix.detectChanges();
+
+ await exportAndVerify(grid, options, actualData.exportGroupedDataWithIgnoreSorting);
+ });
+
+ it('Should export grouped grid with ignored filtering', async () => {
+ const fix = TestBed.createComponent(GridExportGroupedDataComponent);
+ fix.detectChanges();
+ await wait();
+
+ const grid = fix.componentInstance.grid;
+
+ grid.groupBy({ fieldName: 'Brand', dir: SortingDirection.Asc, ignoreCase: false });
+
+ grid.filter('Model', 'Model', IgxStringFilteringOperand.instance().condition('contains'), true);
+ fix.detectChanges();
+
+ options.ignoreFiltering = true;
+
+ fix.detectChanges();
+
+ await exportAndVerify(grid, options, actualData.exportGroupedDataWithIgnoreFiltering);
+ });
+
+ it('Should export grouped grid with ignored grouping', async () => {
+ const fix = TestBed.createComponent(GridExportGroupedDataComponent);
+ fix.detectChanges();
+ await wait();
+
+ const grid = fix.componentInstance.grid;
+
+ grid.groupBy({ fieldName: 'Brand', dir: SortingDirection.Asc, ignoreCase: false });
+
+ options.ignoreGrouping = true;
+
+ fix.detectChanges();
+
+ await exportAndVerify(grid, options, actualData.exportGroupedDataWithIgnoreGrouping);
+ });
});
describe('', () => {
@@ -607,6 +700,22 @@ describe('Excel Exporter', () => {
await exportAndVerify(treeGrid, options, actualData.treeGridDataExpDepth(0));
});
+ it('should export tree grid with ignore filtering properly.', async () => {
+ treeGrid.filter('Age', 52, IgxNumberFilteringOperand.instance().condition('greaterThan'));
+ options.ignoreFiltering = true;
+ fix.detectChanges();
+
+ await exportAndVerify(treeGrid, options, actualData.treeGridDataIgnoreFiltering);
+ });
+
+ it('should export tree grid with ignore sorting properly.', async () => {
+ treeGrid.sort({fieldName: 'Age', dir: SortingDirection.Desc});
+ options.ignoreSorting = true;
+ fix.detectChanges();
+
+ await exportAndVerify(treeGrid, options, actualData.treeGridData);
+ });
+
it('should throw an exception when nesting level is greater than 8.', async () => {
const nestedData = SampleTestData.employeePrimaryForeignKeyTreeData();
for (let i = 1; i < 9; i++) {
@@ -769,7 +878,7 @@ describe('Excel Exporter', () => {
`
})
-export class GridWithEmtpyColumnsComponent {
+export class GridWithEmptyColumnsComponent {
@ViewChild('grid1', { static: true }) public grid: IgxGridComponent;
public data = SampleTestData.personJobDataFull();
diff --git a/projects/igniteui-angular/src/lib/services/excel/excel-exporter.ts b/projects/igniteui-angular/src/lib/services/excel/excel-exporter.ts
index d92d41e68e3..4f483fab688 100644
--- a/projects/igniteui-angular/src/lib/services/excel/excel-exporter.ts
+++ b/projects/igniteui-angular/src/lib/services/excel/excel-exporter.ts
@@ -5,7 +5,7 @@ import { ExcelElementsFactory } from './excel-elements-factory';
import { ExcelFolderTypes } from './excel-enums';
import { IgxExcelExporterOptions } from './excel-exporter-options';
import { IExcelFolder } from './excel-interfaces';
-import { IgxBaseExporter } from '../exporter-common/base-export-service';
+import { IExportRecord, IgxBaseExporter } from '../exporter-common/base-export-service';
import { ExportUtilities } from '../exporter-common/export-utilities';
import { WorksheetData } from './worksheet-data';
import { IBaseEventArgs } from '../../core/utils';
@@ -72,19 +72,22 @@ export class IgxExcelExporterService extends IgxBaseExporter {
}
}
- protected exportDataImplementation(data: any[], options: IgxExcelExporterOptions): void {
- if (this._isTreeGrid) {
+ protected exportDataImplementation(data: IExportRecord[], options: IgxExcelExporterOptions): void {
+ const level = data[0]?.level;
+
+ if (typeof level !== 'undefined') {
let maxLevel = 0;
+
data.forEach((r) => {
- maxLevel = Math.max(maxLevel, r.originalRowData.level);
+ maxLevel = Math.max(maxLevel, r.level);
});
+
if (maxLevel > 7) {
throw Error('Can create an outline of up to eight levels!');
}
}
- const worksheetData =
- new WorksheetData(data, this.columnWidthList, options, this._indexOfLastPinnedColumn, this._sort, this._isTreeGrid);
+ const worksheetData = new WorksheetData(data, this.columnWidthList, options, this._indexOfLastPinnedColumn, this._sort);
this._xlsx = new JSZip();
diff --git a/projects/igniteui-angular/src/lib/services/excel/excel-files.ts b/projects/igniteui-angular/src/lib/services/excel/excel-files.ts
index 75dd60397c7..e5bbf14f3d1 100644
--- a/projects/igniteui-angular/src/lib/services/excel/excel-files.ts
+++ b/projects/igniteui-angular/src/lib/services/excel/excel-files.ts
@@ -61,83 +61,7 @@ export class WorksheetFile implements IExcelFile {
private freezePane = '';
private rowHeight = '';
- public writeElement(folder: JSZip, worksheetData: WorksheetData) {
- const sheetData = [];
- const cols = [];
- let dimension: string;
- const dictionary = worksheetData.dataDictionary;
- let freezePane = '';
- let maxOutlineLevel = 0;
-
- if (worksheetData.isEmpty) {
- sheetData.push('');
- dimension = 'A1';
- } else {
- sheetData.push('');
- const height = worksheetData.options.rowHeight;
- const rowHeight = height ? ' ht="' + height + '" customHeight="1"' : '';
-
- sheetData.push(``);
- for (let i = 0; i < worksheetData.columnCount; i++) {
- const column = ExcelStrings.getExcelColumn(i) + 1;
- const value = dictionary.saveValue(worksheetData.keys[i], i, true);
- sheetData.push(`${value}`);
- }
- sheetData.push('
');
-
- for (let i = 1; i < worksheetData.rowCount; i++) {
- if (!worksheetData.isTreeGridData) {
- sheetData.push(``);
- } else {
- const rowData = worksheetData.data[i - 1].originalRowData;
- const sCollapsed = (!rowData.expanded) ? '' : (rowData.expanded === true) ? '' : ` collapsed="1"`;
- const sHidden = (rowData.parent && this.hasCollapsedParent(rowData)) ? ` hidden="1"` : '';
- const rowOutlineLevel = rowData.level ? rowData.level : 0;
- const sOutlineLevel = rowOutlineLevel > 0 ? ` outlineLevel="${rowOutlineLevel}"` : '';
- maxOutlineLevel = maxOutlineLevel < rowOutlineLevel ? rowOutlineLevel : maxOutlineLevel;
-
- sheetData.push(``);
- }
- for (let j = 0; j < worksheetData.columnCount; j++) {
- const cellData = WorksheetFile.getCellData(worksheetData, i, j);
- sheetData.push(cellData);
- }
- sheetData.push('
');
- }
- sheetData.push('
');
- dimension = 'A1:' + ExcelStrings.getExcelColumn(worksheetData.columnCount - 1) + worksheetData.rowCount;
-
- cols.push('');
-
- for (let i = 0; i < worksheetData.columnCount; i++) {
- const width = dictionary.columnWidths[i];
- // Use the width provided in the options if it exists
- let widthInTwips = worksheetData.options.columnWidth !== undefined ?
- worksheetData.options.columnWidth :
- Math.max(((width / 96) * 14.4), WorksheetFile.MIN_WIDTH);
- if (!(widthInTwips > 0)) {
- widthInTwips = WorksheetFile.MIN_WIDTH;
- }
-
- cols.push(``);
- }
-
- cols.push('');
-
- if (worksheetData.indexOfLastPinnedColumn !== -1 &&
- !worksheetData.options.ignorePinning &&
- !worksheetData.options.ignoreColumnsOrder) {
- const frozenColumnCount = worksheetData.indexOfLastPinnedColumn + 1;
- const firstCell = ExcelStrings.getExcelColumn(frozenColumnCount) + '1';
- freezePane = ``;
- }
- }
- const hasTable = !worksheetData.isEmpty && worksheetData.options.exportAsTable;
-
- folder.file('sheet1.xml',
- ExcelStrings.getSheetXML(dimension, freezePane, cols.join(''), sheetData.join(''), hasTable,
- worksheetData.isTreeGridData, maxOutlineLevel));
- }
+ public writeElement(folder: JSZip, worksheetData: WorksheetData) {}
public async writeElementAsync(folder: JSZip, worksheetData: WorksheetData) {
return new Promise(resolve => {
@@ -145,7 +69,7 @@ export class WorksheetFile implements IExcelFile {
const hasTable = !worksheetData.isEmpty && worksheetData.options.exportAsTable;
folder.file('sheet1.xml', ExcelStrings.getSheetXML(
- this.dimension, this.freezePane, cols, rows, hasTable, worksheetData.isTreeGridData, this.maxOutlineLevel));
+ this.dimension, this.freezePane, cols, rows, hasTable, this.maxOutlineLevel));
resolve();
});
});
@@ -223,45 +147,35 @@ export class WorksheetFile implements IExcelFile {
private processRow(worksheetData: WorksheetData, i: number) {
const rowData = new Array(worksheetData.columnCount + 2);
- if (!worksheetData.isTreeGridData) {
- rowData[0] = ``;
- } else {
- const originalData = worksheetData.data[i - 1].originalRowData;
- const sCollapsed = (!originalData.expanded) ? '' : (originalData.expanded === true) ? '' : ` collapsed="1"`;
- const sHidden = (originalData.parent && this.hasCollapsedParent(originalData)) ? ` hidden="1"` : '';
- const rowOutlineLevel = originalData.level ? originalData.level : 0;
- const sOutlineLevel = rowOutlineLevel > 0 ? ` outlineLevel="${rowOutlineLevel}"` : '';
- this.maxOutlineLevel = this.maxOutlineLevel < rowOutlineLevel ? rowOutlineLevel : this.maxOutlineLevel;
- rowData[0] = ``;
- }
+ const record = worksheetData.data[i - 1];
+ const sHidden = record.hidden ? ` hidden="1"` : '';
+ const rowLevel = record.level;
+ const outlineLevel = rowLevel > 0 ? ` outlineLevel="${rowLevel}"` : '';
+
+ this.maxOutlineLevel = this.maxOutlineLevel < rowLevel ? rowLevel : this.maxOutlineLevel;
+
+ rowData[0] = ``;
for (let j = 0; j < worksheetData.columnCount; j++) {
const cellData = WorksheetFile.getCellData(worksheetData, i, j);
rowData[j + 1] = cellData;
}
+
rowData[worksheetData.columnCount + 1] = '
';
return rowData.join('');
}
- private hasCollapsedParent(rowData) {
- let result = !rowData.parent.expanded;
- while (rowData.parent) {
- result = result || !rowData.parent.expanded;
- rowData = rowData.parent;
- }
-
- return result;
- }
/* eslint-disable @typescript-eslint/member-ordering */
private static getCellData(worksheetData: WorksheetData, row: number, column: number): string {
const dictionary = worksheetData.dataDictionary;
const columnName = ExcelStrings.getExcelColumn(column) + (row + 1);
const columnHeader = worksheetData.keys[column];
+ const fullRow = worksheetData.data[row - 1];
- const rowData = worksheetData.data[row - 1].rowData;
-
- const cellValue = worksheetData.isSpecialData ? rowData : rowData[columnHeader];
+ const cellValue = worksheetData.isSpecialData ?
+ fullRow.data :
+ fullRow.data[columnHeader];
if (cellValue === undefined || cellValue === null) {
return ``;
diff --git a/projects/igniteui-angular/src/lib/services/excel/excel-strings.ts b/projects/igniteui-angular/src/lib/services/excel/excel-strings.ts
index 93bd9bc1d30..33345512ea2 100644
--- a/projects/igniteui-angular/src/lib/services/excel/excel-strings.ts
+++ b/projects/igniteui-angular/src/lib/services/excel/excel-strings.ts
@@ -51,10 +51,11 @@ export class ExcelStrings {
return retVal;
}
- public static getSheetXML(dimension: string, freezePane: string, cols: string, sheetData: string, hasTable: boolean, hasGroupedRows = false, outlineLevel = 0): string {
+ public static getSheetXML(dimension: string, freezePane: string, cols: string, sheetData: string, hasTable: boolean, outlineLevel = 0): string {
+ const hasOutline = outlineLevel > 0;
const tableParts = hasTable ? '' : '';
- const sheetOutlineProp = hasGroupedRows ? '' : '';
- const sOutlineLevel = outlineLevel > 0 ? `outlineLevelRow="${outlineLevel}"` : '';
+ const sheetOutlineProp = hasOutline ? '' : '';
+ const sOutlineLevel = hasOutline ? `outlineLevelRow="${outlineLevel}"` : '';
// return ExcelStrings.XML_STRING +
// '' + freezePane + '' + cols + sheetData + '' + tableParts + '';
diff --git a/projects/igniteui-angular/src/lib/services/excel/test-data.service.spec.ts b/projects/igniteui-angular/src/lib/services/excel/test-data.service.spec.ts
index 04d2831760e..7297599ec56 100644
--- a/projects/igniteui-angular/src/lib/services/excel/test-data.service.spec.ts
+++ b/projects/igniteui-angular/src/lib/services/excel/test-data.service.spec.ts
@@ -252,18 +252,15 @@ export class FileContentData {
return this._fileContentCollection;
}
- public simpleGridSortByNameDesc(isSorted: boolean) {
- const sortedTag = isSorted ? `` : ``;
-
+ public simpleGridSortByNameDesc() {
this._sharedStringsData = `count="23" uniqueCount="21">IDNameJobTitle` +
`Tanya BennettDirectorLeslie HansenAssociate Software Developer` +
`Jack SimonSoftware DeveloperGilberto ToddErma WalshCEO` +
`Erika WellsSoftware Development Team LeadEduardo RamirezManager` +
`Debra MortonCelia MartinezSenior Software DeveloperCasey HoustonVice President`;
- this._tableData = `ref="A1:C11" totalsRowShown="0">` +
- `${ sortedTag }` +
- ``;
+ this._tableData = `ref="A1:C11" totalsRowShown="0">
+ `;
this._worksheetData = `012
334
956
478
294
61011
81213
101415
7166
51718
11920
`;
@@ -714,8 +711,7 @@ export class FileContentData {
`Leslie HansenTanya Bennett`;
this._tableData = `ref="A1:C11" totalsRowShown="0">
- ` +
- ``;
+ `;
this._worksheetData = `012
134
556
778
10910
81112
61314
21516
41718
9198
32016
`;
@@ -827,6 +823,23 @@ export class FileContentData {
return this.createData();
}
+ get treeGridDataIgnoreFiltering() {
+ this._sharedStringsData =
+ `count="21" uniqueCount="19">IDParentIDNameJobTitleAgeCasey HoustonVice PresidentGilberto ToddDirectorTanya BennettDebra MortonAssociate Software DeveloperJack SimonSoftware DeveloperErma WalshCEOEduardo RamirezManagerLeslie Hansen`;
+
+ this._tableData = `ref="A1:E9" totalsRowShown="0">
+ `;
+
+ this._worksheetData = `
+
+
+
+
+ 01234
1-15632
217841
329829
72101135
41121333
6-1141552
10-1161753
910181144
`;
+
+ return this.createData();
+ }
+
get treeGridDataFormatted() {
this._sharedStringsData =
`count="21" uniqueCount="19">IDParentIDNameJobTitleAgeCasey HoustonVice PresidentGilberto ToddDirectorTanya BennettDebra MortonAssociate Software DeveloperJack SimonSoftware DeveloperErma WalshCEOEduardo RamirezManagerLeslie Hansen`;
@@ -850,7 +863,7 @@ export class FileContentData {
`count="21" uniqueCount="19">IDParentIDNameJobTitleAgeEduardo RamirezManagerLeslie HansenAssociate Software DeveloperErma WalshCEOCasey HoustonVice PresidentJack SimonSoftware DeveloperGilberto ToddDirectorDebra MortonTanya Bennett`;
this._tableData = `ref="A1:E9" totalsRowShown="0">
- `;
+ `;
this._worksheetData = `
@@ -886,7 +899,7 @@ export class FileContentData {
`count="19" uniqueCount="18">IDParentIDNameJobTitleAgeErma WalshCEOEduardo RamirezManagerLeslie HansenAssociate Software DeveloperCasey HoustonVice PresidentJack SimonSoftware DeveloperGilberto ToddDirectorDebra Morton`;
this._tableData = `ref="A1:E8" totalsRowShown="0">
- `;
+ `;
this._worksheetData = `
@@ -1014,5 +1027,99 @@ export class FileContentData {
return this.createData();
}
- /* eslint-enable max-len */
+
+ get exportGroupedData() {
+ this._sharedStringsData =
+ `count="29" uniqueCount="20">ModelEditionBrand: BMW (2)Price: 150000 (1)M5CompetitionPrice: 100000 (1)PerformanceBrand: Tesla (3)RoadsterPrice: 75000 (1)Model SSportPrice: 65000 (1)BaseBrand: VW (3)ArteonR LineBusinessPassat`;
+
+ this._tableData =
+ `ref="A1:B20" totalsRowShown="0">
+ `;
+
+ this._worksheetData =
+ `
+
+
+
+
+ 01
2
3
45
6
47
8
6
97
10
1112
13
1114
15
6
1617
10
1618
13
1918
`;
+
+ return this.createData();
+ }
+
+ get exportGroupedDataWithCollapsedRows() {
+ this._sharedStringsData =
+ `count="29" uniqueCount="20">ModelEditionBrand: BMW (2)Price: 150000 (1)M5CompetitionPrice: 100000 (1)PerformanceBrand: Tesla (3)RoadsterPrice: 75000 (1)Model SSportPrice: 65000 (1)BaseBrand: VW (3)ArteonR LineBusinessPassat`;
+
+ this._tableData =
+ `ref="A1:B20" totalsRowShown="0">
+ `;
+
+ this._worksheetData =
+ `
+
+
+
+
+ 01
2
3
45
6
47
8
6
97
10
1112
13
1114
15
6
1617
10
1618
13
1918
`;
+
+ return this.createData();
+ }
+
+ get exportGroupedDataWithIgnoreSorting() {
+ this._sharedStringsData =
+ `count="30" uniqueCount="21">PriceModelEditionBrand: Tesla (3)75000Model SSport100000RoadsterPerformance65000BaseBrand: BMW (2)150000M5CompetitionBrand: VW (3)ArteonBusinessPassatR Line`;
+
+ this._tableData =
+ `ref="A1:C12" totalsRowShown="0">
+ `;
+
+ this._worksheetData =
+ `
+
+
+
+
+ 012
3
456
789
10511
12
131415
7149
16
41718
101918
71720
`;
+
+ return this.createData();
+ }
+
+ get exportGroupedDataWithIgnoreFiltering() {
+ this._sharedStringsData =
+ `count="30" uniqueCount="21">PriceModelEditionBrand: BMW (2)150000M5Competition100000PerformanceBrand: Tesla (3)75000Model SSportRoadster65000BaseBrand: VW (3)ArteonBusinessPassatR Line`;
+
+ this._tableData =
+ `ref="A1:C12" totalsRowShown="0">
+ `;
+
+ this._worksheetData =
+ `
+
+
+
+
+ 012
3
456
758
9
101112
7138
141115
16
101718
141918
71720
`;
+
+ return this.createData();
+ }
+
+ get exportGroupedDataWithIgnoreGrouping() {
+ this._sharedStringsData =
+ `count="19" uniqueCount="14">PriceModelEditionM5CompetitionPerformanceModel SSportRoadsterBaseArteonBusinessPassatR Line`;
+
+ this._tableData =
+ `ref="A1:C9" totalsRowShown="0">
+ `;
+
+ this._worksheetData =
+ `
+
+
+
+ 012
15000034
10000035
7500067
10000085
6500069
750001011
650001211
1000001013
`;
+
+ return this.createData();
+ }
+
}
diff --git a/projects/igniteui-angular/src/lib/services/excel/worksheet-data.ts b/projects/igniteui-angular/src/lib/services/excel/worksheet-data.ts
index fa944268059..cd989f24425 100644
--- a/projects/igniteui-angular/src/lib/services/excel/worksheet-data.ts
+++ b/projects/igniteui-angular/src/lib/services/excel/worksheet-data.ts
@@ -1,3 +1,4 @@
+import { IExportRecord } from '../exporter-common/base-export-service';
import { ExportUtilities } from '../exporter-common/export-utilities';
import { IgxExcelExporterOptions } from './excel-exporter-options';
import { WorksheetDataDictionary } from './worksheet-data-dictionary';
@@ -10,12 +11,12 @@ export class WorksheetData {
private _keys: string[];
private _isSpecialData: boolean;
- constructor(private _data: any[], private _columnWidths: number[], public options: IgxExcelExporterOptions,
- public indexOfLastPinnedColumn, public sort: any, public isTreeGridData = false) {
- this.initializeData();
+ constructor(private _data: IExportRecord[], private _columnWidths: number[], public options: IgxExcelExporterOptions,
+ public indexOfLastPinnedColumn: number, public sort: any) {
+ this.initializeData();
}
- public get data() {
+ public get data(): IExportRecord[] {
return this._data;
}
@@ -27,7 +28,7 @@ export class WorksheetData {
return this._rowCount;
}
- public get isEmpty() {
+ public get isEmpty(): boolean {
return !this.rowCount || !this._columnCount;
}
@@ -39,7 +40,7 @@ export class WorksheetData {
return this._isSpecialData;
}
- public get dataDictionary() {
+ public get dataDictionary(): WorksheetDataDictionary {
return this._dataDictionary;
}
@@ -48,7 +49,7 @@ export class WorksheetData {
return;
}
- const actualData = this._data.map((item) => item.rowData);
+ const actualData = this._data.map(item => item.data);
this._keys = ExportUtilities.getKeysFromData(actualData);
if (this._keys.length === 0) {
diff --git a/projects/igniteui-angular/src/lib/services/exporter-common/base-export-service.ts b/projects/igniteui-angular/src/lib/services/exporter-common/base-export-service.ts
index 858520d4f2d..2c87501b3a0 100644
--- a/projects/igniteui-angular/src/lib/services/exporter-common/base-export-service.ts
+++ b/projects/igniteui-angular/src/lib/services/exporter-common/base-export-service.ts
@@ -1,12 +1,34 @@
import { EventEmitter } from '@angular/core';
-import { cloneValue, IBaseEventArgs, resolveNestedPath, yieldingLoop } from '../../core/utils';
+import { cloneArray, cloneValue, IBaseEventArgs, resolveNestedPath, yieldingLoop } from '../../core/utils';
import { DataUtil } from '../../data-operations/data-util';
import { ExportUtilities } from './export-utilities';
import { IgxExporterOptionsBase } from './exporter-options-base';
import { ITreeGridRecord } from '../../grids/tree-grid/tree-grid.interfaces';
import { TreeGridFilteringStrategy } from '../../grids/tree-grid/tree-grid.filtering.pipe';
+import { IGroupingState } from '../../data-operations/groupby-state.interface';
+import { getHierarchy, isHierarchyMatch } from '../../data-operations/operations';
+import { IGroupByExpandState } from '../../data-operations/groupby-expand-state.interface';
+import { IFilteringState } from '../../data-operations/filtering-state.interface';
+import { IgxGridBaseDirective } from '../../grids/public_api';
+import { IgxTreeGridComponent } from '../../grids/tree-grid/public_api';
+import { IgxGridComponent } from '../../grids/grid/public_api';
+import { DatePipe } from '@angular/common';
+import { IGroupByRecord } from '../../data-operations/groupby-record.interface';
+
+export enum ExportRecordType {
+ GroupedRecord = 1,
+ TreeGridRecord = 2,
+ DataRecord = 3,
+}
+
+export interface IExportRecord {
+ data: any;
+ level: number;
+ hidden?: boolean;
+ type: ExportRecordType;
+}
/**
* onRowExport event arguments
@@ -95,13 +117,12 @@ export abstract class IgxBaseExporter {
*/
public onColumnExport = new EventEmitter();
- protected _isTreeGrid = false;
protected _indexOfLastPinnedColumn = -1;
protected _sort = null;
private _columnList: any[];
- private flatRecords = [];
private _columnWidthList: number[];
+ private flatRecords: IExportRecord[] = [];
public get columnWidthList() {
return this._columnWidthList;
@@ -125,7 +146,7 @@ export abstract class IgxBaseExporter {
this._columnWidthList = new Array(columns.filter(c => !c.hidden).length);
const hiddenColumns = [];
- let lastVisbleColumnIndex = -1;
+ let lastVisibleColumnIndex = -1;
columns.forEach((column) => {
const columnHeader = column.header !== '' ? column.header : column.field;
@@ -145,7 +166,7 @@ export abstract class IgxBaseExporter {
if (index !== -1) {
this._columnList[index] = columnInfo;
this._columnWidthList[index] = columnWidth;
- lastVisbleColumnIndex = Math.max(lastVisbleColumnIndex, index);
+ lastVisibleColumnIndex = Math.max(lastVisibleColumnIndex, index);
} else {
hiddenColumns.push(columnInfo);
}
@@ -157,11 +178,11 @@ export abstract class IgxBaseExporter {
// Append the hidden columns to the end of the list
hiddenColumns.forEach((hiddenColumn) => {
- this._columnList[++lastVisbleColumnIndex] = hiddenColumn;
+ this._columnList[++lastVisibleColumnIndex] = hiddenColumn;
});
- const data = this.prepareData(grid, options);
- this.exportData(data, options);
+ this.prepareData(grid, options);
+ this.exportGridRecordsData(this.flatRecords, options);
}
/**
@@ -177,8 +198,27 @@ export abstract class IgxBaseExporter {
throw Error('No options provided!');
}
+ const records = data.map(d => {
+ const record: IExportRecord = {
+ data: d,
+ type: ExportRecordType.DataRecord,
+ level: 0
+ };
+
+ return record;
+ });
+
+ this.exportGridRecordsData(records, options);
+ }
+
+ private exportGridRecordsData(records: IExportRecord[], options: IgxExporterOptionsBase) {
+ if (options === undefined || options === null) {
+ throw Error('No options provided!');
+ }
+
if (!this._columnList || this._columnList.length === 0) {
- const keys = ExportUtilities.getKeysFromData(data);
+ const recordsData = records.map(r => r.data);
+ const keys = ExportUtilities.getKeysFromData(recordsData);
this._columnList = keys.map((k) => ({ header: k, field: k, skip: false }));
this._columnWidthList = new Array(keys.length).fill(DEFAULT_COLUMN_WIDTH);
}
@@ -188,8 +228,11 @@ export abstract class IgxBaseExporter {
this._columnList.forEach((column, index) => {
if (!column.skip) {
const columnExportArgs = {
- header: ExportUtilities.isNullOrWhitespaces(column.header) ?
- 'Column' + columnsWithoutHeaderCount++ : column.header,
+ header: !ExportUtilities.isNullOrWhitespaces(column.header) ?
+ column.header :
+ !ExportUtilities.isNullOrWhitespaces(column.field) ?
+ column.field :
+ 'Column' + columnsWithoutHeaderCount++,
field: column.field,
columnIndex: index,
cancel: false,
@@ -217,11 +260,12 @@ export abstract class IgxBaseExporter {
this._indexOfLastPinnedColumn -= skippedPinnedColumnsCount;
- const dataToExport = new Array();
- const isSpecialData = ExportUtilities.isSpecialData(data);
+ const dataToExport = new Array();
+ const actualData = records.map(r => r.data);
+ const isSpecialData = ExportUtilities.isSpecialData(actualData);
- yieldingLoop(data.length, 100, (i) => {
- const row = data[i];
+ yieldingLoop(records.length, 100, (i) => {
+ const row = records[i];
this.exportRow(dataToExport, row, i, isSpecialData);
}, () => {
this.exportDataImplementation(dataToExport, options);
@@ -229,14 +273,13 @@ export abstract class IgxBaseExporter {
});
}
- private exportRow(data: any[], rowData: any, index: number, isSpecialData: boolean) {
- let row;
-
+ private exportRow(data: IExportRecord[], record: IExportRecord, index: number, isSpecialData: boolean) {
if (!isSpecialData) {
- row = this._columnList.reduce((a, e) => {
+ record.data = this._columnList.reduce((a, e) => {
if (!e.skip) {
- let rawValue = this._isTreeGrid ? resolveNestedPath(rowData.data, e.field) : resolveNestedPath(rowData, e.field);
- const shouldApplyFormatter = e.formatter && !e.skipFormatter;
+ let rawValue = resolveNestedPath(record.data, e.field);
+
+ const shouldApplyFormatter = e.formatter && !e.skipFormatter && record.type !== ExportRecordType.GroupedRecord;
if (e.dataType === 'date' &&
!(rawValue instanceof Date) &&
@@ -252,82 +295,217 @@ export abstract class IgxBaseExporter {
}
return a;
}, {});
- } else {
- row = this._isTreeGrid ? rowData.data : rowData;
}
const rowArgs = {
- rowData: row,
+ rowData: record.data,
rowIndex: index,
cancel: false
};
+
this.onRowExport.emit(rowArgs);
if (!rowArgs.cancel) {
- data.push({ rowData: rowArgs.rowData, originalRowData: rowData });
+ data.push(record);
}
}
- private prepareData(grid: any, options: IgxExporterOptionsBase): any[] {
+ private prepareData(grid: IgxGridBaseDirective, options: IgxExporterOptionsBase) {
this.flatRecords = [];
- let rootRecords = grid.rootRecords;
- this._isTreeGrid = rootRecords !== undefined;
+ const tagName = grid.nativeElement.tagName.toLowerCase();
+
+ const hasFiltering = (grid.filteringExpressionsTree && grid.filteringExpressionsTree.filteringOperands.length > 0) ||
+ (grid.advancedFilteringExpressionsTree && grid.advancedFilteringExpressionsTree.filteringOperands.length > 0);
+
+ const hasSorting = grid.sortingExpressions &&
+ grid.sortingExpressions.length > 0;
- if (this._isTreeGrid) {
- this.prepareHierarchicalData(rootRecords);
+
+ if (tagName === 'igx-grid') {
+ this.prepareGridData(grid as IgxGridComponent, options, hasFiltering, hasSorting);
+ } if (tagName === 'igx-tree-grid') {
+ this.prepareTreeGridData(grid as IgxTreeGridComponent, options, hasFiltering, hasSorting);
}
+ }
- let data = this._isTreeGrid ? this.flatRecords : grid.data;
-
- if (((grid.filteringExpressionsTree &&
- grid.filteringExpressionsTree.filteringOperands.length > 0) ||
- (grid.advancedFilteringExpressionsTree &&
- grid.advancedFilteringExpressionsTree.filteringOperands.length > 0)) &&
- !options.ignoreFiltering) {
- const filteringState: any = {
- expressionsTree: grid.filteringExpressionsTree,
- advancedExpressionsTree: grid.advancedFilteringExpressionsTree,
- logic: grid.filteringLogic
- };
+ private prepareGridData(grid: IgxGridComponent, options: IgxExporterOptionsBase, hasFiltering: boolean, hasSorting: boolean) {
+ const groupedGridGroupingState: IGroupingState = {
+ expressions: grid.groupingExpressions,
+ expansion: grid.groupingExpansionState,
+ defaultExpanded: grid.groupsExpanded,
+ };
- if (this._isTreeGrid) {
- this.flatRecords = [];
- filteringState.strategy = (grid.filterStrategy) ? grid.filterStrategy : new TreeGridFilteringStrategy();
- rootRecords = filteringState.strategy.filter(rootRecords,
- filteringState.expressionsTree, filteringState.advancedExpressionsTree);
- this.prepareHierarchicalData(rootRecords);
- data = this.flatRecords;
+ const hasGrouping = grid.groupingExpressions &&
+ grid.groupingExpressions.length > 0;
+
+ const skipOperations =
+ (!hasFiltering || !options.ignoreFiltering) &&
+ (!hasSorting || !options.ignoreSorting) &&
+ (!hasGrouping || !options.ignoreGrouping);
+
+ if (skipOperations) {
+ if (hasGrouping) {
+ this.addGroupedData(grid, grid.groupsRecords, groupedGridGroupingState);
} else {
+ this.addFlatData(grid.filteredSortedData);
+ }
+ } else {
+ let gridData = grid.data;
+
+ if (hasFiltering && !options.ignoreFiltering) {
+ const filteringState: IFilteringState = {
+ expressionsTree: grid.filteringExpressionsTree,
+ advancedExpressionsTree: grid.advancedFilteringExpressionsTree,
+ };
filteringState.strategy = grid.filterStrategy;
- data = DataUtil.filter(data, filteringState, grid);
+ gridData = DataUtil.filter(gridData, filteringState, grid);
}
- }
- if (grid.sortingExpressions &&
- grid.sortingExpressions.length > 0 &&
- !options.ignoreSorting) {
- this._sort = cloneValue(grid.sortingExpressions[0]);
+ if (hasSorting && !options.ignoreSorting) {
+ // TODO: We should drop support for this since in a grouped grid it doesn't make sense
+ // this._sort = !isGroupedGrid ?
+ // cloneValue(grid.sortingExpressions[0]) :
+ // grid.sortingExpressions.length > 1 ?
+ // cloneValue(grid.sortingExpressions[1]) :
+ // cloneValue(grid.sortingExpressions[0]);
- if (this._isTreeGrid) {
- this.flatRecords = [];
- rootRecords = DataUtil.treeGridSort(rootRecords, grid.sortingExpressions, grid.sortStrategy);
- this.prepareHierarchicalData(rootRecords);
- data = this.flatRecords;
+ gridData = DataUtil.sort(gridData, grid.sortingExpressions, grid.sortStrategy, grid);
+ }
+
+ if (hasGrouping && !options.ignoreGrouping) {
+ const groupsRecords = [];
+ DataUtil.group(cloneArray(gridData), groupedGridGroupingState, grid, groupsRecords);
+ gridData = groupsRecords;
+ }
+
+ if (hasGrouping && !options.ignoreGrouping) {
+ this.addGroupedData(grid, gridData, groupedGridGroupingState);
} else {
- data = DataUtil.sort(data, grid.sortingExpressions, grid.sortStrategy, grid);
+ this.addFlatData(gridData);
}
}
+ }
+
+ private prepareTreeGridData(grid: IgxTreeGridComponent, options: IgxExporterOptionsBase, hasFiltering: boolean, hasSorting: boolean) {
+ const skipOperations =
+ (!hasFiltering || !options.ignoreFiltering) &&
+ (!hasSorting || !options.ignoreSorting);
+
+ if (skipOperations) {
+ this.addTreeGridData(grid.processedRootRecords);
+ } else {
+ let gridData = grid.rootRecords;
+
+ if (hasFiltering && !options.ignoreFiltering) {
+ const filteringState: IFilteringState = {
+ expressionsTree: grid.filteringExpressionsTree,
+ advancedExpressionsTree: grid.advancedFilteringExpressionsTree,
+ };
- return data;
+ filteringState.strategy = (grid.filterStrategy) ? grid.filterStrategy : new TreeGridFilteringStrategy();
+
+ gridData = filteringState.strategy
+ .filter(gridData, filteringState.expressionsTree, filteringState.advancedExpressionsTree);
+ }
+
+ if (hasSorting && !options.ignoreSorting) {
+ this._sort = cloneValue(grid.sortingExpressions[0]);
+
+ gridData = DataUtil.treeGridSort(gridData, grid.sortingExpressions, grid.sortStrategy);
+ }
+
+ this.addTreeGridData(gridData);
+ }
}
- private prepareHierarchicalData(records: ITreeGridRecord[]) {
+ private addTreeGridData(records: ITreeGridRecord[], parentExpanded: boolean = true) {
if (!records) {
return;
}
- for (const hierarchicalRecord of records) {
+
+ for (const record of records) {
+ const hierarchicalRecord: IExportRecord = {
+ data: record.data,
+ level: record.level,
+ hidden: !parentExpanded,
+ type: ExportRecordType.TreeGridRecord
+ };
this.flatRecords.push(hierarchicalRecord);
- this.prepareHierarchicalData(hierarchicalRecord.children);
+ this.addTreeGridData(record.children, parentExpanded && record.expanded);
+ }
+ }
+
+ private addFlatData(records: any) {
+ if (!records) {
+ return;
+ }
+ for (const record of records) {
+ const data: IExportRecord = {
+ data: record,
+ type: ExportRecordType.DataRecord,
+ level: 0
+ };
+
+ this.flatRecords.push(data);
+ }
+ }
+
+ private addGroupedData(grid: IgxGridComponent, records: IGroupByRecord[],
+ groupingState: IGroupingState, parentExpanded: boolean = true) {
+ if (!records) {
+ return;
+ }
+
+ const firstCol = this._columnList[0].field;
+
+ for (const record of records) {
+ let recordVal = record.value;
+
+ const hierarchy = getHierarchy(record);
+ const expandState: IGroupByExpandState = groupingState.expansion.find((s) =>
+ isHierarchyMatch(s.hierarchy || [{ fieldName: record.expression.fieldName, value: recordVal }], hierarchy));
+ const expanded = expandState ? expandState.expanded : groupingState.defaultExpanded;
+
+ const isDate = recordVal instanceof Date;
+
+ if (isDate) {
+ const timeZoneOffset = recordVal.getTimezoneOffset() * 60000;
+ const isoString = (new Date(recordVal - timeZoneOffset)).toISOString();
+ const pipe = new DatePipe(grid.locale);
+ recordVal = pipe.transform(isoString);
+ }
+
+ const groupExpressionName = record.column && record.column.header ?
+ record.column.header :
+ record.expression.fieldName;
+
+ recordVal = recordVal !== null ? recordVal : '';
+
+ const groupExpression: IExportRecord = {
+ data: { [firstCol]: `${groupExpressionName}: ${recordVal} (${record.records.length})` },
+ level: record.level,
+ hidden: !parentExpanded,
+ type: ExportRecordType.GroupedRecord,
+ };
+
+ this.flatRecords.push(groupExpression);
+
+ if (record.groups.length > 0) {
+ this.addGroupedData(grid, record.groups, groupingState, expanded && parentExpanded);
+ } else {
+ const rowRecords = record.records;
+
+ for (const rowRecord of rowRecords) {
+ const currentRecord: IExportRecord = {
+ data: rowRecord,
+ level: record.level + 1,
+ hidden: !(expanded && parentExpanded),
+ type: ExportRecordType.DataRecord
+ };
+
+ this.flatRecords.push(currentRecord);
+ }
+ }
}
}
diff --git a/projects/igniteui-angular/src/lib/services/exporter-common/exporter-options-base.ts b/projects/igniteui-angular/src/lib/services/exporter-common/exporter-options-base.ts
index be0392e7053..60d3f731449 100644
--- a/projects/igniteui-angular/src/lib/services/exporter-common/exporter-options-base.ts
+++ b/projects/igniteui-angular/src/lib/services/exporter-common/exporter-options-base.ts
@@ -34,6 +34,8 @@ export abstract class IgxExporterOptionsBase {
/**
* Specifies whether the exported data should be sorted as in the provided IgxGrid.
+ * When you export grouped data, setting ignoreSorting to true will cause
+ * the grouping to fail because it relies on the sorting of the records.
* ```typescript
* let ignoreSorting = this.exportOptions.ignoreSorting;
* this.exportOptions.ignoreSorting = true;
@@ -43,6 +45,17 @@ export abstract class IgxExporterOptionsBase {
*/
public ignoreSorting = false;
+ /**
+ * Specifies whether the exported data should be grouped as in the provided IgxGrid.
+ * ```typescript
+ * let ignoreGrouping = this.exportOptions.ignoreGrouping;
+ * this.exportOptions.ignoreGrouping = true;
+ * ```
+ *
+ * @memberof IgxExporterOptionsBase
+ */
+ public ignoreGrouping = false;
+
private _fileName: string;
constructor(fileName: string, protected _fileExtension: string) {
diff --git a/projects/igniteui-angular/src/lib/test-utils/grid-samples.spec.ts b/projects/igniteui-angular/src/lib/test-utils/grid-samples.spec.ts
index 0bdcec18451..886e4ea9c49 100644
--- a/projects/igniteui-angular/src/lib/test-utils/grid-samples.spec.ts
+++ b/projects/igniteui-angular/src/lib/test-utils/grid-samples.spec.ts
@@ -2316,3 +2316,10 @@ export class IgxAddRowComponent implements OnInit {
/* eslint-enable max-len */
}
}
+
+@Component({
+ template: GridTemplateStrings.declareGrid(` [hideGroupedColumns]="true"`, '', ColumnDefinitions.exportGroupedDataColumns)
+})
+export class GridExportGroupedDataComponent extends BasicGridComponent {
+ data = SampleTestData.exportGroupedDataColumns();
+}
diff --git a/projects/igniteui-angular/src/lib/test-utils/sample-test-data.spec.ts b/projects/igniteui-angular/src/lib/test-utils/sample-test-data.spec.ts
index d19dbe3ebb9..47d43b872d7 100644
--- a/projects/igniteui-angular/src/lib/test-utils/sample-test-data.spec.ts
+++ b/projects/igniteui-angular/src/lib/test-utils/sample-test-data.spec.ts
@@ -1576,6 +1576,17 @@ export class SampleTestData {
}
]);
+ /* Data fields: Price: number, Brand: string, Model: string, Edition: string */
+ public static exportGroupedDataColumns = () => ([
+ { Price: 75000, Brand: 'Tesla', Model: 'Model S', Edition: 'Sport' },
+ { Price: 100000, Brand: 'Tesla', Model: 'Roadster', Edition: 'Performance' },
+ { Price: 65000, Brand: 'Tesla', Model: 'Model S', Edition: 'Base' },
+ { Price: 150000, Brand: 'BMW', Model: 'M5', Edition: 'Competition' },
+ { Price: 100000, Brand: 'BMW', Model: 'M5', Edition: 'Performance' },
+ { Price: 75000, Brand: 'VW', Model: 'Arteon', Edition: 'Business' },
+ { Price: 65000, Brand: 'VW', Model: 'Passat', Edition: 'Business' },
+ { Price: 100000, Brand: 'VW', Model: 'Arteon', Edition: 'R Line' },
+ ]);
/**
* Generates simple array of primitve values
diff --git a/projects/igniteui-angular/src/lib/test-utils/template-strings.spec.ts b/projects/igniteui-angular/src/lib/test-utils/template-strings.spec.ts
index 68fa6c3405c..f014689ee2f 100644
--- a/projects/igniteui-angular/src/lib/test-utils/template-strings.spec.ts
+++ b/projects/igniteui-angular/src/lib/test-utils/template-strings.spec.ts
@@ -454,6 +454,13 @@ export class ColumnDefinitions {
`;
+
+ public static exportGroupedDataColumns = `
+
+
+
+
+ `;
}
export class EventSubscriptions {
diff --git a/src/app/action-strip/action-strip.sample.scss b/src/app/action-strip/action-strip.sample.scss
index 438ae49103b..323541f01a9 100644
--- a/src/app/action-strip/action-strip.sample.scss
+++ b/src/app/action-strip/action-strip.sample.scss
@@ -1,5 +1,3 @@
-@import '../../styles/igniteui-theme.scss';
-
.display-density-actions {
max-width: 600px;
margin: 16px;
diff --git a/src/app/carousel/carousel.sample.html b/src/app/carousel/carousel.sample.html
index eab067cf953..bba155e9d80 100644
--- a/src/app/carousel/carousel.sample.html
+++ b/src/app/carousel/carousel.sample.html
@@ -21,7 +21,7 @@ Desktop
-
+
@@ -45,28 +45,7 @@ Desktop
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/app/grid-auto-size/grid-auto-size.sample.html b/src/app/grid-auto-size/grid-auto-size.sample.html
index 7982446268a..ee7f55cf21e 100644
--- a/src/app/grid-auto-size/grid-auto-size.sample.html
+++ b/src/app/grid-auto-size/grid-auto-size.sample.html
@@ -24,7 +24,6 @@
#grid1
[data]="data"
[displayDensity]="density"
- [showToolbar]="true" [columnHiding]="true"
[allowFiltering]="true"
[rowSelection]="selectionMode"
[filterMode]="'excelStyleFilter'"
diff --git a/src/app/grid-column-actions/grid-column-actions.sample.html b/src/app/grid-column-actions/grid-column-actions.sample.html
index 912e909dccb..80b12304a00 100644
--- a/src/app/grid-column-actions/grid-column-actions.sample.html
+++ b/src/app/grid-column-actions/grid-column-actions.sample.html
@@ -1,5 +1,5 @@
-
+
@@ -15,7 +15,6 @@ Hiding Action
diff --git a/src/app/grid-column-groups/grid-column-groups.sample.html b/src/app/grid-column-groups/grid-column-groups.sample.html
index a2893e13878..bcd4a951f5b 100644
--- a/src/app/grid-column-groups/grid-column-groups.sample.html
+++ b/src/app/grid-column-groups/grid-column-groups.sample.html
@@ -2,7 +2,14 @@
{{column.expanded ? 'remove' : 'add'}}
-
+
+
+ Grid Toolbar
+
+
+
+
+
diff --git a/src/app/grid-column-moving/grid-column-moving.sample.html b/src/app/grid-column-moving/grid-column-moving.sample.html
index e919838bd41..53879b127c8 100644
--- a/src/app/grid-column-moving/grid-column-moving.sample.html
+++ b/src/app/grid-column-moving/grid-column-moving.sample.html
@@ -17,8 +17,6 @@
[columnSelection]="'single'"
[data]="data"
[displayDensity]="density"
- [showToolbar]="true" [columnHiding]="true"
- [columnPinning]='true'
[allowFiltering]="true"
(onColumnMovingStart)="onColumnMovingStart($event)"
(onColumnMoving)="onColumnMoving($event)"
@@ -69,7 +67,7 @@
-
+
{{ column.header }} {{ column.visibleIndex }}
diff --git a/src/app/grid-column-pinning/grid-column-pinning.sample.html b/src/app/grid-column-pinning/grid-column-pinning.sample.html
index f43d20d9824..569d13cfb93 100644
--- a/src/app/grid-column-pinning/grid-column-pinning.sample.html
+++ b/src/app/grid-column-pinning/grid-column-pinning.sample.html
@@ -9,6 +9,12 @@
+
+ Grid Toolbar
+
+
+
+
@@ -25,8 +31,8 @@
- Toolbar
- Column Pinning
+ Toolbar
+ Column Pinning
Row Selectors
Right/Left Column Pinning toggle
Top/Bottom Row Pinning toggle
diff --git a/src/app/grid-column-pinning/grid-column-pinning.sample.ts b/src/app/grid-column-pinning/grid-column-pinning.sample.ts
index bd8ea5042f1..08a6f60d809 100644
--- a/src/app/grid-column-pinning/grid-column-pinning.sample.ts
+++ b/src/app/grid-column-pinning/grid-column-pinning.sample.ts
@@ -33,6 +33,8 @@ export class GridColumnPinningSampleComponent implements OnInit {
data: any[];
columns: any[];
+ showToolbar: true;
+ pinningEnabled: true;
onChange() {
if (this.pinningConfig.columns === ColumnPinningPosition.End) {
diff --git a/src/app/grid-column-selection/grid-column-selection.sample.html b/src/app/grid-column-selection/grid-column-selection.sample.html
index 991771aed86..40445977a21 100644
--- a/src/app/grid-column-selection/grid-column-selection.sample.html
+++ b/src/app/grid-column-selection/grid-column-selection.sample.html
@@ -16,8 +16,7 @@
-
TEST EXAMPLE
+ columnWidth="100px">
diff --git a/src/app/grid-esf-load-on-demand/grid-esf-load-on-demand.component.html b/src/app/grid-esf-load-on-demand/grid-esf-load-on-demand.component.html
index d40a9d59069..03484c81e01 100644
--- a/src/app/grid-esf-load-on-demand/grid-esf-load-on-demand.component.html
+++ b/src/app/grid-esf-load-on-demand/grid-esf-load-on-demand.component.html
@@ -5,7 +5,7 @@
diff --git a/src/app/grid-external-filtering/grid-external-filtering.sample.html b/src/app/grid-external-filtering/grid-external-filtering.sample.html
index 17a78ac3789..abf428c4662 100644
--- a/src/app/grid-external-filtering/grid-external-filtering.sample.html
+++ b/src/app/grid-external-filtering/grid-external-filtering.sample.html
@@ -27,15 +27,7 @@
[rowSelection]="selectionMode"
[paging]="false"
[width]="'100%'"
- [height]="'450px'"
- [showToolbar]="true"
- [columnHiding]="true"
- [columnPinning]="true"
- [exportExcel]="true"
- [exportCsv]="true"
- exportText="Export"
- exportExcelText="Export to Excel"
- exportCsvText="Export to CSV">
+ [height]="'450px'">
+ [height]="'600px'">
ESF Templates
[rowSelection]="selectionMode"
[paging]="false"
[width]="'980px'"
- [height]="'600px'"
- [showToolbar]="true"
- [columnHiding]="true"
- [columnPinning]="true"
- [columnSelection]="'single'"
- [exportExcel]="true"
- [exportCsv]="true"
- exportText="Export"
- exportExcelText="Export to Excel"
- exportCsvText="Export to CSV">
+ [height]="'600px'">
Grid1
-
+
Grid2
HGrid
-
+
diff --git a/src/app/grid-groupby/grid-groupby.sample.ts b/src/app/grid-groupby/grid-groupby.sample.ts
index 5e2c12d0834..6616b3e877f 100644
--- a/src/app/grid-groupby/grid-groupby.sample.ts
+++ b/src/app/grid-groupby/grid-groupby.sample.ts
@@ -96,6 +96,7 @@ export class GridGroupBySampleComponent implements OnInit {
}
this.grid1.groupBy({ fieldName: name, dir: SortingDirection.Asc, ignoreCase: false, strategy: DefaultSortingStrategy.instance() });
}
+
toggleGroupedVisibility(event) {
this.grid1.hideGroupedColumns = !event.checked;
}
diff --git a/src/app/grid-mrl-custom-navigation/grid-mrl-custom-navigation.sample.html b/src/app/grid-mrl-custom-navigation/grid-mrl-custom-navigation.sample.html
index 5f98b113ba0..d67f0f9f495 100644
--- a/src/app/grid-mrl-custom-navigation/grid-mrl-custom-navigation.sample.html
+++ b/src/app/grid-mrl-custom-navigation/grid-mrl-custom-navigation.sample.html
@@ -2,7 +2,7 @@
-
+
diff --git a/src/app/grid-multi-row-layout/grid-mrl.sample.html b/src/app/grid-multi-row-layout/grid-mrl.sample.html
index 784dc56662e..b8d2773fc8e 100644
--- a/src/app/grid-multi-row-layout/grid-mrl.sample.html
+++ b/src/app/grid-multi-row-layout/grid-mrl.sample.html
@@ -2,7 +2,7 @@
-
diff --git a/src/app/grid-nested-props/grid-nested-props.sample.html b/src/app/grid-nested-props/grid-nested-props.sample.html
index bed39726250..37ed4c99812 100644
--- a/src/app/grid-nested-props/grid-nested-props.sample.html
+++ b/src/app/grid-nested-props/grid-nested-props.sample.html
@@ -6,12 +6,6 @@
[allowFiltering]="true"
filterMode="excelStyleFilter"
allowAdvancedFiltering="true"
- [showToolbar]="true"
- [exportCsv]="true"
- [exportExcel]="true"
- exportText="Export"
- exportExcelText="Export to Excel"
- exportCsvText="Export to CSV"
>
Fixed Size Rows
-
+
diff --git a/src/app/grid-row-pinning/grid-row-pinning.sample.html b/src/app/grid-row-pinning/grid-row-pinning.sample.html
index 01db9e7ad6c..d525fab32ef 100644
--- a/src/app/grid-row-pinning/grid-row-pinning.sample.html
+++ b/src/app/grid-row-pinning/grid-row-pinning.sample.html
@@ -16,7 +16,7 @@
Right Column Pinning toggle
@@ -51,7 +51,7 @@
igxHierarchicalGrid
+ [showExpandAll]='true' [data]="hierarchicalData" [pinning]="pinningConfig" >
@@ -79,9 +79,7 @@
igxTreeGrid
+ [width]="'900px'" [height]="'800px'" >
diff --git a/src/app/grid-search/grid-search.sample.html b/src/app/grid-search/grid-search.sample.html
index 15cf18fdd05..943eea9287f 100644
--- a/src/app/grid-search/grid-search.sample.html
+++ b/src/app/grid-search/grid-search.sample.html
@@ -14,15 +14,7 @@
[rowSelection]="selectionMode"
[paging]="false"
[width]="'980px'"
- [height]="'600px'"
- [showToolbar]="true"
- [columnHiding]="true"
- [columnPinning]="true"
- [exportExcel]="true"
- [exportCsv]="true"
- exportText="Export"
- exportExcelText="Export to Excel"
- exportCsvText="Export to CSV">
+ [height]="'600px'">
-
@@ -64,7 +61,7 @@
-
@@ -81,7 +78,7 @@
[hidden]="c.hidden">
-
+ [allowFiltering]="true">
diff --git a/src/app/grid-summaries/grid-summaries.sample.html b/src/app/grid-summaries/grid-summaries.sample.html
index 54f8f75edf2..aa1588385ef 100644
--- a/src/app/grid-summaries/grid-summaries.sample.html
+++ b/src/app/grid-summaries/grid-summaries.sample.html
@@ -1,5 +1,12 @@
+
+ Grid Toolbar
+
+
+
+
+
@@ -46,10 +53,10 @@
Enable Paging
allowFiltering
-columnHiding
-columnPinning
+columnHiding
+columnPinning
Left Pinning toggle
-showToolbar
+showToolbar
ReorderLevel groupable
ReorderLevel filterable
ReorderLevel disableHiding
diff --git a/src/app/grid-summaries/grid-summaries.sample.ts b/src/app/grid-summaries/grid-summaries.sample.ts
index 8430cf3e8a9..a17726496c0 100644
--- a/src/app/grid-summaries/grid-summaries.sample.ts
+++ b/src/app/grid-summaries/grid-summaries.sample.ts
@@ -8,6 +8,7 @@ import {
} from 'igniteui-angular';
class MySummary extends IgxNumberSummaryOperand {
+
constructor() {
super();
}
@@ -34,10 +35,15 @@ export class GridSummaryComponent {
@ViewChild('grid1', { read: IgxGridComponent, static: true })
private grid1: IgxGridComponent;
+ public showToolbar = false;
+ public hidingEnabled = false;
+ public pinningEnabled = false;
+
public mySummary = MySummary;
public w = '1200px';
public h = '500px';
public cw = '200px';
+
public groupable = false;
public filterable = true;
public disableHiding = false;
diff --git a/src/app/grid-toolbar/grid-toolbar-custom.sample.html b/src/app/grid-toolbar/grid-toolbar-custom.sample.html
index 41728f52efe..3ef18e62fe5 100644
--- a/src/app/grid-toolbar/grid-toolbar-custom.sample.html
+++ b/src/app/grid-toolbar/grid-toolbar-custom.sample.html
@@ -3,20 +3,20 @@
Toolbar
-
+
+
+
+ {{ title }}
+
+
+
+
+ Transform to Excel
+ Transform to CSV
+
+
+
-
Toolbar
-
Column Hiding
-
Column Pinning
-
Export Excel
-
Export CSV
-
+
+ Toolbar
+
+ Column Hiding
+
+ Column Pinning
+
+ Export Excel
+
+ Export CSV
+
-
+
\ No newline at end of file
diff --git a/src/app/grid-toolbar/grid-toolbar-custom.sample.ts b/src/app/grid-toolbar/grid-toolbar-custom.sample.ts
index 16c73aa9ce8..5fc71a3e4b3 100644
--- a/src/app/grid-toolbar/grid-toolbar-custom.sample.ts
+++ b/src/app/grid-toolbar/grid-toolbar-custom.sample.ts
@@ -1,6 +1,6 @@
import { Component } from '@angular/core';
-import { IgxColumnComponent } from 'igniteui-angular';
+import { IgxColumnComponent, IgxCsvExporterService, IgxExcelExporterService } from 'igniteui-angular';
@Component({
selector: 'app-grid-toolbar-custom-sample',
@@ -8,6 +8,14 @@ import { IgxColumnComponent } from 'igniteui-angular';
templateUrl: 'grid-toolbar-custom.sample.html'
})
export class GridToolbarCustomSampleComponent {
+
+ showToolbar = true;
+ title = 'Custom Title';
+ hidingEnabled = true;
+ pinningEnabled = true;
+ csv = true;
+ excel = true;
+
public data = [
{
Name: 'Alice',
diff --git a/src/app/grid/grid.sample.html b/src/app/grid/grid.sample.html
index 4577b7cf77a..4d325e2e060 100644
--- a/src/app/grid/grid.sample.html
+++ b/src/app/grid/grid.sample.html
@@ -52,8 +52,7 @@
+ [grid]="grid1">
@@ -95,7 +94,7 @@ Grid with templated column cells and local observable d
+ >
@@ -187,8 +186,7 @@
Grid with templated cell remote data
+ [grid]="grid3">
diff --git a/src/app/styleguide/colors/color.sample.scss b/src/app/styleguide/colors/color.sample.scss
index 99695da5837..46d08360371 100644
--- a/src/app/styleguide/colors/color.sample.scss
+++ b/src/app/styleguide/colors/color.sample.scss
@@ -1,5 +1,4 @@
@import '../../../../projects/igniteui-angular/src/lib/core/styles/themes/utilities';
-@import'../../../styles/igniteui-theme.scss';
.sample-wrapper {
display: grid;
@@ -32,10 +31,6 @@
margin-bottom: 48px;
}
-.color-container {
- @include igx-color-classes('background', $palette: $palette);
-}
-
.color-title {
font-size: 18px;
line-height: 54px;
diff --git a/src/app/tree-grid-flat-data/tree-grid-flat-data.sample.html b/src/app/tree-grid-flat-data/tree-grid-flat-data.sample.html
index de56ecf8686..c06f0ad7e43 100644
--- a/src/app/tree-grid-flat-data/tree-grid-flat-data.sample.html
+++ b/src/app/tree-grid-flat-data/tree-grid-flat-data.sample.html
@@ -8,9 +8,7 @@
+ [paging]="false" [displayDensity]="density" [width]="'900px'" [height]="'800px'" [summaryCalculationMode]="summaryMode">
Primary/Foreign key
+ [rowSelection]="selectionMode" [displayDensity]="density" [width]="'900px'" [height]="'800px'"
+ [summaryCalculationMode]="summaryMode" [allowFiltering]="true" [filterMode]="'excelStyleFilter'">
Primary/Foreign key
ChildData key
+ [rowSelection]="selectionMode" [displayDensity]="density" [width]="'900px'" [height]="'800px'"
+ [summaryCalculationMode]="summaryMode" [allowFiltering]="true" [filterMode]="'excelStyleFilter'">
+ [allowFiltering]="true" [filterMode]="'excelStyleFilter'">