Supporting DOM element param

This commit is contained in:
Francisco Hodge
2020-02-06 00:30:41 -05:00
parent 304f47701e
commit 1a5d917a7d
12 changed files with 235 additions and 77 deletions
+31
View File
@@ -0,0 +1,31 @@
import Keyboard from "../lib";
import "./css/DOMElementDemo.css";
class Demo {
constructor() {
const rootDOM =
document.querySelector("#root") || document.createElement("div");
const keyboard1DOM = document.createElement("div");
keyboard1DOM.className = "my-keyboard";
const keyboard2DOM = document.createElement("div");
keyboard2DOM.className = "my-keyboard2";
/**
* Demo Start
*/
this.keyboard1 = new Keyboard(keyboard1DOM, {
onChange: input => console.log(this.keyboard1.keyboardDOMClass, input)
});
this.keyboard2 = new Keyboard(keyboard2DOM, {
onChange: input => console.log(this.keyboard2.keyboardDOMClass, input)
});
rootDOM.appendChild(keyboard1DOM);
rootDOM.appendChild(keyboard2DOM);
}
}
export default Demo;
+14
View File
@@ -0,0 +1,14 @@
input {
width: 100%;
height: 100px;
padding: 20px;
font-size: 20px;
border: none;
box-sizing: border-box;
}
.my-keyboard,
.my-keyboard2 {
max-width: 850px;
margin-top: 20px;
}
+4 -3
View File
@@ -4,9 +4,10 @@ import "./css/index.css";
* Demos
*/
import BasicDemo from "./BasicDemo";
//import FullKeyboardDemo from "./FullKeyboardDemo";
//import ButtonThemeDemo from "./ButtonThemeDemo";
//import MultipleKeyboardsDemo from "./MultipleKeyboardsDestroyDemo";
// import ButtonThemeDemo from "./ButtonThemeDemo";
// import DOMElementDemo from "./DOMElementDemo";
// import FullKeyboardDemo from "./FullKeyboardDemo";
// import MultipleKeyboardsDemo from "./MultipleKeyboardsDestroyDemo";
/**
* Selected demo
+22
View File
@@ -0,0 +1,22 @@
import DOMElementDemo from '../DOMElementDemo';
import Keyboard from '../../lib/components/Keyboard';
it('Demo will load', () => {
new DOMElementDemo();
});
it('Demo keyboards will be instantiated', () => {
const demo = new DOMElementDemo();
expect(demo.keyboard1).toBeInstanceOf(Keyboard);
expect(demo.keyboard2).toBeInstanceOf(Keyboard);
});
it('Demo input change will work', () => {
const demo = new DOMElementDemo();
demo.keyboard1.getButtonElement("q").onclick();
demo.keyboard2.getButtonElement("e").onclick();
expect(demo.keyboard1.getInput()).toBe("q");
expect(demo.keyboard2.getInput()).toBe("e");
});
+122 -55
View File
@@ -1,8 +1,8 @@
import "./Keyboard.css";
// Services
import { getDefaultLayout } from "../services/KeyboardLayout";
import PhysicalKeyboard from "../services/PhysicalKeyboard";
import KeyboardLayout from "../services/KeyboardLayout";
import Utilities from "../services/Utilities";
/**
@@ -18,11 +18,9 @@ class SimpleKeyboard {
* @param {Array} params If first parameter is a string, it is considered the container class. The second parameter is then considered the options object. If first parameter is an object, it is considered the options object.
*/
constructor(...params) {
let keyboardDOMQuery =
typeof params[0] === "string" ? params[0] : ".simple-keyboard";
let options = typeof params[0] === "object" ? params[0] : params[1];
if (!options) options = {};
const { keyboardDOMClass, keyboardDOM, options = {} } = this.handleParams(
params
);
/**
* Initializing Utilities
@@ -41,7 +39,7 @@ class SimpleKeyboard {
/**
* Processing options
*/
this.keyboardDOM = document.querySelector(keyboardDOMQuery);
this.keyboardDOM = keyboardDOM;
/**
* @type {object}
@@ -101,7 +99,7 @@ class SimpleKeyboard {
* This removes any dependency to input DOM elements. You can type and directly display the value in a div element, for example.
* @example
* // To get entered input
* let input = keyboard.getInput();
* const input = keyboard.getInput();
*
* // To clear entered input.
* keyboard.clearInput();
@@ -116,7 +114,7 @@ class SimpleKeyboard {
/**
* @type {string} DOM class of the keyboard wrapper, normally "simple-keyboard" by default.
*/
this.keyboardDOMClass = keyboardDOMQuery.split(".").join("");
this.keyboardDOMClass = keyboardDOMClass;
/**
* @type {object} Contains the DOM elements of every rendered button, the key being the button's layout name (e.g.: "{enter}").
@@ -154,7 +152,7 @@ class SimpleKeyboard {
*/
if (this.keyboardDOM) this.render();
else {
console.warn(`"${keyboardDOMQuery}" was not found in the DOM.`);
console.warn(`".${keyboardDOMClass}" was not found in the DOM.`);
throw new Error("KEYBOARD_DOM_ERROR");
}
@@ -165,6 +163,56 @@ class SimpleKeyboard {
this.loadModules();
}
/**
* parseParams
*/
handleParams = params => {
let keyboardDOMClass;
let keyboardDOM;
let options;
/**
* If first parameter is a string:
* Consider it as an element's class
*/
if (typeof params[0] === "string") {
keyboardDOMClass = params[0].split(".").join("");
keyboardDOM = document.querySelector(`.${keyboardDOMClass}`);
options = params[1];
/**
* If first parameter is an HTMLDivElement
* Consider it as the keyboard DOM element
*/
} else if (params[0] instanceof HTMLDivElement) {
/**
* This element must have a class, otherwise throw
*/
if (!params[0].className) {
console.warn("Any DOM element passed as parameter must have a class.");
throw new Error("KEYBOARD_DOM_CLASS_ERROR");
}
keyboardDOMClass = params[0].className.split(" ")[0];
keyboardDOM = params[0];
options = params[1];
/**
* Otherwise, search for .simple-keyboard DOM element
*/
} else {
keyboardDOMClass = "simple-keyboard";
keyboardDOM = document.querySelector(`.${keyboardDOMClass}`);
options = params[0];
}
return {
keyboardDOMClass,
keyboardDOM,
options
};
};
/**
* Getters
*/
@@ -176,7 +224,7 @@ class SimpleKeyboard {
* @param {string} button The button's layout name.
*/
handleButtonClicked(button) {
let debug = this.options.debug;
const debug = this.options.debug;
/**
* Ignoring placeholder buttons
@@ -192,7 +240,7 @@ class SimpleKeyboard {
if (!this.input[this.options.inputName])
this.input[this.options.inputName] = "";
let updatedInput = this.utilities.getUpdatedInput(
const updatedInput = this.utilities.getUpdatedInput(
button,
this.input[this.options.inputName],
this.caretPosition
@@ -499,7 +547,7 @@ class SimpleKeyboard {
if (buttonTheme.class.split(" ").includes(classNameItem)) {
classNameFound = true;
let buttonThemeArray = buttonTheme.buttons.split(" ");
const buttonThemeArray = buttonTheme.buttons.split(" ");
if (!buttonThemeArray.includes(button)) {
classNameFound = true;
buttonThemeArray.push(button);
@@ -547,8 +595,8 @@ class SimpleKeyboard {
Array.isArray(this.options.buttonTheme) &&
this.options.buttonTheme.length
) {
let buttonArray = buttons.split(" ");
buttonArray.forEach((button, key) => {
const buttonArray = buttons.split(" ");
buttonArray.forEach(button => {
this.options.buttonTheme.map((buttonTheme, index) => {
/**
* If className is set, we affect the buttons only for that class
@@ -558,7 +606,7 @@ class SimpleKeyboard {
(className && className.includes(buttonTheme.class)) ||
!className
) {
let filteredButtonArray = buttonTheme.buttons
const filteredButtonArray = buttonTheme.buttons
.split(" ")
.filter(item => item !== button);
@@ -588,7 +636,7 @@ class SimpleKeyboard {
getButtonElement(button) {
let output;
let buttonArr = this.buttonElements[button];
const buttonArr = this.buttonElements[button];
if (buttonArr) {
if (buttonArr.length > 1) {
output = buttonArr;
@@ -605,7 +653,7 @@ class SimpleKeyboard {
* by checking if the provided inputPattern passes
*/
inputPatternIsValid(inputVal) {
let inputPatternRaw = this.options.inputPattern;
const inputPatternRaw = this.options.inputPattern;
let inputPattern;
/**
@@ -618,7 +666,7 @@ class SimpleKeyboard {
}
if (inputPattern && inputVal) {
let didInputMatch = inputPattern.test(inputVal);
const didInputMatch = inputPattern.test(inputVal);
if (this.options.debug) {
console.log(
@@ -817,7 +865,7 @@ class SimpleKeyboard {
* Process buttonTheme option
*/
getButtonThemeClasses(button) {
let buttonTheme = this.options.buttonTheme;
const buttonTheme = this.options.buttonTheme;
let buttonClasses = [];
if (Array.isArray(buttonTheme)) {
@@ -828,8 +876,8 @@ class SimpleKeyboard {
themeObj.buttons &&
typeof themeObj.buttons === "string"
) {
let themeObjClasses = themeObj.class.split(" ");
let themeObjButtons = themeObj.buttons.split(" ");
const themeObjClasses = themeObj.class.split(" ");
const themeObjButtons = themeObj.buttons.split(" ");
if (themeObjButtons.includes(button)) {
buttonClasses = [...buttonClasses, ...themeObjClasses];
@@ -850,7 +898,7 @@ class SimpleKeyboard {
* Process buttonAttributes option
*/
setDOMButtonAttributes(button, callback) {
let buttonAttributes = this.options.buttonAttributes;
const buttonAttributes = this.options.buttonAttributes;
if (Array.isArray(buttonAttributes)) {
buttonAttributes.forEach(attrObj => {
@@ -862,7 +910,7 @@ class SimpleKeyboard {
attrObj.buttons &&
typeof attrObj.buttons === "string"
) {
let attrObjButtons = attrObj.buttons.split(" ");
const attrObjButtons = attrObj.buttons.split(" ");
if (attrObjButtons.includes(button)) {
callback(attrObj.attribute, attrObj.value);
@@ -1011,19 +1059,22 @@ class SimpleKeyboard {
*/
loadModules() {
if (Array.isArray(this.options.modules)) {
this.options.modules.forEach(Module => {
let module = new Module();
this.options.modules.forEach(KeyboardModule => {
const keyboardModule = new KeyboardModule();
/* istanbul ignore next */
if (module.constructor.name && module.constructor.name !== "Function") {
let classStr = `module-${this.utilities.camelCase(
module.constructor.name
if (
keyboardModule.constructor.name &&
keyboardModule.constructor.name !== "Function"
) {
const classStr = `module-${this.utilities.camelCase(
keyboardModule.constructor.name
)}`;
this.keyboardPluginClasses =
this.keyboardPluginClasses + ` ${classStr}`;
}
module.init(this);
keyboardModule.init(this);
});
this.keyboardPluginClasses =
@@ -1059,12 +1110,12 @@ class SimpleKeyboard {
containerStartIndexes,
containerEndIndexes
) {
let rowDOMArray = Array.from(rowDOM.children);
const rowDOMArray = Array.from(rowDOM.children);
let removedElements = 0;
if (rowDOMArray.length) {
containerStartIndexes.forEach((startIndex, arrIndex) => {
let endIndex = containerEndIndexes[arrIndex];
const endIndex = containerEndIndexes[arrIndex];
/**
* If there exists a respective end index
@@ -1079,21 +1130,21 @@ class SimpleKeyboard {
* This is since the removal of buttons to place a single button container
* results in a modified array size
*/
let updated_startIndex = startIndex - removedElements;
let updated_endIndex = endIndex - removedElements;
const updated_startIndex = startIndex - removedElements;
const updated_endIndex = endIndex - removedElements;
/**
* Create button container
*/
let containerDOM = document.createElement("div");
const containerDOM = document.createElement("div");
containerDOM.className += "hg-button-container";
let containerUID = `${this.options.layoutName}-r${rowIndex}c${arrIndex}`;
const containerUID = `${this.options.layoutName}-r${rowIndex}c${arrIndex}`;
containerDOM.setAttribute("data-skUID", containerUID);
/**
* Taking elements due to be inserted into container
*/
let containedElements = rowDOMArray.splice(
const containedElements = rowDOMArray.splice(
updated_startIndex,
updated_endIndex - updated_startIndex + 1
);
@@ -1134,6 +1185,17 @@ class SimpleKeyboard {
return rowDOM;
}
/**
* getKeyboardClassString
*/
getKeyboardClassString = (...baseDOMClasses) => {
const keyboardClasses = [this.keyboardDOMClass, ...baseDOMClasses].filter(
DOMClass => !!DOMClass
);
return keyboardClasses.join(" ");
};
/**
* Renders rows and buttons as per options
*/
@@ -1155,23 +1217,28 @@ class SimpleKeyboard {
*/
this.beforeRender();
let layoutClass = `hg-layout-${this.options.layoutName}`;
let layout = this.options.layout || KeyboardLayout.getDefaultLayout();
let useTouchEvents = this.options.useTouchEvents || false;
let useTouchEventsClass = useTouchEvents ? "hg-touch-events" : "";
let useMouseEvents = this.options.useMouseEvents || false;
let disableRowButtonContainers = this.options.disableRowButtonContainers;
const layoutClass = `hg-layout-${this.options.layoutName}`;
const layout = this.options.layout || getDefaultLayout();
const useTouchEvents = this.options.useTouchEvents || false;
const useTouchEventsClass = useTouchEvents ? "hg-touch-events" : "";
const useMouseEvents = this.options.useMouseEvents || false;
const disableRowButtonContainers = this.options.disableRowButtonContainers;
/**
* Adding themeClass, layoutClass to keyboardDOM
*/
this.keyboardDOM.className += ` ${this.options.theme} ${layoutClass} ${this.keyboardPluginClasses} ${useTouchEventsClass}`;
this.keyboardDOM.className = this.getKeyboardClassString(
this.options.theme,
layoutClass,
this.keyboardPluginClasses,
useTouchEventsClass
);
/**
* Iterating through each row
*/
layout[this.options.layoutName].forEach((row, rIndex) => {
let rowArray = row.split(" ");
const rowArray = row.split(" ");
/**
* Creating empty row
@@ -1182,8 +1249,8 @@ class SimpleKeyboard {
/**
* Tracking container indicators in rows
*/
let containerStartIndexes = [];
let containerEndIndexes = [];
const containerStartIndexes = [];
const containerEndIndexes = [];
/**
* Iterating through each button in row
@@ -1192,11 +1259,11 @@ class SimpleKeyboard {
/**
* Check if button has a container indicator
*/
let buttonHasContainerStart =
const buttonHasContainerStart =
!disableRowButtonContainers &&
button.includes("[") &&
button.length > 1;
let buttonHasContainerEnd =
const buttonHasContainerEnd =
!disableRowButtonContainers &&
button.includes("]") &&
button.length > 1;
@@ -1225,8 +1292,8 @@ class SimpleKeyboard {
/**
* Processing button options
*/
let fctBtnClass = this.utilities.getButtonClass(button);
let buttonDisplayName = this.utilities.getButtonDisplayName(
const fctBtnClass = this.utilities.getButtonClass(button);
const buttonDisplayName = this.utilities.getButtonDisplayName(
button,
this.options.display,
this.options.mergeDisplay
@@ -1235,8 +1302,8 @@ class SimpleKeyboard {
/**
* Creating button
*/
let buttonType = this.options.useButtonTag ? "button" : "div";
let buttonDOM = document.createElement(buttonType);
const buttonType = this.options.useButtonTag ? "button" : "div";
const buttonDOM = document.createElement(buttonType);
buttonDOM.className += `hg-button ${fctBtnClass}`;
/**
@@ -1319,13 +1386,13 @@ class SimpleKeyboard {
* Adding unique id
* Since there's no limit on spawning same buttons, the unique id ensures you can style every button
*/
let buttonUID = `${this.options.layoutName}-r${rIndex}b${bIndex}`;
const buttonUID = `${this.options.layoutName}-r${rIndex}b${bIndex}`;
buttonDOM.setAttribute("data-skBtnUID", buttonUID);
/**
* Adding button label to button
*/
let buttonSpanDOM = document.createElement("span");
const buttonSpanDOM = document.createElement("span");
buttonSpanDOM.innerHTML = buttonDisplayName;
buttonDOM.appendChild(buttonSpanDOM);
+23
View File
@@ -1338,3 +1338,26 @@ it('Keyboard recurseButtons will not work without a valid param', () => {
const keyboard = new Keyboard();
expect(keyboard.recurseButtons()).toBe(false);
});
it('Keyboard will not work with a DOM element param without class', () => {
try {
const keyboardDOM = document.createElement("div");
new Keyboard(keyboardDOM);
expect(true).toBe(false);
} catch (e) {
expect(e.message).toBe("KEYBOARD_DOM_CLASS_ERROR");
}
});
it('Keyboard will work with a DOM element param with class', () => {
try {
const keyboardClass = "my-keyboard";
const keyboardDOM = document.createElement("div");
keyboardDOM.className = keyboardClass;
const keyboard = new Keyboard(keyboardDOM);
expect(keyboard.keyboardDOMClass).toBe(keyboardClass);
} catch (e) {
expect(true).toBe(false);
}
});
+14 -14
View File
@@ -23,11 +23,11 @@ class Utilities {
* @return {string} The classes to be added to the button
*/
getButtonClass(button) {
let buttonTypeClass =
const buttonTypeClass =
button.includes("{") && button.includes("}") && button !== "{//}"
? "functionBtn"
: "standardBtn";
let buttonWithoutBraces = button.replace("{", "").replace("}", "");
const buttonWithoutBraces = button.replace("{", "").replace("}", "");
let buttonNormalized = "";
if (buttonTypeClass !== "standardBtn")
@@ -128,7 +128,7 @@ class Utilities {
* @param {boolean} moveCaret Whether to update simple-keyboard's cursor
*/
getUpdatedInput(button, input, caretPos, moveCaret) {
let options = this.getOptions();
const options = this.getOptions();
let output = input;
if (
@@ -186,7 +186,7 @@ class Utilities {
* @param {boolean} minus Whether the cursor should be moved to the left or not.
*/
updateCaretPos(length, minus) {
let newCaretPos = this.updateCaretPosAction(length, minus);
const newCaretPos = this.updateCaretPosAction(length, minus);
this.dispatch(instance => {
instance.caretPosition = newCaretPos;
@@ -200,7 +200,7 @@ class Utilities {
* @param {boolean} minus Whether the cursor should be moved to the left or not.
*/
updateCaretPosAction(length, minus) {
let options = this.getOptions();
const options = this.getOptions();
let caretPosition = this.getCaretPosition();
if (minus) {
@@ -253,7 +253,7 @@ class Utilities {
* @param {boolean} moveCaret Whether to update simple-keyboard's cursor
*/
removeAt(source, position, moveCaret) {
let caretPosition = this.getCaretPosition();
const caretPosition = this.getCaretPosition();
if (caretPosition === 0) {
return source;
@@ -262,7 +262,7 @@ class Utilities {
let output;
let prevTwoChars;
let emojiMatched;
let emojiMatchedReg = /([\uD800-\uDBFF][\uDC00-\uDFFF])/g;
const emojiMatchedReg = /([\uD800-\uDBFF][\uDC00-\uDFFF])/g;
/**
* Emojis are made out of two characters, so we must take a custom approach to trim them.
@@ -301,10 +301,10 @@ class Utilities {
* @param {string} updatedInput
*/
handleMaxLength(inputObj, updatedInput) {
let options = this.getOptions();
let maxLength = options.maxLength;
let currentInput = inputObj[options.inputName];
let condition = updatedInput.length - 1 >= maxLength;
const options = this.getOptions();
const maxLength = options.maxLength;
const currentInput = inputObj[options.inputName];
const condition = updatedInput.length - 1 >= maxLength;
if (
/**
@@ -334,7 +334,7 @@ class Utilities {
}
if (typeof maxLength === "object") {
let condition = currentInput.length === maxLength[options.inputName];
const condition = currentInput.length === maxLength[options.inputName];
if (options.debug) {
console.log("maxLength (obj) reached:", condition);
@@ -377,8 +377,8 @@ class Utilities {
static bindMethods(myClass, instance) {
// eslint-disable-next-line no-unused-vars
for (let myMethod of Object.getOwnPropertyNames(myClass.prototype)) {
let excludeMethod =
for (const myMethod of Object.getOwnPropertyNames(myClass.prototype)) {
const excludeMethod =
myMethod === "constructor" || myMethod === "bindMethods";
if (!excludeMethod) {
instance[myMethod] = instance[myMethod].bind(instance);