@WidgetComponent

nm-default-data-list-footer

File

src/app/shared/widgets/data-list/default-footer-component/default-data-list-footer.component.ts

Implements

OnInit OnDestroy OnChanges

Metadata

changeDetection ChangeDetectionStrategy.OnPush
selector nm-default-data-list-footer
styleUrls default-data-list-footer.component.scss
templateUrl ./default-data-list-footer.component.html

Index

Widget inputs
Widget outputs
Properties
Methods
Inputs
Outputs

Constructor

constructor(appContext: AppContext, localStorageService: LocalStorageService, dialog: DialogService, dataListActionsService: DataListActionsService, moduleConfig: NmModuleConfiguration, validationService: ValidationService, notificationService: CustomNotificationService, translateService: TranslateService, cdr: ChangeDetectorRef)
Parameters :
Name Type Optional
appContext AppContext no
localStorageService LocalStorageService no
dialog DialogService no
dataListActionsService DataListActionsService no
moduleConfig NmModuleConfiguration no
validationService ValidationService no
notificationService CustomNotificationService no
translateService TranslateService no
cdr ChangeDetectorRef no

Inputs

api

Service that allows to directly interact with the data-list

Type: DataListApi

cell
column
configuration

Data-list configuration

Type: any

dataType

Data-list selected data type

Type: string

grid

Data-list grid instance

Type: DataListComponent

link

Data-list data url without paging

Type: Observable<string>

refreshButtons

Deprecated: feature is now removed.

selected-items

Data-list all selected items

Type: Observable<SelectionParams>

total

Number of entries (rows) in the data-list

Type: Observable<number>

viewMode

Data-list selected view mode

Type: string

Outputs

export

Emits export settings

$event type: EventEmitter

Methods

Private initializeDefaultActions
initializeDefaultActions()
Returns : void
ngOnChanges
ngOnChanges(changes: SimpleChanges)
Parameters :
Name Type Optional
changes SimpleChanges no
Returns : void
ngOnDestroy
ngOnDestroy()
Returns : void
ngOnInit
ngOnInit()
Returns : void
Private updateTranslations
updateTranslations()
Returns : void

Properties

Public _configuration
_configuration: FooterConfiguration
Type : FooterConfiguration
Private _islands
_islands: string[]
Type : string[]
Default value : []
Private cellSubject
cellSubject:
Default value : new ReplaySubject<IgxGridCell>(1)
Private columnSubject
columnSubject:
Default value : new ReplaySubject<Column>(1)
Private localstorageExportSettings
localstorageExportSettings: LocalStorageEntry
Type : LocalStorageEntry
menuTrigger
menuTrigger:
Decorators : ViewChild
showFavoriteInteractions
showFavoriteInteractions: boolean
Type : boolean
Default value : true
stateObservable
stateObservable: Observable<DataListState>
Type : Observable<DataListState>
Public tableConfiguration
tableConfiguration:
translations
translations: literal type
Type : literal type
visible
visible: boolean
Type : boolean
Default value : false

Accessors

refreshButtons
setrefreshButtons(value: )

Deprecated: feature is now removed.

Parameters :
Name Optional
value no
Returns : void
configuration
getconfiguration()
setconfiguration(configuration: any)

Data-list configuration

Parameters :
Name Type Optional
configuration any no
Returns : void
column
setcolumn(column: )
Parameters :
Name Optional
column no
Returns : void
cell
setcell(cell: )
Parameters :
Name Optional
cell no
Returns : void
import {
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  EventEmitter,
  Inject,
  Input,
  OnChanges,
  OnDestroy,
  OnInit,
  Output,
  SimpleChanges,
  ViewChild,
} from "@angular/core";

import { Observable, of, ReplaySubject } from "rxjs";
import { first } from "rxjs/operators";
import { Column, SelectionParams } from "../../interfaces/list.interfaces";
import { Selectors } from "../../../components/app-context/api";
import { AppContext } from "../../../components/app-context/app.context";
import { MatMenuTrigger } from "@angular/material/menu";
import { DialogService } from "../../../components/dialog/dialog.service";
import { DataListExportComponent } from "../data-list-dialog-component/data-list-export.component";
import {
  DeletionMode,
  Scope,
} from "../../../components/local-storage/local-storage-constants";
import {
  LocalStorageEntry,
  LocalStorageService,
} from "../../../components/local-storage/local-storage.service";
import { getOrDefault } from "../../widget.configuration";
import {
  deepCopy,
  firstNonNull,
  UtilService,
} from "../../../components/util/util.service";
import { DataListActionsService } from "./data-list-actions.service";
import { DataListApi } from "../data-list.api";
import {
  NmModuleConfiguration,
  NOVOMIND_MODULE_CONFIGURATION,
} from "../../../../nm.module-descriptor";
import { ValidationService } from "../../../components/validation/validation.service";
import { CustomNotificationService } from "../../../components/notification/customnotification.service";
import { TranslateService } from "@ngx-translate/core";
import { DataListComponent } from "../data-list-component/data-list.component";
import { Attributes } from "../../../components/edit-attribute";
import { IgxGridComponent } from "@infragistics/igniteui-angular";
import { IgxGridCell } from "@infragistics/igniteui-angular";

