Skip to content

Commit

Permalink
Adding step feature and focus input on click.
Browse files Browse the repository at this point in the history
  • Loading branch information
manolo authored and samiheikki committed Dec 16, 2018
1 parent 27f4603 commit ab5fdec
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 34 deletions.
13 changes: 10 additions & 3 deletions demo/text-field-number-demos.html
Expand Up @@ -9,27 +9,34 @@
<p>The <code>&lt;vaadin-number-field&gt;</code> element has all the same features as the <code>&lt;vaadin-text-field&gt;</code> element plus the additional features listed on this page.</p>


<h3>Basic Number Field</h3>
<h3>Basic number field</h3>
<vaadin-demo-snippet id="text-field-number-demos-basic">
<template preserve-content>
<vaadin-number-field></vaadin-number-field>
</template>
</vaadin-demo-snippet>

<h3>Number Field with controls</h3>
<h3>Number field with controls</h3>
<vaadin-demo-snippet id="text-field-number-demos-with-controls">
<template preserve-content>
<vaadin-number-field has-controls></vaadin-number-field>
</template>
</vaadin-demo-snippet>

<h3>Number Field with value limits</h3>
<h3>Number field with value limits</h3>
<vaadin-demo-snippet id="text-field-number-demos-with-limits">
<template preserve-content>
<vaadin-number-field value="1" min="1" max="10" has-controls></vaadin-number-field>
</template>
</vaadin-demo-snippet>

<h3>Number field with step</h3>
<vaadin-demo-snippet id="text-field-number-demos-with-step">
<template preserve-content>
<vaadin-number-field step="0.2" min="0" max="10" has-controls></vaadin-number-field>
</template>
</vaadin-demo-snippet>

