activator-oce-exporter
Version:
Extract Activator binder and convert it to valid OCE mono pacakge
325 lines (283 loc) • 9.71 kB
JavaScript
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);
}
};
}