import { getElement } from './utils/handle/element'; import { getElements } from './utils/handle/elements'; import { disableFormSubmission, formValue, formValues, formValidate, formError, formSuccess,formErrorHide, formSubmitLoader, formReset } from './utils/handle/form'; import { getPageName } from './utils/page/url/name'; import { getParameters, getParameter } from './utils/page/url/parameters'; import { RenderOptions, RenderConfig, ConfigAction } from './utils/types'; import { renderData, updateData, clearData} from './utils/render/render'; import { DataStore } from './utils/dataStore'; import { SortableTable } from './utils/table'; import { skelaton } from './utils/handle/skeleton'; import OTPInputHandler from './utils/handle/otpInput'; /** * Thind class for handling DOM manipulation and form submission in Weblow. * @class Thind * @example * const thind = new Thind({ * attribute: 'data-my-el' * }); * This will create a new instance of Thind with the unique attribute set to 'data-my-el' */ export class Thind { uniqueAttribute: string; constructor({ attribute = 'data-my-el' }: { attribute?: string } = {}) { this.uniqueAttribute = attribute; } /** * Get an element from the DOM using the unique attribute * @param value * @param parent * @returns * @memberof Thind * @method element.get * @example * thind.element.get('my-element') * This will return the element with the unique attribute 'data-my-el' set to 'my-element' * @example * thind.element.getAll('my-element', parentElement) * This will return all elements with the unique attribute 'data-my-el' set to 'my-element' within the parent element */ get element(): { get: (value: string, parent?: HTMLElement) => HTMLElement | null; getAll: (value: string, parent?: HTMLElement) => NodeListOf; } { return { get: (value: string, parent?: HTMLElement): HTMLElement | null => { return getElement(value, this.uniqueAttribute, parent) as HTMLElement; }, getAll: (value: string, parent?: HTMLElement): NodeListOf => { return getElements(value, this.uniqueAttribute, parent) as NodeListOf; } }; } /** * Skeleton function creates a loading skeleton for an elemement pand class provided. * Skeleton handles all the elements like dics, secitons, container, images, text and input. Once you provide it any element, this function will clone the class onto a div and hide the actual element. * Then it will show the skeleton div with the class provided. You can than toggle the skeleton off by providing the original element and the class, this will destoy the skeleton and show the original element. * @param element * @param show by default it is true * @param classToAdd by default it is 'skeleton' * @example * thind.skeleton.toggle(thind.element.get('my-element'), true, 'skeleton') */ get skeleton(): { toggle: (element: HTMLElement, show: boolean, classToAdd: string) => void; } { return { toggle: (element: HTMLElement, show: boolean = true, classToAdd: string): void => { skelaton(element, show, classToAdd); } }; } /** * Form functions for handling form submission and validation * @memberof Thind * @method form */ get form(): { /** * Disable form submission * @param form * @memberof Thind.form * @method disable * @example * thind.form.disable(thind.element.get('my-form')) */ disable: (form: HTMLFormElement) => void; /** * Get the value of a form input * @param form * @param key * @returns * @memberof Thind.form * @method value * @example * thind.form.value(thind.element.get('my-form'), 'email') */ value: (form: HTMLFormElement, key: string) => string; /** * Get the values of all form inputs * @param form * @returns * @memberof Thind.form * @method values * @example * thind.form.values(thind.element.get('my-form')) */ values: (form: HTMLFormElement) => { [key: string]: string }; /** * Validate a form, my making sure all required fields are filled * @param form * @returns * @memberof Thind.form * @method isValid * @example * thind.form.isValid(thind.element.get('my-form')) */ isValid: (form: HTMLFormElement) => boolean; /** * Show native Webflow Form error message state. Pass the form element, the message to show. * @param form * @param message * @memberof Thind.form * @method error * @example * thind.form.error(thind.element.get('my-form'), 'Email is required') */ error: (form: HTMLFormElement, message: string) => void; /** * Hide the native Webflow Form error message state * @param form * @memberof Thind.form * @method errorHide * @example * thind.form.errorHide(thind.element.get('my-form')) */ errorHide: (form: HTMLFormElement) => void; /** * Show a success message for a form submission * @param form * @param formSubmitAction choose from 'success', 'redirect'. If 'success' is chosen, the nativeSucessMessage will be shown. If 'redirect' is chosen, the form will be redirected to the action attribute of the form. * @param nativeSucessMessage the message to show when the form is successfully submitted. Leave empty if you want to show the default message. * @memberof Thind.form * @method success * @example * thind.form.success(thind.element.get('my-form'), 'Sucess', 'Success') */ success: (form: HTMLFormElement, formSubmitAction: string, nativeSucessMessage: any) => void; /** * Change the submit button state of a form * @param form * @param message the message to show on the submit button * @param disable whether to disable the submit button * @memberof Thind.form * @method changeSubmitButton * @example * thind.form.changeSubmitButton(thind.element.get('my-form'), 'Submitting...', true) * @example * thind.form.changeSubmitButton(thind.element.get('my-form'), 'Submit', false) */ changeSubmitButton: (form: HTMLFormElement, message: string, disable: boolean) => void; /** * Reset a form * @param form * @memberof Thind.form * @method reset * @example * thind.form.reset(thind.element.get('my-form')) */ reset: (form: HTMLFormElement, resetFields: boolean) => void; } { return { disable: (form: HTMLFormElement): void => { disableFormSubmission(form); }, value: (form: HTMLFormElement, key: string): string => { return formValue(form, key); }, values: (form: HTMLFormElement): { [key: string]: string } => { return formValues(form); }, isValid: (form: HTMLFormElement): boolean => { return formValidate(form); }, error: (form: HTMLFormElement, message: string): void => { formError(form, message); }, errorHide: (form: HTMLFormElement): void => { formErrorHide(form); }, success: (form: HTMLFormElement, formSubmitAction: string, nativeSucessMessage: any): void => { formSuccess(form, formSubmitAction, nativeSucessMessage); }, changeSubmitButton: (form: HTMLFormElement, message: string, disable: boolean): void => { formSubmitLoader(form, message, disable); }, reset: (form: HTMLFormElement, resetFields: boolean): void => { formReset(form, resetFields); } }; } /** * Get the current page name and parameters * @memberof Thind * @method page */ get page(): { /** * Get the current page name * @returns * @memberof Thind.page * @method name * @example * thind.page.name() */ name: () => string; /** * Get the current page parameters * @returns * @memberof Thind.page * @method parameters * @example * thind.page.parameters() */ parameters: () => Record; /** * Get a specific page parameter * @param key * @returns * @memberof Thind.page * @method parameter * @example * thind.page.parameter('id') */ parameter: (key: string) => string | undefined; } { return { name: getPageName, parameters: getParameters, parameter: getParameter, }; } } /** * Render class for rendering data to the DOM, with support for nested configurations. * @class Render * @example * const render = new Render({ * element: document.getElementById('my-element'), * data: [{ name: 'John' }, { name: 'Doe' }], * config: { * action: (data, element) => { * element.innerHTML = data.name; * } * } * }); * This render options can be used with static data or with a DataStore, Whn used with a DataStore, the data key must be provided and REnder will subscribe to the DataStore for updates. */ export class Render { data: Record[]; htmlObject: HTMLElement; template: HTMLElement; parent: HTMLElement; config: RenderConfig; prop: string; hideEmptyParent: boolean; childRenders: Render[]; clearOnUpdate: boolean; emptyState: HTMLElement; configAction: ConfigAction; preprocessAction?: (data: any) => any; configAfter?: ConfigAction; dataStore: DataStore; unsubscribe: Function; constructor({ element, data, config = {}, prop, clearOnUpdate = false, hideEmptyParent, emptyState, action, dataStore, key }: RenderOptions & { dataStore: DataStore }) { if (!dataStore && !data ) { throw new Error('DataStore or data is required'); } if (dataStore && !key) { throw new Error('Key is required when using DataStore'); } if (dataStore && !dataStore.state[key]) { throw new Error(`Key ${key} does not exist in DataStore`); } if (!(element instanceof HTMLElement)) { throw new TypeError('Expected an HTMLElement for "element"'); } this.preprocessAction = action; this.childRenders = []; this.htmlObject = element; this.config = config.props; this.configAction = config.action; this.configAfter = config.after; this.prop = prop; this.data = data; this.clearOnUpdate = clearOnUpdate; this.hideEmptyParent = hideEmptyParent; this.parent = element.parentElement; this.emptyState = emptyState; this.dataStore = dataStore; if (dataStore && key){ this.unsubscribe = dataStore.subscribe(key, (newData) => { this.data = newData; this.render(); }); } this.render(); } async init() { // If a preprocess action is defined, execute it first if (this.preprocessAction) { await this.preprocessAction(this.data); } } /** * Render the data to the DOM using the configuration provided */ render = async (): Promise => { try { await renderData.call(this); } catch (error) { throw error; } } /** * Update the data in the Render instance * @param data * @memberof Render * @method update * @example * uREnder.update([{ name: 'Jane' }]) * This will update the data in the Render instance with the new data * and re-render the data to the DOM */ update = (data: Record[]): void => { updateData.call(this, data); } /** * Clear the data in the Render instance * @param clearParent * @memberof Render * @method clear * @example * Render.clear() * This will clear the data in the Render instance and remove the parent element * from the DOM */ clear = (clearParent: boolean = this.clearOnUpdate): void => { clearData.call(this, clearParent); } destroy() { this.unsubscribe(); } } export { DataStore, SortableTable, OTPInputHandler };