</template>
<script>
class TextFieldNumberDemos extends DemoReadyEventEmitter(TextFieldDemo(Polymer.Element)) {
Expand Down
53 changes: 44 additions & 9 deletions src/vaadin-number-field.html
Expand Up @@ -52,6 +52,8 @@

<script>
(function() {
let memoizedTemplate;

/**
* `<vaadin-number-field>` is a Polymer 2 element for number field control in forms.
*
Expand All @@ -64,9 +66,6 @@
* @extends Vaadin.TextFieldElement
* @demo demo/index.html
*/

let memoizedTemplate;

class NumberFieldElement extends Vaadin.TextFieldElement {
static get is() {
return 'vaadin-number-field';
Expand Down Expand Up @@ -99,13 +98,24 @@
type: Number,
reflectToAttribue: true,
observer: '_maxChanged'
},

/**
* Specifies the allowed number intervals of the field.
*/
step: {
reflectToAttribue: true,
observer: '_stepChanged',
value: 1
}

};
}

ready() {
super.ready();
this.focusElement.type = 'number';
this.focusElement.addEventListener('change', this.__onInputChange.bind(this));
}

static get template() {
Expand All @@ -131,15 +141,24 @@
}

_decreaseValue() {
this._allowed(-1) && this.value --;
this._allowed(-1) && this.__add(-1);
}

_increaseValue() {
this._allowed(1) && this.value ++;
this._allowed(1) && this.__add(1);
}

__add(sign) {
const incr = sign * (this.step || 1);
// Behave like native number input adjusting to the next exact multiple of step.
this.value = (incr + (incr * Math.floor((parseFloat(this.value || 0) / incr).toFixed(1)))).toFixed(this.__decimals);
this.dispatchEvent(new CustomEvent('change', {bubbles: true}));
this.focusElement.focus();
}

_allowed(increment, value, min, max) {
return !this.disabled && (increment < 0
_allowed(sign, value, min, max) {
const incr = sign * (this.step || 1);
return !this.disabled && (incr < 0
? this.min == null || this.value > this.min
: this.max == null || this.value < this.max);
}
Expand All @@ -153,12 +172,28 @@
}

_valueChanged(newVal, oldVal) {
// native input does not accept certain values and converts
// from number to string.
// native input does not accept certain values, and also converts
// from number to string, so we need synchronize value
this.value = this.focusElement.value;
super._valueChanged(this.value, oldVal);
}

__onInputChange() {
this.checkValidity() && this.__adjustDecimals();
}

__adjustDecimals() {
// when step is not an integer, adjust decimals.
this.focusElement.value && (this.value = parseFloat(this.focusElement.value).toFixed(this.__decimals));
}

_stepChanged(step) {
this.focusElement.step = step;
// Compute number of dedimals to display in input based on provided step
this.__decimals = String(step).replace(/^\d*\.?(.*)?$/, '$1').length;
this.__adjustDecimals();
}

checkValidity() {
// text-field mixin does not check against `min` and `max`
if (this.min !== undefined || this.max !== undefined) {
Expand Down
100 changes: 78 additions & 22 deletions test/number-field.html
Expand Up @@ -5,7 +5,7 @@
<title>vaadin-number-field tests</title>
<script src="../../web-component-tester/browser.js"></script>
<script src="../../webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="../../test-fixture/test-fixture.html">
<link rel="import" href="../../test-fixture/test-fixture.html">
<link rel="import" href="../vaadin-number-field.html">
<link rel="import" href="../../iron-form/iron-form.html">
</head>
Expand All @@ -18,29 +18,29 @@
</test-fixture>

<script>
describe('number-field', function() {
describe('number-field', () => {
var numberField, input, decreaseButton, increaseButton;

beforeEach(function() {
beforeEach(() => {
numberField = fixture('default');
input = numberField.focusElement;
decreaseButton = numberField.root.querySelector('[part=decrease-button]');
increaseButton = numberField.root.querySelector('[part=increase-button]');
});

describe('native', function() {
describe('native', () => {

it('should have [type=number]', function() {
it('should have [type=number]', () => {
expect(input.type).to.equal('number');
});

it('should have hidden controls', function() {
it('should have hidden controls', () => {
expect(decreaseButton.hidden).to.be.true;
expect(increaseButton.hidden).to.be.true;
});

['min', 'max'].forEach(function(attr) {
it('should set numeric attribute ' + attr, function() {
it('should set numeric attribute ' + attr, () => {
var value = 5;
numberField[attr] = value;
var attrval = input.getAttribute(attr);
Expand All @@ -51,8 +51,8 @@

});

describe('value control buttons', function() {
it('should have value controls when hasControls is set to true', function() {
describe('value control buttons', () => {
it('should have value controls when hasControls is set to true', () => {
expect(decreaseButton.hidden).to.be.true;
expect(increaseButton.hidden).to.be.true;

Expand All @@ -62,23 +62,80 @@
expect(increaseButton.hidden).to.be.false;
});

it('should increase value by 1 when increaseButton is clicked', function() {
it('should increase value by 1 when increaseButton is clicked', () => {
numberField.value = 0;

increaseButton.click();

expect(numberField.value).to.be.equal('1');
});

it('should decrease value by 1 when increaseButton is clicked', function() {
it('should focus input and dispatch change event when a button is clicked', () => {
const changeSpy = sinon.spy();
numberField.addEventListener('change', changeSpy);

let hasFocus;
input.focus = () => {
hasFocus = true;
};

increaseButton.click();
expect(hasFocus).to.be.true;
expect(changeSpy.callCount).to.equal(1);
});

it('should increase value by 0.2 when step is 0.2 and increaseButton is clicked', () => {
numberField.step = 0.2;
numberField.value = 0.6;

increaseButton.click();

expect(numberField.value).to.be.equal('0.8');
});

it('should adjust value to exact step when increaseButton is clicked', () => {
numberField.value = 0.5;
numberField.step = 0.2;

increaseButton.click();

expect(numberField.value).to.be.equal('0.6');
});

it('should decrease value by 1 when decreaseButton is clicked', () => {
numberField.value = 0;

decreaseButton.click();

expect(numberField.value).to.be.equal('-1');
});

it('should not increase value when increaseButton is clicked and max value is reached', function() {
it('should decrease value by 0.2 when decreaseButton is clicked', () => {
numberField.value = 0;
numberField.step = 0.2;

decreaseButton.click();

expect(numberField.value).to.be.equal('-0.2');
});

it('should adjust value to exact step when decreaseButton is clicked', () => {
numberField.value = 7;
numberField.step = 2;

decreaseButton.click();

expect(numberField.value).to.be.equal('6');
});

it('should adjust decimals based on the step value', () => {
numberField.value = 1;
numberField.step = 0.001;

expect(numberField.value).to.be.equal('1.000');
});

it('should not increase value when increaseButton is clicked and max value is reached', () => {
numberField.value = 0;
numberField.max = 0;

Expand All @@ -88,7 +145,7 @@
});


it('should not decrease value when decreaseButton is clicked and min value is reached', function() {
it('should not decrease value when decreaseButton is clicked and min value is reached', () => {
numberField.value = 0;
numberField.min = 0;

Expand All @@ -97,26 +154,26 @@
expect(numberField.value).to.be.equal('0');
});

it('should not disable buttons if there are no limits set', function() {
it('should not disable buttons if there are no limits set', () => {
expect(decreaseButton.hasAttribute('disabled')).to.be.false;
expect(increaseButton.hasAttribute('disabled')).to.be.false;
});

it('should disable plus button if max value is reached', function() {
it('should disable plus button if max value is reached', () => {
numberField.value = 0;
numberField.min = 0;
expect(decreaseButton.hasAttribute('disabled')).to.be.true;
expect(increaseButton.hasAttribute('disabled')).to.be.false;
});

it('should disable minus button if min value is reached', function() {
it('should disable minus button if min value is reached', () => {
numberField.value = 1;
numberField.max = 1;
expect(decreaseButton.hasAttribute('disabled')).to.be.false;
expect(increaseButton.hasAttribute('disabled')).to.be.true;
});

it('should not change value when number field is disabled and controls are clicked', function() {
it('should not change value when number field is disabled and controls are clicked', () => {
numberField.disabled = true;
numberField.value = 0;

Expand All @@ -126,26 +183,25 @@
decreaseButton.click();
expect(numberField.value).to.be.equal('0');
});

});

describe('input validation', function() {
describe('input validation', () => {

it('should be valid with numeric vaules', function() {
it('should be valid with numeric vaules', () => {
expect(numberField.validate()).to.be.true;

numberField.value = '1';
expect(numberField.focusElement.value).to.be.equal('1');
expect(numberField.validate()).to.be.true;
});

it('should prevent setting non-numeric values', function() {
it('should prevent setting non-numeric values', () => {
numberField.value = 'foo';
expect(numberField.value).to.be.empty;
expect(numberField.validate()).to.be.true;
});

it('should validate when setting limits', function() {
it('should validate when setting limits', () => {
numberField.min = 2;
numberField.max = 4;

Expand Down

0 comments on commit ab5fdec

Please sign in to comment.