export interface DataListState {
  /**
   * Number of rows in data-list
   */
  total: number;

  /**
   * Number of selected rows of data-list
   */
  selected: number;

  /**
   * Selected rows of data-list
   */
  selection?: SelectionParams;

  /**
   * Data url without paging
   */
  link?: string;
}

const DEFAULT_CLIPBOARD_OPTIONS = {
  enabled: true,
  copyHeaders: false,
  copyFormatters: false,
  separator: "\t",
};

const DEFAULT_ACTION_CONFIGURATION = {
  hidden: false,
  selectors: { target: "default-data-list-footer" },
  showAsButton: 0,
  showFavoriteInteractions: true,
  clipboardOptions: DEFAULT_CLIPBOARD_OPTIONS,
};

const CONDITION_HAS_SELECTION = [
  (config, param) => param && param.selected > 0,
];

const CONDITION_HAS_CONTENT = [
  (config, param) => {
    return param && (param.total > 0 || param?.grid?.grid?.data?.length > 0);
  },
];

const CONDITION_IS_EDITABLE_ATTRIBUTE = [
  (config, param) => {
    if (
      !param.column ||
      !param.column.editable ||
      param.column.type !== "attribute"
    ) {
      return false;
    }
    return true;
  },
];

const DEFAULT_TRANSLATIONS = {
  "no-content": "data-list.footer.no-content",
  "no-selection": "data-list.footer.no-selection",
  selection: "data-list.footer.selection",
};

const LOCAL_STORAGE_EXPORT_SETTINGS = "data-list-export-settings";

const APPLY_FOR_SELECTORS = { "@toolbox-config": "apply-for" };

/**
 * Translation keys for message types
 */
interface TranslationHash {
  /**
   * Translation map using the message type as the key and a translation label or description as the value.
   * (message types: no-content, no-selection or selection)
   */
  [key: string]: string;
}

/**
 * Single translation key that is scoped to a message type and possibly a view mode or a data type
 */
interface TranslationDefinition {
  /**
   * Translation message type identifier (options: no-content, no-selection or selection)
   */
  type: "no-content" | "no-selection" | "selection";

  /**
   * View mode identifier to apply translation to
   */
  viewMode?: string;

  /**
   * Data type identifier to apply translation to
   */
  dataType?: string;

  /**
   * The translation label or description
   */
  message: string;
}

/**
 * Definition of options when copying
 * data list cells
 */
interface ClipboardOptions {
  enabled: boolean;
  copyHeaders: boolean;
  copyFormatters: boolean;
  separator: string;
}

/**
 * Interface defining the footer configuration
 */
interface FooterConfiguration {
  /**
   * Either, a json object, containing properties for the available translations,
   * or an array of objects, each defining the message for a possible message type and data type or view mode
   * combination.
   */
  translations: TranslationHash | TranslationDefinition[];

  /**
   * Hides/Shows the footer
   */
  hidden: boolean;

  /**
   * Selector configuration to look up menu entries
   */
  selectors: Selectors;

  /**
   * Selector configuration to look up actions entries
   */
  buttonSelectors: Selectors;

  /**
   * Number of interactions defined by `selectors` that should be displayed as buttons
   * (instead of as part of the menu)
   */
  showAsButton: number;

  /**
   * Shows/Hides the footer favorites list
   */
  showFavoriteInteractions: boolean;

  /**
   * JSON object containing options to use when copying list cells
   */
  clipboardOptions?: ClipboardOptions;
}

@Component({
  selector: "nm-default-data-list-footer",
  templateUrl: "./default-data-list-footer.component.html",
  styleUrls: ["./default-data-list-footer.component.scss"],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DefaultDataListFooterComponent
  implements OnInit, OnDestroy, OnChanges
{
  @ViewChild(MatMenuTrigger) menuTrigger;

  /**
   * Deprecated: feature is now removed.
   */
  @Input()
  public set refreshButtons(value) {
    // No need to do anything, angular will automtically trigger change detection when inputs change
  }

  /**
   * Service that allows to directly interact with the data-list
   */
  @Input()
  public api: DataListApi;

  /**
   * Data-list configuration
   */
  @Input()
  public set configuration(configuration: any) {
    this.tableConfiguration = configuration;
    this._configuration = Object.assign(
      {},
      DEFAULT_ACTION_CONFIGURATION,
      configuration.footer || {}
    );
    const clipboard =
      configuration.footer && configuration.footer.clipboardOptions
        ? configuration.footer.clipboardOptions
        : {};
    this._configuration.clipboardOptions = Object.assign(
      {},
      DEFAULT_CLIPBOARD_OPTIONS,
      clipboard
    );

    if (configuration.islands) {
      let islands = <any[]>configuration.islands;
      this._islands = islands.map((island) => island.key);
    }
  }

  public get configuration(): any {
    return this._configuration;
  }

  /**
   * Data-list all selected items
   */
  @Input("selected-items")
  selectedItems: Observable<SelectionParams>;

  /**
   * Number of entries (rows) in the data-list
   */
  @Input()
  total: Observable<number>;

  /**
   * Data-list grid instance
   */
  @Input()
  grid: DataListComponent;

  /**
   * Data-list data url without paging
   */
  @Input()
  link: Observable<string>;

  /**
   * Emits export settings
   */
  @Output()
  export = new EventEmitter<any>();

  /**
   * Data-list selected view mode
   */
  @Input()
  viewMode: string;

  /**
   * Data-list selected data type
   */
  @Input()
  dataType: string;

  @Input()
  public set column(column: Column) {
    this.columnSubject.next(column);
  }

  @Input()
  public set cell(cell) {
    this.cellSubject.next(cell);
  }

  public _configuration: FooterConfiguration;
  public tableConfiguration;
  private _islands: string[] = [];

  private columnSubject = new ReplaySubject<Column>(1);
  private cellSubject = new ReplaySubject<IgxGridCell>(1);

  visible: boolean = false;
  showFavoriteInteractions: boolean = true;
  stateObservable: Observable<DataListState>;
  translations: { [key: string]: string };

  private localstorageExportSettings: LocalStorageEntry;

  constructor(
    private appContext: AppContext,
    protected localStorageService: LocalStorageService,
    private dialog: DialogService,
    private dataListActionsService: DataListActionsService,
    @Inject(NOVOMIND_MODULE_CONFIGURATION)
    private moduleConfig: NmModuleConfiguration,
    private validationService: ValidationService,
    private notificationService: CustomNotificationService,
    private translateService: TranslateService,
    private cdr: ChangeDetectorRef
  ) {}

  ngOnChanges(changes: SimpleChanges): void {
    if (changes.configuration || changes.viewMode || changes.dataType) {
      this.updateTranslations();
    }

    //Wait till the api is here before initializing
    if (changes.api && this.api) {
      this.stateObservable = this.dataListActionsService.mapSelectionState(
        this.total,
        this.selectedItems,
        this.link,
        this._islands,
        this.columnSubject,
        this.cellSubject,
        this.grid,
        this.api,
        this.export
      );
    }
  }

  ngOnInit(): void {
    this.initializeDefaultActions();
    this.visible =
      !this._configuration.hidden &&
      (this.tableConfiguration.rowSelectable || true);
    this.showFavoriteInteractions =
      this._configuration.showFavoriteInteractions;

    this.localstorageExportSettings =
      this.localStorageService.getLocalStorageEntry(
        LOCAL_STORAGE_EXPORT_SETTINGS,
        Scope.GLOBAL,
        DeletionMode.NEVER
      );

    if (
      !this.localstorageExportSettings.exists() ||
      !this.localstorageExportSettings.value ||
      this.localstorageExportSettings.value == "undefined"
    ) {
      this.localstorageExportSettings.value = JSON.stringify({
        assetFormat: "thumbnail",
        exportFormat: "CSV",
        csvSeparator: ";",
        csvEncoding: "ISO-8859-15",
        csvHeaders: true,
        onlySelectedRows: true,
      });
    }
  }

  private updateTranslations() {
    let translations = (this.translations = Object.assign(
      {},
      DEFAULT_TRANSLATIONS
    ));

    if (Array.isArray(this._configuration.translations)) {
      let definitions = this._configuration.translations;

      Object.keys(DEFAULT_TRANSLATIONS).forEach((type) => {
        let definition = definitions.find((def) => {
          return (
            def.type === type &&
            (def.dataType === this.dataType ||
              (def.dataType == null && this.dataType == null)) &&
            (def.viewMode === this.viewMode ||
              (def.viewMode == null && this.viewMode == null))
          );
        });

        if (definition) {
          translations[type] = definition.message;
        }
      });
    } else {
      Object.assign(translations, this._configuration.translations);
    }

    this.translations = translations;
    this.cdr.markForCheck();
  }

  private initializeDefaultActions() {
    this.appContext.browserContext.addIfNotExists(
      {
        target: [
          "default-data-list-footer",
          "default-data-list-footer-clipboard",
        ],
        content: "*",
        action: "copySelectionToClipBoard",
      },
      {
        type: "action",
        menu: "menu.clipboard",
        order: -300,
        description: "placeholder.copySelectionToClipBoard",
        name: "copySelectionToClipBoard",
        conditions: CONDITION_HAS_SELECTION,
        onClick: (configuration, params) => {
          copySelectionToClipBoard(
            params,
            this._configuration.clipboardOptions
          );
        },
      }
    );

    this.appContext.browserContext.addIfNotExists(
      {
        target: [
          "default-data-list-footer",
          "default-data-list-footer-clipboard",
        ],
        content: "*",
        action: "copySelectedCellsToClipBoard",
      },
      {
        type: "action",
        menu: "menu.clipboard",
        order: -300,
        description: "placeholder.copySelectedCellsToClipBoard",
        name: "copySelectedCellsToClipBoard",
        conditions: CONDITION_HAS_SELECTION,
        onClick: (configuration, params) => {
          copySelectedCellsToClipBoard(params);
        },
      }
    );

    this.appContext.browserContext.addIfNotExists(
      {
        target: [
          "default-data-list-footer",
          "default-data-list-footer-clipboard",
        ],
        content: "*",
        action: "copyColumnCellsToClipBoard",
      },
      {
        type: "action",
        menu: "menu.clipboard",
        order: -300,
        description: "placeholder.copyColumnCellsToClipBoard",
        name: "copyColumnCellsToClipBoard",
        conditions: CONDITION_HAS_CONTENT,
        onClick: (configuration, params) => {
          copySeletedColumnCellsToClipBoard(
            params,
            this._configuration.clipboardOptions
          );
        },
      }
    );

    this.appContext.browserContext.addIfNotExists(
      {
        target: [
          "default-data-list-footer",
          "default-data-list-footer-clipboard",
        ],
        content: "*",
        action: "copyAllToClipBoard",
      },
      {
        type: "action",
        menu: "menu.clipboard",
        order: -200,
        description: "placeholder.copyAllToClipBoard",
        name: "copyAllToClipBoard",
        conditions: CONDITION_HAS_CONTENT,
        onClick: (configuration, params) => {
          const options = deepCopy(this._configuration.clipboardOptions);
          options.copyHeaders = false;
          copyAllToClipBoard(params, options);
        },
      }
    );

    this.appContext.browserContext.addIfNotExists(
      {
        target: [
          "default-data-list-footer",
          "default-data-list-footer-clipboard",
        ],
        content: "*",
        action: "copyAllWithHeaderToClipBoard",
      },
      {
        type: "action",
        menu: "menu.clipboard",
        order: -200,
        description: "placeholder.copyAllWithHeaderToClipBoard",
        name: "copyAllWithHeaderToClipBoard",
        conditions: CONDITION_HAS_CONTENT,
        onClick: (configuration, params) => {
          const options = deepCopy(this._configuration.clipboardOptions);
          options.copyHeaders = true;
          copyAllToClipBoard(params, options);
        },
      }
    );

    this.appContext.browserContext.addIfNotExists(
      {
        target: ["default-data-list-footer", "default-data-list-footer-export"],
        content: "*",
        action: "export-csv-or-excel",
      },
      {
        type: "action",
        menu: "menu.export",
        order: -100,
        description: "button.export",
        name: "export",
        conditions: CONDITION_HAS_CONTENT,
        onClick: (configuration, params) => {
          exportList(
            this.dialog,
            params,
            this.localstorageExportSettings,
            this.moduleConfig
          );
        },
      }
    );

    this.appContext.browserContext.addIfNotExists(
      {
        target: [
          "default-data-list-footer",
          "default-data-list-footer-selection",
        ],
        content: "*",
        action: "invertSelection",
      },
      {
        order: -400,
        type: "action",
        description: "placeholder.invertSelection",
        name: "invertSelection",
        conditions: CONDITION_HAS_SELECTION,
        onClick: (configuration, params) => {
          return invertSelection(params);
        },
      }
    );

    this.appContext.browserContext.addIfNotExists(
      {
        target: ["default-data-list-context-menu"],
        content: "*",
        action: "select.same.values",
      },
      {
        type: "action",
        order: -400,
        description: "button.select.same.values",
        name: "export",
        onClick: (configuration, params) => {
          selectSameValues(configuration, params);
        },
      }
    );

    this.appContext.browserContext.addIfNotExists(
      {
        target: "edit-data-list-context-menu",
        content: "*",
        action: "apply-for-all",
      },
      {
        type: "action",
        order: -50,
        description: "button.apply.for.all",
        name: "export",
        conditions: CONDITION_IS_EDITABLE_ATTRIBUTE,
        onClick: (config, params) => {
          applyForAll(
            config,
            params,
            this.notificationService,
            this.translateService,
            this.appContext,
            this.validationService
          );
        },
      }
    );

    this.appContext.browserContext.addIfNotExists(
      {
        target: "edit-data-list-context-menu",
        content: "*",
        action: "apply-for-selection",
      },
      {
        type: "action",
        order: -40,
        description: "button.apply.for.selection",
        name: "export",
        conditions: CONDITION_IS_EDITABLE_ATTRIBUTE,
        onClick: (configuration, params) => {
          const applyingRowData = params.cell.row.rowData;
          const applyingAttribute =
            applyingRowData[Attributes.toSourceField(params.column.field)];
          if (
            !params.column ||
            !params.column.field ||
            (applyingAttribute.errors && applyingAttribute.errors.length > 0)
          ) {
            this.notificationService.error(
              this.translateService.instant("ERROR"),
              this.translateService.instant(
                "message.default-data-list-footer.error"
              )
            );
            return;
          }
          if (params.column.type !== "attribute") {
            console.error(
              "Apply for selection is not available for non attribute editable fields"
            );
          }
          this.appContext.browserContext
            .subscribe(APPLY_FOR_SELECTORS)
            .pipe(first())
            .subscribe((selectors) => {
              if (selectors.length !== 0 && selectors[0].horizontal) {
                applyHorizontally(params, this.validationService, true);
              } else {
                applyForSelectedVertically(params, this.validationService);
              }
            });
        },
      }
    );
  }

  ngOnDestroy(): void {}
}

//Below this are all functions registered for the table footer.
// You are not allowed to use the this context in those functions, since all you need should be passed via the DataListActionParameter
function applyForAll(
  configuration,
  params: DataListActionParameter,
  notificationService: CustomNotificationService,
  translateService: TranslateService,
  appContext: AppContext,
  validationService: ValidationService
) {
  const applyingRowData = params.cell.row.rowData;
  const applyingAttribute =
    applyingRowData[Attributes.toSourceField(params.column.field)];
  if (
    !params.column ||
    !params.column.field ||
    (applyingAttribute.errors && applyingAttribute.errors.length > 0)
  ) {
    notificationService.error(
      translateService.instant("ERROR"),
      translateService.instant("default-data-list-footer.error.message")
    );
    return;
  }
  if (params.column.type !== "attribute") {
    console.error(
      "Apply for selection is not available for non attribute editable fields"
    );
  }
  appContext.browserContext
    .subscribe(APPLY_FOR_SELECTORS)
    .pipe(first())
    .subscribe((selectors) => {
      if (selectors.length !== 0 && selectors[0].horizontal) {
        applyHorizontally(params, validationService, false);
      } else {
        applyForAllVertically(params, validationService);
      }
    });
}

function selectSameValues(configuration, params: DataListActionParameter) {
  if (!params.column || !params.column.field) {
    return;
  }
  const applyingRowData = params.cell.row.rowData;
  const field = Attributes.toSourceField(params.column.field);
  const applyingAttribute = applyingRowData[field];
  const applyingValue = applyingAttribute ? applyingAttribute.source : null;
  const primaryKey = params.grid.primaryKey(params.grid.configuration);

  params.grid.listService.data.pipe(first()).subscribe((data) => {
    const newSelections = [];
    data.forEach((entry) => {
      const key = entry[primaryKey];
      const rowData = entry[field];
      if (applyingValue === null) {
        if (!rowData) {
          newSelections.push(key);
        }
        return;
      }
      if (!rowData) {
        return;
      }
      if (params.column.type === "attribute") {
        if (rowData.value === applyingAttribute.value) {
          newSelections.push(key);
        }
      } else {
        if (rowData === applyingAttribute) {
          newSelections.push(key);
        }
      }
    });
    params.grid.grid.deselectAllRows();
    params.grid.grid.selectRows(newSelections);
    // @ts-ignore ignites interface on what it really needs is wrong - tsignore this
    params.grid.grid.rowSelected.emit({
      newSelection: params.grid.grid.selectedRows,
    });
  });
}

function applyForAllVertically(
  params: DataListActionParameter,
  validationService: ValidationService
) {
  const applyingRowData = params.cell.row.rowData;
  const valueField = params.column.field;
  const sourceField = Attributes.toSourceField(valueField);
  const applyingAttribute = applyingRowData[sourceField];
  const applyingValue = applyingAttribute.source;
  //TODO This will make problems for nested tables in the hierachy grid
  const primaryKey = params.grid.primaryKey(params.grid.configuration);
  params.grid.listService.data.pipe(first()).subscribe((data) => {
    const keysToUpdate = [];
    data.forEach((entry) => {
      const key = entry[primaryKey];
      if (key === applyingRowData[primaryKey]) {
        return;
      }

      const rowAttribute = deepCopy(entry[sourceField]);
      if (!rowAttribute) {
        return;
      }
      if (rowAttribute["read-only"]) {
        return;
      }

      rowAttribute.source = applyingValue;

      const eventAttributeData = {
        [applyingAttribute.identifier]: rowAttribute,
      };
      params.api.fireEditedRowsEvent(key, entry, null, eventAttributeData);

      const row = params.grid.grid.getRowByKey(key);
      // no row found? then the data is outside the current view port
      // we only need to fire the correct event
      if (!row) {
        entry[sourceField] = rowAttribute;
        return;
      }

      updateRowOrCell(rowAttribute, row, valueField, params, validationService);
      keysToUpdate.push(key);
    });

    params.grid.validateList(null, null);
    params.api.forceReRender(keysToUpdate);
  });
}

function applyHorizontally(
  params: DataListActionParameter,
  validationService: ValidationService,
  onlySelectedCells: boolean
) {
  const row = params.cell.row;
  const applyingRowData = row.rowData;
  let selectedItemsIdentifier = [];

  if (onlySelectedCells) {
    selectedItemsIdentifier = getSelectedItemsIdentifier(params.grid.grid);
  }
  const applyingAttribute =
    applyingRowData[Attributes.toSourceField(params.column.field)];
  const applyingValue = applyingAttribute.source;

  Attributes.rowAttributes(applyingRowData).forEach((attribute) => {
    if (!attribute || Attributes.isReadOnly(attribute)) {
      return;
    }

    if (
      onlySelectedCells &&
      !selectedItemsIdentifier.includes(attribute.identifier)
    ) {
      return;
    }

    attribute.source = deepCopy(applyingValue);

    const valueField = Attributes.toValueField(attribute.identifier);
    const cell = params.grid.grid.getCellByColumn(row.index, valueField);
    if (cell) {
      updateRowOrCell(attribute, row, valueField, params, validationService);
    }
    const eventAttributeData = {
      [attribute.identifier]: deepCopy(attribute),
    };

    params.api.fireEditedRowsEvent(
      applyingRowData.attributeId,
      applyingRowData,
      null,
      eventAttributeData
    );
  });

  const primaryKey =
    applyingRowData[params.grid.primaryKey(params.grid.configuration)];

  params.grid.validateList(null, null);
  params.api.forceReRender([primaryKey]);
}

function getSelectedItemsIdentifier(grid: IgxGridComponent): string[] {
  const columnFields = getColumnFields(grid);
  const selections: string[] = [];

  grid.getSelectedRanges().forEach((selectedRange) => {
    columnFields
      .slice(
        <number>selectedRange.columnStart,
        <number>selectedRange.columnEnd + 1
      )
      .forEach((field) => selections.push(field.split("#")[0]));
  });

  return selections;
}

function applyForSelectedVertically(
  params: DataListActionParameter,
  validationService: ValidationService
) {
  const applyingRowData = params.cell.row.rowData;
  const valueField = params.column.field;
  const sourceField = Attributes.toSourceField(valueField);
  const applyingAttribute = applyingRowData[sourceField];
  const applyingValue = applyingAttribute.source;
  const keysToUpdate = [];
  const primaryKey = params.grid.primaryKey(params.grid.configuration);

  const missingRows = [];
  params.grid.grid.selectedRows.forEach((key) => {
    if (key === applyingRowData[primaryKey]) {
      return;
    }

    const gridRow = params.grid.grid.getRowByKey(key);
    if (!gridRow) {
      missingRows.push(key);
      return;
    }

    const row = gridRow.rowData;
    const rowAttribute = deepCopy(row[sourceField]);
    if (rowAttribute["read-only"]) {
      return;
    }

    const eventAttributeData = {
      [applyingAttribute.identifier]: rowAttribute,
    };
    params.api.fireEditedRowsEvent(key, row, null, eventAttributeData);

    rowAttribute.source = applyingValue;
    keysToUpdate.push(key);

    updateRowOrCell(
      rowAttribute,
      gridRow,
      valueField,
      params,
      validationService
    );
  });

  if (missingRows.length !== 0) {
    params.grid.listService.data.pipe(first()).subscribe((rows) => {
      const rowsToUpdate = rows.filter(
        (entry) => missingRows.indexOf(entry[primaryKey]) !== -1
      );
      rowsToUpdate.forEach((row) => {
        const key = row[primaryKey];
        const rowAttribute = row[sourceField];
        rowAttribute.source = deepCopy(applyingValue);

        row[sourceField] = rowAttribute;

        validationService.validateAttribute(rowAttribute);

        const eventAttributeData = {
          [applyingAttribute.identifier]: deepCopy(applyingAttribute),
        };
        params.api.fireEditedRowsEvent(key, row, null, eventAttributeData);
      });
    });
  }
  params.grid.validateList(null, null);
  params.api.forceReRender(keysToUpdate);
}

/**
 * `field` must be a #value field to ensure, that we find the corresponding cell
 *
 * @param rowAttribute
 * @param row
 * @param field #value field for the attribute to update
 * @param params
 * @param validationService
 */
export function updateRowOrCell(
  rowAttribute,
  row,
  field: string,
  params: DataListActionParameter,
  validationService: ValidationService
) {
  // validate newly updated attribute
  validationService.validateAttribute(rowAttribute);
  if (params.grid.configuration.rowEditable) {
    // Trigger edit event
    const rowData = deepCopy(row.rowData);
    rowData[field] = rowAttribute;

    row.update(rowData);
  } else {
    let cell = params.grid.grid.getCellByColumn(row.index, field);
    if (cell) {
      Attributes.updateCell(cell, rowAttribute);
    }
  }
}

function invertSelection(params: DataListActionParameter) {
  params.grid.grid.clearCellSelection();
  if (params.grid.grid.selectedRows.length > 0) {
    let selectedRowIds = params.grid.grid.selectedRows;
    params.grid.grid.selectAllRows();
    params.grid.grid.deselectRows(selectedRowIds);
  } else {
    params.grid.grid.selectAllRows();
  }
  params.grid.grid.cdr.detectChanges();
  // @ts-ignore it looks like the ignite api is demanding values that are not needed - ts ignore this so
  params.grid.grid.rowSelected.emit({
    newSelection: params.grid.grid.selectedRows,
  });
}

function copySelectedCellsToClipBoard(params: DataListActionParameter) {
  const columnFields = getColumnFields(params.grid.grid);
  const primaryKey = params.grid._primaryKey;
  const selections = new Map<number, string[]>();
  const selectedValues = [];

  params.grid.grid.getSelectedRanges().forEach((selectedRange) => {
    let fields = columnFields.slice(
      <number>selectedRange.columnStart,
      <number>selectedRange.columnEnd + 1
    );
    for (
      let rowIndex = selectedRange.rowStart;
      rowIndex <= selectedRange.rowEnd;
      rowIndex++
    ) {
      if (selections.has(rowIndex)) {
        let mapFields = selections.get(rowIndex);
        fields.forEach((field) => mapFields.push(field));
      } else {
        selections.set(rowIndex, fields);
      }
    }
  });

  selections.forEach((fields, rowIndex) => {
    const gridRow = params.grid.grid.getRowByIndex(rowIndex).rowData;
    let valueString = `${
      selections.size > 1 ? gridRow[primaryKey] + "->\t" : ""
    } ${fields.map((field) => mapFieldValue(gridRow[field])).join("\t")}`;
    selectedValues.push(valueString);
  });

  UtilService.copyTextToClipboard(selectedValues.join("\n"));
}

function getColumnFields(grid): string[] {
  return grid.columns
    .filter((column) => !column.hidden)
    .map((column) => column.field);
}

function copySelectionToClipBoard(
  params: DataListActionParameter,
  options: ClipboardOptions
) {
  if (params.grid.isEagerLoading) {
    let primaryKey = params.grid.grid.primaryKey;
    let data = params.grid.grid.data;
    let filtered = data.filter((row) =>
      params.grid.grid.selectedRows.includes(row[primaryKey].toString())
    );
    copyToClipboard(filtered, params, options);
  } else {
    let filtered = params.grid.listService.cachedData.filter(
      (data) => data.selected
    );
    copyToClipboard(filtered, params, options);
  }
}

function copySeletedColumnCellsToClipBoard(
  params: DataListActionParameter,
  options: ClipboardOptions
) {
  const data = params.grid.isEagerLoading
    ? params.grid.grid.data
    : params.grid.listService.cachedData.filter((cd) => !cd.cache);

  const columns = [params.column.field];
  copyColumnsToClipboard(data, columns, params, options);
}

function copyAllToClipBoard(
  params: DataListActionParameter,
  options: ClipboardOptions
) {
  let data = params.grid.isEagerLoading
    ? params.grid.grid.data
    : params.grid.listService.cachedData.filter((cd) => !cd.cache);
  copyToClipboard(data, params, options);
}

function copyToClipboard(
  data,
  params: DataListActionParameter,
  options: ClipboardOptions
) {
  // only export visible columns, that have a set "field" property
  // and actually exist in the data model (i.e., if only some cells/columns have been selected)
  let columns = params.grid.grid.visibleColumns
    .map((col) => col.field)
    .filter((field) => field && field !== "")
    .filter(
      (field) =>
        data &&
        data[0] &&
        (data[0].hasOwnProperty(field) ||
          Attributes.hasRowAttribute(data[0], field))
    );

  copyColumnsToClipboard(data, columns, params, options);
}

function copyColumnsToClipboard(
  data,
  columns,
  params: DataListActionParameter,
  options: ClipboardOptions
) {
  let clipboardtext = "";
  if (options.copyHeaders) {
    let columnsHeaders = params.grid.grid.visibleColumns
      .map((col) => col.header)
      .filter((header) => header && header !== "")
      .join(options.separator);

    clipboardtext = columnsHeaders + "\n";
  }

  clipboardtext =
    clipboardtext +
    data
      .map((datarow) =>
        columns
          .map((field) => datarow[field])
          .map((value) => mapFieldValue(value))
          .join(options.separator)
      )
      .join("\n");

  UtilService.copyTextToClipboard(clipboardtext);
}

function mapFieldValue(value: any): string {
  if (!value && value !== 0) {
    return "";
  } else if (Array.isArray(value) && value.length > 0) {
    return value[0].hasOwnProperty("exportValue")
      ? value.map((v) => v.exportValue).join(", ")
      : value.join(", ");
  } else if (typeof value === "object") {
    return value.hasOwnProperty("exportValue")
      ? String(value.exportValue)
      : value.toString();
  } else {
    return value.toString().trim();
  }
}

function exportList(
  dialog: DialogService,
  params: DataListActionParameter,
  localstorageExportSettings: LocalStorageEntry,
  moduleConfig: NmModuleConfiguration
) {
  let dialogRef = dialog.open(DataListExportComponent, {
    autoFocus: true,
    width: "450px",
    height: "530px",
  });

  let exportSettings;

  if (localstorageExportSettings.exists()) {
    exportSettings = JSON.parse(localstorageExportSettings.value);
  }

  dialogRef.componentInstance.showDownload = firstNonNull(
    params.grid.configuration["show-download-button"],
    moduleConfig["nm-default-data-list-footer.show-download-button"],
    true
  );
  dialogRef.componentInstance.showExport = firstNonNull(
    params.grid.configuration["show-export-button"],
    moduleConfig["nm-default-data-list-footer.show-export-button"],
    true
  );
  dialogRef.componentInstance.exportFormat = getOrDefault(
    exportSettings.exportFormat,
    "CSV"
  );
  dialogRef.componentInstance.assetFormat = getOrDefault(
    exportSettings.assetFormat,
    "thumbnail"
  );
  dialogRef.componentInstance.csvSeparator = getOrDefault(
    exportSettings.csvSeparator,
    ";"
  );
  dialogRef.componentInstance.csvEncoding = getOrDefault(
    exportSettings.csvEncoding,
    "ISO-8859-15"
  );
  dialogRef.componentInstance.csvHeaders = getOrDefault(
    exportSettings.csvHeaders,
    true
  );
  dialogRef.componentInstance.onlySelectedRows = getOrDefault(
    exportSettings.onlySelectedRows,
    false
  );
  dialogRef.componentInstance.showExportAssetFormats = getOrDefault(
    params.grid.configuration.showExportAssetFormats,
    true
  );
  dialogRef.componentInstance.showCsvHeaders = getOrDefault(
    params.grid.configuration.showCsvHeaders,
    true
  );

  dialogRef.afterClosed().subscribe((data) => {
    if (data) {
      localstorageExportSettings.value = JSON.stringify(data);
      params.export.emit(data);
    }
  });
}

export interface DataListActionParameter {
  grid: DataListComponent;
  api: DataListApi;
  export: EventEmitter<any>;
  [key: string]: any;
}
<div class="nm-data-list__footer" *ngIf="visible">
  <ng-container *ngIf="stateObservable | async as state">
    <nm-interaction-menu-bar
      [menu]="_configuration.selectors"
      [buttons]="
        _configuration.buttonSelectors || _configuration.showAsButton || 0
      "
      [param]="state"
    >
      <div
        slot="between"
        class="nm-data-list__footerSelectionInfo"
        *ngIf="state.total && !tableConfiguration.hideFooterCounter"
      >
        <ng-container *ngIf="state.total == 0">
          {{ translations["no-content"] | translate }}
        </ng-container>

        <ng-container
          *ngIf="
            state.total > 0 &&
            state.selected == 0 &&
            state.childrenSelected == 0
          "
        >
          {{ translations["no-selection"] | translate: state }}
        </ng-container>
        <ng-container
          *ngIf="
            state.total > 0 &&
            (state.selected > 0 || state.childrenSelected > 0)
          "
        >
          {{ translations["selection"] | translate: state }}
        </ng-container>
      </div>
    </nm-interaction-menu-bar>

    <nm-container
      *ngFor="let component of configuration.components"
      [configuration]="component | widgetFor: configuration"
      [parent]="component"
      [id]="component"
    >
    </nm-container>

    <nm-favorite-interactions
      *ngIf="showFavoriteInteractions"
      [identifier]="
        tableConfiguration.identifier
          ? tableConfiguration.identifier
          : tableConfiguration.title
      "
      [param]="state"
    >
    </nm-favorite-interactions>
  </ng-container>
</div>
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""