-
-
Notifications
You must be signed in to change notification settings - Fork 45
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Make one-way-input-mask component * Don't bind mask and options to the attribute * Update README * Update babel * Make one-way-number-mask component Fix issues with calling `update` erroneously * Document one-way-number-mask
- Loading branch information
1 parent
17823b2
commit 042b053
Showing
11 changed files
with
399 additions
and
10 deletions.
There are no files selected for viewing
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
/* global Inputmask */ | ||
import { OneWayInput } from 'ember-one-way-controls'; | ||
import { computed, get, set } from '@ember/object'; | ||
import { schedule } from '@ember/runloop'; | ||
|
||
const DEFAULT_OPTIONS = { | ||
rightAlign: false, | ||
}; | ||
|
||
/** | ||
* Displays an input with the specified mask applied to it | ||
* using Inputmask library. Follows Data-down actions up pattern | ||
* | ||
* @param {string} value The unmasked value to display in the input | ||
* @param {action} update The function to perform when the value changes. Will be passed the | ||
* unmasked value and the masked values | ||
* @param {string} mask The mask to use on the input | ||
* @param {object} options The options to pass into the Inputmask library | ||
*/ | ||
export default OneWayInput.extend({ | ||
/** | ||
* Set the `_value` to be whatever the `element.value` is | ||
*/ | ||
attributeBindings: [ | ||
'type', | ||
'_value:value' | ||
], | ||
|
||
// In ember-one-way-controls all attributes are bound dynamically via a mixin, except for | ||
// the ones specified in this property. We need to include 'mask', and 'options' to the list | ||
NON_ATTRIBUTE_BOUND_PROPS: [ | ||
'keyEvents', | ||
'classNames', | ||
'positionalParamValue', | ||
'update', | ||
'mask', | ||
'options', | ||
], | ||
|
||
/** | ||
* mask - Pass in the `mask` string to set it on the element | ||
* | ||
* @public | ||
*/ | ||
mask: '', | ||
|
||
/** | ||
* options - Options accepted by the Inputmask library | ||
*/ | ||
options: null, | ||
|
||
/** | ||
* Setup _value to be a positional param or the passed param if that is not defined | ||
* | ||
* @private | ||
*/ | ||
_value: computed('positionalParamValue', 'value', { | ||
get() { | ||
let value = get(this, 'positionalParamValue'); | ||
if (value === undefined) { | ||
value = get(this, 'value'); | ||
} | ||
|
||
return value; | ||
} | ||
}), | ||
|
||
init() { | ||
this._super(...arguments); | ||
|
||
// Give the mask some default options that can be overridden | ||
let options = get(this, 'options'); | ||
set(this, 'options', Object.assign({}, DEFAULT_OPTIONS, options)); | ||
}, | ||
|
||
/** | ||
* update - This action will be called when the value changes and will be passed the unmasked value | ||
* and the masked value | ||
* | ||
* @public | ||
*/ | ||
update() {}, | ||
|
||
didInsertElement() { | ||
this._setupMask(); | ||
}, | ||
|
||
willDestroyElement() { | ||
this._destroyMask(); | ||
this.element.removeEventListener('input', this._changeEventListener); | ||
}, | ||
|
||
/** | ||
* Disabling this so we don't have conflicts with manual addEventListener in case something | ||
* changes one day | ||
* | ||
* @override | ||
*/ | ||
change(){}, | ||
|
||
/** | ||
* Disabling thi so we don't have conflicts with manual addEventListener in case something | ||
* changes one day | ||
* | ||
* @override | ||
*/ | ||
input(){}, | ||
|
||
/** | ||
* _changeEventListener - A place to store the event listener we setup to listen to the 'input' | ||
* events, because the Inputmask library events don't play nice with the Ember components event | ||
* | ||
* @private | ||
*/ | ||
_changeEventListener() {}, | ||
|
||
/** | ||
* _processNewValue - Handle when a new value changes | ||
* | ||
* @private | ||
* @param {string} value - The masked value visible in the element | ||
*/ | ||
_processNewValue(value) { | ||
let cursorStart = this.element.selectionStart; | ||
let cursorEnd = this.element.selectionEnd; | ||
let unmaskedValue = this._getUnmaskedValue(); | ||
let oldUnmaskedValue = get(this, '_value'); | ||
let options = get(this, 'options'); | ||
|
||
// We only want to make changes if something is different so we don't cause infinite loops or | ||
// double renders. | ||
// We want to make sure that that values we compare are going to come out the same through | ||
// the masking algorithm, to ensure that we only call `update` if the values are actually different | ||
// (e.g. '1234.' will be masked as '1234' and so when `update` is called and passed back | ||
// into the component the decimal will be removed, we don't want this) | ||
if (Inputmask.format(String(oldUnmaskedValue), options) !== Inputmask.format(unmaskedValue, options)) { | ||
get(this, 'update')(unmaskedValue, value); | ||
|
||
// When the value is updated, and then sent back down the cursor moves to the end of the field. | ||
// We therefore need to put it back to where the user was typing so they don't get janked around | ||
schedule('afterRender', () => { | ||
this.element.setSelectionRange(cursorStart, cursorEnd); | ||
}); | ||
} | ||
}, | ||
|
||
/** | ||
* _setupMask - Connect the 3rd party input masking library to the element | ||
* | ||
* @private | ||
*/ | ||
_setupMask() { | ||
let mask = get(this, 'mask'), options = get(this, 'options'); | ||
let inputmask = new Inputmask(mask, options); | ||
inputmask.mask(this.element); | ||
|
||
// We need to setup a manual event listener for the change event instead of using the Ember | ||
// Component event methods, because the Inputmask events don't play nice with the Component | ||
// ones. Similar issue happens in React.js as well | ||
// https://github.com/RobinHerbots/Inputmask/issues/1377 | ||
let eventListener = event => this._processNewValue(event.target.value); | ||
set(this, '_changeEventListener', eventListener); | ||
this.element.addEventListener('input', eventListener); | ||
}, | ||
|
||
/** | ||
* _getUnmaskedValue - Get the value of the element without the mask | ||
* | ||
* @private | ||
* @return {string} The unmasked value | ||
*/ | ||
_getUnmaskedValue() { | ||
return this.element.inputmask.unmaskedvalue(); | ||
}, | ||
|
||
_destroyMask() { | ||
this.element.inputmask.remove(); | ||
}, | ||
}); |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import OneWayInputMask from 'ember-inputmask/components/one-way-input-mask'; | ||
import { get, set } from '@ember/object'; | ||
import { isBlank } from '@ember/utils'; | ||
|
||
const DEFAULT_OPTIONS = { | ||
groupSeparator: ',', | ||
radixPoint: '.', | ||
groupSize: '3', | ||
autoGroup: true, | ||
}; | ||
|
||
export default OneWayInputMask.extend({ | ||
/** | ||
* @override | ||
*/ | ||
mask: 'integer', | ||
|
||
/** | ||
* Set this to true to include decimals | ||
*/ | ||
decimal: false, | ||
|
||
init() { | ||
this._super(...arguments); | ||
|
||
set(this, 'options', Object.assign({}, get(this, 'options'), DEFAULT_OPTIONS)); | ||
|
||
if (get(this, 'decimal')) { | ||
set(this, 'mask', 'decimal'); | ||
|
||
// Give default digits if we don't have them aleady | ||
if (isBlank(get(this, 'options.digits'))) { | ||
set(this, 'options.digits', 2); | ||
} | ||
} | ||
}, | ||
}); |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
import OneWayInputMaskComponent from 'ember-inputmask/components/one-way-input-mask'; | ||
export default OneWayInputMaskComponent; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default } from 'ember-inputmask/components/one-way-number-mask'; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
One Way Number Mask | ||
=================== | ||
|
||
``` | ||
{{one-way-number-mask value update=(action (mut value))}} | ||
``` | ||
|
||
This component defaults to masking as an `integer`. You can pass other number based options supported by [Inputmask](https://github.com/RobinHerbots/Inputmask) in the `options` hash. | ||
|
||
## Arguments | ||
|
||
### decimal | ||
|
||
``` | ||
{{one-way-number-mask value decimal=true update=(action (mut value))}} | ||
``` | ||
|
||
Pass in `decimal` and it will mask as a decimal with 2 digits. If you'd like more or less digits then you can pass in `options.digits`. |
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { moduleForComponent, test } from 'ember-qunit'; | ||
import hbs from 'htmlbars-inline-precompile'; | ||
import { find, fillIn } from 'ember-native-dom-helpers'; | ||
|
||
moduleForComponent('one-way-number-mask', 'Integration | Component | one way number mask', { | ||
integration: true | ||
}); | ||
|
||
test('It defaults to an integer mask', function(assert) { | ||
this.set('value', 1234.56) | ||
this.render(hbs`{{one-way-number-mask value}}`); | ||
|
||
assert.equal(find('input').value, '1,234'); | ||
}); | ||
|
||
test('It can be a decimal mask with 2 digits with one argument', function(assert) { | ||
this.set('value', 1234.567) | ||
this.render(hbs`{{one-way-number-mask value decimal=true}}`); | ||
|
||
assert.equal(find('input').value, '1,234.57'); | ||
}); | ||
|
||
test('Can change default digits with options', function(assert) { | ||
this.set('value', 1234.567) | ||
this.render(hbs`{{one-way-number-mask value decimal=true options=(hash digits=3)}}`); | ||
|
||
assert.equal(find('input').value, '1,234.567'); | ||
}); | ||
|
||
test('Can change default digits with options', function(assert) { | ||
this.set('value', 1234.567) | ||
this.render(hbs`{{one-way-number-mask value decimal=true options=(hash digits=3)}}`); | ||
|
||
assert.equal(find('input').value, '1,234.567'); | ||
}); | ||
|
||
test('The parent can receive the updated value via the `update` action', async function(assert) { | ||
this.set('value', 123) | ||
this.render(hbs`{{one-way-number-mask value update=(action (mut value))}}`); | ||
await fillIn('input', 456); | ||
assert.equal(this.get('value'), '456'); | ||
}); |
Oops, something went wrong.