Newer
Older
vmk-ui-kit / src / js / components / editable-string.js
/**
 * editableString browser helper.
 *
 * Mirrors gnexus-ui-kit editableString public surface.
 *
 * @param {HTMLElement} stringContainer - Container to replace with editable component
 * @param {boolean} [isMultiString=false] - Use textarea instead of input
 * @returns {HTMLElement} Component root element
 */

function template(originalText, isMultiString) {
	const placeholder = "Write something";
	const input = !isMultiString
		? `<input type="text" class="input" placeholder="${placeholder}">`
		: `<textarea class="input" placeholder="${placeholder}" rows="3"></textarea>`;

	return `
		<div class="editable-string-content">
			<span class="editable-string">${originalText}</span>
			<button type="button" class="btn-icon edit-text-btn" aria-label="Edit"><i class="ph ph-pencil-simple"></i></button>
		</div>
		<div class="editable-string-form d-none">
			<div class="form-group">
				<label class="label">
					${input}
				</label>
			</div>
			<button type="button" class="btn-icon apply-changes-btn" aria-label="Apply"><i class="ph ph-check"></i></button>
			<button type="button" class="btn-icon cancel-changes-btn" aria-label="Cancel"><i class="ph ph-x"></i></button>
		</div>
	`;
}

export default function editableString(stringContainer, isMultiString = false) {
	const originalText = stringContainer.innerHTML;

	const component = document.createElement("div");
	component.className = "editable-string-component";
	component.innerHTML = template(originalText, isMultiString);

	stringContainer.innerHTML = "";
	stringContainer.append(component);

	const editBtn = component.querySelector(".edit-text-btn");
	const applyBtn = component.querySelector(".apply-changes-btn");
	const cancelBtn = component.querySelector(".cancel-changes-btn");
	const content = component.querySelector(".editable-string-content");
	const editableStringEl = component.querySelector(".editable-string");
	const form = component.querySelector(".editable-string-form");
	const input = component.querySelector(".input");

	const api = {
		formIsDisplaying: false,
		value: originalText,
		input,
		eventsHandlers: {
			onChange: [],
			onSwitch: []
		},
		switch: () => {
			if(api.formIsDisplaying) {
				form.classList.add("d-none");
				content.classList.remove("d-none");
				editableStringEl.textContent = api.value;
			} else {
				form.classList.remove("d-none");
				content.classList.add("d-none");
				input.value = api.value;
				input.focus();
			}

			api.formIsDisplaying = !api.formIsDisplaying;
			api.runEventHandler("onSwitch");
		},
		setValue: val => {
			api.value = val;
			input.value = val;
			editableStringEl.textContent = val;
		},
		apply: () => {
			const previousValue = api.value;
			api.value = input.value;
			api.switch();
			if(input.value !== previousValue) {
				api.runEventHandler("onChange");
			}
		},
		onChange: cb => {
			api.eventsHandlers.onChange.push(cb);
		},
		onSwitch: cb => {
			api.eventsHandlers.onSwitch.push(cb);
		},
		runEventHandler: evName => {
			api.eventsHandlers[evName].forEach(handler => handler(api));
		}
	};

	component.editableString = api;

	editBtn.addEventListener("click", () => api.switch());
	cancelBtn.addEventListener("click", () => api.switch());
	applyBtn.addEventListener("click", () => api.apply());

	input.addEventListener("keydown", event => {
		if(event.key === "Enter" && !isMultiString) {
			input.blur();
			api.apply();
		}
		if(event.key === "Escape") {
			api.switch();
		}
	});

	return component;
}