UNPKG

@syncfusion/ej2-buttons

Version:

A package of feature-rich Essential JS 2 components such as Button, CheckBox, RadioButton and Switch.

1,253 lines (1,145 loc) 60.3 kB
import { Component, NotifyPropertyChanges, INotifyPropertyChanged, Property, append, isNullOrUndefined, remove } from '@syncfusion/ej2-base'; import { removeClass, KeyboardEventArgs, rippleEffect, closest, MouseEventArgs } from '@syncfusion/ej2-base'; import { Draggable, DragEventArgs } from '@syncfusion/ej2-base'; import { EventHandler, detach, EmitType, Event, addClass, compile} from '@syncfusion/ej2-base'; import { ChipListModel } from './chip-list-model'; import { ChipModel } from './chip'; export const classNames: ClassNames = { chipSet: 'e-chip-set', chip: 'e-chip', avatar: 'e-chip-avatar', text: 'e-chip-text', icon: 'e-chip-icon', delete: 'e-chip-delete', deleteIcon: 'e-dlt-btn', multiSelection: 'e-multi-selection', singleSelection: 'e-selection', active: 'e-active', chipWrapper: 'e-chip-avatar-wrap', iconWrapper: 'e-chip-icon-wrap', focused: 'e-focused', disabled: 'e-disabled', rtl: 'e-rtl', template: 'e-chip-template', chipList: 'e-chip-list', customIcon: 'e-icons', chipDrag: 'e-chip-drag', dragAndDrop: 'e-drag-and-drop', dropRestricted: 'e-error-treeview', cloneChip: 'e-clone-chip', dragIndicator: 'e-drag-indicator' }; /** * ```props * index :- Refers to the position of the selected chip in the list of chips * value :- Refers to the underlying data value associated with the selected chip. * text :-Refers to the displayed text on the selected chip. * ``` */ export type selectionType = 'index' | 'value' | 'text'; /** * ```props * Single :- Allows the user to select single chip at the same time. * Multiple :- Allows the user to select multiple chips at the same time. * None :- Chips are displayed as read-only. * ``` */ export type Selection = 'Single' | 'Multiple' | 'None'; export interface ClassNames { chipSet: string; chip: string; avatar: string; text: string; icon: string; delete: string; deleteIcon: string; multiSelection: string; singleSelection: string; active: string; chipWrapper: string; iconWrapper: string; focused: string; disabled: string; rtl: string; template: string; chipList: string; customIcon: string; chipDrag: string; dragAndDrop: string; dropRestricted: string; cloneChip: string; dragIndicator: string; } interface ChipFields { text: string; cssClass: string; avatarText: string; avatarIconCss: string; htmlAttributes: { [key: string]: string }; leadingIconCss: string; trailingIconCss: string; enabled: boolean; value: string | number | null; leadingIconUrl: string; trailingIconUrl: string; template: string | Function; } interface EJ2Instance extends HTMLElement { // eslint-disable-next-line ej2_instances: Object[]; } export interface SelectedItems { /** * It denotes the selected items text. */ texts: string[]; /** * It denotes the selected items index. */ Indexes: number[]; /** * It denotes the selected items data. */ data: string[] | number[] | ChipModel[]; /** * It denotes the selected items element. */ elements: HTMLElement[]; } export interface SelectedItem { /** * It denotes the selected item text. */ text: string; /** * It denotes the selected item index. */ index: number; /** * It denotes the selected item data. */ data: string | number | ChipModel; /** * It denotes the selected item element. */ element: HTMLElement; } export interface ClickEventArgs { /** * It denotes the clicked item text. */ text: string; /** * It denotes the clicked item index. */ index?: number; /** * It denotes the clicked item data. */ data: string | number | ChipModel; /** * It denotes the clicked item element. */ element: HTMLElement; /** * It denotes whether the clicked item is selected or not. */ selected?: boolean; /** * It denotes whether the item can be clicked or not. */ cancel: boolean; /** * It denotes the event. */ event: MouseEventArgs | KeyboardEventArgs; } export interface DeleteEventArgs { /** * It denotes the deleted item text. */ text: string; /** * It denotes the deleted item index. */ index: number; /** * It denotes the deleted item data. */ data: string | number | ChipModel; /** * It denotes the deleted Item element. */ element: HTMLElement; /** * It denotes whether the item can be deleted or not. */ cancel: boolean; /** * It denotes the event. */ event: MouseEventArgs | KeyboardEventArgs; } export interface ChipDeletedEventArgs { /** * Specifies the text value of the deleted chip item. */ text: string; /** * Specifies the index value of the deleted chip item. */ index: number; /** * Specifies the data of the deleted chip item. */ data: string | number | ChipModel; } export interface DragAndDropEventArgs { /** * If you want to cancel this event then, set cancel as true. Otherwise, false. * * @default false */ cancel?: boolean; /** Return the actual event. */ event: MouseEvent & TouchEvent; /** Return the currently dragged chip item. */ draggedItem: HTMLElement; /** Return the currently dragged chip item details as array of JSON object */ draggedItemData: { [key: string]: Object }; /** Return the dragged element destination target. */ dropTarget: HTMLElement; } export interface ChipDataArgs { /** * It denotes the item text. */ text: string | undefined; /** * It denotes the Item index. */ index: number; /** * It denotes the item data. */ data: string | number | ChipModel; /** * It denotes the item element. */ element: HTMLElement; } /** * A chip component is a small block of essential information, mostly used on contacts or filter tags. * ```html * <div id="chip"></div> * ``` * ```typescript * <script> * var chipObj = new ChipList(); * chipObj.appendTo("#chip"); * </script> * ``` */ @NotifyPropertyChanges export class ChipList extends Component<HTMLElement> implements INotifyPropertyChanged { /** * This chips property helps to render ChipList component. * {% codeBlock src='chips/chips/index.md' %}{% endcodeBlock %} * * @default [] * */ @Property([]) public chips: string[] | number[] | ChipModel[]; /** * Specifies the text content for the chip. * {% codeBlock src='chips/text/index.md' %}{% endcodeBlock %} * * @default '' */ @Property('') public text: string; /** * Specifies the customized text value for the avatar in the chip. * {% codeBlock src='chips/avatarText/index.md' %}{% endcodeBlock %} * * @default '' */ @Property('') public avatarText: string; /** * Specifies the icon CSS class for the avatar in the chip. * {% codeBlock src='chips/avatarIconCss/index.md' %}{% endcodeBlock %} * * @default '' */ @Property('') public avatarIconCss: string; /** * Allows additional HTML attributes such as aria labels, title, name, etc., and * accepts n number of attributes in a key-value pair format. * {% codeBlock src='chiplist/htmlAttributes/index.md' %}{% endcodeBlock %} * * @default {} */ @Property('') public htmlAttributes: { [key: string]: string }; /** * Specifies the leading icon CSS class for the chip. * {% codeBlock src='chips/leadingIconCss/index.md' %}{% endcodeBlock %} * * @default '' */ @Property('') public leadingIconCss: string; /** * Specifies the trailing icon CSS class for the chip. * {% codeBlock src='chips/trailingIconCss/index.md' %}{% endcodeBlock %} * * @default '' */ @Property('') public trailingIconCss: string; /** * Specifies the leading icon url for the chip. * * @default '' */ @Property('') public leadingIconUrl: string; /** * Specifies the trailing icon url for the chip. * * @default '' */ @Property('') public trailingIconUrl: string; /** * Specifies the custom classes to be added to the chip element used to customize the ChipList component. * {% codeBlock src='chips/cssClass/index.md' %}{% endcodeBlock %} * * @default '' */ @Property('') public cssClass: string; /** * Specifies a value that indicates whether the chip component is enabled or not. * * @default true */ @Property(true) public enabled: boolean; /** * Sets or gets the selected chip items in the chip list. * {% codeBlock src='chips/selectedChips/index.md' %}{% endcodeBlock %} * * @default [] */ @Property([]) public selectedChips: string[] | number[] | number; /** * Defines the selection type of the chip. The available types are: * 1. Input chip * 2. Choice chip * 3. Filter chip * 4. Action chip * * @default 'None' */ @Property('None') public selection: Selection; /** * Enables or disables the delete functionality of a chip. * {% codeBlock src='chips/enableDelete/index.md' %}{% endcodeBlock %} * * @default false */ @Property(false) public enableDelete: boolean; /** * Specifies a boolean value that indicates whether the chip item can be dragged and reordered. * This enables drag-and-drop functionality within a single container or across multiple containers of chips when dragging is enabled. * * @default false */ @Property(false) public allowDragAndDrop: boolean; /** * Specifies the target in which the draggable element can be moved and dropped. * By default, the draggable element movement occurs in the page. * * @default null */ @Property(null) public dragArea: HTMLElement | string; /** * Triggers when the component is created successfully. * {% codeBlock src='chips/created/index.md' %}{% endcodeBlock %} * * @event created */ @Event() public created: EmitType<Event>; /** * Triggers when a chip is clicked. * {% codeBlock src='chips/click/index.md' %}{% endcodeBlock %} * * @event click */ @Event() public click: EmitType<ClickEventArgs>; /** * Triggers before the click event of the chip is fired. * This event can be used to prevent the further process and restrict the click action over a chip. * * {% codeBlock src='chips/beforeClick/index.md' %}{% endcodeBlock %} * * @event beforeClick */ @Event() public beforeClick: EmitType<ClickEventArgs>; /** * Fires before removing the chip element. * {% codeBlock src='chips/delete/index.md' %}{% endcodeBlock %} * * @event delete */ @Event() public delete: EmitType<DeleteEventArgs>; /** * Triggers when the chip item is removed. * {% codeBlock src='chips/deleted/index.md' %}{% endcodeBlock %} * * @event deleted */ @Event() public deleted: EmitType<ChipDeletedEventArgs>; /** * Fires when a chip item starts moving due to a drag action. * * @event dragStart */ @Event() public dragStart: EmitType<DragAndDropEventArgs>; /** * Fires while a chip item is being dragged. * * @event dragging */ @Event() public dragging: EmitType<DragAndDropEventArgs>; /** * Fires when a chip item is reordered after completing a drag action. * * @event dragStop */ @Event() public dragStop: EmitType<DragAndDropEventArgs>; constructor(options?: ChipListModel, element?: string | HTMLElement) { super(options, element); } private rippleFunction: Function; private type: string; private innerText: string; public multiSelectedChip: number[] = []; private dragObj: Draggable; private dragCollection: Draggable[]; private dragIndicator: HTMLElement; private updatedInstance: HTMLElement; /** * Initialize the event handler * * @private */ protected preRender(): void { //prerender } /** * To find the chips length. * * @returns boolean * @private */ protected chipType(): boolean { return (this.chips && this.chips.length && this.chips.length > 0) as boolean; } /** * To Initialize the control rendering. * * @returns void * @private */ protected render(): void { this.type = (!isNullOrUndefined(this.chips) && this.chips.length) ? 'chipset' : (this.text || this.element.innerText ? 'chip' : 'chipset'); this.setAttributes(); this.createChip(); this.setRtl(); this.select(this.selectedChips); this.wireEvent(false); this.rippleFunction = rippleEffect(this.element, { selector: '.' + classNames.chip }); this.renderComplete(); this.dragCollection = []; if (this.allowDragAndDrop) { this.enableDraggingChips(); } } private enableDraggingChips(): void { let clonedChipElement: HTMLElement; const chipElements: NodeListOf<HTMLElement> = this.element.querySelectorAll('.' + classNames.chip); chipElements.forEach((chip: HTMLElement, index: number) => { this.dragObj = new Draggable(chip, { preventDefault: false, clone: true, dragArea: this.dragArea, helper: () => { clonedChipElement = chip.cloneNode(true) as HTMLElement; clonedChipElement.classList.add(classNames.cloneChip); this.element.appendChild(clonedChipElement); return clonedChipElement; }, dragStart: (args: DragEventArgs) => { this.dragIndicator = this.createElement('div', { className: classNames.dragIndicator }); document.body.appendChild(this.dragIndicator); const chipData: ChipDataArgs = this.find(args.element); const dragStartArgs: DragAndDropEventArgs = { cancel: false, event: args.event, draggedItem: args.element, draggedItemData: chipData as any, dropTarget: null }; this.trigger('dragStart', dragStartArgs, () => { if (isNullOrUndefined(dragStartArgs.cancel)) { dragStartArgs.cancel = false; } }); if (!dragStartArgs.cancel) { clonedChipElement.setAttribute('drag-indicator-index', index.toString()); } else { this.dragObj.intDestroy(args.event); } }, drag: (args: DragEventArgs) => { const chipData: ChipDataArgs = this.find(args.element); const draggingArgs: DragAndDropEventArgs = { event: args.event, draggedItem: args.element, draggedItemData: chipData as any, dropTarget: null }; this.trigger('dragging', draggingArgs); let draggingIconEle: HTMLElement | null = clonedChipElement.querySelector('.' + classNames.chipDrag); if (isNullOrUndefined(draggingIconEle)) { draggingIconEle = this.createElement('span', { className: `${classNames.customIcon} ${classNames.dragAndDrop} ${classNames.chipDrag}` }) as HTMLElement; clonedChipElement.prepend(draggingIconEle); } this.allowExternalDragging(args, clonedChipElement, draggingIconEle); }, dragStop: (args: DragEventArgs) => { const chipData: ChipDataArgs = this.find(args.element); const dragStopArgs: DragAndDropEventArgs = { cancel: false, event: args.event, draggedItem: args.element, draggedItemData: chipData as any, dropTarget: args.target }; this.trigger('dragStop', dragStopArgs, () => { if (isNullOrUndefined(dragStopArgs.cancel)) { dragStopArgs.cancel = false; } }); if (!dragStopArgs.cancel) { this.allowExternalDrop(args, clonedChipElement); } if (!isNullOrUndefined(this.dragIndicator)) { remove(this.dragIndicator); } if (!isNullOrUndefined(clonedChipElement)) { clonedChipElement.remove(); } } }); if (this.dragCollection.indexOf(this.dragObj) === -1) { this.dragCollection.push(this.dragObj); } }); } private checkInstance(args: DragEventArgs, context: ChipList): boolean { const isInstanceMatched: boolean = !isNullOrUndefined(args.target.closest('.' + classNames.chipList)) && args.target.closest('.' + classNames.chipList).id !== context.element.id; if (isInstanceMatched) { this.updatedInstance = args.target.closest('.' + classNames.chipList) as HTMLElement; } return isInstanceMatched; } private setIcons(currentInstance: ChipList, draggingIconEle: HTMLElement, target: HTMLElement, indicatorEle: HTMLElement, outOfDragArea: boolean): void { const isTargetInside: boolean = currentInstance.element.contains(target); const isDroppable: Element = target.closest('.e-droppable'); if ((isTargetInside || isDroppable) && !outOfDragArea) { draggingIconEle.classList.add(classNames.dragAndDrop); draggingIconEle.classList.remove(classNames.dropRestricted); if (isDroppable) { indicatorEle.style.display = 'none'; } } else { draggingIconEle.classList.remove(classNames.dragAndDrop); draggingIconEle.classList.add(classNames.dropRestricted); indicatorEle.style.display = 'none'; } } private allowExternalDragging(args: DragEventArgs, clonedChipElement: HTMLElement, draggingIconEle: HTMLElement): void { let currentInstance: ChipList; let closestChip: Element | null = null; let closestDistance: number = Infinity; let newIndex: number = -1; let outOfDragArea: boolean = false; if (this.checkInstance(args, this)) { this.dragIndicator.style.display = 'none'; currentInstance = this.getCurrentInstance(args); currentInstance.dragIndicator = this.dragIndicator; if (!currentInstance.allowDragAndDrop) { return; } } else { currentInstance = this as ChipList; } const indicatorEle: HTMLElement = currentInstance.dragIndicator; indicatorEle.style.display = 'inline'; outOfDragArea = this.dragAreaCheck(this.dragArea, args.target, outOfDragArea, draggingIconEle, indicatorEle); this.setIcons(currentInstance, draggingIconEle, args.target, indicatorEle, outOfDragArea); currentInstance.element.appendChild(clonedChipElement); const droppedRect: DOMRect = clonedChipElement.getBoundingClientRect() as DOMRect; const allChips: Element[] = Array.from(currentInstance.element.querySelectorAll('.' + classNames.chip)); allChips.forEach((chip: Element, i: number) => { if (chip !== clonedChipElement) { const rect: DOMRect = chip.getBoundingClientRect() as DOMRect; const distance: number = Math.sqrt(Math.pow(droppedRect.left - rect.left, 2) + Math.pow(droppedRect.top - rect.top, 2)); if (distance < closestDistance) { closestDistance = distance; closestChip = chip; newIndex = i; } } }); if (newIndex === -1) { newIndex = allChips.length; } const chipsDistance: number = this.getChipsDistance(currentInstance); const cloneRect: DOMRect = clonedChipElement.getBoundingClientRect() as DOMRect; let rect: DOMRect; if (closestChip || allChips.length > 0) { const targetChip: Element = closestChip || allChips[allChips.length - 1]; rect = targetChip.getBoundingClientRect() as DOMRect; indicatorEle.style.top = rect.top + window.scrollY + 'px'; indicatorEle.style.left = currentInstance.enableRtl ? (rect.right + chipsDistance + 'px') : (rect.left - chipsDistance + window.scrollX + 'px'); } if (currentInstance.enableRtl) { if (cloneRect.left < rect.left - rect.width / 2 && cloneRect.top > rect.top) { indicatorEle.style.left = rect.left - chipsDistance + window.scrollX + 'px'; } } else if (cloneRect.left > rect.left + rect.width / 2 && cloneRect.top > rect.top) { indicatorEle.style.left = rect.left + rect.width + chipsDistance + window.scrollX + 'px'; } } private dragAreaCheck(dragArea: string | HTMLElement, target: HTMLElement, outOfDragArea: boolean, draggingIconEle: HTMLElement, indicatorEle: HTMLElement): boolean { if (isNullOrUndefined(dragArea)) { return false; } const isString: boolean = typeof dragArea === 'string'; const isHtmlElement: boolean = dragArea instanceof HTMLElement; const dragAreaElement: string | Element = isString ? document.querySelector(dragArea as string) : dragArea; if (!isNullOrUndefined(dragAreaElement)) { if ((isString || isHtmlElement) && !(dragAreaElement as HTMLElement).contains(target)) { outOfDragArea = true; indicatorEle.style.display = 'none'; draggingIconEle.classList.add(classNames.dropRestricted); draggingIconEle.classList.remove(classNames.dragAndDrop); } } return outOfDragArea; } private getChipsDistance(currentInstance: ChipList): number { const constValue: number = 4; if (currentInstance.chips.length <= 1) { return constValue; } let constantDistance: number; const firstChipClientRect: DOMRect = currentInstance.find(0).element.getBoundingClientRect() as DOMRect; const secondChipClientRect: DOMRect = currentInstance.find(1).element.getBoundingClientRect() as DOMRect; const firstChipLeft: number = firstChipClientRect.left; if (currentInstance.enableRtl) { const secondChipRight: number = secondChipClientRect.right; constantDistance = firstChipLeft < secondChipRight ? constValue : ((firstChipLeft - secondChipRight) / 2); return constantDistance; } else { const firstChipWidth: number = firstChipClientRect.width; const secondChipLeft: number = secondChipClientRect.left; constantDistance = secondChipLeft < (firstChipLeft + firstChipWidth) ? constValue : (secondChipLeft - (firstChipLeft + firstChipWidth)) / 2; return constantDistance; } } private getCurrentInstance(args: DragEventArgs): ChipList { const chipContainer: HTMLElement = args.target.closest('.' + classNames.chipList) as HTMLElement; if (!isNullOrUndefined(chipContainer) && !isNullOrUndefined((chipContainer as EJ2Instance).ej2_instances)) { for (let i: number = 0; i < (chipContainer as EJ2Instance).ej2_instances.length; i++) { if ((chipContainer as EJ2Instance).ej2_instances[parseInt(i.toString(), 10)] instanceof ChipList) { return (chipContainer as EJ2Instance).ej2_instances[i as number] as ChipList; } } } return null; } private allowExternalDrop(args: DragEventArgs, clonedChipElement: HTMLElement): void { const originalIndex: number = parseInt(clonedChipElement.getAttribute('drag-indicator-index') as string, 10); let currentInstance: ChipList; let outOfDragArea: boolean = false; let isInstanceChanged: boolean = false; if (this.checkInstance(args, this)) { isInstanceChanged = true; currentInstance = this.getCurrentInstance(args); if (!currentInstance.allowDragAndDrop) { return; } } else { currentInstance = this as ChipList; } const indicatorEle: HTMLElement = currentInstance.dragIndicator; indicatorEle.style.display = 'inline'; if (!currentInstance.element.contains(args.target)) { return; } outOfDragArea = this.dragAreaCheck(this.dragArea, args.target, outOfDragArea, clonedChipElement.querySelector('.' + classNames.chipDrag), indicatorEle); if (outOfDragArea) { return; } const indicatorRect: DOMRect = indicatorEle.getBoundingClientRect() as DOMRect; const allChips: Element[] = Array.from(currentInstance.element.querySelectorAll('.' + classNames.chip)); let newIndex: number = -1; let topOffset: boolean = false; let leftOffset: boolean = false; let rightOffset: boolean = false; for (let i: number = 0; i < allChips.length; i++) { if (allChips[i as number] !== clonedChipElement) { const chipRect: DOMRect = allChips[i as number].getBoundingClientRect() as DOMRect; topOffset = indicatorRect.top < chipRect.top + chipRect.height / 2; leftOffset = indicatorRect.left < chipRect.left + chipRect.width / 2; rightOffset = indicatorRect.left > chipRect.left + chipRect.width / 2; if ((!currentInstance.enableRtl && topOffset && leftOffset) || (currentInstance.enableRtl && topOffset && rightOffset)) { newIndex = i; if (i > originalIndex && !isInstanceChanged) { newIndex = i - 1; } break; } } } if (newIndex === -1) { let nextChipIndex: number; for (let i: number = 0; i < allChips.length; i++) { const chipRect: DOMRect = allChips[i as number].getBoundingClientRect() as DOMRect; if ((chipRect.top > indicatorRect.top) || (chipRect.top === indicatorRect.top && chipRect.left > indicatorRect.left)) { nextChipIndex = i as number; break; } } if (nextChipIndex !== allChips.length) { newIndex = nextChipIndex; } else { newIndex = allChips.length; } } const currentChipList: string[] = Array.from(this.chips as string[]); if (isInstanceChanged) { this.dropChip(currentChipList, originalIndex, currentInstance, newIndex, true); } else if (newIndex !== originalIndex) { this.dropChip(currentChipList, originalIndex, currentInstance, newIndex, false); } } private dropChip(currentChipList: string[], originalIndex: number, currentInstance: ChipList, newIndex: number, instanceChanged: boolean): void { const draggedChip: string = currentChipList.splice(originalIndex, 1)[0]; if (!instanceChanged) { currentChipList.splice(newIndex, 0, draggedChip); currentInstance.chips = currentChipList; } else { const newChips: string[] = Array.from(currentInstance.chips as string[]); newChips.splice(newIndex, 0, draggedChip); currentInstance.chips = newChips; } this.chips = currentChipList; currentInstance.dataBind(); this.dataBind(); currentInstance.enableDraggingChips(); } private createChip(): void { this.innerText = (this.element.innerText && this.element.innerText.length !== 0) ? this.element.innerText.trim() : this.element.innerText; this.element.innerHTML = ''; this.chipCreation(this.type === 'chip' ? [this.innerText ? this.innerText : this.text] : this.chips); } private setAttributes(): void { if (this.type === 'chip') { if (this.enabled) {this.element.tabIndex = 0; } this.element.setAttribute('role', 'button'); } else { this.element.classList.add(classNames.chipSet); this.element.setAttribute('role', 'listbox'); if (this.selection === 'Multiple') { this.element.classList.add(classNames.multiSelection); this.element.setAttribute('aria-multiselectable', 'true'); } else if (this.selection === 'Single') { this.element.classList.add(classNames.singleSelection); this.element.setAttribute('aria-multiselectable', 'false'); } else { this.element.setAttribute('aria-multiselectable', 'false'); } } } private setRtl(): void { this.element.classList[this.enableRtl ? 'add' : 'remove'](classNames.rtl); } private renderTemplates(): void { if ((this as any).isReact) { this.renderReactTemplates(); } } private templateParser(template: string | Function): Function { if (template) { try { if (typeof template !== 'function' && document.querySelectorAll(template).length) { return compile(document.querySelector(template).innerHTML.trim()); } else { return compile(template); } } catch (error) { return compile(template); } } return undefined; } private chipCreation(data: string[] | number[] | ChipModel[]): void { if (isNullOrUndefined(data)) { return; } let chipListArray: HTMLElement[] = []; const attributeArray: { [key: string]: string; }[] = []; for (let i: number = 0; i < data.length; i++) { const fieldsData: ChipFields = this.getFieldValues(data[i as number]); const attributesValue: { [key: string]: string; } = fieldsData.htmlAttributes; attributeArray.push(attributesValue); const chipArray: HTMLElement[] = this.elementCreation(fieldsData); const className: string[] = (classNames.chip + ' ' + (fieldsData.enabled ? ' ' : classNames.disabled) + ' ' + (fieldsData.avatarIconCss || fieldsData.avatarText ? classNames.chipWrapper : (fieldsData.leadingIconCss ? classNames.iconWrapper : ' ')) + ' ' + fieldsData.cssClass).split(' ').filter((css: string) => css); if (!this.chipType() || this.type === 'chip') { chipListArray = chipArray; addClass([this.element], className); this.element.setAttribute('aria-label', fieldsData.text); if (fieldsData.value) { this.element.setAttribute('data-value', fieldsData.value.toString()); } } else { const wrapper: HTMLElement = this.createElement('DIV', { className: className.join(' '), attrs: { tabIndex: '0', role: 'option', 'aria-label': fieldsData.text, 'aria-selected': 'false' } }); if (this.enableDelete) { wrapper.setAttribute('aria-keyshortcuts', 'Press delete or backspace key to delete'); } if (fieldsData.value) { wrapper.setAttribute('data-value', fieldsData.value.toString()); } if (fieldsData.enabled) { wrapper.setAttribute('aria-disabled', 'false'); } else { wrapper.removeAttribute('tabindex'); wrapper.setAttribute('aria-disabled', 'true'); } if (!isNullOrUndefined(attributeArray[i as number])) { if (attributeArray.length > i && Object.keys(attributeArray[i as number]).length) { let htmlAttr: string[] = []; htmlAttr = (Object.keys(attributeArray[i as number])); for (let j: number = 0; j < htmlAttr.length; j++) { wrapper.setAttribute(htmlAttr[j as number], attributeArray[i as number][htmlAttr[j as number]]); } } } append(chipArray, wrapper); chipListArray.push(wrapper); } } append(chipListArray, this.element); } private getFieldValues(data: string | number | ChipModel): ChipFields { const chipEnabled: boolean = !(this.enabled.toString() === 'false'); const fields: ChipFields = { text: typeof data === 'object' ? (data.text ? data.text.toString() : this.text.toString()) : (!this.chipType() ? (this.innerText ? this.innerText : this.text.toString()) : data.toString()), cssClass: typeof data === 'object' ? (data.cssClass ? data.cssClass.toString() : this.cssClass.toString()) : (this.cssClass.toString()), leadingIconCss: typeof data === 'object' ? (data.leadingIconCss ? data.leadingIconCss.toString() : this.leadingIconCss.toString()) : (this.leadingIconCss.toString()), avatarIconCss: typeof data === 'object' ? (data.avatarIconCss ? data.avatarIconCss.toString() : this.avatarIconCss.toString()) : (this.avatarIconCss.toString()), avatarText: typeof data === 'object' ? (data.avatarText ? data.avatarText.toString() : this.avatarText.toString()) : (this.avatarText.toString()), trailingIconCss: typeof data === 'object' ? (data.trailingIconCss ? data.trailingIconCss.toString() : this.trailingIconCss.toString()) : (this.trailingIconCss.toString()), enabled: typeof data === 'object' ? (data.enabled !== undefined ? (data.enabled.toString() === 'false' ? false : true) : chipEnabled) : (chipEnabled), value: typeof data === 'object' ? ((data.value ? data.value.toString() : null)) : null, leadingIconUrl: typeof data === 'object' ? (data.leadingIconUrl ? data.leadingIconUrl.toString() : this.leadingIconUrl) : this.leadingIconUrl, trailingIconUrl: typeof data === 'object' ? (data.trailingIconUrl ? data.trailingIconUrl.toString() : this.trailingIconUrl) : this.trailingIconUrl, htmlAttributes: typeof data === 'object' ? (data.htmlAttributes ? data.htmlAttributes : this.htmlAttributes) : this.htmlAttributes, template: typeof data === 'object' ? (data.template ? data.template : null) : null }; return fields; } private elementCreation(fields: ChipFields): HTMLElement[] { const chipArray: HTMLElement[] = []; if (fields.avatarText || fields.avatarIconCss) { const className: string = (classNames.avatar + ' ' + fields.avatarIconCss).trim(); const chipAvatarElement: HTMLElement = this.createElement('span', { className: className }); chipAvatarElement.innerText = fields.avatarText; chipArray.push(chipAvatarElement); } else if (fields.leadingIconCss) { const className: string = (classNames.icon + ' ' + fields.leadingIconCss).trim(); const chipIconElement: HTMLElement = this.createElement('span', { className: className }); chipArray.push(chipIconElement); } else if (fields.leadingIconUrl) { const className: string = (classNames.avatar + ' ' + 'image-url').trim(); const chipIconElement: HTMLElement = this.createElement('span', { className: className}); chipIconElement.style.backgroundImage = 'url(' + fields.leadingIconUrl + ')'; chipArray.push(chipIconElement); } const chipTextElement: HTMLElement = this.createElement('span', { className: classNames.text }); chipTextElement.innerText = fields.text; chipArray.push(chipTextElement); if (fields.template) { const templateWrapper: HTMLElement = this.createElement('div', { className: classNames.template }); const templateContent: HTMLElement[] = this.templateParser(fields.template)(fields, this, 'template', this.element.id + '_template', false); append(templateContent, templateWrapper); chipArray.push(templateWrapper); this.renderTemplates(); } if (fields.trailingIconCss || (this.chipType() && this.enableDelete)) { const className: string = (classNames.delete + ' ' + (fields.trailingIconCss ? fields.trailingIconCss : classNames.deleteIcon)).trim(); const chipdeleteElement: HTMLElement = this.createElement('span', { className: className }); chipArray.push(chipdeleteElement); } else if (fields.trailingIconUrl) { const className: string = ('trailing-icon-url').trim(); const chipIconsElement: HTMLElement = this.createElement('span', { className: className }); chipIconsElement.style.backgroundImage = 'url(' + fields.trailingIconUrl + ')'; chipArray.push(chipIconsElement); } return chipArray; } /** * A function that finds chip based on given input. * * @param {number | HTMLElement } fields - We can pass index number or element of chip. * {% codeBlock src='chips/find/index.md' %}{% endcodeBlock %} * * @returns {void} */ public find(fields: number | HTMLElement): ChipDataArgs { const chipData : ChipDataArgs = {text: '', index: -1, element: this.element, data: ''}; const chipElement: HTMLElement = fields instanceof HTMLElement ? fields as HTMLElement : this.element.querySelectorAll('.' + classNames.chip)[fields as number] as HTMLElement; if (chipElement && this.chipType()) { chipData.index = Array.prototype.slice.call(this.element.querySelectorAll('.' + classNames.chip)).indexOf(chipElement); const chip: string | number | ChipModel = this.chips[chipData.index as number]; if (typeof chip === 'object' && chip !== null) { const chipModel: ChipModel = chip as ChipModel; if (chipModel.text !== undefined) { chipData.text = chipModel.text.toString(); } } else if (chip !== undefined) { chipData.text = chip.toString(); } chipData.data = chip; chipData.element = chipElement; } return chipData; } /** * Allows adding the chip item(s) by passing a single or array of string, number, or ChipModel values. * * @param {string[] | number[] | ChipModel[] | string | number | ChipModel} chipsData - We can pass array of string or * array of number or array of chip model or string data or number data or chip model. * {% codeBlock src='chips/add/index.md' %}{% endcodeBlock %} * * @returns {void} * @deprecated */ public add(chipsData: string[] | number[] | ChipModel[] | string | number | ChipModel): void { if (this.type !== 'chip') { const fieldData: string[] | number[] | ChipModel[] = chipsData instanceof Array ? chipsData : <string[] | number[] | ChipModel[]>[chipsData as string | number | ChipModel]; this.chips = [].slice.call(this.chips).concat(...fieldData as ChipModel[]); this.chipCreation(fieldData); } } /** * Allows selecting the chip item(s) by passing a single or array of string, number, or ChipModel values. * * @param {number | number[] | HTMLElement | HTMLElement[]} fields - We can pass number or array of number * or chip element or array of chip element. * {% codeBlock src='chips/select/index.md' %}{% endcodeBlock %} * * @returns {void} */ public select(fields: number | number[] | HTMLElement | HTMLElement[] | string[], selectionType?: selectionType): void { this.onSelect(fields, false, selectionType); } private multiSelection(newProp: number[] | string[]): void { const items: NodeListOf<Element> = this.element.querySelectorAll('.' + classNames.chip); for (let j: number = 0; j < newProp.length; j++) { if (typeof newProp[j as number] === 'string') { for (let k: number = 0; k < items.length; k++) { if (newProp[j as number] !== k) { if (newProp[j as number] === items[k as number].attributes[5].value) { this.multiSelectedChip.push(k); break; } } } } else { this.multiSelectedChip.push(newProp[j as number] as number); } } } private onSelect(fields: number | number[] | HTMLElement | HTMLElement[] | string[], callFromProperty: boolean, selectionType?: selectionType): void { let index: number; let chipNodes: HTMLElement; let chipValue: string | number | null = null; if (this.chipType() && this.selection !== 'None') { if (callFromProperty) { const chipElements: NodeListOf<Element> = this.element.querySelectorAll('.' + classNames.chip); for (let i: number = 0; i < chipElements.length; i++) { chipElements[i as number].setAttribute('aria-selected', 'false'); chipElements[i as number].classList.remove(classNames.active); } } const fieldData: number[] | HTMLElement[] | string[] = fields instanceof Array ? fields : <number[] | HTMLElement[]>[fields]; for (let i: number = 0; i < fieldData.length; i++) { let chipElement: HTMLElement = fieldData[i as number] instanceof HTMLElement ? fieldData[i as number] as HTMLElement : this.element.querySelectorAll('.' + classNames.chip)[fieldData[i as number] as number] as HTMLElement; if (selectionType !== 'index') { for (let j: number = 0; j < this.chips.length; j++) { chipNodes = this.element.querySelectorAll('.' + classNames.chip)[j as number] as HTMLElement; const fieldsData: ChipFields = this.getFieldValues(this.chips[j as number]); if (selectionType === 'value') { if (fieldsData.value !== null) { chipValue = chipNodes.dataset.value as string | number; } } else if (selectionType === 'text') { chipValue = chipNodes.innerText; } if (chipValue === fieldData[i as number].toString()) { index = j; chipElement = this.element.querySelectorAll('.' + classNames.chip)[index as number] as HTMLElement; } } } if (chipElement instanceof HTMLElement) { this.selectionHandler(chipElement); } } } } /** * Allows removing the chip item(s) by passing a single or array of string, number, or ChipModel values. * * @param {number | number[] | HTMLElement | HTMLElement[]} fields - We can pass number or array of number * or chip element or array of chip element. * {% codeBlock src='chips/remove/index.md' %}{% endcodeBlock %} * * @returns {void} */ public remove(fields: number | number[] | HTMLElement | HTMLElement[]): void { if (this.chipType()) { const fieldData: number[] | HTMLElement[] = fields instanceof Array ? fields : <number[] | HTMLElement[]>[fields]; const chipElements: HTMLElement[] = []; const chipCollection: NodeListOf<HTMLElement> = this.element.querySelectorAll('.' + classNames.chip); (fieldData as HTMLElement[]).forEach((data: HTMLElement | number) => { const chipElement: HTMLElement = data instanceof HTMLElement ? data as HTMLElement : chipCollection[data as number] as HTMLElement; if (chipElement instanceof HTMLElement) { chipElements.push(chipElement); } }); chipElements.forEach((element: HTMLElement) => { const chips: NodeListOf<HTMLElement> = this.element.querySelectorAll('.' + classNames.chip); const index: number = Array.prototype.slice.call(chips).indexOf(element); this.deleteHandler(element, index); }); } } /** * Returns the selected chip(s) data. * {% codeBlock src='chips/getSelectedChips/index.md' %}{% endcodeBlock %} * * @returns {void} */ public getSelectedChips(): SelectedItem | SelectedItems | undefined{ let selectedChips: SelectedItem | SelectedItems | undefined; if (this.chipType() && this.selection !== 'None') { const selectedItems: SelectedItems = { texts: [], Indexes: [], data: [], elements: [] }; const items: NodeListOf<Element> = this.element.querySelectorAll('.' + classNames.active); for (let i: number = 0; i < items.length; i++) { const chip: HTMLElement = items[i as number] as HTMLElement; selectedItems.elements.push(chip); const index: number = Array.prototype.slice.call(this.element.querySelectorAll('.' + classNames.chip)).indexOf(chip); selectedItems.Indexes.push(index); (selectedItems.data as ChipModel[]).push((this.chips as ChipModel[])[index as number]); const text: string | null | undefined = typeof this.chips[index as number] === 'object' ? (this.chips[index as number] as ChipModel).text ? (this.chips[index as number] as ChipModel).text : null : this.chips[index as number].toString(); selectedItems.texts.push(text as string); } const selectedItem: SelectedItem = { text: selectedItems.texts[0], index: selectedItems.Indexes[0], data: selectedItems.data[0], element: selectedItems.elements[0] }; selectedChips = !isNullOrUndefined(selectedItem.index) ? (this.selection === 'Multiple' ? selectedItems : selectedItem) : undefined; } return selectedChips; } private wireEvent(unWireEvent: boolean): void { if (!unWireEvent) { EventHandler.add(this.element, 'click', this.clickHandler, this); EventHandler.add(this.element, 'focusout', this.focusOutHandler, this); EventHandler.add(this.element, 'keydown', this.keyHandler, this); EventHandler.add(this.element, 'keyup', this.keyHandler, this); } else { EventHandler.remove(this.element, 'click', this.clickHandler); EventHandler.remove(this.element, 'focusout', this.focusOutHandler); EventHandler.remove(this.element, 'keydown', this.keyHandler); EventHandler.remove(this.element, 'keyup', this.keyHandler); } } private keyHandler(e: KeyboardEventArgs): void { if ((e.target as HTMLElement).classList.contains(classNames.chip)) { if (e.type === 'keydown') { if (e.keyCode === 13 || e.keyCode === 32) { this.clickHandler(e); } else if ((e.keyCode === 46 || e.keyCode === 8) && this.enableDelete) { this.clickHandler(e, true); } } else if (e.keyCode === 9) { this.focusInHandler(e.target as HTMLElement); } } } private focusInHandler(chipWrapper: HTMLElement): void { if (!chipWrapper.classList.contains(classNames.focused)) { chipWrapper.classList.add(classNames.focused); } } private focusOutHandler(e: MouseEventArgs): void { const chipWrapper: HTMLElement = <HTMLElement>closest((e.target as HTMLElement), '.' + classNames.chip); const focusedElement: HTMLElement | null = !this.chipType() ? (this