Skip to content

Commit

Permalink
feat: add support for min and max limits
Browse files Browse the repository at this point in the history
Close #75
  • Loading branch information
flang authored and javier-godoy committed Dec 1, 2023
1 parent 18b07ef commit becd21b
Show file tree
Hide file tree
Showing 3 changed files with 206 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,23 @@
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.function.SerializableFunction;
import com.vaadin.flow.internal.JsonSerializer;
import elemental.json.Json;
import elemental.json.JsonValue;
import java.time.YearMonth;
import java.util.Objects;
import java.util.Optional;

@SuppressWarnings("serial")
@Tag("fc-year-month-field")
@JsModule("./fc-year-month-field/fc-year-month-field.js")
public class YearMonthField extends AbstractSinglePropertyField<YearMonthField, YearMonth> implements HasTheme {
public class YearMonthField extends AbstractSinglePropertyField<YearMonthField, YearMonth>
implements HasTheme {

private static final String VALUE_PROPERTY = "value";

private YearMonth max;
private YearMonth min;

private static <R,S> SerializableFunction<R,S> map(SerializableFunction<R,S> f) {
return r->Optional.ofNullable(r).map(f).orElse(null);
}
Expand All @@ -57,4 +63,50 @@ public void setI18n(DatePickerI18n i18n) {
getElement().setPropertyJson("i18n", JsonSerializer.toJson(i18n));
}

/**
* Sets the minimum year/month in the field.
*
* @param min the minimum year/month that is allowed to be selected, or <code>null</code> to
* remove any minimum constraints
*/
public void setMin(YearMonth min) {
JsonValue value = min == null ? Json.createNull()
: Json.parse("{'month': " + min.getMonth().ordinal() + ", 'year': " + min.getYear() + "}");
getElement().setPropertyJson("min", value);
this.min = min;
}

/**
* Gets the minimum year/month in the field.
*
* @return the minimum year/month that is allowed to be selected, or <code>null</code> if there's
* no minimum
*/
public YearMonth getMin() {
return min;
}

/**
* Sets the maximum year/month in the field.
*
* @param min the maximum year/month that is allowed to be selected, or <code>null</code> to
* remove any maximum constraints
*/
public void setMax(YearMonth max) {
JsonValue value = max == null ? Json.createNull()
: Json.parse("{'month': " + max.getMonth().ordinal() + ", 'year': " + max.getYear() + "}");
getElement().setPropertyJson("max", value);
this.max = max;
}

/**
* Gets the maximum year/month in the field.
*
* @return the maximum year/month that is allowed to be selected, or <code>null</code> if there's
* no maximum
*/
public YearMonth getMax() {
return max;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* #L%
*/
import { css, html, LitElement } from 'lit';

export class YearMonthField extends LitElement {

static get is() { return 'fc-year-month-field'; }
Expand All @@ -29,6 +29,8 @@ export class YearMonthField extends LitElement {
date: {type: Date},
year: {type: Number, readOnly: true, state: true},
month: {type: Number, readOnly: true, state: true},
min: {type: Object, readOnly: true},
max: {type: Object, readOnly: true},
i18n: {type: Object}
}
}
Expand All @@ -39,6 +41,10 @@ export class YearMonthField extends LitElement {
this._i18n = {};
this.__setDefaultFormatTitle(this._i18n);
this.__setDefaultMonthNames(this._i18n);
this._decMonthDisabled = false;
this._incMonthDisabled = false;
this._decYearDisabled = false;
this._incYearDisabled = false;
}

set i18n(value) {
Expand All @@ -56,19 +62,42 @@ export class YearMonthField extends LitElement {

get i18n() { return this._i18n; }

/**
* Checks if value is between min and max (inclusive).
*/
__checkRange(value) {
return (!this.min || value >= this._minAsNumber)
&& (!this.max || value <= this._maxAsNumber);
}

willUpdate(changedProperties) {
if (changedProperties.has('value')) {
this.date = this.value ? new Date(this.value.substring(0,7)+'-02') : new Date();
}
if (changedProperties.has('date')) {
this.value = this.date.toISOString().substring(0,7);
this.date.setDate(1);
this._year = this.date.getFullYear();
this._month = this.date.getMonth()+1;
this.date = this.value ? new Date(this.value.substring(0,7)+'-02') : new Date();
}
if (changedProperties.has('i18n') && !this.i18n) {
this.i18n = changedProperties.get('i18n');
}
if (changedProperties.has('date') || changedProperties.has('min') || changedProperties.has('max')) {
const strValue = this.date.toISOString().substring(0,7);
this.__normalizeValue(strValue);
this.date.setDate(1);
this._year = this.date.getFullYear();
this._month = this.date.getMonth() + 1;
this.__toggleButtons(this.min, this.max);
}
}

__normalizeValue(value) {
const intValue = parseInt(this.__yearMonthAsNumber(this.date.getFullYear(), this.date.getMonth()));
if (this.min && intValue < this._minAsNumber){
this.value = this.min.year + '-' + String(this.min.month + 1).padStart(2, '0');
this.date = new Date(this.min.year, this.min.month);
} if (this.max && intValue > this._maxAsNumber){
this.value = this.max.year + '-' + String(this.max.month + 1).padStart(2, '0');
this.date = new Date(this.max.year, this.max.month);
} else {
this.value = value;
}
}

updated(changedProperties) {
Expand Down Expand Up @@ -96,11 +125,11 @@ export class YearMonthField extends LitElement {

render() {
return html`
<vaadin-button theme="small icon" @click="${this.__decYear}">&lt;&lt;</vaadin-button>
<vaadin-button theme="small icon" @click="${this.__decMonth}">&lt;</vaadin-button>
<vaadin-button id="dec-year-button"theme="small icon" @click="${this.__decYear}" ?disabled="${this._decYearDisabled}">&lt;&lt;</vaadin-button>
<vaadin-button id="dec-month-button" theme="small icon" @click="${this.__decMonth}" ?disabled="${this._decMonthDisabled}">&lt;</vaadin-button>
<div part="header">${this.formatTitle(this._month, this._year)}</div>
<vaadin-button theme="small icon" @click="${this.__incMonth}">&gt;</vaadin-button>
<vaadin-button theme="small icon" @click="${this.__incYear}">&gt;&gt;</vaadin-button>
<vaadin-button id="inc-month-button" theme="small icon" @click="${this.__incMonth}" ?disabled="${this._incMonthDisabled}">&gt;</vaadin-button>
<vaadin-button id="inc-year-button" theme="small icon" @click="${this.__incYear}" ?disabled="${this._incYearDisabled}">&gt;&gt;</vaadin-button>
`;
}

Expand All @@ -116,19 +145,27 @@ export class YearMonthField extends LitElement {
}

__decYear() {
this.__addMonths(-12);
if(!this._minAsNumber || this._minAsNumber < this.__dateAsNumber()){
this.__addMonths(-12);
}
}

__decMonth() {
this.__addMonths(-1);
if(!this._minAsNumber || this._minAsNumber < this.__dateAsNumber()){
this.__addMonths(-1);
}
}

__incYear() {
this.__addMonths(12);
if(!this._maxAsNumber || this._maxAsNumber > this.__dateAsNumber()){
this.__addMonths(12);
}
}

__incMonth() {
this.__addMonths(1);
if(!this._maxAsNumber || this._maxAsNumber > this.__dateAsNumber()){
this.__addMonths(1);
}
}

__setDefaultFormatTitle(obj){
Expand All @@ -138,6 +175,65 @@ export class YearMonthField extends LitElement {
__setDefaultMonthNames(obj){
obj.monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
}

/**
* Returns a number joining year and month. Month is left padded with zero up to two chars length.
*/
__yearMonthAsNumber(year, month) {
return parseInt(year + '' + String(month).padStart(2, '0'));
}

/**
* Converts this.date to yearMonth number
*/
__dateAsNumber() {
return this.date ? this.__yearMonthAsNumber(this.date.getFullYear(), this.date.getMonth()) : undefined;
}

/**
* Converts min or max value to yearMonth number.
*/
__minMaxAsNumber(value) {
return value ? this.__yearMonthAsNumber(value.year, value.month) : undefined;
}

/**
* Converts this.min to number
* @private
*/
get _minAsNumber() {
return this.__minMaxAsNumber(this.min);
}

/**
* Converts this.max to number
* @private
*/
get _maxAsNumber() {
return this.__minMaxAsNumber(this.max);
}

/**
* Returns true if delta between minOrMax and this.date is less than 1 year (12 months)
*/
__dateDeltaLtYear(minOrMax) {
const toMonths = (year, month) => year * 12 + month;
const dateToMonths = toMonths(this.date.getFullYear(), this.date.getMonth());
return Math.abs(dateToMonths - toMonths(minOrMax.year, minOrMax.month)) < 12;
}

/**
* Enable or disabled navigation buttons
*/
__toggleButtons(min, max) {
const minAsNumber = this.__minMaxAsNumber(min);
const maxAsNumber = this.__minMaxAsNumber(max);

this._decMonthDisabled = minAsNumber && minAsNumber >= this.__dateAsNumber();
this._incMonthDisabled = maxAsNumber && maxAsNumber <= this.__dateAsNumber();
this._decYearDisabled = minAsNumber && (minAsNumber >= this.__dateAsNumber() || this.__dateDeltaLtYear(min));
this._incYearDisabled = maxAsNumber && (maxAsNumber <= this.__dateAsNumber() || this.__dateDeltaLtYear(max));
}

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,18 @@
package com.flowingcode.addons.ycalendar;

import com.flowingcode.vaadin.addons.demo.DemoSource;
import com.vaadin.flow.component.Text;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.datepicker.DatePicker.DatePickerI18n;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.radiobutton.RadioButtonGroup;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import java.time.YearMonth;
import java.util.List;
import java.util.Optional;

@DemoSource
@PageTitle("Year-Month Field")
Expand Down Expand Up @@ -62,6 +68,41 @@ public YearMonthFieldDemo() {
});

add(languageSelector);

Span minRangeValue = new Span("-");
Span maxRangeValue = new Span("-");
add(new Div(new Text("Min: "), minRangeValue, new Text(" :: Max: "), maxRangeValue));

YearMonth min = YearMonth.now().minusYears(2);
Button setMinRangeButton = new Button("Set min " + min.toString());
setMinRangeButton.addClickListener(e -> {
field.setMin(min);
minRangeValue
.setText(Optional.ofNullable(field.getMin()).map(YearMonth::toString).orElse("-"));
});

YearMonth max = YearMonth.now().plusYears(2);
Button setMaxRangeButton = new Button("Set max " + max.toString());
setMaxRangeButton.addClickListener(e -> {
field.setMax(max);
maxRangeValue
.setText(Optional.ofNullable(field.getMax()).map(YearMonth::toString).orElse("-"));
});

Button clearRangeButton = new Button("Clear range");
clearRangeButton.addClickListener(e -> {
field.setMin(null);
field.setMax(null);
minRangeValue.setText("-");
maxRangeValue.setText("-");
});

YearMonth newValue = YearMonth.now().plusYears(3);
Button setValueButton = new Button("Set value " + newValue.toString());
setValueButton.addClickListener(e -> field.setValue(newValue));

add(new HorizontalLayout(setMinRangeButton, setMaxRangeButton, clearRangeButton,
setValueButton));
}

}
Expand Down

0 comments on commit becd21b

Please sign in to comment.