Current Path : /var/www/www-root/data/www/monolith-realty.ru/bitrix/js/ui/entity-selector/src/dialog/ |
Current File : /var/www/www-root/data/www/monolith-realty.ru/bitrix/js/ui/entity-selector/src/dialog/dialog.js |
import { Type, Text, Tag, Dom, ajax as Ajax, Cache, Loc, Runtime, Reflection } from 'main.core'; import { EventEmitter, BaseEvent } from 'main.core.events'; import { Popup } from 'main.popup'; import { Loader } from 'main.loader'; import Item from '../item/item'; import Tab from './tabs/tab'; import Entity from '../entity/entity'; import TagSelector from '../tag-selector/tag-selector'; import Navigation from './navigation'; import SliderIntegration from './integration/slider-integration'; import Animation from '../common/animation'; import BaseHeader from './header/base-header'; import DefaultHeader from './header/default-header'; import BaseFooter from './footer/base-footer'; import DefaultFooter from './footer/default-footer'; import RecentTab from './tabs/recent-tab'; import SearchTab from './tabs/search-tab'; import type ItemNode from '../item/item-node'; import type { TabOptions } from './tabs/tab-options'; import type { DialogOptions } from './dialog-options'; import type { ItemOptions } from '../item/item-options'; import type { EntityOptions } from '../entity/entity-options'; import type { ItemId } from '../item/item-id'; import type { PopupOptions } from 'main.popup'; import type { HeaderOptions, HeaderContent } from './header/header-content'; import type { FooterOptions, FooterContent } from './footer/footer-content'; import type { ItemNodeOptions } from '../item/item-node-options'; import type { TagItemOptions } from '../tag-selector/tag-item-options'; class LoadState { static UNSENT: string = 'UNSENT'; static LOADING: string = 'LOADING'; static DONE: string = 'DONE'; } class TagSelectorMode { static INSIDE: string = 'INSIDE'; static OUTSIDE: string = 'OUTSIDE'; } const instances = new Map(); /** * @memberof BX.UI.EntitySelector */ export default class Dialog extends EventEmitter { id: string = null; items: Map<string, Map<string, Item>> = new Map(); tabs: Map<string, Entity> = new Map(); entities: Map<string, Entity> = new Map(); targetNode: HTMLElement = null; popup: Popup = null; cache = new Cache.MemoryCache(); multiple: boolean = true; hideOnSelect: boolean = null; hideOnDeselect: boolean = null; addTagOnSelect: boolean = null; clearSearchOnSelect: boolean = true; context: string = null; selectedItems: Set<Item> = new Set(); preselectedItems: ItemId[] = []; undeselectedItems: ItemId[] = []; dropdownMode: boolean = false; frozen: boolean = false; frozenProps: { [propName: string]: any } = {}; hideByEsc: boolean = true; autoHide: boolean = true; autoHideHandler: Function = null; offsetTop: number = 5; offsetLeft: number = 0; cacheable: boolean = true; width: number = 565; height: number = 420; maxLabelWidth: number = 160; minLabelWidth: number = 45; showAvatars: boolean = true; compactView: boolean = false; activeTab: Tab = null; recentTab: Tab = null; searchTab: Tab = null; rendered: boolean = false; loadState: LoadState = LoadState.UNSENT; loader: ?Loader = null; tagSelector: ?TagSelector = null; tagSelectorMode: ?TagSelectorMode = null; tagSelectorHeight: ?number = null; saveRecentItemsWithDebounce: Function = Runtime.debounce(this.saveRecentItems, 2000, this); recentItemsToSave = []; recentItemsLimit: ?number = null; navigation: Navigation = null; header: BaseHeader = null; footer: BaseFooter = null; popupOptions: PopupOptions = {}; focusOnFirst: boolean = true; focusedNode: ItemNode = null; clearUnavailableItems: boolean = false; overlappingObserver: MutationObserver = null; offsetAnimation: boolean = true; static getById(id: string): ?Dialog { return instances.get(id) || null; } static getInstances(): Dialog[] { return Array.from(instances.values()); } constructor(dialogOptions: DialogOptions) { super(); this.setEventNamespace('BX.UI.EntitySelector.Dialog'); const options: DialogOptions = Type.isPlainObject(dialogOptions) ? dialogOptions : {}; this.id = Type.isStringFilled(options.id) ? options.id : `ui-selector-${Text.getRandom().toLowerCase()}`; this.multiple = Type.isBoolean(options.multiple) ? options.multiple : true; this.context = Type.isStringFilled(options.context) ? options.context : null; this.clearUnavailableItems = options.clearUnavailableItems === true; this.compactView = options.compactView === true; this.dropdownMode = Type.isBoolean(options.dropdownMode) ? options.dropdownMode : false; if (Type.isArray(options.entities)) { options.entities.forEach((entity) => { this.addEntity(entity); }); } if (options.tagSelector instanceof TagSelector) { this.tagSelectorMode = TagSelectorMode.OUTSIDE; this.setTagSelector(options.tagSelector); } else if (options.enableSearch === true) { const defaultOptions = { placeholder: Loc.getMessage('UI_TAG_SELECTOR_SEARCH_PLACEHOLDER'), maxHeight: 102, // three lines textBoxWidth: 105, }; const customOptions = Type.isPlainObject(options.tagSelectorOptions) ? options.tagSelectorOptions : {}; const mandatoryOptions = { dialogOptions: null, showTextBox: true, showAddButton: false, showCreateButton: false, multiple: this.isMultiple(), }; const tagSelectorOptions = Object.assign(defaultOptions, customOptions, mandatoryOptions); const tagSelector = new TagSelector(tagSelectorOptions); this.tagSelectorMode = TagSelectorMode.INSIDE; this.setTagSelector(tagSelector); } this.setTargetNode(options.targetNode); this.setHideOnSelect(options.hideOnSelect); this.setHideOnDeselect(options.hideOnDeselect); this.setAddTagOnSelect(options.addTagOnSelect); this.setClearSearchOnSelect(options.clearSearchOnSelect); this.setWidth(options.width); void this.setHeight(options.height); this.setAutoHide(options.autoHide); this.setAutoHideHandler(options.autoHideHandler); this.setHideByEsc(options.hideByEsc); this.setOffsetLeft(options.offsetLeft); this.setOffsetTop(options.offsetTop); this.setCacheable(options.cacheable); this.setFocusOnFirst(options.focusOnFirst); this.setShowAvatars(options.showAvatars); this.setRecentItemsLimit(options.recentItemsLimit); this.setOffsetAnimation(options.offsetAnimation); this.recentTab = new RecentTab(this, options.recentTabOptions); this.searchTab = new SearchTab(this, options.searchTabOptions, options.searchOptions); this.addTab(this.recentTab); this.addTab(this.searchTab); this.setPreselectedItems(options.preselectedItems); this.setUndeselectedItems(options.undeselectedItems); this.setOptions(options); const preload = options.preload === true || this.getPreselectedItems().length > 0; if (preload) { this.load(); } if (Type.isPlainObject(options.popupOptions)) { const allowedOptions = new Set([ 'overlay', 'bindOptions', 'targetContainer', 'zIndexOptions', 'events', 'animation', 'className', ]); const popupOptions = {}; Object.keys(options.popupOptions).forEach((option: string) => { if (allowedOptions.has(option)) { popupOptions[option] = options.popupOptions[option]; } }); this.popupOptions = popupOptions; } this.navigation = new Navigation(this); (new SliderIntegration(this)); this.subscribe('ItemNode:onFocus', this.handleItemNodeFocus.bind(this)); this.subscribe('ItemNode:onUnfocus', this.handleItemNodeUnfocus.bind(this)); this.subscribeFromOptions(options.events); instances.set(this.id, this); } show(): void { this.load(); this.getPopup().show(); } hide(): void { this.getPopup().close(); } destroy(): void { if (this.destroying) { return; } this.destroying = true; this.emit('onDestroy'); this.disconnectTabOverlapping(); instances.delete(this.getId()); if (this.isRendered()) { this.getPopup().destroy(); } for (const property in this) { if (this.hasOwnProperty(property)) { delete this[property]; } } Object.setPrototypeOf(this, null); } isOpen(): boolean { return this.popup && this.popup.isShown(); } adjustPosition(): void { if (this.isRendered()) { this.getPopup().adjustPosition(); } } search(queryString: string): void { const query = Type.isStringFilled(queryString) ? queryString.trim() : ''; const event = new BaseEvent({ data: { query } }); this.emit('onBeforeSearch', event); if (event.isDefaultPrevented()) { return; } if (!Type.isStringFilled(query)) { this.selectFirstTab(); if (this.getSearchTab()) { this.getSearchTab().clearResults(); } } else if (this.getSearchTab()) { this.selectTab(this.getSearchTab().getId()); this.getSearchTab().search(query); } this.emit('onSearch', { query }); } addItem(options: ItemOptions): Item { if (!Type.isPlainObject(options)) { throw new Error('EntitySelector.addItem: wrong item options.'); } let item = this.getItem(options); if (!item) { item = new Item(options); const undeselectable = this.getUndeselectedItems().some((itemId: ItemId) => { return itemId[0] === item.getEntityId() && String(itemId[1]) === String(item.getId()); }); if (undeselectable) { item.setDeselectable(false); } item.setDialog(this); const entity = this.getEntity(item.getEntityId()); if (entity === null) { this.addEntity({ id: item.getEntityId() }); } let entityItems = this.items.get(item.getEntityId()); if (!entityItems) { entityItems = new Map(); this.items.set(item.getEntityId(), entityItems); } entityItems.set(String(item.getId()), item); if (item.isSelected()) { this.handleItemSelect(item); } } let tabs = []; if (Type.isArray(options.tabs)) { tabs = options.tabs; } else if (Type.isStringFilled(options.tabs)) { tabs = [options.tabs]; } const children = Type.isArray(options.children) ? options.children : []; tabs.forEach((tabId) => { const tab = this.getTab(tabId); if (tab) { const itemNode = tab.getRootNode().addItem(item, options.nodeOptions); itemNode.addChildren(children); } }); return item; } removeItem(item: Item | ItemOptions): ?Item { item = this.getItem(item); if (item) { this.handleItemDeselect(item); item.getNodes().forEach((node: ItemNode) => { node.getParentNode().removeChild(node); }); const entityItems = this.getEntityItemsInternal(item.getEntityId()); if (entityItems) { entityItems.delete(String(item.getId())); if (entityItems.size === 0) { this.items.delete(item.getEntityId()); } } } return item; } removeItems(): void { this.getItemsInternal().forEach((items: Map<string, Item>) => { items.forEach((item: Item) => { this.removeItem(item); }); }); } getItem(item: ItemId | Item | ItemOptions): ?Item { let id = null; let entityId = null; if (Type.isArray(item) && item.length === 2) { [entityId, id] = item; } else if (item instanceof Item) { id = item.getId(); entityId = item.getEntityId(); } else if (Type.isObjectLike(item)) { ({ id, entityId } = item); } const entityItems = this.getEntityItemsInternal(entityId); if (entityItems) { return entityItems.get(String(id)) || null; } return null; } getSelectedItems(): Item[] { return Array.from(this.selectedItems); } getItems(): Item[] { const items = []; this.getItemsInternal().forEach((entityItems: Map<string, Item>) => { Array.prototype.push.apply(items, Array.from(entityItems.values())); }); return items; } /** * @internal */ getItemsInternal(): Map<string, Map<string, Item>> { return this.items; } getEntityItems(entityId: string): Item[] { const items = this.getEntityItemsInternal(entityId); return items === null ? [] : Array.from(items.values()); } /** * @internal */ getEntityItemsInternal(entityId: string): Map<string, Item> | null { return this.items.get(entityId) || null; } /** * @private */ validateItemIds(itemIds: ItemId[]): ItemId[] { if (!Type.isArrayFilled(itemIds)) { return []; } const result = []; itemIds.forEach((itemId: ItemId) => { if (!Type.isArray(itemId) || itemId.length !== 2) { return; } const [entityId, id] = itemId; if (Type.isStringFilled(entityId) && (Type.isStringFilled(id) || Type.isNumber(id))) { result.push(itemId); } }); return result; } addTab(tab: Tab | TabOptions): Tab { if (Type.isPlainObject(tab)) { tab = new Tab(this, tab); } if (!(tab instanceof Tab)) { throw new Error('EntitySelector: a tab must be an instance of EntitySelector.Tab.'); } if (this.getTab(tab.getId())) { console.error(`EntitySelector: the "${tab.getId()}" tab is already existed.`); return tab; } tab.setDialog(this); this.tabs.set(tab.getId(), tab); if (this.isRendered()) { this.insertTab(tab); } return tab; } getTabs(): Tab[] { return Array.from(this.tabs.values()); } getTab(id: string): ?Tab { return this.tabs.get(id) || null; } getRecentTab(): RecentTab { return this.recentTab; } getSearchTab(): SearchTab { return this.searchTab; } selectTab(id: string): ?Tab { const newActiveTab = this.getTab(id); if (!newActiveTab || newActiveTab === this.getActiveTab()) { return newActiveTab; } if (this.getActiveTab()) { this.getActiveTab().deselect(); } this.activeTab = newActiveTab; newActiveTab.select(); if (!newActiveTab.isRendered()) { newActiveTab.render(); } requestAnimationFrame(() => { requestAnimationFrame(() => { this.focusSearch(); }); }); this.clearNodeFocus(); if (this.shouldFocusOnFirst()) { this.focusOnFirstNode(); } this.adjustHeader(); this.adjustFooter(); return newActiveTab; } /** * @private */ insertTab(tab: Tab): void { tab.renderLabel(); tab.renderContainer(); Dom.append(tab.getLabelContainer(), this.getLabelsContainer()); Dom.append(tab.getContainer(), this.getTabContentsContainer()); if (tab.getHeader()) { Dom.append(tab.getHeader().getContainer(), this.getHeaderContainer()); } if (tab.getFooter()) { Dom.append(tab.getFooter().getContainer(), this.getFooterContainer()); } } selectFirstTab(onlyVisible = true): ?Tab { const tabs = this.getTabs(); for (let i = 0; i < tabs.length; i++) { const tab = tabs[i]; if (onlyVisible === false || tab.isVisible()) { return this.selectTab(tab.getId()); } } if (this.isDropdownMode()) { return this.selectTab(this.getRecentTab().getId()); } return null; } selectLastTab(onlyVisible = true): ?Tab { const tabs = this.getTabs(); for (let i = tabs.length - 1; i >= 0; i--) { const tab = tabs[i]; if (onlyVisible === false || tab.isVisible()) { return this.selectTab(tab.getId()); } } if (this.isDropdownMode()) { return this.selectTab(this.getRecentTab().getId()); } return null; } getActiveTab(): ?Tab { return this.activeTab; } getNextTab(onlyVisible = true): ?Tab { let nextTab = null; let activeFound = false; const tabs = this.getTabs(); for (let i = 0; i < tabs.length; i++) { const tab = tabs[i]; if (onlyVisible && !tab.isVisible()) { continue; } if (tab === this.getActiveTab()) { activeFound = true; } else if (activeFound) { nextTab = tab; break; } } return nextTab; } getPreviousTab(onlyVisible = true): ?Tab { let previousTab = null; let activeFound = false; const tabs = this.getTabs(); for (let i = tabs.length - 1; i >= 0; i--) { const tab = tabs[i]; if (onlyVisible && !tab.isVisible()) { continue; } if (tab === this.getActiveTab()) { activeFound = true; } else if (activeFound) { previousTab = tab; break; } } return previousTab; } removeTab(id: string): void { const tab = this.getTab(id); if (!tab) { return; } tab.getRootNode().removeChildren(); this.tabs.delete(id); Dom.remove(tab.getLabelContainer(), this.getLabelsContainer()); Dom.remove(tab.getContainer(), this.getTabContentsContainer()); if (tab.getHeader()) { Dom.remove(tab.getHeader().getContainer(), this.getHeaderContainer()); } if (tab.getFooter()) { Dom.remove(tab.getFooter().getContainer(), this.getFooterContainer()); } this.selectFirstTab(); } addEntity(entity: Entity | EntityOptions): Entity { if (Type.isPlainObject(entity)) { entity = new Entity(entity); } if (!(entity instanceof Entity)) { throw new Error('EntitySelector: an entity must be an instance of EntitySelector.Entity.'); } if (this.hasEntity(entity.getId())) { console.error(`EntitySelector: the "${entity.getId()}" entity is already existed.`); return entity; } this.entities.set(entity.getId(), entity); return entity; } getEntity(id: string): ?Entity { return this.entities.get(id) || null; } hasEntity(id: string): boolean { return this.entities.has(id); } getEntities(): Entity[] { return Array.from(this.entities.values()); } removeEntity(id: string): void { this.removeEntityItems(id); this.entities.delete(id); } removeEntityItems(id: string): void { const items = this.getEntityItemsInternal(id); if (items) { items.forEach((item: Item) => { this.removeItem(item); }); } } getHeader(): ?BaseHeader { return this.header; } getActiveHeader(): ?BaseHeader { if (!this.getActiveTab()) { return null; } if (this.getActiveTab().getHeader()) { return this.getActiveTab().getHeader(); } return this.getHeader() && this.getActiveTab().canShowDefaultHeader() ? this.getHeader() : null; } /** * @internal */ adjustHeader(): void { if (!this.getActiveTab()) { return; } if (this.getActiveTab().getHeader()) { if (this.getHeader()) { this.getHeader().hide(); } this.getActiveTab().getHeader().show(); } else { if (this.getHeader()) { if (this.getActiveTab().canShowDefaultHeader()) { this.getHeader().show(); } else { this.getHeader().hide(); } } } } setHeader(headerContent: ?HeaderContent, headerOptions?: HeaderOptions): ?BaseHeader { /** @var {BaseHeader} */ let header = null; if (headerContent !== null) { header = this.constructor.createHeader(this, headerContent, headerOptions); if (header === null) { return null; } } if (this.isRendered() && this.getHeader() !== null) { Dom.remove(this.getHeader().getContainer()); this.adjustHeader(); } this.header = header; if (this.isRendered()) { this.appendHeader(header); this.adjustHeader(); } return header; } /** * @internal */ appendHeader(header: BaseHeader): void { if (header instanceof BaseHeader) { Dom.append(header.getContainer(), this.getHeaderContainer()); } } /** * @internal */ static createHeader(context: Dialog | Tab, headerContent: HeaderContent, headerOptions?: HeaderOptions): ?BaseHeader { if ( !Type.isStringFilled(headerContent) && !Type.isArrayFilled(headerContent) && !Type.isDomNode(headerContent) && !Type.isFunction(headerContent) ) { return null; } /** @var {BaseHeader} */ let header = null; const options = Type.isPlainObject(headerOptions) ? headerOptions : {}; if (Type.isFunction(headerContent) || Type.isString(headerContent)) { const className = Type.isString(headerContent) ? Reflection.getClass(headerContent) : headerContent; if (Type.isFunction(className)) { header = new className(context, options); if (!(header instanceof BaseHeader)) { console.error('EntitySelector: header is not an instance of BaseHeader.'); header = null; } } } if (headerContent !== null && !header) { header = new DefaultHeader(context, Object.assign({}, options, { content: headerContent })); } return header; } getFooter(): ?BaseFooter { return this.footer; } getActiveFooter(): ?BaseFooter { if (!this.getActiveTab()) { return null; } if (this.getActiveTab().getFooter()) { return this.getActiveTab().getFooter(); } return this.getFooter() && this.getActiveTab().canShowDefaultFooter() ? this.getFooter() : null; } /** * @internal */ adjustFooter(): void { if (!this.getActiveTab()) { return; } if (this.getActiveTab().getFooter()) { if (this.getFooter()) { this.getFooter().hide(); } this.getActiveTab().getFooter().show(); } else { if (this.getFooter()) { if (this.getActiveTab().canShowDefaultFooter()) { this.getFooter().show(); } else { this.getFooter().hide(); } } } } setFooter(footerContent: ?FooterContent, footerOptions?: FooterOptions): ?BaseFooter { /** @var {BaseFooter} */ let footer = null; if (footerContent !== null) { footer = this.constructor.createFooter(this, footerContent, footerOptions); if (footer === null) { return null; } } if (this.isRendered() && this.getFooter() !== null) { Dom.remove(this.getFooter().getContainer()); this.adjustFooter(); } this.footer = footer; if (this.isRendered()) { this.appendFooter(footer); this.adjustFooter(); } return footer; } /** * @internal */ appendFooter(footer: BaseFooter): void { if (footer instanceof BaseFooter) { Dom.append(footer.getContainer(), this.getFooterContainer()); } } /** * @internal */ static createFooter(context: Dialog | Tab, footerContent: FooterContent, footerOptions?: FooterOptions): ?BaseFooter { if ( !Type.isStringFilled(footerContent) && !Type.isArrayFilled(footerContent) && !Type.isDomNode(footerContent) && !Type.isFunction(footerContent) ) { return null; } /** @var {BaseFooter} */ let footer = null; const options = Type.isPlainObject(footerOptions) ? footerOptions : {}; if (Type.isFunction(footerContent) || Type.isString(footerContent)) { const className = Type.isString(footerContent) ? Reflection.getClass(footerContent) : footerContent; if (Type.isFunction(className)) { footer = new className(context, options); if (!(footer instanceof BaseFooter)) { console.error('EntitySelector: footer is not an instance of BaseFooter.'); footer = null; } } } if (footerContent !== null && !footer) { footer = new DefaultFooter(context, Object.assign({}, options, { content: footerContent })); } return footer; } getId(): string { return this.id; } getContext(): ?string { return this.context; } getNavigation(): Navigation { return this.navigation; } deselectAll(): void { this.getSelectedItems().forEach((item: Item) => { item.deselect(); }); } isMultiple(): boolean { return this.multiple; } setTargetNode(node: HTMLElement | { left: number, top: number } | null | MouseEvent): void { if (!Type.isDomNode(node) && !Type.isNull(node) && !Type.isObject(node)) { return; } this.targetNode = node; if (this.isRendered()) { this.getPopup().setBindElement(this.targetNode); this.getPopup().adjustPosition(); } } getTargetNode(): ?HTMLElement { if (this.targetNode === null) { if (this.getTagSelectorMode() === TagSelectorMode.OUTSIDE) { return this.getTagSelector().getOuterContainer(); } } return this.targetNode; } setHideOnSelect(flag: boolean): void { if (Type.isBoolean(flag)) { this.hideOnSelect = flag; } } shouldHideOnSelect(): boolean { if (this.hideOnSelect !== null) { return this.hideOnSelect; } return !this.isMultiple(); } setHideOnDeselect(flag: boolean): void { if (Type.isBoolean(flag)) { this.hideOnDeselect = flag; } } shouldHideOnDeselect(): boolean { if (this.hideOnDeselect !== null) { return this.hideOnDeselect; } return false; } setClearSearchOnSelect(flag: boolean): void { if (Type.isBoolean(flag)) { this.clearSearchOnSelect = flag; } } shouldClearSearchOnSelect(): boolean { return this.clearSearchOnSelect; } setAddTagOnSelect(flag: boolean): void { if (Type.isBoolean(flag)) { this.addTagOnSelect = flag; } } shouldAddTagOnSelect(): boolean { if (this.addTagOnSelect !== null) { return this.addTagOnSelect; } return this.isMultiple() || this.isTagSelectorOutside(); } setShowAvatars(flag: boolean): void { if (Type.isBoolean(flag)) { this.showAvatars = flag; if (this.isRendered()) { this.getTabs().forEach((tab: Tab) => { tab.renderContainer(); }); } } } shouldShowAvatars(): boolean { return this.showAvatars; } setRecentItemsLimit(recentItemsLimit: number): void { if (Type.isNumber(recentItemsLimit) && recentItemsLimit > 0) { this.recentItemsLimit = recentItemsLimit; } } getRecentItemsLimit(): ?number { return this.recentItemsLimit; } setOffsetAnimation(flag: boolean): any { if (Type.isBoolean(flag)) { this.offsetAnimation = flag; if (this.isRendered() && !this.offsetAnimation) { Dom.removeClass(this.getPopup().getPopupContainer(), 'ui-selector-popup-offset-animation'); } } } isCompactView(): boolean { return this.compactView; } setAutoHide(enable: boolean): void { if (Type.isBoolean(enable)) { this.autoHide = enable; if (this.isRendered()) { this.getPopup().setAutoHide(enable); } } } isAutoHide(): boolean { return this.autoHide; } setAutoHideHandler(handler?: (event: MouseEvent, dialog: Dialog) => boolean): void { if (Type.isFunction(handler) || handler === null) { this.autoHideHandler = handler; } } setHideByEsc(enable: boolean): void { if (Type.isBoolean(enable)) { this.hideByEsc = enable; if (this.isRendered()) { this.getPopup().setClosingByEsc(enable); } } } shouldHideByEsc(): boolean { return this.hideByEsc; } getWidth(): number { return this.width; } setWidth(width: number): void { if (Type.isNumber(width) && width > 0) { this.width = width; if (this.isRendered()) { Dom.style(this.getContainer(), 'width', `${width}px`); } } } getHeight(): number { return this.height; } setHeight(height: number): Promise { if (Type.isNumber(height) && height > 0) { this.height = height; if (this.isRendered()) { Dom.style(this.getContainer(), 'height', `${height}px`); return Animation.handleTransitionEnd(this.getContainer(), 'height'); } else { return Promise.resolve(); } } else { return Promise.resolve(); } } getOffsetLeft(): number { return this.offsetLeft; } setOffsetLeft(offset: number): void { if (Type.isNumber(offset) && offset >= 0) { this.offsetLeft = offset; if (this.isRendered()) { this.getPopup().setOffset({ offsetLeft: offset }); this.adjustPosition(); } } } getOffsetTop(): number { return this.offsetTop; } setOffsetTop(offset: number): void { if (Type.isNumber(offset) && offset >= 0) { this.offsetTop = offset; if (this.isRendered()) { this.getPopup().setOffset({ offsetTop: offset }); this.adjustPosition(); } } } getZindex(): number { return this.getPopup().getZindex(); } isCacheable(): boolean { return this.cacheable; } setCacheable(cacheable: boolean): void { if (Type.isBoolean(cacheable)) { this.cacheable = cacheable; if (this.isRendered()) { this.getPopup().setCacheable(cacheable); } } } shouldFocusOnFirst(): boolean { return this.focusOnFirst; } setFocusOnFirst(flag: boolean): void { if (Type.isBoolean(flag)) { this.focusOnFirst = flag; } } focusOnFirstNode(): ?ItemNode { if (this.getActiveTab()) { const itemNode = this.getActiveTab().getRootNode().getFirstChild(); if (itemNode) { itemNode.focus(); return itemNode; } } return null; } getFocusedNode(): ?ItemNode { return this.focusedNode; } clearNodeFocus(): void { if (this.focusedNode) { this.focusedNode.unfocus(); this.focusedNode = null; } } isDropdownMode(): boolean { return this.dropdownMode; } setPreselectedItems(itemIds: ItemId[]): void { this.preselectedItems = this.validateItemIds(itemIds); } getPreselectedItems(): ItemId[] { return this.preselectedItems; } setUndeselectedItems(itemIds: ItemId[]): void { this.undeselectedItems = this.validateItemIds(itemIds); } getUndeselectedItems() { return this.undeselectedItems; } /** * @private */ setOptions(dialogOptions: DialogOptions): void { const options = Type.isPlainObject(dialogOptions) ? dialogOptions : {}; if (Type.isArray(options.tabs)) { options.tabs.forEach((tab) => { this.addTab(tab); }); } if (Type.isArray(options.selectedItems)) { options.selectedItems.forEach((itemOptions: ItemOptions) => { const options = Object.assign({}, Type.isPlainObject(itemOptions) ? itemOptions : {}); options.selected = true; this.addItem(options); }); } if (Type.isArray(options.items)) { options.items.forEach((itemOptions: ItemOptions) => { this.addItem(itemOptions); }); } this.setHeader(options.header, options.headerOptions); this.setFooter(options.footer, options.footerOptions); } getMaxLabelWidth(): number { return this.maxLabelWidth; } getMinLabelWidth(): number { return this.minLabelWidth; } getTagSelector(): ?TagSelector { return this.tagSelector; } getTagSelectorMode(): ?TagSelectorMode { return this.tagSelectorMode; } isTagSelectorInside(): boolean { return this.getTagSelector() && this.getTagSelectorMode() === TagSelectorMode.INSIDE; } isTagSelectorOutside(): boolean { return this.getTagSelector() && this.getTagSelectorMode() === TagSelectorMode.OUTSIDE; } getTagSelectorQuery(): string { return this.getTagSelector() ? this.getTagSelector().getTextBoxValue() : ''; } /** * @private */ setTagSelector(tagSelector: TagSelector): void { this.tagSelector = tagSelector; this.tagSelector.subscribe('onInput', Runtime.debounce(this.handleTagSelectorInput, 200, this)); this.tagSelector.subscribe('onAddButtonClick', this.handleTagSelectorAddButtonClick.bind(this)); this.tagSelector.subscribe('onTagRemove', this.handleTagSelectorTagRemove.bind(this)); this.tagSelector.subscribe('onAfterTagRemove', this.handleTagSelectorAfterTagRemove.bind(this)); this.tagSelector.subscribe('onAfterTagAdd', this.handleTagSelectorAfterTagAdd.bind(this)); this.tagSelector.subscribe('onContainerClick', this.handleTagSelectorClick.bind(this)); this.tagSelector.setDialog(this); } focusSearch(): void { if (this.getTagSelector()) { if (this.getActiveTab() !== this.getSearchTab()) { this.getTagSelector().clearTextBox(); } this.getTagSelector().focusTextBox(); } } clearSearch(): void { if (this.getTagSelector()) { this.getTagSelector().clearTextBox(); if (this.getActiveTab() === this.getSearchTab()) { this.selectFirstTab(); } } } getLoader(): Loader { if (this.loader === null) { this.loader = new Loader({ target: this.getTabsContainer(), size: 100 }); } return this.loader; } showLoader(): void { void this.getLoader().show(); } hideLoader(): void { if (this.loader !== null) { void this.getLoader().hide(); } } destroyLoader(): void { if (this.loader !== null) { this.getLoader().destroy(); } this.loader = null; } getPopup(): Popup { if (this.popup !== null) { return this.popup; } this.getTabs().forEach((tab: Tab) => { this.insertTab(tab); }); const popupOptions = { ...this.popupOptions }; const userEvents = popupOptions.events; delete popupOptions.events; this.popup = new Popup({ contentPadding: 0, padding: 0, offsetTop: this.getOffsetTop(), offsetLeft: this.getOffsetLeft(), animation: { showClassName: 'ui-selector-popup-animation-show', closeClassName: 'ui-selector-popup-animation-close', closeAnimationType: 'animation', }, bindElement: this.getTargetNode(), bindOptions: { forceBindPosition: true, }, autoHide: this.isAutoHide(), autoHideHandler: this.handleAutoHide.bind(this), closeByEsc: this.shouldHideByEsc(), cacheable: this.isCacheable(), events: { onFirstShow: this.handlePopupFirstShow.bind(this), onShow: this.handlePopupShow.bind(this), onAfterShow: this.handlePopupAfterShow.bind(this), onAfterClose: this.handlePopupAfterClose.bind(this), onDestroy: this.handlePopupDestroy.bind(this), }, content: this.getContainer(), ...popupOptions, }); this.popup.subscribeFromOptions(userEvents); this.rendered = true; this.selectFirstTab(); return this.popup; } isRendered(): boolean { return this.rendered; } getContainer(): HTMLElement { return this.cache.remember('container', () => { let searchContainer = ''; if (this.getTagSelectorMode() === TagSelectorMode.INSIDE) { searchContainer = Tag.render`<div class="ui-selector-search"></div>`; this.getTagSelector().renderTo(searchContainer); } const className = this.isCompactView() ? ' ui-selector-dialog--compact-view' : ''; return Tag.render` <div class="ui-selector-dialog${className}" style="width:${this.getWidth()}px; height:${this.getHeight()}px;" > ${this.getHeaderContainer()} ${searchContainer} ${this.getTabsContainer()} ${this.getFooterContainer()} </div> `; }); } getTabsContainer(): HTMLElement { return this.cache.remember('tabs-container', () => { return Tag.render` <div class="ui-selector-tabs"> ${this.getTabContentsContainer()} ${this.getLabelsContainer()} </div> `; }); } getTabContentsContainer(): HTMLElement { return this.cache.remember('tab-contents', () => { return Tag.render`<div class="ui-selector-tab-contents"></div>`; }); } getLabelsContainer(): HTMLElement { return this.cache.remember('labels-container', () => { return Tag.render` <div class="ui-selector-tab-labels" onmouseenter="${this.handleLabelsMouseEnter.bind(this)}" onmouseleave="${this.handleLabelsMouseLeave.bind(this)}" ></div> `; }); } getHeaderContainer(): HTMLElement { return this.cache.remember('header', () => { const header = this.getHeader() && this.getHeader().getContainer(); return Tag.render` <div class="ui-selector-header-container">${header ? header : ''}</div> `; }); } getFooterContainer(): HTMLElement { return this.cache.remember('footer', () => { const footer = this.getFooter() && this.getFooter().getContainer(); return Tag.render` <div class="ui-selector-footer-container">${footer ? footer : ''}</div> `; }); } freeze(): void { if (this.isFrozen()) { return; } this.frozenProps = { autoHide: this.isAutoHide(), hideByEsc: this.shouldHideByEsc(), }; this.setAutoHide(false); this.setHideByEsc(false); this.getNavigation().disable(); Dom.addClass(this.getContainer(), 'ui-selector-dialog--freeze'); this.frozen = true; } unfreeze(): void { if (!this.isFrozen()) { return; } this.setAutoHide(this.frozenProps.autoHide !== false); this.setHideByEsc(this.frozenProps.hideByEsc !== false); this.getNavigation().enable(); Dom.removeClass(this.getContainer(), 'ui-selector-dialog--freeze'); this.frozen = false; } isFrozen(): boolean { return this.frozen; } hasRecentItems(): Promise { return new Promise((resolve, reject) => { Ajax .runAction('ui.entityselector.load', { json: { dialog: this.getAjaxJson() }, getParameters: { context: this.getContext() } }) .then((response) => { resolve( response.data && response.data.dialog && Type.isArrayFilled(response.data.dialog.recentItems) ); }) .catch((error) => { reject(error); }) ; }); } load(): void { if (this.loadState !== LoadState.UNSENT || !this.hasDynamicLoad()) { return; } if (this.getTagSelector()) { this.getTagSelector().lock(); } setTimeout(() => { if (this.isLoading()) { this.showLoader(); } }, 400); this.loadState = LoadState.LOADING; Ajax.runAction('ui.entityselector.load', { json: { dialog: this.getAjaxJson() }, getParameters: { context: this.getContext() } }) .then((response) => { if (response && response.data && Type.isPlainObject(response.data.dialog)) { this.loadState = LoadState.DONE; const entities = Type.isArrayFilled(response.data.dialog.entities) ? response.data.dialog.entities : [] ; entities.forEach((entityOptions: EntityOptions) => { const entity = this.getEntity(entityOptions.id); if (entity) { entity.setDynamicSearch(entityOptions.dynamicSearch); } }); this.setOptions(response.data.dialog); this.getPreselectedItems().forEach((preselectedItem: ItemId) => { const item = this.getItem(preselectedItem); if (item) { item.select(true); } }); const recentItems = response.data.dialog.recentItems; if (Type.isArray(recentItems)) { const nodeOptionsMap: Map<Item, ItemNodeOptions> = new Map(); const itemsOptions: ItemOptions[] = response.data.dialog.items; if (Type.isArray(itemsOptions)) { itemsOptions.forEach((itemOptions: ItemOptions) => { if (itemOptions.nodeOptions) { const item = this.getItem(itemOptions); if (item) { nodeOptionsMap.set(item, itemOptions.nodeOptions); } } }); } const items = recentItems.map((recentItem: ItemId) => { const item = this.getItem(recentItem); return [item, nodeOptionsMap.get(item)]; }); this.getRecentTab().getRootNode().addItems(items); } if (!this.getRecentTab().getRootNode().hasChildren() && this.getRecentTab().getStub()) { this.getRecentTab().getStub().show(); } if (this.getTagSelector()) { this.getTagSelector().unlock(); } if (this.isRendered()) { if (this.isDropdownMode() && this.getActiveTab() === this.getRecentTab()) { this.selectFirstTab(); } else if (!this.getActiveTab()) { this.selectFirstTab(); } } this.focusSearch(); this.destroyLoader(); if (this.shouldFocusOnFirst()) { this.focusOnFirstNode(); } this.emit('onLoad'); } }) .catch((error) => { this.loadState = LoadState.UNSENT; if (this.getTagSelector()) { this.getTagSelector().unlock(); } this.focusSearch(); this.destroyLoader(); this.emit('onLoadError', { error }); console.error(error); }); } isLoaded(): boolean { return this.loadState === LoadState.DONE; } isLoading(): boolean { return this.loadState === LoadState.LOADING; } hasDynamicLoad(): boolean { let hasDynamicLoad = false; this.entities.forEach((entity: Entity) => { hasDynamicLoad = hasDynamicLoad || entity.hasDynamicLoad(); }); return hasDynamicLoad; } hasDynamicSearch(): boolean { let hasDynamicSearch = false; this.entities.forEach((entity: Entity) => { hasDynamicSearch = hasDynamicSearch || (entity.isSearchable() && entity.hasDynamicSearch()); }); return hasDynamicSearch; } saveRecentItem(item: Item): void { if (this.getContext() === null || !item.isSaveable()) { return; } this.recentItemsToSave.push(item); this.saveRecentItemsWithDebounce(); } /** * @private */ saveRecentItems(): void { if (!Type.isArrayFilled(this.recentItemsToSave)) { return; } Ajax.runAction('ui.entityselector.saveRecentItems', { json: { dialog: this.getAjaxJson(), recentItems: this.recentItemsToSave.map((item: Item) => item.getAjaxJson()) }, getParameters: { context: this.getContext() } }) .then((response) => { }) .catch((error) => { console.error(error); }); this.recentItemsToSave = []; } shouldClearUnavailableItems(): boolean { return this.clearUnavailableItems; } /** * @private */ handleTagSelectorInput(): void { if (this.getTagSelectorMode() === TagSelectorMode.OUTSIDE && !this.isOpen()) { this.show(); } const query = this.getTagSelector().getTextBoxValue(); this.search(query); } /** * @private */ handleTagSelectorAddButtonClick(): void { this.show(); } /** * @private */ handleTagSelectorTagRemove(event: BaseEvent): void { const { tag } = event.getData(); const item = this.getItem({ id: tag.getId(), entityId: tag.getEntityId() }); if (item) { item.deselect(); } this.focusSearch(); } /** * @private */ handleTagSelectorAfterTagRemove(): void { this.adjustByTagSelector(); } /** * @private */ handleTagSelectorAfterTagAdd(): void { this.adjustByTagSelector(); } /** * @private */ adjustByTagSelector(): void { if (this.getTagSelectorMode() === TagSelectorMode.OUTSIDE) { this.adjustPosition(); } else if (this.getTagSelectorMode() === TagSelectorMode.INSIDE) { const newTagSelectorHeight = this.getTagSelector().calcHeight(); if (newTagSelectorHeight > 0) { const offset = newTagSelectorHeight - (this.tagSelectorHeight || this.getTagSelector().getMinHeight()); this.tagSelectorHeight = newTagSelectorHeight; if (offset !== 0) { const height = this.getHeight(); this.setHeight(height + offset).then(() => { this.adjustPosition(); }); } } } } /** * @private */ handleTagSelectorClick(): void { this.focusSearch(); } /** * @internal */ handleItemSelect(item: Item, animate: boolean = true): void { const shouldAnimate: boolean = this.isMultiple() ? animate : this.getSelectedItems().length === 0 ; if (!this.isMultiple()) { this.deselectAll(); if (this.getSelectedItems().length > 0) { console.error('EntitySelector: some items are still selected.', this.getSelectedItems()); } } if (this.getTagSelector() && this.shouldAddTagOnSelect()) { const tag: TagItemOptions = item.createTag(); tag.animate = shouldAnimate; this.getTagSelector().addTag(tag); } this.selectedItems.add(item); } /** * @internal */ handleItemDeselect(item: Item): void { const shouldAnimate: boolean = this.isMultiple(); this.selectedItems.delete(item); if (this.getTagSelector()) { this.getTagSelector().removeTag({ id: item.getId(), entityId: item.getEntityId() }, shouldAnimate); } } /** * @private */ handlePopupAfterShow(): void { this.focusSearch(); this.adjustByTagSelector(); this.emit('onShow'); } /** * @private */ handlePopupFirstShow(): void { this.emit('onFirstShow'); this.observeTabOverlapping(); } /** * @private */ handlePopupShow(): void { if (this.offsetAnimation) { requestAnimationFrame(() => { requestAnimationFrame(() => { Dom.addClass(this.getPopup().getPopupContainer(), 'ui-selector-popup-offset-animation'); }); }); } } /** * @private */ handleAutoHide(event: MouseEvent): void { const target = event.target; const el = this.getPopup().getPopupContainer(); if (target === el || el.contains(target)) { return false; } if ( this.isTagSelectorOutside() && target === this.getTagSelector().getTextBox() && Type.isStringFilled(this.getTagSelector().getTextBoxValue()) ) { return false; } if (this.autoHideHandler !== null) { const result = this.autoHideHandler(event, this); if (Type.isBoolean(result)) { return result; } } return true; } /** * @private */ observeTabOverlapping(): void { this.disconnectTabOverlapping(); this.overlappingObserver = new MutationObserver(() => { if (this.getLabelsContainer().offsetWidth > 0) { const left = parseInt(this.getPopup().getPopupContainer().style.left, 10); if (left < this.getMinLabelWidth()) { Dom.style(this.getPopup().getPopupContainer(), 'left', `${this.getMinLabelWidth()}px`); } } }); this.overlappingObserver.observe(this.getPopup().getPopupContainer(), { attributes: true, attributeFilter: ['style'] }); } /** * @private */ disconnectTabOverlapping(): void { if (this.overlappingObserver) { this.overlappingObserver.disconnect(); } } /** * @private */ handlePopupAfterClose(): void { if (this.isTagSelectorOutside()) { if (this.getActiveTab() && this.getActiveTab() === this.getSearchTab()) { this.selectFirstTab(); } this.getTagSelector().clearTextBox(); this.getTagSelector().showAddButton(); this.getTagSelector().hideTextBox(); } if (this.offsetAnimation) { Dom.removeClass(this.getPopup().getPopupContainer(), 'ui-selector-popup-offset-animation'); } this.emit('onHide'); } /** * @private */ handlePopupDestroy(): void { this.destroy(); } /** * @private */ handleLabelsMouseEnter(): void { const rect = Dom.getRelativePosition(this.getLabelsContainer(), this.getPopup().getTargetContainer()); const freeSpace = rect.right; if (freeSpace > this.getMinLabelWidth()) { Dom.removeClass(this.getLabelsContainer(), 'ui-selector-tab-labels--animate-hide'); Dom.addClass(this.getLabelsContainer(), 'ui-selector-tab-labels--animate-show'); Dom.style(this.getLabelsContainer(), 'max-width', `${Math.min(freeSpace, this.getMaxLabelWidth())}px`); Animation.handleTransitionEnd(this.getLabelsContainer(), 'max-width').then(() => { Dom.removeClass(this.getLabelsContainer(), 'ui-selector-tab-labels--animate-show'); Dom.addClass(this.getLabelsContainer(), 'ui-selector-tab-labels--active'); }); } else { Dom.addClass(this.getLabelsContainer(), 'ui-selector-tab-labels--active'); } } /** * @private */ handleLabelsMouseLeave(): void { Dom.addClass(this.getLabelsContainer(), 'ui-selector-tab-labels--animate-hide'); Dom.removeClass(this.getLabelsContainer(), 'ui-selector-tab-labels--animate-show'); Dom.removeClass(this.getLabelsContainer(), 'ui-selector-tab-labels--active'); Animation.handleTransitionEnd(this.getLabelsContainer(), 'max-width').then(() => { Dom.removeClass(this.getLabelsContainer(), 'ui-selector-tab-labels--animate-hide'); }); Dom.style(this.getLabelsContainer(), 'max-width', null); } /** * @private */ handleItemNodeFocus(event: BaseEvent): void { const { node } = event.getData(); if (this.focusedNode === node) { return; } this.clearNodeFocus(); this.focusedNode = node; } /** * @private */ handleItemNodeUnfocus(): void { this.clearNodeFocus(); } getAjaxJson(): { [key: string]: any } { return { id: this.getId(), context: this.getContext(), entities: this.getEntities(), preselectedItems: this.getPreselectedItems(), recentItemsLimit: this.getRecentItemsLimit(), clearUnavailableItems: this.shouldClearUnavailableItems(), }; } }