Skip to content

Commit

Permalink
feat: add vaadin-number-field element (#110)
Browse files Browse the repository at this point in the history
* Implement number-field

* Include number-field demos in demos suite

* Align the name of the file with number field demo with other demos

* The reference to native input has changed. Fixed tests

* move number field to src, and create its lumo file

* Update screenshots

* Validate against min, max. Synchronize input value

* Disable buttons on readonly

* Fix min/max limits when set to zero

* Simply code by reducing conditional blocks

* Disable buttons when limits have been reached

* Align with master

* Adding step feature and focus input on click.

* Improve vaadin-number-field (#292)

* Improve vaadin-number-field

* Fix value control buttons focusing behavior for touch

* Add browser native validation behaviour
  • Loading branch information
gatanaso authored and web-padawan committed Jan 16, 2019
1 parent 424d5be commit 9bed48b
Show file tree
Hide file tree
Showing 22 changed files with 676 additions and 1 deletion.
2 changes: 2 additions & 0 deletions bower.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
"main": [
"vaadin-text-field.html",
"vaadin-password-field.html",
"vaadin-number-field.html",
"vaadin-text-area.html",
"vaadin-email-field.html",
"theme/lumo/vaadin-placeholder-styles.html",
"theme/material/vaadin-placeholder-styles.html",
"theme/material/vaadin-password-field.html",
"theme/material/vaadin-number-field.html",
"theme/material/vaadin-text-area.html",
"theme/material/vaadin-text-field.html",
"theme/material/vaadin-email-field.html"
Expand Down
10 changes: 10 additions & 0 deletions demo/demos.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@
"image": ""
}
},
{
"name": "Number Field",
"url": "number-field-demos",
"src": "number-field-demos.html",
"meta": {
"title": "Vaadin Number Field Examples",
"description": "",
"image": ""
}
},
{
"name": "Text Area",
"url": "text-area-demos",
Expand Down
1 change: 1 addition & 0 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<link rel="import" href="../vaadin-password-field.html">
<link rel="import" href="../vaadin-email-field.html">
<link rel="import" href="../vaadin-text-area.html">
<link rel="import" href="../vaadin-number-field.html">
<link rel="import" href="../../iron-form/iron-form.html">
<link rel="import" href="../../vaadin-lumo-styles/icons.html">

Expand Down
56 changes: 56 additions & 0 deletions demo/number-field-demos.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<dom-module id="number-field-demos">
<template>
<style include="vaadin-component-demo-shared-styles">
:host {
display: block;
}
</style>

<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 number validation and the additional features listed on this page.</p>


<h3>Basic number field</h3>
<vaadin-demo-snippet id="number-field-demos-basic">
<template preserve-content>
<vaadin-number-field label="Years of expertise"></vaadin-number-field>
</template>
</vaadin-demo-snippet>

<h3>Validation</h3>
<vaadin-demo-snippet id="number-field-demos-basic-valiation">
<template preserve-content>
<vaadin-number-field label="Years of expertise" required error-message="Please enter your years of expertise"></vaadin-number-field>
</template>
</vaadin-demo-snippet>

<h3>Number field with controls</h3>
<vaadin-demo-snippet id="number-field-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>
<vaadin-demo-snippet id="number-field-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="number-field-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 NumberFieldDemos extends DemoReadyEventEmitter(TextFieldDemo(Polymer.Element)) {
static get is() {
return 'number-field-demos';
}
}
customElements.define(NumberFieldDemos.is, NumberFieldDemos);
</script>
</dom-module>
245 changes: 245 additions & 0 deletions src/vaadin-number-field.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
<!--
@license
Copyright (c) 2017 Vaadin Ltd.
This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
-->

<link rel="import" href="../../polymer/polymer-element.html">
<link rel="import" href="../../polymer/lib/elements/custom-style.html">
<link rel="import" href="vaadin-text-field.html">

<dom-module id="vaadin-number-field-template">
<template>
<style>
:host([readonly]) {
pointer-events: none;
}

[part="decrease-button"]::before {
content: "−";
}

[part="increase-button"]::before {
content: "+";
}

[part="decrease-button"],
[part="increase-button"] {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}

/* Hide the native arrow icons */
[part="value"]::-webkit-outer-spin-button,
[part="value"]::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}

[part="value"] {
/* Older Firefox versions (v47.0) requires !important */
-moz-appearance: textfield !important;
}
</style>

<div
disabled$="[[!_allowed(-1, value, min, max)]]"
part="decrease-button"
on-click="_decreaseValue"
on-touchend="_decreaseButtonTouchend"
hidden$="[[!hasControls]]">
</div>

<div
disabled$="[[!_allowed(1, value, min, max)]]"
part="increase-button"
on-click="_increaseValue"
on-touchend="_increaseButtonTouchend"
hidden$="[[!hasControls]]">
</div>
</template>

<script>
(function() {
let memoizedTemplate;

/**
* `<vaadin-number-field>` is a Polymer 2 element for number field control in forms.
*
* ```html
* <vaadin-number-field label="Number">
* </vaadin-number-field>
* ```
*
* @memberof Vaadin
* @extends Vaadin.TextFieldElement
* @demo demo/index.html
*/
class NumberFieldElement extends Vaadin.TextFieldElement {
static get is() {
return 'vaadin-number-field';
}

static get properties() {
return {
/**
* Set to true to display value increase/decrease controls.
*/
hasControls: {
type: Boolean,
value: false,
reflectToAttribue: true
},

/**
* The minimum value of the field.
*/
min: {
type: Number,
reflectToAttribue: true,
observer: '_minChanged'
},

/**
* The maximum value of the field.
*/
max: {
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.__previousValidInput = this.value || '';
this.focusElement.type = 'number';
this.focusElement.addEventListener('change', this.__onInputChange.bind(this));
}

_decreaseButtonTouchend(e) {
// Cancel the following click and focus events
e.preventDefault();
this._decreaseValue();
}

_increaseButtonTouchend(e) {
// Cancel the following click and focus events
e.preventDefault();
this._increaseValue();
}

static get template() {
if (!memoizedTemplate) {
// Clone the superclass template
memoizedTemplate = super.template.cloneNode(true);

// Retrieve this element's dom-module template
const thisTemplate = Polymer.DomModule.import(this.is + '-template', 'template');
const decreaseButton = thisTemplate.content.querySelector('[part="decrease-button"]');
const increaseButton = thisTemplate.content.querySelector('[part="increase-button"]');
const styles = thisTemplate.content.querySelector('style');

// Add the buttons and styles to the text-field template
const inputField = memoizedTemplate.content.querySelector('[part="input-field"]');
const prefixSlot = memoizedTemplate.content.querySelector('[name="prefix"]');
inputField.insertBefore(decreaseButton, prefixSlot);
inputField.appendChild(increaseButton);
memoizedTemplate.content.appendChild(styles);

return memoizedTemplate;
}
}

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

_increaseValue() {
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 = this.focusElement.value =
(incr + (incr * Math.floor((parseFloat(this.value || 0) / incr).toFixed(1)))).toFixed(this.__decimals);
this.dispatchEvent(new CustomEvent('change', {bubbles: true}));
}

_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);
}

_minChanged() {
this.focusElement.min = this.min;
}

_maxChanged() {
this.focusElement.max = this.max;
}

_valueChanged(newVal, oldVal) {
// Validate value to be numeric
if (newVal && isNaN(parseFloat(newVal).toFixed(this.__decimals))) {
this.value = '';
} else if (!isNaN(parseFloat(this.value)) &&
parseFloat(this.value) !== parseFloat(parseFloat(this.value).toFixed(this.__decimals))) {
// Validate correct decimals
this.value = parseFloat(parseFloat(this.value).toFixed(this.__decimals));
}

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) {
this.invalid = !this.focusElement.checkValidity();
}
return super.checkValidity();
}
}

window.customElements.define(NumberFieldElement.is, NumberFieldElement);

/**
* @namespace Vaadin
*/
window.Vaadin = window.Vaadin || {};
Vaadin.NumberFieldElement = NumberFieldElement;
})();
</script>
</dom-module>
Loading

0 comments on commit 9bed48b

Please sign in to comment.