Last active
August 3, 2020 23:28
-
-
Save danmeyers/b1295c1ce2ae82b29fc96f5908559313 to your computer and use it in GitHub Desktop.
New Twiddle
This file contains hidden or 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
| import Component from '@ember/component'; | |
| import { schedule } from '@ember/runloop'; | |
| function format(gtin) { | |
| if (!gtin) { | |
| return ''; | |
| } | |
| // If this is not an all-numeric gtin, don't even try to add spacing. | |
| if (Number.isNaN(Number(gtin))) { | |
| return gtin; | |
| } | |
| switch (gtin.length) { | |
| // GTIN-8: XXXX XXXX | |
| case 8: | |
| return `${gtin.slice(0, 4)} ${gtin.slice(4)}`; | |
| // GTIN-12: X XXXXX XXXXX X | |
| case 12: | |
| return `${gtin[0]} ${gtin.slice(1, 6)} ${gtin.slice(6, 11)} ${gtin[11]}`; | |
| // GTIN-13: X XXXXXX XXXXXX | |
| case 13: | |
| return `${gtin[0]} ${gtin.slice(1, 7)} ${gtin.slice(7)}`; | |
| // GTIN-14: X XX XXXXX XXXXX X | |
| case 14: | |
| return `${gtin[0]} ${gtin.slice(1, 3)} ${gtin.slice(3, 8)} ${gtin.slice(8, 13)} ${gtin[13]}`; | |
| default: | |
| return gtin; | |
| } | |
| } | |
| function unformat(formattedGtin) { | |
| return formattedGtin.replace(/\D/g, ''); | |
| } | |
| /** | |
| * For a newly formatted display value, determine where the the cursor | |
| * must be moved based on its current position. In the new formatted value, it should be | |
| * after the same exact digit that it is after in the current display value. | |
| * | |
| * @param {string} currentDisplayValue The value as it is displayed, which may need updated formatting. | |
| * @param {Number} currentCursorPosition The current position of the cursor in the input box. | |
| * @returns {Number} The new position of the cursor in the updated, formatted value string. | |
| */ | |
| function findNewCursorPosition(currentDisplayValue, currentCursorPosition) { | |
| const digitsBeforeCursor = currentDisplayValue.slice(0, currentCursorPosition).match(/\d/g); | |
| const numDigitsBeforeCursor = (digitsBeforeCursor || []).length; | |
| // Traverse the new formatted value, counting digits until reaching the | |
| // same amount of digits that we had before the cursor in the current display value. | |
| const newFormattedValue = format(unformat(currentDisplayValue)); | |
| let digitsCounted = 0; | |
| let newCursorPosition = 0; | |
| for (let char of newFormattedValue) { | |
| if (digitsCounted === numDigitsBeforeCursor) { | |
| break; | |
| } | |
| if (char.match('[0-9]')) { | |
| digitsCounted++; | |
| } | |
| newCursorPosition++; | |
| } | |
| return newCursorPosition; | |
| } | |
| export default Component.extend({ | |
| value: null, // passed in | |
| displayValue: null, // set internally | |
| deletedSpaceCharacter: false, // set internally | |
| didInsertElement(...args) { | |
| this._super(...args); | |
| this.setUpListeners(); | |
| }, | |
| willDestroyElement(...args) { | |
| this._super(...args); | |
| this.teardownListeners(); | |
| }, | |
| didReceiveAttrs() { | |
| this._super(); | |
| this.set('displayValue', format(this.value)); | |
| }, | |
| /*********************************************************************** | |
| | Event Handling | | |
| | | | |
| | - Input: Reformat display value and set internal property value | | |
| | when input changes. | | |
| | – KeyDown: | | |
| | - Left/Right arrow, to skip over formatted space characters | | |
| | - Backspace, to delete an additional character if a space | | |
| | character is being deleted | | |
| | - Copy/Cut: Remove space characters from copied value | | |
| | | | |
| ***********************************************************************/ | |
| /* | |
| * Event Handling: Setup / Teardown | |
| */ | |
| setUpListeners() { | |
| // Binding `this` provides the handler access to the component as its context. | |
| this._inputEventListener = this.inputEventListener.bind(this); | |
| this._copyEventListener = this.copyEventListener.bind(this); | |
| this._cutEventListener = this.cutEventListener.bind(this); | |
| this._keyDownEventListener = this.keyDownEventListener.bind(this); | |
| const inputElement = this.element.querySelector('input'); | |
| inputElement.addEventListener('input', this._inputEventListener); | |
| inputElement.addEventListener('copy', this._copyEventListener); | |
| inputElement.addEventListener('cut', this._cutEventListener); | |
| inputElement.addEventListener('keydown', this._keyDownEventListener); | |
| }, | |
| teardownListeners() { | |
| const inputElement = this.element.querySelector('input'); | |
| inputElement.removeEventListener('input', this._inputEventListener); | |
| inputElement.removeEventListener('copy', this._copyEventListener); | |
| inputElement.removeEventListener('cut', this._cutEventListener); | |
| inputElement.removeEventListener('keydown', this._keyDownEventListener); | |
| }, | |
| /* | |
| * Event Handling: Listeners | |
| */ | |
| // Format the new input value. Update visible and internal state. | |
| inputEventListener(e) { | |
| const inputElement = e.target; | |
| let cursorPosition = inputElement.selectionStart; | |
| let inputValue = inputElement.value; | |
| const formattedValue = format(unformat(inputValue)); | |
| const newCursorPosition = findNewCursorPosition(inputValue, cursorPosition); | |
| this.updateAndRenderValue({ inputElement, formattedValue, cursorPosition : newCursorPosition }); | |
| }, | |
| keyDownEventListener(e) { | |
| // Modifier keys can have complex, browser-specific effects that seem dangerous | |
| // to approximate. E.g., holding shift with the left key selects a range, or | |
| // cmd/ctrl + left moves to the front of the field. | |
| // | |
| // We're solving for the 95% case here, to help sellers understand that the spaces | |
| // are not a part of the actual value. | |
| if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { | |
| return; | |
| } | |
| const inputElement = e.target; | |
| const inputValue = inputElement.value; | |
| let cursorPosition = inputElement.selectionStart; | |
| // If moving left to become right-adjacent to a space, skip over the space. | |
| if(e.key == 'ArrowLeft' && inputValue[cursorPosition - 2] === ' ') { | |
| e.preventDefault(); | |
| cursorPosition -= 2; | |
| inputElement.setSelectionRange(cursorPosition, cursorPosition); | |
| } | |
| // If moving right to become right-adjacent to a space, skip over the space. | |
| if (e.key == 'ArrowRight' && inputValue[cursorPosition] === ' ') { | |
| e.preventDefault(); | |
| cursorPosition += 2; | |
| inputElement.setSelectionRange(cursorPosition, cursorPosition); | |
| } | |
| // If deleting a space, also delete the prior digit. | |
| // E.g., `1234 |5678` becomes `123|567`. | |
| if (e.key === 'Backspace' && inputValue[cursorPosition - 1] === ' ') { | |
| e.preventDefault(); | |
| const cursorPositionWithoutDeletedChars = cursorPosition - 2; | |
| const inputValueWithoutDeletedChars = inputValue.slice(0, cursorPositionWithoutDeletedChars) + inputValue.slice(cursorPosition); | |
| // Re-format the value and find the new cursor position. | |
| const formattedValue = format(unformat(inputValueWithoutDeletedChars)); | |
| cursorPosition = findNewCursorPosition(inputValueWithoutDeletedChars, cursorPositionWithoutDeletedChars); | |
| // We've modified the value by deleting a digit. Update visible and internal state. | |
| this.updateAndRenderValue({ inputElement, formattedValue, cursorPosition }); | |
| } | |
| }, | |
| // For the selected range, unformat the data and copy to clipboard. | |
| copyEventListener(e) { | |
| e.preventDefault(); | |
| let inputElement = e.target; | |
| let selectionStart = inputElement.selectionStart; | |
| let selectionEnd = inputElement.selectionEnd; | |
| let selectedUnformattedText = unformat(inputElement.value.slice(selectionStart, selectionEnd)); | |
| e.clipboardData.setData('Text', selectedUnformattedText); | |
| }, | |
| // The same as copy, but we need to also delete the selected text. | |
| cutEventListener(e) { | |
| this.copyEventListener(e); | |
| const inputElement = e.target; | |
| const selectionStart = inputElement.selectionStart; | |
| const selectionEnd = inputElement.selectionEnd; | |
| const formattedValue = `${this.displayValue.slice(0, selectionStart)}${this.displayValue.slice(selectionEnd, this.displayValue.length)}`; | |
| this.updateAndRenderValue({ inputElement, formattedValue, cursorPosition : selectionStart }); | |
| }, | |
| /* | |
| * Event Handling: Helpers | |
| */ | |
| /** | |
| * Updates the visible and internal property values. Then, repositions the cursor. | |
| * | |
| * @param {inputElement} The GTIN <input> DOM element. | |
| * @param {formattedValue} The new formatted value to display. | |
| * The internal property value will be set to the `unformat`ed formattedValue. | |
| * @param {cursorPosition} After updating the display value in the input element, | |
| where to place the cursor. | |
| */ | |
| updateAndRenderValue({ inputElement, formattedValue, cursorPosition }) { | |
| this.set('displayValue', formattedValue); | |
| this.set('value', unformat(formattedValue)); | |
| // Ember will render the input box with the new display value. After, we need to | |
| // set the position of the cursor, otherwise it will be at the end of the string. | |
| schedule('afterRender', this, () => { | |
| inputElement.setSelectionRange(cursorPosition, cursorPosition); | |
| }); | |
| }, | |
| }); |
This file contains hidden or 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
| import Controller from '@ember/controller'; | |
| export default class ApplicationController extends Controller { | |
| appName = '123456789012'; | |
| } |
This file contains hidden or 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
| { | |
| "version": "0.17.1", | |
| "EmberENV": { | |
| "FEATURES": {}, | |
| "_TEMPLATE_ONLY_GLIMMER_COMPONENTS": false, | |
| "_APPLICATION_TEMPLATE_WRAPPER": true, | |
| "_JQUERY_INTEGRATION": true | |
| }, | |
| "options": { | |
| "use_pods": false, | |
| "enable-testing": false | |
| }, | |
| "dependencies": { | |
| "jquery": "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.js", | |
| "ember": "3.18.1", | |
| "ember-template-compiler": "3.18.1", | |
| "ember-testing": "3.18.1" | |
| }, | |
| "addons": { | |
| "@glimmer/component": "1.0.0" | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment