File

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

Index

Properties

Properties

link
link: string
Type : string
Optional

Data url without paging

selected
selected: number
Type : number

Number of selected rows of data-list

selection
selection: SelectionParams
Type : SelectionParams
Optional

Selected rows of data-list

total
total: number
Type : number

Number of rows in data-list

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;
}

results matching ""

    No results matching ""