From 7562cc5f56f7a600d80d1465025acf7deae73854 Mon Sep 17 00:00:00 2001 From: MKirova Date: Wed, 2 Jul 2025 15:02:21 +0300 Subject: [PATCH 01/54] chore(*): Cell merge POC. --- .../src/lib/grids/common/enums.ts | 14 ++ .../src/lib/grids/common/grid.interface.ts | 4 +- .../src/lib/grids/grid-base.directive.ts | 19 +- .../lib/grids/grid/grid-row.component.html | 48 ++++- .../src/lib/grids/grid/grid.component.html | 3 +- .../src/lib/grids/grid/grid.component.ts | 5 +- .../src/lib/grids/grid/grid.pipes.ts | 40 ++++ .../src/lib/grids/row.directive.ts | 10 + src/app/app.component.ts | 5 + src/app/app.routes.ts | 5 + .../grid-cellMerging.component.html | 15 ++ .../grid-cellMerging.component.scss | 19 ++ .../grid-cellMerging.component.ts | 190 ++++++++++++++++++ 13 files changed, 371 insertions(+), 6 deletions(-) create mode 100644 src/app/grid-cellMerging/grid-cellMerging.component.html create mode 100644 src/app/grid-cellMerging/grid-cellMerging.component.scss create mode 100644 src/app/grid-cellMerging/grid-cellMerging.component.ts diff --git a/projects/igniteui-angular/src/lib/grids/common/enums.ts b/projects/igniteui-angular/src/lib/grids/common/enums.ts index 88d277ccd39..bd66328c97e 100644 --- a/projects/igniteui-angular/src/lib/grids/common/enums.ts +++ b/projects/igniteui-angular/src/lib/grids/common/enums.ts @@ -73,6 +73,20 @@ export const GridSelectionMode = { } as const; export type GridSelectionMode = (typeof GridSelectionMode)[keyof typeof GridSelectionMode]; + +/** + * Enumeration representing different cell merging modes for the grid elements. + * - 'never': Never merge cells. + * - 'always': Always merge adjacent cells based on merge strategy. + * - 'onSort': Only merge cells in column that are sorted. + */ +export const GridCellMergeMode = { + never: 'never', + always: 'always', + onSort: 'onSort' +} as const; +export type GridCellMergeMode = (typeof GridCellMergeMode)[keyof typeof GridCellMergeMode]; + /** Enumeration representing different column display order options. */ export const ColumnDisplayOrder = { Alphabetical: 'Alphabetical', diff --git a/projects/igniteui-angular/src/lib/grids/common/grid.interface.ts b/projects/igniteui-angular/src/lib/grids/common/grid.interface.ts index 3c8d8aa4f38..3b8d8d2e098 100644 --- a/projects/igniteui-angular/src/lib/grids/common/grid.interface.ts +++ b/projects/igniteui-angular/src/lib/grids/common/grid.interface.ts @@ -1,4 +1,4 @@ -import { ColumnPinningPosition, FilterMode, GridPagingMode, GridSelectionMode, GridSummaryCalculationMode, GridSummaryPosition, GridValidationTrigger, RowPinningPosition, Size } from './enums'; +import { ColumnPinningPosition, FilterMode, GridCellMergeMode, GridPagingMode, GridSelectionMode, GridSummaryCalculationMode, GridSummaryPosition, GridValidationTrigger, RowPinningPosition, Size } from './enums'; import { ISearchInfo, IGridCellEventArgs, IRowSelectionEventArgs, IColumnSelectionEventArgs, IPinColumnCancellableEventArgs, IColumnVisibilityChangedEventArgs, IColumnVisibilityChangingEventArgs, @@ -690,6 +690,7 @@ export interface GridServiceType { export interface GridType extends IGridDataBindable { /** Represents the locale of the grid: `USD`, `EUR`, `GBP`, `CNY`, `JPY`, etc. */ locale: string; + cellMergeMode: GridCellMergeMode; resourceStrings: IGridResourceStrings; /* blazorSuppress */ /** Represents the native HTML element itself */ @@ -1180,6 +1181,7 @@ export interface GridType extends IGridDataBindable { getEmptyRecordObjectFor(inRow: RowType): any; isSummaryRow(rec: any): boolean; isRecordPinned(rec: any): boolean; + isRecordMerged(rec: any): boolean; getInitialPinnedIndex(rec: any): number; isRecordPinnedByViewIndex(rowIndex: number): boolean; isColumnGrouped(fieldName: string): boolean; diff --git a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts index 5213151e54e..3398421a771 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts @@ -93,7 +93,8 @@ import { RowPinningPosition, GridPagingMode, GridValidationTrigger, - Size + Size, + GridCellMergeMode } from './common/enums'; import { IGridCellEventArgs, @@ -2911,6 +2912,14 @@ export abstract class IgxGridBaseDirective implements GridType, // } } + /** + * Gets/Sets cell merge mode. + * + */ + @WatchChanges() + @Input() + public cellMergeMode: GridCellMergeMode = GridCellMergeMode.never; + /** * Gets/Sets row selection mode * @@ -3635,6 +3644,14 @@ export abstract class IgxGridBaseDirective implements GridType, return this.getInitialPinnedIndex(rec) !== -1; } + /** + * @hidden + * @internal + */ + public isRecordMerged(rec) { + return rec.cellMergeMeta; + } + /** * @hidden * @internal diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html b/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html index 7289bc16f29..99124fd2124 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html @@ -31,7 +31,19 @@ } } - + @if (this.hasMergedCells) { +
+ +
+ } + @else { + + } +
@if (pinnedColumns.length > 0 && !grid.isPinningToStart) { @for (col of pinnedColumns | igxNotGrouped; track trackPinnedColumn(col)) { @@ -158,6 +170,40 @@ + + + + + () }; + for (const col of visibleColumns) { + recData.cellMergeMeta.set(col.field, { rowSpan: 1 }); + //TODO condition can be a strategy or some callback that the user can set. + //TODO can also be limited to only sorted columns + if ( prev && prev.recordRef[col.field] === rec[col.field]) { + const root = prev.cellMergeMeta.get(col.field)?.root ?? prev; + root.cellMergeMeta.get(col.field).rowSpan += 1; + recData.cellMergeMeta.get(col.field).root = root; + } + } + prev = recData; + result.push(recData); + } + return result; + } +} + +export interface IMergeByResult { + rowSpan: number; + root?: any; +} + /** * @hidden */ diff --git a/projects/igniteui-angular/src/lib/grids/row.directive.ts b/projects/igniteui-angular/src/lib/grids/row.directive.ts index b2118b1f15e..a353b728c6e 100644 --- a/projects/igniteui-angular/src/lib/grids/row.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/row.directive.ts @@ -27,6 +27,7 @@ import { mergeWith } from 'lodash-es'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { trackByIdentity } from '../core/utils'; +import { IMergeByResult } from './grid/grid.pipes'; @Directive({ selector: '[igxRowBaseComponent]', @@ -117,6 +118,10 @@ export class IgxRowDirective implements DoCheck, AfterViewInit, OnDestroy { return this.grid.isRecordPinned(this.data); } + public get hasMergedCells(): boolean { + return this.grid.isRecordMerged(this.data); + } + /** * Gets the expanded state of the row. * ```typescript @@ -592,6 +597,11 @@ export class IgxRowDirective implements DoCheck, AfterViewInit, OnDestroy { this.addAnimationEnd.emit(this); } + protected getMergeCellSpan(col: ColumnType){ + const rowCount = this.data.cellMergeMeta.get(col.field).rowSpan; + return `repeat(${rowCount},51px)`; + } + /** * @hidden */ diff --git a/src/app/app.component.ts b/src/app/app.component.ts index c4637552c6b..c3d2d4d104b 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -211,6 +211,11 @@ export class AppComponent implements OnInit { icon: 'view_column', name: 'Grid Cell Editing' }, + { + link: '/gridCellMerging', + icon: 'view_column', + name: 'Grid Cell Merging' + }, { link: '/gridClipboard', icon: 'insert_comment', diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 992516f1574..5234d396dc1 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -51,6 +51,7 @@ import { TimePickerSampleComponent } from './time-picker/time-picker.sample'; import { ToastShowcaseSampleComponent } from './toast-showcase/toast-showcase.sample'; import { VirtualForSampleComponent } from './virtual-for-directive/virtual-for.sample'; import { GridCellEditingComponent } from './grid-cellEditing/grid-cellEditing.component'; +import { GridCellMergingComponent } from './grid-cellMerging/grid-cellMerging.component'; import { GridSampleComponent } from './grid/grid.sample'; import { GridColumnMovingSampleComponent } from './grid-column-moving/grid-column-moving.sample'; import { GridColumnSelectionSampleComponent } from './grid-column-selection/grid-column-selection.sample'; @@ -419,6 +420,10 @@ export const appRoutes: Routes = [ path: 'gridCellEditing', component: GridCellEditingComponent }, + { + path: 'gridCellMerging', + component: GridCellMergingComponent + }, { path: 'gridConditionalCellStyling', component: GridCellStylingSampleComponent diff --git a/src/app/grid-cellMerging/grid-cellMerging.component.html b/src/app/grid-cellMerging/grid-cellMerging.component.html new file mode 100644 index 00000000000..6b2628ac345 --- /dev/null +++ b/src/app/grid-cellMerging/grid-cellMerging.component.html @@ -0,0 +1,15 @@ +

Grid with cell merge

+ + + + + + + + + + + + + + diff --git a/src/app/grid-cellMerging/grid-cellMerging.component.scss b/src/app/grid-cellMerging/grid-cellMerging.component.scss new file mode 100644 index 00000000000..3d4037836d0 --- /dev/null +++ b/src/app/grid-cellMerging/grid-cellMerging.component.scss @@ -0,0 +1,19 @@ +.sample-actions { + display: flex; + flex-wrap: wrap; + margin: 1rem 0; + gap: 0.5rem; +} + +.density-chooser { + margin-bottom: 1rem; + + igx-buttongroup { + display: block; + width: 500px; + } +} + +.grid-size { + --ig-size: var(--ig-size-small); +} diff --git a/src/app/grid-cellMerging/grid-cellMerging.component.ts b/src/app/grid-cellMerging/grid-cellMerging.component.ts new file mode 100644 index 00000000000..c2b9d9c0a88 --- /dev/null +++ b/src/app/grid-cellMerging/grid-cellMerging.component.ts @@ -0,0 +1,190 @@ +import { Component, HostBinding, ViewChild } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { + IgxColumnComponent, + IgxGridComponent, +} from 'igniteui-angular'; + +import { data, dataWithoutPK } from '../shared/data'; + +@Component({ + selector: 'app-grid-cellMerging', + templateUrl: 'grid-cellMerging.component.html', + styleUrl: 'grid-cellMerging.component.scss', + imports: [ + FormsModule, + IgxColumnComponent, + IgxGridComponent, + ] +}) +export class GridCellMergingComponent { + public data = [{ + ProductID: 1, + ProductName: 'Chai', + SupplierID: 1, + CategoryID: 1, + QuantityPerUnit: '10 boxes x 20 bags', + UnitPrice: '18.0000', + UnitsInStock: 39, + UnitsOnOrder: 0, + ReorderLevel: 10.567, + Discontinued: false, + OrderDate: null, + OrderDate2: new Date(1991, 2, 12, 18, 40, 50).toISOString() + }, { + ProductID: 2, + ProductName: 'Chai', + SupplierID: 1, + CategoryID: 1, + QuantityPerUnit: '24 - 12 oz bottles', + UnitPrice: '19.0000', + UnitsInStock: 17, + UnitsOnOrder: 40, + ReorderLevel: 25, + Discontinued: false, + OrderDate: new Date('2003-03-17').toISOString(), + OrderDate2: new Date('2003-03-17').toISOString() + }, + { + ProductID: 3, + ProductName: 'Chai', + SupplierID: 1, + CategoryID: 1, + QuantityPerUnit: '24 - 12 oz bottles', + UnitPrice: '19.0000', + UnitsInStock: 17, + UnitsOnOrder: 40, + ReorderLevel: 25, + Discontinued: false, + OrderDate: new Date('2003-03-17').toISOString(), + OrderDate2: new Date('2003-03-17').toISOString() + }, + { + ProductID: 4, + ProductName: 'Chai', + SupplierID: 1, + CategoryID: 1, + QuantityPerUnit: '24 - 12 oz bottles', + UnitPrice: '20.0000', + UnitsInStock: 20, + UnitsOnOrder: 40, + ReorderLevel: 25, + Discontinued: false, + OrderDate: new Date('2003-03-17').toISOString(), + OrderDate2: new Date('2003-03-17').toISOString() + }, + { + ProductID: 5, + ProductName: 'Chai', + SupplierID: 1, + CategoryID: 1, + QuantityPerUnit: '24 - 12 oz bottles', + UnitPrice: '19.0000', + UnitsInStock: 17, + UnitsOnOrder: 40, + ReorderLevel: 25, + Discontinued: false, + OrderDate: new Date('2003-03-17').toISOString(), + OrderDate2: new Date('2003-03-17').toISOString() + }, + { + ProductID: 6, + ProductName: 'Chang', + SupplierID: 1, + CategoryID: 1, + QuantityPerUnit: '24 - 12 oz bottles', + UnitPrice: '19.0000', + UnitsInStock: 17, + UnitsOnOrder: 40, + ReorderLevel: 25, + Discontinued: false, + OrderDate: new Date('2003-03-17').toISOString(), + OrderDate2: new Date('2003-03-17').toISOString() + }, + { + ProductID: 7, + ProductName: 'Chang', + SupplierID: 1, + CategoryID: 1, + QuantityPerUnit: '24 - 12 oz bottles', + UnitPrice: '19.0000', + UnitsInStock: 17, + UnitsOnOrder: 40, + ReorderLevel: 25, + Discontinued: false, + OrderDate: new Date('2003-03-17').toISOString(), + OrderDate2: new Date('2003-03-17').toISOString() + }, + { + ProductID: 8, + ProductName: 'Chang', + SupplierID: 1, + CategoryID: 1, + QuantityPerUnit: '24 - 12 oz bottles', + UnitPrice: '19.0000', + UnitsInStock: 17, + UnitsOnOrder: 40, + ReorderLevel: 30, + Discontinued: false, + OrderDate: new Date('2003-03-17').toISOString(), + OrderDate2: new Date('2003-03-17').toISOString() + }, + { + ProductID: 9, + ProductName: 'Aniseed Syrup', + SupplierID: 1, + CategoryID: 2, + QuantityPerUnit: '12 - 550 ml bottles', + UnitPrice: '10.0000', + UnitsInStock: 13, + UnitsOnOrder: 70, + ReorderLevel: 25, + Discontinued: false, + OrderDate: new Date('2006-03-17').toISOString(), + OrderDate2: new Date(1991, 2, 12, 15, 40, 50).toISOString() + }, + { + ProductID: 10, + ProductName: 'Chang', + SupplierID: 1, + CategoryID: 2, + QuantityPerUnit: '12 - 550 ml bottles', + UnitPrice: '10.0000', + UnitsInStock: 13, + UnitsOnOrder: 70, + ReorderLevel: 25, + Discontinued: false, + OrderDate: new Date('2006-03-17').toISOString(), + OrderDate2: new Date(1991, 2, 12, 15, 40, 50).toISOString() + }, + { + ProductID: 11, + ProductName: 'Chai', + SupplierID: 1, + CategoryID: 2, + QuantityPerUnit: '12 - 550 ml bottles', + UnitPrice: '10.0000', + UnitsInStock: 13, + UnitsOnOrder: 70, + ReorderLevel: 25, + Discontinued: false, + OrderDate: new Date('2006-03-17').toISOString(), + OrderDate2: new Date(1991, 2, 12, 15, 40, 50).toISOString() + }, + { + ProductID: 12, + ProductName: 'Chai', + SupplierID: 1, + CategoryID: 2, + QuantityPerUnit: '12 - 550 ml bottles', + UnitPrice: '10.0000', + UnitsInStock: 12, + UnitsOnOrder: 70, + ReorderLevel: 30, + Discontinued: false, + OrderDate: new Date('2006-03-17').toISOString(), + OrderDate2: new Date(1991, 2, 12, 15, 40, 50).toISOString() + }]; + +} + From f42bb3c7d6f7f94938351138d24c046bf0ec4422 Mon Sep 17 00:00:00 2001 From: MKirova Date: Wed, 2 Jul 2025 16:47:10 +0300 Subject: [PATCH 02/54] chore(*): Minor tweaks to suggestion. --- .../lib/grids/grid/grid-row.component.html | 2 +- .../src/lib/grids/grid/grid.pipes.ts | 13 +- .../grid-cellMerging.component.ts | 167 ++++++++++++++++++ 3 files changed, 178 insertions(+), 4 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html b/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html index 99124fd2124..e3b00c36d28 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html @@ -194,7 +194,7 @@ [cellSelectionMode]="grid.cellSelection" [displayPinnedChip]="shouldDisplayPinnedChip(col.visibleIndex)" [style.height.px]="data.cellMergeMeta.get(col.field).rowSpan * (this.grid.rowHeight + 1)" - [style.zIndex]="100" + [style.zIndex]="data.cellMergeMeta.get(col.field).rowSpan" [style.min-width]="col.resolvedWidth" [style.max-width]="col.resolvedWidth" [style.flex-basis]="col.resolvedWidth" diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid.pipes.ts b/projects/igniteui-angular/src/lib/grids/grid/grid.pipes.ts index 2b6304904ff..2aa8eafd495 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid.pipes.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.pipes.ts @@ -99,9 +99,15 @@ export class IgxGridCellMergePipe implements PipeTransform { //TODO condition can be a strategy or some callback that the user can set. //TODO can also be limited to only sorted columns if ( prev && prev.recordRef[col.field] === rec[col.field]) { - const root = prev.cellMergeMeta.get(col.field)?.root ?? prev; - root.cellMergeMeta.get(col.field).rowSpan += 1; - recData.cellMergeMeta.get(col.field).root = root; + // const root = prev.cellMergeMeta.get(col.field)?.root ?? prev; + // root.cellMergeMeta.get(col.field).rowSpan += 1; + // recData.cellMergeMeta.get(col.field).root = root; + recData.cellMergeMeta.get(col.field).prev = prev; + let curr = prev; + while(curr) { + curr.cellMergeMeta.get(col.field).rowSpan += 1; + curr = curr.cellMergeMeta.get(col.field).prev; + } } } prev = recData; @@ -114,6 +120,7 @@ export class IgxGridCellMergePipe implements PipeTransform { export interface IMergeByResult { rowSpan: number; root?: any; + prev?: any; } /** diff --git a/src/app/grid-cellMerging/grid-cellMerging.component.ts b/src/app/grid-cellMerging/grid-cellMerging.component.ts index c2b9d9c0a88..c4184a997ae 100644 --- a/src/app/grid-cellMerging/grid-cellMerging.component.ts +++ b/src/app/grid-cellMerging/grid-cellMerging.component.ts @@ -171,6 +171,173 @@ export class GridCellMergingComponent { OrderDate: new Date('2006-03-17').toISOString(), OrderDate2: new Date(1991, 2, 12, 15, 40, 50).toISOString() }, + { + ProductID: 12, + ProductName: 'Chai', + SupplierID: 1, + CategoryID: 2, + QuantityPerUnit: '12 - 550 ml bottles', + UnitPrice: '10.0000', + UnitsInStock: 12, + UnitsOnOrder: 70, + ReorderLevel: 30, + Discontinued: false, + OrderDate: new Date('2006-03-17').toISOString(), + OrderDate2: new Date(1991, 2, 12, 15, 40, 50).toISOString() + }, + { + ProductID: 1, + ProductName: 'Chai', + SupplierID: 1, + CategoryID: 1, + QuantityPerUnit: '10 boxes x 20 bags', + UnitPrice: '18.0000', + UnitsInStock: 39, + UnitsOnOrder: 0, + ReorderLevel: 10.567, + Discontinued: false, + OrderDate: null, + OrderDate2: new Date(1991, 2, 12, 18, 40, 50).toISOString() + }, { + ProductID: 2, + ProductName: 'Chai', + SupplierID: 1, + CategoryID: 1, + QuantityPerUnit: '24 - 12 oz bottles', + UnitPrice: '19.0000', + UnitsInStock: 17, + UnitsOnOrder: 40, + ReorderLevel: 25, + Discontinued: false, + OrderDate: new Date('2003-03-17').toISOString(), + OrderDate2: new Date('2003-03-17').toISOString() + }, + { + ProductID: 3, + ProductName: 'Chai', + SupplierID: 1, + CategoryID: 1, + QuantityPerUnit: '24 - 12 oz bottles', + UnitPrice: '19.0000', + UnitsInStock: 17, + UnitsOnOrder: 40, + ReorderLevel: 25, + Discontinued: false, + OrderDate: new Date('2003-03-17').toISOString(), + OrderDate2: new Date('2003-03-17').toISOString() + }, + { + ProductID: 4, + ProductName: 'Chai', + SupplierID: 1, + CategoryID: 1, + QuantityPerUnit: '24 - 12 oz bottles', + UnitPrice: '20.0000', + UnitsInStock: 20, + UnitsOnOrder: 40, + ReorderLevel: 25, + Discontinued: false, + OrderDate: new Date('2003-03-17').toISOString(), + OrderDate2: new Date('2003-03-17').toISOString() + }, + { + ProductID: 5, + ProductName: 'Chai', + SupplierID: 1, + CategoryID: 1, + QuantityPerUnit: '24 - 12 oz bottles', + UnitPrice: '19.0000', + UnitsInStock: 17, + UnitsOnOrder: 40, + ReorderLevel: 25, + Discontinued: false, + OrderDate: new Date('2003-03-17').toISOString(), + OrderDate2: new Date('2003-03-17').toISOString() + }, + { + ProductID: 6, + ProductName: 'Chang', + SupplierID: 1, + CategoryID: 1, + QuantityPerUnit: '24 - 12 oz bottles', + UnitPrice: '19.0000', + UnitsInStock: 17, + UnitsOnOrder: 40, + ReorderLevel: 25, + Discontinued: false, + OrderDate: new Date('2003-03-17').toISOString(), + OrderDate2: new Date('2003-03-17').toISOString() + }, + { + ProductID: 7, + ProductName: 'Chang', + SupplierID: 1, + CategoryID: 1, + QuantityPerUnit: '24 - 12 oz bottles', + UnitPrice: '19.0000', + UnitsInStock: 17, + UnitsOnOrder: 40, + ReorderLevel: 25, + Discontinued: false, + OrderDate: new Date('2003-03-17').toISOString(), + OrderDate2: new Date('2003-03-17').toISOString() + }, + { + ProductID: 8, + ProductName: 'Chang', + SupplierID: 1, + CategoryID: 1, + QuantityPerUnit: '24 - 12 oz bottles', + UnitPrice: '19.0000', + UnitsInStock: 17, + UnitsOnOrder: 40, + ReorderLevel: 30, + Discontinued: false, + OrderDate: new Date('2003-03-17').toISOString(), + OrderDate2: new Date('2003-03-17').toISOString() + }, + { + ProductID: 9, + ProductName: 'Aniseed Syrup', + SupplierID: 1, + CategoryID: 2, + QuantityPerUnit: '12 - 550 ml bottles', + UnitPrice: '10.0000', + UnitsInStock: 13, + UnitsOnOrder: 70, + ReorderLevel: 25, + Discontinued: false, + OrderDate: new Date('2006-03-17').toISOString(), + OrderDate2: new Date(1991, 2, 12, 15, 40, 50).toISOString() + }, + { + ProductID: 10, + ProductName: 'Chang', + SupplierID: 1, + CategoryID: 2, + QuantityPerUnit: '12 - 550 ml bottles', + UnitPrice: '10.0000', + UnitsInStock: 13, + UnitsOnOrder: 70, + ReorderLevel: 25, + Discontinued: false, + OrderDate: new Date('2006-03-17').toISOString(), + OrderDate2: new Date(1991, 2, 12, 15, 40, 50).toISOString() + }, + { + ProductID: 11, + ProductName: 'Chai', + SupplierID: 1, + CategoryID: 2, + QuantityPerUnit: '12 - 550 ml bottles', + UnitPrice: '10.0000', + UnitsInStock: 13, + UnitsOnOrder: 70, + ReorderLevel: 25, + Discontinued: false, + OrderDate: new Date('2006-03-17').toISOString(), + OrderDate2: new Date(1991, 2, 12, 15, 40, 50).toISOString() + }, { ProductID: 12, ProductName: 'Chai', From 870e7f61f966e3353502d9f93342d0c1e72caa08 Mon Sep 17 00:00:00 2001 From: MKirova Date: Thu, 3 Jul 2025 17:46:06 +0300 Subject: [PATCH 03/54] chore(*): Implement with rows that are retained outside the virt.frame. --- .../src/lib/grids/grid-base.directive.ts | 5 +++++ .../src/lib/grids/grid/grid-row.component.html | 1 - .../src/lib/grids/grid/grid.component.html | 8 ++++++++ .../src/lib/grids/grid/grid.component.ts | 11 +++++++++++ .../src/lib/grids/grid/grid.pipes.ts | 18 +++++++++--------- .../grid-cellMerging.component.html | 4 +--- 6 files changed, 34 insertions(+), 13 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts index 3398421a771..659e5e6e18c 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts @@ -3652,6 +3652,11 @@ export abstract class IgxGridBaseDirective implements GridType, return rec.cellMergeMeta; } + public getMergeCellOffset(rec) { + const index = this.verticalScrollContainer.igxForOf.indexOf(rec); + return -(this.verticalScrollContainer.scrollPosition - this.verticalScrollContainer.getScrollForIndex(index)); + } + /** * @hidden * @internal diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html b/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html index e3b00c36d28..d477ce770ad 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html @@ -199,7 +199,6 @@ [style.max-width]="col.resolvedWidth" [style.flex-basis]="col.resolvedWidth" [width]="col.getCellWidth()" - [style.background]="'white'" #cell>
diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid.component.html b/projects/igniteui-angular/src/lib/grids/grid/grid.component.html index cf02d0ab7d0..fc0df2e4644 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid.component.html +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.component.html @@ -47,6 +47,14 @@ } + + + @for (rowData of mergedData; track rowData; let rowIndex = $index) { + + + } @if (data | gridTransaction:id:pipeTrigger diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid.component.ts b/projects/igniteui-angular/src/lib/grids/grid/grid.component.ts index 927692b4a45..d1d0a3fa116 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.component.ts @@ -389,6 +389,17 @@ export class IgxGridComponent extends IgxGridBaseDirective implements GridType, private _groupByRowSelectorTemplate: TemplateRef; private _detailTemplate; + public get mergedData() { + const startIndex = this.verticalScrollContainer.state.startIndex; + const chunkSize = this.verticalScrollContainer.state.chunkSize; + const mergeRecs = this.verticalScrollContainer.igxForOf?.filter((x, index) => { + if (!x.cellMergeMeta) { return false;} + const maxSpan = Math.max(...x.cellMergeMeta.values().toArray().map(x => x.rowSpan)); + return startIndex > index && startIndex <= (index + maxSpan) && startIndex + chunkSize >= (index + maxSpan); + }); + return mergeRecs; + } + /** * Gets/Sets the array of data that populates the component. diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid.pipes.ts b/projects/igniteui-angular/src/lib/grids/grid/grid.pipes.ts index 2aa8eafd495..d9e5900c410 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid.pipes.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.pipes.ts @@ -99,15 +99,15 @@ export class IgxGridCellMergePipe implements PipeTransform { //TODO condition can be a strategy or some callback that the user can set. //TODO can also be limited to only sorted columns if ( prev && prev.recordRef[col.field] === rec[col.field]) { - // const root = prev.cellMergeMeta.get(col.field)?.root ?? prev; - // root.cellMergeMeta.get(col.field).rowSpan += 1; - // recData.cellMergeMeta.get(col.field).root = root; - recData.cellMergeMeta.get(col.field).prev = prev; - let curr = prev; - while(curr) { - curr.cellMergeMeta.get(col.field).rowSpan += 1; - curr = curr.cellMergeMeta.get(col.field).prev; - } + const root = prev.cellMergeMeta.get(col.field)?.root ?? prev; + root.cellMergeMeta.get(col.field).rowSpan += 1; + recData.cellMergeMeta.get(col.field).root = root; + // recData.cellMergeMeta.get(col.field).prev = prev; + // let curr = prev; + // while(curr) { + // curr.cellMergeMeta.get(col.field).rowSpan += 1; + // curr = curr.cellMergeMeta.get(col.field).prev; + // } } } prev = recData; diff --git a/src/app/grid-cellMerging/grid-cellMerging.component.html b/src/app/grid-cellMerging/grid-cellMerging.component.html index 6b2628ac345..bc1c1f1bc9c 100644 --- a/src/app/grid-cellMerging/grid-cellMerging.component.html +++ b/src/app/grid-cellMerging/grid-cellMerging.component.html @@ -1,5 +1,5 @@

Grid with cell merge

- + @@ -10,6 +10,4 @@

Grid with cell merge

- -
From bb4e39fcce4350ab6111f6291c00f5b72537c845 Mon Sep 17 00:00:00 2001 From: MKirova Date: Mon, 7 Jul 2025 14:46:10 +0300 Subject: [PATCH 04/54] chore(*): Fix border styles. --- projects/igniteui-angular/src/lib/grids/common/pipes.ts | 2 +- .../src/lib/grids/grid/grid-row.component.html | 8 +++++--- projects/igniteui-angular/src/lib/grids/row.directive.ts | 4 ++++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/common/pipes.ts b/projects/igniteui-angular/src/lib/grids/common/pipes.ts index 818a0ea3854..371245c96ad 100644 --- a/projects/igniteui-angular/src/lib/grids/common/pipes.ts +++ b/projects/igniteui-angular/src/lib/grids/common/pipes.ts @@ -126,7 +126,7 @@ export class IgxGridRowClassesPipe implements PipeTransform { [dirty, 'igx-grid__tr--edited'], [deleted, 'igx-grid__tr--deleted'], [dragging, 'igx-grid__tr--drag'], - [mrl, 'igx-grid__tr--mrl'], + [mrl || _rowData.cellMergeMeta, 'igx-grid__tr--mrl'], // Tree grid only [filteredOut, 'igx-grid__tr--filtered'] ]; diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html b/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html index d477ce770ad..6111ac5ec15 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html @@ -32,9 +32,10 @@ } @if (this.hasMergedCells) { -
@@ -173,6 +174,7 @@ Date: Mon, 7 Jul 2025 15:25:33 +0300 Subject: [PATCH 05/54] chore(*): Add handling for variable row height. --- .../src/lib/grids/grid/grid-row.component.html | 4 ++-- projects/igniteui-angular/src/lib/grids/row.directive.ts | 6 ++++-- src/app/grid-cellMerging/grid-cellMerging.component.html | 7 +++++++ src/app/grid-cellMerging/grid-cellMerging.component.ts | 4 ++++ 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html b/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html index 6111ac5ec15..a2391599eed 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html @@ -32,11 +32,11 @@ } @if (this.hasMergedCells) { -
diff --git a/projects/igniteui-angular/src/lib/grids/row.directive.ts b/projects/igniteui-angular/src/lib/grids/row.directive.ts index 7a44f1851d1..9ebf02488d6 100644 --- a/projects/igniteui-angular/src/lib/grids/row.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/row.directive.ts @@ -599,11 +599,13 @@ export class IgxRowDirective implements DoCheck, AfterViewInit, OnDestroy { protected getMergeCellSpan(col: ColumnType){ const rowCount = this.data.cellMergeMeta.get(col.field).rowSpan; - return `repeat(${rowCount},51px)`; + const rowH = this.grid.verticalScrollContainer.getSizeAt(this.index); + return `repeat(${rowCount},${rowH}px)`; } protected getRowHeight() { - return this.grid.rowHeight; + const size = this.grid.verticalScrollContainer.getSizeAt(this.index) - 1; + return size || this.grid.rowHeight; } /** diff --git a/src/app/grid-cellMerging/grid-cellMerging.component.html b/src/app/grid-cellMerging/grid-cellMerging.component.html index bc1c1f1bc9c..b5f66aaf5dc 100644 --- a/src/app/grid-cellMerging/grid-cellMerging.component.html +++ b/src/app/grid-cellMerging/grid-cellMerging.component.html @@ -1,6 +1,13 @@

Grid with cell merge

+ +
+ +
+ +
+
diff --git a/src/app/grid-cellMerging/grid-cellMerging.component.ts b/src/app/grid-cellMerging/grid-cellMerging.component.ts index c4184a997ae..0d79c827d11 100644 --- a/src/app/grid-cellMerging/grid-cellMerging.component.ts +++ b/src/app/grid-cellMerging/grid-cellMerging.component.ts @@ -1,6 +1,8 @@ import { Component, HostBinding, ViewChild } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { + IgxButtonDirective, + IgxCellTemplateDirective, IgxColumnComponent, IgxGridComponent, } from 'igniteui-angular'; @@ -15,6 +17,8 @@ import { data, dataWithoutPK } from '../shared/data'; FormsModule, IgxColumnComponent, IgxGridComponent, + IgxCellTemplateDirective, + IgxButtonDirective ] }) export class GridCellMergingComponent { From 4667bb083ba7d63982b95b333172fff51f9b5221 Mon Sep 17 00:00:00 2001 From: MKirova Date: Mon, 7 Jul 2025 15:44:31 +0300 Subject: [PATCH 06/54] chore(*): Add handling if different rows have different sizes. --- projects/igniteui-angular/src/lib/grids/row.directive.ts | 8 ++++++-- src/app/grid-cellMerging/grid-cellMerging.component.html | 7 +++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/row.directive.ts b/projects/igniteui-angular/src/lib/grids/row.directive.ts index 9ebf02488d6..d61923064bf 100644 --- a/projects/igniteui-angular/src/lib/grids/row.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/row.directive.ts @@ -599,8 +599,12 @@ export class IgxRowDirective implements DoCheck, AfterViewInit, OnDestroy { protected getMergeCellSpan(col: ColumnType){ const rowCount = this.data.cellMergeMeta.get(col.field).rowSpan; - const rowH = this.grid.verticalScrollContainer.getSizeAt(this.index); - return `repeat(${rowCount},${rowH}px)`; + let sizeSpans = ""; + for (let index = this.index; index < this.index + rowCount; index++) { + const size = this.grid.verticalScrollContainer.getSizeAt(index); + sizeSpans += size + 'px '; + } + return `${sizeSpans}`; } protected getRowHeight() { diff --git a/src/app/grid-cellMerging/grid-cellMerging.component.html b/src/app/grid-cellMerging/grid-cellMerging.component.html index b5f66aaf5dc..a745f0cb5ce 100644 --- a/src/app/grid-cellMerging/grid-cellMerging.component.html +++ b/src/app/grid-cellMerging/grid-cellMerging.component.html @@ -4,8 +4,11 @@

Grid with cell merge

-
- + @if (cell.row.index % 2 == 0) { +
+ + } +
From febbd43e19776bcfba9cd44e9e83ec6a42a93595 Mon Sep 17 00:00:00 2001 From: MKirova Date: Mon, 7 Jul 2025 16:53:10 +0300 Subject: [PATCH 07/54] chore(*): Extract hardcoded styles in class. --- .../styles/components/grid/_grid-component.scss | 4 ++++ .../core/styles/components/grid/_grid-theme.scss | 15 ++++++++++----- .../src/lib/grids/grid/grid-row.component.html | 2 -- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-component.scss b/projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-component.scss index f446ffbe128..93737fc219c 100644 --- a/projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-component.scss +++ b/projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-component.scss @@ -296,6 +296,10 @@ @extend %igx-grid__td--edited !optional; } + @include e(td, $m: merged) { + @extend %igx-grid__td--merged !optional; + } + @include e(td, $m: editing) { @extend %igx-grid__td--editing !optional; } diff --git a/projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-theme.scss b/projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-theme.scss index 0166b635863..47123b240b3 100644 --- a/projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-theme.scss +++ b/projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-theme.scss @@ -1872,6 +1872,11 @@ } } + %igx-grid__td--merged { + z-index: 1; + grid-row: 1 / -1; + } + %igx-grid__tr--deleted { %grid-cell-text { font-style: italic; @@ -2084,7 +2089,7 @@ .sort-icon { color: var-get($theme, 'header-selected-text-color'); - + ::after { background: var-get($theme, 'header-selected-background'); } @@ -2112,7 +2117,7 @@ &%igx-grid-th--sorted { .sort-icon { color: var-get($theme, 'header-selected-text-color'); - + > igx-icon { color: inherit; } @@ -2120,7 +2125,7 @@ &:focus, &:hover { color: var-get($theme, 'header-selected-text-color'); - + > igx-icon { color: inherit; } @@ -2177,14 +2182,14 @@ .sort-icon { opacity: 1; color: var-get($theme, 'sorted-header-icon-color'); - + > igx-icon { color: inherit; } &:hover { color: var-get($theme, 'sortable-header-icon-hover-color'); - + > igx-icon { color: inherit; } diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html b/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html index a2391599eed..c87ab17861c 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html @@ -195,8 +195,6 @@ [active]="isCellActive(col.visibleIndex)" [cellSelectionMode]="grid.cellSelection" [displayPinnedChip]="shouldDisplayPinnedChip(col.visibleIndex)" - [style.gridRow]="'1 / -1'" - [style.zIndex]="data.cellMergeMeta.get(col.field).rowSpan" [style.min-width]="col.resolvedWidth" [style.max-width]="col.resolvedWidth" [style.flex-basis]="col.resolvedWidth" From 4c5d922f1e287f9ac160df2c1385b4f42eb7c70a Mon Sep 17 00:00:00 2001 From: MKirova Date: Mon, 7 Jul 2025 17:27:53 +0300 Subject: [PATCH 08/54] chore(*): Adjust API and members. --- .../src/lib/grids/columns/column.component.ts | 9 +++++++++ .../src/lib/grids/common/enums.ts | 1 - .../src/lib/grids/common/grid.interface.ts | 1 + .../src/lib/grids/grid-base.directive.ts | 2 +- .../src/lib/grids/grid/grid-row.component.html | 8 ++++---- .../src/lib/grids/grid/grid.pipes.ts | 18 ++++++++---------- .../grid-cellMerging.component.html | 6 +++--- 7 files changed, 26 insertions(+), 19 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/columns/column.component.ts b/projects/igniteui-angular/src/lib/grids/columns/column.component.ts index 3482a5076f1..0809995f477 100644 --- a/projects/igniteui-angular/src/lib/grids/columns/column.component.ts +++ b/projects/igniteui-angular/src/lib/grids/columns/column.component.ts @@ -107,6 +107,15 @@ export class IgxColumnComponent implements AfterContentInit, OnDestroy, ColumnTy return this._field; } + /** + * Sets/gets whether to merge cells in this column. + * ```html + * + * ``` + * + */ + @Input() + public merge = false; /** * @hidden @internal diff --git a/projects/igniteui-angular/src/lib/grids/common/enums.ts b/projects/igniteui-angular/src/lib/grids/common/enums.ts index bd66328c97e..fd19aa101ca 100644 --- a/projects/igniteui-angular/src/lib/grids/common/enums.ts +++ b/projects/igniteui-angular/src/lib/grids/common/enums.ts @@ -81,7 +81,6 @@ export type GridSelectionMode = (typeof GridSelectionMode)[keyof typeof GridSele * - 'onSort': Only merge cells in column that are sorted. */ export const GridCellMergeMode = { - never: 'never', always: 'always', onSort: 'onSort' } as const; diff --git a/projects/igniteui-angular/src/lib/grids/common/grid.interface.ts b/projects/igniteui-angular/src/lib/grids/common/grid.interface.ts index 3b8d8d2e098..259e621c056 100644 --- a/projects/igniteui-angular/src/lib/grids/common/grid.interface.ts +++ b/projects/igniteui-angular/src/lib/grids/common/grid.interface.ts @@ -453,6 +453,7 @@ export interface ColumnType extends FieldType { pinned: boolean; /** Indicates if the column is currently expanded or collapsed. If the value is true, the column is expanded */ expanded: boolean; + merge: boolean; /** Indicates if the column is currently selected. If the value is true, the column is selected */ selected: boolean; /** Indicates if the column can be selected. If the value is true, the column can be selected */ diff --git a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts index 659e5e6e18c..b5a0123f568 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts @@ -2918,7 +2918,7 @@ export abstract class IgxGridBaseDirective implements GridType, */ @WatchChanges() @Input() - public cellMergeMode: GridCellMergeMode = GridCellMergeMode.never; + public cellMergeMode: GridCellMergeMode = GridCellMergeMode.onSort; /** * Gets/Sets row selection mode diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html b/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html index c87ab17861c..0489078ac7d 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html @@ -32,11 +32,11 @@ } @if (this.hasMergedCells) { -
@@ -174,7 +174,7 @@ x.merge && (mergeMode ==='always' || + (mergeMode === 'onSort' && !!sortExpr.find( x=> x.fieldName === x.fieldName))) + ); + if (columnToMerge.length === 0) { return collection; } - const visibleColumns = this.grid.visibleColumns; let prev = null; let result = []; for (const rec of collection) { let recData = { recordRef: rec, cellMergeMeta: new Map() }; - for (const col of visibleColumns) { + for (const col of columnToMerge) { recData.cellMergeMeta.set(col.field, { rowSpan: 1 }); //TODO condition can be a strategy or some callback that the user can set. - //TODO can also be limited to only sorted columns if ( prev && prev.recordRef[col.field] === rec[col.field]) { const root = prev.cellMergeMeta.get(col.field)?.root ?? prev; root.cellMergeMeta.get(col.field).rowSpan += 1; recData.cellMergeMeta.get(col.field).root = root; - // recData.cellMergeMeta.get(col.field).prev = prev; - // let curr = prev; - // while(curr) { - // curr.cellMergeMeta.get(col.field).rowSpan += 1; - // curr = curr.cellMergeMeta.get(col.field).prev; - // } } } prev = recData; diff --git a/src/app/grid-cellMerging/grid-cellMerging.component.html b/src/app/grid-cellMerging/grid-cellMerging.component.html index a745f0cb5ce..ca4bbb3d153 100644 --- a/src/app/grid-cellMerging/grid-cellMerging.component.html +++ b/src/app/grid-cellMerging/grid-cellMerging.component.html @@ -12,11 +12,11 @@

Grid with cell merge

- + - + - + From a42b192b63d62753ffc0f19b30a103ccd9ee7e8f Mon Sep 17 00:00:00 2001 From: MKirova Date: Tue, 8 Jul 2025 16:33:27 +0300 Subject: [PATCH 09/54] chore(*): Add mergeStrategy for grid and merging comparer for column. --- .../src/lib/data-operations/data-util.ts | 19 +++++- .../src/lib/data-operations/merge-strategy.ts | 67 +++++++++++++++++++ .../src/lib/grids/columns/column.component.ts | 26 +++++++ .../src/lib/grids/common/grid.interface.ts | 3 + .../src/lib/grids/grid-base.directive.ts | 13 ++++ .../src/lib/grids/grid/grid.pipes.ts | 23 +------ .../src/lib/grids/row.directive.ts | 1 - .../grid-cellMerging.component.html | 6 +- .../grid-cellMerging.component.ts | 24 +++---- 9 files changed, 143 insertions(+), 39 deletions(-) create mode 100644 projects/igniteui-angular/src/lib/data-operations/merge-strategy.ts diff --git a/projects/igniteui-angular/src/lib/data-operations/data-util.ts b/projects/igniteui-angular/src/lib/data-operations/data-util.ts index cc0e0d5ad1c..d3e66f19248 100644 --- a/projects/igniteui-angular/src/lib/data-operations/data-util.ts +++ b/projects/igniteui-angular/src/lib/data-operations/data-util.ts @@ -8,7 +8,7 @@ import { IGroupingState } from './groupby-state.interface'; import { mergeObjects } from '../core/utils'; import { Transaction, TransactionType, HierarchicalTransaction } from '../services/transaction/transaction'; import { getHierarchy, isHierarchyMatch } from './operations'; -import { GridType } from '../grids/common/grid.interface'; +import { ColumnType, GridType } from '../grids/common/grid.interface'; import { ITreeGridRecord } from '../grids/tree-grid/tree-grid.interfaces'; import { ISortingExpression } from './sorting-strategy'; import { @@ -20,6 +20,7 @@ import { } from '../grids/common/strategy'; import { DefaultDataCloneStrategy, IDataCloneStrategy } from '../data-operations/data-clone-strategy'; import { IGroupingExpression } from './grouping-expression.interface'; +import { DefaultMergeStrategy, IGridMergeStrategy } from './merge-strategy'; /** * @hidden @@ -37,6 +38,13 @@ import { IGroupingExpression } from './grouping-expression.interface'; } as const; export type DataType = (typeof DataType)[keyof typeof DataType]; + +export interface IMergeByResult { + rowSpan: number; + root?: any; + prev?: any; +} + /** * @hidden */ @@ -90,6 +98,15 @@ export class DataUtil { return grouping.groupBy(data, state, grid, groupsRecords, fullResult); } + public static merge(data: T[], columns: ColumnType[], strategy: IGridMergeStrategy = new DefaultMergeStrategy(), grid: GridType = null, + ): any[] { + let result = []; + for (const col of columns) { + strategy.merge(data, col.field, col.mergingComparer, result); + } + return result; +} + public static page(data: T[], state: IPagingState, dataLength?: number): T[] { if (!state) { return data; diff --git a/projects/igniteui-angular/src/lib/data-operations/merge-strategy.ts b/projects/igniteui-angular/src/lib/data-operations/merge-strategy.ts new file mode 100644 index 00000000000..c9afd6fc3f9 --- /dev/null +++ b/projects/igniteui-angular/src/lib/data-operations/merge-strategy.ts @@ -0,0 +1,67 @@ +import { GridCellMergeMode, IMergeByResult } from 'igniteui-angular'; +import type { KeyOfOrString } from '../core/types'; +import { IBaseEventArgs } from '../core/utils'; +import { ColumnType, GridType } from '../grids/common/grid.interface'; + + +export interface IGridMergeStrategy { + /* blazorSuppress */ + merge: ( + data: any[], + field: string, + comparer: (prevRecord: any, currentRecord: any, field: string) => boolean, + result: any[] + ) => any[]; +} + +export class DefaultMergeStrategy implements IGridMergeStrategy { + protected static _instance: DefaultMergeStrategy = null; + + public static instance(): DefaultMergeStrategy { + return this._instance || (this._instance = new this()); + } + + /* blazorSuppress */ + public merge( + data: any[], + field: string, + comparer: (prevRecord: any, record: any, field: string) => boolean = this.comparer, + result: any[] + ) { + let prev = null; + let index = 0; + for (const rec of data) { + const recData = result[index]; + let recToUpdateData = recData ?? { recordRef: rec, cellMergeMeta: new Map() }; + recToUpdateData.cellMergeMeta.set(field, { rowSpan: 1 }); + if (prev && comparer(prev.recordRef, recToUpdateData.recordRef, field)) { + const root = prev.cellMergeMeta.get(field)?.root ?? prev; + root.cellMergeMeta.get(field).rowSpan += 1; + recToUpdateData.cellMergeMeta.get(field).root = root; + } + prev = recToUpdateData; + if (!recData) { + result.push(recToUpdateData); + } + index++; + } + return result; + } + + /* blazorSuppress */ + public comparer(prevRecord: any, record: any, field: string): boolean { + const a = prevRecord[field]; + const b = record[field]; + const an = (a === null || a === undefined); + const bn = (b === null || b === undefined); + if (an) { + if (bn) { + return true; + } + return false; + } else if (bn) { + return false; + } + return a === b; + } +} diff --git a/projects/igniteui-angular/src/lib/grids/columns/column.component.ts b/projects/igniteui-angular/src/lib/grids/columns/column.component.ts index 0809995f477..58b3de78586 100644 --- a/projects/igniteui-angular/src/lib/grids/columns/column.component.ts +++ b/projects/igniteui-angular/src/lib/grids/columns/column.component.ts @@ -1209,6 +1209,30 @@ export class IgxColumnComponent implements AfterContentInit, OnDestroy, ColumnTy this._sortStrategy = classRef; } + /* blazorSuppress */ + /** + * Gets the function that compares values for merging. + * ```typescript + * let mergingComparer = this.column.mergingComparer' + * ``` + */ + @Input() + public get mergingComparer(): (prevRecord: any, record: any, field: string) => boolean { + return this._mergingComparer; + } + + /* blazorSuppress */ + /** + * Sets a custom function to compare values for merging. + * ```typescript + * this.column.mergingComparer = (prevRecord: any, record: any, field: string) => { return prevRecord[field] === record[field]; } + * ``` + */ + public set mergingComparer(funcRef: (prevRecord: any, record: any, field: string) => boolean) { + this._mergingComparer = funcRef; + } + + /* blazorSuppress */ /** * Gets the function that compares values for grouping. @@ -1849,6 +1873,8 @@ export class IgxColumnComponent implements AfterContentInit, OnDestroy, ColumnTy * @hidden */ protected _groupingComparer: (a: any, b: any, currRec?: any, groupRec?: any) => number; + + protected _mergingComparer: (prevRecord: any, record: any, field: string) => boolean; /** * @hidden */ diff --git a/projects/igniteui-angular/src/lib/grids/common/grid.interface.ts b/projects/igniteui-angular/src/lib/grids/common/grid.interface.ts index 259e621c056..308c6be4084 100644 --- a/projects/igniteui-angular/src/lib/grids/common/grid.interface.ts +++ b/projects/igniteui-angular/src/lib/grids/common/grid.interface.ts @@ -38,6 +38,7 @@ import { IDimensionsChange, IPivotConfiguration, IPivotDimension, IPivotKeys, IP import { IDataCloneStrategy } from '../../data-operations/data-clone-strategy'; import { FormControl, FormGroup, ValidationErrors } from '@angular/forms'; import { IgxGridValidationService } from '../grid/grid-validation.service'; +import { IGridMergeStrategy } from '../../data-operations/merge-strategy'; export const IGX_GRID_BASE = /*@__PURE__*/new InjectionToken('IgxGridBaseToken'); export const IGX_GRID_SERVICE_BASE = /*@__PURE__*/new InjectionToken('IgxGridServiceBaseToken'); @@ -335,6 +336,7 @@ export interface ColumnType extends FieldType { /** @hidden @internal */ headerCell: any; validators: any[]; + mergingComparer: (prevRecord: any, record: any, field: string) => boolean; /** * The template reference for the custom header of the column @@ -692,6 +694,7 @@ export interface GridType extends IGridDataBindable { /** Represents the locale of the grid: `USD`, `EUR`, `GBP`, `CNY`, `JPY`, etc. */ locale: string; cellMergeMode: GridCellMergeMode; + mergeStrategy: IGridMergeStrategy; resourceStrings: IGridResourceStrings; /* blazorSuppress */ /** Represents the native HTML element itself */ diff --git a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts index b5a0123f568..b2baeb1cc30 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts @@ -185,6 +185,7 @@ import { IgxGridValidationService } from './grid/grid-validation.service'; import { getCurrentResourceStrings } from '../core/i18n/resources'; import { isTree, recreateTree, recreateTreeFromFields } from '../data-operations/expressions-tree-util'; import { getUUID } from './common/random'; +import { IGridMergeStrategy } from '../data-operations/merge-strategy'; interface IMatchInfoCache { row: any; @@ -2494,6 +2495,18 @@ export abstract class IgxGridBaseDirective implements GridType, this._sortingStrategy = value; } + + /** + * Gets/Sets the merge strategy of the grid. + * + * @example + * ```html + * + * ``` + */ + @Input() + public mergeStrategy: IGridMergeStrategy; + /** * Gets/Sets the sorting options - single or multiple sorting. * Accepts an `ISortingOptions` object with any of the `mode` properties. diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid.pipes.ts b/projects/igniteui-angular/src/lib/grids/grid/grid.pipes.ts index 73d87e9c00d..43267186900 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid.pipes.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.pipes.ts @@ -95,32 +95,11 @@ export class IgxGridCellMergePipe implements PipeTransform { if (columnToMerge.length === 0) { return collection; } - let prev = null; - let result = []; - for (const rec of collection) { - let recData = { recordRef: rec, cellMergeMeta: new Map() }; - for (const col of columnToMerge) { - recData.cellMergeMeta.set(col.field, { rowSpan: 1 }); - //TODO condition can be a strategy or some callback that the user can set. - if ( prev && prev.recordRef[col.field] === rec[col.field]) { - const root = prev.cellMergeMeta.get(col.field)?.root ?? prev; - root.cellMergeMeta.get(col.field).rowSpan += 1; - recData.cellMergeMeta.get(col.field).root = root; - } - } - prev = recData; - result.push(recData); - } + const result = DataUtil.merge(cloneArray(collection), columnToMerge, this.grid.mergeStrategy, this.grid); return result; } } -export interface IMergeByResult { - rowSpan: number; - root?: any; - prev?: any; -} - /** * @hidden */ diff --git a/projects/igniteui-angular/src/lib/grids/row.directive.ts b/projects/igniteui-angular/src/lib/grids/row.directive.ts index d61923064bf..38dde79226c 100644 --- a/projects/igniteui-angular/src/lib/grids/row.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/row.directive.ts @@ -27,7 +27,6 @@ import { mergeWith } from 'lodash-es'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { trackByIdentity } from '../core/utils'; -import { IMergeByResult } from './grid/grid.pipes'; @Directive({ selector: '[igxRowBaseComponent]', diff --git a/src/app/grid-cellMerging/grid-cellMerging.component.html b/src/app/grid-cellMerging/grid-cellMerging.component.html index ca4bbb3d153..9d7d1814595 100644 --- a/src/app/grid-cellMerging/grid-cellMerging.component.html +++ b/src/app/grid-cellMerging/grid-cellMerging.component.html @@ -3,11 +3,11 @@

Grid with cell merge

- - @if (cell.row.index % 2 == 0) { + +
diff --git a/src/app/grid-cellMerging/grid-cellMerging.component.ts b/src/app/grid-cellMerging/grid-cellMerging.component.ts index 0d79c827d11..7443240d731 100644 --- a/src/app/grid-cellMerging/grid-cellMerging.component.ts +++ b/src/app/grid-cellMerging/grid-cellMerging.component.ts @@ -190,7 +190,7 @@ export class GridCellMergingComponent { OrderDate2: new Date(1991, 2, 12, 15, 40, 50).toISOString() }, { - ProductID: 1, + ProductID: 13, ProductName: 'Chai', SupplierID: 1, CategoryID: 1, @@ -203,7 +203,7 @@ export class GridCellMergingComponent { OrderDate: null, OrderDate2: new Date(1991, 2, 12, 18, 40, 50).toISOString() }, { - ProductID: 2, + ProductID: 14, ProductName: 'Chai', SupplierID: 1, CategoryID: 1, @@ -217,7 +217,7 @@ export class GridCellMergingComponent { OrderDate2: new Date('2003-03-17').toISOString() }, { - ProductID: 3, + ProductID: 15, ProductName: 'Chai', SupplierID: 1, CategoryID: 1, @@ -231,7 +231,7 @@ export class GridCellMergingComponent { OrderDate2: new Date('2003-03-17').toISOString() }, { - ProductID: 4, + ProductID: 16, ProductName: 'Chai', SupplierID: 1, CategoryID: 1, @@ -245,7 +245,7 @@ export class GridCellMergingComponent { OrderDate2: new Date('2003-03-17').toISOString() }, { - ProductID: 5, + ProductID: 17, ProductName: 'Chai', SupplierID: 1, CategoryID: 1, @@ -259,7 +259,7 @@ export class GridCellMergingComponent { OrderDate2: new Date('2003-03-17').toISOString() }, { - ProductID: 6, + ProductID: 18, ProductName: 'Chang', SupplierID: 1, CategoryID: 1, @@ -273,7 +273,7 @@ export class GridCellMergingComponent { OrderDate2: new Date('2003-03-17').toISOString() }, { - ProductID: 7, + ProductID: 19, ProductName: 'Chang', SupplierID: 1, CategoryID: 1, @@ -287,7 +287,7 @@ export class GridCellMergingComponent { OrderDate2: new Date('2003-03-17').toISOString() }, { - ProductID: 8, + ProductID: 20, ProductName: 'Chang', SupplierID: 1, CategoryID: 1, @@ -301,7 +301,7 @@ export class GridCellMergingComponent { OrderDate2: new Date('2003-03-17').toISOString() }, { - ProductID: 9, + ProductID: 21, ProductName: 'Aniseed Syrup', SupplierID: 1, CategoryID: 2, @@ -315,7 +315,7 @@ export class GridCellMergingComponent { OrderDate2: new Date(1991, 2, 12, 15, 40, 50).toISOString() }, { - ProductID: 10, + ProductID: 22, ProductName: 'Chang', SupplierID: 1, CategoryID: 2, @@ -329,7 +329,7 @@ export class GridCellMergingComponent { OrderDate2: new Date(1991, 2, 12, 15, 40, 50).toISOString() }, { - ProductID: 11, + ProductID: 23, ProductName: 'Chai', SupplierID: 1, CategoryID: 2, @@ -343,7 +343,7 @@ export class GridCellMergingComponent { OrderDate2: new Date(1991, 2, 12, 15, 40, 50).toISOString() }, { - ProductID: 12, + ProductID: 24, ProductName: 'Chai', SupplierID: 1, CategoryID: 2, From 13619527242455f4da928093c97ec0c63b7bec48 Mon Sep 17 00:00:00 2001 From: MKirova Date: Wed, 9 Jul 2025 13:12:20 +0300 Subject: [PATCH 10/54] chore(*): Adjust pipe triggers. --- .../src/lib/grids/grid/grid.component.html | 2 +- .../igniteui-angular/src/lib/grids/grid/grid.pipes.ts | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid.component.html b/projects/igniteui-angular/src/lib/grids/grid/grid.component.html index fc0df2e4644..3af125f1e03 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid.component.html +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.component.html @@ -89,7 +89,7 @@ | gridDetails:hasDetails:expansionStates:pipeTrigger | gridAddRow:false:pipeTrigger | gridRowPinning:id:false:pipeTrigger - | gridCellMerge:pipeTrigger" + | gridCellMerge:visibleColumns:cellMergeMode:sortingExpressions:pipeTrigger" let-rowIndex="index" [igxForScrollOrientation]="'vertical'" [igxForScrollContainer]="verticalScroll" [igxForContainerSize]="calcHeight" [igxForItemSize]="hasColumnLayouts ? rowHeight * multiRowLayoutRowSize + 1 : renderedRowHeight" diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid.pipes.ts b/projects/igniteui-angular/src/lib/grids/grid/grid.pipes.ts index 43267186900..77d52bcbad1 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid.pipes.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.pipes.ts @@ -5,7 +5,7 @@ import { IGroupByExpandState } from '../../data-operations/groupby-expand-state. import { IGroupByResult } from '../../data-operations/grouping-result.interface'; import { IFilteringExpressionsTree, FilteringExpressionsTree } from '../../data-operations/filtering-expressions-tree'; import { IGroupingExpression } from '../../data-operations/grouping-expression.interface'; -import { GridType, IGX_GRID_BASE } from '../common/grid.interface'; +import { ColumnType, GridType, IGX_GRID_BASE } from '../common/grid.interface'; import { FilterUtil, IFilteringStrategy } from '../../data-operations/filtering-strategy'; import { ISortingExpression } from '../../data-operations/sorting-strategy'; import { IGridSortingStrategy, IGridGroupingStrategy } from '../common/strategy'; @@ -85,10 +85,8 @@ export class IgxGridCellMergePipe implements PipeTransform { constructor(@Inject(IGX_GRID_BASE) private grid: GridType) { } - public transform(collection: any, _pipeTrigger: number) { - const mergeMode = this.grid.cellMergeMode; - const sortExpr = this.grid.sortingExpressions; - const columnToMerge = this.grid.visibleColumns.filter( + public transform(collection: any, visibleColumns: ColumnType[], mergeMode: GridCellMergeMode, sortExpr: ISortingExpression[], _pipeTrigger: number) { + const columnToMerge = visibleColumns.filter( x => x.merge && (mergeMode ==='always' || (mergeMode === 'onSort' && !!sortExpr.find( x=> x.fieldName === x.fieldName))) ); From 407357f5e58adcd6eb26a18d9d11cdae605cad88 Mon Sep 17 00:00:00 2001 From: MKirova Date: Wed, 9 Jul 2025 14:42:42 +0300 Subject: [PATCH 11/54] chore(*): Re-use cell templates. --- .../src/lib/grids/common/pipes.ts | 3 +- .../src/lib/grids/grid-base.directive.ts | 2 +- .../lib/grids/grid/grid-row.component.html | 42 +++---------------- .../src/lib/grids/grid/grid.component.html | 14 +++---- .../src/lib/grids/grid/grid.component.ts | 5 ++- .../hierarchical-grid.component.html | 4 +- .../pivot-grid/pivot-grid.component.html | 2 +- .../src/lib/grids/row.directive.ts | 10 ++++- .../grids/tree-grid/tree-grid.component.html | 4 +- .../grid-cellMerging.component.html | 6 ++- .../grid-cellMerging.component.ts | 4 +- 11 files changed, 39 insertions(+), 57 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/common/pipes.ts b/projects/igniteui-angular/src/lib/grids/common/pipes.ts index 371245c96ad..27d0667fbeb 100644 --- a/projects/igniteui-angular/src/lib/grids/common/pipes.ts +++ b/projects/igniteui-angular/src/lib/grids/common/pipes.ts @@ -115,6 +115,7 @@ export class IgxGridRowClassesPipe implements PipeTransform { dragging: boolean, index: number, mrl: boolean, + merged: boolean, filteredOut: boolean, _rowData: any, _: number @@ -126,7 +127,7 @@ export class IgxGridRowClassesPipe implements PipeTransform { [dirty, 'igx-grid__tr--edited'], [deleted, 'igx-grid__tr--deleted'], [dragging, 'igx-grid__tr--drag'], - [mrl || _rowData.cellMergeMeta, 'igx-grid__tr--mrl'], + [mrl || merged, 'igx-grid__tr--mrl'], // Tree grid only [filteredOut, 'igx-grid__tr--filtered'] ]; diff --git a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts index b2baeb1cc30..44db042c792 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts @@ -3662,7 +3662,7 @@ export abstract class IgxGridBaseDirective implements GridType, * @internal */ public isRecordMerged(rec) { - return rec.cellMergeMeta; + return rec?.cellMergeMeta; } public getMergeCellOffset(rec) { diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html b/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html index 0489078ac7d..dd1eac61f3f 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html @@ -32,13 +32,13 @@ } @if (this.hasMergedCells) { -
- +
} @else { @@ -108,6 +108,7 @@ - - - - - } @@ -105,15 +105,15 @@ - - + - + diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid.component.ts b/projects/igniteui-angular/src/lib/grids/grid/grid.component.ts index d1d0a3fa116..8395d09cf5e 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.component.ts @@ -960,13 +960,14 @@ export class IgxGridComponent extends IgxGridBaseDirective implements GridType, } } return { - $implicit: this.isGhostRecord(rowData) ? rowData.recordRef : rowData, + $implicit: this.isGhostRecord(rowData) || this.isRecordMerged(rowData) ? rowData.recordRef : rowData, index: this.getDataViewIndex(rowIndex, pinned), templateID: { type: this.isGroupByRecord(rowData) ? 'groupRow' : this.isSummaryRow(rowData) ? 'summaryRow' : 'dataRow', id: null }, - disabled: this.isGhostRecord(rowData) + disabled: this.isGhostRecord(rowData), + metaData: rowData }; } diff --git a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.html b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.html index d5f31ada6b4..59be7cefe5b 100644 --- a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.html +++ b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.html @@ -75,14 +75,14 @@ diff --git a/projects/igniteui-angular/src/lib/grids/pivot-grid/pivot-grid.component.html b/projects/igniteui-angular/src/lib/grids/pivot-grid/pivot-grid.component.html index 9ca659192f9..95089b96554 100644 --- a/projects/igniteui-angular/src/lib/grids/pivot-grid/pivot-grid.component.html +++ b/projects/igniteui-angular/src/lib/grids/pivot-grid/pivot-grid.component.html @@ -52,7 +52,7 @@
diff --git a/projects/igniteui-angular/src/lib/grids/row.directive.ts b/projects/igniteui-angular/src/lib/grids/row.directive.ts index 38dde79226c..d4e4b6ae5c6 100644 --- a/projects/igniteui-angular/src/lib/grids/row.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/row.directive.ts @@ -45,6 +45,12 @@ export class IgxRowDirective implements DoCheck, AfterViewInit, OnDestroy { @HostBinding('attr.role') public role = 'row'; + /** + * @hidden + */ + @Input() + public metaData: any; + /** * The data passed to the row component. * @@ -118,7 +124,7 @@ export class IgxRowDirective implements DoCheck, AfterViewInit, OnDestroy { } public get hasMergedCells(): boolean { - return this.grid.isRecordMerged(this.data); + return this.grid.isRecordMerged(this.metaData); } /** @@ -597,7 +603,7 @@ export class IgxRowDirective implements DoCheck, AfterViewInit, OnDestroy { } protected getMergeCellSpan(col: ColumnType){ - const rowCount = this.data.cellMergeMeta.get(col.field).rowSpan; + const rowCount = this.metaData.cellMergeMeta.get(col.field).rowSpan; let sizeSpans = ""; for (let index = this.index; index < this.index + rowCount; index++) { const size = this.grid.verticalScrollContainer.getSizeAt(index); diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.html b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.html index 2521cc28e58..5db61b5629c 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.html +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.html @@ -78,13 +78,13 @@ diff --git a/src/app/grid-cellMerging/grid-cellMerging.component.html b/src/app/grid-cellMerging/grid-cellMerging.component.html index 9d7d1814595..da15f25881d 100644 --- a/src/app/grid-cellMerging/grid-cellMerging.component.html +++ b/src/app/grid-cellMerging/grid-cellMerging.component.html @@ -1,5 +1,5 @@

Grid with cell merge

- +
@@ -12,7 +12,7 @@

Grid with cell merge

- + @@ -20,4 +20,6 @@

Grid with cell merge

+ +
diff --git a/src/app/grid-cellMerging/grid-cellMerging.component.ts b/src/app/grid-cellMerging/grid-cellMerging.component.ts index 7443240d731..527180f1a09 100644 --- a/src/app/grid-cellMerging/grid-cellMerging.component.ts +++ b/src/app/grid-cellMerging/grid-cellMerging.component.ts @@ -5,6 +5,7 @@ import { IgxCellTemplateDirective, IgxColumnComponent, IgxGridComponent, + IgxPaginatorComponent, } from 'igniteui-angular'; import { data, dataWithoutPK } from '../shared/data'; @@ -18,7 +19,8 @@ import { data, dataWithoutPK } from '../shared/data'; IgxColumnComponent, IgxGridComponent, IgxCellTemplateDirective, - IgxButtonDirective + IgxButtonDirective, + IgxPaginatorComponent ] }) export class GridCellMergingComponent { From f23dd6cf4ed5915851605635c4c541d9397acdde Mon Sep 17 00:00:00 2001 From: MKirova Date: Wed, 9 Jul 2025 14:51:01 +0300 Subject: [PATCH 12/54] chore(*): Fix row indexes in merged row area. --- .../igniteui-angular/src/lib/grids/grid/grid.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid.component.html b/projects/igniteui-angular/src/lib/grids/grid/grid.component.html index 2f84f207d22..fdb8366e80b 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid.component.html +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.component.html @@ -50,7 +50,7 @@ @for (rowData of mergedData; track rowData; let rowIndex = $index) { - From f6ffa428065517c39a3168dd4260468c29463dce Mon Sep 17 00:00:00 2001 From: MKirova Date: Wed, 9 Jul 2025 18:22:03 +0300 Subject: [PATCH 13/54] chore(*): Adjust some styles for row selection. --- .../core/styles/components/grid/_grid-theme.scss | 14 ++++++++++++++ .../grid-cellMerging.component.html | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-theme.scss b/projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-theme.scss index 47123b240b3..37d8794db01 100644 --- a/projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-theme.scss +++ b/projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-theme.scss @@ -1825,6 +1825,20 @@ color: var-get($theme, 'row-selected-text-color'); background: var-get($theme, 'row-selected-background'); + &%grid-row--mrl { + background: transparent; + %grid-mrl-block { + color: var-get($theme, 'row-selected-text-color'); + background: var-get($theme, 'row-selected-background'); + %igx-grid__td--merged { + color: var-get($theme, 'row-selected-text-color'); + background: var-get($theme, 'row-selected-background'); + } + } + } + + + %grid-cell--selected, %grid-cell--pinned-selected { color: var-get($theme, 'cell-selected-within-text-color'); diff --git a/src/app/grid-cellMerging/grid-cellMerging.component.html b/src/app/grid-cellMerging/grid-cellMerging.component.html index da15f25881d..21d28741a49 100644 --- a/src/app/grid-cellMerging/grid-cellMerging.component.html +++ b/src/app/grid-cellMerging/grid-cellMerging.component.html @@ -1,5 +1,5 @@

Grid with cell merge

- +
From 4d1b981a518b1a430903b35c4d3c02029a8b4807 Mon Sep 17 00:00:00 2001 From: MKirova Date: Thu, 10 Jul 2025 16:15:28 +0300 Subject: [PATCH 14/54] chore(*): Merged cells navigation. --- .../src/lib/grids/grid-base.directive.ts | 18 +++ .../grids/grid-merge-navigation.service.ts | 108 ++++++++++++++++++ .../src/lib/grids/grid/grid.component.ts | 3 +- .../src/lib/grids/row.directive.ts | 6 + 4 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 projects/igniteui-angular/src/lib/grids/grid-merge-navigation.service.ts diff --git a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts index 44db042c792..e9d3ec5710c 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts @@ -186,6 +186,7 @@ import { getCurrentResourceStrings } from '../core/i18n/resources'; import { isTree, recreateTree, recreateTreeFromFields } from '../data-operations/expressions-tree-util'; import { getUUID } from './common/random'; import { IGridMergeStrategy } from '../data-operations/merge-strategy'; +import { IgxGridMergeNavigationService } from './grid-merge-navigation.service'; interface IMatchInfoCache { row: any; @@ -3984,6 +3985,23 @@ export abstract class IgxGridBaseDirective implements GridType, if (this.actionStrip) { this.actionStrip.menuOverlaySettings.outlet = this.outlet; } + this._setupNavigationService(); + } + + protected _setupNavigationService() { + if (this.hasCellsToMerge) { + this.navigation = new IgxGridMergeNavigationService(this.platform); + this.navigation.grid = this; + } + } + + protected get hasCellsToMerge() { + const columnToMerge = this.visibleColumns.filter( + x => x.merge && (this.cellMergeMode ==='always' || + (this.cellMergeMode === 'onSort' && !!this.sortingExpressions + .find( x=> x.fieldName === x.fieldName))) + ); + return columnToMerge.length > 0; } /** diff --git a/projects/igniteui-angular/src/lib/grids/grid-merge-navigation.service.ts b/projects/igniteui-angular/src/lib/grids/grid-merge-navigation.service.ts new file mode 100644 index 00000000000..295f100a30d --- /dev/null +++ b/projects/igniteui-angular/src/lib/grids/grid-merge-navigation.service.ts @@ -0,0 +1,108 @@ +import { Injectable } from '@angular/core'; +import { IgxGridNavigationService } from './grid-navigation.service'; +import { first } from 'rxjs/operators'; +/** @hidden */ +@Injectable() +export class IgxGridMergeNavigationService extends IgxGridNavigationService { + public override shouldPerformVerticalScroll(targetRowIndex: number, visibleColIndex: number): boolean { + const targetRec = this.grid.verticalScrollContainer.igxForOf[targetRowIndex]; + const field = this.grid.visibleColumns[visibleColIndex]?.field; + const rowSpan = targetRec?.cellMergeMeta?.get(field)?.rowSpan; + if (rowSpan > 1) { + const targetRow = super.getRowElementByIndex(targetRowIndex); + const containerHeight = this.grid.calcHeight ? Math.ceil(this.grid.calcHeight) : 0; + const scrollPos = this.getVerticalScrollPositions(targetRowIndex, rowSpan); + return (!targetRow || targetRow.offsetTop < Math.abs(this.containerTopOffset) + || containerHeight && containerHeight < scrollPos.rowBottom - Math.ceil(this.scrollTop)); + } else { + return super.shouldPerformVerticalScroll(targetRowIndex, visibleColIndex); + } + } + + public override performVerticalScrollToCell(rowIndex: number, visibleColIndex: number, cb?: () => void) { + const targetRec = this.grid.verticalScrollContainer.igxForOf[rowIndex]; + const field = this.grid.visibleColumns[visibleColIndex]?.field; + const rowSpan = targetRec?.cellMergeMeta?.get(field)?.rowSpan; + if (rowSpan > 1) { + const containerHeight = this.grid.calcHeight ? Math.ceil(this.grid.calcHeight) : 0; + const pos = this.getVerticalScrollPositions(rowIndex, rowSpan); + const row = super.getRowElementByIndex(rowIndex); + if ((this.scrollTop > pos.rowTop) && (!row || row.offsetTop < Math.abs(this.containerTopOffset))) { + if (pos.topOffset === 0) { + this.grid.verticalScrollContainer.scrollTo(rowIndex); + } else { + this.grid.verticalScrollContainer.scrollPosition = pos.rowTop; + } + } else { + this.grid.verticalScrollContainer.addScrollTop(Math.abs(pos.rowBottom - this.scrollTop - containerHeight)); + } + this.grid.verticalScrollContainer.chunkLoad + .pipe(first()).subscribe(() => { + if (cb) { + cb(); + } + }); + } else { + super.performVerticalScrollToCell(rowIndex, visibleColIndex, cb); + } + } + + protected override getNextPosition(rowIndex: number, colIndex: number, key: string, shift: boolean, ctrl: boolean, event: KeyboardEvent) { + + const field = this.grid.visibleColumns[colIndex]?.field; + switch (key) { + case 'tab': + case ' ': + case 'spacebar': + case 'space': + case 'escape': + case 'esc': + case 'enter': + case 'f2': + case 'left': + case 'arrowleft': + case 'arrowright': + case 'right': + // same as base for these keys + return super.getNextPosition(rowIndex, colIndex, key, shift, ctrl, event); + break; + case 'end': + rowIndex = ctrl ? this.findLastDataRowIndex() : this.activeNode.row; + colIndex = this.lastColumnIndex; + break; + case 'home': + rowIndex = ctrl ? this.findFirstDataRowIndex() : this.activeNode.row; + colIndex = 0; + break; + case 'arrowup': + case 'up': + const prevRec = this.grid.verticalScrollContainer.igxForOf[this.activeNode.row - 1]; + const root = prevRec?.cellMergeMeta?.get(field)?.root; + const prev = this.activeNode.row - (root?.cellMergeMeta?.get(field).rowSpan || 1); + colIndex = this.activeNode.column !== undefined ? this.activeNode.column : 0; + rowIndex = ctrl ? this.findFirstDataRowIndex() : prev; + break; + case 'arrowdown': + case 'down': + const rec = this.grid.verticalScrollContainer.igxForOf[this.activeNode.row]; + const next = this.activeNode.row + (rec?.cellMergeMeta?.get(field)?.rowSpan || 1); + colIndex = this.activeNode.column !== undefined ? this.activeNode.column : 0; + rowIndex = ctrl ? this.findLastDataRowIndex() : next; + break; + default: + return; + } + return { rowIndex, colIndex }; + } + + private getVerticalScrollPositions(rowIndex: number, rowSpan: number) { + const rowTop = this.grid.verticalScrollContainer.sizesCache[rowIndex]; + const rowBottom = this.grid.verticalScrollContainer.sizesCache[rowIndex + rowSpan]; + const topOffset = rowBottom - rowTop; + return { topOffset, rowTop, rowBottom }; + } + + private get scrollTop(): number { + return Math.abs(this.grid.verticalScrollContainer.getScroll().scrollTop); + } +} diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid.component.ts b/projects/igniteui-angular/src/lib/grids/grid/grid.component.ts index 8395d09cf5e..b3ea155bd0c 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.component.ts @@ -1357,11 +1357,12 @@ export class IgxGridComponent extends IgxGridBaseDirective implements GridType, this._gridAPI.sort_groupBy_multiple(this._groupingExpressions); } - private _setupNavigationService() { + protected override _setupNavigationService() { if (this.hasColumnLayouts) { this.navigation = new IgxGridMRLNavigationService(this.platform); this.navigation.grid = this; } + super._setupNavigationService(); } private checkIfNoColumnField(expression: IGroupingExpression | Array | any): boolean { diff --git a/projects/igniteui-angular/src/lib/grids/row.directive.ts b/projects/igniteui-angular/src/lib/grids/row.directive.ts index d4e4b6ae5c6..1cdcacc5c3e 100644 --- a/projects/igniteui-angular/src/lib/grids/row.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/row.directive.ts @@ -522,6 +522,12 @@ export class IgxRowDirective implements DoCheck, AfterViewInit, OnDestroy { public isCellActive(visibleColumnIndex) { const node = this.grid.navigation.activeNode; + const field = this.grid.visibleColumns[visibleColumnIndex].field; + const rowSpan = this.metaData?.cellMergeMeta?.get(field)?.rowSpan; + if (rowSpan > 1) { + return node ? (node.row >= this.index && node.row < this.index + rowSpan) + && node.column === visibleColumnIndex : false; + } return node ? node.row === this.index && node.column === visibleColumnIndex : false; } From aca57ee8a9a9d0fb09c394654daeb8bed62e6d52 Mon Sep 17 00:00:00 2001 From: MKirova Date: Thu, 10 Jul 2025 17:03:50 +0300 Subject: [PATCH 15/54] chore(*): Adjust for scenarios after horizontal nav into merged cell. --- .../src/lib/grids/grid-merge-navigation.service.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid-merge-navigation.service.ts b/projects/igniteui-angular/src/lib/grids/grid-merge-navigation.service.ts index 295f100a30d..870c26e34b5 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-merge-navigation.service.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-merge-navigation.service.ts @@ -48,8 +48,10 @@ export class IgxGridMergeNavigationService extends IgxGridNavigationService { } protected override getNextPosition(rowIndex: number, colIndex: number, key: string, shift: boolean, ctrl: boolean, event: KeyboardEvent) { - const field = this.grid.visibleColumns[colIndex]?.field; + const currentRec = this.grid.verticalScrollContainer.igxForOf[this.activeNode.row]; + const currentRootRec = currentRec?.cellMergeMeta?.get(field)?.root; + const currentIndex = currentRootRec ? this.grid.verticalScrollContainer.igxForOf.indexOf(currentRootRec) : this.activeNode.row; switch (key) { case 'tab': case ' ': @@ -76,16 +78,15 @@ export class IgxGridMergeNavigationService extends IgxGridNavigationService { break; case 'arrowup': case 'up': - const prevRec = this.grid.verticalScrollContainer.igxForOf[this.activeNode.row - 1]; - const root = prevRec?.cellMergeMeta?.get(field)?.root; - const prev = this.activeNode.row - (root?.cellMergeMeta?.get(field).rowSpan || 1); + const prevRec = this.grid.verticalScrollContainer.igxForOf[currentIndex - 1]; + const prevRoot = prevRec?.cellMergeMeta?.get(field)?.root; + const prev = currentIndex - (prevRoot?.cellMergeMeta?.get(field).rowSpan || 1); colIndex = this.activeNode.column !== undefined ? this.activeNode.column : 0; rowIndex = ctrl ? this.findFirstDataRowIndex() : prev; break; case 'arrowdown': case 'down': - const rec = this.grid.verticalScrollContainer.igxForOf[this.activeNode.row]; - const next = this.activeNode.row + (rec?.cellMergeMeta?.get(field)?.rowSpan || 1); + const next = currentIndex + ((currentRootRec || currentRec)?.cellMergeMeta?.get(field)?.rowSpan || 1); colIndex = this.activeNode.column !== undefined ? this.activeNode.column : 0; rowIndex = ctrl ? this.findLastDataRowIndex() : next; break; From e168c3a30ad05133a6eccc48a072bf2674fbeb08 Mon Sep 17 00:00:00 2001 From: MKirova Date: Thu, 10 Jul 2025 18:42:05 +0300 Subject: [PATCH 16/54] chore(*): Add integration with pinning. --- .../src/lib/data-operations/merge-strategy.ts | 10 ++++++++++ .../src/lib/grids/grid-base.directive.ts | 6 +++++- .../src/lib/grids/grid/grid.component.ts | 2 +- .../igniteui-angular/src/lib/grids/row.directive.ts | 6 ++++-- .../grid-cellMerging/grid-cellMerging.component.html | 5 ++++- src/app/grid-cellMerging/grid-cellMerging.component.ts | 6 +++++- 6 files changed, 29 insertions(+), 6 deletions(-) diff --git a/projects/igniteui-angular/src/lib/data-operations/merge-strategy.ts b/projects/igniteui-angular/src/lib/data-operations/merge-strategy.ts index c9afd6fc3f9..183b29ed4c6 100644 --- a/projects/igniteui-angular/src/lib/data-operations/merge-strategy.ts +++ b/projects/igniteui-angular/src/lib/data-operations/merge-strategy.ts @@ -31,7 +31,17 @@ export class DefaultMergeStrategy implements IGridMergeStrategy { let prev = null; let index = 0; for (const rec of data) { + const recData = result[index]; + // if this is some special record type - add and skip merging + if (rec.ghostRecord) { + if(!recData) { + result.push(rec); + } + prev = null; + index++; + continue; + } let recToUpdateData = recData ?? { recordRef: rec, cellMergeMeta: new Map() }; recToUpdateData.cellMergeMeta.set(field, { rowSpan: 1 }); if (prev && comparer(prev.recordRef, recToUpdateData.recordRef, field)) { diff --git a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts index e9d3ec5710c..a2a5116ce34 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts @@ -3668,7 +3668,11 @@ export abstract class IgxGridBaseDirective implements GridType, public getMergeCellOffset(rec) { const index = this.verticalScrollContainer.igxForOf.indexOf(rec); - return -(this.verticalScrollContainer.scrollPosition - this.verticalScrollContainer.getScrollForIndex(index)); + let offset = this.verticalScrollContainer.scrollPosition - this.verticalScrollContainer.getScrollForIndex(index); + if (this.hasPinnedRecords && this.isRowPinningToTop) { + offset -= this.pinnedRowHeight; + } + return -offset; } /** diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid.component.ts b/projects/igniteui-angular/src/lib/grids/grid/grid.component.ts index b3ea155bd0c..e48438fad91 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.component.ts @@ -967,7 +967,7 @@ export class IgxGridComponent extends IgxGridBaseDirective implements GridType, id: null }, disabled: this.isGhostRecord(rowData), - metaData: rowData + metaData: this.isRecordMerged(rowData) ? rowData : null }; } diff --git a/projects/igniteui-angular/src/lib/grids/row.directive.ts b/projects/igniteui-angular/src/lib/grids/row.directive.ts index 1cdcacc5c3e..94df28fb9f0 100644 --- a/projects/igniteui-angular/src/lib/grids/row.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/row.directive.ts @@ -611,7 +611,8 @@ export class IgxRowDirective implements DoCheck, AfterViewInit, OnDestroy { protected getMergeCellSpan(col: ColumnType){ const rowCount = this.metaData.cellMergeMeta.get(col.field).rowSpan; let sizeSpans = ""; - for (let index = this.index; index < this.index + rowCount; index++) { + const indexInData = this.grid.verticalScrollContainer.igxForOf.indexOf(this.metaData); + for (let index = indexInData; index < indexInData + rowCount; index++) { const size = this.grid.verticalScrollContainer.getSizeAt(index); sizeSpans += size + 'px '; } @@ -619,7 +620,8 @@ export class IgxRowDirective implements DoCheck, AfterViewInit, OnDestroy { } protected getRowHeight() { - const size = this.grid.verticalScrollContainer.getSizeAt(this.index) - 1; + const indexInData = this.grid.verticalScrollContainer.igxForOf.indexOf(this.metaData); + const size = this.grid.verticalScrollContainer.getSizeAt(indexInData) - 1; return size || this.grid.rowHeight; } diff --git a/src/app/grid-cellMerging/grid-cellMerging.component.html b/src/app/grid-cellMerging/grid-cellMerging.component.html index 21d28741a49..a98f0885a7a 100644 --- a/src/app/grid-cellMerging/grid-cellMerging.component.html +++ b/src/app/grid-cellMerging/grid-cellMerging.component.html @@ -3,7 +3,7 @@

Grid with cell merge

- + - - + - - + diff --git a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts index 69695e21ee2..ea8a0a04543 100644 --- a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts @@ -50,7 +50,7 @@ import { IgxGridValidationService } from '../grid/grid-validation.service'; import { IgxGridHierarchicalPipe, IgxGridHierarchicalPagingPipe } from './hierarchical-grid.pipes'; import { IgxSummaryDataPipe } from '../summaries/grid-root-summary.pipe'; import { IgxGridTransactionPipe, IgxHasVisibleColumnsPipe, IgxGridRowPinningPipe, IgxGridAddRowPipe, IgxGridRowClassesPipe, IgxGridRowStylesPipe, IgxStringReplacePipe } from '../common/pipes'; -import { IgxGridSortingPipe, IgxGridFilteringPipe } from '../grid/grid.pipes'; +import { IgxGridSortingPipe, IgxGridFilteringPipe, IgxGridCellMergePipe } from '../grid/grid.pipes'; import { IgxGridColumnResizerComponent } from '../resizing/resizer.component'; import { IgxRowEditTabStopDirective } from '../grid.rowEdit.directive'; import { IgxIconComponent } from '../../icon/icon.component'; @@ -350,7 +350,8 @@ export class IgxChildGridRowComponent implements AfterViewInit, OnInit { IgxSummaryDataPipe, IgxGridHierarchicalPipe, IgxGridHierarchicalPagingPipe, - IgxStringReplacePipe + IgxStringReplacePipe, + IgxGridCellMergePipe ], schemas: [CUSTOM_ELEMENTS_SCHEMA] }) @@ -942,7 +943,7 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti /** * @hidden */ - public isChildGridRecord(record: any): boolean { + public override isChildGridRecord(record: any): boolean { // Can be null when there is defined layout but no child data was found return record?.childGridsData !== undefined; } @@ -986,13 +987,14 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirecti } } else { return { - $implicit: this.isGhostRecord(rowData) ? rowData.recordRef : rowData, + $implicit: this.isGhostRecord(rowData) || this.isRecordMerged(rowData) ? rowData.recordRef : rowData, templateID: { type: 'dataRow', id: null }, index: this.getDataViewIndex(rowIndex, pinned), - disabled: this.isGhostRecord(rowData) + disabled: this.isGhostRecord(rowData), + metaData: this.isRecordMerged(rowData) ? rowData : null }; } } diff --git a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-row.component.html b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-row.component.html index 42d4a19c5d3..4c286034977 100644 --- a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-row.component.html +++ b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-row.component.html @@ -54,32 +54,19 @@ } - - + @if (this.hasMergedCells) { +
+ +
+ } + @else { + + }
@if (pinnedColumns.length > 0 && !grid.isPinningToStart) { @@ -132,3 +119,35 @@ } + + + + + diff --git a/src/app/grid-cellMerging/grid-cellMerging.component.html b/src/app/grid-cellMerging/grid-cellMerging.component.html index 0be130b991f..f73e2c33de8 100644 --- a/src/app/grid-cellMerging/grid-cellMerging.component.html +++ b/src/app/grid-cellMerging/grid-cellMerging.component.html @@ -1,15 +1,16 @@

Grid with cell merge

- + -
- - -
+
@@ -33,3 +34,31 @@

Grid with cell merge

+ +

Hierarchical grid with cell merge

+ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/grid-cellMerging/grid-cellMerging.component.ts b/src/app/grid-cellMerging/grid-cellMerging.component.ts index f60a23cd7a9..e44ce6a7179 100644 --- a/src/app/grid-cellMerging/grid-cellMerging.component.ts +++ b/src/app/grid-cellMerging/grid-cellMerging.component.ts @@ -12,8 +12,11 @@ import { IgxGridToolbarExporterComponent, IgxGridToolbarHidingComponent, IgxGridToolbarPinningComponent, + IgxHierarchicalGridComponent, IgxPaginatorComponent, + IgxRowIslandComponent, } from 'igniteui-angular'; +import { HIERARCHICAL_DATA } from '../shared/hierarchicalData'; import { data, dataWithoutPK } from '../shared/data'; @@ -34,10 +37,13 @@ import { data, dataWithoutPK } from '../shared/data'; IgxGridToolbarActionsComponent, IgxGridToolbarPinningComponent, IgxGridToolbarHidingComponent, - IgxGridToolbarExporterComponent + IgxGridToolbarExporterComponent, + IgxHierarchicalGridComponent, + IgxRowIslandComponent ] }) export class GridCellMergingComponent { + public hierarchicalData = HIERARCHICAL_DATA.concat(HIERARCHICAL_DATA).concat(HIERARCHICAL_DATA); public data = [{ ProductID: 1, ProductName: 'Chai', From 5c4587fb89e879df1e0e46e33c3ed66696fafaa4 Mon Sep 17 00:00:00 2001 From: MKirova Date: Wed, 16 Jul 2025 12:45:33 +0300 Subject: [PATCH 29/54] chore(*): Implement for tree grid. Add 2 strategies. --- .../src/lib/data-operations/merge-strategy.ts | 41 ++++ .../src/lib/grids/grid-base.directive.ts | 13 +- .../tree-grid/tree-grid-row.component.html | 223 ++++++++---------- .../grids/tree-grid/tree-grid.component.html | 26 +- .../grids/tree-grid/tree-grid.component.ts | 11 +- .../grid-cellMerging.component.html | 13 +- .../grid-cellMerging.component.ts | 6 +- 7 files changed, 194 insertions(+), 139 deletions(-) diff --git a/projects/igniteui-angular/src/lib/data-operations/merge-strategy.ts b/projects/igniteui-angular/src/lib/data-operations/merge-strategy.ts index d18a6c43be5..4d20587775e 100644 --- a/projects/igniteui-angular/src/lib/data-operations/merge-strategy.ts +++ b/projects/igniteui-angular/src/lib/data-operations/merge-strategy.ts @@ -79,3 +79,44 @@ export class DefaultMergeStrategy implements IGridMergeStrategy { return a === b; } } + + +export class DefaultTreeGridMergeStrategy extends DefaultMergeStrategy { + /* blazorSuppress */ + public override comparer(prevRecord: any, record: any, field: string): boolean { + const a = prevRecord.data[field]; + const b = record.data[field]; + const an = (a === null || a === undefined); + const bn = (b === null || b === undefined); + if (an) { + if (bn) { + return true; + } + return false; + } else if (bn) { + return false; + } + return a === b; + } +} + +export class ByLevelTreeGridMergeStrategy extends DefaultMergeStrategy { + /* blazorSuppress */ + public override comparer(prevRecord: any, record: any, field: string): boolean { + const a = prevRecord.data[field]; + const b = record.data[field]; + const levelA = prevRecord.level; + const levelB = record.level; + const an = (a === null || a === undefined); + const bn = (b === null || b === undefined); + if (an) { + if (bn) { + return true; + } + return false; + } else if (bn) { + return false; + } + return a === b && levelA === levelB; + } +} diff --git a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts index a453a3af36c..97a625a806e 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts @@ -185,7 +185,7 @@ import { IgxGridValidationService } from './grid/grid-validation.service'; import { getCurrentResourceStrings } from '../core/i18n/resources'; import { isTree, recreateTree, recreateTreeFromFields } from '../data-operations/expressions-tree-util'; import { getUUID } from './common/random'; -import { IGridMergeStrategy } from '../data-operations/merge-strategy'; +import { DefaultMergeStrategy, IGridMergeStrategy } from '../data-operations/merge-strategy'; interface IMatchInfoCache { row: any; @@ -2505,7 +2505,12 @@ export abstract class IgxGridBaseDirective implements GridType, * ``` */ @Input() - public mergeStrategy: IGridMergeStrategy; + get mergeStrategy() { + return this._mergeStrategy; + } + set mergeStrategy(value) { + this._mergeStrategy = value; + } /** * Gets/Sets the sorting options - single or multiple sorting. @@ -3181,6 +3186,10 @@ export abstract class IgxGridBaseDirective implements GridType, protected _columnPinning = false; protected _pinnedRecordIDs = []; + /** + * @hidden + */ + protected _mergeStrategy: IGridMergeStrategy = new DefaultMergeStrategy(); /** * @hidden diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-row.component.html b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-row.component.html index 05bbb9e49c4..fd1fa6737fb 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-row.component.html +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-row.component.html @@ -23,66 +23,19 @@ } - - - - - - - - - + @if (this.hasMergedCells) { +
+ +
+ } + @else { + + }
@if (pinnedColumns.length > 0 && !grid.isPinningToStart) { @@ -106,69 +59,91 @@ @for (col of pinnedColumns | igxNotGrouped; track trackPinnedColumn(col)) { - - - - - - - - - + @if (this.hasMergedCells) { +
+ +
+ } + @else { + + } }
+ + + + + + + + + + diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.html b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.html index 5db61b5629c..3560107c5ce 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.html +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.html @@ -30,6 +30,14 @@ [igxColumnMovingDrop]="headerContainer" [attr.droppable]="true" id="left" class="igx-grid__scroll-on-drag-pinned" [style.left.px]="pinnedWidth"> } + + @for (rowData of mergedDataInView; track rowData.record;) { + + + } + @if (data | treeGridTransaction:pipeTrigger @@ -38,7 +46,8 @@ | treeGridAddRow:true:pipeTrigger | gridRowPinning:id:true:pipeTrigger | treeGridFiltering:filteringExpressionsTree:filterStrategy:advancedFilteringExpressionsTree:pipeTrigger:filteringPipeTrigger:true - | treeGridSorting:sortingExpressions:treeGroupArea?.expressions:sortStrategy:pipeTrigger:true; as pinnedData + | treeGridSorting:sortingExpressions:treeGroupArea?.expressions:sortStrategy:pipeTrigger:true + | gridCellMerge:visibleColumns:cellMergeMode:sortingExpressions:activeRowIndex:pipeTrigger; as pinnedData ) { @if (pinnedData.length > 0) {
@@ -76,15 +86,15 @@ - - + - - + diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts index 30f413e00e4..32a424a37a7 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts @@ -82,6 +82,8 @@ import { IgxGridDragSelectDirective } from '../selection/drag-select.directive'; import { IgxGridBodyDirective } from '../grid.common'; import { IgxGridHeaderRowComponent } from '../headers/grid-header-row.component'; import { IgxTextHighlightService } from '../../directives/text-highlight/text-highlight.service'; +import { IgxGridCellMergePipe } from '../grid/grid.pipes'; +import { DefaultTreeGridMergeStrategy, IGridMergeStrategy } from '../../data-operations/merge-strategy'; let NEXT_ID = 0; @@ -168,7 +170,8 @@ let NEXT_ID = 0; IgxTreeGridSummaryPipe, IgxTreeGridNormalizeRecordsPipe, IgxTreeGridAddRowPipe, - IgxStringReplacePipe + IgxStringReplacePipe, + IgxGridCellMergePipe ], schemas: [CUSTOM_ELEMENTS_SCHEMA] }) @@ -348,6 +351,7 @@ export class IgxTreeGridComponent extends IgxGridBaseDirective implements GridTy protected override _filterStrategy = new TreeGridFilteringStrategy(); protected override _transactions: HierarchicalTransactionService; + protected override _mergeStrategy: IGridMergeStrategy = new DefaultTreeGridMergeStrategy(); private _data; private _rowLoadingIndicatorTemplate: TemplateRef; private _expansionDepth = Infinity; @@ -699,13 +703,14 @@ export class IgxTreeGridComponent extends IgxGridBaseDirective implements GridTy */ public getContext(rowData: any, rowIndex: number, pinned?: boolean): any { return { - $implicit: this.isGhostRecord(rowData) ? rowData.recordRef : rowData, + $implicit: this.isGhostRecord(rowData) || this.isRecordMerged(rowData) ? rowData.recordRef : rowData, index: this.getDataViewIndex(rowIndex, pinned), templateID: { type: this.isSummaryRow(rowData) ? 'summaryRow' : 'dataRow', id: null }, - disabled: this.isGhostRecord(rowData) ? rowData.recordRef.isFilteredOutParent === undefined : false + disabled: this.isGhostRecord(rowData) ? rowData.recordRef.isFilteredOutParent === undefined : false, + metaData: this.isRecordMerged(rowData) ? rowData : null }; } diff --git a/src/app/grid-cellMerging/grid-cellMerging.component.html b/src/app/grid-cellMerging/grid-cellMerging.component.html index f73e2c33de8..5dd64944ae1 100644 --- a/src/app/grid-cellMerging/grid-cellMerging.component.html +++ b/src/app/grid-cellMerging/grid-cellMerging.component.html @@ -37,7 +37,7 @@

Grid with cell merge

Hierarchical grid with cell merge

- @@ -62,3 +62,14 @@

Hierarchical grid with cell merge

+ +

Tree grid with cell merge

+ + + + + + + + diff --git a/src/app/grid-cellMerging/grid-cellMerging.component.ts b/src/app/grid-cellMerging/grid-cellMerging.component.ts index e44ce6a7179..19acbca30da 100644 --- a/src/app/grid-cellMerging/grid-cellMerging.component.ts +++ b/src/app/grid-cellMerging/grid-cellMerging.component.ts @@ -15,10 +15,12 @@ import { IgxHierarchicalGridComponent, IgxPaginatorComponent, IgxRowIslandComponent, + IgxTreeGridComponent } from 'igniteui-angular'; import { HIERARCHICAL_DATA } from '../shared/hierarchicalData'; import { data, dataWithoutPK } from '../shared/data'; +import { HIERARCHICAL_SAMPLE_DATA } from '../shared/sample-data'; @Component({ selector: 'app-grid-cellMerging', @@ -39,11 +41,13 @@ import { data, dataWithoutPK } from '../shared/data'; IgxGridToolbarHidingComponent, IgxGridToolbarExporterComponent, IgxHierarchicalGridComponent, - IgxRowIslandComponent + IgxRowIslandComponent, + IgxTreeGridComponent ] }) export class GridCellMergingComponent { public hierarchicalData = HIERARCHICAL_DATA.concat(HIERARCHICAL_DATA).concat(HIERARCHICAL_DATA); + public treeData = HIERARCHICAL_SAMPLE_DATA; public data = [{ ProductID: 1, ProductName: 'Chai', From 7ac17e45e2ef5803cb0ec4cdfe903fceb291ea11 Mon Sep 17 00:00:00 2001 From: MKirova Date: Wed, 16 Jul 2025 14:39:55 +0300 Subject: [PATCH 30/54] chore(*): Export strategies in package. --- projects/igniteui-angular-elements/src/public_api.ts | 3 ++- projects/igniteui-angular/src/public_api.ts | 1 + src/app/grid-cellMerging/grid-cellMerging.component.html | 2 +- src/app/grid-cellMerging/grid-cellMerging.component.ts | 2 ++ 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/projects/igniteui-angular-elements/src/public_api.ts b/projects/igniteui-angular-elements/src/public_api.ts index d8081637986..aa796e8c5b2 100644 --- a/projects/igniteui-angular-elements/src/public_api.ts +++ b/projects/igniteui-angular-elements/src/public_api.ts @@ -12,7 +12,7 @@ import { IgxPivotDateDimension } from 'projects/igniteui-angular/src/lib/grids/p import { PivotDimensionType } from 'projects/igniteui-angular/src/lib/grids/pivot-grid/pivot-grid.interface'; import { IgxDateSummaryOperand, IgxNumberSummaryOperand, IgxSummaryOperand, IgxTimeSummaryOperand } from 'projects/igniteui-angular/src/lib/grids/summaries/grid-summary'; import { HorizontalAlignment, VerticalAlignment } from 'projects/igniteui-angular/src/lib/services/overlay/utilities'; - +import { ByLevelTreeGridMergeStrategy } from 'projects/igniteui-angular/src/lib/data-operations/merge-strategy'; /** Export Public API, TODO: reorganize, Generate all w/ renames? */ export { @@ -35,6 +35,7 @@ export { NoopSortingStrategy as IgcNoopSortingStrategy, NoopFilteringStrategy as IgcNoopFilteringStrategy, + ByLevelTreeGridMergeStrategy as IgcByLevelTreeGridMergeStrategy, // Pivot API IgxPivotDateDimension as IgcPivotDateDimension, diff --git a/projects/igniteui-angular/src/public_api.ts b/projects/igniteui-angular/src/public_api.ts index 0e844761ba2..d4a70104a95 100644 --- a/projects/igniteui-angular/src/public_api.ts +++ b/projects/igniteui-angular/src/public_api.ts @@ -52,6 +52,7 @@ export * from './lib/data-operations/filtering-expressions-tree'; export * from './lib/data-operations/filtering-condition'; export * from './lib/data-operations/filtering-state.interface'; export * from './lib/data-operations/filtering-strategy'; +export * from './lib/data-operations/merge-strategy'; export { ExpressionsTreeUtil } from './lib/data-operations/expressions-tree-util'; export * from './lib/data-operations/groupby-expand-state.interface'; export * from './lib/data-operations/groupby-record.interface'; diff --git a/src/app/grid-cellMerging/grid-cellMerging.component.html b/src/app/grid-cellMerging/grid-cellMerging.component.html index 5dd64944ae1..f9c41c1580e 100644 --- a/src/app/grid-cellMerging/grid-cellMerging.component.html +++ b/src/app/grid-cellMerging/grid-cellMerging.component.html @@ -65,7 +65,7 @@

Hierarchical grid with cell merge

Tree grid with cell merge

- diff --git a/src/app/grid-cellMerging/grid-cellMerging.component.ts b/src/app/grid-cellMerging/grid-cellMerging.component.ts index 19acbca30da..497e4001350 100644 --- a/src/app/grid-cellMerging/grid-cellMerging.component.ts +++ b/src/app/grid-cellMerging/grid-cellMerging.component.ts @@ -21,6 +21,7 @@ import { HIERARCHICAL_DATA } from '../shared/hierarchicalData'; import { data, dataWithoutPK } from '../shared/data'; import { HIERARCHICAL_SAMPLE_DATA } from '../shared/sample-data'; +import { ByLevelTreeGridMergeStrategy } from 'igniteui-angular'; @Component({ selector: 'app-grid-cellMerging', @@ -48,6 +49,7 @@ import { HIERARCHICAL_SAMPLE_DATA } from '../shared/sample-data'; export class GridCellMergingComponent { public hierarchicalData = HIERARCHICAL_DATA.concat(HIERARCHICAL_DATA).concat(HIERARCHICAL_DATA); public treeData = HIERARCHICAL_SAMPLE_DATA; + public treeGridMergeStrategy = new ByLevelTreeGridMergeStrategy(); public data = [{ ProductID: 1, ProductName: 'Chai', From 401370e6e9b4c0ba1e70662ff498724efedf3ae4 Mon Sep 17 00:00:00 2001 From: MKirova Date: Wed, 16 Jul 2025 16:39:25 +0300 Subject: [PATCH 31/54] chore(*): Fix chip displaying when in merged cell. --- .../src/lib/grids/grid/grid-row.component.html | 8 ++++---- .../hierarchical-grid/hierarchical-row.component.html | 4 ++-- .../src/lib/grids/pivot-grid/pivot-row.component.html | 2 +- projects/igniteui-angular/src/lib/grids/row.directive.ts | 4 ++-- .../src/lib/grids/tree-grid/tree-grid-row.component.html | 4 ++-- src/app/grid-cellMerging/grid-cellMerging.component.html | 1 - src/app/grid-cellMerging/grid-cellMerging.component.ts | 2 ++ 7 files changed, 13 insertions(+), 12 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html b/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html index b8e27a76e34..e8f2a00cab7 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html @@ -162,7 +162,7 @@ [lastSearchInfo]="grid.lastSearchInfo" [active]="isCellActive(col.visibleIndex)" [cellSelectionMode]="grid.cellSelection" - [displayPinnedChip]="shouldDisplayPinnedChip(col.visibleIndex)" + [displayPinnedChip]="shouldDisplayPinnedChip(col)" #cell> @@ -194,7 +194,7 @@ [lastSearchInfo]="grid.lastSearchInfo" [active]="isCellActive(col.visibleIndex)" [cellSelectionMode]="grid.cellSelection" - [displayPinnedChip]="shouldDisplayPinnedChip(col.visibleIndex)" + [displayPinnedChip]="shouldDisplayPinnedChip(col)" #cell> @@ -226,7 +226,7 @@ [lastSearchInfo]="grid.lastSearchInfo" [active]="isCellActive(col.visibleIndex)" [cellSelectionMode]="grid.cellSelection" - [displayPinnedChip]="shouldDisplayPinnedChip(col.visibleIndex)" + [displayPinnedChip]="shouldDisplayPinnedChip(col)" #cell> @@ -258,7 +258,7 @@ [lastSearchInfo]="grid.lastSearchInfo" [active]="isCellActive(col.visibleIndex)" [cellSelectionMode]="grid.cellSelection" - [displayPinnedChip]="shouldDisplayPinnedChip(col.visibleIndex)" + [displayPinnedChip]="shouldDisplayPinnedChip(col)" #cell> diff --git a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-row.component.html b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-row.component.html index 4c286034977..1f0b9e60507 100644 --- a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-row.component.html +++ b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-row.component.html @@ -114,7 +114,7 @@ [cellValidationErrorTemplate]="col.errorTemplate" [lastSearchInfo]="grid.lastSearchInfo" [cellSelectionMode]="grid.cellSelection" - [displayPinnedChip]="shouldDisplayPinnedChip(col.visibleIndex)"> + [displayPinnedChip]="shouldDisplayPinnedChip(col)"> } @@ -148,6 +148,6 @@ [cellValidationErrorTemplate]="col.errorTemplate" [lastSearchInfo]="grid.lastSearchInfo" [cellSelectionMode]="grid.cellSelection" - [displayPinnedChip]="shouldDisplayPinnedChip(col.visibleIndex)"> + [displayPinnedChip]="shouldDisplayPinnedChip(col)"> diff --git a/projects/igniteui-angular/src/lib/grids/pivot-grid/pivot-row.component.html b/projects/igniteui-angular/src/lib/grids/pivot-grid/pivot-row.component.html index 39d6a48bd63..a6a93da2e92 100644 --- a/projects/igniteui-angular/src/lib/grids/pivot-grid/pivot-row.component.html +++ b/projects/igniteui-angular/src/lib/grids/pivot-grid/pivot-row.component.html @@ -16,7 +16,7 @@ [style.flex-basis]="col.resolvedWidth" [width]="col.getCellWidth()" [visibleColumnIndex]="col.visibleIndex" [value]="pivotAggregationData[col.field] | dataMapper:col.field:grid.pipeTrigger:pivotAggregationData[col.field]:col.hasNestedPath" [cellTemplate]="col.bodyTemplate" [lastSearchInfo]="grid.lastSearchInfo" - [cellSelectionMode]="grid.cellSelection" [displayPinnedChip]="shouldDisplayPinnedChip(col.visibleIndex)" + [cellSelectionMode]="grid.cellSelection" [displayPinnedChip]="shouldDisplayPinnedChip(col)" (pointerdown)="grid.navigation.focusOutRowHeader($event)"> diff --git a/projects/igniteui-angular/src/lib/grids/row.directive.ts b/projects/igniteui-angular/src/lib/grids/row.directive.ts index f094531c37f..4794d494075 100644 --- a/projects/igniteui-angular/src/lib/grids/row.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/row.directive.ts @@ -578,8 +578,8 @@ export class IgxRowDirective implements DoCheck, AfterViewInit, OnDestroy { /** * @hidden */ - public shouldDisplayPinnedChip(visibleColumnIndex: number): boolean { - return this.pinned && this.disabled && visibleColumnIndex === 0; + public shouldDisplayPinnedChip(col: ColumnType): boolean { + return this.pinned && this.disabled && col.visibleIndex === 0 && !this.metaData?.cellMergeMeta?.get(col.field)?.root; } /** diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-row.component.html b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-row.component.html index fd1fa6737fb..16ccc6ae995 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-row.component.html +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-row.component.html @@ -105,7 +105,7 @@ [lastSearchInfo]="grid.lastSearchInfo" [active]="isCellActive(col.visibleIndex)" [cellSelectionMode]="grid.cellSelection" - [displayPinnedChip]="shouldDisplayPinnedChip(col.visibleIndex)" + [displayPinnedChip]="shouldDisplayPinnedChip(col)" #treeCell> @@ -142,7 +142,7 @@ [lastSearchInfo]="grid.lastSearchInfo" [active]="isCellActive(col.visibleIndex)" [cellSelectionMode]="grid.cellSelection" - [displayPinnedChip]="shouldDisplayPinnedChip(col.visibleIndex)" + [displayPinnedChip]="shouldDisplayPinnedChip(col)" #treeCell> diff --git a/src/app/grid-cellMerging/grid-cellMerging.component.html b/src/app/grid-cellMerging/grid-cellMerging.component.html index f9c41c1580e..e2054dac1a7 100644 --- a/src/app/grid-cellMerging/grid-cellMerging.component.html +++ b/src/app/grid-cellMerging/grid-cellMerging.component.html @@ -59,7 +59,6 @@

Hierarchical grid with cell merge

- diff --git a/src/app/grid-cellMerging/grid-cellMerging.component.ts b/src/app/grid-cellMerging/grid-cellMerging.component.ts index 497e4001350..ec6eed2b1a3 100644 --- a/src/app/grid-cellMerging/grid-cellMerging.component.ts +++ b/src/app/grid-cellMerging/grid-cellMerging.component.ts @@ -50,6 +50,8 @@ export class GridCellMergingComponent { public hierarchicalData = HIERARCHICAL_DATA.concat(HIERARCHICAL_DATA).concat(HIERARCHICAL_DATA); public treeData = HIERARCHICAL_SAMPLE_DATA; public treeGridMergeStrategy = new ByLevelTreeGridMergeStrategy(); + public alignBottom = { alignItems: "flex-end", paddingBottom: "12px"}; + public alignTop= { alignItems: "flex-start", paddingTop: "12px" }; public data = [{ ProductID: 1, ProductName: 'Chai', From 5e1b702f619c4b3d9313df1309a3dc920749916d Mon Sep 17 00:00:00 2001 From: MKirova Date: Wed, 16 Jul 2025 17:16:15 +0300 Subject: [PATCH 32/54] chore(*): Minor tweaks. Console warn on invalid setup. --- .../src/lib/data-operations/merge-strategy.ts | 1 + .../src/lib/grids/columns/column.component.ts | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/data-operations/merge-strategy.ts b/projects/igniteui-angular/src/lib/data-operations/merge-strategy.ts index 4d20587775e..1c16f7df755 100644 --- a/projects/igniteui-angular/src/lib/data-operations/merge-strategy.ts +++ b/projects/igniteui-angular/src/lib/data-operations/merge-strategy.ts @@ -14,6 +14,7 @@ export interface IGridMergeStrategy { activeRowIndex? : number, grid?: GridType ) => any[]; + comparer: (prevRecord: any, record: any, field: string) => boolean; } export class DefaultMergeStrategy implements IGridMergeStrategy { diff --git a/projects/igniteui-angular/src/lib/grids/columns/column.component.ts b/projects/igniteui-angular/src/lib/grids/columns/column.component.ts index 58b3de78586..7aa2e7807b7 100644 --- a/projects/igniteui-angular/src/lib/grids/columns/column.component.ts +++ b/projects/igniteui-angular/src/lib/grids/columns/column.component.ts @@ -115,7 +115,17 @@ export class IgxColumnComponent implements AfterContentInit, OnDestroy, ColumnTy * */ @Input() - public merge = false; + public get merge() { + return this._merge; + } + + public set merge(value) { + if (this.grid.hasColumnLayouts) { + console.warn('Merging is not supported with multi-row layouts.'); + return; + } + this._merge = value; + } /** * @hidden @internal @@ -1911,6 +1921,10 @@ export class IgxColumnComponent implements AfterContentInit, OnDestroy, ColumnTy * @hidden */ protected _groupable = false; + /** + * @hidden + */ + protected _merge = false; /** * @hidden */ From 58e6bfac1c39e6b505efeb13965233bec2b9d140 Mon Sep 17 00:00:00 2001 From: MKirova Date: Thu, 17 Jul 2025 13:39:30 +0300 Subject: [PATCH 33/54] chore(*): Fix lint errors. --- .../igniteui-angular/src/lib/grids/grid-base.directive.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts index 97a625a806e..325f9db900f 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts @@ -2505,10 +2505,10 @@ export abstract class IgxGridBaseDirective implements GridType, * ``` */ @Input() - get mergeStrategy() { + public get mergeStrategy() { return this._mergeStrategy; } - set mergeStrategy(value) { + public set mergeStrategy(value) { this._mergeStrategy = value; } @@ -3974,7 +3974,7 @@ export abstract class IgxGridBaseDirective implements GridType, public get columnsToMerge() : ColumnType[] { return this.visibleColumns.filter( x => x.merge && (this.cellMergeMode ==='always' || - (this.cellMergeMode === 'onSort' && !!this.sortingExpressions.find( x=> x.fieldName === x.fieldName))) + (this.cellMergeMode === 'onSort' && !!this.sortingExpressions.find( y => y.fieldName === x.field))) ); } @@ -4058,7 +4058,7 @@ export abstract class IgxGridBaseDirective implements GridType, const columnToMerge = this.visibleColumns.filter( x => x.merge && (this.cellMergeMode ==='always' || (this.cellMergeMode === 'onSort' && !!this.sortingExpressions - .find( x=> x.fieldName === x.fieldName))) + .find(y => y.fieldName === x.field))) ); return columnToMerge.length > 0; } From 5e81bf91320d989045f9f13e7f4adb8e8904fa20 Mon Sep 17 00:00:00 2001 From: MKirova Date: Thu, 17 Jul 2025 13:44:02 +0300 Subject: [PATCH 34/54] chore(*): Fix theming lint. --- .../src/lib/core/styles/components/grid/_grid-theme.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-theme.scss b/projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-theme.scss index bf9d1774dd5..605fcda9a59 100644 --- a/projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-theme.scss +++ b/projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-theme.scss @@ -1830,10 +1830,12 @@ %igx-grid__td--merged { color: var-get($theme, 'row-selected-text-color'); background: var-get($theme, 'row-selected-background'); + &:hover { background: var-get($theme, 'row-selected-hover-background'); color: var-get($theme, 'row-selected-hover-text-color'); } + &%igx-grid__td--merged-hovered { background: var-get($theme, 'row-selected-hover-background'); color: var-get($theme, 'row-selected-hover-text-color'); From b2b61a7492cdcb6f072cabef5cd8bd95d425aff8 Mon Sep 17 00:00:00 2001 From: MKirova Date: Thu, 17 Jul 2025 15:02:18 +0300 Subject: [PATCH 35/54] chore(*): Fix build and imports. --- .../src/lib/data-operations/data-util.ts | 7 ------- .../src/lib/data-operations/merge-strategy.ts | 13 +++++++++---- .../src/lib/grids/grid/grid.pipes.ts | 2 +- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/projects/igniteui-angular/src/lib/data-operations/data-util.ts b/projects/igniteui-angular/src/lib/data-operations/data-util.ts index 429321ffbef..bdd51b5d07f 100644 --- a/projects/igniteui-angular/src/lib/data-operations/data-util.ts +++ b/projects/igniteui-angular/src/lib/data-operations/data-util.ts @@ -38,13 +38,6 @@ import { DefaultMergeStrategy, IGridMergeStrategy } from './merge-strategy'; } as const; export type DataType = (typeof DataType)[keyof typeof DataType]; - -export interface IMergeByResult { - rowSpan: number; - root?: any; - prev?: any; -} - /** * @hidden */ diff --git a/projects/igniteui-angular/src/lib/data-operations/merge-strategy.ts b/projects/igniteui-angular/src/lib/data-operations/merge-strategy.ts index 1c16f7df755..6339f0064d9 100644 --- a/projects/igniteui-angular/src/lib/data-operations/merge-strategy.ts +++ b/projects/igniteui-angular/src/lib/data-operations/merge-strategy.ts @@ -1,9 +1,14 @@ -import { GridCellMergeMode, IMergeByResult } from 'igniteui-angular'; -import type { KeyOfOrString } from '../core/types'; -import { IBaseEventArgs } from '../core/utils'; -import { ColumnType, GridType } from '../grids/common/grid.interface'; +import { GridType } from '../grids/common/grid.interface'; + + +export interface IMergeByResult { + rowSpan: number; + root?: any; + prev?: any; +} + export interface IGridMergeStrategy { /* blazorSuppress */ merge: ( diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid.pipes.ts b/projects/igniteui-angular/src/lib/grids/grid/grid.pipes.ts index dbfb9d676c1..8cef2a8034e 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid.pipes.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.pipes.ts @@ -9,7 +9,7 @@ import { ColumnType, GridType, IGX_GRID_BASE } from '../common/grid.interface'; import { FilterUtil, IFilteringStrategy } from '../../data-operations/filtering-strategy'; import { ISortingExpression } from '../../data-operations/sorting-strategy'; import { IGridSortingStrategy, IGridGroupingStrategy } from '../common/strategy'; -import { GridCellMergeMode } from 'igniteui-angular'; +import { GridCellMergeMode } from '../common/enums'; /** * @hidden From 9a7a2d25bfe2ed796ee5b19aa8c8395a4d996c07 Mon Sep 17 00:00:00 2001 From: MKirova Date: Thu, 17 Jul 2025 15:13:16 +0300 Subject: [PATCH 36/54] chore(*): Add null check. --- projects/igniteui-angular/src/lib/grids/row.directive.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/grids/row.directive.ts b/projects/igniteui-angular/src/lib/grids/row.directive.ts index 4794d494075..0e6c12ef6b7 100644 --- a/projects/igniteui-angular/src/lib/grids/row.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/row.directive.ts @@ -524,7 +524,7 @@ export class IgxRowDirective implements DoCheck, AfterViewInit, OnDestroy { public isCellActive(visibleColumnIndex) { const node = this.grid.navigation.activeNode; - const field = this.grid.visibleColumns[visibleColumnIndex].field; + const field = this.grid.visibleColumns[visibleColumnIndex]?.field; const rowSpan = this.metaData?.cellMergeMeta?.get(field)?.rowSpan; if (rowSpan > 1) { return node ? (node.row >= this.index && node.row < this.index + rowSpan) From ce0c854c386590ac1321c20730cd2eee87821ea8 Mon Sep 17 00:00:00 2001 From: MKirova Date: Thu, 17 Jul 2025 16:53:59 +0300 Subject: [PATCH 37/54] chore(*): Null check for activeRowIndex. --- projects/igniteui-angular/src/lib/grids/grid-base.directive.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts index 325f9db900f..13c24649a0b 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts @@ -4051,7 +4051,7 @@ export abstract class IgxGridBaseDirective implements GridType, protected get activeRowIndex() { - return this.navigation.activeNode.row; + return this.navigation.activeNode?.row; } protected get hasCellsToMerge() { From a6fc511f5ab346aad330b17267bdb555307f41b8 Mon Sep 17 00:00:00 2001 From: MKirova Date: Thu, 17 Jul 2025 17:07:49 +0300 Subject: [PATCH 38/54] chore(*): Generate elements config. --- .../src/analyzer/elements.config.ts | 3 +++ .../igniteui-angular/src/lib/grids/grid-base.directive.ts | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/projects/igniteui-angular-elements/src/analyzer/elements.config.ts b/projects/igniteui-angular-elements/src/analyzer/elements.config.ts index be2f3f4d6d5..e78d3492d9d 100644 --- a/projects/igniteui-angular-elements/src/analyzer/elements.config.ts +++ b/projects/igniteui-angular-elements/src/analyzer/elements.config.ts @@ -95,6 +95,7 @@ export var registerConfig = [ ], numericProps: ["rowEnd", "colEnd", "rowStart", "colStart"], boolProps: [ + "merge", "sortable", "selectable", "groupable", @@ -158,6 +159,7 @@ export var registerConfig = [ "expanded", "searchable", "hidden", + "merge", "sortable", "groupable", "editable", @@ -213,6 +215,7 @@ export var registerConfig = [ "collapsible", "expanded", "searchable", + "merge", "sortable", "groupable", "editable", diff --git a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts index 13c24649a0b..8b34b98060e 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts @@ -3695,7 +3695,7 @@ export abstract class IgxGridBaseDirective implements GridType, return rec?.cellMergeMeta; } - public getMergeCellOffset(rowData) { + protected getMergeCellOffset(rowData) { const index = rowData.index; let offset = this.verticalScrollContainer.scrollPosition - this.verticalScrollContainer.getScrollForIndex(index); if (this.hasPinnedRecords && this.isRowPinningToTop) { @@ -3971,6 +3971,10 @@ export abstract class IgxGridBaseDirective implements GridType, } } + /** + * @hidden + * @internal + */ public get columnsToMerge() : ColumnType[] { return this.visibleColumns.filter( x => x.merge && (this.cellMergeMode ==='always' || From d0254734c210cabbedbfe65d02e5e5d5f6ca803c Mon Sep 17 00:00:00 2001 From: MKirova Date: Thu, 17 Jul 2025 18:03:04 +0300 Subject: [PATCH 39/54] chore(*): Break up merge groups on cell selection. --- .../igniteui-angular/src/lib/data-operations/data-util.ts | 4 ++-- .../src/lib/data-operations/merge-strategy.ts | 6 +++--- .../igniteui-angular/src/lib/grids/grid-base.directive.ts | 6 ++++-- .../igniteui-angular/src/lib/grids/grid/grid.component.html | 4 ++-- projects/igniteui-angular/src/lib/grids/grid/grid.pipes.ts | 4 ++-- .../hierarchical-grid/hierarchical-grid.component.html | 4 ++-- .../src/lib/grids/tree-grid/tree-grid.component.html | 4 ++-- src/app/grid-cellMerging/grid-cellMerging.component.html | 2 +- 8 files changed, 18 insertions(+), 16 deletions(-) diff --git a/projects/igniteui-angular/src/lib/data-operations/data-util.ts b/projects/igniteui-angular/src/lib/data-operations/data-util.ts index bdd51b5d07f..0066cd58f4e 100644 --- a/projects/igniteui-angular/src/lib/data-operations/data-util.ts +++ b/projects/igniteui-angular/src/lib/data-operations/data-util.ts @@ -91,11 +91,11 @@ export class DataUtil { return grouping.groupBy(data, state, grid, groupsRecords, fullResult); } - public static merge(data: T[], columns: ColumnType[], strategy: IGridMergeStrategy = new DefaultMergeStrategy(), activeRowIndex = -1, grid: GridType = null, + public static merge(data: T[], columns: ColumnType[], strategy: IGridMergeStrategy = new DefaultMergeStrategy(), activeRowIndexes = [], grid: GridType = null, ): any[] { let result = []; for (const col of columns) { - strategy.merge(data, col.field, col.mergingComparer, result, activeRowIndex, grid); + strategy.merge(data, col.field, col.mergingComparer, result, activeRowIndexes, grid); } return result; } diff --git a/projects/igniteui-angular/src/lib/data-operations/merge-strategy.ts b/projects/igniteui-angular/src/lib/data-operations/merge-strategy.ts index 6339f0064d9..d1e5b5f9b5a 100644 --- a/projects/igniteui-angular/src/lib/data-operations/merge-strategy.ts +++ b/projects/igniteui-angular/src/lib/data-operations/merge-strategy.ts @@ -16,7 +16,7 @@ export interface IGridMergeStrategy { field: string, comparer: (prevRecord: any, currentRecord: any, field: string) => boolean, result: any[], - activeRowIndex? : number, + activeRowIndexes : number[], grid?: GridType ) => any[]; comparer: (prevRecord: any, record: any, field: string) => boolean; @@ -35,7 +35,7 @@ export class DefaultMergeStrategy implements IGridMergeStrategy { field: string, comparer: (prevRecord: any, record: any, field: string) => boolean = this.comparer, result: any[], - activeRowIndex?: number, + activeRowIndexes : number[], grid?: GridType ) { let prev = null; @@ -44,7 +44,7 @@ export class DefaultMergeStrategy implements IGridMergeStrategy { const recData = result[index]; // if this is active row or some special record type - add and skip merging - if (activeRowIndex === index || (grid && grid.isDetailRecord(rec) || grid.isGroupByRecord(rec) || grid.isChildGridRecord(rec))) { + if (activeRowIndexes.indexOf(index) != -1 || (grid && grid.isDetailRecord(rec) || grid.isGroupByRecord(rec) || grid.isChildGridRecord(rec))) { if(!recData) { result.push(rec); } diff --git a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts index 8b34b98060e..b41b24eb7bb 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts @@ -4054,8 +4054,10 @@ export abstract class IgxGridBaseDirective implements GridType, } - protected get activeRowIndex() { - return this.navigation.activeNode?.row; + protected get activeRowIndexes(): number[] { + const activeRow = this.navigation.activeNode?.row; + const selectedCellIndexes = (this.selectionService.selection?.keys() as any)?.toArray(); + return [activeRow, ...selectedCellIndexes]; } protected get hasCellsToMerge() { diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid.component.html b/projects/igniteui-angular/src/lib/grids/grid/grid.component.html index 04915e5f558..4e86643da62 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid.component.html +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.component.html @@ -63,7 +63,7 @@ | gridRowPinning:id:true:pipeTrigger | gridFiltering:filteringExpressionsTree:filterStrategy:advancedFilteringExpressionsTree:id:pipeTrigger:filteringPipeTrigger:true | gridSort:sortingExpressions:groupingExpressions:sortStrategy:id:pipeTrigger:true - | gridCellMerge:visibleColumns:cellMergeMode:sortingExpressions:activeRowIndex:pipeTrigger; as pinnedData) { + | gridCellMerge:visibleColumns:cellMergeMode:sortingExpressions:activeRowIndexes:pipeTrigger; as pinnedData) { @if (pinnedData.length > 0) {
0) {
diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.html b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.html index 3560107c5ce..9e16f82611d 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.html +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.html @@ -47,7 +47,7 @@ | gridRowPinning:id:true:pipeTrigger | treeGridFiltering:filteringExpressionsTree:filterStrategy:advancedFilteringExpressionsTree:pipeTrigger:filteringPipeTrigger:true | treeGridSorting:sortingExpressions:treeGroupArea?.expressions:sortStrategy:pipeTrigger:true - | gridCellMerge:visibleColumns:cellMergeMode:sortingExpressions:activeRowIndex:pipeTrigger; as pinnedData + | gridCellMerge:visibleColumns:cellMergeMode:sortingExpressions:activeRowIndexes:pipeTrigger; as pinnedData ) { @if (pinnedData.length > 0) {
diff --git a/src/app/grid-cellMerging/grid-cellMerging.component.html b/src/app/grid-cellMerging/grid-cellMerging.component.html index e2054dac1a7..15a44d78af3 100644 --- a/src/app/grid-cellMerging/grid-cellMerging.component.html +++ b/src/app/grid-cellMerging/grid-cellMerging.component.html @@ -1,5 +1,5 @@

Grid with cell merge

- From 190f29d52afa0635f30619ad87a17f9caad249da Mon Sep 17 00:00:00 2001 From: MKirova Date: Fri, 18 Jul 2025 11:58:07 +0300 Subject: [PATCH 40/54] chore(*): Update active indexes on events. Cache result to limit pipe trigger. --- .../src/lib/grids/grid-base.directive.ts | 20 ++++++++++++++++--- .../lib/grids/selection/selection.service.ts | 9 ++++++++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts index b41b24eb7bb..43cd98538a7 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts @@ -3246,6 +3246,7 @@ export abstract class IgxGridBaseDirective implements GridType, private _filteredSortedData = null; private _filteredData = null; private _mergedDataInView = null; + private _activeRowIndexes = null; private _customDragIndicatorIconTemplate: TemplateRef; private _excelStyleHeaderIconTemplate: TemplateRef; @@ -3907,6 +3908,14 @@ export abstract class IgxGridBaseDirective implements GridType, this.autoSizeColumnsInView(); this._firstAutoResize = false; }); + + this.activeNodeChange.pipe(filter(() => !this._init), destructor).subscribe(() => { + this._activeRowIndexes = null; + }); + + this.selectionService.selectedRangeChange.pipe(filter(() => !this._init), destructor).subscribe(() => { + this._activeRowIndexes = null; + }); } /** @@ -4055,9 +4064,14 @@ export abstract class IgxGridBaseDirective implements GridType, protected get activeRowIndexes(): number[] { - const activeRow = this.navigation.activeNode?.row; - const selectedCellIndexes = (this.selectionService.selection?.keys() as any)?.toArray(); - return [activeRow, ...selectedCellIndexes]; + if (this._activeRowIndexes) { + return this._activeRowIndexes; + } else { + const activeRow = this.navigation.activeNode?.row; + const selectedCellIndexes = (this.selectionService.selection?.keys() as any)?.toArray(); + this._activeRowIndexes = [activeRow, ...selectedCellIndexes]; + return this._activeRowIndexes; + } } protected get hasCellsToMerge() { diff --git a/projects/igniteui-angular/src/lib/grids/selection/selection.service.ts b/projects/igniteui-angular/src/lib/grids/selection/selection.service.ts index 83287824d16..0a92780d866 100644 --- a/projects/igniteui-angular/src/lib/grids/selection/selection.service.ts +++ b/projects/igniteui-angular/src/lib/grids/selection/selection.service.ts @@ -35,6 +35,11 @@ export class IgxGridSelectionService { */ public selectedRowsChange = new Subject(); + /** + * @hidden @internal + */ + public selectedRangeChange = new Subject>>(); + /** * Toggled when a pointerdown event is triggered inside the grid body (cells). * When `false` the drag select behavior is disabled. @@ -355,6 +360,8 @@ export class IgxGridSelectionService { } } } + + this.selectedRangeChange.next(collection); } public dragSelect(node: ISelectionNode, state: SelectionState): void { @@ -637,7 +644,7 @@ export class IgxGridSelectionService { if (this.areEqualCollections(currSelection, newSelection)) { return; } - + const args: IRowSelectionEventArgs = { owner: this.grid, oldSelection: currSelection, From 50e7d85a7dae9575aa97636f939da85580b4dc2f Mon Sep 17 00:00:00 2001 From: MKirova Date: Fri, 18 Jul 2025 17:14:00 +0300 Subject: [PATCH 41/54] chore(*): When searching, mark merged cells as a single result. --- .../src/lib/grids/grid-base.directive.ts | 28 ++++++----- .../grid-cellMerging.component.html | 46 ++++++++++++++++++- .../grid-cellMerging.component.ts | 28 ++++++++++- 3 files changed, 89 insertions(+), 13 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts index 43cd98538a7..c563367f506 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts @@ -38,7 +38,7 @@ import { IgcTrialWatermark } from 'igniteui-trial-watermark'; import { Subject, pipe, fromEvent, animationFrameScheduler, merge } from 'rxjs'; import { takeUntil, first, filter, throttleTime, map, shareReplay, takeWhile } from 'rxjs/operators'; import { cloneArray, mergeObjects, compareMaps, resolveNestedPath, isObject, PlatformUtil } from '../core/utils'; -import { GridColumnDataType } from '../data-operations/data-util'; +import { DataUtil, GridColumnDataType } from '../data-operations/data-util'; import { FilteringLogic } from '../data-operations/filtering-expression.interface'; import { IGroupByRecord } from '../data-operations/groupby-record.interface'; import { IForOfDataChangeEventArgs, IgxGridForOfDirective } from '../directives/for-of/for_of.directive'; @@ -3911,10 +3911,12 @@ export abstract class IgxGridBaseDirective implements GridType, this.activeNodeChange.pipe(filter(() => !this._init), destructor).subscribe(() => { this._activeRowIndexes = null; + this.refreshSearch(); }); this.selectionService.selectedRangeChange.pipe(filter(() => !this._init), destructor).subscribe(() => { this._activeRowIndexes = null; + this.refreshSearch(); }); } @@ -7977,25 +7979,29 @@ export abstract class IgxGridBaseDirective implements GridType, const caseSensitive = this._lastSearchInfo.caseSensitive; const exactMatch = this._lastSearchInfo.exactMatch; const searchText = caseSensitive ? this._lastSearchInfo.searchText : this._lastSearchInfo.searchText.toLowerCase(); - const data = this.filteredSortedData; + let data = this.filteredSortedData; + if (this.hasCellsToMerge) { + data = DataUtil.merge(cloneArray(this.filteredSortedData), this.columnsToMerge, this.mergeStrategy, this.activeRowIndexes, this); + } const columnItems = this.visibleColumns.filter((c) => !c.columnGroup).sort((c1, c2) => c1.visibleIndex - c2.visibleIndex); const columnsPathParts = columnItems.map(col => columnFieldPath(col.field)); data.forEach((dataRow, rowIndex) => { + const currentRowData = this.isRecordMerged(dataRow) ? dataRow.recordRef : dataRow; columnItems.forEach((c, cid) => { const pipeArgs = this.getColumnByName(c.field).pipeArgs; - const value = c.formatter ? c.formatter(resolveNestedPath(dataRow, columnsPathParts[cid]), dataRow) : - c.dataType === 'number' ? formatNumber(resolveNestedPath(dataRow, columnsPathParts[cid]) as number, this.locale, pipeArgs.digitsInfo) : + const value = c.formatter ? c.formatter(resolveNestedPath(currentRowData, columnsPathParts[cid]), currentRowData) : + c.dataType === 'number' ? formatNumber(resolveNestedPath(currentRowData, columnsPathParts[cid]) as number, this.locale, pipeArgs.digitsInfo) : c.dataType === 'date' - ? formatDate(resolveNestedPath(dataRow, columnsPathParts[cid]) as string, pipeArgs.format, this.locale, pipeArgs.timezone) - : resolveNestedPath(dataRow, columnsPathParts[cid]); + ? formatDate(resolveNestedPath(currentRowData, columnsPathParts[cid]) as string, pipeArgs.format, this.locale, pipeArgs.timezone) + : resolveNestedPath(currentRowData, columnsPathParts[cid]); if (value !== undefined && value !== null && c.searchable) { let searchValue = caseSensitive ? String(value) : String(value).toLowerCase(); - + const isMergePlaceHolder = this.isRecordMerged(dataRow) ? !!dataRow?.cellMergeMeta.get(c.field)?.root : false; if (exactMatch) { - if (searchValue === searchText) { + if (searchValue === searchText && !isMergePlaceHolder) { const mic: IMatchInfoCache = { - row: dataRow, + row: currentRowData, column: c.field, index: 0, metadata: new Map([['pinned', this.isRecordPinnedByIndex(rowIndex)]]) @@ -8007,9 +8013,9 @@ export abstract class IgxGridBaseDirective implements GridType, let occurrenceIndex = 0; let searchIndex = searchValue.indexOf(searchText); - while (searchIndex !== -1) { + while (searchIndex !== -1 && !isMergePlaceHolder) { const mic: IMatchInfoCache = { - row: dataRow, + row: currentRowData, column: c.field, index: occurrenceIndex++, metadata: new Map([['pinned', this.isRecordPinnedByIndex(rowIndex)]]) diff --git a/src/app/grid-cellMerging/grid-cellMerging.component.html b/src/app/grid-cellMerging/grid-cellMerging.component.html index 15a44d78af3..b522b1af332 100644 --- a/src/app/grid-cellMerging/grid-cellMerging.component.html +++ b/src/app/grid-cellMerging/grid-cellMerging.component.html @@ -1,5 +1,49 @@

Grid with cell merge

- + + + @if (searchText.length === 0) { + search + } + @if (searchText.length > 0) { + clear + } + + + + + @if (searchText.length > 0) { + + @if (grid.lastSearchInfo) { +
+ @if (grid.lastSearchInfo.matchInfoCache.length > 0) { + + {{ grid.lastSearchInfo.activeMatchIndex + 1 }} of {{ grid.lastSearchInfo.matchInfoCache.length }} + results + + } + @if (grid.lastSearchInfo.matchInfoCache.length === 0) { + + No results + + } +
+ } +
+ + +
+
+ } +
+
+ diff --git a/src/app/grid-cellMerging/grid-cellMerging.component.ts b/src/app/grid-cellMerging/grid-cellMerging.component.ts index ec6eed2b1a3..89b86f37a5e 100644 --- a/src/app/grid-cellMerging/grid-cellMerging.component.ts +++ b/src/app/grid-cellMerging/grid-cellMerging.component.ts @@ -13,8 +13,13 @@ import { IgxGridToolbarHidingComponent, IgxGridToolbarPinningComponent, IgxHierarchicalGridComponent, + IgxIconComponent, + IgxInputDirective, + IgxInputGroupComponent, IgxPaginatorComponent, + IgxPrefixDirective, IgxRowIslandComponent, + IgxSuffixDirective, IgxTreeGridComponent } from 'igniteui-angular'; import { HIERARCHICAL_DATA } from '../shared/hierarchicalData'; @@ -43,7 +48,12 @@ import { ByLevelTreeGridMergeStrategy } from 'igniteui-angular'; IgxGridToolbarExporterComponent, IgxHierarchicalGridComponent, IgxRowIslandComponent, - IgxTreeGridComponent + IgxTreeGridComponent, + IgxInputGroupComponent, + IgxPrefixDirective, + IgxSuffixDirective, + IgxIconComponent, + IgxInputDirective ] }) export class GridCellMergingComponent { @@ -52,6 +62,8 @@ export class GridCellMergingComponent { public treeGridMergeStrategy = new ByLevelTreeGridMergeStrategy(); public alignBottom = { alignItems: "flex-end", paddingBottom: "12px"}; public alignTop= { alignItems: "flex-start", paddingTop: "12px" }; + public searchText: string =''; + @ViewChild('grid1', { static: true }) public grid: IgxGridComponent; public data = [{ ProductID: 1, ProductName: 'Chai', @@ -387,5 +399,19 @@ export class GridCellMergingComponent { OrderDate2: new Date(1991, 2, 12, 15, 40, 50).toISOString() }]; + public searchKeyDown(ev) { + if (ev.key === 'Enter' || ev.key === 'ArrowDown' || ev.key === 'ArrowRight') { + ev.preventDefault(); + this.grid.findNext(this.searchText, true, false); + } else if (ev.key === 'ArrowUp' || ev.key === 'ArrowLeft') { + ev.preventDefault(); + this.grid.findPrev(this.searchText, true, false); + } + } + + public clearSearch() { + this.searchText = ''; + this.grid.clearSearch(); + } } From 1e560e8aca89e839fa8e87728949903fe488bc5c Mon Sep 17 00:00:00 2001 From: MKirova Date: Fri, 18 Jul 2025 17:33:47 +0300 Subject: [PATCH 42/54] chore(*): Refresh search if needed only. --- .../igniteui-angular/src/lib/grids/grid-base.directive.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts index c563367f506..8b3889b317c 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts @@ -3911,12 +3911,16 @@ export abstract class IgxGridBaseDirective implements GridType, this.activeNodeChange.pipe(filter(() => !this._init), destructor).subscribe(() => { this._activeRowIndexes = null; - this.refreshSearch(); + if (this.hasCellsToMerge) { + this.refreshSearch(); + } }); this.selectionService.selectedRangeChange.pipe(filter(() => !this._init), destructor).subscribe(() => { this._activeRowIndexes = null; - this.refreshSearch(); + if (this.hasCellsToMerge) { + this.refreshSearch(); + } }); } From 7db787aaa2477999633e98516bc164870f0507a1 Mon Sep 17 00:00:00 2001 From: MKirova Date: Mon, 21 Jul 2025 13:53:19 +0300 Subject: [PATCH 43/54] chore(*): Fix scrollTo when scrolling to a merged cell that has larger rowspan. --- .../src/lib/grids/grid-base.directive.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts index 8b3889b317c..e5bcaa27c3e 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts @@ -7633,15 +7633,21 @@ export abstract class IgxGridBaseDirective implements GridType, this.page = page; } } - + let targetRowIndex = (typeof (row) === 'number' ? row : this.unpinnedDataView.indexOf(row)); + const virtRec = this.verticalScrollContainer.igxForOf[targetRowIndex]; + const col = typeof (column) === 'number' ? this.visibleColumns[column] : column; + const rowSpan = this.isRecordMerged(virtRec) ? virtRec?.cellMergeMeta.get(col)?.rowSpan : 1; + if (rowSpan > 1) { + targetRowIndex += Math.floor(rowSpan/2); + } if (delayScrolling) { this.verticalScrollContainer.dataChanged.pipe(first(), takeUntil(this.destroy$)).subscribe(() => { this.scrollDirective(this.verticalScrollContainer, - typeof (row) === 'number' ? row : this.unpinnedDataView.indexOf(row)); + targetRowIndex); }); } else { this.scrollDirective(this.verticalScrollContainer, - typeof (row) === 'number' ? row : this.unpinnedDataView.indexOf(row)); + targetRowIndex); } this.scrollToHorizontally(column); From 86bc021d837cc0f1fe5ee20a0f1ef512ae5a73c0 Mon Sep 17 00:00:00 2001 From: MKirova Date: Mon, 21 Jul 2025 16:54:29 +0300 Subject: [PATCH 44/54] chore(*): Add basic merging tests. --- .../src/lib/grids/grid/cell-merge.spec.ts | 227 ++++++++++++++++++ .../src/lib/test-utils/grid-functions.spec.ts | 17 +- 2 files changed, 243 insertions(+), 1 deletion(-) create mode 100644 projects/igniteui-angular/src/lib/grids/grid/cell-merge.spec.ts diff --git a/projects/igniteui-angular/src/lib/grids/grid/cell-merge.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/cell-merge.spec.ts new file mode 100644 index 00000000000..f5f62896f78 --- /dev/null +++ b/projects/igniteui-angular/src/lib/grids/grid/cell-merge.spec.ts @@ -0,0 +1,227 @@ +import { Component, ViewChild } from '@angular/core'; +import { TestBed, waitForAsync } from '@angular/core/testing'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { DefaultMergeStrategy, GridCellMergeMode, GridColumnDataType, GridType, IgxColumnComponent, IgxGridComponent, SortingDirection } from 'igniteui-angular'; +import { DataParent } from '../../test-utils/sample-test-data.spec'; +import { GridFunctions } from '../../test-utils/grid-functions.spec'; + +describe('IgxGrid - Cell merging #grid', () => { + let fix; + let grid: IgxGridComponent; + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [ + NoopAnimationsModule, DefaultCellMergeGridComponent + ] + }).compileComponents(); + })); + + beforeEach(() => { + fix = TestBed.createComponent(DefaultCellMergeGridComponent); + fix.detectChanges(); + grid = fix.componentInstance.grid; + }); + + describe('Basic', () => { + it('should allow enabling/disabling merging per column.', () => { + + const col = grid.getColumnByName('ProductName'); + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: 'Ignite UI for JavaScript', span: 2 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 2 }, + { value: null , span: 1 }, + { value: 'NetAdvantage' , span: 2 } + ]); + + // disable merge + col.merge = false; + fix.detectChanges(); + + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: null , span: 1 }, + { value: 'NetAdvantage' , span: 1 }, + { value: 'NetAdvantage' , span: 1 } + ]); + }); + + it('should always merge columns if mergeMode is always.', () => { + const col = grid.getColumnByName('Released'); + col.merge = true; + fix.detectChanges(); + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: true, span: 9 } + ]); + }); + + it('should merge only sorted columns if mergeMode is onSort.', () => { + grid.cellMergeMode = 'onSort'; + fix.detectChanges(); + const col = grid.getColumnByName('ProductName'); + //nothing is merged initially + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: null , span: 1 }, + { value: 'NetAdvantage' , span: 1 }, + { value: 'NetAdvantage' , span: 1 } + ]); + + grid.sort({ fieldName: 'ProductName', dir: SortingDirection.Desc, ignoreCase: false }); + fix.detectChanges(); + + // merge only after sorted + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: 'NetAdvantage' , span: 2 }, + { value: 'Ignite UI for JavaScript', span: 3 }, + { value: 'Ignite UI for Angular', span: 3 }, + { value: null , span: 1 } + ]); + }); + + it('should allow setting a custom merge strategy via mergeStrategy on grid.', () => { + grid.mergeStrategy = new NoopMergeStrategy(); + fix.detectChanges(); + const col = grid.getColumnByName('ProductName'); + // this strategy does no merging + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: null , span: 1 }, + { value: 'NetAdvantage' , span: 1 }, + { value: 'NetAdvantage' , span: 1 } + ]); + }); + + it('should allow setting a custom comparer for merging on particular column via mergingComparer.', () => { + const col = grid.getColumnByName('ProductName'); + // all are same and should merge + col.mergingComparer = (prev:any, rec: any, field: string) => { + return true; + }; + grid.pipeTrigger += 1; + fix.detectChanges(); + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: 'Ignite UI for JavaScript', span: 9 } + ]); + }); + }); +}); + +@Component({ + template: ` + + @for(col of cols; track col) { + + } + + `, + imports: [IgxGridComponent, IgxColumnComponent] +}) +export class DefaultCellMergeGridComponent extends DataParent { + public mergeMode: GridCellMergeMode = GridCellMergeMode.always; + @ViewChild('grid', { read: IgxGridComponent, static: true }) + public grid: IgxGridComponent; + public cols = [ + { field:'ID', merge: false }, + { field:'ProductName', dataType: GridColumnDataType.String, merge: true }, + { field:'Downloads', dataType: GridColumnDataType.Number, merge: false }, + { field:'Released', dataType: GridColumnDataType.Boolean, merge: false }, + { field:'ReleaseDate', dataType: GridColumnDataType.Date, merge: false } + ]; + + public override data = [ + { + Downloads: 254, + ID: 1, + ProductName: 'Ignite UI for JavaScript', + ReleaseDate: this.today, + Released: true + }, + { + Downloads: 1000, + ID: 2, + ProductName: 'Ignite UI for JavaScript', + ReleaseDate: this.nextDay, + Released: true + }, + { + Downloads: 20, + ID: 3, + ProductName: 'Ignite UI for Angular', + ReleaseDate: null, + Released: true + }, + { + Downloads: null, + ID: 4, + ProductName: 'Ignite UI for JavaScript', + ReleaseDate: this.prevDay, + Released: true + }, + { + Downloads: 100, + ID: 5, + ProductName: 'Ignite UI for Angular', + ReleaseDate: null, + Released: true + }, + { + Downloads: 1000, + ID: 6, + ProductName: 'Ignite UI for Angular', + ReleaseDate: this.nextDay, + Released: true + }, + { + Downloads: 0, + ID: 7, + ProductName: null, + ReleaseDate: this.prevDay, + Released: true + }, + { + Downloads: 1000, + ID: 8, + ProductName: 'NetAdvantage', + ReleaseDate: this.today, + Released: true + }, + { + Downloads: 1000, + ID: 9, + ProductName: 'NetAdvantage', + ReleaseDate: this.prevDay, + Released: true + } + ]; + +} + +class NoopMergeStrategy extends DefaultMergeStrategy { + public override merge( + data: any[], + field: string, + comparer: (prevRecord: any, record: any, field: string) => boolean = this.comparer, + result: any[], + activeRowIndexes : number[], + grid?: GridType + ) { + return data; + } +} diff --git a/projects/igniteui-angular/src/lib/test-utils/grid-functions.spec.ts b/projects/igniteui-angular/src/lib/test-utils/grid-functions.spec.ts index e6689a977de..53058f1328a 100644 --- a/projects/igniteui-angular/src/lib/test-utils/grid-functions.spec.ts +++ b/projects/igniteui-angular/src/lib/test-utils/grid-functions.spec.ts @@ -19,7 +19,7 @@ import { IgxGridCellComponent } from '../grids/cell.component'; import { IgxPivotRowComponent } from '../grids/pivot-grid/pivot-row.component'; import { SortingDirection } from '../data-operations/sorting-strategy'; import { IgxRowDirective } from '../grids/row.directive'; -import { CellType, GridType, RowType } from '../grids/common/grid.interface'; +import { CellType, ColumnType, GridType, RowType } from '../grids/common/grid.interface'; import { IgxTreeNodeComponent } from '../tree/tree-node/tree-node.component'; import { IgxColumnComponent } from '../grids/columns/column.component'; import { IgxPivotGridComponent } from '../grids/pivot-grid/pivot-grid.component'; @@ -104,6 +104,21 @@ export const SAFE_DISPOSE_COMP_ID = 'root'; export class GridFunctions { + public static verifyColumnMergedState(grid: GridType, col: ColumnType, state: any[]) { + const dataRows = grid.dataRowList.toArray(); + let totalSpan = 0; + for (let index = 0; index < dataRows.length - 1; index++) { + const row = dataRows[index]; + const cellValue = row.cells.toArray().find(x => x.column === col).value; + const rowSpan = row.metaData?.cellMergeMeta.get(col.field)?.rowSpan || 1; + const currState = state[index - totalSpan]; + expect(cellValue).toBe(currState.value); + expect(rowSpan).toBe(currState.span); + totalSpan += (rowSpan - 1); + index += (rowSpan - 1); + } + } + public static getRows(fix): DebugElement[] { const rows: DebugElement[] = fix.debugElement.queryAll(By.css(ROW_CSS_CLASS)); rows.shift(); From 91c457b27f9e261cf15d8aa1cb69fc65cb51764a Mon Sep 17 00:00:00 2001 From: MKirova Date: Mon, 21 Jul 2025 18:26:49 +0300 Subject: [PATCH 45/54] chore(*): Add some UI tests for merging. --- .../src/lib/grids/grid/cell-merge.spec.ts | 49 ++++++++++++++++++- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid/cell-merge.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/cell-merge.spec.ts index f5f62896f78..b52e0c929e6 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/cell-merge.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/cell-merge.spec.ts @@ -1,13 +1,19 @@ -import { Component, ViewChild } from '@angular/core'; -import { TestBed, waitForAsync } from '@angular/core/testing'; +import { Component, TemplateRef, ViewChild } from '@angular/core'; +import { fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { DefaultMergeStrategy, GridCellMergeMode, GridColumnDataType, GridType, IgxColumnComponent, IgxGridComponent, SortingDirection } from 'igniteui-angular'; import { DataParent } from '../../test-utils/sample-test-data.spec'; import { GridFunctions } from '../../test-utils/grid-functions.spec'; +import { By } from '@angular/platform-browser'; +import { UIInteractions, wait } from '../../test-utils/ui-interactions.spec'; +import { hasClass } from '../../test-utils/helper-utils.spec'; describe('IgxGrid - Cell merging #grid', () => { let fix; let grid: IgxGridComponent; + const MERGE_CELL_CSS_CLASS = '.igx-grid__td--merged'; + const CELL_CSS_CLASS = '.igx-grid__td'; + const CSS_CLASS_GRID_ROW = '.igx-grid__tr'; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [ @@ -121,6 +127,38 @@ describe('IgxGrid - Cell merging #grid', () => { ]); }); }); + + + describe('UI', () => { + it ('should properly align merged cells with their spanned rows.', () => { + const mergedCell = fix.debugElement.queryAll(By.css(MERGE_CELL_CSS_CLASS))[0].nativeNode; + const endRow = fix.debugElement.queryAll(By.css(CSS_CLASS_GRID_ROW))[2].nativeNode; + expect(mergedCell.getBoundingClientRect().bottom).toBe(endRow.getBoundingClientRect().bottom); + }); + + it('should mark merged cell as hovered when hovering any row that intersects that cell.', () => { + const secondRow = fix.debugElement.queryAll(By.css(CSS_CLASS_GRID_ROW))[2]; + UIInteractions.hoverElement(secondRow.nativeNode); + fix.detectChanges(); + // hover 2nd row that intersects the merged cell in row 1 + const mergedCell = fix.debugElement.queryAll(By.css(MERGE_CELL_CSS_CLASS))[0].nativeNode; + // merged cell should be marked as hovered + hasClass(mergedCell, 'igx-grid__td--merged-hovered', true); + }); + + it('should set correct size to merged cell that spans multiple rows that have different sizes.', () => { + const col = grid.getColumnByName('ID'); + col.bodyTemplate = fix.componentInstance.customTemplate; + fix.detectChanges(); + grid.verticalScrollContainer.recalcUpdateSizes(); + grid.dataRowList.toArray().forEach(x => x.cdr.detectChanges()); + const mergedCell = fix.debugElement.queryAll(By.css(MERGE_CELL_CSS_CLASS))[0].nativeNode; + // one row is 100px, other is 200, 4px border + expect(mergedCell.getBoundingClientRect().height).toBe(100 + 200 + 4); + + + }); + }); }); @Component({ @@ -130,6 +168,9 @@ describe('IgxGrid - Cell merging #grid', () => { } + + + `, imports: [IgxGridComponent, IgxColumnComponent] }) @@ -137,6 +178,10 @@ export class DefaultCellMergeGridComponent extends DataParent { public mergeMode: GridCellMergeMode = GridCellMergeMode.always; @ViewChild('grid', { read: IgxGridComponent, static: true }) public grid: IgxGridComponent; + + @ViewChild('customTemplate', { read: TemplateRef, static: true }) + public customTemplate: TemplateRef; + public cols = [ { field:'ID', merge: false }, { field:'ProductName', dataType: GridColumnDataType.String, merge: true }, From a5c5566911789aa96ae481dea7fa2c3951d99529 Mon Sep 17 00:00:00 2001 From: MKirova Date: Tue, 22 Jul 2025 17:40:07 +0300 Subject: [PATCH 46/54] chore(*): Add some integration tests. --- .../src/lib/grids/grid-base.directive.ts | 2 +- .../src/lib/grids/grid/cell-merge.spec.ts | 460 +++++++++++++----- 2 files changed, 341 insertions(+), 121 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts index e5bcaa27c3e..e47d720920b 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts @@ -3891,7 +3891,7 @@ export abstract class IgxGridBaseDirective implements GridType, const rec = prevDataView[index]; if (rec.cellMergeMeta && // index + maxRowSpan is within view - startIndex <= (index + Math.max(...rec.cellMergeMeta.values().toArray().map(x => x.rowSpan)))) { + startIndex < (index + Math.max(...rec.cellMergeMeta.values().toArray().map(x => x.rowSpan)))) { data.push({record: rec, index: index }); } } diff --git a/projects/igniteui-angular/src/lib/grids/grid/cell-merge.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/cell-merge.spec.ts index b52e0c929e6..87710439b54 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/cell-merge.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/cell-merge.spec.ts @@ -1,7 +1,7 @@ import { Component, TemplateRef, ViewChild } from '@angular/core'; import { fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { DefaultMergeStrategy, GridCellMergeMode, GridColumnDataType, GridType, IgxColumnComponent, IgxGridComponent, SortingDirection } from 'igniteui-angular'; +import { DefaultMergeStrategy, DefaultSortingStrategy, GridCellMergeMode, GridColumnDataType, GridType, IgxColumnComponent, IgxGridComponent, IgxPaginatorComponent, SortingDirection } from 'igniteui-angular'; import { DataParent } from '../../test-utils/sample-test-data.spec'; import { GridFunctions } from '../../test-utils/grid-functions.spec'; import { By } from '@angular/platform-browser'; @@ -22,142 +22,337 @@ describe('IgxGrid - Cell merging #grid', () => { }).compileComponents(); })); - beforeEach(() => { - fix = TestBed.createComponent(DefaultCellMergeGridComponent); - fix.detectChanges(); - grid = fix.componentInstance.grid; - }); + describe('Basic', () => { - it('should allow enabling/disabling merging per column.', () => { - - const col = grid.getColumnByName('ProductName'); - GridFunctions.verifyColumnMergedState(grid, col, [ - { value: 'Ignite UI for JavaScript', span: 2 }, - { value: 'Ignite UI for Angular', span: 1 }, - { value: 'Ignite UI for JavaScript', span: 1 }, - { value: 'Ignite UI for Angular', span: 2 }, - { value: null , span: 1 }, - { value: 'NetAdvantage' , span: 2 } - ]); - - // disable merge - col.merge = false; - fix.detectChanges(); - GridFunctions.verifyColumnMergedState(grid, col, [ - { value: 'Ignite UI for JavaScript', span: 1 }, - { value: 'Ignite UI for JavaScript', span: 1 }, - { value: 'Ignite UI for Angular', span: 1 }, - { value: 'Ignite UI for JavaScript', span: 1 }, - { value: 'Ignite UI for Angular', span: 1 }, - { value: 'Ignite UI for Angular', span: 1 }, - { value: null , span: 1 }, - { value: 'NetAdvantage' , span: 1 }, - { value: 'NetAdvantage' , span: 1 } - ]); + beforeEach(() => { + fix = TestBed.createComponent(DefaultCellMergeGridComponent); + fix.detectChanges(); + grid = fix.componentInstance.grid; }); - it('should always merge columns if mergeMode is always.', () => { - const col = grid.getColumnByName('Released'); - col.merge = true; - fix.detectChanges(); - GridFunctions.verifyColumnMergedState(grid, col, [ - { value: true, span: 9 } - ]); + describe('Configuration', () => { + + it('should allow enabling/disabling merging per column.', () => { + + const col = grid.getColumnByName('ProductName'); + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: 'Ignite UI for JavaScript', span: 2 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 2 }, + { value: null, span: 1 }, + { value: 'NetAdvantage', span: 2 } + ]); + + // disable merge + col.merge = false; + fix.detectChanges(); + + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: null, span: 1 }, + { value: 'NetAdvantage', span: 1 }, + { value: 'NetAdvantage', span: 1 } + ]); + }); + + it('should always merge columns if mergeMode is always.', () => { + const col = grid.getColumnByName('Released'); + col.merge = true; + fix.detectChanges(); + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: true, span: 9 } + ]); + }); + + it('should merge only sorted columns if mergeMode is onSort.', () => { + grid.cellMergeMode = 'onSort'; + fix.detectChanges(); + const col = grid.getColumnByName('ProductName'); + //nothing is merged initially + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: null, span: 1 }, + { value: 'NetAdvantage', span: 1 }, + { value: 'NetAdvantage', span: 1 } + ]); + + grid.sort({ fieldName: 'ProductName', dir: SortingDirection.Desc, ignoreCase: false }); + fix.detectChanges(); + + // merge only after sorted + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: 'NetAdvantage', span: 2 }, + { value: 'Ignite UI for JavaScript', span: 3 }, + { value: 'Ignite UI for Angular', span: 3 }, + { value: null, span: 1 } + ]); + }); + + it('should allow setting a custom merge strategy via mergeStrategy on grid.', () => { + grid.mergeStrategy = new NoopMergeStrategy(); + fix.detectChanges(); + const col = grid.getColumnByName('ProductName'); + // this strategy does no merging + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: null, span: 1 }, + { value: 'NetAdvantage', span: 1 }, + { value: 'NetAdvantage', span: 1 } + ]); + }); + + it('should allow setting a custom comparer for merging on particular column via mergingComparer.', () => { + const col = grid.getColumnByName('ProductName'); + // all are same and should merge + col.mergingComparer = (prev: any, rec: any, field: string) => { + return true; + }; + grid.pipeTrigger += 1; + fix.detectChanges(); + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: 'Ignite UI for JavaScript', span: 9 } + ]); + }); }); - it('should merge only sorted columns if mergeMode is onSort.', () => { - grid.cellMergeMode = 'onSort'; - fix.detectChanges(); - const col = grid.getColumnByName('ProductName'); - //nothing is merged initially - GridFunctions.verifyColumnMergedState(grid, col, [ - { value: 'Ignite UI for JavaScript', span: 1 }, - { value: 'Ignite UI for JavaScript', span: 1 }, - { value: 'Ignite UI for Angular', span: 1 }, - { value: 'Ignite UI for JavaScript', span: 1 }, - { value: 'Ignite UI for Angular', span: 1 }, - { value: 'Ignite UI for Angular', span: 1 }, - { value: null , span: 1 }, - { value: 'NetAdvantage' , span: 1 }, - { value: 'NetAdvantage' , span: 1 } - ]); - - grid.sort({ fieldName: 'ProductName', dir: SortingDirection.Desc, ignoreCase: false }); - fix.detectChanges(); + describe('UI', () => { + it('should properly align merged cells with their spanned rows.', () => { + const mergedCell = fix.debugElement.queryAll(By.css(MERGE_CELL_CSS_CLASS))[0].nativeNode; + const endRow = fix.debugElement.queryAll(By.css(CSS_CLASS_GRID_ROW))[2].nativeNode; + expect(mergedCell.getBoundingClientRect().bottom).toBe(endRow.getBoundingClientRect().bottom); + }); + + it('should mark merged cell as hovered when hovering any row that intersects that cell.', () => { + const secondRow = fix.debugElement.queryAll(By.css(CSS_CLASS_GRID_ROW))[2]; + UIInteractions.hoverElement(secondRow.nativeNode); + fix.detectChanges(); + // hover 2nd row that intersects the merged cell in row 1 + const mergedCell = fix.debugElement.queryAll(By.css(MERGE_CELL_CSS_CLASS))[0].nativeNode; + // merged cell should be marked as hovered + hasClass(mergedCell, 'igx-grid__td--merged-hovered', true); + }); - // merge only after sorted - GridFunctions.verifyColumnMergedState(grid, col, [ - { value: 'NetAdvantage' , span: 2 }, - { value: 'Ignite UI for JavaScript', span: 3 }, - { value: 'Ignite UI for Angular', span: 3 }, - { value: null , span: 1 } - ]); + it('should set correct size to merged cell that spans multiple rows that have different sizes.', () => { + const col = grid.getColumnByName('ID'); + col.bodyTemplate = fix.componentInstance.customTemplate; + fix.detectChanges(); + grid.verticalScrollContainer.recalcUpdateSizes(); + grid.dataRowList.toArray().forEach(x => x.cdr.detectChanges()); + const mergedCell = fix.debugElement.queryAll(By.css(MERGE_CELL_CSS_CLASS))[0].nativeNode; + // one row is 100px, other is 200, 4px border + expect(mergedCell.getBoundingClientRect().height).toBe(100 + 200 + 4); + }); }); + }); - it('should allow setting a custom merge strategy via mergeStrategy on grid.', () => { - grid.mergeStrategy = new NoopMergeStrategy(); + describe('Integration', () => { + beforeEach(() => { + fix = TestBed.createComponent(IntegrationCellMergeGridComponent); fix.detectChanges(); - const col = grid.getColumnByName('ProductName'); - // this strategy does no merging - GridFunctions.verifyColumnMergedState(grid, col, [ - { value: 'Ignite UI for JavaScript', span: 1 }, - { value: 'Ignite UI for JavaScript', span: 1 }, - { value: 'Ignite UI for Angular', span: 1 }, - { value: 'Ignite UI for JavaScript', span: 1 }, - { value: 'Ignite UI for Angular', span: 1 }, - { value: 'Ignite UI for Angular', span: 1 }, - { value: null , span: 1 }, - { value: 'NetAdvantage' , span: 1 }, - { value: 'NetAdvantage' , span: 1 } - ]); + grid = fix.componentInstance.grid; }); - it('should allow setting a custom comparer for merging on particular column via mergingComparer.', () => { - const col = grid.getColumnByName('ProductName'); - // all are same and should merge - col.mergingComparer = (prev:any, rec: any, field: string) => { - return true; - }; - grid.pipeTrigger += 1; - fix.detectChanges(); - GridFunctions.verifyColumnMergedState(grid, col, [ - { value: 'Ignite UI for JavaScript', span: 9 } - ]); + describe('Virtualization', () => { + beforeEach(() => { + fix.componentInstance.width = '400px'; + fix.componentInstance.height = '300px'; + fix.detectChanges(); + }); + it('should retain rows with merged cells that span multiple rows in DOM as long as merged cell is still in view.', async() => { + // initial row list is same as the virtualization chunk + expect(grid.rowList.length).toBe(grid.virtualizationState.chunkSize); + + grid.navigateTo(grid.virtualizationState.chunkSize - 1, 0); + await wait(100); + fix.detectChanges(); + + //virtualization starts from 1 + expect(grid.virtualizationState.startIndex).toBe(1); + + // check row is chunkSize + 1 extra row at the top + expect(grid.rowList.length).toBe(grid.virtualizationState.chunkSize + 1); + // first row at top is index 0 + expect(grid.rowList.first.index).toBe(0); + // and has offset to position correctly the merged cell + expect(grid.rowList.first.nativeElement.offsetTop).toBeLessThan(-50); + }); + + it('should remove row from DOM when merged cell is no longer in view.', async() => { + // scroll so that first row with merged cell is not in view + grid.navigateTo(grid.virtualizationState.chunkSize, 0); + await wait(100); + fix.detectChanges(); + + //virtualization starts from 2 + expect(grid.virtualizationState.startIndex).toBe(2); + + // no merge cells from previous chunks + expect(grid.rowList.length).toBe(grid.virtualizationState.chunkSize); + // first row is from the virtualization + expect(grid.rowList.first.index).toBe(grid.virtualizationState.startIndex); + }); + + it('horizontal virtualization should not be affected by vertically merged cells.', async() => { + let mergedCell = grid.rowList.first.cells.find(x => x.column.field === 'ProductName'); + expect(mergedCell.value).toBe('Ignite UI for JavaScript'); + expect(mergedCell.nativeElement.parentElement.style.gridTemplateRows).toBe("51px 51px"); + + // scroll horizontally + grid.navigateTo(0, 4); + await wait(100); + fix.detectChanges(); + + // not in DOM + mergedCell = grid.rowList.first.cells.find(x => x.column.field === 'ProductName'); + expect(mergedCell).toBeUndefined(); + + // scroll back + grid.navigateTo(0, 0); + await wait(100); + fix.detectChanges(); + + mergedCell = grid.rowList.first.cells.find(x => x.column.field === 'ProductName'); + expect(mergedCell.value).toBe('Ignite UI for JavaScript'); + expect(mergedCell.nativeElement.parentElement.style.gridTemplateRows).toBe("51px 51px"); + }); }); - }); + describe('Group By', () => { + it('cells should merge only within their respective groups.', () => { + grid.groupBy({ + fieldName: 'ProductName', dir: SortingDirection.Desc, + ignoreCase: false, strategy: DefaultSortingStrategy.instance() + }); + fix.detectChanges(); + + const col = grid.getColumnByName('ProductName'); + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: 'NetAdvantage', span: 2 }, + { value: 'Ignite UI for JavaScript', span: 3 }, + { value: 'Ignite UI for Angular', span: 3 }, + { value: null, span: 1 } + ]); + + grid.groupBy({ + fieldName: 'ReleaseDate', dir: SortingDirection.Desc, + ignoreCase: false, strategy: DefaultSortingStrategy.instance() + }); + fix.detectChanges(); + + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: 'NetAdvantage', span: 1 }, + { value: 'NetAdvantage', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: 'Ignite UI for Angular', span: 2 }, + { value: null, span: 1 } + ]); + + }); - describe('UI', () => { - it ('should properly align merged cells with their spanned rows.', () => { - const mergedCell = fix.debugElement.queryAll(By.css(MERGE_CELL_CSS_CLASS))[0].nativeNode; - const endRow = fix.debugElement.queryAll(By.css(CSS_CLASS_GRID_ROW))[2].nativeNode; - expect(mergedCell.getBoundingClientRect().bottom).toBe(endRow.getBoundingClientRect().bottom); }); - it('should mark merged cell as hovered when hovering any row that intersects that cell.', () => { - const secondRow = fix.debugElement.queryAll(By.css(CSS_CLASS_GRID_ROW))[2]; - UIInteractions.hoverElement(secondRow.nativeNode); - fix.detectChanges(); - // hover 2nd row that intersects the merged cell in row 1 - const mergedCell = fix.debugElement.queryAll(By.css(MERGE_CELL_CSS_CLASS))[0].nativeNode; - // merged cell should be marked as hovered - hasClass(mergedCell, 'igx-grid__td--merged-hovered', true); + describe('Master-Detail', () => { + + it('should interrupt merge sequence if a master-detail row is expanded.', () => { + grid.detailTemplate = fix.componentInstance.detailTemplate; + fix.detectChanges(); + + const col = grid.getColumnByName('ProductName'); + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: 'Ignite UI for JavaScript', span: 2 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 2 }, + { value: null, span: 1 }, + { value: 'NetAdvantage', span: 2 } + ]); + + GridFunctions.toggleMasterRow(fix, grid.rowList.first); + fix.detectChanges(); + + // should slit first merge group in 2 + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 2 }, + { value: null, span: 1 }, + { value: 'NetAdvantage', span: 2 } + ]); + }); + + }); - it('should set correct size to merged cell that spans multiple rows that have different sizes.', () => { - const col = grid.getColumnByName('ID'); - col.bodyTemplate = fix.componentInstance.customTemplate; - fix.detectChanges(); - grid.verticalScrollContainer.recalcUpdateSizes(); - grid.dataRowList.toArray().forEach(x => x.cdr.detectChanges()); - const mergedCell = fix.debugElement.queryAll(By.css(MERGE_CELL_CSS_CLASS))[0].nativeNode; - // one row is 100px, other is 200, 4px border - expect(mergedCell.getBoundingClientRect().height).toBe(100 + 200 + 4); + describe('Paging', () => { + it('should merge cells only on current page of data.', () => { + fix.componentInstance.paging = true; + fix.detectChanges(); + grid.triggerPipes(); + fix.detectChanges(); + + const col = grid.getColumnByName('ProductName'); + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: 'Ignite UI for JavaScript', span: 2 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 1 } + ]); + grid.page = 2; + fix.detectChanges(); + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: 'Ignite UI for Angular', span: 1 }, + { value: null, span: 1 }, + { value: 'NetAdvantage', span: 2 } + ]); + }); }); + + describe('Column Pinning', () => { + it('should merge cells in pinned columns.', () => { + const col = grid.getColumnByName('ProductName'); + col.pinned = true; + fix.detectChanges(); + + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: 'Ignite UI for JavaScript', span: 2 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 2 }, + { value: null, span: 1 }, + { value: 'NetAdvantage', span: 2 } + ]); + + const mergedCell = grid.rowList.first.cells.find(x => x.column.field === 'ProductName'); + expect(mergedCell.value).toBe('Ignite UI for JavaScript'); + expect(mergedCell.nativeElement.parentElement.style.gridTemplateRows).toBe("51px 51px"); + }); + }); + }); }); @@ -183,11 +378,11 @@ export class DefaultCellMergeGridComponent extends DataParent { public customTemplate: TemplateRef; public cols = [ - { field:'ID', merge: false }, - { field:'ProductName', dataType: GridColumnDataType.String, merge: true }, - { field:'Downloads', dataType: GridColumnDataType.Number, merge: false }, - { field:'Released', dataType: GridColumnDataType.Boolean, merge: false }, - { field:'ReleaseDate', dataType: GridColumnDataType.Date, merge: false } + { field: 'ID', merge: false }, + { field: 'ProductName', dataType: GridColumnDataType.String, merge: true }, + { field: 'Downloads', dataType: GridColumnDataType.Number, merge: false }, + { field: 'Released', dataType: GridColumnDataType.Boolean, merge: false }, + { field: 'ReleaseDate', dataType: GridColumnDataType.Date, merge: false } ]; public override data = [ @@ -258,13 +453,38 @@ export class DefaultCellMergeGridComponent extends DataParent { } +@Component({ + template: ` + + @for(col of cols; track col) { + + } + @if (paging) { + + } + + + + + `, + imports: [IgxGridComponent, IgxColumnComponent, IgxPaginatorComponent] +}) +export class IntegrationCellMergeGridComponent extends DefaultCellMergeGridComponent { + public height = '100%'; + public width = '100%'; + public paging = false; + + @ViewChild('detailTemplate', { read: TemplateRef, static: true }) + public detailTemplate: TemplateRef; +} + class NoopMergeStrategy extends DefaultMergeStrategy { public override merge( data: any[], field: string, comparer: (prevRecord: any, record: any, field: string) => boolean = this.comparer, result: any[], - activeRowIndexes : number[], + activeRowIndexes: number[], grid?: GridType ) { return data; From 46ea7ae3cf27f111415fccde2151fa52556cbdae Mon Sep 17 00:00:00 2001 From: MKirova Date: Wed, 23 Jul 2025 14:09:14 +0300 Subject: [PATCH 47/54] chore(*): Add more integration tests. --- .../src/lib/grids/grid/cell-merge.spec.ts | 186 +++++++++++++++++- .../src/lib/grids/row.directive.ts | 2 +- 2 files changed, 186 insertions(+), 2 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid/cell-merge.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/cell-merge.spec.ts index 87710439b54..0229ef9af01 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/cell-merge.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/cell-merge.spec.ts @@ -3,7 +3,7 @@ import { fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { DefaultMergeStrategy, DefaultSortingStrategy, GridCellMergeMode, GridColumnDataType, GridType, IgxColumnComponent, IgxGridComponent, IgxPaginatorComponent, SortingDirection } from 'igniteui-angular'; import { DataParent } from '../../test-utils/sample-test-data.spec'; -import { GridFunctions } from '../../test-utils/grid-functions.spec'; +import { GridFunctions, GridSelectionFunctions } from '../../test-utils/grid-functions.spec'; import { By } from '@angular/platform-browser'; import { UIInteractions, wait } from '../../test-utils/ui-interactions.spec'; import { hasClass } from '../../test-utils/helper-utils.spec'; @@ -353,6 +353,190 @@ describe('IgxGrid - Cell merging #grid', () => { }); }); + describe('Row Pinning', () => { + it('should merge adjacent pinned rows in pinned row area.', () => { + const row1 = grid.rowList.toArray()[0]; + const row2 = grid.rowList.toArray()[1]; + const col = grid.getColumnByName('ProductName'); + row1.pin(); + row2.pin(); + fix.detectChanges(); + + expect(grid.pinnedRows.length).toBe(2); + const pinnedRow = grid.pinnedRows[0]; + expect(pinnedRow.metaData.cellMergeMeta.get(col.field)?.rowSpan).toBe(2); + const mergedPinnedCell = pinnedRow.cells.find(x => x.column.field === 'ProductName'); + expect(mergedPinnedCell.value).toBe('Ignite UI for JavaScript'); + expect(mergedPinnedCell.nativeElement.parentElement.style.gridTemplateRows).toBe("51px 51px"); + }); + + it('should merge adjacent ghost rows in unpinned area.', () => { + const row1 = grid.rowList.toArray()[0]; + const row2 = grid.rowList.toArray()[1]; + const col = grid.getColumnByName('ProductName'); + row1.pin(); + row2.pin(); + fix.detectChanges(); + + const ghostRows = grid.rowList.filter(x => x.disabled); + expect(ghostRows.length).toBe(2); + const ghostRow = ghostRows[0]; + expect(ghostRow.metaData.cellMergeMeta.get(col.field)?.rowSpan).toBe(2); + const mergedPinnedCell = ghostRow.cells.find(x => x.column.field === 'ProductName'); + expect(mergedPinnedCell.value).toBe('Ignite UI for JavaScript'); + expect(mergedPinnedCell.nativeElement.parentElement.style.gridTemplateRows).toBe("51px 51px"); + }); + + it('should not merge ghost and data rows together.', () => { + const col = grid.getColumnByName('ProductName'); + const row1 = grid.rowList.toArray()[0]; + row1.pin(); + fix.detectChanges(); + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 2 }, + { value: null, span: 1 }, + { value: 'NetAdvantage', span: 2 } + ]); + }); + }); + + describe('Activation', () => { + + it('should interrupt merge sequence so that active row has no merging.', () => { + const col = grid.getColumnByName('ProductName'); + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: 'Ignite UI for JavaScript', span: 2 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 2 }, + { value: null, span: 1 }, + { value: 'NetAdvantage', span: 2 } + ]); + + const row1 = grid.rowList.toArray()[0]; + + UIInteractions.simulateClickAndSelectEvent(row1.cells.toArray()[1].nativeElement); + fix.detectChanges(); + + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 2 }, + { value: null, span: 1 }, + { value: 'NetAdvantage', span: 2 } + ]); + }); + + }); + + describe('Updating', () => { + + beforeEach(() => { + grid.primaryKey = 'ID'; + grid.columns.forEach(x => x.editable = true); + fix.detectChanges(); + }); + + it('should edit the individual row values for the active row.', () => { + const col = grid.getColumnByName('ProductName'); + grid.rowEditable = true; + fix.detectChanges(); + + const row = grid.gridAPI.get_row_by_index(0); + const cell = grid.gridAPI.get_cell_by_index(0, 'ProductName'); + UIInteractions.simulateDoubleClickAndSelectEvent(cell.nativeElement); + fix.detectChanges(); + expect(row.inEditMode).toBe(true); + + // row in edit is not merged anymore + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 2 }, + { value: null, span: 1 }, + { value: 'NetAdvantage', span: 2 } + ]); + + // enter new val + const cellInput = grid.gridAPI.get_cell_by_index(0, 'ProductName').nativeElement.querySelector('[igxinput]'); + UIInteractions.setInputElementValue(cellInput, "NewValue"); + fix.detectChanges(); + + // Done button click + const doneButtonElement = GridFunctions.getRowEditingDoneButton(fix); + doneButtonElement.click(); + fix.detectChanges(); + + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: 'NewValue', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 2 }, + { value: null, span: 1 }, + { value: 'NetAdvantage', span: 2 } + ]); + }); + + it('should edit the individual cell value for the active row.', () => { + const col = grid.getColumnByName('ProductName'); + let cell = grid.gridAPI.get_cell_by_index(0, 'ProductName'); + + UIInteractions.simulateDoubleClickAndSelectEvent(cell.nativeElement); + fix.detectChanges(); + + cell = grid.gridAPI.get_cell_by_index(0, 'ProductName'); + expect(cell.editMode).toBe(true); + + // enter new val + const cellInput = grid.gridAPI.get_cell_by_index(0, 'ProductName').nativeElement.querySelector('[igxinput]'); + UIInteractions.setInputElementValue(cellInput, "NewValue"); + fix.detectChanges(); + + UIInteractions.triggerEventHandlerKeyDown('enter', GridFunctions.getGridContent(fix)); + fix.detectChanges(); + + // row with edit cell is not merged anymore + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: 'NewValue', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 2 }, + { value: null, span: 1 }, + { value: 'NetAdvantage', span: 2 } + ]); + }); + }); + describe('Row Selection', () => { + + it('should mark all merged cells that intersect with a selected row as selected.', () => { + grid.rowSelection = 'multiple'; + fix.detectChanges(); + + const secondRow = grid.rowList.toArray()[1]; + GridSelectionFunctions.clickRowCheckbox(secondRow); + fix.detectChanges(); + + expect(secondRow.selected).toBe(true); + grid.markForCheck(); + + const mergedIntersectedCell = grid.gridAPI.get_cell_by_index(0, 'ProductName'); + // check cell has selected style + hasClass(mergedIntersectedCell.nativeElement,'igx-grid__td--merged-selected', true); + }); + + }); + }); }); diff --git a/projects/igniteui-angular/src/lib/grids/row.directive.ts b/projects/igniteui-angular/src/lib/grids/row.directive.ts index 0e6c12ef6b7..a7bf2a10cb7 100644 --- a/projects/igniteui-angular/src/lib/grids/row.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/row.directive.ts @@ -628,7 +628,7 @@ export class IgxRowDirective implements DoCheck, AfterViewInit, OnDestroy { if (mergeMeta && rowCount > 1) { const indexInData = this.pinned && this.grid.isRowPinningToTop ? this.index - this.grid.pinnedRecordsCount : this.index; const range = this.grid.verticalScrollContainer.igxForOf.slice(indexInData, indexInData + rowCount); - const inRange = range.filter(x => this.selectionService.isRowSelected(this.grid.primaryKey ? (x.recordRef || x)[this.grid.primaryKey] : (x.recordRef || x).recordRef)).length > 0; + const inRange = range.filter(x => this.selectionService.isRowSelected(this.grid.primaryKey ? (x.recordRef || x)[this.grid.primaryKey] : (x.recordRef || x))).length > 0; return inRange; } return false; From f8624690fd01254b48b0ca8a33729e0f856b283f Mon Sep 17 00:00:00 2001 From: MKirova Date: Wed, 23 Jul 2025 16:02:36 +0300 Subject: [PATCH 48/54] chore(*): Add more integration tests. --- .../src/lib/grids/grid/cell-merge.spec.ts | 146 +++++++++++++++++- 1 file changed, 143 insertions(+), 3 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid/cell-merge.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/cell-merge.spec.ts index 0229ef9af01..5b0df570546 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/cell-merge.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/cell-merge.spec.ts @@ -1,12 +1,13 @@ import { Component, TemplateRef, ViewChild } from '@angular/core'; import { fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { DefaultMergeStrategy, DefaultSortingStrategy, GridCellMergeMode, GridColumnDataType, GridType, IgxColumnComponent, IgxGridComponent, IgxPaginatorComponent, SortingDirection } from 'igniteui-angular'; +import { DefaultMergeStrategy, DefaultSortingStrategy, GridCellMergeMode, GridColumnDataType, GridType, IgxColumnComponent, IgxGridComponent, IgxPaginatorComponent, IgxStringFilteringOperand, SortingDirection } from 'igniteui-angular'; import { DataParent } from '../../test-utils/sample-test-data.spec'; import { GridFunctions, GridSelectionFunctions } from '../../test-utils/grid-functions.spec'; import { By } from '@angular/platform-browser'; import { UIInteractions, wait } from '../../test-utils/ui-interactions.spec'; import { hasClass } from '../../test-utils/helper-utils.spec'; +import { ColumnLayoutTestComponent } from './grid.multi-row-layout.spec'; describe('IgxGrid - Cell merging #grid', () => { let fix; @@ -14,10 +15,12 @@ describe('IgxGrid - Cell merging #grid', () => { const MERGE_CELL_CSS_CLASS = '.igx-grid__td--merged'; const CELL_CSS_CLASS = '.igx-grid__td'; const CSS_CLASS_GRID_ROW = '.igx-grid__tr'; + const HIGHLIGHT_ACTIVE_CSS_CLASS = '.igx-highlight__active'; + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [ - NoopAnimationsModule, DefaultCellMergeGridComponent + NoopAnimationsModule, DefaultCellMergeGridComponent, ColumnLayoutTestComponent ] }).compileComponents(); })); @@ -517,6 +520,7 @@ describe('IgxGrid - Cell merging #grid', () => { ]); }); }); + describe('Row Selection', () => { it('should mark all merged cells that intersect with a selected row as selected.', () => { @@ -528,7 +532,6 @@ describe('IgxGrid - Cell merging #grid', () => { fix.detectChanges(); expect(secondRow.selected).toBe(true); - grid.markForCheck(); const mergedIntersectedCell = grid.gridAPI.get_cell_by_index(0, 'ProductName'); // check cell has selected style @@ -537,6 +540,143 @@ describe('IgxGrid - Cell merging #grid', () => { }); + describe('Cell Selection', () => { + it('should interrupt merge sequence so that selected cell has no merging.', () => { + const col = grid.getColumnByName('ProductName'); + grid.cellSelection = 'multiple'; + fix.detectChanges(); + + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: 'Ignite UI for JavaScript', span: 2 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 2 }, + { value: null, span: 1 }, + { value: 'NetAdvantage', span: 2 } + ]); + + const startCell = grid.gridAPI.get_cell_by_index(4, 'ProductName'); + const endCell = grid.gridAPI.get_cell_by_index(0, 'ID'); + + GridSelectionFunctions.selectCellsRangeNoWait(fix, startCell, endCell); + fix.detectChanges(); + + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: 'Ignite UI for JavaScript', span: 1 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: 'Ignite UI for Angular', span: 1 }, + { value: null, span: 1 }, + { value: 'NetAdvantage', span: 2 } + ]); + + // check api + expect(grid.getSelectedData().length).toBe(5); + expect(grid.getSelectedData()).toEqual(grid.data.slice(0, 5).map(x => { return { 'ID': x.ID, 'ProductName': x. ProductName}})); + }); + }); + + describe('Column selection', () => { + it('should mark merged cells in selected column as selected.', () => { + grid.columnSelection = 'multiple'; + fix.detectChanges(); + const col = grid.getColumnByName('ProductName'); + col.selected = true; + fix.detectChanges(); + + const mergedCells = fix.debugElement.queryAll(By.css(MERGE_CELL_CSS_CLASS)); + mergedCells.forEach(element => { + hasClass(element.nativeNode, 'igx-grid__td--column-selected', true); + }); + }); + + it('selected data API should return all associated data fields as selected.', () => { + grid.columnSelection = 'multiple'; + fix.detectChanges(); + const col = grid.getColumnByName('ProductName'); + col.selected = true; + fix.detectChanges(); + + expect(grid.getSelectedColumnsData()).toEqual(grid.data.map(x => { return {'ProductName': x. ProductName}})); + }); + }); + + describe('Filtering', () => { + + it('should merge cells in filtered data.', () => { + grid.filter('ProductName', 'Net', IgxStringFilteringOperand.instance().condition('startsWith'), true); + fix.detectChanges(); + const col = grid.getColumnByName('ProductName'); + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: 'NetAdvantage', span: 2 } + ]); + }); + + }); + + describe('Searching', () => { + + it('findNext \ findPrev should count merged cells as 1 result and navigate once through them.', () => { + const cell0 = grid.gridAPI.get_cell_by_index(0, 'ProductName').nativeElement; + const cell3 = grid.gridAPI.get_cell_by_index(3, 'ProductName').nativeElement; + const fixNativeElem = fix.debugElement.nativeElement; + + let matches = grid.findNext('JavaScript'); + fix.detectChanges(); + + expect(matches).toBe(2); + + let activeHighlight = fixNativeElem.querySelectorAll(HIGHLIGHT_ACTIVE_CSS_CLASS); + expect(activeHighlight[0].closest("igx-grid-cell")).toBe(cell0); + + matches = grid.findNext('JavaScript'); + fix.detectChanges(); + + activeHighlight = fixNativeElem.querySelectorAll(HIGHLIGHT_ACTIVE_CSS_CLASS); + expect(activeHighlight[0].closest("igx-grid-cell")).toBe(cell3); + + matches = grid.findPrev('JavaScript'); + fix.detectChanges(); + + activeHighlight = fixNativeElem.querySelectorAll(HIGHLIGHT_ACTIVE_CSS_CLASS); + expect(activeHighlight[0].closest("igx-grid-cell")).toBe(cell0); + }); + + it('should update matches if a cell becomes unmerged.', () => { + let matches = grid.findNext('JavaScript'); + fix.detectChanges(); + + expect(matches).toBe(2); + + UIInteractions.simulateClickAndSelectEvent(grid.gridAPI.get_cell_by_index(0, 'ProductName').nativeElement); + fix.detectChanges(); + + matches = grid.findNext('JavaScript'); + fix.detectChanges(); + expect(matches).toBe(3); + }); + + }); + + describe('Multi-row layout', () => { + it('should throw warning and disallow merging with mrl.', () => { + jasmine.getEnv().allowRespy(true); + fix = TestBed.createComponent(ColumnLayoutTestComponent); + fix.detectChanges(); + grid = fix.componentInstance.grid; + spyOn(console, 'warn'); + grid.columns[1].merge = true; + fix.detectChanges(); + + expect(console.warn).toHaveBeenCalledWith('Merging is not supported with multi-row layouts.'); + expect(console.warn).toHaveBeenCalledTimes(1); + jasmine.getEnv().allowRespy(false); + }); + + }); + }); }); From fc467396f89401c67edb5578b700d4ff47862737 Mon Sep 17 00:00:00 2001 From: MKirova Date: Thu, 24 Jul 2025 14:40:33 +0300 Subject: [PATCH 49/54] chore(*): Add Hgrid and TreeGrid integration tests. --- .../src/lib/grids/grid/cell-merge.spec.ts | 209 +++++++++++++++++- 1 file changed, 207 insertions(+), 2 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid/cell-merge.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/cell-merge.spec.ts index 5b0df570546..8e8bb8e58fb 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/cell-merge.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/cell-merge.spec.ts @@ -1,13 +1,16 @@ import { Component, TemplateRef, ViewChild } from '@angular/core'; import { fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { DefaultMergeStrategy, DefaultSortingStrategy, GridCellMergeMode, GridColumnDataType, GridType, IgxColumnComponent, IgxGridComponent, IgxPaginatorComponent, IgxStringFilteringOperand, SortingDirection } from 'igniteui-angular'; +import { ByLevelTreeGridMergeStrategy, DefaultMergeStrategy, DefaultSortingStrategy, GridCellMergeMode, GridColumnDataType, GridType, IgxColumnComponent, IgxGridComponent, IgxHierarchicalGridComponent, IgxPaginatorComponent, IgxStringFilteringOperand, SortingDirection } from 'igniteui-angular'; import { DataParent } from '../../test-utils/sample-test-data.spec'; import { GridFunctions, GridSelectionFunctions } from '../../test-utils/grid-functions.spec'; import { By } from '@angular/platform-browser'; import { UIInteractions, wait } from '../../test-utils/ui-interactions.spec'; import { hasClass } from '../../test-utils/helper-utils.spec'; import { ColumnLayoutTestComponent } from './grid.multi-row-layout.spec'; +import { IgxHierarchicalGridTestBaseComponent } from '../hierarchical-grid/hierarchical-grid.spec'; +import { IgxHierarchicalRowComponent } from '../hierarchical-grid/hierarchical-row.component'; +import { IgxTreeGridSelectionComponent } from '../../test-utils/tree-grid-components.spec'; describe('IgxGrid - Cell merging #grid', () => { let fix; @@ -20,7 +23,8 @@ describe('IgxGrid - Cell merging #grid', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [ - NoopAnimationsModule, DefaultCellMergeGridComponent, ColumnLayoutTestComponent + NoopAnimationsModule, DefaultCellMergeGridComponent, ColumnLayoutTestComponent, + IgxHierarchicalGridTestBaseComponent, IgxTreeGridSelectionComponent ] }).compileComponents(); })); @@ -677,6 +681,207 @@ describe('IgxGrid - Cell merging #grid', () => { }); + describe('HierarchicalGrid', () => { + + beforeEach(() => { + fix = TestBed.createComponent(IgxHierarchicalGridTestBaseComponent); + fix.componentInstance.data = [ + { + ID: 1, ChildLevels: 1, ProductName: 'Product A' , Col1: 1, + childData: [ + { + ID: 1, ChildLevels: 2, ProductName: 'Product A' , Col1: 1, + }, + { + ID: 2, ChildLevels: 2, ProductName: 'Product A' , Col1: 1, + }, + { + ID: 3, ChildLevels: 2, ProductName: 'Product B' , Col1: 1, + }, + { + ID: 4, ChildLevels: 2, ProductName: 'Product A' , Col1: 1, + } + ] + }, + { + ID: 2, ChildLevels: 1, ProductName: 'Product A' , Col1: 1, childData: [ + { + ID: 1, ChildLevels: 2, ProductName: 'Product A' , Col1: 1, + }, + { + ID: 2, ChildLevels: 2, ProductName: 'Product A' , Col1: 1, + }, + { + ID: 3, ChildLevels: 2, ProductName: 'Product A' , Col1: 1, + }, + { + ID: 4, ChildLevels: 2, ProductName: 'Product A' , Col1: 1, + } + ] + }, + { + ID: 3, ChildLevels: 1, ProductName: 'Product B' , Col1: 1 + }, + { + ID: 4, ChildLevels: 1, ProductName: 'Product B' , Col1: 1 + }, + { + ID: 5, ChildLevels: 1, ProductName: 'Product C' , Col1: 1 + }, + { + ID: 6, ChildLevels: 1, ProductName: 'Product B' , Col1: 1 + } + ]; + fix.detectChanges(); + grid = fix.componentInstance.hgrid; + // enable merging + grid.cellMergeMode = 'always'; + const col = grid.getColumnByName('ProductName'); + col.merge = true; + fix.detectChanges(); + }); + + it('should allow configuring and merging cells on each level of hierarchy.', () => { + + const col = grid.getColumnByName('ProductName'); + // root grid should be merged + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: 'Product A', span: 2 }, + { value: 'Product B', span: 2 }, + { value: 'Product C', span: 1 }, + { value: 'Product B', span: 1 } + ]); + + const ri = fix.componentInstance.rowIsland; + ri.cellMergeMode = 'always'; + ri.getColumnByName('ProductName').merge = true; + fix.detectChanges(); + + // toggle row + const firstRow = grid.gridAPI.get_row_by_index(0) as IgxHierarchicalRowComponent; + firstRow.toggle(); + fix.detectChanges(); + + const childGrid = grid.gridAPI.getChildGrids(false)[0] as IgxHierarchicalGridComponent; + expect(childGrid).toBeDefined(); + + // merging enabled + GridFunctions.verifyColumnMergedState(childGrid, childGrid.getColumnByName('ProductName'), [ + { value: 'Product A', span: 2 }, + { value: 'Product B', span: 1 }, + { value: 'Product A', span: 1 } + ]); + }); + + it('should merge cells within their respective grids only.', () => { + const ri = fix.componentInstance.rowIsland; + ri.cellMergeMode = 'always'; + ri.getColumnByName('ProductName').merge = true; + fix.detectChanges(); + + // toggle row 1 + const firstRow = grid.gridAPI.get_row_by_index(0) as IgxHierarchicalRowComponent; + firstRow.toggle(); + fix.detectChanges(); + + // toggle row 2 + const secondRow = grid.gridAPI.get_row_by_index(2) as IgxHierarchicalRowComponent; + secondRow.toggle(); + fix.detectChanges(); + + const childGrid1 = grid.gridAPI.getChildGrids(false)[0] as IgxHierarchicalGridComponent; + expect(childGrid1).toBeDefined(); + + GridFunctions.verifyColumnMergedState(childGrid1, childGrid1.getColumnByName('ProductName'), [ + { value: 'Product A', span: 2 }, + { value: 'Product B', span: 1 }, + { value: 'Product A', span: 1 } + ]); + + const childGrid2 = grid.gridAPI.getChildGrids(false)[1] as IgxHierarchicalGridComponent; + expect(childGrid2).toBeDefined(); + + GridFunctions.verifyColumnMergedState(childGrid2, childGrid2.getColumnByName('ProductName'), [ + { value: 'Product A', span: 4 } + ]); + }); + + it('should interrupt merge sequence if row is expanded and a child grid is shown between same value cells.', () => { + const col = grid.getColumnByName('ProductName'); + // root grid should be merged + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: 'Product A', span: 2 }, + { value: 'Product B', span: 2 }, + { value: 'Product C', span: 1 }, + { value: 'Product B', span: 1 } + ]); + + // toggle row 1 + const firstRow = grid.gridAPI.get_row_by_index(0) as IgxHierarchicalRowComponent; + firstRow.toggle(); + fix.detectChanges(); + + // first merge sequence interrupted due to expanded row + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: 'Product A', span: 1 }, + { value: 'Product A', span: 1 }, + { value: 'Product B', span: 2 }, + { value: 'Product C', span: 1 }, + { value: 'Product B', span: 1 } + ]); + }); + + }); + + describe('TreeGrid', () => { + + beforeEach(() => { + fix = TestBed.createComponent(IgxTreeGridSelectionComponent); + fix.detectChanges(); + grid = fix.componentInstance.treeGrid; + // enable merging + grid.cellMergeMode = 'always'; + const col = grid.getColumnByName('OnPTO'); + col.merge = true; + fix.detectChanges(); + }); + + it('should merge all cells with same values, even if on different levels by default.', () => { + const col = grid.getColumnByName('OnPTO'); + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: false, span: 2 }, + { value: true, span: 1 }, + { value: false, span: 1 }, + { value: true, span: 1 }, + { value: false, span: 2 }, + { value: true, span: 1 }, + { value: false, span: 3 }, + { value: true, span: 1 } + ]); + }); + + it('should allow setting the ByLevelTreeGridMergeStrategy as the mergeStrategy to merge only data on the same hierarchy level.', () => { + grid.mergeStrategy = new ByLevelTreeGridMergeStrategy(); + fix.detectChanges(); + grid.triggerPipes(); + fix.detectChanges(); + const col = grid.getColumnByName('OnPTO'); + GridFunctions.verifyColumnMergedState(grid, col, [ + { value: false, span: 1 }, + { value: false, span: 1 }, + { value: true, span: 1 }, + { value: false, span: 1 }, + { value: true, span: 1 }, + { value: false, span: 1 }, + { value: false, span: 1 }, + { value: true, span: 1 }, + { value: false, span: 1 }, + { value: false, span: 1 }, + { value: false, span: 1 }, + { value: true, span: 1 } + ]); + }); + }); }); }); From ea9baa684dfa247b46b8d2c76d43dd98e5460b9b Mon Sep 17 00:00:00 2001 From: MKirova Date: Thu, 24 Jul 2025 14:43:14 +0300 Subject: [PATCH 50/54] chore(*): Fix lint i tests. --- .../src/lib/grids/grid/cell-merge.spec.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid/cell-merge.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/cell-merge.spec.ts index 8e8bb8e58fb..0493bdef316 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/cell-merge.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/cell-merge.spec.ts @@ -578,7 +578,9 @@ describe('IgxGrid - Cell merging #grid', () => { // check api expect(grid.getSelectedData().length).toBe(5); - expect(grid.getSelectedData()).toEqual(grid.data.slice(0, 5).map(x => { return { 'ID': x.ID, 'ProductName': x. ProductName}})); + expect(grid.getSelectedData()).toEqual(grid.data.slice(0, 5).map(x => { + return { 'ID': x.ID, 'ProductName': x. ProductName}; + })); }); }); @@ -603,7 +605,9 @@ describe('IgxGrid - Cell merging #grid', () => { col.selected = true; fix.detectChanges(); - expect(grid.getSelectedColumnsData()).toEqual(grid.data.map(x => { return {'ProductName': x. ProductName}})); + expect(grid.getSelectedColumnsData()).toEqual(grid.data.map(x => { + return {'ProductName': x. ProductName}; + })); }); }); From 1f4c5543152a7365aee0a474aff59ca7e0451710 Mon Sep 17 00:00:00 2001 From: MKirova Date: Thu, 24 Jul 2025 15:07:16 +0300 Subject: [PATCH 51/54] chore(*): Update Changelog. --- CHANGELOG.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a5d8b720cf..1ea27757245 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,34 @@ All notable changes for each version of this project will be documented in this file. +## 20.1.0 + +### New Features + +- `IgxGrid`, `IgxTreeGrid`, `IgxHierarchicalGrid` + - Introduced a new cell merging feature that allows you to configure and merge cells in a column based on same data or other custom condition, into a single cell. + + It can be enabled on the individual columns: + + ```html + + ``` + The merging can be configured on the grid level to apply either: + - `onSort` - only when the column is sorted. + - `always` - always, regardless of data operations. + + ```html + + + ``` + + The default `cellMergeMode` is `onSort`. + + The functionality can be modified by setting a custom `mergeStrategy` on the grid, in case some other merge conditions or logic is needed for a custom scenario. + + It's possible also to set a `mergeComparer` on the individual columns, in case some custom handling is needed for a particular data field. + + ## 20.0.0 ### General From cc478207714eacca4dde0359dc7f89c2e99960af Mon Sep 17 00:00:00 2001 From: MKirova Date: Thu, 24 Jul 2025 16:11:36 +0300 Subject: [PATCH 52/54] chore(*): Fix unrelated respy issue in combo tests. --- .../src/lib/simple-combo/simple-combo.component.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.spec.ts b/projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.spec.ts index d14691194bb..f72b08c7292 100644 --- a/projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.spec.ts +++ b/projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.spec.ts @@ -532,6 +532,7 @@ describe('IgxSimpleCombo', () => { }); it('should delete the selection on destroy', () => { + jasmine.getEnv().allowRespy(true); const selectionService = new IgxSelectionAPIService(); const comboClearSpy = spyOn(mockComboService, 'clear'); const selectionDeleteSpy = spyOn(selectionService, 'delete'); @@ -548,6 +549,7 @@ describe('IgxSimpleCombo', () => { combo.ngOnDestroy(); expect(comboClearSpy).toHaveBeenCalled(); expect(selectionDeleteSpy).toHaveBeenCalled(); + jasmine.getEnv().allowRespy(false); }); }); From 35a1bde653e04b8f282497ce8198479d2a853525 Mon Sep 17 00:00:00 2001 From: MKirova Date: Thu, 24 Jul 2025 16:57:51 +0300 Subject: [PATCH 53/54] chore(*): Fix hardcoded value in unrelated test. --- .../src/lib/query-builder/query-builder.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/query-builder/query-builder.component.spec.ts b/projects/igniteui-angular/src/lib/query-builder/query-builder.component.spec.ts index 8a1e2af8635..38f0578a17e 100644 --- a/projects/igniteui-angular/src/lib/query-builder/query-builder.component.spec.ts +++ b/projects/igniteui-angular/src/lib/query-builder/query-builder.component.spec.ts @@ -773,7 +773,7 @@ describe('IgxQueryBuilder', () => { // Verify value input placeholder const input = QueryBuilderFunctions.getQueryBuilderValueInput(fix).querySelector('input'); // Verify value input placeholder - expect(input.placeholder).toEqual('Select date'); + expect(input.placeholder).toEqual(queryBuilder.resourceStrings.igx_query_builder_date_placeholder); QueryBuilderFunctions.verifyEditModeExpressionInputStates(fix, true, true, false, true); // Third input should be disabled for unary operators. // Commit the populated expression. From a7970d126246a35165c2cbdcf61c48ffbd2485fb Mon Sep 17 00:00:00 2001 From: MKirova Date: Fri, 25 Jul 2025 11:55:06 +0300 Subject: [PATCH 54/54] chore(*): Improve samples a bit. --- .../styles/components/grid/_grid-theme.scss | 2 + .../grid-cellMerging.component.html | 45 +- .../grid-cellMerging.component.scss | 3 + .../grid-cellMerging.component.ts | 343 +- src/app/shared/invoiceData.ts | 8081 +++++++++++++++++ 5 files changed, 8113 insertions(+), 361 deletions(-) create mode 100644 src/app/shared/invoiceData.ts diff --git a/projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-theme.scss b/projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-theme.scss index 5f5df28fc9c..4537aa86c68 100644 --- a/projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-theme.scss +++ b/projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-theme.scss @@ -1178,6 +1178,7 @@ %grid-row--mrl { %igx-grid__hierarchical-expander--header, + %igx-grid__hierarchical-expander, %igx-grid__header-indentation, %igx-grid__row-indentation, %grid__cbx-selection { @@ -1310,6 +1311,7 @@ } %grid__cbx-selection, + %igx-grid__hierarchical-expander, %igx-grid__row-indentation, %igx-grid__drag-indicator { border-bottom: rem(1px) solid var-get($theme, 'row-border-color'); diff --git a/src/app/grid-cellMerging/grid-cellMerging.component.html b/src/app/grid-cellMerging/grid-cellMerging.component.html index b522b1af332..bc53e1cb9fa 100644 --- a/src/app/grid-cellMerging/grid-cellMerging.component.html +++ b/src/app/grid-cellMerging/grid-cellMerging.component.html @@ -1,7 +1,7 @@

Grid with cell merge

- + @if (searchText.length === 0) { search @@ -45,46 +45,47 @@

Grid with cell merge

- - -
- - - -
-
+ + + + + + + + + + + + + - + - + - + - + - + -

Hierarchical grid with cell merge

- - + @@ -108,7 +109,7 @@

Hierarchical grid with cell merge

Tree grid with cell merge

- diff --git a/src/app/grid-cellMerging/grid-cellMerging.component.scss b/src/app/grid-cellMerging/grid-cellMerging.component.scss index 3d4037836d0..9f4f5d20867 100644 --- a/src/app/grid-cellMerging/grid-cellMerging.component.scss +++ b/src/app/grid-cellMerging/grid-cellMerging.component.scss @@ -17,3 +17,6 @@ .grid-size { --ig-size: var(--ig-size-small); } +.searchInput{ + width: 800px; +} diff --git a/src/app/grid-cellMerging/grid-cellMerging.component.ts b/src/app/grid-cellMerging/grid-cellMerging.component.ts index 89b86f37a5e..d8cb3919a15 100644 --- a/src/app/grid-cellMerging/grid-cellMerging.component.ts +++ b/src/app/grid-cellMerging/grid-cellMerging.component.ts @@ -27,6 +27,7 @@ import { HIERARCHICAL_DATA } from '../shared/hierarchicalData'; import { data, dataWithoutPK } from '../shared/data'; import { HIERARCHICAL_SAMPLE_DATA } from '../shared/sample-data'; import { ByLevelTreeGridMergeStrategy } from 'igniteui-angular'; +import { INVOICE_DATA } from '../shared/invoiceData'; @Component({ selector: 'app-grid-cellMerging', @@ -36,16 +37,13 @@ import { ByLevelTreeGridMergeStrategy } from 'igniteui-angular'; FormsModule, IgxColumnComponent, IgxGridComponent, - IgxCellTemplateDirective, - IgxButtonDirective, IgxPaginatorComponent, - IgxActionStripComponent, - IgxGridPinningActionsComponent, + // IgxActionStripComponent, + // IgxGridPinningActionsComponent, IgxGridToolbarComponent, IgxGridToolbarActionsComponent, IgxGridToolbarPinningComponent, IgxGridToolbarHidingComponent, - IgxGridToolbarExporterComponent, IgxHierarchicalGridComponent, IgxRowIslandComponent, IgxTreeGridComponent, @@ -64,340 +62,7 @@ export class GridCellMergingComponent { public alignTop= { alignItems: "flex-start", paddingTop: "12px" }; public searchText: string =''; @ViewChild('grid1', { static: true }) public grid: IgxGridComponent; - public data = [{ - ProductID: 1, - ProductName: 'Chai', - SupplierID: 1, - CategoryID: 1, - QuantityPerUnit: '10 boxes x 20 bags', - UnitPrice: '18.0000', - UnitsInStock: 39, - UnitsOnOrder: 0, - ReorderLevel: 10.567, - Discontinued: false, - OrderDate: null, - OrderDate2: new Date(1991, 2, 12, 18, 40, 50).toISOString() - }, { - ProductID: 2, - ProductName: 'Chai', - SupplierID: 1, - CategoryID: 1, - QuantityPerUnit: '24 - 12 oz bottles', - UnitPrice: '19.0000', - UnitsInStock: 17, - UnitsOnOrder: 40, - ReorderLevel: 25, - Discontinued: false, - OrderDate: new Date('2003-03-17').toISOString(), - OrderDate2: new Date('2003-03-17').toISOString() - }, - { - ProductID: 3, - ProductName: 'Chai', - SupplierID: 1, - CategoryID: 1, - QuantityPerUnit: '24 - 12 oz bottles', - UnitPrice: '19.0000', - UnitsInStock: 17, - UnitsOnOrder: 40, - ReorderLevel: 25, - Discontinued: false, - OrderDate: new Date('2003-03-17').toISOString(), - OrderDate2: new Date('2003-03-17').toISOString() - }, - { - ProductID: 4, - ProductName: 'Chai', - SupplierID: 1, - CategoryID: 1, - QuantityPerUnit: '24 - 12 oz bottles', - UnitPrice: '20.0000', - UnitsInStock: 20, - UnitsOnOrder: 40, - ReorderLevel: 25, - Discontinued: false, - OrderDate: new Date('2003-03-17').toISOString(), - OrderDate2: new Date('2003-03-17').toISOString() - }, - { - ProductID: 5, - ProductName: 'Chai', - SupplierID: 1, - CategoryID: 1, - QuantityPerUnit: '24 - 12 oz bottles', - UnitPrice: '19.0000', - UnitsInStock: 17, - UnitsOnOrder: 40, - ReorderLevel: 25, - Discontinued: false, - OrderDate: new Date('2003-03-17').toISOString(), - OrderDate2: new Date('2003-03-17').toISOString() - }, - { - ProductID: 6, - ProductName: 'Chang', - SupplierID: 1, - CategoryID: 1, - QuantityPerUnit: '24 - 12 oz bottles', - UnitPrice: '19.0000', - UnitsInStock: 17, - UnitsOnOrder: 40, - ReorderLevel: 25, - Discontinued: false, - OrderDate: new Date('2003-03-17').toISOString(), - OrderDate2: new Date('2003-03-17').toISOString() - }, - { - ProductID: 7, - ProductName: 'Chang', - SupplierID: 1, - CategoryID: 1, - QuantityPerUnit: '24 - 12 oz bottles', - UnitPrice: '19.0000', - UnitsInStock: 17, - UnitsOnOrder: 40, - ReorderLevel: 25, - Discontinued: false, - OrderDate: new Date('2003-03-17').toISOString(), - OrderDate2: new Date('2003-03-17').toISOString() - }, - { - ProductID: 8, - ProductName: 'Chang', - SupplierID: 1, - CategoryID: 1, - QuantityPerUnit: '24 - 12 oz bottles', - UnitPrice: '19.0000', - UnitsInStock: 17, - UnitsOnOrder: 40, - ReorderLevel: 30, - Discontinued: false, - OrderDate: new Date('2003-03-17').toISOString(), - OrderDate2: new Date('2003-03-17').toISOString() - }, - { - ProductID: 9, - ProductName: 'Aniseed Syrup', - SupplierID: 1, - CategoryID: 2, - QuantityPerUnit: '12 - 550 ml bottles', - UnitPrice: '10.0000', - UnitsInStock: 13, - UnitsOnOrder: 70, - ReorderLevel: 25, - Discontinued: false, - OrderDate: new Date('2006-03-17').toISOString(), - OrderDate2: new Date(1991, 2, 12, 15, 40, 50).toISOString() - }, - { - ProductID: 10, - ProductName: 'Chang', - SupplierID: 1, - CategoryID: 2, - QuantityPerUnit: '12 - 550 ml bottles', - UnitPrice: '10.0000', - UnitsInStock: 13, - UnitsOnOrder: 70, - ReorderLevel: 25, - Discontinued: false, - OrderDate: new Date('2006-03-17').toISOString(), - OrderDate2: new Date(1991, 2, 12, 15, 40, 50).toISOString() - }, - { - ProductID: 11, - ProductName: 'Chai', - SupplierID: 1, - CategoryID: 2, - QuantityPerUnit: '12 - 550 ml bottles', - UnitPrice: '10.0000', - UnitsInStock: 13, - UnitsOnOrder: 70, - ReorderLevel: 25, - Discontinued: false, - OrderDate: new Date('2006-03-17').toISOString(), - OrderDate2: new Date(1991, 2, 12, 15, 40, 50).toISOString() - }, - { - ProductID: 12, - ProductName: 'Chai', - SupplierID: 1, - CategoryID: 2, - QuantityPerUnit: '12 - 550 ml bottles', - UnitPrice: '10.0000', - UnitsInStock: 12, - UnitsOnOrder: 70, - ReorderLevel: 30, - Discontinued: false, - OrderDate: new Date('2006-03-17').toISOString(), - OrderDate2: new Date(1991, 2, 12, 15, 40, 50).toISOString() - }, - { - ProductID: 13, - ProductName: 'Chai', - SupplierID: 1, - CategoryID: 1, - QuantityPerUnit: '10 boxes x 20 bags', - UnitPrice: '18.0000', - UnitsInStock: 39, - UnitsOnOrder: 0, - ReorderLevel: 10.567, - Discontinued: false, - OrderDate: null, - OrderDate2: new Date(1991, 2, 12, 18, 40, 50).toISOString() - }, { - ProductID: 14, - ProductName: 'Chai', - SupplierID: 1, - CategoryID: 1, - QuantityPerUnit: '24 - 12 oz bottles', - UnitPrice: '19.0000', - UnitsInStock: 17, - UnitsOnOrder: 40, - ReorderLevel: 25, - Discontinued: false, - OrderDate: new Date('2003-03-17').toISOString(), - OrderDate2: new Date('2003-03-17').toISOString() - }, - { - ProductID: 15, - ProductName: 'Chai', - SupplierID: 1, - CategoryID: 1, - QuantityPerUnit: '24 - 12 oz bottles', - UnitPrice: '19.0000', - UnitsInStock: 17, - UnitsOnOrder: 40, - ReorderLevel: 25, - Discontinued: false, - OrderDate: new Date('2003-03-17').toISOString(), - OrderDate2: new Date('2003-03-17').toISOString() - }, - { - ProductID: 16, - ProductName: 'Chai', - SupplierID: 1, - CategoryID: 1, - QuantityPerUnit: '24 - 12 oz bottles', - UnitPrice: '20.0000', - UnitsInStock: 20, - UnitsOnOrder: 40, - ReorderLevel: 25, - Discontinued: false, - OrderDate: new Date('2003-03-17').toISOString(), - OrderDate2: new Date('2003-03-17').toISOString() - }, - { - ProductID: 17, - ProductName: 'Chai', - SupplierID: 1, - CategoryID: 1, - QuantityPerUnit: '24 - 12 oz bottles', - UnitPrice: '19.0000', - UnitsInStock: 17, - UnitsOnOrder: 40, - ReorderLevel: 25, - Discontinued: false, - OrderDate: new Date('2003-03-17').toISOString(), - OrderDate2: new Date('2003-03-17').toISOString() - }, - { - ProductID: 18, - ProductName: 'Chang', - SupplierID: 1, - CategoryID: 1, - QuantityPerUnit: '24 - 12 oz bottles', - UnitPrice: '19.0000', - UnitsInStock: 17, - UnitsOnOrder: 40, - ReorderLevel: 25, - Discontinued: false, - OrderDate: new Date('2003-03-17').toISOString(), - OrderDate2: new Date('2003-03-17').toISOString() - }, - { - ProductID: 19, - ProductName: 'Chang', - SupplierID: 1, - CategoryID: 1, - QuantityPerUnit: '24 - 12 oz bottles', - UnitPrice: '19.0000', - UnitsInStock: 17, - UnitsOnOrder: 40, - ReorderLevel: 25, - Discontinued: false, - OrderDate: new Date('2003-03-17').toISOString(), - OrderDate2: new Date('2003-03-17').toISOString() - }, - { - ProductID: 20, - ProductName: 'Chang', - SupplierID: 1, - CategoryID: 1, - QuantityPerUnit: '24 - 12 oz bottles', - UnitPrice: '19.0000', - UnitsInStock: 17, - UnitsOnOrder: 40, - ReorderLevel: 30, - Discontinued: false, - OrderDate: new Date('2003-03-17').toISOString(), - OrderDate2: new Date('2003-03-17').toISOString() - }, - { - ProductID: 21, - ProductName: 'Aniseed Syrup', - SupplierID: 1, - CategoryID: 2, - QuantityPerUnit: '12 - 550 ml bottles', - UnitPrice: '10.0000', - UnitsInStock: 13, - UnitsOnOrder: 70, - ReorderLevel: 25, - Discontinued: false, - OrderDate: new Date('2006-03-17').toISOString(), - OrderDate2: new Date(1991, 2, 12, 15, 40, 50).toISOString() - }, - { - ProductID: 22, - ProductName: 'Chang', - SupplierID: 1, - CategoryID: 2, - QuantityPerUnit: '12 - 550 ml bottles', - UnitPrice: '10.0000', - UnitsInStock: 13, - UnitsOnOrder: 70, - ReorderLevel: 25, - Discontinued: false, - OrderDate: new Date('2006-03-17').toISOString(), - OrderDate2: new Date(1991, 2, 12, 15, 40, 50).toISOString() - }, - { - ProductID: 23, - ProductName: 'Chai', - SupplierID: 1, - CategoryID: 2, - QuantityPerUnit: '12 - 550 ml bottles', - UnitPrice: '10.0000', - UnitsInStock: 13, - UnitsOnOrder: 70, - ReorderLevel: 25, - Discontinued: false, - OrderDate: new Date('2006-03-17').toISOString(), - OrderDate2: new Date(1991, 2, 12, 15, 40, 50).toISOString() - }, - { - ProductID: 24, - ProductName: 'Chai', - SupplierID: 1, - CategoryID: 2, - QuantityPerUnit: '12 - 550 ml bottles', - UnitPrice: '10.0000', - UnitsInStock: 12, - UnitsOnOrder: 70, - ReorderLevel: 30, - Discontinued: false, - OrderDate: new Date('2006-03-17').toISOString(), - OrderDate2: new Date(1991, 2, 12, 15, 40, 50).toISOString() - }]; + public data = INVOICE_DATA; public searchKeyDown(ev) { if (ev.key === 'Enter' || ev.key === 'ArrowDown' || ev.key === 'ArrowRight') { diff --git a/src/app/shared/invoiceData.ts b/src/app/shared/invoiceData.ts new file mode 100644 index 00000000000..051444c0009 --- /dev/null +++ b/src/app/shared/invoiceData.ts @@ -0,0 +1,8081 @@ +/* eslint-disable */ + +export interface Invoice { + ID: number; + ShipAddress: string; + ShipCity: string; + ShipCountry: string; + ShipName: string; + ShipRegion: string; + ShipPostalCode: string; + CustomerID: string; + CustomerName: string; + Address: string; + City: string; + Region: string; + PostalCode: string; + Country: string; + Salesperson: string; + OrderID: number; + OrderDate: Date; + ShipperName: string; + ProductID: number; + ProductName: string; + UnitPrice: number; + Quantity: number; + Discontinued: boolean; + ExtendedPrice: number; + Freight: number; +} + +export const INVOICE_DATA = [{ + ShipAddress: "Obere Str. 57", + ShipCity: "Berlin", + ShipCountry: "Germany", + ShipName: "Alfred's Futterkiste", + ShipRegion: null, + ShipPostalCode: "12209", + CustomerID: "ALFKI", + CustomerName: "Alfreds Futterkiste", + Address: "Obere Str. 57", + City: "Berlin", + Region: null, + PostalCode: "12209", + Country: "Germany", + Salesperson: "Margaret Peacock", + OrderID: 10692, + OrderDate: new Date("11/23/2016"), + ShipperName: "United Package", + ProductID: 63, + ProductName: "Vegie-spread", + UnitPrice: 43.9000, + Quantity: 20, + Discontinued: false, + ExtendedPrice: 878.0000, + Freight: 61.0200 +}, { + ShipName: "Alfred's Futterkiste", + ShipCity: "Berlin", + ShipRegion: null, + ShipPostalCode: "12209", + ShipCountry: "Germany", + CustomerID: "ALFKI", + CustomerName: "Alfreds Futterkiste", + Address: "Obere Str. 57", + City: "Berlin", + Region: null, + PostalCode: "12209", + Country: "Germany", + Salesperson: "Margaret Peacock", + OrderID: 10702, + OrderDate: new Date("11/23/2016"), + ShipperName: "Speedy Express", + ProductID: 3, + ProductName: "Aniseed Syrup", + UnitPrice: 10.0000, + Quantity: 6, + Discontinued: false, + ExtendedPrice: 60.0000, + Freight: 23.9400 +}, { + ShipName: "Alfred's Futterkiste", + ShipAddress: "Obere Str. 57", + ShipCity: "Berlin", + ShipRegion: null, + ShipPostalCode: "12209", + ShipCountry: "Germany", + CustomerID: "ALFKI", + CustomerName: "Alfreds Futterkiste", + Address: "Obere Str. 57", + City: "Berlin", + Region: null, + PostalCode: "12209", + Country: "Germany", + Salesperson: "Margaret Peacock", + OrderID: 10702, + OrderDate: new Date("11/23/2016"), + ShipperName: "Speedy Express", + ProductID: 76, + ProductName: "Lakkalik\u00f6\u00f6ri", + UnitPrice: 18.0000, + Quantity: 15, + Discontinued: false, + ExtendedPrice: 270.0000, + Freight: 23.9400 +}, { + ShipName: "Alfred's Futterkiste", + ShipAddress: "Obere Str. 57", + ShipCity: "Berlin", + ShipRegion: null, + ShipPostalCode: "12209", + ShipCountry: "Germany", + CustomerID: "ALFKI", + CustomerName: "Alfreds Futterkiste", + Address: "Obere Str. 57", + City: "Berlin", + Region: null, + PostalCode: "12209", + Country: "Germany", + Salesperson: "Nancy Davolio", + OrderID: 10835, + OrderDate: new Date("11/23/2016"), + ShipperName: "Federal Shipping", + ProductID: 59, + ProductName: "Raclette Courdavault", + UnitPrice: 55.0000, + Quantity: 15, + Discontinued: false, + ExtendedPrice: 825.0000, + Freight: 69.5300 +}, { + ShipName: "Alfred's Futterkiste", + ShipAddress: "Obere Str. 57", + ShipCity: "Berlin", + ShipRegion: null, + ShipPostalCode: "12209", + ShipCountry: "Germany", + CustomerID: "ALFKI", + CustomerName: "Alfreds Futterkiste", + Address: "Obere Str. 57", + City: "Berlin", + Region: null, + PostalCode: "12209", + Country: "Germany", + Salesperson: "Nancy Davolio", + OrderID: 10952, + OrderDate: new Date("11/23/2016"), + ShipperName: "Speedy Express", + ProductID: 28, + ProductName: "R\u00f6ssle Sauerkraut", + UnitPrice: 45.6000, + Quantity: 2, + Discontinued: true, + ExtendedPrice: 91.2000, + Freight: 40.4200 +}, { + ShipName: "Alfred's Futterkiste", + ShipAddress: "Obere Str. 57", + ShipCity: "Berlin", + ShipRegion: null, + ShipPostalCode: "12209", + ShipCountry: "Germany", + CustomerID: "ALFKI", + CustomerName: "Alfreds Futterkiste", + Address: "Obere Str. 57", + City: "Berlin", + Region: null, + PostalCode: "12209", + Country: "Germany", + Salesperson: "Janet Leverling", + OrderID: 11011, + OrderDate: new Date("11/23/2016"), + ShipperName: "Speedy Express", + ProductID: 71, + ProductName: "Flotemysost", + UnitPrice: 21.5000, + Quantity: 20, + Discontinued: true, + ExtendedPrice: 430.0000, + Freight: 1.2100 +}, { + ShipName: "Alfred's Futterkiste", + ShipAddress: "Obere Str. 57", + ShipCity: "Berlin", + ShipRegion: null, + ShipPostalCode: "12209", + ShipCountry: "Germany", + CustomerID: "ALFKI", + CustomerName: "Alfreds Futterkiste", + Address: "Obere Str. 57", + City: "Berlin", + Region: null, + PostalCode: "12209", + Country: "Germany", + Salesperson: "Nancy Davolio", + OrderID: 10952, + OrderDate: new Date("11/23/2016"), + ShipperName: "Speedy Express", + ProductID: 6, + ProductName: "Grandma's Boysenberry Spread", + UnitPrice: 25.0000, + Quantity: 16, + Discontinued: true, + ExtendedPrice: 380.0000, + Freight: 40.4200 +}, { + ShipName: "Alfred's Futterkiste", + ShipAddress: "Obere Str. 57", + ShipCity: "Berlin", + ShipRegion: null, + ShipPostalCode: "12209", + ShipCountry: "Germany", + CustomerID: "ALFKI", + CustomerName: "Alfreds Futterkiste", + Address: "Obere Str. 57", + City: "Berlin", + Region: null, + PostalCode: "12209", + Country: "Germany", + Salesperson: "Janet Leverling", + OrderID: 11011, + OrderDate: new Date("11/23/2016"), + ShipperName: "Speedy Express", + ProductID: 58, + ProductName: "Escargots de Bourgogne", + UnitPrice: 13.2500, + Quantity: 40, + Discontinued: true, + ExtendedPrice: 503.5000, + Freight: 1.2100 +}, { + ShipName: "Alfred's Futterkiste", + ShipAddress: "Obere Str. 57", + ShipCity: "Berlin", + ShipRegion: null, + ShipPostalCode: "12209", + ShipCountry: "Germany", + CustomerID: "ALFKI", + CustomerName: "Alfreds Futterkiste", + Address: "Obere Str. 57", + City: "Berlin", + Region: null, + PostalCode: "12209", + Country: "Germany", + Salesperson: "Nancy Davolio", + OrderID: 10835, + OrderDate: new Date("11/23/2016"), + ShipperName: "Federal Shipping", + ProductID: 77, + ProductName: "Original Frankfurter gr\u00fcne So\u00dfe", + UnitPrice: 13.0000, + Quantity: 2, + Discontinued: true, + ExtendedPrice: 20.8000, + Freight: 69.5300 +}, { + ShipName: "Alfreds Futterkiste", + ShipAddress: "Obere Str. 57", + ShipCity: "Berlin", + ShipRegion: null, + ShipPostalCode: "12209", + ShipCountry: "Germany", + CustomerID: "ALFKI", + CustomerName: "Alfreds Futterkiste", + Address: "Obere Str. 57", + City: "Berlin", + Region: null, + PostalCode: "12209", + Country: "Germany", + Salesperson: "Michael Suyama", + OrderID: 10643, + OrderDate: new Date("11/23/2016"), + ShipperName: "Speedy Express", + ProductID: 28, + ProductName: "R\u00f6ssle Sauerkraut", + UnitPrice: 45.6000, + Quantity: 15, + Discontinued: true, + ExtendedPrice: 513.0000, + Freight: 29.4600 +}, { + ShipName: "Alfreds Futterkiste", + ShipAddress: "Obere Str. 57", + ShipCity: "Berlin", + ShipRegion: null, + ShipPostalCode: "12209", + ShipCountry: "Germany", + CustomerID: "ALFKI", + CustomerName: "Alfreds Futterkiste", + Address: "Obere Str. 57", + City: "Berlin", + Region: null, + PostalCode: "12209", + Country: "Germany", + Salesperson: "Michael Suyama", + OrderID: 10643, + OrderDate: new Date("11/23/2016"), + ShipperName: "Speedy Express", + ProductID: 39, + ProductName: "Chartreuse verte", + UnitPrice: 18.0000, + Quantity: 21, + Discontinued: true, + ExtendedPrice: 283.5000, + Freight: 29.4600 +}, { + ShipName: "Alfreds Futterkiste", + ShipAddress: "Obere Str. 57", + ShipCity: "Berlin", + ShipRegion: null, + ShipPostalCode: "12209", + ShipCountry: "Germany", + CustomerID: "ALFKI", + CustomerName: "Alfreds Futterkiste", + Address: "Obere Str. 57", + City: "Berlin", + Region: null, + PostalCode: "12209", + Country: "Germany", + Salesperson: "Michael Suyama", + OrderID: 10643, + OrderDate: new Date("11/23/2016"), + ShipperName: "Speedy Express", + ProductID: 46, + ProductName: "Spegesild", + UnitPrice: 12.0000, + Quantity: 2, + Discontinued: true, + ExtendedPrice: 18.0000, + Freight: 29.4600 +}, { + ShipName: "Ana Trujillo Emparedados y helados", + ShipAddress: "Avda. de la Constituci\u00f3n 2222", + ShipCity: "M\u00e9xico D.F.", + ShipRegion: null, + ShipPostalCode: "05021", + ShipCountry: "Mexico", + CustomerID: "ANATR", + CustomerName: "Ana Trujillo Emparedados y helados", + Address: "Avda. de la Constituci\u00f3n 2222", + City: "M\u00e9xico D.F.", + Region: null, + PostalCode: "05021", + Country: "Mexico", + Salesperson: "Robert King", + OrderID: 10308, + OrderDate: new Date("11/23/2016"), + ShipperName: "Federal Shipping", + ProductID: 69, + ProductName: "Gudbrandsdalsost", + UnitPrice: 28.8000, + Quantity: 1, + Discontinued: false, + ExtendedPrice: 28.8000, + Freight: 1.6100 +}, { + ShipName: "Ana Trujillo Emparedados y helados", + ShipAddress: "Avda. de la Constituci\u00f3n 2222", + ShipCity: "M\u00e9xico D.F.", + ShipRegion: null, + ShipPostalCode: "05021", + ShipCountry: "Mexico", + CustomerID: "ANATR", + CustomerName: "Ana Trujillo Emparedados y helados", + Address: "Avda. de la Constituci\u00f3n 2222", + City: "M\u00e9xico D.F.", + Region: null, + PostalCode: "05021", + Country: "Mexico", + Salesperson: "Robert King", + OrderID: 10308, + OrderDate: new Date("11/23/2016"), + ShipperName: "Federal Shipping", + ProductID: 70, + ProductName: "Outback Lager", + UnitPrice: 12.0000, + Quantity: 5, + Discontinued: false, + ExtendedPrice: 60.0000, + Freight: 1.6100 +}, { + ShipName: "Ana Trujillo Emparedados y helados", + ShipAddress: "Avda. de la Constituci\u00f3n 2222", + ShipCity: "M\u00e9xico D.F.", + ShipRegion: null, + ShipPostalCode: "05021", + ShipCountry: "Mexico", + CustomerID: "ANATR", + CustomerName: "Ana Trujillo Emparedados y helados", + Address: "Avda. de la Constituci\u00f3n 2222", + City: "M\u00e9xico D.F.", + Region: null, + PostalCode: "05021", + Country: "Mexico", + Salesperson: "Janet Leverling", + OrderID: 10625, + OrderDate: new Date("11/23/2016"), + ShipperName: "Speedy Express", + ProductID: 14, + ProductName: "Tofu", + UnitPrice: 23.2500, + Quantity: 3, + Discontinued: false, + ExtendedPrice: 69.7500, + Freight: 43.9000 +}, { + ShipName: "Ana Trujillo Emparedados y helados", + ShipAddress: "Avda. de la Constituci\u00f3n 2222", + ShipCity: "M\u00e9xico D.F.", + ShipRegion: null, + ShipPostalCode: "05021", + ShipCountry: "Mexico", + CustomerID: "ANATR", + CustomerName: "Ana Trujillo Emparedados y helados", + Address: "Avda. de la Constituci\u00f3n 2222", + City: "M\u00e9xico D.F.", + Region: null, + PostalCode: "05021", + Country: "Mexico", + Salesperson: "Janet Leverling", + OrderID: 10625, + OrderDate: new Date("11/23/2016"), + ShipperName: "Speedy Express", + ProductID: 42, + ProductName: "Singaporean Hokkien Fried Mee", + UnitPrice: 14.0000, + Quantity: 5, + Discontinued: false, + ExtendedPrice: 70.0000, + Freight: 43.9000 +}, { + ShipName: "Ana Trujillo Emparedados y helados", + ShipAddress: "Avda. de la Constituci\u00f3n 2222", + ShipCity: "M\u00e9xico D.F.", + ShipRegion: null, + ShipPostalCode: "05021", + ShipCountry: "Mexico", + CustomerID: "ANATR", + CustomerName: "Ana Trujillo Emparedados y helados", + Address: "Avda. de la Constituci\u00f3n 2222", + City: "M\u00e9xico D.F.", + Region: null, + PostalCode: "05021", + Country: "Mexico", + Salesperson: "Janet Leverling", + OrderID: 10625, + OrderDate: new Date("11/23/2016"), + ShipperName: "Speedy Express", + ProductID: 60, + ProductName: "Camembert Pierrot", + UnitPrice: 34.0000, + Quantity: 10, + Discontinued: false, + ExtendedPrice: 340.0000, + Freight: 43.9000 +}, { + ShipName: "Ana Trujillo Emparedados y helados", + ShipAddress: "Avda. de la Constituci\u00f3n 2222", + ShipCity: "M\u00e9xico D.F.", + ShipRegion: null, + ShipPostalCode: "05021", + ShipCountry: "Mexico", + CustomerID: "ANATR", + CustomerName: "Ana Trujillo Emparedados y helados", + Address: "Avda. de la Constituci\u00f3n 2222", + City: "M\u00e9xico D.F.", + Region: null, + PostalCode: "05021", + Country: "Mexico", + Salesperson: "Janet Leverling", + OrderID: 10759, + OrderDate: new Date("11/23/2016"), + ShipperName: "Federal Shipping", + ProductID: 32, + ProductName: "Mascarpone Fabioli", + UnitPrice: 32.0000, + Quantity: 10, + Discontinued: false, + ExtendedPrice: 320.0000, + Freight: 11.9900 +}, { + ShipName: "Ana Trujillo Emparedados y helados", + ShipAddress: "Avda. de la Constituci\u00f3n 2222", + ShipCity: "M\u00e9xico D.F.", + ShipRegion: null, + ShipPostalCode: "05021", + ShipCountry: "Mexico", + CustomerID: "ANATR", + CustomerName: "Ana Trujillo Emparedados y helados", + Address: "Avda. de la Constituci\u00f3n 2222", + City: "M\u00e9xico D.F.", + Region: null, + PostalCode: "05021", + Country: "Mexico", + Salesperson: "Margaret Peacock", + OrderID: 10926, + OrderDate: new Date("11/23/2016"), + ShipperName: "Federal Shipping", + ProductID: 11, + ProductName: "Queso Cabrales", + UnitPrice: 21.0000, + Quantity: 2, + Discontinued: false, + ExtendedPrice: 42.0000, + Freight: 39.9200 +}, { + ShipName: "Ana Trujillo Emparedados y helados", + ShipAddress: "Avda. de la Constituci\u00f3n 2222", + ShipCity: "M\u00e9xico D.F.", + ShipRegion: null, + ShipPostalCode: "05021", + ShipCountry: "Mexico", + CustomerID: "ANATR", + CustomerName: "Ana Trujillo Emparedados y helados", + Address: "Avda. de la Constituci\u00f3n 2222", + City: "M\u00e9xico D.F.", + Region: null, + PostalCode: "05021", + Country: "Mexico", + Salesperson: "Margaret Peacock", + OrderID: 10926, + OrderDate: new Date("11/23/2016"), + ShipperName: "Federal Shipping", + ProductID: 13, + ProductName: "Konbu", + UnitPrice: 6.0000, + Quantity: 10, + Discontinued: false, + ExtendedPrice: 60.0000, + Freight: 39.9200 +}, { + ShipName: "Ana Trujillo Emparedados y helados", + ShipAddress: "Avda. de la Constituci\u00f3n 2222", + ShipCity: "M\u00e9xico D.F.", + ShipRegion: null, + ShipPostalCode: "05021", + ShipCountry: "Mexico", + CustomerID: "ANATR", + CustomerName: "Ana Trujillo Emparedados y helados", + Address: "Avda. de la Constituci\u00f3n 2222", + City: "M\u00e9xico D.F.", + Region: null, + PostalCode: "05021", + Country: "Mexico", + Salesperson: "Margaret Peacock", + OrderID: 10926, + OrderDate: new Date("11/23/2016"), + ShipperName: "Federal Shipping", + ProductID: 19, + ProductName: "Teatime Chocolate Biscuits", + UnitPrice: 9.2000, + Quantity: 7, + Discontinued: false, + ExtendedPrice: 64.4000, + Freight: 39.9200 +}, { + ShipName: "Ana Trujillo Emparedados y helados", + ShipAddress: "Avda. de la Constituci\u00f3n 2222", + ShipCity: "M\u00e9xico D.F.", + ShipRegion: null, + ShipPostalCode: "05021", + ShipCountry: "Mexico", + CustomerID: "ANATR", + CustomerName: "Ana Trujillo Emparedados y helados", + Address: "Avda. de la Constituci\u00f3n 2222", + City: "M\u00e9xico D.F.", + Region: null, + PostalCode: "05021", + Country: "Mexico", + Salesperson: "Margaret Peacock", + OrderID: 10926, + OrderDate: new Date("11/23/2016"), + ShipperName: "Federal Shipping", + ProductID: 72, + ProductName: "Mozzarella di Giovanni", + UnitPrice: 34.8000, + Quantity: 10, + Discontinued: false, + ExtendedPrice: 348.0000, + Freight: 39.9200 +}, { + ShipName: "Antonio Moreno Taquer\u00eda", + ShipAddress: "Mataderos 2312", + ShipCity: "M\u00e9xico D.F.", + ShipRegion: null, + ShipPostalCode: "05023", + ShipCountry: "Mexico", + CustomerID: "ANTON", + CustomerName: "Antonio Moreno Taquer\u00eda", + Address: "Mataderos 2312", + City: "M\u00e9xico D.F.", + Region: null, + PostalCode: "05023", + Country: "Mexico", + Salesperson: "Janet Leverling", + OrderID: 10365, + OrderDate: new Date("11/23/2016"), + ShipperName: "United Package", + ProductID: 11, + ProductName: "Queso Cabrales", + UnitPrice: 16.8000, + Quantity: 24, + Discontinued: false, + ExtendedPrice: 403.2000, + Freight: 22.0000 +}, { + ShipName: "Antonio Moreno Taquer\u00eda", + ShipAddress: "Mataderos 2312", + ShipCity: "M\u00e9xico D.F.", + ShipRegion: null, + ShipPostalCode: "05023", + ShipCountry: "Mexico", + CustomerID: "ANTON", + CustomerName: "Antonio Moreno Taquer\u00eda", + Address: "Mataderos 2312", + City: "M\u00e9xico D.F.", + Region: null, + PostalCode: "05023", + Country: "Mexico", + Salesperson: "Robert King", + OrderID: 10573, + OrderDate: new Date("11/23/2016"), + ShipperName: "Federal Shipping", + ProductID: 17, + ProductName: "Alice Mutton", + UnitPrice: 39.0000, + Quantity: 18, + Discontinued: false, + ExtendedPrice: 702.0000, + Freight: 84.8400 +}, { + ShipName: "Antonio Moreno Taquer\u00eda", + ShipAddress: "Mataderos 2312", + ShipCity: "M\u00e9xico D.F.", + ShipRegion: null, + ShipPostalCode: "05023", + ShipCountry: "Mexico", + CustomerID: "ANTON", + CustomerName: "Antonio Moreno Taquer\u00eda", + Address: "Mataderos 2312", + City: "M\u00e9xico D.F.", + Region: null, + PostalCode: "05023", + Country: "Mexico", + Salesperson: "Robert King", + OrderID: 10573, + OrderDate: new Date("11/23/2016"), + ShipperName: "Federal Shipping", + ProductID: 34, + ProductName: "Sasquatch Ale", + UnitPrice: 14.0000, + Quantity: 40, + Discontinued: false, + ExtendedPrice: 560.0000, + Freight: 84.8400 +}, { + ShipName: "Antonio Moreno Taquer\u00eda", + ShipAddress: "Mataderos 2312", + ShipCity: "M\u00e9xico D.F.", + ShipRegion: null, + ShipPostalCode: "05023", + ShipCountry: "Mexico", + CustomerID: "ANTON", + CustomerName: "Antonio Moreno Taquer\u00eda", + Address: "Mataderos 2312", + City: "M\u00e9xico D.F.", + Region: null, + PostalCode: "05023", + Country: "Mexico", + Salesperson: "Robert King", + OrderID: 10573, + OrderDate: new Date("11/23/2016"), + ShipperName: "Federal Shipping", + ProductID: 53, + ProductName: "Perth Pasties", + UnitPrice: 32.8000, + Quantity: 25, + Discontinued: false, + ExtendedPrice: 820.0000, + Freight: 84.8400 +}, { + ShipName: "Antonio Moreno Taquer\u00eda", + ShipAddress: "Mataderos 2312", + ShipCity: "M\u00e9xico D.F.", + ShipRegion: null, + ShipPostalCode: "05023", + ShipCountry: "Mexico", + CustomerID: "ANTON", + CustomerName: "Antonio Moreno Taquer\u00eda", + Address: "Mataderos 2312", + City: "M\u00e9xico D.F.", + Region: null, + PostalCode: "05023", + Country: "Mexico", + Salesperson: "Janet Leverling", + OrderID: 10682, + OrderDate: new Date("11/23/2016"), + ShipperName: "United Package", + ProductID: 33, + ProductName: "Geitost", + UnitPrice: 2.5000, + Quantity: 30, + Discontinued: false, + ExtendedPrice: 75.0000, + Freight: 36.1300 +}, { + ShipName: "Antonio Moreno Taquer\u00eda", + ShipAddress: "Mataderos 2312", + ShipCity: "M\u00e9xico D.F.", + ShipRegion: null, + ShipPostalCode: "05023", + ShipCountry: "Mexico", + CustomerID: "ANTON", + CustomerName: "Antonio Moreno Taquer\u00eda", + Address: "Mataderos 2312", + City: "M\u00e9xico D.F.", + Region: null, + PostalCode: "05023", + Country: "Mexico", + Salesperson: "Janet Leverling", + OrderID: 10682, + OrderDate: new Date("11/23/2016"), + ShipperName: "United Package", + ProductID: 66, + ProductName: "Louisiana Hot Spiced Okra", + UnitPrice: 17.0000, + Quantity: 4, + Discontinued: false, + ExtendedPrice: 68.0000, + Freight: 36.1300 +}, { + ShipName: "Antonio Moreno Taquer\u00eda", + ShipAddress: "Mataderos 2312", + ShipCity: "M\u00e9xico D.F.", + ShipRegion: null, + ShipPostalCode: "05023", + ShipCountry: "Mexico", + CustomerID: "ANTON", + CustomerName: "Antonio Moreno Taquer\u00eda", + Address: "Mataderos 2312", + City: "M\u00e9xico D.F.", + Region: null, + PostalCode: "05023", + Country: "Mexico", + Salesperson: "Janet Leverling", + OrderID: 10682, + OrderDate: new Date("11/23/2016"), + ShipperName: "United Package", + ProductID: 75, + ProductName: "Rh\u00f6nbr\u00e4u Klosterbier", + UnitPrice: 7.7500, + Quantity: 30, + Discontinued: false, + ExtendedPrice: 232.5000, + Freight: 36.1300 +}, { + ShipName: "Antonio Moreno Taquer\u00eda", + ShipAddress: "Mataderos 2312", + ShipCity: "M\u00e9xico D.F.", + ShipRegion: null, + ShipPostalCode: "05023", + ShipCountry: "Mexico", + CustomerID: "ANTON", + CustomerName: "Antonio Moreno Taquer\u00eda", + Address: "Mataderos 2312", + City: "M\u00e9xico D.F.", + Region: null, + PostalCode: "05023", + Country: "Mexico", + Salesperson: "Janet Leverling", + OrderID: 10856, + OrderDate: new Date("11/23/2016"), + ShipperName: "United Package", + ProductID: 2, + ProductName: "Chang", + UnitPrice: 19.0000, + Quantity: 20, + Discontinued: false, + ExtendedPrice: 380.0000, + Freight: 58.4300 +}, { + ShipName: "Antonio Moreno Taquer\u00eda", + ShipAddress: "Mataderos 2312", + ShipCity: "M\u00e9xico D.F.", + ShipRegion: null, + ShipPostalCode: "05023", + ShipCountry: "Mexico", + CustomerID: "ANTON", + CustomerName: "Antonio Moreno Taquer\u00eda", + Address: "Mataderos 2312", + City: "M\u00e9xico D.F.", + Region: null, + PostalCode: "05023", + Country: "Mexico", + Salesperson: "Janet Leverling", + OrderID: 10856, + OrderDate: new Date("11/23/2016"), + ShipperName: "United Package", + ProductID: 42, + ProductName: "Singaporean Hokkien Fried Mee", + UnitPrice: 14.0000, + Quantity: 20, + Discontinued: false, + ExtendedPrice: 280.0000, + Freight: 58.4300 +}, { + ShipName: "Antonio Moreno Taquer\u00eda", + ShipAddress: "Mataderos 2312", + ShipCity: "M\u00e9xico D.F.", + ShipRegion: null, + ShipPostalCode: "05023", + ShipCountry: "Mexico", + CustomerID: "ANTON", + CustomerName: "Antonio Moreno Taquer\u00eda", + Address: "Mataderos 2312", + City: "M\u00e9xico D.F.", + Region: null, + PostalCode: "05023", + Country: "Mexico", + Salesperson: "Margaret Peacock", + OrderID: 10535, + OrderDate: new Date("11/23/2016"), + ShipperName: "Speedy Express", + ProductID: 11, + ProductName: "Queso Cabrales", + UnitPrice: 21.0000, + Quantity: 50, + Discontinued: true, + ExtendedPrice: 945.0000, + Freight: 15.6400 +}, { + ShipName: "Antonio Moreno Taquer\u00eda", + ShipAddress: "Mataderos 2312", + ShipCity: "M\u00e9xico D.F.", + ShipRegion: null, + ShipPostalCode: "05023", + ShipCountry: "Mexico", + CustomerID: "ANTON", + CustomerName: "Antonio Moreno Taquer\u00eda", + Address: "Mataderos 2312", + City: "M\u00e9xico D.F.", + Region: null, + PostalCode: "05023", + Country: "Mexico", + Salesperson: "Margaret Peacock", + OrderID: 10535, + OrderDate: new Date("11/23/2016"), + ShipperName: "Speedy Express", + ProductID: 40, + ProductName: "Boston Crab Meat", + UnitPrice: 18.4000, + Quantity: 10, + Discontinued: true, + ExtendedPrice: 165.6000, + Freight: 15.6400 +}, { + ShipName: "Antonio Moreno Taquer\u00eda", + ShipAddress: "Mataderos 2312", + ShipCity: "M\u00e9xico D.F.", + ShipRegion: null, + ShipPostalCode: "05023", + ShipCountry: "Mexico", + CustomerID: "ANTON", + CustomerName: "Antonio Moreno Taquer\u00eda", + Address: "Mataderos 2312", + City: "M\u00e9xico D.F.", + Region: null, + PostalCode: "05023", + Country: "Mexico", + Salesperson: "Margaret Peacock", + OrderID: 10535, + OrderDate: new Date("11/23/2016"), + ShipperName: "Speedy Express", + ProductID: 57, + ProductName: "Ravioli Angelo", + UnitPrice: 19.5000, + Quantity: 5, + Discontinued: true, + ExtendedPrice: 87.7500, + Freight: 15.6400 +}, { + ShipName: "Antonio Moreno Taquer\u00eda", + ShipAddress: "Mataderos 2312", + ShipCity: "M\u00e9xico D.F.", + ShipRegion: null, + ShipPostalCode: "05023", + ShipCountry: "Mexico", + CustomerID: "ANTON", + CustomerName: "Antonio Moreno Taquer\u00eda", + Address: "Mataderos 2312", + City: "M\u00e9xico D.F.", + Region: null, + PostalCode: "05023", + Country: "Mexico", + Salesperson: "Margaret Peacock", + OrderID: 10535, + OrderDate: new Date("11/23/2016"), + ShipperName: "Speedy Express", + ProductID: 59, + ProductName: "Raclette Courdavault", + UnitPrice: 55.0000, + Quantity: 15, + Discontinued: false, + ExtendedPrice: 742.5000, + Freight: 15.6400 +}, { + ShipName: "Antonio Moreno Taquer\u00eda", + ShipAddress: "Mataderos 2312", + ShipCity: "M\u00e9xico D.F.", + ShipRegion: null, + ShipPostalCode: "05023", + ShipCountry: "Mexico", + CustomerID: "ANTON", + CustomerName: "Antonio Moreno Taquer\u00eda", + Address: "Mataderos 2312", + City: "M\u00e9xico D.F.", + Region: null, + PostalCode: "05023", + Country: "Mexico", + Salesperson: "Robert King", + OrderID: 10507, + OrderDate: new Date("11/23/2016"), + ShipperName: "Speedy Express", + ProductID: 43, + ProductName: "Ipoh Coffee", + UnitPrice: 46.0000, + Quantity: 15, + Discontinued: false, + ExtendedPrice: 586.5000, + Freight: 47.4500 +}, { + ShipName: "Antonio Moreno Taquer\u00eda", + ShipAddress: "Mataderos 2312", + ShipCity: "M\u00e9xico D.F.", + ShipRegion: null, + ShipPostalCode: "05023", + ShipCountry: "Mexico", + CustomerID: "ANTON", + CustomerName: "Antonio Moreno Taquer\u00eda", + Address: "Mataderos 2312", + City: "M\u00e9xico D.F.", + Region: null, + PostalCode: "05023", + Country: "Mexico", + Salesperson: "Robert King", + OrderID: 10507, + OrderDate: new Date("11/23/2016"), + ShipperName: "Speedy Express", + ProductID: 48, + ProductName: "Chocolade", + UnitPrice: 12.7500, + Quantity: 15, + Discontinued: true, + ExtendedPrice: 162.5600, + Freight: 47.4500 +}, { + ShipName: "Antonio Moreno Taquer\u00eda", + ShipAddress: "Mataderos 2312", + ShipCity: "M\u00e9xico D.F.", + ShipRegion: null, + ShipPostalCode: "05023", + ShipCountry: "Mexico", + CustomerID: "ANTON", + CustomerName: "Antonio Moreno Taquer\u00eda", + Address: "Mataderos 2312", + City: "M\u00e9xico D.F.", + Region: null, + PostalCode: "05023", + Country: "Mexico", + Salesperson: "Nancy Davolio", + OrderID: 10677, + OrderDate: new Date("10/4/2017"), + ShipperName: "Federal Shipping", + ProductID: 26, + ProductName: "Gumb\u00e4r Gummib\u00e4rchen", + UnitPrice: 31.2300, + Quantity: 30, + Discontinued: true, + ExtendedPrice: 796.3700, + Freight: 4.0300 +}, { + ShipName: "Antonio Moreno Taquer\u00eda", + ShipAddress: "Mataderos 2312", + ShipCity: "M\u00e9xico D.F.", + ShipRegion: null, + ShipPostalCode: "05023", + ShipCountry: "Mexico", + CustomerID: "ANTON", + CustomerName: "Antonio Moreno Taquer\u00eda", + Address: "Mataderos 2312", + City: "M\u00e9xico D.F.", + Region: null, + PostalCode: "05023", + Country: "Mexico", + Salesperson: "Nancy Davolio", + OrderID: 10677, + OrderDate: new Date("10/4/2017"), + ShipperName: "Federal Shipping", + ProductID: 33, + ProductName: "Geitost", + UnitPrice: 2.5000, + Quantity: 8, + Discontinued: false, + ExtendedPrice: 17.0000, + Freight: 4.0300 +}, { + ShipName: "Around the Horn", + ShipAddress: "Brook Farm Stratford St. Mary", + ShipCity: "Colchester", + ShipRegion: "Essex", + ShipPostalCode: "CO7 6JX", + ShipCountry: "UK", + CustomerID: "AROUT", + CustomerName: "Around the Horn", + Address: "120 Hanover Sq.", + City: "London", + Region: null, + PostalCode: "WA1 1DP", + Country: "UK", + Salesperson: "Michael Suyama", + OrderID: 10355, + OrderDate: new Date("10/4/2017"), + ShipperName: "Speedy Express", + ProductID: 24, + ProductName: "Guaran\u00e1 Fant\u00e1stica", + UnitPrice: 3.6000, + Quantity: 25, + Discontinued: false, + ExtendedPrice: 90.0000, + Freight: 41.9500 +}, { + ShipName: "Around the Horn", + ShipAddress: "Brook Farm Stratford St. Mary", + ShipCity: "Colchester", + ShipRegion: "Essex", + ShipPostalCode: "CO7 6JX", + ShipCountry: "UK", + CustomerID: "AROUT", + CustomerName: "Around the Horn", + Address: "120 Hanover Sq.", + City: "London", + Region: null, + PostalCode: "WA1 1DP", + Country: "UK", + Salesperson: "Michael Suyama", + OrderID: 10355, + OrderDate: new Date("10/4/2017"), + ShipperName: "Speedy Express", + ProductID: 57, + ProductName: "Ravioli Angelo", + UnitPrice: 15.6000, + Quantity: 25, + Discontinued: false, + ExtendedPrice: 390.0000, + Freight: 41.9500 +}, { + ShipName: "Around the Horn", + ShipAddress: "Brook Farm Stratford St. Mary", + ShipCity: "Colchester", + ShipRegion: "Essex", + ShipPostalCode: "CO7 6JX", + ShipCountry: "UK", + CustomerID: "AROUT", + CustomerName: "Around the Horn", + Address: "120 Hanover Sq.", + City: "London", + Region: null, + PostalCode: "WA1 1DP", + Country: "UK", + Salesperson: "Laura Callahan", + OrderID: 10383, + OrderDate: new Date("10/4/2017"), + ShipperName: "Federal Shipping", + ProductID: 13, + ProductName: "Konbu", + UnitPrice: 4.8000, + Quantity: 20, + Discontinued: false, + ExtendedPrice: 96.0000, + Freight: 34.2400 +}, { + ShipName: "Around the Horn", + ShipAddress: "Brook Farm Stratford St. Mary", + ShipCity: "Colchester", + ShipRegion: "Essex", + ShipPostalCode: "CO7 6JX", + ShipCountry: "UK", + CustomerID: "AROUT", + CustomerName: "Around the Horn", + Address: "120 Hanover Sq.", + City: "London", + Region: null, + PostalCode: "WA1 1DP", + Country: "UK", + Salesperson: "Laura Callahan", + OrderID: 10383, + OrderDate: new Date("10/4/2017"), + ShipperName: "Federal Shipping", + ProductID: 50, + ProductName: "Valkoinen suklaa", + UnitPrice: 13.0000, + Quantity: 15, + Discontinued: false, + ExtendedPrice: 195.0000, + Freight: 34.2400 +}, { + ShipName: "Around the Horn", + ShipAddress: "Brook Farm Stratford St. Mary", + ShipCity: "Colchester", + ShipRegion: "Essex", + ShipPostalCode: "CO7 6JX", + ShipCountry: "UK", + CustomerID: "AROUT", + CustomerName: "Around the Horn", + Address: "120 Hanover Sq.", + City: "London", + Region: null, + PostalCode: "WA1 1DP", + Country: "UK", + Salesperson: "Laura Callahan", + OrderID: 10383, + OrderDate: new Date("10/4/2017"), + ShipperName: "Federal Shipping", + ProductID: 56, + ProductName: "Gnocchi di nonna Alice", + UnitPrice: 30.4000, + Quantity: 20, + Discontinued: false, + ExtendedPrice: 608.0000, + Freight: 34.2400 +}, { + ShipName: "Around the Horn", + ShipAddress: "Brook Farm Stratford St. Mary", + ShipCity: "Colchester", + ShipRegion: "Essex", + ShipPostalCode: "CO7 6JX", + ShipCountry: "UK", + CustomerID: "AROUT", + CustomerName: "Around the Horn", + Address: "120 Hanover Sq.", + City: "London", + Region: null, + PostalCode: "WA1 1DP", + Country: "UK", + Salesperson: "Nancy Davolio", + OrderID: 10558, + OrderDate: new Date("10/4/2017"), + ShipperName: "United Package", + ProductID: 47, + ProductName: "Zaanse koeken", + UnitPrice: 9.5000, + Quantity: 25, + Discontinued: false, + ExtendedPrice: 237.5000, + Freight: 72.9700 +}, { + ShipName: "Around the Horn", + ShipAddress: "Brook Farm Stratford St. Mary", + ShipCity: "Colchester", + ShipRegion: "Essex", + ShipPostalCode: "CO7 6JX", + ShipCountry: "UK", + CustomerID: "AROUT", + CustomerName: "Around the Horn", + Address: "120 Hanover Sq.", + City: "London", + Region: null, + PostalCode: "WA1 1DP", + Country: "UK", + Salesperson: "Nancy Davolio", + OrderID: 10558, + OrderDate: new Date("10/4/2017"), + ShipperName: "United Package", + ProductID: 51, + ProductName: "Manjimup Dried Apples", + UnitPrice: 53.0000, + Quantity: 20, + Discontinued: false, + ExtendedPrice: 1060.0000, + Freight: 72.9700 +}, { + ShipName: "Around the Horn", + ShipAddress: "Brook Farm Stratford St. Mary", + ShipCity: "Colchester", + ShipRegion: "Essex", + ShipPostalCode: "CO7 6JX", + ShipCountry: "UK", + CustomerID: "AROUT", + CustomerName: "Around the Horn", + Address: "120 Hanover Sq.", + City: "London", + Region: null, + PostalCode: "WA1 1DP", + Country: "UK", + Salesperson: "Nancy Davolio", + OrderID: 10558, + OrderDate: new Date("10/4/2017"), + ShipperName: "United Package", + ProductID: 52, + ProductName: "Filo Mix", + UnitPrice: 7.0000, + Quantity: 30, + Discontinued: true, + ExtendedPrice: 210.0000, + Freight: 72.9700 +}, { + ShipName: "Around the Horn", + ShipAddress: "Brook Farm Stratford St. Mary", + ShipCity: "Colchester", + ShipRegion: "Essex", + ShipPostalCode: "CO7 6JX", + ShipCountry: "UK", + CustomerID: "AROUT", + CustomerName: "Around the Horn", + Address: "120 Hanover Sq.", + City: "London", + Region: null, + PostalCode: "WA1 1DP", + Country: "UK", + Salesperson: "Nancy Davolio", + OrderID: 10558, + OrderDate: new Date("10/4/2017"), + ShipperName: "United Package", + ProductID: 53, + ProductName: "Perth Pasties", + UnitPrice: 32.8000, + Quantity: 18, + Discontinued: false, + ExtendedPrice: 590.4000, + Freight: 72.9700 +}, { + ShipName: "Around the Horn", + ShipAddress: "Brook Farm Stratford St. Mary", + ShipCity: "Colchester", + ShipRegion: "Essex", + ShipPostalCode: "CO7 6JX", + ShipCountry: "UK", + CustomerID: "AROUT", + CustomerName: "Around the Horn", + Address: "120 Hanover Sq.", + City: "London", + Region: null, + PostalCode: "WA1 1DP", + Country: "UK", + Salesperson: "Nancy Davolio", + OrderID: 10558, + OrderDate: new Date("10/4/2017"), + ShipperName: "United Package", + ProductID: 73, + ProductName: "R\u00f6d Kaviar", + UnitPrice: 15.0000, + Quantity: 3, + Discontinued: false, + ExtendedPrice: 45.0000, + Freight: 72.9700 +}, { + ShipName: "Around the Horn", + ShipAddress: "Brook Farm Stratford St. Mary", + ShipCity: "Colchester", + ShipRegion: "Essex", + ShipPostalCode: "CO7 6JX", + ShipCountry: "UK", + CustomerID: "AROUT", + CustomerName: "Around the Horn", + Address: "120 Hanover Sq.", + City: "London", + Region: null, + PostalCode: "WA1 1DP", + Country: "UK", + Salesperson: "Margaret Peacock", + OrderID: 10707, + OrderDate: new Date("10/4/2017"), + ShipperName: "Federal Shipping", + ProductID: 55, + ProductName: "P\u00e2t\u00e9 chinois", + UnitPrice: 24.0000, + Quantity: 21, + Discontinued: false, + ExtendedPrice: 504.0000, + Freight: 21.7400 +}, { + ShipName: "Around the Horn", + ShipAddress: "Brook Farm Stratford St. Mary", + ShipCity: "Colchester", + ShipRegion: "Essex", + ShipPostalCode: "CO7 6JX", + ShipCountry: "UK", + CustomerID: "AROUT", + CustomerName: "Around the Horn", + Address: "120 Hanover Sq.", + City: "London", + Region: null, + PostalCode: "WA1 1DP", + Country: "UK", + Salesperson: "Margaret Peacock", + OrderID: 10707, + OrderDate: new Date("10/4/2017"), + ShipperName: "Federal Shipping", + ProductID: 57, + ProductName: "Ravioli Angelo", + UnitPrice: 19.5000, + Quantity: 40, + Discontinued: false, + ExtendedPrice: 780.0000, + Freight: 21.7400 +}, { + ShipName: "Around the Horn", + ShipAddress: "Brook Farm Stratford St. Mary", + ShipCity: "Colchester", + ShipRegion: "Essex", + ShipPostalCode: "CO7 6JX", + ShipCountry: "UK", + CustomerID: "AROUT", + CustomerName: "Around the Horn", + Address: "120 Hanover Sq.", + City: "London", + Region: null, + PostalCode: "WA1 1DP", + Country: "UK", + Salesperson: "Janet Leverling", + OrderID: 10768, + OrderDate: new Date("10/4/2017"), + ShipperName: "United Package", + ProductID: 22, + ProductName: "Gustaf's Kn\u00e4ckebr\u00f6d", + UnitPrice: 21.0000, + Quantity: 4, + Discontinued: false, + ExtendedPrice: 84.0000, + Freight: 146.3200 +}, { + ShipName: "Around the Horn", + ShipAddress: "Brook Farm Stratford St. Mary", + ShipCity: "Colchester", + ShipRegion: "Essex", + ShipPostalCode: "CO7 6JX", + ShipCountry: "UK", + CustomerID: "AROUT", + CustomerName: "Around the Horn", + Address: "120 Hanover Sq.", + City: "London", + Region: null, + PostalCode: "WA1 1DP", + Country: "UK", + Salesperson: "Janet Leverling", + OrderID: 10768, + OrderDate: new Date("10/4/2017"), + ShipperName: "United Package", + ProductID: 31, + ProductName: "Gorgonzola Telino", + UnitPrice: 12.5000, + Quantity: 50, + Discontinued: true, + ExtendedPrice: 625.0000, + Freight: 146.3200 +}, { + ShipName: "Around the Horn", + ShipAddress: "Brook Farm Stratford St. Mary", + ShipCity: "Colchester", + ShipRegion: "Essex", + ShipPostalCode: "CO7 6JX", + ShipCountry: "UK", + CustomerID: "AROUT", + CustomerName: "Around the Horn", + Address: "120 Hanover Sq.", + City: "London", + Region: null, + PostalCode: "WA1 1DP", + Country: "UK", + Salesperson: "Janet Leverling", + OrderID: 10768, + OrderDate: new Date("10/4/2017"), + ShipperName: "United Package", + ProductID: 60, + ProductName: "Camembert Pierrot", + UnitPrice: 34.0000, + Quantity: 15, + Discontinued: false, + ExtendedPrice: 510.0000, + Freight: 146.3200 +}, { + ShipName: "Around the Horn", + ShipAddress: "Brook Farm Stratford St. Mary", + ShipCity: "Colchester", + ShipRegion: "Essex", + ShipPostalCode: "CO7 6JX", + ShipCountry: "UK", + CustomerID: "AROUT", + CustomerName: "Around the Horn", + Address: "120 Hanover Sq.", + City: "London", + Region: null, + PostalCode: "WA1 1DP", + Country: "UK", + Salesperson: "Janet Leverling", + OrderID: 10768, + OrderDate: new Date("10/4/2017"), + ShipperName: "United Package", + ProductID: 71, + ProductName: "Flotemysost", + UnitPrice: 21.5000, + Quantity: 12, + Discontinued: false, + ExtendedPrice: 258.0000, + Freight: 146.3200 +}, { + ShipName: "Around the Horn", + ShipAddress: "Brook Farm Stratford St. Mary", + ShipCity: "Colchester", + ShipRegion: "Essex", + ShipPostalCode: "CO7 6JX", + ShipCountry: "UK", + CustomerID: "AROUT", + CustomerName: "Around the Horn", + Address: "120 Hanover Sq.", + City: "London", + Region: null, + PostalCode: "WA1 1DP", + Country: "UK", + Salesperson: "Janet Leverling", + OrderID: 10793, + OrderDate: new Date("10/4/2017"), + ShipperName: "Federal Shipping", + ProductID: 41, + ProductName: "Jack's New England Clam Chowder", + UnitPrice: 9.6500, + Quantity: 14, + Discontinued: false, + ExtendedPrice: 135.1000, + Freight: 4.5200 +}, { + ShipName: "Around the Horn", + ShipAddress: "Brook Farm Stratford St. Mary", + ShipCity: "Colchester", + ShipRegion: "Essex", + ShipPostalCode: "CO7 6JX", + ShipCountry: "UK", + CustomerID: "AROUT", + CustomerName: "Around the Horn", + Address: "120 Hanover Sq.", + City: "London", + Region: null, + PostalCode: "WA1 1DP", + Country: "UK", + Salesperson: "Janet Leverling", + OrderID: 10793, + OrderDate: new Date("10/4/2017"), + ShipperName: "Federal Shipping", + ProductID: 52, + ProductName: "Filo Mix", + UnitPrice: 7.0000, + Quantity: 8, + Discontinued: false, + ExtendedPrice: 56.0000, + Freight: 4.5200 +}, { + ShipName: "Around the Horn", + ShipAddress: "Brook Farm Stratford St. Mary", + ShipCity: "Colchester", + ShipRegion: "Essex", + ShipPostalCode: "CO7 6JX", + ShipCountry: "UK", + CustomerID: "AROUT", + CustomerName: "Around the Horn", + Address: "120 Hanover Sq.", + City: "London", + Region: null, + PostalCode: "WA1 1DP", + Country: "UK", + Salesperson: "Margaret Peacock", + OrderID: 10864, + OrderDate: new Date("10/4/2017"), + ShipperName: "United Package", + ProductID: 35, + ProductName: "Steeleye Stout", + UnitPrice: 18.0000, + Quantity: 4, + Discontinued: false, + ExtendedPrice: 72.0000, + Freight: 3.0400 +}, { + ShipName: "Around the Horn", + ShipAddress: "Brook Farm Stratford St. Mary", + ShipCity: "Colchester", + ShipRegion: "Essex", + ShipPostalCode: "CO7 6JX", + ShipCountry: "UK", + CustomerID: "AROUT", + CustomerName: "Around the Horn", + Address: "120 Hanover Sq.", + City: "London", + Region: null, + PostalCode: "WA1 1DP", + Country: "UK", + Salesperson: "Margaret Peacock", + OrderID: 10864, + OrderDate: new Date("10/4/2017"), + ShipperName: "United Package", + ProductID: 67, + ProductName: "Laughing Lumberjack Lager", + UnitPrice: 14.0000, + Quantity: 15, + Discontinued: false, + ExtendedPrice: 210.0000, + Freight: 3.0400 +}, { + ShipName: "Around the Horn", + ShipAddress: "Brook Farm Stratford St. Mary", + ShipCity: "Colchester", + ShipRegion: "Essex", + ShipPostalCode: "CO7 6JX", + ShipCountry: "UK", + CustomerID: "AROUT", + CustomerName: "Around the Horn", + Address: "120 Hanover Sq.", + City: "London", + Region: null, + PostalCode: "WA1 1DP", + Country: "UK", + Salesperson: "Margaret Peacock", + OrderID: 10920, + OrderDate: new Date("10/4/2017"), + ShipperName: "United Package", + ProductID: 50, + ProductName: "Valkoinen suklaa", + UnitPrice: 16.2500, + Quantity: 24, + Discontinued: false, + ExtendedPrice: 390.0000, + Freight: 29.6100 +}, { + ShipName: "Around the Horn", + ShipAddress: "Brook Farm Stratford St. Mary", + ShipCity: "Colchester", + ShipRegion: "Essex", + ShipPostalCode: "CO7 6JX", + ShipCountry: "UK", + CustomerID: "AROUT", + CustomerName: "Around the Horn", + Address: "120 Hanover Sq.", + City: "London", + Region: null, + PostalCode: "WA1 1DP", + Country: "UK", + Salesperson: "Anne Dodsworth", + OrderID: 11016, + OrderDate: new Date("10/4/2017"), + ShipperName: "United Package", + ProductID: 31, + ProductName: "Gorgonzola Telino", + UnitPrice: 12.5000, + Quantity: 15, + Discontinued: false, + ExtendedPrice: 187.5000, + Freight: 33.8000 +}, { + ShipName: "Around the Horn", + ShipAddress: "Brook Farm Stratford St. Mary", + ShipCity: "Colchester", + ShipRegion: "Essex", + ShipPostalCode: "CO7 6JX", + ShipCountry: "UK", + CustomerID: "AROUT", + CustomerName: "Around the Horn", + Address: "120 Hanover Sq.", + City: "London", + Region: null, + PostalCode: "WA1 1DP", + Country: "UK", + Salesperson: "Anne Dodsworth", + OrderID: 11016, + OrderDate: new Date("10/4/2017"), + ShipperName: "United Package", + ProductID: 36, + ProductName: "Inlagd Sill", + UnitPrice: 19.0000, + Quantity: 16, + Discontinued: false, + ExtendedPrice: 304.0000, + Freight: 33.8000 +}, { + ShipName: "Around the Horn", + ShipAddress: "Brook Farm Stratford St. Mary", + ShipCity: "Colchester", + ShipRegion: "Essex", + ShipPostalCode: "CO7 6JX", + ShipCountry: "UK", + CustomerID: "AROUT", + CustomerName: "Around the Horn", + Address: "120 Hanover Sq.", + City: "London", + Region: null, + PostalCode: "WA1 1DP", + Country: "UK", + Salesperson: "Nancy Davolio", + OrderID: 10743, + OrderDate: new Date("10/4/2017"), + ShipperName: "United Package", + ProductID: 46, + ProductName: "Spegesild", + UnitPrice: 12.0000, + Quantity: 28, + Discontinued: false, + ExtendedPrice: 319.2000, + Freight: 23.7200 +}, { + ShipName: "Around the Horn", + ShipAddress: "Brook Farm Stratford St. Mary", + ShipCity: "Colchester", + ShipRegion: "Essex", + ShipPostalCode: "CO7 6JX", + ShipCountry: "UK", + CustomerID: "AROUT", + CustomerName: "Around the Horn", + Address: "120 Hanover Sq.", + City: "London", + Region: null, + PostalCode: "WA1 1DP", + Country: "UK", + Salesperson: "Anne Dodsworth", + OrderID: 10953, + OrderDate: new Date("10/4/2017"), + ShipperName: "United Package", + ProductID: 20, + ProductName: "Sir Rodney's Marmalade", + UnitPrice: 81.0000, + Quantity: 50, + Discontinued: false, + ExtendedPrice: 3847.5000, + Freight: 23.7200 +}, { + ShipName: "Around the Horn", + ShipAddress: "Brook Farm Stratford St. Mary", + ShipCity: "Colchester", + ShipRegion: "Essex", + ShipPostalCode: "CO7 6JX", + ShipCountry: "UK", + CustomerID: "AROUT", + CustomerName: "Around the Horn", + Address: "120 Hanover Sq.", + City: "London", + Region: null, + PostalCode: "WA1 1DP", + Country: "UK", + Salesperson: "Anne Dodsworth", + OrderID: 10953, + OrderDate: new Date("10/4/2017"), + ShipperName: "United Package", + ProductID: 31, + ProductName: "Gorgonzola Telino", + UnitPrice: 12.5000, + Quantity: 50, + Discontinued: false, + ExtendedPrice: 593.7500, + Freight: 23.7200 +}, { + ShipName: "Around the Horn", + ShipAddress: "Brook Farm Stratford St. Mary", + ShipCity: "Colchester", + ShipRegion: "Essex", + ShipPostalCode: "CO7 6JX", + ShipCountry: "UK", + CustomerID: "AROUT", + CustomerName: "Around the Horn", + Address: "120 Hanover Sq.", + City: "London", + Region: null, + PostalCode: "WA1 1DP", + Country: "UK", + Salesperson: "Nancy Davolio", + OrderID: 10453, + OrderDate: new Date("10/4/2017"), + ShipperName: "United Package", + ProductID: 48, + ProductName: "Chocolade", + UnitPrice: 10.2000, + Quantity: 15, + Discontinued: true, + ExtendedPrice: 137.7000, + Freight: 25.3600 +}, { + ShipName: "Around the Horn", + ShipAddress: "Brook Farm Stratford St. Mary", + ShipCity: "Colchester", + ShipRegion: "Essex", + ShipPostalCode: "CO7 6JX", + ShipCountry: "UK", + CustomerID: "AROUT", + CustomerName: "Around the Horn", + Address: "120 Hanover Sq.", + City: "London", + Region: null, + PostalCode: "WA1 1DP", + Country: "UK", + Salesperson: "Nancy Davolio", + OrderID: 10453, + OrderDate: new Date("10/4/2017"), + ShipperName: "United Package", + ProductID: 70, + ProductName: "Outback Lager", + UnitPrice: 12.0000, + Quantity: 25, + Discontinued: true, + ExtendedPrice: 270.0000, + Freight: 25.3600 +}, { + ShipName: "Around the Horn", + ShipAddress: "Brook Farm Stratford St. Mary", + ShipCity: "Colchester", + ShipRegion: "Essex", + ShipPostalCode: "CO7 6JX", + ShipCountry: "UK", + CustomerID: "AROUT", + CustomerName: "Around the Horn", + Address: "120 Hanover Sq.", + City: "London", + Region: null, + PostalCode: "WA1 1DP", + Country: "UK", + Salesperson: "Margaret Peacock", + OrderID: 10707, + OrderDate: new Date("10/4/2017"), + ShipperName: "Federal Shipping", + ProductID: 70, + ProductName: "Outback Lager", + UnitPrice: 15.0000, + Quantity: 28, + Discontinued: false, + ExtendedPrice: 357.0000, + Freight: 21.7400 +}, { + ShipName: "Around the Horn", + ShipAddress: "Brook Farm Stratford St. Mary", + ShipCity: "Colchester", + ShipRegion: "Essex", + ShipPostalCode: "CO7 6JX", + ShipCountry: "UK", + CustomerID: "AROUT", + CustomerName: "Around the Horn", + Address: "120 Hanover Sq.", + City: "London", + Region: null, + PostalCode: "WA1 1DP", + Country: "UK", + Salesperson: "Margaret Peacock", + OrderID: 10741, + OrderDate: new Date("10/4/2017"), + ShipperName: "Federal Shipping", + ProductID: 2, + ProductName: "Chang", + UnitPrice: 19.0000, + Quantity: 15, + Discontinued: false, + ExtendedPrice: 228.0000, + Freight: 10.9600 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Laura Callahan", + OrderID: 10278, + OrderDate: new Date("10/4/2017"), + ShipperName: "United Package", + ProductID: 44, + ProductName: "Gula Malacca", + UnitPrice: 15.5000, + Quantity: 16, + Discontinued: false, + ExtendedPrice: 248.0000, + Freight: 92.6900 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Laura Callahan", + OrderID: 10278, + OrderDate: new Date("10/4/2017"), + ShipperName: "United Package", + ProductID: 59, + ProductName: "Raclette Courdavault", + UnitPrice: 44.0000, + Quantity: 15, + Discontinued: false, + ExtendedPrice: 660.0000, + Freight: 92.6900 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Laura Callahan", + OrderID: 10278, + OrderDate: new Date("10/4/2017"), + ShipperName: "United Package", + ProductID: 63, + ProductName: "Vegie-spread", + UnitPrice: 35.1000, + Quantity: 8, + Discontinued: false, + ExtendedPrice: 280.8000, + Freight: 92.6900 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Laura Callahan", + OrderID: 10278, + OrderDate: new Date("10/4/2017"), + ShipperName: "United Package", + ProductID: 73, + ProductName: "R\u00f6d Kaviar", + UnitPrice: 12.0000, + Quantity: 25, + Discontinued: false, + ExtendedPrice: 300.0000, + Freight: 92.6900 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Andrew Fuller", + OrderID: 10280, + OrderDate: new Date("10/4/2017"), + ShipperName: "Speedy Express", + ProductID: 24, + ProductName: "Guaran\u00e1 Fant\u00e1stica", + UnitPrice: 3.6000, + Quantity: 12, + Discontinued: false, + ExtendedPrice: 43.2000, + Freight: 8.9800 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Andrew Fuller", + OrderID: 10280, + OrderDate: new Date("10/4/2017"), + ShipperName: "Speedy Express", + ProductID: 55, + ProductName: "P\u00e2t\u00e9 chinois", + UnitPrice: 19.2000, + Quantity: 20, + Discontinued: false, + ExtendedPrice: 384.0000, + Freight: 8.9800 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Andrew Fuller", + OrderID: 10280, + OrderDate: new Date("10/4/2017"), + ShipperName: "Speedy Express", + ProductID: 75, + ProductName: "Rh\u00f6nbr\u00e4u Klosterbier", + UnitPrice: 6.2000, + Quantity: 30, + Discontinued: false, + ExtendedPrice: 186.0000, + Freight: 8.9800 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Janet Leverling", + OrderID: 10384, + OrderDate: new Date("10/4/2017"), + ShipperName: "Federal Shipping", + ProductID: 20, + ProductName: "Sir Rodney's Marmalade", + UnitPrice: 64.8000, + Quantity: 28, + Discontinued: false, + ExtendedPrice: 1814.4000, + Freight: 168.6400 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Janet Leverling", + OrderID: 10384, + OrderDate: new Date("10/4/2017"), + ShipperName: "Federal Shipping", + ProductID: 60, + ProductName: "Camembert Pierrot", + UnitPrice: 27.2000, + Quantity: 15, + Discontinued: false, + ExtendedPrice: 408.0000, + Freight: 168.6400 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Janet Leverling", + OrderID: 10444, + OrderDate: new Date("10/4/2017"), + ShipperName: "Federal Shipping", + ProductID: 17, + ProductName: "Alice Mutton", + UnitPrice: 31.2000, + Quantity: 10, + Discontinued: false, + ExtendedPrice: 312.0000, + Freight: 3.5000 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Janet Leverling", + OrderID: 10444, + OrderDate: new Date("10/4/2017"), + ShipperName: "Federal Shipping", + ProductID: 26, + ProductName: "Gumb\u00e4r Gummib\u00e4rchen", + UnitPrice: 24.9000, + Quantity: 15, + Discontinued: false, + ExtendedPrice: 373.5000, + Freight: 3.5000 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Janet Leverling", + OrderID: 10444, + OrderDate: new Date("10/4/2017"), + ShipperName: "Federal Shipping", + ProductID: 35, + ProductName: "Steeleye Stout", + UnitPrice: 14.4000, + Quantity: 8, + Discontinued: false, + ExtendedPrice: 115.2000, + Freight: 3.5000 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Janet Leverling", + OrderID: 10444, + OrderDate: new Date("10/4/2017"), + ShipperName: "Federal Shipping", + ProductID: 41, + ProductName: "Jack's New England Clam Chowder", + UnitPrice: 7.7000, + Quantity: 30, + Discontinued: false, + ExtendedPrice: 231.0000, + Freight: 3.5000 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Nancy Davolio", + OrderID: 10524, + OrderDate: new Date("10/4/2017"), + ShipperName: "United Package", + ProductID: 54, + ProductName: "Tourti\u00e8re", + UnitPrice: 7.4500, + Quantity: 15, + Discontinued: false, + ExtendedPrice: 111.7500, + Freight: 244.7900 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Janet Leverling", + OrderID: 10572, + OrderDate: new Date("10/4/2017"), + ShipperName: "United Package", + ProductID: 40, + ProductName: "Boston Crab Meat", + UnitPrice: 18.4000, + Quantity: 50, + Discontinued: false, + ExtendedPrice: 920.0000, + Freight: 116.4300 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Nancy Davolio", + OrderID: 10626, + OrderDate: new Date("10/4/2017"), + ShipperName: "United Package", + ProductID: 53, + ProductName: "Perth Pasties", + UnitPrice: 32.8000, + Quantity: 12, + Discontinued: false, + ExtendedPrice: 393.6000, + Freight: 138.6900 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Nancy Davolio", + OrderID: 10626, + OrderDate: new Date("10/4/2017"), + ShipperName: "United Package", + ProductID: 60, + ProductName: "Camembert Pierrot", + UnitPrice: 34.0000, + Quantity: 20, + Discontinued: false, + ExtendedPrice: 680.0000, + Freight: 138.6900 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Nancy Davolio", + OrderID: 10626, + OrderDate: new Date("10/4/2017"), + ShipperName: "United Package", + ProductID: 71, + ProductName: "Flotemysost", + UnitPrice: 21.5000, + Quantity: 20, + Discontinued: false, + ExtendedPrice: 430.0000, + Freight: 138.6900 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Anne Dodsworth", + OrderID: 10672, + OrderDate: new Date("10/4/2017"), + ShipperName: "United Package", + ProductID: 71, + ProductName: "Flotemysost", + UnitPrice: 21.5000, + Quantity: 12, + Discontinued: false, + ExtendedPrice: 258.0000, + Freight: 95.7500 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Nancy Davolio", + OrderID: 10733, + OrderDate: new Date("10/4/2017"), + ShipperName: "Federal Shipping", + ProductID: 14, + ProductName: "Tofu", + UnitPrice: 23.2500, + Quantity: 16, + Discontinued: false, + ExtendedPrice: 372.0000, + Freight: 110.1100 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Nancy Davolio", + OrderID: 10733, + OrderDate: new Date("10/4/2017"), + ShipperName: "Federal Shipping", + ProductID: 28, + ProductName: "R\u00f6ssle Sauerkraut", + UnitPrice: 45.6000, + Quantity: 20, + Discontinued: false, + ExtendedPrice: 912.0000, + Freight: 110.1100 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Nancy Davolio", + OrderID: 10733, + OrderDate: new Date("10/4/2017"), + ShipperName: "Federal Shipping", + ProductID: 52, + ProductName: "Filo Mix", + UnitPrice: 7.0000, + Quantity: 25, + Discontinued: false, + ExtendedPrice: 175.0000, + Freight: 110.1100 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Janet Leverling", + OrderID: 10778, + OrderDate: new Date("10/4/2017"), + ShipperName: "Speedy Express", + ProductID: 41, + ProductName: "Jack's New England Clam Chowder", + UnitPrice: 9.6500, + Quantity: 10, + Discontinued: false, + ExtendedPrice: 96.5000, + Freight: 6.7900 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Anne Dodsworth", + OrderID: 10837, + OrderDate: new Date("10/4/2018"), + ShipperName: "Federal Shipping", + ProductID: 13, + ProductName: "Konbu", + UnitPrice: 6.0000, + Quantity: 6, + Discontinued: false, + ExtendedPrice: 36.0000, + Freight: 13.3200 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Anne Dodsworth", + OrderID: 10837, + OrderDate: new Date("10/4/2018"), + ShipperName: "Federal Shipping", + ProductID: 40, + ProductName: "Boston Crab Meat", + UnitPrice: 18.4000, + Quantity: 25, + Discontinued: false, + ExtendedPrice: 460.0000, + Freight: 13.3200 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Laura Callahan", + OrderID: 10857, + OrderDate: new Date("10/4/2018"), + ShipperName: "United Package", + ProductID: 3, + ProductName: "Aniseed Syrup", + UnitPrice: 10.0000, + Quantity: 30, + Discontinued: false, + ExtendedPrice: 300.0000, + Freight: 188.8500 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Margaret Peacock", + OrderID: 10875, + OrderDate: new Date("10/4/2018"), + ShipperName: "United Package", + ProductID: 19, + ProductName: "Teatime Chocolate Biscuits", + UnitPrice: 9.2000, + Quantity: 25, + Discontinued: false, + ExtendedPrice: 230.0000, + Freight: 32.3700 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Margaret Peacock", + OrderID: 10875, + OrderDate: new Date("10/4/2018"), + ShipperName: "United Package", + ProductID: 49, + ProductName: "Maxilaku", + UnitPrice: 20.0000, + Quantity: 15, + Discontinued: false, + ExtendedPrice: 300.0000, + Freight: 32.3700 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Janet Leverling", + OrderID: 10924, + OrderDate: new Date("10/4/2018"), + ShipperName: "United Package", + ProductID: 75, + ProductName: "Rh\u00f6nbr\u00e4u Klosterbier", + UnitPrice: 7.7500, + Quantity: 6, + Discontinued: false, + ExtendedPrice: 46.5000, + Freight: 151.5200 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Janet Leverling", + OrderID: 10572, + OrderDate: new Date("10/4/2018"), + ShipperName: "United Package", + ProductID: 16, + ProductName: "Pavlova", + UnitPrice: 17.4500, + Quantity: 12, + Discontinued: false, + ExtendedPrice: 188.4600, + Freight: 116.4300 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Janet Leverling", + OrderID: 10572, + OrderDate: new Date("10/4/2018"), + ShipperName: "United Package", + ProductID: 32, + ProductName: "Mascarpone Fabioli", + UnitPrice: 32.0000, + Quantity: 10, + Discontinued: false, + ExtendedPrice: 288.0000, + Freight: 116.4300 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Janet Leverling", + OrderID: 10572, + OrderDate: new Date("10/4/2018"), + ShipperName: "United Package", + ProductID: 75, + ProductName: "Rh\u00f6nbr\u00e4u Klosterbier", + UnitPrice: 7.7500, + Quantity: 15, + Discontinued: false, + ExtendedPrice: 104.6200, + Freight: 116.4300 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Steven Buchanan", + OrderID: 10654, + OrderDate: new Date("10/4/2018"), + ShipperName: "Speedy Express", + ProductID: 4, + ProductName: "Chef Anton's Cajun Seasoning", + UnitPrice: 22.0000, + Quantity: 12, + Discontinued: false, + ExtendedPrice: 237.6000, + Freight: 55.2600 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Steven Buchanan", + OrderID: 10654, + OrderDate: new Date("10/4/2018"), + ShipperName: "Speedy Express", + ProductID: 39, + ProductName: "Chartreuse verte", + UnitPrice: 18.0000, + Quantity: 20, + Discontinued: false, + ExtendedPrice: 324.0000, + Freight: 55.2600 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Steven Buchanan", + OrderID: 10654, + OrderDate: new Date("10/4/2018"), + ShipperName: "Speedy Express", + ProductID: 54, + ProductName: "Tourti\u00e8re", + UnitPrice: 7.4500, + Quantity: 6, + Discontinued: false, + ExtendedPrice: 40.2300, + Freight: 55.2600 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Anne Dodsworth", + OrderID: 10672, + OrderDate: new Date("10/4/2018"), + ShipperName: "United Package", + ProductID: 38, + ProductName: "C\u00f4te de Blaye", + UnitPrice: 263.5000, + Quantity: 15, + Discontinued: false, + ExtendedPrice: 3557.2500, + Freight: 95.7500 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Margaret Peacock", + OrderID: 10875, + OrderDate: new Date("10/4/2018"), + ShipperName: "United Package", + ProductID: 47, + ProductName: "Zaanse koeken", + UnitPrice: 9.5000, + Quantity: 21, + Discontinued: false, + ExtendedPrice: 179.5500, + Freight: 32.3700 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Janet Leverling", + OrderID: 10924, + OrderDate: new Date("10/4/2018"), + ShipperName: "United Package", + ProductID: 10, + ProductName: "Ikura", + UnitPrice: 31.0000, + Quantity: 20, + Discontinued: false, + ExtendedPrice: 558.0000, + Freight: 151.5200 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Janet Leverling", + OrderID: 10924, + OrderDate: new Date("10/4/2018"), + ShipperName: "United Package", + ProductID: 28, + ProductName: "R\u00f6ssle Sauerkraut", + UnitPrice: 45.6000, + Quantity: 30, + Discontinued: true, + ExtendedPrice: 1231.2000, + Freight: 151.5200 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Nancy Davolio", + OrderID: 10689, + OrderDate: new Date("10/4/2018"), + ShipperName: "United Package", + ProductID: 1, + ProductName: "Chai", + UnitPrice: 18.0000, + Quantity: 35, + Discontinued: true, + ExtendedPrice: 472.5000, + Freight: 13.4200 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Anne Dodsworth", + OrderID: 10837, + OrderDate: new Date("10/4/2018"), + ShipperName: "Federal Shipping", + ProductID: 47, + ProductName: "Zaanse koeken", + UnitPrice: 9.5000, + Quantity: 40, + Discontinued: true, + ExtendedPrice: 285.0000, + Freight: 13.3200 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Anne Dodsworth", + OrderID: 10837, + OrderDate: new Date("10/4/2018"), + ShipperName: "Federal Shipping", + ProductID: 76, + ProductName: "Lakkalik\u00f6\u00f6ri", + UnitPrice: 18.0000, + Quantity: 21, + Discontinued: true, + ExtendedPrice: 283.5000, + Freight: 13.3200 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Laura Callahan", + OrderID: 10857, + OrderDate: new Date("10/4/2018"), + ShipperName: "United Package", + ProductID: 26, + ProductName: "Gumb\u00e4r Gummib\u00e4rchen", + UnitPrice: 31.2300, + Quantity: 35, + Discontinued: true, + ExtendedPrice: 819.7900, + Freight: 188.8500 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Laura Callahan", + OrderID: 10857, + OrderDate: new Date("10/4/2018"), + ShipperName: "United Package", + ProductID: 29, + ProductName: "Th\u00fcringer Rostbratwurst", + UnitPrice: 123.7900, + Quantity: 10, + Discontinued: true, + ExtendedPrice: 928.4300, + Freight: 188.8500 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Steven Buchanan", + OrderID: 10866, + OrderDate: new Date("10/4/2018"), + ShipperName: "Speedy Express", + ProductID: 2, + ProductName: "Chang", + UnitPrice: 19.0000, + Quantity: 21, + Discontinued: true, + ExtendedPrice: 299.2500, + Freight: 109.1100 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Steven Buchanan", + OrderID: 10866, + OrderDate: new Date("10/4/2018"), + ShipperName: "Speedy Express", + ProductID: 24, + ProductName: "Guaran\u00e1 Fant\u00e1stica", + UnitPrice: 4.5000, + Quantity: 6, + Discontinued: true, + ExtendedPrice: 20.2500, + Freight: 109.1100 +}, { + ShipName: "Berglunds snabbk\u00f6p", + ShipAddress: "Berguvsv\u00e4gen 8", + ShipCity: "Lule\u00e5", + ShipRegion: null, + ShipPostalCode: "S-958 22", + ShipCountry: "Sweden", + CustomerID: "BERGS", + CustomerName: "Berglunds snabbk\u00f6p", + Address: "Berguvsv\u00e4gen 8", + City: "Lule\u00e5", + Region: null, + PostalCode: "S-958 22", + Country: "Sweden", + Salesperson: "Steven Buchanan", + OrderID: 10866, + OrderDate: new Date("10/4/2018"), + ShipperName: "Speedy Express", + ProductID: 30, + ProductName: "Nord-Ost Matjeshering", + UnitPrice: 25.8900, + Quantity: 40, + Discontinued: false, + ExtendedPrice: 776.7000, + Freight: 109.1100 +}, { + ShipName: "Blauer See Delikatessen", + ShipAddress: "Forsterstr. 57", + ShipCity: "Mannheim", + ShipRegion: null, + ShipPostalCode: "68306", + ShipCountry: "Germany", + CustomerID: "BLAUS", + CustomerName: "Blauer See Delikatessen", + Address: "Forsterstr. 57", + City: "Mannheim", + Region: null, + PostalCode: "68306", + Country: "Germany", + Salesperson: "Anne Dodsworth", + OrderID: 10501, + OrderDate: new Date("10/4/2018"), + ShipperName: "Federal Shipping", + ProductID: 54, + ProductName: "Tourti\u00e8re", + UnitPrice: 7.4500, + Quantity: 20, + Discontinued: false, + ExtendedPrice: 149.0000, + Freight: 8.8500 +}, { + ShipName: "Blauer See Delikatessen", + ShipAddress: "Forsterstr. 57", + ShipCity: "Mannheim", + ShipRegion: null, + ShipPostalCode: "68306", + ShipCountry: "Germany", + CustomerID: "BLAUS", + CustomerName: "Blauer See Delikatessen", + Address: "Forsterstr. 57", + City: "Mannheim", + Region: null, + PostalCode: "68306", + Country: "Germany", + Salesperson: "Margaret Peacock", + OrderID: 10509, + OrderDate: new Date("10/4/2018"), + ShipperName: "Speedy Express", + ProductID: 28, + ProductName: "R\u00f6ssle Sauerkraut", + UnitPrice: 45.6000, + Quantity: 3, + Discontinued: false, + ExtendedPrice: 136.8000, + Freight: 0.1500 +}, { + ShipName: "Blauer See Delikatessen", + ShipAddress: "Forsterstr. 57", + ShipCity: "Mannheim", + ShipRegion: null, + ShipPostalCode: "68306", + ShipCountry: "Germany", + CustomerID: "BLAUS", + CustomerName: "Blauer See Delikatessen", + Address: "Forsterstr. 57", + City: "Mannheim", + Region: null, + PostalCode: "68306", + Country: "Germany", + Salesperson: "Janet Leverling", + OrderID: 10582, + OrderDate: new Date("10/4/2018"), + ShipperName: "United Package", + ProductID: 57, + ProductName: "Ravioli Angelo", + UnitPrice: 19.5000, + Quantity: 4, + Discontinued: false, + ExtendedPrice: 78.0000, + Freight: 27.7100 +}, { + ShipName: "Blauer See Delikatessen", + ShipAddress: "Forsterstr. 57", + ShipCity: "Mannheim", + ShipRegion: null, + ShipPostalCode: "68306", + ShipCountry: "Germany", + CustomerID: "BLAUS", + CustomerName: "Blauer See Delikatessen", + Address: "Forsterstr. 57", + City: "Mannheim", + Region: null, + PostalCode: "68306", + Country: "Germany", + Salesperson: "Janet Leverling", + OrderID: 10582, + OrderDate: new Date("10/4/2018"), + ShipperName: "United Package", + ProductID: 76, + ProductName: "Lakkalik\u00f6\u00f6ri", + UnitPrice: 18.0000, + Quantity: 14, + Discontinued: false, + ExtendedPrice: 252.0000, + Freight: 27.7100 +}, { + ShipName: "Blauer See Delikatessen", + ShipAddress: "Forsterstr. 57", + ShipCity: "Mannheim", + ShipRegion: null, + ShipPostalCode: "68306", + ShipCountry: "Germany", + CustomerID: "BLAUS", + CustomerName: "Blauer See Delikatessen", + Address: "Forsterstr. 57", + City: "Mannheim", + Region: null, + PostalCode: "68306", + Country: "Germany", + Salesperson: "Laura Callahan", + OrderID: 10614, + OrderDate: new Date("10/4/2018"), + ShipperName: "Federal Shipping", + ProductID: 11, + ProductName: "Queso Cabrales", + UnitPrice: 21.0000, + Quantity: 14, + Discontinued: false, + ExtendedPrice: 294.0000, + Freight: 1.9300 +}, { + ShipName: "Blauer See Delikatessen", + ShipAddress: "Forsterstr. 57", + ShipCity: "Mannheim", + ShipRegion: null, + ShipPostalCode: "68306", + ShipCountry: "Germany", + CustomerID: "BLAUS", + CustomerName: "Blauer See Delikatessen", + Address: "Forsterstr. 57", + City: "Mannheim", + Region: null, + PostalCode: "68306", + Country: "Germany", + Salesperson: "Laura Callahan", + OrderID: 10614, + OrderDate: new Date("10/4/2016"), + ShipperName: "Federal Shipping", + ProductID: 21, + ProductName: "Sir Rodney's Scones", + UnitPrice: 10.0000, + Quantity: 8, + Discontinued: false, + ExtendedPrice: 80.0000, + Freight: 1.9300 +}, { + ShipName: "Blauer See Delikatessen", + ShipAddress: "Forsterstr. 57", + ShipCity: "Mannheim", + ShipRegion: null, + ShipPostalCode: "68306", + ShipCountry: "Germany", + CustomerID: "BLAUS", + CustomerName: "Blauer See Delikatessen", + Address: "Forsterstr. 57", + City: "Mannheim", + Region: null, + PostalCode: "68306", + Country: "Germany", + Salesperson: "Laura Callahan", + OrderID: 10614, + OrderDate: new Date("10/4/2016"), + ShipperName: "Federal Shipping", + ProductID: 39, + ProductName: "Chartreuse verte", + UnitPrice: 18.0000, + Quantity: 5, + Discontinued: false, + ExtendedPrice: 90.0000, + Freight: 1.9300 +}, { + ShipName: "Blauer See Delikatessen", + ShipAddress: "Forsterstr. 57", + ShipCity: "Mannheim", + ShipRegion: null, + ShipPostalCode: "68306", + ShipCountry: "Germany", + CustomerID: "BLAUS", + CustomerName: "Blauer See Delikatessen", + Address: "Forsterstr. 57", + City: "Mannheim", + Region: null, + PostalCode: "68306", + Country: "Germany", + Salesperson: "Anne Dodsworth", + OrderID: 10853, + OrderDate: new Date("10/4/2016"), + ShipperName: "United Package", + ProductID: 18, + ProductName: "Carnarvon Tigers", + UnitPrice: 62.5000, + Quantity: 10, + Discontinued: false, + ExtendedPrice: 625.0000, + Freight: 53.8300 +}, { + ShipName: "Blauer See Delikatessen", + ShipAddress: "Forsterstr. 57", + ShipCity: "Mannheim", + ShipRegion: null, + ShipPostalCode: "68306", + ShipCountry: "Germany", + CustomerID: "BLAUS", + CustomerName: "Blauer See Delikatessen", + Address: "Forsterstr. 57", + City: "Mannheim", + Region: null, + PostalCode: "68306", + Country: "Germany", + Salesperson: "Michael Suyama", + OrderID: 10956, + OrderDate: new Date("10/4/2016"), + ShipperName: "United Package", + ProductID: 21, + ProductName: "Sir Rodney's Scones", + UnitPrice: 10.0000, + Quantity: 12, + Discontinued: false, + ExtendedPrice: 120.0000, + Freight: 44.6500 +}, { + ShipName: "Blauer See Delikatessen", + ShipAddress: "Forsterstr. 57", + ShipCity: "Mannheim", + ShipRegion: null, + ShipPostalCode: "68306", + ShipCountry: "Germany", + CustomerID: "BLAUS", + CustomerName: "Blauer See Delikatessen", + Address: "Forsterstr. 57", + City: "Mannheim", + Region: null, + PostalCode: "68306", + Country: "Germany", + Salesperson: "Michael Suyama", + OrderID: 10956, + OrderDate: new Date("10/4/2016"), + ShipperName: "United Package", + ProductID: 47, + ProductName: "Zaanse koeken", + UnitPrice: 9.5000, + Quantity: 14, + Discontinued: false, + ExtendedPrice: 133.0000, + Freight: 44.6500 +}, { + ShipName: "B\u00f3lido Comidas preparadas", + ShipAddress: "C/ Araquil, 67", + ShipCity: "Madrid", + ShipRegion: null, + ShipPostalCode: "28023", + ShipCountry: "Spain", + CustomerID: "BOLID", + CustomerName: "B\u00f3lido Comidas preparadas", + Address: "C/ Araquil, 67", + City: "Madrid", + Region: null, + PostalCode: "28023", + Country: "Spain", + Salesperson: "Margaret Peacock", + OrderID: 10326, + OrderDate: new Date("10/4/2016"), + ShipperName: "United Package", + ProductID: 75, + ProductName: "Rh\u00f6nbr\u00e4u Klosterbier", + UnitPrice: 6.2000, + Quantity: 50, + Discontinued: false, + ExtendedPrice: 310.0000, + Freight: 77.9200 +}, { + ShipName: "B\u00f3lido Comidas preparadas", + ShipAddress: "C/ Araquil, 67", + ShipCity: "Madrid", + ShipRegion: null, + ShipPostalCode: "28023", + ShipCountry: "Spain", + CustomerID: "BOLID", + CustomerName: "B\u00f3lido Comidas preparadas", + Address: "C/ Araquil, 67", + City: "Madrid", + Region: null, + PostalCode: "28023", + Country: "Spain", + Salesperson: "Anne Dodsworth", + OrderID: 10970, + OrderDate: new Date("10/4/2016"), + ShipperName: "Speedy Express", + ProductID: 52, + ProductName: "Filo Mix", + UnitPrice: 7.0000, + Quantity: 40, + Discontinued: false, + ExtendedPrice: 224.0000, + Freight: 16.1600 +}, { + ShipName: "B\u00f3lido Comidas preparadas", + ShipAddress: "C/ Araquil, 67", + ShipCity: "Madrid", + ShipRegion: null, + ShipPostalCode: "28023", + ShipCountry: "Spain", + CustomerID: "BOLID", + CustomerName: "B\u00f3lido Comidas preparadas", + Address: "C/ Araquil, 67", + City: "Madrid", + Region: null, + PostalCode: "28023", + Country: "Spain", + Salesperson: "Margaret Peacock", + OrderID: 10801, + OrderDate: new Date("10/4/2016"), + ShipperName: "United Package", + ProductID: 17, + ProductName: "Alice Mutton", + UnitPrice: 39.0000, + Quantity: 40, + Discontinued: false, + ExtendedPrice: 1170.0000, + Freight: 97.0900 +}, { + ShipName: "B\u00f3lido Comidas preparadas", + ShipAddress: "C/ Araquil, 67", + ShipCity: "Madrid", + ShipRegion: null, + ShipPostalCode: "28023", + ShipCountry: "Spain", + CustomerID: "BOLID", + CustomerName: "B\u00f3lido Comidas preparadas", + Address: "C/ Araquil, 67", + City: "Madrid", + Region: null, + PostalCode: "28023", + Country: "Spain", + Salesperson: "Margaret Peacock", + OrderID: 10801, + OrderDate: new Date("10/4/2016"), + ShipperName: "United Package", + ProductID: 29, + ProductName: "Th\u00fcringer Rostbratwurst", + UnitPrice: 123.7900, + Quantity: 20, + Discontinued: false, + ExtendedPrice: 1856.8500, + Freight: 97.0900 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Anne Dodsworth", + OrderID: 10331, + OrderDate: new Date("10/4/2016"), + ShipperName: "Speedy Express", + ProductID: 54, + ProductName: "Tourti\u00e8re", + UnitPrice: 5.9000, + Quantity: 15, + Discontinued: false, + ExtendedPrice: 88.5000, + Freight: 10.1900 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Janet Leverling", + OrderID: 10362, + OrderDate: new Date("10/4/2016"), + ShipperName: "Speedy Express", + ProductID: 25, + ProductName: "NuNuCa Nu\u00df-Nougat-Creme", + UnitPrice: 11.2000, + Quantity: 50, + Discontinued: false, + ExtendedPrice: 560.0000, + Freight: 96.0400 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Janet Leverling", + OrderID: 10362, + OrderDate: new Date("10/4/2016"), + ShipperName: "Speedy Express", + ProductID: 51, + ProductName: "Manjimup Dried Apples", + UnitPrice: 42.4000, + Quantity: 20, + Discontinued: false, + ExtendedPrice: 848.0000, + Freight: 96.0400 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Janet Leverling", + OrderID: 10362, + OrderDate: new Date("10/4/2016"), + ShipperName: "Speedy Express", + ProductID: 54, + ProductName: "Tourti\u00e8re", + UnitPrice: 5.9000, + Quantity: 24, + Discontinued: false, + ExtendedPrice: 141.6000, + Freight: 96.0400 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Margaret Peacock", + OrderID: 10470, + OrderDate: new Date("10/4/2016"), + ShipperName: "United Package", + ProductID: 18, + ProductName: "Carnarvon Tigers", + UnitPrice: 50.0000, + Quantity: 30, + Discontinued: false, + ExtendedPrice: 1500.0000, + Freight: 64.5600 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Margaret Peacock", + OrderID: 10470, + OrderDate: new Date("10/4/2016"), + ShipperName: "United Package", + ProductID: 23, + ProductName: "Tunnbr\u00f6d", + UnitPrice: 7.2000, + Quantity: 15, + Discontinued: false, + ExtendedPrice: 108.0000, + Freight: 64.5600 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Margaret Peacock", + OrderID: 10470, + OrderDate: new Date("10/4/2016"), + ShipperName: "United Package", + ProductID: 64, + ProductName: "Wimmers gute Semmelkn\u00f6del", + UnitPrice: 26.6000, + Quantity: 8, + Discontinued: false, + ExtendedPrice: 212.8000, + Freight: 64.5600 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Nancy Davolio", + OrderID: 10525, + OrderDate: new Date("10/4/2016"), + ShipperName: "United Package", + ProductID: 36, + ProductName: "Inlagd Sill", + UnitPrice: 19.0000, + Quantity: 30, + Discontinued: false, + ExtendedPrice: 570.0000, + Freight: 11.0600 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Janet Leverling", + OrderID: 10715, + OrderDate: new Date("10/4/2016"), + ShipperName: "Speedy Express", + ProductID: 10, + ProductName: "Ikura", + UnitPrice: 31.0000, + Quantity: 21, + Discontinued: false, + ExtendedPrice: 651.0000, + Freight: 63.2000 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Janet Leverling", + OrderID: 10715, + OrderDate: new Date("10/4/2016"), + ShipperName: "Speedy Express", + ProductID: 71, + ProductName: "Flotemysost", + UnitPrice: 21.5000, + Quantity: 30, + Discontinued: false, + ExtendedPrice: 645.0000, + Freight: 63.2000 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Janet Leverling", + OrderID: 10732, + OrderDate: new Date("10/4/2016"), + ShipperName: "Speedy Express", + ProductID: 76, + ProductName: "Lakkalik\u00f6\u00f6ri", + UnitPrice: 18.0000, + Quantity: 20, + Discontinued: false, + ExtendedPrice: 360.0000, + Freight: 16.9700 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Nancy Davolio", + OrderID: 10827, + OrderDate: new Date("10/4/2017"), + ShipperName: "United Package", + ProductID: 10, + ProductName: "Ikura", + UnitPrice: 31.0000, + Quantity: 15, + Discontinued: false, + ExtendedPrice: 465.0000, + Freight: 63.5400 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Nancy Davolio", + OrderID: 10827, + OrderDate: new Date("10/4/2016"), + ShipperName: "United Package", + ProductID: 39, + ProductName: "Chartreuse verte", + UnitPrice: 18.0000, + Quantity: 21, + Discontinued: false, + ExtendedPrice: 378.0000, + Freight: 63.5400 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Robert King", + OrderID: 10876, + OrderDate: new Date("10/4/2016"), + ShipperName: "Federal Shipping", + ProductID: 46, + ProductName: "Spegesild", + UnitPrice: 12.0000, + Quantity: 21, + Discontinued: false, + ExtendedPrice: 252.0000, + Freight: 60.4200 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Robert King", + OrderID: 10876, + OrderDate: new Date("10/4/2016"), + ShipperName: "Federal Shipping", + ProductID: 64, + ProductName: "Wimmers gute Semmelkn\u00f6del", + UnitPrice: 33.2500, + Quantity: 20, + Discontinued: false, + ExtendedPrice: 665.0000, + Freight: 60.4200 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Laura Callahan", + OrderID: 10932, + OrderDate: new Date("10/4/2016"), + ShipperName: "Speedy Express", + ProductID: 72, + ProductName: "Mozzarella di Giovanni", + UnitPrice: 34.8000, + Quantity: 16, + Discontinued: false, + ExtendedPrice: 556.8000, + Freight: 134.6400 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Laura Callahan", + OrderID: 10940, + OrderDate: new Date("10/4/2016"), + ShipperName: "Federal Shipping", + ProductID: 13, + ProductName: "Konbu", + UnitPrice: 6.0000, + Quantity: 20, + Discontinued: false, + ExtendedPrice: 120.0000, + Freight: 19.7700 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Nancy Davolio", + OrderID: 10340, + OrderDate: new Date("10/4/2016"), + ShipperName: "Federal Shipping", + ProductID: 18, + ProductName: "Carnarvon Tigers", + UnitPrice: 50.0000, + Quantity: 20, + Discontinued: false, + ExtendedPrice: 950.0000, + Freight: 166.3100 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Nancy Davolio", + OrderID: 10340, + OrderDate: new Date("10/4/2016"), + ShipperName: "Federal Shipping", + ProductID: 41, + ProductName: "Jack's New England Clam Chowder", + UnitPrice: 7.7000, + Quantity: 12, + Discontinued: false, + ExtendedPrice: 87.7800, + Freight: 166.3100 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Nancy Davolio", + OrderID: 10340, + OrderDate: new Date("10/4/2016"), + ShipperName: "Federal Shipping", + ProductID: 43, + ProductName: "Ipoh Coffee", + UnitPrice: 36.8000, + Quantity: 40, + Discontinued: false, + ExtendedPrice: 1398.4000, + Freight: 166.3100 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Andrew Fuller", + OrderID: 10663, + OrderDate: new Date("10/4/2016"), + ShipperName: "United Package", + ProductID: 40, + ProductName: "Boston Crab Meat", + UnitPrice: 18.4000, + Quantity: 30, + Discontinued: false, + ExtendedPrice: 524.4000, + Freight: 113.1500 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Andrew Fuller", + OrderID: 10663, + OrderDate: new Date("10/4/2016"), + ShipperName: "United Package", + ProductID: 42, + ProductName: "Singaporean Hokkien Fried Mee", + UnitPrice: 14.0000, + Quantity: 30, + Discontinued: false, + ExtendedPrice: 399.0000, + Freight: 113.1500 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Andrew Fuller", + OrderID: 10663, + OrderDate: new Date("10/4/2016"), + ShipperName: "United Package", + ProductID: 51, + ProductName: "Manjimup Dried Apples", + UnitPrice: 53.0000, + Quantity: 20, + Discontinued: false, + ExtendedPrice: 1007.0000, + Freight: 113.1500 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Steven Buchanan", + OrderID: 10730, + OrderDate: new Date("10/4/2016"), + ShipperName: "Speedy Express", + ProductID: 16, + ProductName: "Pavlova", + UnitPrice: 17.4500, + Quantity: 15, + Discontinued: false, + ExtendedPrice: 248.6600, + Freight: 20.1200 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Steven Buchanan", + OrderID: 10730, + OrderDate: new Date("10/4/2016"), + ShipperName: "Speedy Express", + ProductID: 31, + ProductName: "Gorgonzola Telino", + UnitPrice: 12.5000, + Quantity: 3, + Discontinued: false, + ExtendedPrice: 35.6200, + Freight: 20.1200 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Steven Buchanan", + OrderID: 10730, + OrderDate: new Date("10/4/2016"), + ShipperName: "Speedy Express", + ProductID: 65, + ProductName: "Louisiana Fiery Hot Pepper Sauce", + UnitPrice: 21.0500, + Quantity: 10, + Discontinued: false, + ExtendedPrice: 199.9700, + Freight: 20.1200 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Anne Dodsworth", + OrderID: 10871, + OrderDate: new Date("10/4/2016"), + ShipperName: "United Package", + ProductID: 6, + ProductName: "Grandma's Boysenberry Spread", + UnitPrice: 25.0000, + Quantity: 50, + Discontinued: false, + ExtendedPrice: 1187.5000, + Freight: 112.2700 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Anne Dodsworth", + OrderID: 10871, + OrderDate: new Date("10/4/2016"), + ShipperName: "United Package", + ProductID: 16, + ProductName: "Pavlova", + UnitPrice: 17.4500, + Quantity: 12, + Discontinued: false, + ExtendedPrice: 198.9300, + Freight: 112.2700 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Anne Dodsworth", + OrderID: 10871, + OrderDate: new Date("10/4/2016"), + ShipperName: "United Package", + ProductID: 17, + ProductName: "Alice Mutton", + UnitPrice: 39.0000, + Quantity: 16, + Discontinued: false, + ExtendedPrice: 592.8000, + Freight: 112.2700 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Nancy Davolio", + OrderID: 10525, + OrderDate: new Date("10/4/2016"), + ShipperName: "United Package", + ProductID: 40, + ProductName: "Boston Crab Meat", + UnitPrice: 18.4000, + Quantity: 15, + Discontinued: false, + ExtendedPrice: 248.4000, + Freight: 11.0600 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Laura Callahan", + OrderID: 10932, + OrderDate: new Date("10/4/2016"), + ShipperName: "Speedy Express", + ProductID: 16, + ProductName: "Pavlova", + UnitPrice: 17.4500, + Quantity: 30, + Discontinued: false, + ExtendedPrice: 471.1500, + Freight: 134.6400 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Laura Callahan", + OrderID: 10932, + OrderDate: new Date("10/4/2016"), + ShipperName: "Speedy Express", + ProductID: 62, + ProductName: "Tarte au sucre", + UnitPrice: 49.3000, + Quantity: 14, + Discontinued: true, + ExtendedPrice: 621.1800, + Freight: 134.6400 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Laura Callahan", + OrderID: 10932, + OrderDate: new Date("10/4/2016"), + ShipperName: "Speedy Express", + ProductID: 75, + ProductName: "Rh\u00f6nbr\u00e4u Klosterbier", + UnitPrice: 7.7500, + Quantity: 20, + Discontinued: true, + ExtendedPrice: 139.5000, + Freight: 134.6400 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Margaret Peacock", + OrderID: 10511, + OrderDate: new Date("10/4/2016"), + ShipperName: "Federal Shipping", + ProductID: 4, + ProductName: "Chef Anton's Cajun Seasoning", + UnitPrice: 22.0000, + Quantity: 50, + Discontinued: true, + ExtendedPrice: 935.0000, + Freight: 350.6400 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Margaret Peacock", + OrderID: 10511, + OrderDate: new Date("10/4/2016"), + ShipperName: "Federal Shipping", + ProductID: 7, + ProductName: "Uncle Bob's Organic Dried Pears", + UnitPrice: 30.0000, + Quantity: 50, + Discontinued: true, + ExtendedPrice: 1275.0000, + Freight: 350.6400 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Margaret Peacock", + OrderID: 10511, + OrderDate: new Date("10/4/2016"), + ShipperName: "Federal Shipping", + ProductID: 8, + ProductName: "Northwoods Cranberry Sauce", + UnitPrice: 40.0000, + Quantity: 10, + Discontinued: true, + ExtendedPrice: 340.0000, + Freight: 350.6400 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Margaret Peacock", + OrderID: 10755, + OrderDate: new Date("10/4/2016"), + ShipperName: "United Package", + ProductID: 47, + ProductName: "Zaanse koeken", + UnitPrice: 9.5000, + Quantity: 30, + Discontinued: true, + ExtendedPrice: 213.7500, + Freight: 16.7100 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Margaret Peacock", + OrderID: 10755, + OrderDate: new Date("10/4/2016"), + ShipperName: "United Package", + ProductID: 56, + ProductName: "Gnocchi di nonna Alice", + UnitPrice: 38.0000, + Quantity: 30, + Discontinued: true, + ExtendedPrice: 855.0000, + Freight: 16.7100 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Margaret Peacock", + OrderID: 10755, + OrderDate: new Date("10/4/2016"), + ShipperName: "United Package", + ProductID: 57, + ProductName: "Ravioli Angelo", + UnitPrice: 19.5000, + Quantity: 14, + Discontinued: true, + ExtendedPrice: 204.7500, + Freight: 16.7100 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Margaret Peacock", + OrderID: 10755, + OrderDate: new Date("10/4/2016"), + ShipperName: "United Package", + ProductID: 69, + ProductName: "Gudbrandsdalsost", + UnitPrice: 36.0000, + Quantity: 25, + Discontinued: true, + ExtendedPrice: 675.0000, + Freight: 16.7100 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Margaret Peacock", + OrderID: 11076, + OrderDate: new Date("10/4/2016"), + ShipperName: "United Package", + ProductID: 6, + ProductName: "Grandma's Boysenberry Spread", + UnitPrice: 25.0000, + Quantity: 20, + Discontinued: true, + ExtendedPrice: 375.0000, + Freight: 38.2800 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Margaret Peacock", + OrderID: 11076, + OrderDate: new Date("10/4/2016"), + ShipperName: "United Package", + ProductID: 14, + ProductName: "Tofu", + UnitPrice: 23.2500, + Quantity: 20, + Discontinued: true, + ExtendedPrice: 348.7500, + Freight: 38.2800 +}, { + ShipName: "Bon app'", + ShipAddress: "12, rue des Bouchers", + ShipCity: "Marseille", + ShipRegion: null, + ShipPostalCode: "13008", + ShipCountry: "France", + CustomerID: "BONAP", + CustomerName: "Bon app'", + Address: "12, rue des Bouchers", + City: "Marseille", + Region: null, + PostalCode: "13008", + Country: "France", + Salesperson: "Margaret Peacock", + OrderID: 11076, + OrderDate: new Date("10/4/2016"), + ShipperName: "United Package", + ProductID: 19, + ProductName: "Teatime Chocolate Biscuits", + UnitPrice: 9.2000, + Quantity: 10, + Discontinued: true, + ExtendedPrice: 69.0000, + Freight: 38.2800 +}, { + ShipName: "Bottom-Dollar Markets", + ShipAddress: "23 Toronto Blvd.", + ShipCity: "Toronto", + ShipRegion: "BC", + ShipPostalCode: "T2F 8M4", + ShipCountry: "Canada", + CustomerID: "BOTTM", + CustomerName: "Bottom-Dollar Markets", + Address: "23 Toronto Blvd.", + City: "Toronto", + Region: "BC", + PostalCode: "T2F 8M4", + Country: "Canada", + Salesperson: "Margaret Peacock", + OrderID: 10389, + OrderDate: new Date("10/4/2016"), + ShipperName: "United Package", + ProductID: 10, + ProductName: "Ikura", + UnitPrice: 24.8000, + Quantity: 16, + Discontinued: false, + ExtendedPrice: 396.8000, + Freight: 47.4200 +}, { + ShipName: "Bottom-Dollar Markets", + ShipAddress: "23 Toronto Blvd.", + ShipCity: "Toronto", + ShipRegion: "BC", + ShipPostalCode: "T2F 8M4", + ShipCountry: "Canada", + CustomerID: "BOTTM", + CustomerName: "Bottom-Dollar Markets", + Address: "23 Toronto Blvd.", + City: "Toronto", + Region: "BC", + PostalCode: "T2F 8M4", + Country: "Canada", + Salesperson: "Margaret Peacock", + OrderID: 10389, + OrderDate: new Date("10/4/2016"), + ShipperName: "United Package", + ProductID: 55, + ProductName: "P\u00e2t\u00e9 chinois", + UnitPrice: 19.2000, + Quantity: 15, + Discontinued: false, + ExtendedPrice: 288.0000, + Freight: 47.4200 +}, { + ShipName: "Bottom-Dollar Markets", + ShipAddress: "23 Tsawassen Blvd.", + ShipCity: "Tsawassen", + ShipRegion: "BC", + ShipPostalCode: "T2F 8M4", + ShipCountry: "Canada", + CustomerID: "BOTTM", + CustomerName: "Bottom-Dollar Markets", + Address: "23 Tsawassen Blvd.", + City: "Tsawassen", + Region: "BC", + PostalCode: "T2F 8M4", + Country: "Canada", + Salesperson: "Margaret Peacock", + OrderID: 10389, + OrderDate: new Date("9/4/2018"), + ShipperName: "United Package", + ProductID: 62, + ProductName: "Tarte au sucre", + UnitPrice: 39.4000, + Quantity: 20, + Discontinued: false, + ExtendedPrice: 788.0000, + Freight: 47.4200 +}, { + ShipName: "Bottom-Dollar Markets", + ShipAddress: "23 Tsawassen Blvd.", + ShipCity: "Tsawassen", + ShipRegion: "BC", + ShipPostalCode: "T2F 8M4", + ShipCountry: "Canada", + CustomerID: "BOTTM", + CustomerName: "Bottom-Dollar Markets", + Address: "23 Tsawassen Blvd.", + City: "Tsawassen", + Region: "BC", + PostalCode: "T2F 8M4", + Country: "Canada", + Salesperson: "Margaret Peacock", + OrderID: 10389, + OrderDate: new Date("9/4/2018"), + ShipperName: "United Package", + ProductID: 70, + ProductName: "Outback Lager", + UnitPrice: 12.0000, + Quantity: 30, + Discontinued: false, + ExtendedPrice: 360.0000, + Freight: 47.4200 +}, { + ShipName: "Bottom-Dollar Markets", + ShipAddress: "23 Tsawassen Blvd.", + ShipCity: "Tsawassen", + ShipRegion: "BC", + ShipPostalCode: "T2F 8M4", + ShipCountry: "Canada", + CustomerID: "BOTTM", + CustomerName: "Bottom-Dollar Markets", + Address: "23 Tsawassen Blvd.", + City: "Tsawassen", + Region: "BC", + PostalCode: "T2F 8M4", + Country: "Canada", + Salesperson: "Janet Leverling", + OrderID: 10410, + OrderDate: new Date("9/4/2018"), + ShipperName: "Federal Shipping", + ProductID: 33, + ProductName: "Geitost", + UnitPrice: 2.0000, + Quantity: 49, + Discontinued: false, + ExtendedPrice: 98.0000, + Freight: 2.4000 +}, { + ShipName: "Bottom-Dollar Markets", + ShipAddress: "23 Toronto Blvd.", + ShipCity: "Toronto", + ShipRegion: "BC", + ShipPostalCode: "T2F 8M4", + ShipCountry: "Canada", + CustomerID: "BOTTM", + CustomerName: "Bottom-Dollar Markets", + Address: "23 Toronto Blvd.", + City: "Toronto", + Region: "BC", + PostalCode: "T2F 8M4", + Country: "Canada", + Salesperson: "Janet Leverling", + OrderID: 10410, + OrderDate: new Date("9/4/2018"), + ShipperName: "Federal Shipping", + ProductID: 59, + ProductName: "Raclette Courdavault", + UnitPrice: 44.0000, + Quantity: 16, + Discontinued: false, + ExtendedPrice: 704.0000, + Freight: 2.4000 +}, { + ShipName: "Bottom-Dollar Markets", + ShipAddress: "23 Tsawassen Blvd.", + ShipCity: "Tsawassen", + ShipRegion: "BC", + ShipPostalCode: "T2F 8M4", + ShipCountry: "Canada", + CustomerID: "BOTTM", + CustomerName: "Bottom-Dollar Markets", + Address: "23 Tsawassen Blvd.", + City: "Tsawassen", + Region: "BC", + PostalCode: "T2F 8M4", + Country: "Canada", + Salesperson: "Janet Leverling", + OrderID: 10742, + OrderDate: new Date("9/4/2018"), + ShipperName: "Federal Shipping", + ProductID: 3, + ProductName: "Aniseed Syrup", + UnitPrice: 10.0000, + Quantity: 20, + Discontinued: false, + ExtendedPrice: 200.0000, + Freight: 243.7300 +}, { + ShipName: "Bottom-Dollar Markets", + ShipAddress: "23 Tsawassen Blvd.", + ShipCity: "Tsawassen", + ShipRegion: "BC", + ShipPostalCode: "T2F 8M4", + ShipCountry: "Canada", + CustomerID: "BOTTM", + CustomerName: "Bottom-Dollar Markets", + Address: "23 Tsawassen Blvd.", + City: "Tsawassen", + Region: "BC", + PostalCode: "T2F 8M4", + Country: "Canada", + Salesperson: "Janet Leverling", + OrderID: 10742, + OrderDate: new Date("9/4/2018"), + ShipperName: "Federal Shipping", + ProductID: 60, + ProductName: "Camembert Pierrot", + UnitPrice: 34.0000, + Quantity: 50, + Discontinued: false, + ExtendedPrice: 1700.0000, + Freight: 243.7300 +}, { + ShipName: "Bottom-Dollar Markets", + ShipAddress: "23 Tsawassen Blvd.", + ShipCity: "Tsawassen", + ShipRegion: "BC", + ShipPostalCode: "T2F 8M4", + ShipCountry: "Canada", + CustomerID: "BOTTM", + CustomerName: "Bottom-Dollar Markets", + Address: "23 Tsawassen Blvd.", + City: "Tsawassen", + Region: "BC", + PostalCode: "T2F 8M4", + Country: "Canada", + Salesperson: "Janet Leverling", + OrderID: 10742, + OrderDate: new Date("9/4/2018"), + ShipperName: "Federal Shipping", + ProductID: 72, + ProductName: "Mozzarella di Giovanni", + UnitPrice: 34.8000, + Quantity: 35, + Discontinued: false, + ExtendedPrice: 1218.0000, + Freight: 243.7300 +}, { + ShipName: "Bottom-Dollar Markets", + ShipAddress: "23 Toronto Blvd.", + ShipCity: "Toronto", + ShipRegion: "BC", + ShipPostalCode: "T2F 8M4", + ShipCountry: "Canada", + CustomerID: "BOTTM", + CustomerName: "Bottom-Dollar Markets", + Address: "23 Toronto Blvd.", + City: "Toronto", + Region: "BC", + PostalCode: "T2F 8M4", + Country: "Canada", + Salesperson: "Michael Suyama", + OrderID: 10944, + OrderDate: new Date("9/4/2018"), + ShipperName: "Federal Shipping", + ProductID: 56, + ProductName: "Gnocchi di nonna Alice", + UnitPrice: 38.0000, + Quantity: 18, + Discontinued: false, + ExtendedPrice: 684.0000, + Freight: 52.9200 +}, { + ShipName: "Bottom-Dollar Markets", + ShipAddress: "23 Toronto Blvd.", + ShipCity: "Toronto", + ShipRegion: "BC", + ShipPostalCode: "T2F 8M4", + ShipCountry: "Canada", + CustomerID: "BOTTM", + CustomerName: "Bottom-Dollar Markets", + Address: "23 Toronto Blvd.", + City: "Toronto", + Region: "BC", + PostalCode: "T2F 8M4", + Country: "Canada", + Salesperson: "Andrew Fuller", + OrderID: 10949, + OrderDate: new Date("9/4/2018"), + ShipperName: "Federal Shipping", + ProductID: 6, + ProductName: "Grandma's Boysenberry Spread", + UnitPrice: 25.0000, + Quantity: 12, + Discontinued: false, + ExtendedPrice: 300.0000, + Freight: 74.4400 +}, { + ShipName: "Bottom-Dollar Markets", + ShipAddress: "23 Tsawassen Blvd.", + ShipCity: "Tsawassen", + ShipRegion: "BC", + ShipPostalCode: "T2F 8M4", + ShipCountry: "Canada", + CustomerID: "BOTTM", + CustomerName: "Bottom-Dollar Markets", + Address: "23 Tsawassen Blvd.", + City: "Tsawassen", + Region: "BC", + PostalCode: "T2F 8M4", + Country: "Canada", + Salesperson: "Andrew Fuller", + OrderID: 10949, + OrderDate: new Date("9/4/2018"), + ShipperName: "Federal Shipping", + ProductID: 10, + ProductName: "Ikura", + UnitPrice: 31.0000, + Quantity: 30, + Discontinued: false, + ExtendedPrice: 930.0000, + Freight: 74.4400 +}, { + ShipName: "Bottom-Dollar Markets", + ShipAddress: "23 Tsawassen Blvd.", + ShipCity: "Tsawassen", + ShipRegion: "BC", + ShipPostalCode: "T2F 8M4", + ShipCountry: "Canada", + CustomerID: "BOTTM", + CustomerName: "Bottom-Dollar Markets", + Address: "23 Tsawassen Blvd.", + City: "Tsawassen", + Region: "BC", + PostalCode: "T2F 8M4", + Country: "Canada", + Salesperson: "Andrew Fuller", + OrderID: 10949, + OrderDate: new Date("9/4/2018"), + ShipperName: "Federal Shipping", + ProductID: 17, + ProductName: "Alice Mutton", + UnitPrice: 39.0000, + Quantity: 6, + Discontinued: false, + ExtendedPrice: 234.0000, + Freight: 74.4400 +}, { + ShipName: "Bottom-Dollar Markets", + ShipAddress: "23 Tsawassen Blvd.", + ShipCity: "Tsawassen", + ShipRegion: "BC", + ShipPostalCode: "T2F 8M4", + ShipCountry: "Canada", + CustomerID: "BOTTM", + CustomerName: "Bottom-Dollar Markets", + Address: "23 Tsawassen Blvd.", + City: "Tsawassen", + Region: "BC", + PostalCode: "T2F 8M4", + Country: "Canada", + Salesperson: "Andrew Fuller", + OrderID: 10949, + OrderDate: new Date("9/4/2018"), + ShipperName: "Federal Shipping", + ProductID: 62, + ProductName: "Tarte au sucre", + UnitPrice: 49.3000, + Quantity: 60, + Discontinued: false, + ExtendedPrice: 2958.0000, + Freight: 74.4400 +}, { + ShipName: "Bottom-Dollar Markets", + ShipAddress: "23 Tsawassen Blvd.", + ShipCity: "Tsawassen", + ShipRegion: "BC", + ShipPostalCode: "T2F 8M4", + ShipCountry: "Canada", + CustomerID: "BOTTM", + CustomerName: "Bottom-Dollar Markets", + Address: "23 Tsawassen Blvd.", + City: "Tsawassen", + Region: "BC", + PostalCode: "T2F 8M4", + Country: "Canada", + Salesperson: "Nancy Davolio", + OrderID: 10975, + OrderDate: new Date("9/4/2018"), + ShipperName: "Federal Shipping", + ProductID: 8, + ProductName: "Northwoods Cranberry Sauce", + UnitPrice: 40.0000, + Quantity: 16, + Discontinued: false, + ExtendedPrice: 640.0000, + Freight: 32.2700 +}, { + ShipName: "Bottom-Dollar Markets", + ShipAddress: "23 Tsawassen Blvd.", + ShipCity: "Tsawassen", + ShipRegion: "BC", + ShipPostalCode: "T2F 8M4", + ShipCountry: "Canada", + CustomerID: "BOTTM", + CustomerName: "Bottom-Dollar Markets", + Address: "23 Tsawassen Blvd.", + City: "Tsawassen", + Region: "BC", + PostalCode: "T2F 8M4", + Country: "Canada", + Salesperson: "Nancy Davolio", + OrderID: 10975, + OrderDate: new Date("9/4/2018"), + ShipperName: "Federal Shipping", + ProductID: 75, + ProductName: "Rh\u00f6nbr\u00e4u Klosterbier", + UnitPrice: 7.7500, + Quantity: 10, + Discontinued: false, + ExtendedPrice: 77.5000, + Freight: 32.2700 +}, { + ShipName: "Bottom-Dollar Markets", + ShipAddress: "23 Toronto Blvd.", + ShipCity: "Toronto", + ShipRegion: "BC", + ShipPostalCode: "T2F 8M4", + ShipCountry: "Canada", + CustomerID: "BOTTM", + CustomerName: "Bottom-Dollar Markets", + Address: "23 Toronto Blvd.", + City: "Toronto", + Region: "BC", + PostalCode: "T2F 8M4", + Country: "Canada", + Salesperson: "Anne Dodsworth", + OrderID: 10411, + OrderDate: new Date("5/12/2017"), + ShipperName: "Federal Shipping", + ProductID: 41, + ProductName: "Jack's New England Clam Chowder", + UnitPrice: 7.7000, + Quantity: 25, + Discontinued: true, + ExtendedPrice: 154.0000, + Freight: 23.6500 +}, { + ShipName: "Bottom-Dollar Markets", + ShipAddress: "23 Tsawassen Blvd.", + ShipCity: "Tsawassen", + ShipRegion: "BC", + ShipPostalCode: "T2F 8M4", + ShipCountry: "Canada", + CustomerID: "BOTTM", + CustomerName: "Bottom-Dollar Markets", + Address: "23 Tsawassen Blvd.", + City: "Tsawassen", + Region: "BC", + PostalCode: "T2F 8M4", + Country: "Canada", + Salesperson: "Anne Dodsworth", + OrderID: 10411, + OrderDate: new Date("5/12/2017"), + ShipperName: "Federal Shipping", + ProductID: 44, + ProductName: "Gula Malacca", + UnitPrice: 15.5000, + Quantity: 40, + Discontinued: true, + ExtendedPrice: 496.0000, + Freight: 23.6500 +}, { + ShipName: "Bottom-Dollar Markets", + ShipAddress: "23 Tsawassen Blvd.", + ShipCity: "Tsawassen", + ShipRegion: "BC", + ShipPostalCode: "T2F 8M4", + ShipCountry: "Canada", + CustomerID: "BOTTM", + CustomerName: "Bottom-Dollar Markets", + Address: "23 Tsawassen Blvd.", + City: "Tsawassen", + Region: "BC", + PostalCode: "T2F 8M4", + Country: "Canada", + Salesperson: "Anne Dodsworth", + OrderID: 10411, + OrderDate: new Date("5/12/2017"), + ShipperName: "Federal Shipping", + ProductID: 59, + ProductName: "Raclette Courdavault", + UnitPrice: 44.0000, + Quantity: 9, + Discontinued: true, + ExtendedPrice: 316.8000, + Freight: 23.6500 +}, { + ShipName: "Bottom-Dollar Markets", + ShipAddress: "23 Tsawassen Blvd.", + ShipCity: "Tsawassen", + ShipRegion: "BC", + ShipPostalCode: "T2F 8M4", + ShipCountry: "Canada", + CustomerID: "BOTTM", + CustomerName: "Bottom-Dollar Markets", + Address: "23 Tsawassen Blvd.", + City: "Tsawassen", + Region: "BC", + PostalCode: "T2F 8M4", + Country: "Canada", + Salesperson: "Margaret Peacock", + OrderID: 10431, + OrderDate: new Date("5/12/2017"), + ShipperName: "United Package", + ProductID: 17, + ProductName: "Alice Mutton", + UnitPrice: 31.2000, + Quantity: 50, + Discontinued: true, + ExtendedPrice: 1170.0000, + Freight: 44.1700 +}, { + ShipName: "Bottom-Dollar Markets", + ShipAddress: "23 Tsawassen Blvd.", + ShipCity: "Tsawassen", + ShipRegion: "BC", + ShipPostalCode: "T2F 8M4", + ShipCountry: "Canada", + CustomerID: "BOTTM", + CustomerName: "Bottom-Dollar Markets", + Address: "23 Tsawassen Blvd.", + City: "Tsawassen", + Region: "BC", + PostalCode: "T2F 8M4", + Country: "Canada", + Salesperson: "Margaret Peacock", + OrderID: 10431, + OrderDate: new Date("5/12/2017"), + ShipperName: "United Package", + ProductID: 40, + ProductName: "Boston Crab Meat", + UnitPrice: 14.7000, + Quantity: 50, + Discontinued: true, + ExtendedPrice: 551.2500, + Freight: 44.1700 +}, { + ShipName: "Bottom-Dollar Markets", + ShipAddress: "23 Tsawassen Blvd.", + ShipCity: "Tsawassen", + ShipRegion: "BC", + ShipPostalCode: "T2F 8M4", + ShipCountry: "Canada", + CustomerID: "BOTTM", + CustomerName: "Bottom-Dollar Markets", + Address: "23 Tsawassen Blvd.", + City: "Tsawassen", + Region: "BC", + PostalCode: "T2F 8M4", + Country: "Canada", + Salesperson: "Margaret Peacock", + OrderID: 10431, + OrderDate: new Date("5/12/2017"), + ShipperName: "United Package", + ProductID: 47, + ProductName: "Zaanse koeken", + UnitPrice: 7.6000, + Quantity: 30, + Discontinued: true, + ExtendedPrice: 171.0000, + Freight: 44.1700 +}, { + ShipName: "Bottom-Dollar Markets", + ShipAddress: "23 Tsawassen Blvd.", + ShipCity: "Tsawassen", + ShipRegion: "BC", + ShipPostalCode: "T2F 8M4", + ShipCountry: "Canada", + CustomerID: "BOTTM", + CustomerName: "Bottom-Dollar Markets", + Address: "23 Tsawassen Blvd.", + City: "Tsawassen", + Region: "BC", + PostalCode: "T2F 8M4", + Country: "Canada", + Salesperson: "Janet Leverling", + OrderID: 10918, + OrderDate: new Date("5/12/2017"), + ShipperName: "Federal Shipping", + ProductID: 1, + ProductName: "Chai", + UnitPrice: 18.0000, + Quantity: 60, + Discontinued: true, + ExtendedPrice: 810.0000, + Freight: 48.8300 +}, { + ShipName: "Bottom-Dollar Markets", + ShipAddress: "23 Tsawassen Blvd.", + ShipCity: "Tsawassen", + ShipRegion: "BC", + ShipPostalCode: "T2F 8M4", + ShipCountry: "Canada", + CustomerID: "BOTTM", + CustomerName: "Bottom-Dollar Markets", + Address: "23 Tsawassen Blvd.", + City: "Tsawassen", + Region: "BC", + PostalCode: "T2F 8M4", + Country: "Canada", + Salesperson: "Janet Leverling", + OrderID: 10918, + OrderDate: new Date("5/12/2017"), + ShipperName: "Federal Shipping", + ProductID: 60, + ProductName: "Camembert Pierrot", + UnitPrice: 34.0000, + Quantity: 25, + Discontinued: true, + ExtendedPrice: 637.5000, + Freight: 48.8300 +}, { + ShipName: "Bottom-Dollar Markets", + ShipAddress: "23 Tsawassen Blvd.", + ShipCity: "Tsawassen", + ShipRegion: "BC", + ShipPostalCode: "T2F 8M4", + ShipCountry: "Canada", + CustomerID: "BOTTM", + CustomerName: "Bottom-Dollar Markets", + Address: "23 Tsawassen Blvd.", + City: "Tsawassen", + Region: "BC", + PostalCode: "T2F 8M4", + Country: "Canada", + Salesperson: "Michael Suyama", + OrderID: 10944, + OrderDate: new Date("5/12/2017"), + ShipperName: "Federal Shipping", + ProductID: 11, + ProductName: "Queso Cabrales", + UnitPrice: 21.0000, + Quantity: 5, + Discontinued: true, + ExtendedPrice: 78.7500, + Freight: 52.9200 +}, { + ShipName: "Bottom-Dollar Markets", + ShipAddress: "23 Tsawassen Blvd.", + ShipCity: "Tsawassen", + ShipRegion: "BC", + ShipPostalCode: "T2F 8M4", + ShipCountry: "Canada", + CustomerID: "BOTTM", + CustomerName: "Bottom-Dollar Markets", + Address: "23 Tsawassen Blvd.", + City: "Tsawassen", + Region: "BC", + PostalCode: "T2F 8M4", + Country: "Canada", + Salesperson: "Michael Suyama", + OrderID: 10944, + OrderDate: new Date("5/12/2017"), + ShipperName: "Federal Shipping", + ProductID: 44, + ProductName: "Gula Malacca", + UnitPrice: 19.4500, + Quantity: 18, + Discontinued: true, + ExtendedPrice: 262.5800, + Freight: 52.9200 +}, { + ShipName: "Bottom-Dollar Markets", + ShipAddress: "23 Tsawassen Blvd.", + ShipCity: "Tsawassen", + ShipRegion: "BC", + ShipPostalCode: "T2F 8M4", + ShipCountry: "Canada", + CustomerID: "BOTTM", + CustomerName: "Bottom-Dollar Markets", + Address: "23 Tsawassen Blvd.", + City: "Tsawassen", + Region: "BC", + PostalCode: "T2F 8M4", + Country: "Canada", + Salesperson: "Nancy Davolio", + OrderID: 11027, + OrderDate: new Date("5/12/2017"), + ShipperName: "Speedy Express", + ProductID: 24, + ProductName: "Guaran\u00e1 Fant\u00e1stica", + UnitPrice: 4.5000, + Quantity: 30, + Discontinued: true, + ExtendedPrice: 101.2500, + Freight: 52.5200 +}, { + ShipName: "Bottom-Dollar Markets", + ShipAddress: "23 Tsawassen Blvd.", + ShipCity: "Tsawassen", + ShipRegion: "BC", + ShipPostalCode: "T2F 8M4", + ShipCountry: "Canada", + CustomerID: "BOTTM", + CustomerName: "Bottom-Dollar Markets", + Address: "23 Tsawassen Blvd.", + City: "Tsawassen", + Region: "BC", + PostalCode: "T2F 8M4", + Country: "Canada", + Salesperson: "Nancy Davolio", + OrderID: 11027, + OrderDate: new Date("5/12/2017"), + ShipperName: "Speedy Express", + ProductID: 62, + ProductName: "Tarte au sucre", + UnitPrice: 49.3000, + Quantity: 21, + Discontinued: true, + ExtendedPrice: 776.4800, + Freight: 52.5200 +}, { + ShipName: "B's Beverages", + ShipAddress: "Fauntleroy Circus", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "EC2 5NT", + ShipCountry: "UK", + CustomerID: "BSBEV", + CustomerName: "B's Beverages", + Address: "Fauntleroy Circus", + City: "London", + Region: null, + PostalCode: "EC2 5NT", + Country: "UK", + Salesperson: "Robert King", + OrderID: 10289, + OrderDate: new Date("5/12/2017"), + ShipperName: "Federal Shipping", + ProductID: 3, + ProductName: "Aniseed Syrup", + UnitPrice: 8.0000, + Quantity: 30, + Discontinued: false, + ExtendedPrice: 240.0000, + Freight: 22.7700 +}, { + ShipName: "B's Beverages", + ShipAddress: "Fauntleroy Circus", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "EC2 5NT", + ShipCountry: "UK", + CustomerID: "BSBEV", + CustomerName: "B's Beverages", + Address: "Fauntleroy Circus", + City: "London", + Region: null, + PostalCode: "EC2 5NT", + Country: "UK", + Salesperson: "Robert King", + OrderID: 10289, + OrderDate: new Date("5/12/2017"), + ShipperName: "Federal Shipping", + ProductID: 64, + ProductName: "Wimmers gute Semmelkn\u00f6del", + UnitPrice: 26.6000, + Quantity: 9, + Discontinued: false, + ExtendedPrice: 239.4000, + Freight: 22.7700 +}, { + ShipName: "B's Beverages", + ShipAddress: "Fauntleroy Circus", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "EC2 5NT", + ShipCountry: "UK", + CustomerID: "BSBEV", + CustomerName: "B's Beverages", + Address: "Fauntleroy Circus", + City: "London", + Region: null, + PostalCode: "EC2 5NT", + Country: "UK", + Salesperson: "Andrew Fuller", + OrderID: 10471, + OrderDate: new Date("4/14/2016"), + ShipperName: "Federal Shipping", + ProductID: 7, + ProductName: "Uncle Bob's Organic Dried Pears", + UnitPrice: 24.0000, + Quantity: 30, + Discontinued: false, + ExtendedPrice: 720.0000, + Freight: 45.5900 +}, { + ShipName: "B's Beverages", + ShipAddress: "Fauntleroy Circus", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "EC2 5NT", + ShipCountry: "UK", + CustomerID: "BSBEV", + CustomerName: "B's Beverages", + Address: "Fauntleroy Circus", + City: "London", + Region: null, + PostalCode: "EC2 5NT", + Country: "UK", + Salesperson: "Andrew Fuller", + OrderID: 10471, + OrderDate: new Date("4/14/2016"), + ShipperName: "Federal Shipping", + ProductID: 56, + ProductName: "Gnocchi di nonna Alice", + UnitPrice: 30.4000, + Quantity: 20, + Discontinued: false, + ExtendedPrice: 608.0000, + Freight: 45.5900 +}, { + ShipName: "B's Beverages", + ShipAddress: "Fauntleroy Circus", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "EC2 5NT", + ShipCountry: "UK", + CustomerID: "BSBEV", + CustomerName: "B's Beverages", + Address: "Fauntleroy Circus", + City: "London", + Region: null, + PostalCode: "EC2 5NT", + Country: "UK", + Salesperson: "Janet Leverling", + OrderID: 10484, + OrderDate: new Date("4/14/2016"), + ShipperName: "Federal Shipping", + ProductID: 21, + ProductName: "Sir Rodney's Scones", + UnitPrice: 8.0000, + Quantity: 14, + Discontinued: false, + ExtendedPrice: 112.0000, + Freight: 6.8800 +}, { + ShipName: "B's Beverages", + ShipAddress: "Fauntleroy Circus", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "EC2 5NT", + ShipCountry: "UK", + CustomerID: "BSBEV", + CustomerName: "B's Beverages", + Address: "Fauntleroy Circus", + City: "London", + Region: null, + PostalCode: "EC2 5NT", + Country: "UK", + Salesperson: "Janet Leverling", + OrderID: 10484, + OrderDate: new Date("4/14/2016"), + ShipperName: "Federal Shipping", + ProductID: 40, + ProductName: "Boston Crab Meat", + UnitPrice: 14.7000, + Quantity: 10, + Discontinued: false, + ExtendedPrice: 147.0000, + Freight: 6.8800 +}, { + ShipName: "B's Beverages", + ShipAddress: "Fauntleroy Circus", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "EC2 5NT", + ShipCountry: "UK", + CustomerID: "BSBEV", + CustomerName: "B's Beverages", + Address: "Fauntleroy Circus", + City: "London", + Region: null, + PostalCode: "EC2 5NT", + Country: "UK", + Salesperson: "Janet Leverling", + OrderID: 10484, + OrderDate: new Date("4/14/2016"), + ShipperName: "Federal Shipping", + ProductID: 51, + ProductName: "Manjimup Dried Apples", + UnitPrice: 42.4000, + Quantity: 3, + Discontinued: false, + ExtendedPrice: 127.2000, + Freight: 6.8800 +}, { + ShipName: "B's Beverages", + ShipAddress: "Fauntleroy Circus", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "EC2 5NT", + ShipCountry: "UK", + CustomerID: "BSBEV", + CustomerName: "B's Beverages", + Address: "Fauntleroy Circus", + City: "London", + Region: null, + PostalCode: "EC2 5NT", + Country: "UK", + Salesperson: "Anne Dodsworth", + OrderID: 10538, + OrderDate: new Date("4/14/2016"), + ShipperName: "Federal Shipping", + ProductID: 70, + ProductName: "Outback Lager", + UnitPrice: 15.0000, + Quantity: 7, + Discontinued: false, + ExtendedPrice: 105.0000, + Freight: 4.8700 +}, { + ShipName: "B's Beverages", + ShipAddress: "Fauntleroy Circus", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "EC2 5NT", + ShipCountry: "UK", + CustomerID: "BSBEV", + CustomerName: "B's Beverages", + Address: "Fauntleroy Circus", + City: "London", + Region: null, + PostalCode: "EC2 5NT", + Country: "UK", + Salesperson: "Anne Dodsworth", + OrderID: 10538, + OrderDate: new Date("4/14/2016"), + ShipperName: "Federal Shipping", + ProductID: 72, + ProductName: "Mozzarella di Giovanni", + UnitPrice: 34.8000, + Quantity: 1, + Discontinued: false, + ExtendedPrice: 34.8000, + Freight: 4.8700 +}, { + ShipName: "B's Beverages", + ShipAddress: "Fauntleroy Circus", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "EC2 5NT", + ShipCountry: "UK", + CustomerID: "BSBEV", + CustomerName: "B's Beverages", + Address: "Fauntleroy Circus", + City: "London", + Region: null, + PostalCode: "EC2 5NT", + Country: "UK", + Salesperson: "Michael Suyama", + OrderID: 10539, + OrderDate: new Date("4/14/2016"), + ShipperName: "Federal Shipping", + ProductID: 13, + ProductName: "Konbu", + UnitPrice: 6.0000, + Quantity: 8, + Discontinued: false, + ExtendedPrice: 48.0000, + Freight: 12.3600 +}, { + ShipName: "B's Beverages", + ShipAddress: "Fauntleroy Circus", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "EC2 5NT", + ShipCountry: "UK", + CustomerID: "BSBEV", + CustomerName: "B's Beverages", + Address: "Fauntleroy Circus", + City: "London", + Region: null, + PostalCode: "EC2 5NT", + Country: "UK", + Salesperson: "Michael Suyama", + OrderID: 10539, + OrderDate: new Date("4/14/2016"), + ShipperName: "Federal Shipping", + ProductID: 21, + ProductName: "Sir Rodney's Scones", + UnitPrice: 10.0000, + Quantity: 15, + Discontinued: false, + ExtendedPrice: 150.0000, + Freight: 12.3600 +}, { + ShipName: "B's Beverages", + ShipAddress: "Fauntleroy Circus", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "EC2 5NT", + ShipCountry: "UK", + CustomerID: "BSBEV", + CustomerName: "B's Beverages", + Address: "Fauntleroy Circus", + City: "London", + Region: null, + PostalCode: "EC2 5NT", + Country: "UK", + Salesperson: "Michael Suyama", + OrderID: 10539, + OrderDate: new Date("4/14/2016"), + ShipperName: "Federal Shipping", + ProductID: 33, + ProductName: "Geitost", + UnitPrice: 2.5000, + Quantity: 15, + Discontinued: false, + ExtendedPrice: 37.5000, + Freight: 12.3600 +}, { + ShipName: "B's Beverages", + ShipAddress: "Fauntleroy Circus", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "EC2 5NT", + ShipCountry: "UK", + CustomerID: "BSBEV", + CustomerName: "B's Beverages", + Address: "Fauntleroy Circus", + City: "London", + Region: null, + PostalCode: "EC2 5NT", + Country: "UK", + Salesperson: "Michael Suyama", + OrderID: 10539, + OrderDate: new Date("4/14/2016"), + ShipperName: "Federal Shipping", + ProductID: 49, + ProductName: "Maxilaku", + UnitPrice: 20.0000, + Quantity: 6, + Discontinued: false, + ExtendedPrice: 120.0000, + Freight: 12.3600 +}, { + ShipName: "B's Beverages", + ShipAddress: "Fauntleroy Circus", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "EC2 5NT", + ShipCountry: "UK", + CustomerID: "BSBEV", + CustomerName: "B's Beverages", + Address: "Fauntleroy Circus", + City: "London", + Region: null, + PostalCode: "EC2 5NT", + Country: "UK", + Salesperson: "Janet Leverling", + OrderID: 10947, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 59, + ProductName: "Raclette Courdavault", + UnitPrice: 55.0000, + Quantity: 4, + Discontinued: false, + ExtendedPrice: 220.0000, + Freight: 3.2600 +}, { + ShipName: "B's Beverages", + ShipAddress: "Fauntleroy Circus", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "EC2 5NT", + ShipCountry: "UK", + CustomerID: "BSBEV", + CustomerName: "B's Beverages", + Address: "Fauntleroy Circus", + City: "London", + Region: null, + PostalCode: "EC2 5NT", + Country: "UK", + Salesperson: "Nancy Davolio", + OrderID: 11023, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 7, + ProductName: "Uncle Bob's Organic Dried Pears", + UnitPrice: 30.0000, + Quantity: 4, + Discontinued: false, + ExtendedPrice: 120.0000, + Freight: 123.8300 +}, { + ShipName: "B's Beverages", + ShipAddress: "Fauntleroy Circus", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "EC2 5NT", + ShipCountry: "UK", + CustomerID: "BSBEV", + CustomerName: "B's Beverages", + Address: "Fauntleroy Circus", + City: "London", + Region: null, + PostalCode: "EC2 5NT", + Country: "UK", + Salesperson: "Nancy Davolio", + OrderID: 11023, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 43, + ProductName: "Ipoh Coffee", + UnitPrice: 46.0000, + Quantity: 30, + Discontinued: false, + ExtendedPrice: 1380.0000, + Freight: 123.8300 +}, { + ShipName: "Cactus Comidas para llevar", + ShipAddress: "Cerrito 333", + ShipCity: "Buenos Aires", + ShipRegion: null, + ShipPostalCode: "1010", + ShipCountry: "Argentina", + CustomerID: "CACTU", + CustomerName: "Cactus Comidas para llevar", + Address: "Cerrito 333", + City: "Buenos Aires", + Region: null, + PostalCode: "1010", + Country: "Argentina", + Salesperson: "Laura Callahan", + OrderID: 10521, + OrderDate: new Date("4/14/2017"), + ShipperName: "United Package", + ProductID: 68, + ProductName: "Scottish Longbreads", + UnitPrice: 12.5000, + Quantity: 6, + Discontinued: true, + ExtendedPrice: 75.0000, + Freight: 17.2200 +}, { + ShipName: "Cactus Comidas para llevar", + ShipAddress: "Cerrito 333", + ShipCity: "Buenos Aires", + ShipRegion: null, + ShipPostalCode: "1010", + ShipCountry: "Argentina", + CustomerID: "CACTU", + CustomerName: "Cactus Comidas para llevar", + Address: "Cerrito 333", + City: "Buenos Aires", + Region: null, + PostalCode: "1010", + Country: "Argentina", + Salesperson: "Laura Callahan", + OrderID: 11054, + OrderDate: new Date("4/14/2017"), + ShipperName: "Speedy Express", + ProductID: 33, + ProductName: "Geitost", + UnitPrice: 2.5000, + Quantity: 10, + Discontinued: false, + ExtendedPrice: 25.0000, + Freight: 0.3300 +}, { + ShipName: "Cactus Comidas para llevar", + ShipAddress: "Cerrito 333", + ShipCity: "Buenos Aires", + ShipRegion: null, + ShipPostalCode: "1010", + ShipCountry: "Argentina", + CustomerID: "CACTU", + CustomerName: "Cactus Comidas para llevar", + Address: "Cerrito 333", + City: "Buenos Aires", + Region: null, + PostalCode: "1010", + Country: "Argentina", + Salesperson: "Laura Callahan", + OrderID: 11054, + OrderDate: new Date("4/14/2016"), + ShipperName: "Speedy Express", + ProductID: 67, + ProductName: "Laughing Lumberjack Lager", + UnitPrice: 14.0000, + Quantity: 20, + Discontinued: false, + ExtendedPrice: 280.0000, + Freight: 0.3300 +}, { + ShipName: "Centro comercial Moctezuma", + ShipAddress: "Sierras de Granada 9993", + ShipCity: "M\u00e9xico D.F.", + ShipRegion: null, + ShipPostalCode: "05022", + ShipCountry: "Mexico", + CustomerID: "CENTC", + CustomerName: "Centro comercial Moctezuma", + Address: "Sierras de Granada 9993", + City: "M\u00e9xico D.F.", + Region: null, + PostalCode: "05022", + Country: "Mexico", + Salesperson: "Margaret Peacock", + OrderID: 10259, + OrderDate: new Date("4/14/2016"), + ShipperName: "Federal Shipping", + ProductID: 21, + ProductName: "Sir Rodney's Scones", + UnitPrice: 8.0000, + Quantity: 10, + Discontinued: false, + ExtendedPrice: 80.0000, + Freight: 3.2500 +}, { + ShipName: "Centro comercial Moctezuma", + ShipAddress: "Sierras de Granada 9993", + ShipCity: "M\u00e9xico D.F.", + ShipRegion: null, + ShipPostalCode: "05022", + ShipCountry: "Mexico", + CustomerID: "CENTC", + CustomerName: "Centro comercial Moctezuma", + Address: "Sierras de Granada 9993", + City: "M\u00e9xico D.F.", + Region: null, + PostalCode: "05022", + Country: "Mexico", + Salesperson: "Margaret Peacock", + OrderID: 10259, + OrderDate: new Date("4/14/2016"), + ShipperName: "Federal Shipping", + ProductID: 37, + ProductName: "Gravad lax", + UnitPrice: 20.8000, + Quantity: 1, + Discontinued: false, + ExtendedPrice: 20.8000, + Freight: 3.2500 +}, { + ShipName: "Chop-suey Chinese", + ShipAddress: "Hauptstr. 31", + ShipCity: "Bern", + ShipRegion: null, + ShipPostalCode: "3012", + ShipCountry: "Switzerland", + CustomerID: "CHOPS", + CustomerName: "Chop-suey Chinese", + Address: "Hauptstr. 29", + City: "Bern", + Region: null, + PostalCode: "3012", + Country: "Switzerland", + Salesperson: "Steven Buchanan", + OrderID: 10254, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 74, + ProductName: "Longlife Tofu", + UnitPrice: 8.0000, + Quantity: 21, + Discontinued: false, + ExtendedPrice: 168.0000, + Freight: 22.9800 +}, { + ShipName: "Chop-suey Chinese", + ShipAddress: "Hauptstr. 31", + ShipCity: "Bern", + ShipRegion: null, + ShipPostalCode: "3012", + ShipCountry: "Switzerland", + CustomerID: "CHOPS", + CustomerName: "Chop-suey Chinese", + Address: "Hauptstr. 29", + City: "Bern", + Region: null, + PostalCode: "3012", + Country: "Switzerland", + Salesperson: "Michael Suyama", + OrderID: 10370, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 64, + ProductName: "Wimmers gute Semmelkn\u00f6del", + UnitPrice: 26.6000, + Quantity: 30, + Discontinued: false, + ExtendedPrice: 798.0000, + Freight: 1.1700 +}, { + ShipName: "Chop-suey Chinese", + ShipAddress: "Hauptstr. 31", + ShipCity: "Bern", + ShipRegion: null, + ShipPostalCode: "3012", + ShipCountry: "Switzerland", + CustomerID: "CHOPS", + CustomerName: "Chop-suey Chinese", + Address: "Hauptstr. 29", + City: "Bern", + Region: null, + PostalCode: "3012", + Country: "Switzerland", + Salesperson: "Michael Suyama", + OrderID: 10519, + OrderDate: new Date("4/14/2016"), + ShipperName: "Federal Shipping", + ProductID: 56, + ProductName: "Gnocchi di nonna Alice", + UnitPrice: 38.0000, + Quantity: 40, + Discontinued: false, + ExtendedPrice: 1520.0000, + Freight: 91.7600 +}, { + ShipName: "Chop-suey Chinese", + ShipAddress: "Hauptstr. 31", + ShipCity: "Bern", + ShipRegion: null, + ShipPostalCode: "3012", + ShipCountry: "Switzerland", + CustomerID: "CHOPS", + CustomerName: "Chop-suey Chinese", + Address: "Hauptstr. 29", + City: "Bern", + Region: null, + PostalCode: "3012", + Country: "Switzerland", + Salesperson: "Nancy Davolio", + OrderID: 10746, + OrderDate: new Date("4/14/2016"), + ShipperName: "Federal Shipping", + ProductID: 13, + ProductName: "Konbu", + UnitPrice: 6.0000, + Quantity: 6, + Discontinued: false, + ExtendedPrice: 36.0000, + Freight: 31.4300 +}, { + ShipName: "Chop-suey Chinese", + ShipAddress: "Hauptstr. 31", + ShipCity: "Bern", + ShipRegion: null, + ShipPostalCode: "3012", + ShipCountry: "Switzerland", + CustomerID: "CHOPS", + CustomerName: "Chop-suey Chinese", + Address: "Hauptstr. 29", + City: "Bern", + Region: null, + PostalCode: "3012", + Country: "Switzerland", + Salesperson: "Nancy Davolio", + OrderID: 10746, + OrderDate: new Date("4/14/2016"), + ShipperName: "Federal Shipping", + ProductID: 42, + ProductName: "Singaporean Hokkien Fried Mee", + UnitPrice: 14.0000, + Quantity: 28, + Discontinued: false, + ExtendedPrice: 392.0000, + Freight: 31.4300 +}, { + ShipName: "Chop-suey Chinese", + ShipAddress: "Hauptstr. 31", + ShipCity: "Bern", + ShipRegion: null, + ShipPostalCode: "3012", + ShipCountry: "Switzerland", + CustomerID: "CHOPS", + CustomerName: "Chop-suey Chinese", + Address: "Hauptstr. 29", + City: "Bern", + Region: null, + PostalCode: "3012", + Country: "Switzerland", + Salesperson: "Nancy Davolio", + OrderID: 10746, + OrderDate: new Date("4/14/2016"), + ShipperName: "Federal Shipping", + ProductID: 62, + ProductName: "Tarte au sucre", + UnitPrice: 49.3000, + Quantity: 9, + Discontinued: false, + ExtendedPrice: 443.7000, + Freight: 31.4300 +}, { + ShipName: "Chop-suey Chinese", + ShipAddress: "Hauptstr. 31", + ShipCity: "Bern", + ShipRegion: null, + ShipPostalCode: "3012", + ShipCountry: "Switzerland", + CustomerID: "CHOPS", + CustomerName: "Chop-suey Chinese", + Address: "Hauptstr. 29", + City: "Bern", + Region: null, + PostalCode: "3012", + Country: "Switzerland", + Salesperson: "Nancy Davolio", + OrderID: 10746, + OrderDate: new Date("4/14/2016"), + ShipperName: "Federal Shipping", + ProductID: 69, + ProductName: "Gudbrandsdalsost", + UnitPrice: 36.0000, + Quantity: 40, + Discontinued: false, + ExtendedPrice: 1440.0000, + Freight: 31.4300 +}, { + ShipName: "Chop-suey Chinese", + ShipAddress: "Hauptstr. 31", + ShipCity: "Bern", + ShipRegion: null, + ShipPostalCode: "3012", + ShipCountry: "Switzerland", + CustomerID: "CHOPS", + CustomerName: "Chop-suey Chinese", + Address: "Hauptstr. 29", + City: "Bern", + Region: null, + PostalCode: "3012", + Country: "Switzerland", + Salesperson: "Margaret Peacock", + OrderID: 10966, + OrderDate: new Date("4/14/2016"), + ShipperName: "Speedy Express", + ProductID: 37, + ProductName: "Gravad lax", + UnitPrice: 26.0000, + Quantity: 8, + Discontinued: false, + ExtendedPrice: 208.0000, + Freight: 27.1900 +}, { + ShipName: "Chop-suey Chinese", + ShipAddress: "Hauptstr. 31", + ShipCity: "Bern", + ShipRegion: null, + ShipPostalCode: "3012", + ShipCountry: "Switzerland", + CustomerID: "CHOPS", + CustomerName: "Chop-suey Chinese", + Address: "Hauptstr. 29", + City: "Bern", + Region: null, + PostalCode: "3012", + Country: "Switzerland", + Salesperson: "Margaret Peacock", + OrderID: 11029, + OrderDate: new Date("4/14/2016"), + ShipperName: "Speedy Express", + ProductID: 56, + ProductName: "Gnocchi di nonna Alice", + UnitPrice: 38.0000, + Quantity: 20, + Discontinued: false, + ExtendedPrice: 760.0000, + Freight: 47.8400 +}, { + ShipName: "Chop-suey Chinese", + ShipAddress: "Hauptstr. 31", + ShipCity: "Bern", + ShipRegion: null, + ShipPostalCode: "3012", + ShipCountry: "Switzerland", + CustomerID: "CHOPS", + CustomerName: "Chop-suey Chinese", + Address: "Hauptstr. 29", + City: "Bern", + Region: null, + PostalCode: "3012", + Country: "Switzerland", + Salesperson: "Margaret Peacock", + OrderID: 11029, + OrderDate: new Date("4/14/2016"), + ShipperName: "Speedy Express", + ProductID: 63, + ProductName: "Vegie-spread", + UnitPrice: 43.9000, + Quantity: 12, + Discontinued: false, + ExtendedPrice: 526.8000, + Freight: 47.8400 +}, { + ShipName: "Chop-suey Chinese", + ShipAddress: "Hauptstr. 31", + ShipCity: "Bern", + ShipRegion: null, + ShipPostalCode: "3012", + ShipCountry: "Switzerland", + CustomerID: "CHOPS", + CustomerName: "Chop-suey Chinese", + Address: "Hauptstr. 29", + City: "Bern", + Region: null, + PostalCode: "3012", + Country: "Switzerland", + Salesperson: "Janet Leverling", + OrderID: 11041, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 63, + ProductName: "Vegie-spread", + UnitPrice: 43.9000, + Quantity: 30, + Discontinued: false, + ExtendedPrice: 1317.0000, + Freight: 48.2200 +}, { + ShipName: "Chop-suey Chinese", + ShipAddress: "Hauptstr. 31", + ShipCity: "Bern", + ShipRegion: null, + ShipPostalCode: "3012", + ShipCountry: "Switzerland", + CustomerID: "CHOPS", + CustomerName: "Chop-suey Chinese", + Address: "Hauptstr. 29", + City: "Bern", + Region: null, + PostalCode: "3012", + Country: "Switzerland", + Salesperson: "Michael Suyama", + OrderID: 10519, + OrderDate: new Date("4/14/2016"), + ShipperName: "Federal Shipping", + ProductID: 10, + ProductName: "Ikura", + UnitPrice: 31.0000, + Quantity: 16, + Discontinued: true, + ExtendedPrice: 471.2000, + Freight: 91.7600 +}, { + ShipName: "Chop-suey Chinese", + ShipAddress: "Hauptstr. 31", + ShipCity: "Bern", + ShipRegion: null, + ShipPostalCode: "3012", + ShipCountry: "Switzerland", + CustomerID: "CHOPS", + CustomerName: "Chop-suey Chinese", + Address: "Hauptstr. 29", + City: "Bern", + Region: null, + PostalCode: "3012", + Country: "Switzerland", + Salesperson: "Michael Suyama", + OrderID: 10519, + OrderDate: new Date("4/14/2016"), + ShipperName: "Federal Shipping", + ProductID: 60, + ProductName: "Camembert Pierrot", + UnitPrice: 34.0000, + Quantity: 10, + Discontinued: true, + ExtendedPrice: 323.0000, + Freight: 91.7600 +}, { + ShipName: "Chop-suey Chinese", + ShipAddress: "Hauptstr. 31", + ShipCity: "Bern", + ShipRegion: null, + ShipPostalCode: "3012", + ShipCountry: "Switzerland", + CustomerID: "CHOPS", + CustomerName: "Chop-suey Chinese", + Address: "Hauptstr. 29", + City: "Bern", + Region: null, + PostalCode: "3012", + Country: "Switzerland", + Salesperson: "Robert King", + OrderID: 10731, + OrderDate: new Date("4/14/2016"), + ShipperName: "Speedy Express", + ProductID: 21, + ProductName: "Sir Rodney's Scones", + UnitPrice: 10.0000, + Quantity: 40, + Discontinued: true, + ExtendedPrice: 380.0000, + Freight: 96.6500 +}, { + ShipName: "Chop-suey Chinese", + ShipAddress: "Hauptstr. 31", + ShipCity: "Bern", + ShipRegion: null, + ShipPostalCode: "3012", + ShipCountry: "Switzerland", + CustomerID: "CHOPS", + CustomerName: "Chop-suey Chinese", + Address: "Hauptstr. 29", + City: "Bern", + Region: null, + PostalCode: "3012", + Country: "Switzerland", + Salesperson: "Robert King", + OrderID: 10731, + OrderDate: new Date("4/14/2016"), + ShipperName: "Speedy Express", + ProductID: 51, + ProductName: "Manjimup Dried Apples", + UnitPrice: 53.0000, + Quantity: 30, + Discontinued: true, + ExtendedPrice: 1510.5000, + Freight: 96.6500 +}, { + ShipName: "Chop-suey Chinese", + ShipAddress: "Hauptstr. 31", + ShipCity: "Bern", + ShipRegion: null, + ShipPostalCode: "3012", + ShipCountry: "Switzerland", + CustomerID: "CHOPS", + CustomerName: "Chop-suey Chinese", + Address: "Hauptstr. 29", + City: "Bern", + Region: null, + PostalCode: "3012", + Country: "Switzerland", + Salesperson: "Steven Buchanan", + OrderID: 10254, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 24, + ProductName: "Guaran\u00e1 Fant\u00e1stica", + UnitPrice: 3.6000, + Quantity: 15, + Discontinued: true, + ExtendedPrice: 45.9000, + Freight: 22.9800 +}, { + ShipName: "Chop-suey Chinese", + ShipAddress: "Hauptstr. 31", + ShipCity: "Bern", + ShipRegion: null, + ShipPostalCode: "3012", + ShipCountry: "Switzerland", + CustomerID: "CHOPS", + CustomerName: "Chop-suey Chinese", + Address: "Hauptstr. 29", + City: "Bern", + Region: null, + PostalCode: "3012", + Country: "Switzerland", + Salesperson: "Steven Buchanan", + OrderID: 10254, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 55, + ProductName: "P\u00e2t\u00e9 chinois", + UnitPrice: 19.2000, + Quantity: 21, + Discontinued: true, + ExtendedPrice: 342.7200, + Freight: 22.9800 +}, { + ShipName: "Chop-suey Chinese", + ShipAddress: "Hauptstr. 31", + ShipCity: "Bern", + ShipRegion: null, + ShipPostalCode: "3012", + ShipCountry: "Switzerland", + CustomerID: "CHOPS", + CustomerName: "Chop-suey Chinese", + Address: "Hauptstr. 29", + City: "Bern", + Region: null, + PostalCode: "3012", + Country: "Switzerland", + Salesperson: "Michael Suyama", + OrderID: 10370, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 1, + ProductName: "Chai", + UnitPrice: 14.4000, + Quantity: 15, + Discontinued: true, + ExtendedPrice: 183.6000, + Freight: 1.1700 +}, { + ShipName: "Chop-suey Chinese", + ShipAddress: "Hauptstr. 31", + ShipCity: "Bern", + ShipRegion: null, + ShipPostalCode: "3012", + ShipCountry: "Switzerland", + CustomerID: "CHOPS", + CustomerName: "Chop-suey Chinese", + Address: "Hauptstr. 29", + City: "Bern", + Region: null, + PostalCode: "3012", + Country: "Switzerland", + Salesperson: "Michael Suyama", + OrderID: 10370, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 74, + ProductName: "Longlife Tofu", + UnitPrice: 8.0000, + Quantity: 20, + Discontinued: true, + ExtendedPrice: 136.0000, + Freight: 1.1700 +}, { + ShipName: "Chop-suey Chinese", + ShipAddress: "Hauptstr. 31", + ShipCity: "Bern", + ShipRegion: null, + ShipPostalCode: "3012", + ShipCountry: "Switzerland", + CustomerID: "CHOPS", + CustomerName: "Chop-suey Chinese", + Address: "Hauptstr. 29", + City: "Bern", + Region: null, + PostalCode: "3012", + Country: "Switzerland", + Salesperson: "Margaret Peacock", + OrderID: 10966, + OrderDate: new Date("4/14/2016"), + ShipperName: "Speedy Express", + ProductID: 56, + ProductName: "Gnocchi di nonna Alice", + UnitPrice: 38.0000, + Quantity: 12, + Discontinued: true, + ExtendedPrice: 387.6000, + Freight: 27.1900 +}, { + ShipName: "Chop-suey Chinese", + ShipAddress: "Hauptstr. 31", + ShipCity: "Bern", + ShipRegion: null, + ShipPostalCode: "3012", + ShipCountry: "Switzerland", + CustomerID: "CHOPS", + CustomerName: "Chop-suey Chinese", + Address: "Hauptstr. 29", + City: "Bern", + Region: null, + PostalCode: "3012", + Country: "Switzerland", + Salesperson: "Margaret Peacock", + OrderID: 10966, + OrderDate: new Date("4/14/2016"), + ShipperName: "Speedy Express", + ProductID: 62, + ProductName: "Tarte au sucre", + UnitPrice: 49.3000, + Quantity: 12, + Discontinued: true, + ExtendedPrice: 502.8600, + Freight: 27.1900 +}, { + ShipName: "Chop-suey Chinese", + ShipAddress: "Hauptstr. 31", + ShipCity: "Bern", + ShipRegion: null, + ShipPostalCode: "3012", + ShipCountry: "Switzerland", + CustomerID: "CHOPS", + CustomerName: "Chop-suey Chinese", + Address: "Hauptstr. 29", + City: "Bern", + Region: null, + PostalCode: "3012", + Country: "Switzerland", + Salesperson: "Janet Leverling", + OrderID: 11041, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 2, + ProductName: "Chang", + UnitPrice: 19.0000, + Quantity: 30, + Discontinued: true, + ExtendedPrice: 456.0000, + Freight: 48.2200 +}, { + ShipName: "Com\u00e9rcio Mineiro", + ShipAddress: "Av. dos Lus\u00edadas, 23", + ShipCity: "Rio de Janeiro", + ShipRegion: "SP", + ShipPostalCode: "05432-043", + ShipCountry: "Brazil", + CustomerID: "COMMI", + CustomerName: "Com\u00e9rcio Mineiro", + Address: "Av. dos Lus\u00edadas, 23", + City: "Rio de Janeiro", + Region: "SP", + PostalCode: "05432-043", + Country: "Brazil", + Salesperson: "Laura Callahan", + OrderID: 10290, + OrderDate: new Date("4/14/2016"), + ShipperName: "Speedy Express", + ProductID: 5, + ProductName: "Chef Anton's Gumbo Mix", + UnitPrice: 17.0000, + Quantity: 20, + Discontinued: true, + ExtendedPrice: 340.0000, + Freight: 79.7000 +}, { + ShipName: "Com\u00e9rcio Mineiro", + ShipAddress: "Av. dos Lus\u00edadas, 23", + ShipCity: "Sao Paulo", + ShipRegion: "SP", + ShipPostalCode: "05432-043", + ShipCountry: "Brazil", + CustomerID: "COMMI", + CustomerName: "Com\u00e9rcio Mineiro", + Address: "Av. dos Lus\u00edadas, 23", + City: "Sao Paulo", + Region: "SP", + PostalCode: "05432-043", + Country: "Brazil", + Salesperson: "Laura Callahan", + OrderID: 10290, + OrderDate: new Date("2/15/2017"), + ShipperName: "Speedy Express", + ProductID: 29, + ProductName: "Th\u00fcringer Rostbratwurst", + UnitPrice: 99.0000, + Quantity: 15, + Discontinued: true, + ExtendedPrice: 1485.0000, + Freight: 79.7000 +}, { + ShipName: "Com\u00e9rcio Mineiro", + ShipAddress: "Av. dos Lus\u00edadas, 23", + ShipCity: "Rio de Janeiro", + ShipRegion: "SP", + ShipPostalCode: "05432-043", + ShipCountry: "Brazil", + CustomerID: "COMMI", + CustomerName: "Com\u00e9rcio Mineiro", + Address: "Av. dos Lus\u00edadas, 23", + City: "Rio de Janeiro", + Region: "SP", + PostalCode: "05432-043", + Country: "Brazil", + Salesperson: "Laura Callahan", + OrderID: 10290, + OrderDate: new Date("4/14/2017"), + ShipperName: "Speedy Express", + ProductID: 49, + ProductName: "Maxilaku", + UnitPrice: 16.0000, + Quantity: 15, + Discontinued: false, + ExtendedPrice: 240.0000, + Freight: 79.7000 +}, { + ShipName: "Com\u00e9rcio Mineiro", + ShipAddress: "Av. dos Lus\u00edadas, 23", + ShipCity: "Sao Paulo", + ShipRegion: "SP", + ShipPostalCode: "05432-043", + ShipCountry: "Brazil", + CustomerID: "COMMI", + CustomerName: "Com\u00e9rcio Mineiro", + Address: "Av. dos Lus\u00edadas, 23", + City: "Sao Paulo", + Region: "SP", + PostalCode: "05432-043", + Country: "Brazil", + Salesperson: "Laura Callahan", + OrderID: 10290, + OrderDate: new Date("4/14/2016"), + ShipperName: "Speedy Express", + ProductID: 77, + ProductName: "Original Frankfurter gr\u00fcne So\u00dfe", + UnitPrice: 10.4000, + Quantity: 10, + Discontinued: false, + ExtendedPrice: 104.0000, + Freight: 79.7000 +}, { + ShipName: "Com\u00e9rcio Mineiro", + ShipAddress: "Av. dos Lus\u00edadas, 23", + ShipCity: "Sao Paulo", + ShipRegion: "SP", + ShipPostalCode: "05432-043", + ShipCountry: "Brazil", + CustomerID: "COMMI", + CustomerName: "Com\u00e9rcio Mineiro", + Address: "Av. dos Lus\u00edadas, 23", + City: "Sao Paulo", + Region: "SP", + PostalCode: "05432-043", + Country: "Brazil", + Salesperson: "Margaret Peacock", + OrderID: 10466, + OrderDate: new Date("5/4/2017"), + ShipperName: "Speedy Express", + ProductID: 11, + ProductName: "Queso Cabrales", + UnitPrice: 16.8000, + Quantity: 10, + Discontinued: false, + ExtendedPrice: 168.0000, + Freight: 11.9300 +}, { + ShipName: "Com\u00e9rcio Mineiro", + ShipAddress: "Av. dos Lus\u00edadas, 23", + ShipCity: "Sao Paulo", + ShipRegion: "SP", + ShipPostalCode: "05432-043", + ShipCountry: "Brazil", + CustomerID: "COMMI", + CustomerName: "Com\u00e9rcio Mineiro", + Address: "Av. dos Lus\u00edadas, 23", + City: "Sao Paulo", + Region: "SP", + PostalCode: "05432-043", + Country: "Brazil", + Salesperson: "Margaret Peacock", + OrderID: 10466, + OrderDate: new Date("4/14/2016"), + ShipperName: "Speedy Express", + ProductID: 46, + ProductName: "Spegesild", + UnitPrice: 9.6000, + Quantity: 5, + Discontinued: false, + ExtendedPrice: 48.0000, + Freight: 11.9300 +}, { + ShipName: "Com\u00e9rcio Mineiro", + ShipAddress: "Av. dos Lus\u00edadas, 23", + ShipCity: "Rio de Janeiro", + ShipRegion: "SP", + ShipPostalCode: "05432-043", + ShipCountry: "Brazil", + CustomerID: "COMMI", + CustomerName: "Com\u00e9rcio Mineiro", + Address: "Av. dos Lus\u00edadas, 23", + City: "Rio de Janeiro", + Region: "SP", + PostalCode: "05432-043", + Country: "Brazil", + Salesperson: "Margaret Peacock", + OrderID: 10494, + OrderDate: new Date("4/1/2017"), + ShipperName: "United Package", + ProductID: 56, + ProductName: "Gnocchi di nonna Alice", + UnitPrice: 30.4000, + Quantity: 30, + Discontinued: false, + ExtendedPrice: 912.0000, + Freight: 65.9900 +}, { + ShipName: "Com\u00e9rcio Mineiro", + ShipAddress: "Av. dos Lus\u00edadas, 23", + ShipCity: "Rio de Janeiro", + ShipRegion: "SP", + ShipPostalCode: "05432-043", + ShipCountry: "Brazil", + CustomerID: "COMMI", + CustomerName: "Com\u00e9rcio Mineiro", + Address: "Av. dos Lus\u00edadas, 23", + City: "Rio de Janeiro", + Region: "SP", + PostalCode: "05432-043", + Country: "Brazil", + Salesperson: "Nancy Davolio", + OrderID: 10969, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 46, + ProductName: "Spegesild", + UnitPrice: 12.0000, + Quantity: 9, + Discontinued: false, + ExtendedPrice: 108.0000, + Freight: 0.2100 +}, { + ShipName: "Com\u00e9rcio Mineiro", + ShipAddress: "Av. dos Lus\u00edadas, 23", + ShipCity: "Sao Paulo", + ShipRegion: "SP", + ShipPostalCode: "05432-043", + ShipCountry: "Brazil", + CustomerID: "COMMI", + CustomerName: "Com\u00e9rcio Mineiro", + Address: "Av. dos Lus\u00edadas, 23", + City: "Sao Paulo", + Region: "SP", + PostalCode: "05432-043", + Country: "Brazil", + Salesperson: "Andrew Fuller", + OrderID: 11042, + OrderDate: new Date("4/14/2016"), + ShipperName: "Speedy Express", + ProductID: 44, + ProductName: "Gula Malacca", + UnitPrice: 19.4500, + Quantity: 15, + Discontinued: false, + ExtendedPrice: 291.7500, + Freight: 29.9900 +}, { + ShipName: "Com\u00e9rcio Mineiro", + ShipAddress: "Av. dos Lus\u00edadas, 23", + ShipCity: "Sao Paulo", + ShipRegion: "SP", + ShipPostalCode: "05432-043", + ShipCountry: "Brazil", + CustomerID: "COMMI", + CustomerName: "Com\u00e9rcio Mineiro", + Address: "Av. dos Lus\u00edadas, 23", + City: "Sao Paulo", + Region: "SP", + PostalCode: "05432-043", + Country: "Brazil", + Salesperson: "Andrew Fuller", + OrderID: 11042, + OrderDate: new Date("4/14/2016"), + ShipperName: "Speedy Express", + ProductID: 61, + ProductName: "Sirop d'\u00e9rable", + UnitPrice: 28.5000, + Quantity: 4, + Discontinued: false, + ExtendedPrice: 114.0000, + Freight: 29.9900 +}, { + ShipName: "Consolidated Holdings", + ShipAddress: "Berkeley Gardens 12 Brewery", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "WX1 6LT", + ShipCountry: "UK", + CustomerID: "CONSH", + CustomerName: "Consolidated Holdings", + Address: "Berkeley Gardens 12 Brewery", + City: "London", + Region: null, + PostalCode: "WX1 6LT", + Country: "UK", + Salesperson: "Laura Callahan", + OrderID: 10435, + OrderDate: new Date("4/16/2016"), + ShipperName: "United Package", + ProductID: 2, + ProductName: "Chang", + UnitPrice: 15.2000, + Quantity: 10, + Discontinued: false, + ExtendedPrice: 152.0000, + Freight: 9.2100 +}, { + ShipName: "Consolidated Holdings", + ShipAddress: "Berkeley Gardens 12 Brewery", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "WX1 6LT", + ShipCountry: "UK", + CustomerID: "CONSH", + CustomerName: "Consolidated Holdings", + Address: "Berkeley Gardens 12 Brewery", + City: "London", + Region: null, + PostalCode: "WX1 6LT", + Country: "UK", + Salesperson: "Laura Callahan", + OrderID: 10435, + OrderDate: new Date("5/12/2016"), + ShipperName: "United Package", + ProductID: 22, + ProductName: "Gustaf's Kn\u00e4ckebr\u00f6d", + UnitPrice: 16.8000, + Quantity: 12, + Discontinued: false, + ExtendedPrice: 201.6000, + Freight: 9.2100 +}, { + ShipName: "Consolidated Holdings", + ShipAddress: "Berkeley Gardens 12 Brewery", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "WX1 6LT", + ShipCountry: "UK", + CustomerID: "CONSH", + CustomerName: "Consolidated Holdings", + Address: "Berkeley Gardens 12 Brewery", + City: "London", + Region: null, + PostalCode: "WX1 6LT", + Country: "UK", + Salesperson: "Laura Callahan", + OrderID: 10435, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 72, + ProductName: "Mozzarella di Giovanni", + UnitPrice: 27.8000, + Quantity: 10, + Discontinued: false, + ExtendedPrice: 278.0000, + Freight: 9.2100 +}, { + ShipName: "Consolidated Holdings", + ShipAddress: "Berkeley Gardens 12 Brewery", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "WX1 6LT", + ShipCountry: "UK", + CustomerID: "CONSH", + CustomerName: "Consolidated Holdings", + Address: "Berkeley Gardens 12 Brewery", + City: "London", + Region: null, + PostalCode: "WX1 6LT", + Country: "UK", + Salesperson: "Andrew Fuller", + OrderID: 10462, + OrderDate: new Date("4/14/2016"), + ShipperName: "Speedy Express", + ProductID: 13, + ProductName: "Konbu", + UnitPrice: 4.8000, + Quantity: 1, + Discontinued: false, + ExtendedPrice: 4.8000, + Freight: 6.1700 +}, { + ShipName: "Consolidated Holdings", + ShipAddress: "Berkeley Gardens 12 Brewery", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "WX1 6LT", + ShipCountry: "UK", + CustomerID: "CONSH", + CustomerName: "Consolidated Holdings", + Address: "Berkeley Gardens 12 Brewery", + City: "London", + Region: null, + PostalCode: "WX1 6LT", + Country: "UK", + Salesperson: "Andrew Fuller", + OrderID: 10462, + OrderDate: new Date("4/14/2016"), + ShipperName: "Speedy Express", + ProductID: 23, + ProductName: "Tunnbr\u00f6d", + UnitPrice: 7.2000, + Quantity: 21, + Discontinued: false, + ExtendedPrice: 151.2000, + Freight: 6.1700 +}, { + ShipName: "Consolidated Holdings", + ShipAddress: "Berkeley Gardens 12 Brewery", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "WX1 6LT", + ShipCountry: "UK", + CustomerID: "CONSH", + CustomerName: "Consolidated Holdings", + Address: "Berkeley Gardens 12 Brewery", + City: "London", + Region: null, + PostalCode: "WX1 6LT", + Country: "UK", + Salesperson: "Robert King", + OrderID: 10848, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 5, + ProductName: "Chef Anton's Gumbo Mix", + UnitPrice: 21.3500, + Quantity: 30, + Discontinued: false, + ExtendedPrice: 640.5000, + Freight: 38.2400 +}, { + ShipName: "Consolidated Holdings", + ShipAddress: "Berkeley Gardens 12 Brewery", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "WX1 6LT", + ShipCountry: "UK", + CustomerID: "CONSH", + CustomerName: "Consolidated Holdings", + Address: "Berkeley Gardens 12 Brewery", + City: "London", + Region: null, + PostalCode: "WX1 6LT", + Country: "UK", + Salesperson: "Robert King", + OrderID: 10848, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 9, + ProductName: "Mishi Kobe Niku", + UnitPrice: 97.0000, + Quantity: 3, + Discontinued: false, + ExtendedPrice: 291.0000, + Freight: 38.2400 +}, { + ShipName: "Die Wandernde Kuh", + ShipAddress: "Adenauerallee 900", + ShipCity: "Stuttgart", + ShipRegion: null, + ShipPostalCode: "70563", + ShipCountry: "Germany", + CustomerID: "WANDK", + CustomerName: "Die Wandernde Kuh", + Address: "Adenauerallee 900", + City: "Stuttgart", + Region: null, + PostalCode: "70563", + Country: "Germany", + Salesperson: "Laura Callahan", + OrderID: 10301, + OrderDate: new Date("4/14/2017"), + ShipperName: "United Package", + ProductID: 40, + ProductName: "Boston Crab Meat", + UnitPrice: 14.7000, + Quantity: 10, + Discontinued: false, + ExtendedPrice: 147.0000, + Freight: 45.0800 +}, { + ShipName: "Die Wandernde Kuh", + ShipAddress: "Adenauerallee 900", + ShipCity: "Stuttgart", + ShipRegion: null, + ShipPostalCode: "70563", + ShipCountry: "Germany", + CustomerID: "WANDK", + CustomerName: "Die Wandernde Kuh", + Address: "Adenauerallee 900", + City: "Stuttgart", + Region: null, + PostalCode: "70563", + Country: "Germany", + Salesperson: "Laura Callahan", + OrderID: 10301, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 56, + ProductName: "Gnocchi di nonna Alice", + UnitPrice: 30.4000, + Quantity: 20, + Discontinued: false, + ExtendedPrice: 608.0000, + Freight: 45.0800 +}, { + ShipName: "Die Wandernde Kuh", + ShipAddress: "Adenauerallee 900", + ShipCity: "Stuttgart", + ShipRegion: null, + ShipPostalCode: "70563", + ShipCountry: "Germany", + CustomerID: "WANDK", + CustomerName: "Die Wandernde Kuh", + Address: "Adenauerallee 900", + City: "Stuttgart", + Region: null, + PostalCode: "70563", + Country: "Germany", + Salesperson: "Andrew Fuller", + OrderID: 10312, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 28, + ProductName: "R\u00f6ssle Sauerkraut", + UnitPrice: 36.4000, + Quantity: 4, + Discontinued: false, + ExtendedPrice: 145.6000, + Freight: 40.2600 +}, { + ShipName: "Die Wandernde Kuh", + ShipAddress: "Adenauerallee 900", + ShipCity: "Stuttgart", + ShipRegion: null, + ShipPostalCode: "70563", + ShipCountry: "Germany", + CustomerID: "WANDK", + CustomerName: "Die Wandernde Kuh", + Address: "Adenauerallee 900", + City: "Stuttgart", + Region: null, + PostalCode: "70563", + Country: "Germany", + Salesperson: "Andrew Fuller", + OrderID: 10312, + OrderDate: new Date("4/14/2018"), + ShipperName: "United Package", + ProductID: 43, + ProductName: "Ipoh Coffee", + UnitPrice: 36.8000, + Quantity: 24, + Discontinued: false, + ExtendedPrice: 883.2000, + Freight: 40.2600 +}, { + ShipName: "Die Wandernde Kuh", + ShipAddress: "Adenauerallee 900", + ShipCity: "Stuttgart", + ShipRegion: null, + ShipPostalCode: "70563", + ShipCountry: "Germany", + CustomerID: "WANDK", + CustomerName: "Die Wandernde Kuh", + Address: "Adenauerallee 900", + City: "Stuttgart", + Region: null, + PostalCode: "70563", + Country: "Germany", + Salesperson: "Andrew Fuller", + OrderID: 10312, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 53, + ProductName: "Perth Pasties", + UnitPrice: 26.2000, + Quantity: 20, + Discontinued: false, + ExtendedPrice: 524.0000, + Freight: 40.2600 +}, { + ShipName: "Die Wandernde Kuh", + ShipAddress: "Adenauerallee 900", + ShipCity: "Stuttgart", + ShipRegion: null, + ShipPostalCode: "70563", + ShipCountry: "Germany", + CustomerID: "WANDK", + CustomerName: "Die Wandernde Kuh", + Address: "Adenauerallee 900", + City: "Stuttgart", + Region: null, + PostalCode: "70563", + Country: "Germany", + Salesperson: "Andrew Fuller", + OrderID: 10312, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 75, + ProductName: "Rh\u00f6nbr\u00e4u Klosterbier", + UnitPrice: 6.2000, + Quantity: 10, + Discontinued: false, + ExtendedPrice: 62.0000, + Freight: 40.2600 +}, { + ShipName: "Die Wandernde Kuh", + ShipAddress: "Adenauerallee 900", + ShipCity: "Stuttgart", + ShipRegion: null, + ShipPostalCode: "70563", + ShipCountry: "Germany", + CustomerID: "WANDK", + CustomerName: "Die Wandernde Kuh", + Address: "Adenauerallee 900", + City: "Stuttgart", + Region: null, + PostalCode: "70563", + Country: "Germany", + Salesperson: "Margaret Peacock", + OrderID: 10348, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 23, + ProductName: "Tunnbr\u00f6d", + UnitPrice: 7.2000, + Quantity: 25, + Discontinued: false, + ExtendedPrice: 180.0000, + Freight: 0.7800 +}, { + ShipName: "Die Wandernde Kuh", + ShipAddress: "Adenauerallee 900", + ShipCity: "Stuttgart", + ShipRegion: null, + ShipPostalCode: "70563", + ShipCountry: "Germany", + CustomerID: "WANDK", + CustomerName: "Die Wandernde Kuh", + Address: "Adenauerallee 900", + City: "Stuttgart", + Region: null, + PostalCode: "70563", + Country: "Germany", + Salesperson: "Michael Suyama", + OrderID: 10356, + OrderDate: new Date("4/14/2018"), + ShipperName: "United Package", + ProductID: 31, + ProductName: "Gorgonzola Telino", + UnitPrice: 10.0000, + Quantity: 30, + Discontinued: false, + ExtendedPrice: 300.0000, + Freight: 36.7100 +}, { + ShipName: "Die Wandernde Kuh", + ShipAddress: "Adenauerallee 900", + ShipCity: "Stuttgart", + ShipRegion: null, + ShipPostalCode: "70563", + ShipCountry: "Germany", + CustomerID: "WANDK", + CustomerName: "Die Wandernde Kuh", + Address: "Adenauerallee 900", + City: "Stuttgart", + Region: null, + PostalCode: "70563", + Country: "Germany", + Salesperson: "Michael Suyama", + OrderID: 10356, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 55, + ProductName: "P\u00e2t\u00e9 chinois", + UnitPrice: 19.2000, + Quantity: 12, + Discontinued: false, + ExtendedPrice: 230.4000, + Freight: 36.7100 +}, { + ShipName: "Die Wandernde Kuh", + ShipAddress: "Adenauerallee 900", + ShipCity: "Stuttgart", + ShipRegion: null, + ShipPostalCode: "70563", + ShipCountry: "Germany", + CustomerID: "WANDK", + CustomerName: "Die Wandernde Kuh", + Address: "Adenauerallee 900", + City: "Stuttgart", + Region: null, + PostalCode: "70563", + Country: "Germany", + Salesperson: "Michael Suyama", + OrderID: 10356, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 69, + ProductName: "Gudbrandsdalsost", + UnitPrice: 28.8000, + Quantity: 20, + Discontinued: false, + ExtendedPrice: 576.0000, + Freight: 36.7100 +}, { + ShipName: "Die Wandernde Kuh", + ShipAddress: "Adenauerallee 900", + ShipCity: "Stuttgart", + ShipRegion: null, + ShipPostalCode: "70563", + ShipCountry: "Germany", + CustomerID: "WANDK", + CustomerName: "Die Wandernde Kuh", + Address: "Adenauerallee 900", + City: "Stuttgart", + Region: null, + PostalCode: "70563", + Country: "Germany", + Salesperson: "Laura Callahan", + OrderID: 10632, + OrderDate: new Date("4/14/2016"), + ShipperName: "Speedy Express", + ProductID: 2, + ProductName: "Chang", + UnitPrice: 19.0000, + Quantity: 30, + Discontinued: true, + ExtendedPrice: 541.5000, + Freight: 41.3800 +}, { + ShipName: "Die Wandernde Kuh", + ShipAddress: "Adenauerallee 900", + ShipCity: "Stuttgart", + ShipRegion: null, + ShipPostalCode: "70563", + ShipCountry: "Germany", + CustomerID: "WANDK", + CustomerName: "Die Wandernde Kuh", + Address: "Adenauerallee 900", + City: "Stuttgart", + Region: null, + PostalCode: "70563", + Country: "Germany", + Salesperson: "Laura Callahan", + OrderID: 10632, + OrderDate: new Date("4/14/2016"), + ShipperName: "Speedy Express", + ProductID: 33, + ProductName: "Geitost", + UnitPrice: 2.5000, + Quantity: 20, + Discontinued: true, + ExtendedPrice: 47.5000, + Freight: 41.3800 +}, { + ShipName: "Die Wandernde Kuh", + ShipAddress: "Adenauerallee 900", + ShipCity: "Stuttgart", + ShipRegion: null, + ShipPostalCode: "70563", + ShipCountry: "Germany", + CustomerID: "WANDK", + CustomerName: "Die Wandernde Kuh", + Address: "Adenauerallee 900", + City: "Stuttgart", + Region: null, + PostalCode: "70563", + Country: "Germany", + Salesperson: "Laura Callahan", + OrderID: 11046, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 12, + ProductName: "Queso Manchego La Pastora", + UnitPrice: 38.0000, + Quantity: 20, + Discontinued: true, + ExtendedPrice: 722.0000, + Freight: 71.6400 +}, { + ShipName: "Die Wandernde Kuh", + ShipAddress: "Adenauerallee 900", + ShipCity: "Stuttgart", + ShipRegion: null, + ShipPostalCode: "70563", + ShipCountry: "Germany", + CustomerID: "WANDK", + CustomerName: "Die Wandernde Kuh", + Address: "Adenauerallee 900", + City: "Stuttgart", + Region: null, + PostalCode: "70563", + Country: "Germany", + Salesperson: "Laura Callahan", + OrderID: 11046, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 32, + ProductName: "Mascarpone Fabioli", + UnitPrice: 32.0000, + Quantity: 15, + Discontinued: true, + ExtendedPrice: 456.0000, + Freight: 71.6400 +}, { + ShipName: "Die Wandernde Kuh", + ShipAddress: "Adenauerallee 900", + ShipCity: "Stuttgart", + ShipRegion: null, + ShipPostalCode: "70563", + ShipCountry: "Germany", + CustomerID: "WANDK", + CustomerName: "Die Wandernde Kuh", + Address: "Adenauerallee 900", + City: "Stuttgart", + Region: null, + PostalCode: "70563", + Country: "Germany", + Salesperson: "Laura Callahan", + OrderID: 11046, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 35, + ProductName: "Steeleye Stout", + UnitPrice: 18.0000, + Quantity: 18, + Discontinued: true, + ExtendedPrice: 307.8000, + Freight: 71.6400 +}, { + ShipName: "Die Wandernde Kuh", + ShipAddress: "Adenauerallee 900", + ShipCity: "Stuttgart", + ShipRegion: null, + ShipPostalCode: "70563", + ShipCountry: "Germany", + CustomerID: "WANDK", + CustomerName: "Die Wandernde Kuh", + Address: "Adenauerallee 900", + City: "Stuttgart", + Region: null, + PostalCode: "70563", + Country: "Germany", + Salesperson: "Nancy Davolio", + OrderID: 10668, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 31, + ProductName: "Gorgonzola Telino", + UnitPrice: 12.5000, + Quantity: 8, + Discontinued: true, + ExtendedPrice: 90.0000, + Freight: 47.2200 +}, { + ShipName: "Die Wandernde Kuh", + ShipAddress: "Adenauerallee 900", + ShipCity: "Stuttgart", + ShipRegion: null, + ShipPostalCode: "70563", + ShipCountry: "Germany", + CustomerID: "WANDK", + CustomerName: "Die Wandernde Kuh", + Address: "Adenauerallee 900", + City: "Stuttgart", + Region: null, + PostalCode: "70563", + Country: "Germany", + Salesperson: "Nancy Davolio", + OrderID: 10668, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 55, + ProductName: "P\u00e2t\u00e9 chinois", + UnitPrice: 24.0000, + Quantity: 4, + Discontinued: true, + ExtendedPrice: 86.4000, + Freight: 47.2200 +}, { + ShipName: "Die Wandernde Kuh", + ShipAddress: "Adenauerallee 900", + ShipCity: "Stuttgart", + ShipRegion: null, + ShipPostalCode: "70563", + ShipCountry: "Germany", + CustomerID: "WANDK", + CustomerName: "Die Wandernde Kuh", + Address: "Adenauerallee 900", + City: "Stuttgart", + Region: null, + PostalCode: "70563", + Country: "Germany", + Salesperson: "Nancy Davolio", + OrderID: 10668, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 64, + ProductName: "Wimmers gute Semmelkn\u00f6del", + UnitPrice: 33.2500, + Quantity: 15, + Discontinued: true, + ExtendedPrice: 448.8700, + Freight: 47.2200 +}, { + ShipName: "Die Wandernde Kuh", + ShipAddress: "Adenauerallee 900", + ShipCity: "Stuttgart", + ShipRegion: null, + ShipPostalCode: "70563", + ShipCountry: "Germany", + CustomerID: "WANDK", + CustomerName: "Die Wandernde Kuh", + Address: "Adenauerallee 900", + City: "Stuttgart", + Region: null, + PostalCode: "70563", + Country: "Germany", + Salesperson: "Margaret Peacock", + OrderID: 10348, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 1, + ProductName: "Chai", + UnitPrice: 14.4000, + Quantity: 15, + Discontinued: true, + ExtendedPrice: 183.6000, + Freight: 0.7800 +}, { + ShipName: "Die Wandernde Kuh", + ShipAddress: "Adenauerallee 900", + ShipCity: "Stuttgart", + ShipRegion: null, + ShipPostalCode: "70563", + ShipCountry: "Germany", + CustomerID: "WANDK", + CustomerName: "Die Wandernde Kuh", + Address: "Adenauerallee 900", + City: "Stuttgart", + Region: null, + PostalCode: "70563", + Country: "Germany", + Salesperson: "Robert King", + OrderID: 10513, + OrderDate: new Date("4/14/2016"), + ShipperName: "Speedy Express", + ProductID: 21, + ProductName: "Sir Rodney's Scones", + UnitPrice: 10.0000, + Quantity: 40, + Discontinued: true, + ExtendedPrice: 320.0000, + Freight: 105.6500 +}, { + ShipName: "Die Wandernde Kuh", + ShipAddress: "Adenauerallee 900", + ShipCity: "Stuttgart", + ShipRegion: null, + ShipPostalCode: "70563", + ShipCountry: "Germany", + CustomerID: "WANDK", + CustomerName: "Die Wandernde Kuh", + Address: "Adenauerallee 900", + City: "Stuttgart", + Region: null, + PostalCode: "70563", + Country: "Germany", + Salesperson: "Robert King", + OrderID: 10513, + OrderDate: new Date("4/14/2016"), + ShipperName: "Speedy Express", + ProductID: 32, + ProductName: "Mascarpone Fabioli", + UnitPrice: 32.0000, + Quantity: 50, + Discontinued: true, + ExtendedPrice: 1280.0000, + Freight: 105.6500 +}, { + ShipName: "Die Wandernde Kuh", + ShipAddress: "Adenauerallee 900", + ShipCity: "Stuttgart", + ShipRegion: null, + ShipPostalCode: "70563", + ShipCountry: "Germany", + CustomerID: "WANDK", + CustomerName: "Die Wandernde Kuh", + Address: "Adenauerallee 900", + City: "Stuttgart", + Region: null, + PostalCode: "70563", + Country: "Germany", + Salesperson: "Robert King", + OrderID: 10513, + OrderDate: new Date("4/14/2016"), + ShipperName: "Speedy Express", + ProductID: 61, + ProductName: "Sirop d'\u00e9rable", + UnitPrice: 28.5000, + Quantity: 15, + Discontinued: true, + ExtendedPrice: 342.0000, + Freight: 105.6500 +}, { + ShipName: "Die Wandernde Kuh", + ShipAddress: "Adenauerallee 900", + ShipCity: "Stuttgart", + ShipRegion: null, + ShipPostalCode: "70563", + ShipCountry: "Germany", + CustomerID: "WANDK", + CustomerName: "Die Wandernde Kuh", + Address: "Adenauerallee 900", + City: "Stuttgart", + Region: null, + PostalCode: "70563", + Country: "Germany", + Salesperson: "Margaret Peacock", + OrderID: 10640, + OrderDate: new Date("4/14/2017"), + ShipperName: "Speedy Express", + ProductID: 69, + ProductName: "Gudbrandsdalsost", + UnitPrice: 36.0000, + Quantity: 20, + Discontinued: true, + ExtendedPrice: 540.0000, + Freight: 23.5500 +}, { + ShipName: "Die Wandernde Kuh", + ShipAddress: "Adenauerallee 900", + ShipCity: "Stuttgart", + ShipRegion: null, + ShipPostalCode: "70563", + ShipCountry: "Germany", + CustomerID: "WANDK", + CustomerName: "Die Wandernde Kuh", + Address: "Adenauerallee 900", + City: "Stuttgart", + Region: null, + PostalCode: "70563", + Country: "Germany", + Salesperson: "Margaret Peacock", + OrderID: 10640, + OrderDate: new Date("4/14/2016"), + ShipperName: "Speedy Express", + ProductID: 70, + ProductName: "Outback Lager", + UnitPrice: 15.0000, + Quantity: 15, + Discontinued: true, + ExtendedPrice: 168.7500, + Freight: 23.5500 +}, { + ShipName: "Die Wandernde Kuh", + ShipAddress: "Adenauerallee 900", + ShipCity: "Stuttgart", + ShipRegion: null, + ShipPostalCode: "70563", + ShipCountry: "Germany", + CustomerID: "WANDK", + CustomerName: "Die Wandernde Kuh", + Address: "Adenauerallee 900", + City: "Stuttgart", + Region: null, + PostalCode: "70563", + Country: "Germany", + Salesperson: "Laura Callahan", + OrderID: 10651, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 19, + ProductName: "Teatime Chocolate Biscuits", + UnitPrice: 9.2000, + Quantity: 12, + Discontinued: true, + ExtendedPrice: 82.8000, + Freight: 20.6000 +}, { + ShipName: "Die Wandernde Kuh", + ShipAddress: "Adenauerallee 900", + ShipCity: "Stuttgart", + ShipRegion: null, + ShipPostalCode: "70563", + ShipCountry: "Germany", + CustomerID: "WANDK", + CustomerName: "Die Wandernde Kuh", + Address: "Adenauerallee 900", + City: "Stuttgart", + Region: null, + PostalCode: "70563", + Country: "Germany", + Salesperson: "Laura Callahan", + OrderID: 10651, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 22, + ProductName: "Gustaf's Kn\u00e4ckebr\u00f6d", + UnitPrice: 21.0000, + Quantity: 20, + Discontinued: true, + ExtendedPrice: 315.0000, + Freight: 20.6000 +}, { + ShipName: "Drachenblut Delikatessen", + ShipAddress: "Walserweg 21", + ShipCity: "Aachen", + ShipRegion: null, + ShipPostalCode: "52066", + ShipCountry: "Germany", + CustomerID: "DRACD", + CustomerName: "Drachenblut Delikatessen", + Address: "Walserweg 21", + City: "Aachen", + Region: null, + PostalCode: "52066", + Country: "Germany", + Salesperson: "Margaret Peacock", + OrderID: 10363, + OrderDate: new Date("4/14/2016"), + ShipperName: "Federal Shipping", + ProductID: 31, + ProductName: "Gorgonzola Telino", + UnitPrice: 10.0000, + Quantity: 20, + Discontinued: false, + ExtendedPrice: 200.0000, + Freight: 30.5400 +}, { + ShipName: "Drachenblut Delikatessen", + ShipAddress: "Walserweg 21", + ShipCity: "Aachen", + ShipRegion: null, + ShipPostalCode: "52066", + ShipCountry: "Germany", + CustomerID: "DRACD", + CustomerName: "Drachenblut Delikatessen", + Address: "Walserweg 21", + City: "Aachen", + Region: null, + PostalCode: "52066", + Country: "Germany", + Salesperson: "Margaret Peacock", + OrderID: 10363, + OrderDate: new Date("12/4/2016"), + ShipperName: "Federal Shipping", + ProductID: 75, + ProductName: "Rh\u00f6nbr\u00e4u Klosterbier", + UnitPrice: 6.2000, + Quantity: 12, + Discontinued: false, + ExtendedPrice: 74.4000, + Freight: 30.5400 +}, { + ShipName: "Drachenblut Delikatessen", + ShipAddress: "Walserweg 21", + ShipCity: "Aachen", + ShipRegion: null, + ShipPostalCode: "52066", + ShipCountry: "Germany", + CustomerID: "DRACD", + CustomerName: "Drachenblut Delikatessen", + Address: "Walserweg 21", + City: "Aachen", + Region: null, + PostalCode: "52066", + Country: "Germany", + Salesperson: "Margaret Peacock", + OrderID: 10363, + OrderDate: new Date("2/8/2016"), + ShipperName: "Federal Shipping", + ProductID: 76, + ProductName: "Lakkalik\u00f6\u00f6ri", + UnitPrice: 14.4000, + Quantity: 12, + Discontinued: false, + ExtendedPrice: 172.8000, + Freight: 30.5400 +}, { + ShipName: "Drachenblut Delikatessen", + ShipAddress: "Walserweg 21", + ShipCity: "Aachen", + ShipRegion: null, + ShipPostalCode: "52066", + ShipCountry: "Germany", + CustomerID: "DRACD", + CustomerName: "Drachenblut Delikatessen", + Address: "Walserweg 21", + City: "Aachen", + Region: null, + PostalCode: "52066", + Country: "Germany", + Salesperson: "Janet Leverling", + OrderID: 10391, + OrderDate: new Date("4/14/2017"), + ShipperName: "Federal Shipping", + ProductID: 13, + ProductName: "Konbu", + UnitPrice: 4.8000, + Quantity: 18, + Discontinued: false, + ExtendedPrice: 86.4000, + Freight: 5.4500 +}, { + ShipName: "Drachenblut Delikatessen", + ShipAddress: "Walserweg 21", + ShipCity: "Aachen", + ShipRegion: null, + ShipPostalCode: "52066", + ShipCountry: "Germany", + CustomerID: "DRACD", + CustomerName: "Drachenblut Delikatessen", + Address: "Walserweg 21", + City: "Aachen", + Region: null, + PostalCode: "52066", + Country: "Germany", + Salesperson: "Robert King", + OrderID: 10797, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 11, + ProductName: "Queso Cabrales", + UnitPrice: 21.0000, + Quantity: 20, + Discontinued: false, + ExtendedPrice: 420.0000, + Freight: 33.3500 +}, { + ShipName: "Drachenblut Delikatessen", + ShipAddress: "Walserweg 21", + ShipCity: "Aachen", + ShipRegion: null, + ShipPostalCode: "52066", + ShipCountry: "Germany", + CustomerID: "DRACD", + CustomerName: "Drachenblut Delikatessen", + Address: "Walserweg 21", + City: "Aachen", + Region: null, + PostalCode: "52066", + Country: "Germany", + Salesperson: "Nancy Davolio", + OrderID: 11067, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 41, + ProductName: "Jack's New England Clam Chowder", + UnitPrice: 9.6500, + Quantity: 9, + Discontinued: false, + ExtendedPrice: 86.8500, + Freight: 7.9800 +}, { + ShipName: "Du monde entier", + ShipAddress: "67, rue des Cinquante Otages", + ShipCity: "Nantes", + ShipRegion: null, + ShipPostalCode: "44000", + ShipCountry: "France", + CustomerID: "DUMON", + CustomerName: "Du monde entier", + Address: "67, rue des Cinquante Otages", + City: "Nantes", + Region: null, + PostalCode: "44000", + Country: "France", + Salesperson: "Nancy Davolio", + OrderID: 10311, + OrderDate: new Date("8/14/2016"), + ShipperName: "Federal Shipping", + ProductID: 42, + ProductName: "Singaporean Hokkien Fried Mee", + UnitPrice: 11.2000, + Quantity: 6, + Discontinued: false, + ExtendedPrice: 67.2000, + Freight: 24.6900 +}, { + ShipName: "Du monde entier", + ShipAddress: "67, rue des Cinquante Otages", + ShipCity: "Nantes", + ShipRegion: null, + ShipPostalCode: "44000", + ShipCountry: "France", + CustomerID: "DUMON", + CustomerName: "Du monde entier", + Address: "67, rue des Cinquante Otages", + City: "Nantes", + Region: null, + PostalCode: "44000", + Country: "France", + Salesperson: "Nancy Davolio", + OrderID: 10311, + OrderDate: new Date("4/14/2017"), + ShipperName: "Federal Shipping", + ProductID: 69, + ProductName: "Gudbrandsdalsost", + UnitPrice: 28.8000, + Quantity: 7, + Discontinued: false, + ExtendedPrice: 201.6000, + Freight: 24.6900 +}, { + ShipName: "Du monde entier", + ShipAddress: "67, rue des Cinquante Otages", + ShipCity: "Nantes", + ShipRegion: null, + ShipPostalCode: "44000", + ShipCountry: "France", + CustomerID: "DUMON", + CustomerName: "Du monde entier", + Address: "67, rue des Cinquante Otages", + City: "Nantes", + Region: null, + PostalCode: "44000", + Country: "France", + Salesperson: "Robert King", + OrderID: 10609, + OrderDate: new Date("6/14/2016"), + ShipperName: "United Package", + ProductID: 1, + ProductName: "Chai", + UnitPrice: 18.0000, + Quantity: 3, + Discontinued: false, + ExtendedPrice: 54.0000, + Freight: 1.8500 +}, { + ShipName: "Du monde entier", + ShipAddress: "67, rue des Cinquante Otages", + ShipCity: "Nantes", + ShipRegion: null, + ShipPostalCode: "44000", + ShipCountry: "France", + CustomerID: "DUMON", + CustomerName: "Du monde entier", + Address: "67, rue des Cinquante Otages", + City: "Nantes", + Region: null, + PostalCode: "44000", + Country: "France", + Salesperson: "Robert King", + OrderID: 10609, + OrderDate: new Date("4/14/2015"), + ShipperName: "United Package", + ProductID: 10, + ProductName: "Ikura", + UnitPrice: 31.0000, + Quantity: 10, + Discontinued: false, + ExtendedPrice: 310.0000, + Freight: 1.8500 +}, { + ShipName: "Du monde entier", + ShipAddress: "67, rue des Cinquante Otages", + ShipCity: "Nantes", + ShipRegion: null, + ShipPostalCode: "44000", + ShipCountry: "France", + CustomerID: "DUMON", + CustomerName: "Du monde entier", + Address: "67, rue des Cinquante Otages", + City: "Nantes", + Region: null, + PostalCode: "44000", + Country: "France", + Salesperson: "Robert King", + OrderID: 10609, + OrderDate: new Date("4/14/2016"), + ShipperName: "United Package", + ProductID: 21, + ProductName: "Sir Rodney's Scones", + UnitPrice: 10.0000, + Quantity: 6, + Discontinued: false, + ExtendedPrice: 60.0000, + Freight: 1.8500 +}, { + ShipName: "Du monde entier", + ShipAddress: "67, rue des Cinquante Otages", + ShipCity: "Nantes", + ShipRegion: null, + ShipPostalCode: "44000", + ShipCountry: "France", + CustomerID: "DUMON", + CustomerName: "Du monde entier", + Address: "67, rue des Cinquante Otages", + City: "Nantes", + Region: null, + PostalCode: "44000", + Country: "France", + Salesperson: "Andrew Fuller", + OrderID: 10683, + OrderDate: new Date("4/14/2017"), + ShipperName: "Speedy Express", + ProductID: 52, + ProductName: "Filo Mix", + UnitPrice: 7.0000, + Quantity: 9, + Discontinued: false, + ExtendedPrice: 63.0000, + Freight: 4.4000 +}, { + ShipName: "Du monde entier", + ShipAddress: "67, rue des Cinquante Otages", + ShipCity: "Nantes", + ShipRegion: null, + ShipPostalCode: "44000", + ShipCountry: "France", + CustomerID: "DUMON", + CustomerName: "Du monde entier", + Address: "67, rue des Cinquante Otages", + City: "Nantes", + Region: null, + PostalCode: "44000", + Country: "France", + Salesperson: "Robert King", + OrderID: 10890, + OrderDate: new Date("4/14/2016"), + ShipperName: "Speedy Express", + ProductID: 17, + ProductName: "Alice Mutton", + UnitPrice: 39.0000, + Quantity: 15, + Discontinued: false, + ExtendedPrice: 585.0000, + Freight: 32.7600 +}, { + ShipName: "Du monde entier", + ShipAddress: "67, rue des Cinquante Otages", + ShipCity: "Nantes", + ShipRegion: null, + ShipPostalCode: "44000", + ShipCountry: "France", + CustomerID: "DUMON", + CustomerName: "Du monde entier", + Address: "67, rue des Cinquante Otages", + City: "Nantes", + Region: null, + PostalCode: "44000", + Country: "France", + Salesperson: "Robert King", + OrderID: 10890, + OrderDate: new Date("4/14/2016"), + ShipperName: "Speedy Express", + ProductID: 34, + ProductName: "Sasquatch Ale", + UnitPrice: 14.0000, + Quantity: 10, + Discontinued: false, + ExtendedPrice: 140.0000, + Freight: 32.7600 +}, { + ShipName: "Du monde entier", + ShipAddress: "67, rue des Cinquante Otages", + ShipCity: "Nantes", + ShipRegion: null, + ShipPostalCode: "44000", + ShipCountry: "France", + CustomerID: "DUMON", + CustomerName: "Du monde entier", + Address: "67, rue des Cinquante Otages", + City: "Nantes", + Region: null, + PostalCode: "44000", + Country: "France", + Salesperson: "Robert King", + OrderID: 10890, + OrderDate: new Date("2/14/2016"), + ShipperName: "Speedy Express", + ProductID: 41, + ProductName: "Jack's New England Clam Chowder", + UnitPrice: 9.6500, + Quantity: 14, + Discontinued: false, + ExtendedPrice: 135.1000, + Freight: 32.7600 +}, { + ShipName: "Eastern Connection", + ShipAddress: "35 King George", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "WX3 6FW", + ShipCountry: "UK", + CustomerID: "EASTC", + CustomerName: "Eastern Connection", + Address: "35 King George", + City: "London", + Region: null, + PostalCode: "WX3 6FW", + Country: "UK", + Salesperson: "Nancy Davolio", + OrderID: 10364, + OrderDate: new Date("4/14/2017"), + ShipperName: "Speedy Express", + ProductID: 69, + ProductName: "Gudbrandsdalsost", + UnitPrice: 28.8000, + Quantity: 30, + Discontinued: false, + ExtendedPrice: 864.0000, + Freight: 71.9700 +}, { + ShipName: "Eastern Connection", + ShipAddress: "35 King George", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "WX3 6FW", + ShipCountry: "UK", + CustomerID: "EASTC", + CustomerName: "Eastern Connection", + Address: "35 King George", + City: "London", + Region: null, + PostalCode: "WX3 6FW", + Country: "UK", + Salesperson: "Nancy Davolio", + OrderID: 10364, + OrderDate: new Date("4/4/2016"), + ShipperName: "Speedy Express", + ProductID: 71, + ProductName: "Flotemysost", + UnitPrice: 17.2000, + Quantity: 5, + Discontinued: false, + ExtendedPrice: 86.0000, + Freight: 71.9700 +}, { + ShipName: "Eastern Connection", + ShipAddress: "35 King George", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "WX3 6FW", + ShipCountry: "UK", + CustomerID: "EASTC", + CustomerName: "Eastern Connection", + Address: "35 King George", + City: "London", + Region: null, + PostalCode: "WX3 6FW", + Country: "UK", + Salesperson: "Nancy Davolio", + OrderID: 10400, + OrderDate: new Date("3/12/2016"), + ShipperName: "Federal Shipping", + ProductID: 29, + ProductName: "Th\u00fcringer Rostbratwurst", + UnitPrice: 99.0000, + Quantity: 21, + Discontinued: false, + ExtendedPrice: 2079.0000, + Freight: 83.9300 +}, { + ShipName: "Eastern Connection", + ShipAddress: "35 King George", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "WX3 6FW", + ShipCountry: "UK", + CustomerID: "EASTC", + CustomerName: "Eastern Connection", + Address: "35 King George", + City: "London", + Region: null, + PostalCode: "WX3 6FW", + Country: "UK", + Salesperson: "Nancy Davolio", + OrderID: 10400, + OrderDate: new Date("4/14/2017"), + ShipperName: "Federal Shipping", + ProductID: 35, + ProductName: "Steeleye Stout", + UnitPrice: 14.4000, + Quantity: 35, + Discontinued: false, + ExtendedPrice: 504.0000, + Freight: 83.9300 +}, { + ShipName: "Eastern Connection", + ShipAddress: "35 King George", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "WX3 6FW", + ShipCountry: "UK", + CustomerID: "EASTC", + CustomerName: "Eastern Connection", + Address: "35 King George", + City: "London", + Region: null, + PostalCode: "WX3 6FW", + Country: "UK", + Salesperson: "Nancy Davolio", + OrderID: 10400, + OrderDate: new Date("1/1/2017"), + ShipperName: "Federal Shipping", + ProductID: 49, + ProductName: "Maxilaku", + UnitPrice: 16.0000, + Quantity: 30, + Discontinued: false, + ExtendedPrice: 480.0000, + Freight: 83.9300 +}, { + ShipName: "Eastern Connection", + ShipAddress: "35 King George", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "WX3 6FW", + ShipCountry: "UK", + CustomerID: "EASTC", + CustomerName: "Eastern Connection", + Address: "35 King George", + City: "London", + Region: null, + PostalCode: "WX3 6FW", + Country: "UK", + Salesperson: "Robert King", + OrderID: 10532, + OrderDate: new Date("4/22/2017"), + ShipperName: "Federal Shipping", + ProductID: 30, + ProductName: "Nord-Ost Matjeshering", + UnitPrice: 25.8900, + Quantity: 15, + Discontinued: false, + ExtendedPrice: 388.3500, + Freight: 74.4600 +}, { + ShipName: "Eastern Connection", + ShipAddress: "35 King George", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "WX3 6FW", + ShipCountry: "UK", + CustomerID: "EASTC", + CustomerName: "Eastern Connection", + Address: "35 King George", + City: "London", + Region: null, + PostalCode: "WX3 6FW", + Country: "UK", + Salesperson: "Robert King", + OrderID: 10532, + OrderDate: new Date("9/25/2017"), + ShipperName: "Federal Shipping", + ProductID: 66, + ProductName: "Louisiana Hot Spiced Okra", + UnitPrice: 17.0000, + Quantity: 24, + Discontinued: false, + ExtendedPrice: 408.0000, + Freight: 74.4600 +}, { + ShipName: "Eastern Connection", + ShipAddress: "35 King George", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "WX3 6FW", + ShipCountry: "UK", + CustomerID: "EASTC", + CustomerName: "Eastern Connection", + Address: "35 King George", + City: "London", + Region: null, + PostalCode: "WX3 6FW", + Country: "UK", + Salesperson: "Margaret Peacock", + OrderID: 10726, + OrderDate: new Date("4/24/2015"), + ShipperName: "Speedy Express", + ProductID: 4, + ProductName: "Chef Anton's Cajun Seasoning", + UnitPrice: 22.0000, + Quantity: 25, + Discontinued: false, + ExtendedPrice: 550.0000, + Freight: 16.5600 +}, { + ShipName: "Eastern Connection", + ShipAddress: "35 King George", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "WX3 6FW", + ShipCountry: "UK", + CustomerID: "EASTC", + CustomerName: "Eastern Connection", + Address: "35 King George", + City: "London", + Region: null, + PostalCode: "WX3 6FW", + Country: "UK", + Salesperson: "Margaret Peacock", + OrderID: 10726, + OrderDate: new Date("3/11/2017"), + ShipperName: "Speedy Express", + ProductID: 11, + ProductName: "Queso Cabrales", + UnitPrice: 21.0000, + Quantity: 5, + Discontinued: false, + ExtendedPrice: 105.0000, + Freight: 16.5600 +}, { + ShipName: "Eastern Connection", + ShipAddress: "35 King George", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "WX3 6FW", + ShipCountry: "UK", + CustomerID: "EASTC", + CustomerName: "Eastern Connection", + Address: "35 King George", + City: "London", + Region: null, + PostalCode: "WX3 6FW", + Country: "UK", + Salesperson: "Laura Callahan", + OrderID: 10987, + OrderDate: new Date("4/14/2016"), + ShipperName: "Speedy Express", + ProductID: 7, + ProductName: "Uncle Bob's Organic Dried Pears", + UnitPrice: 30.0000, + Quantity: 60, + Discontinued: false, + ExtendedPrice: 1800.0000, + Freight: 185.4800 +}, { + ShipName: "Eastern Connection", + ShipAddress: "35 King George", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "WX3 6FW", + ShipCountry: "UK", + CustomerID: "EASTC", + CustomerName: "Eastern Connection", + Address: "35 King George", + City: "London", + Region: null, + PostalCode: "WX3 6FW", + Country: "UK", + Salesperson: "Laura Callahan", + OrderID: 10987, + OrderDate: new Date("1/24/2017"), + ShipperName: "Speedy Express", + ProductID: 43, + ProductName: "Ipoh Coffee", + UnitPrice: 46.0000, + Quantity: 6, + Discontinued: false, + ExtendedPrice: 276.0000, + Freight: 185.4800 +}, { + ShipName: "Eastern Connection", + ShipAddress: "35 King George", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "WX3 6FW", + ShipCountry: "UK", + CustomerID: "EASTC", + CustomerName: "Eastern Connection", + Address: "35 King George", + City: "London", + Region: null, + PostalCode: "WX3 6FW", + Country: "UK", + Salesperson: "Laura Callahan", + OrderID: 10987, + OrderDate: new Date("3/31/2017"), + ShipperName: "Speedy Express", + ProductID: 72, + ProductName: "Mozzarella di Giovanni", + UnitPrice: 34.8000, + Quantity: 20, + Discontinued: false, + ExtendedPrice: 696.0000, + Freight: 185.4800 +}, { + ShipName: "Eastern Connection", + ShipAddress: "35 King George", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "WX3 6FW", + ShipCountry: "UK", + CustomerID: "EASTC", + CustomerName: "Eastern Connection", + Address: "35 King George", + City: "London", + Region: null, + PostalCode: "WX3 6FW", + Country: "UK", + Salesperson: "Margaret Peacock", + OrderID: 11024, + OrderDate: new Date("4/15/2018"), + ShipperName: "Speedy Express", + ProductID: 26, + ProductName: "Gumb\u00e4r Gummib\u00e4rchen", + UnitPrice: 31.2300, + Quantity: 12, + Discontinued: false, + ExtendedPrice: 374.7600, + Freight: 74.3600 +}, { + ShipName: "Eastern Connection", + ShipAddress: "35 King George", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "WX3 6FW", + ShipCountry: "UK", + CustomerID: "EASTC", + CustomerName: "Eastern Connection", + Address: "35 King George", + City: "London", + Region: null, + PostalCode: "WX3 6FW", + Country: "UK", + Salesperson: "Margaret Peacock", + OrderID: 11024, + OrderDate: new Date("5/17/2017"), + ShipperName: "Speedy Express", + ProductID: 33, + ProductName: "Geitost", + UnitPrice: 2.5000, + Quantity: 30, + Discontinued: false, + ExtendedPrice: 75.0000, + Freight: 74.3600 +}, { + ShipName: "Eastern Connection", + ShipAddress: "35 King George", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "WX3 6FW", + ShipCountry: "UK", + CustomerID: "EASTC", + CustomerName: "Eastern Connection", + Address: "35 King George", + City: "London", + Region: null, + PostalCode: "WX3 6FW", + Country: "UK", + Salesperson: "Margaret Peacock", + OrderID: 11024, + OrderDate: new Date("4/24/2016"), + ShipperName: "Speedy Express", + ProductID: 65, + ProductName: "Louisiana Fiery Hot Pepper Sauce", + UnitPrice: 21.0500, + Quantity: 21, + Discontinued: false, + ExtendedPrice: 442.0500, + Freight: 74.3600 +}, { + ShipName: "Eastern Connection", + ShipAddress: "35 King George", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "WX3 6FW", + ShipCountry: "UK", + CustomerID: "EASTC", + CustomerName: "Eastern Connection", + Address: "35 King George", + City: "London", + Region: null, + PostalCode: "WX3 6FW", + Country: "UK", + Salesperson: "Margaret Peacock", + OrderID: 11024, + OrderDate: new Date("6/24/2017"), + ShipperName: "Speedy Express", + ProductID: 71, + ProductName: "Flotemysost", + UnitPrice: 21.5000, + Quantity: 50, + Discontinued: false, + ExtendedPrice: 1075.0000, + Freight: 74.3600 +}, { + ShipName: "Eastern Connection", + ShipAddress: "35 King George", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "WX3 6FW", + ShipCountry: "UK", + CustomerID: "EASTC", + CustomerName: "Eastern Connection", + Address: "35 King George", + City: "London", + Region: null, + PostalCode: "WX3 6FW", + Country: "UK", + Salesperson: "Laura Callahan", + OrderID: 11056, + OrderDate: new Date("4/24/2017"), + ShipperName: "United Package", + ProductID: 7, + ProductName: "Uncle Bob's Organic Dried Pears", + UnitPrice: 30.0000, + Quantity: 40, + Discontinued: false, + ExtendedPrice: 1200.0000, + Freight: 278.9600 +}, { + ShipName: "Eastern Connection", + ShipAddress: "35 King George", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "WX3 6FW", + ShipCountry: "UK", + CustomerID: "EASTC", + CustomerName: "Eastern Connection", + Address: "35 King George", + City: "London", + Region: null, + PostalCode: "WX3 6FW", + Country: "UK", + Salesperson: "Laura Callahan", + OrderID: 11056, + OrderDate: new Date("3/24/2018"), + ShipperName: "United Package", + ProductID: 55, + ProductName: "P\u00e2t\u00e9 chinois", + UnitPrice: 24.0000, + Quantity: 35, + Discontinued: false, + ExtendedPrice: 840.0000, + Freight: 278.9600 +}, { + ShipName: "Eastern Connection", + ShipAddress: "35 King George", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "WX3 6FW", + ShipCountry: "UK", + CustomerID: "EASTC", + CustomerName: "Eastern Connection", + Address: "35 King George", + City: "London", + Region: null, + PostalCode: "WX3 6FW", + Country: "UK", + Salesperson: "Laura Callahan", + OrderID: 11056, + OrderDate: new Date("4/28/2018"), + ShipperName: "United Package", + ProductID: 60, + ProductName: "Camembert Pierrot", + UnitPrice: 34.0000, + Quantity: 50, + Discontinued: false, + ExtendedPrice: 1700.0000, + Freight: 278.9600 +}, { + ShipName: "Eastern Connection", + ShipAddress: "35 King George", + ShipCity: "London", + ShipRegion: null, + ShipPostalCode: "WX3 6FW", + ShipCountry: "UK", + CustomerID: "EASTC", + CustomerName: "Eastern Connection", + Address: "35 King George", + City: "London", + Region: null, + PostalCode: "WX3 6FW", + Country: "UK", + Salesperson: "Robert King", + OrderID: 11047, + OrderDate: new Date("4/24/2017"), + ShipperName: "Federal Shipping", + ProductID: 1, + ProductName: "Chai", + UnitPrice: 18.0000, + Quantity: 25, + Discontinued: true, + ExtendedPrice: 337.5000, + Freight: 46.6200 +}]; +/* tslint:enable */