UNPKG

activator-oce-exporter

Version:

Extract Activator binder and convert it to valid OCE mono pacakge

325 lines (283 loc) 9.71 kB
import { FusionApi } from '../api'; import { getValueObject } from '../utils'; /** * * @description Mixin for work with children which were created from a parent wrapper. It contains a set of methods which help work with children: batch add and remove children, calculation existing items, work with parent wrapper, etc. */ /** * @typedef {object} ComponentEventData * @property {Function<FusionBase>} component - lit-element constructor * @property {object<string>} events * @property {string} component name */ export function ItemsWrapper(superClass) { return class extends superClass { static get properties() { return { ...super.properties, items: { type: String, fieldType: 'Number', value: '1', min: '1', prop: true, }, }; } constructor() { super(); this.wrapperClassName = 'items-wrapper'; } /** * @description Properties which should be synchronized from parent to children * @return {Object.<string, LitElementProperty>} */ static get synchronizableProperties() { return {}; } /** * @description Create item object * @param {Function<FusionBase>} component - lit-element constructor * @returns {ComponentEventData} item */ static createObjectItem(component) { const { componentName } = component.options; return { component, name: componentName, events: { add: `${componentName}:added`, remove: `${componentName}:removed`, }, }; } update(changedProps) { super.update(changedProps); if (!this.isRendered) { this.initItems(); } else if (this.constructor.isStructureAttr(changedProps)) { this.setupItems(changedProps); } } firstUpdated(changedProperties) { super.firstUpdated(changedProperties); this.triggerRequestUpdate(); } triggerRequestUpdate() { const { propCount, domCount } = this.getItemsCount(); if (domCount && propCount !== domCount) { this.requestUpdate('items', domCount); } } getItemsCount() { const { num } = getValueObject(this.items); const domCount = this.constructor.getExistingItems(this.item.name, this).length; return { propCount: num, domCount, }; } initItems() { this.itemsWrapper = this.getWrapper(`.${this.wrapperClassName}`); this.generateContent(); } generateContent(newCount = this.items) { const items = this.constructor.getExistingItems(this.item.name, this); const slots = this.constructor.getExistingItems('slot', this.itemsWrapper); items.length && !slots.length ? this.generateMissedSlots(items) : this.generateItems(newCount); } /** * @description Getting children from the wrapper * @param {string} item name * @param {HTMLElement} node where children will be searched * @returns {HTMLElement[]} array of found items */ static getExistingItems(item, node) { return Array.from(node.getElementsByTagName(item)); } generateMissedSlots(items) { items.forEach(item => this.addSlot(item.getAttribute('slot'))); } generateItems(newCount) { for (let index = 0; index < newCount; index += 1) { this.generateItem(); } } async generateItem() { const slotId = FusionApi.generateId(); const customProps = { slot: { value: slotId } }; const { propCount, domCount } = this.getItemsCount(); if (propCount > domCount) { await this.addGeneratedItem(customProps); } else if (propCount <= domCount) { await this.addCustomItem({ ...customProps, ...{ custom: { value: true } } }); } this.addSlot(slotId); } addCustomItem(customProps = {}) { const element = this.getAddedItem(); const properties = this.getMergedProperties(customProps); FusionApi.setAttributes({ properties, element }); return element; } /** * @description Getting last item from the items array * @returns {HTMLElement} last added item */ getAddedItem() { return this.constructor.getExistingItems(this.item.name, this).pop(); } /** * @description Create child element * @param {Object.<string, LitElementProperty>} [customProps = {}] - custom properties * @returns {Promise<HTMLElement>} */ async addGeneratedItem(customProps = {}) { const children = this.item.component; const properties = this.getMergedProperties(customProps); const { componentName, defaultTemplate } = children.options; return FusionApi.createElement( componentName, properties, defaultTemplate, this, `#${this.id}`, {}, ); } /** * @description merging inheritable and custom properties * @param {Object.<string, LitElementProperty>} customProps - custom properties * @returns {Object.<string, LitElementProperty>} merged properties */ getMergedProperties(customProps) { const properties = this.getStylePropertiesObject(this.constructor.synchronizableProperties); return { ...customProps, ...properties }; } /** * @description create properties object for transferring them to created children * @param {Object.<string, LitElementProperty>} styleProps - object of synchronizable properties * @return {Object.<string, LitElementProperty>} - properties */ getStylePropertiesObject(styleProps = {}) { return Object.keys(styleProps).reduce((childProps, property) => { childProps[property] = { value: this[property] }; return childProps; }, {}); } static isStructureAttr(changedProps) { return changedProps.has('items'); } setupItems(changedProps) { const newVal = Number(this.items); const oldValue = changedProps.get('items') || 0; (newVal <= 0) ? this.clearItems() : this.updateItems(newVal, oldValue); } clearItems() { this.innerHTML = ''; this.itemsWrapper.innerHTML = ''; } updateItems(newVal, oldVal) { const difference = this.constructor.getItemsDifference(newVal, oldVal); newVal > oldVal ? this.generateContent(difference) : this.removeContent(difference); this.saveItemsCount(newVal); } addSlot(slotValue) { const slot = document.createElement('slot'); slot.setAttribute('name', slotValue); this.itemsWrapper.appendChild(slot); } static getItemsDifference(newVal, oldVal) { return Math.abs(newVal - oldVal); } async removeContent(count) { if (this.slotToRemove) { await this.removeSlot(this.slotToRemove); this.slotToRemove = ''; } else { await this.removeItems(count); } } removeItems(count) { const items = this.constructor.getExistingItems(this.item.name, this); const itemsToRemove = items.slice(-count); itemsToRemove.forEach((item) => { FusionApi.deleteElement(item.id); this.removeSlot(item.slot); }); } removeSlot(slotId) { const slots = this.constructor.getExistingItems('slot', this.itemsWrapper); const slotItem = Array.from(slots).find(item => item.name === slotId); this.itemsWrapper.removeChild(slotItem); } connectedCallback() { super.connectedCallback(); this.itemAddEventFunc = this.itemAddHandler.bind(this); this.itemRemoveEventFunc = this.itemRemoveHandler.bind(this); this.addEventListener(this.item.events.add, this.itemAddEventFunc); this.addItemsEventListeners(); } disconnectedCallback() { super.disconnectedCallback(); this.removeItemsEventListeners(); } removeItemsEventListeners() { this.constructor.getExistingItems(this.item.name, this).forEach((item) => { item.removeEventListener(this.item.events.remove, this.itemRemoveEventFunc); item.removeEventListener(this.item.events.add, this.itemAddEventFunc); }); } addItemsEventListeners() { this.constructor.getExistingItems(this.item.name, this).forEach((item) => { item.addEventListener(this.item.events.remove, this.itemRemoveEventFunc); }); } itemAddHandler(event) { const curItem = event.target; if (this.needIncreaseItems()) { this.triggerItemsChange(true); } curItem.addEventListener(this.item.events.remove, this.itemRemoveEventFunc); } itemRemoveHandler(event) { const curItem = event.target; if (this.needDecreaseItems()) { this.slotToRemove = curItem.slot; this.triggerItemsChange(); } } triggerItemsChange(isItemAdded) { const count = Number(this.items); const newItemsCount = isItemAdded ? count + 1 : count - 1; this.saveItemsCount(newItemsCount); } needIncreaseItems() { const { propCount, domCount } = this.getItemsCount(); return domCount > propCount; } needDecreaseItems() { const { propCount, domCount } = this.getItemsCount(); return domCount < propCount; } saveItemsCount(count) { const key = 'items'; this.setAttribute(key, count); FusionApi.saveAttributes(`#${this.id}`, { [key]: count }); } /** * @description Getting wrapper by name * @param {string} wrapper name * @returns {HTMLElement} wrapper */ getWrapper(wrapper) { return this.shadowRoot.querySelector(wrapper); } }; }