Last active
June 13, 2019 01:32
-
-
Save RoryDuncan/c59c7d9e364a2e92d6643f11f8d62bce to your computer and use it in GitHub Desktop.
Simple Focus-based Editor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// A class that manages the event lifecycle, sanitization, and UX resolution when editing an input. | |
// See `Editor.edit()` for primary API usage. | |
// Example code at bottom. | |
// | |
export default class Editor { | |
constructor(maxlength = 60) { | |
this.maxlength = maxlength; | |
this.el = null; | |
// bindings | |
this.keydownHandler = this.keydownHandler.bind(this); | |
this.blurHandler = this.blurHandler.bind(this); | |
} | |
// | |
// properties | |
// | |
get isEditing() { | |
return this.el !== null; | |
} | |
get value() { | |
if (this.isEditing) return this.el.value.trim(); | |
return null; | |
} | |
get isValid() { | |
const value = this.value; | |
if (value === null) return false; | |
if (value.length === 0 || value.length > this.maxlength) return false; | |
if (value === this.initialValue) return false; | |
return true; | |
} | |
// | |
// methods | |
// | |
sanitize(input) { | |
return input.replace(/</gi, "<").replace(/>/gi, ">"); | |
} | |
/** Begins editing the parameter input element, adding event listeners and capturing the initial value. | |
Upon the promise-rejection, Editor will reset it's value to the original value when `.edit()` was first called. | |
* @param {HtmlInputElement} el The element that the user will edit text within. | |
* @returns {Promise} returns a promise that resolves with the new value or rejects with a string reason on why it failed. | |
* @notes Calling `edit` when an edit is already in place will reject the previous edit and begin a new promise-cycle. | |
*/ | |
edit(el) { | |
if (this.isEditing) this.end(); | |
if (el === null) return; | |
const that = this; | |
this.el = el; | |
el.setAttribute("maxlength", this.maxlength); | |
this.initialValue = this.el.value; | |
this.el.setAttribute("placeholder", this.initialValue); | |
this.el.value = ""; | |
el.classList.add("--editing"); | |
el.focus(); | |
// if we want to highlight the selection | |
// document.execCommand('selectAll', false, null); | |
el.addEventListener("blur", this.blurHandler); | |
el.addEventListener("keydown", this.keydownHandler); | |
this.promise = new Promise((resolve, reject) => this.setExecutor(resolve, reject)); | |
return this.promise; | |
} | |
setExecutor(resolve, reject) { | |
this.resolve = resolve; | |
this.reject = reject; | |
} | |
end() { | |
const { el, isValid, value, initialValue } = this; | |
el.contentEditable = false; | |
el.classList.remove("--editing"); | |
el.blur(); | |
el.removeEventListener("blur", this.blurHandler); | |
el.removeEventListener("keydown", this.keydownHandler); | |
if (isValid) { | |
this.resolve(this.sanitize(value)); | |
} | |
else { | |
el.value = initialValue; | |
this.reject("Invalid text"); | |
} | |
this.el = null; | |
} | |
keydownHandler(e){ | |
switch (e.key.toLowerCase()) { | |
case "enter": | |
e.preventDefault(); | |
this.end(); | |
return; | |
case "escape": | |
this.el.value = this.initialValue; | |
this.end(); | |
return; | |
case "backspace": | |
case "delete": | |
case "arrowleft": | |
case "arrowright": | |
return; | |
} | |
} | |
blurHandler(e) { | |
this.end(); | |
} | |
}; | |
// | |
// Example usage | |
// | |
const editor = new Editor(); | |
const someButton = document.getElementByTagName("button"); | |
const someInput = document.getElementByTagName("input"); | |
const store = new SomeDataStore(); // somewhere that we'd want to save the new value of an editor. | |
// when clicking the `someButton` button, begin editing our `someInput` input. | |
// different UI could represent this process, this is just for example. | |
someButton.addEventListener("click", e => editor.edit(someInput) | |
// upon the user pressing enter, the value is saved. | |
.then(newValue => store.save(newValue)); | |
// upon the user bluring the input, pressing ESC, or entering invalid data, it will reject and we'll output why. | |
.catch(reason => console.log(`Exited editor: ${reason || "user aborted"}`))); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment