All Downloads are FREE. Search and download functionalities are using the official Maven repository.

software.xdev.vaadin.daterange_picker.ui.DateRangePickerOverlay Maven / Gradle / Ivy

There is a newer version: 4.2.0
Show newest version
/*
 * Copyright © 2020 XDEV Software (https://xdev.software)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package software.xdev.vaadin.daterange_picker.ui;

import java.time.LocalDate;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;

import com.vaadin.flow.component.AbstractField.ComponentValueChangeEvent;
import com.vaadin.flow.component.AttachEvent;
import com.vaadin.flow.component.ComponentEvent;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.Composite;
import com.vaadin.flow.component.HasStyle;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.datepicker.DatePicker;
import com.vaadin.flow.component.dependency.CssImport;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.data.binder.HasItems;
import com.vaadin.flow.shared.Registration;

import software.xdev.vaadin.daterange_picker.business.DateRange;
import software.xdev.vaadin.daterange_picker.business.DateRangeModel;
import software.xdev.vaadin.daterange_picker.business.DateRangeResult;


/**
 * Overlay of the expanded {@link DateRangePicker}
 * 
 * @author AB
 *
 */
@CssImport(DateRangePickerStyles.LOCATION)
public class DateRangePickerOverlay extends Composite implements
	HasItems,
	FlexComponent
{
	/*
	 * Fields
	 */
	protected boolean readOnly = false;
	
	protected DateRangePicker dateRangePicker;
	protected DateRangeModel currentModel;
	
	/*
	 * UI-Comp
	 */
	protected final Button btnBackwardRange = new Button(VaadinIcon.ANGLE_LEFT.create());
	protected ComboBox cbDateRange = new ComboBox<>("Period");
	protected final Button btnForwardRange = new Button(VaadinIcon.ANGLE_RIGHT.create());
	
	protected DatePicker dpStart = new DatePicker("Start");
	protected DatePicker dpEnd = new DatePicker("End");
	
	public DateRangePickerOverlay(final DateRangePicker dateRangePicker)
	{
		this.dateRangePicker = Objects.requireNonNull(dateRangePicker);
		this.currentModel = this.dateRangePicker.getValue();
		
		this.initUI();
		this.registerListeners();
	}
	
	protected void initUI()
	{
		this.btnBackwardRange
			.addClassNames(DateRangePickerStyles.FLEX_CHILD_CONTENTSIZE, DateRangePickerStyles.CLICKABLE);
		
		this.cbDateRange.addClassName(DateRangePickerStyles.FLEX_CHILD_AUTOGROW);
		this.setTextFieldDefaultWidthFlexConform(this.cbDateRange);
		
		this.btnForwardRange
			.addClassNames(DateRangePickerStyles.FLEX_CHILD_CONTENTSIZE, DateRangePickerStyles.CLICKABLE);
		
		final HorizontalLayout hlRange = new HorizontalLayout();
		hlRange.addClassNames(DateRangePickerStyles.FLEX_CHILD_AUTOGROW, DateRangePickerStyles.FLEX_CONTAINER);
		hlRange.setAlignItems(Alignment.BASELINE);
		hlRange.setJustifyContentMode(JustifyContentMode.BETWEEN);
		hlRange.setMargin(false);
		hlRange.setSpacing(true);
		hlRange.setPadding(false);
		hlRange.add(this.btnBackwardRange, this.cbDateRange, this.btnForwardRange);
		
		this.initDatePicker(this.dpStart);
		this.initDatePicker(this.dpEnd);
		
		final HorizontalLayout hlDatepickers = new HorizontalLayout();
		hlDatepickers.addClassNames(DateRangePickerStyles.FLEX_CHILD_AUTOGROW, DateRangePickerStyles.FLEX_CONTAINER);
		hlDatepickers.setMargin(false);
		hlDatepickers.setSpacing(true);
		hlDatepickers.setPadding(false);
		hlDatepickers.add(this.dpStart, this.dpEnd);
		
		this.addClassName(DateRangePickerStyles.FLEX_CONTAINER);
		this.add(hlRange, hlDatepickers);
		this.getContent().setPadding(true);
	}
	
	protected void initDatePicker(final DatePicker dp)
	{
		this.setTextFieldDefaultWidthFlexConform(dp);
		dp.addClassName(DateRangePickerStyles.FLEX_CHILD_AUTOGROW);
		dp.setWeekNumbersVisible(true);
	}
	
	@Override
	protected void onAttach(final AttachEvent attachEvent)
	{
		this.cbDateRange.setItemLabelGenerator(this.dateRangePicker.getDateRangeLocalizerFunction());
		
		this.dateRangePicker.getDatePickerI18n()
			.ifPresent(i18n ->
			{
				this.dpStart.setI18n(i18n);
				this.dpEnd.setI18n(i18n);
			});
	}
	
	protected void setTextFieldDefaultWidthFlexConform(final HasStyle component)
	{
		component.getStyle().set("--vaadin-field-default-width", "auto");
	}
	
	protected void registerListeners()
	{
		this.cbDateRange.addValueChangeListener(this::onComboBoxDateRangeValueChanged);
		this.btnBackwardRange.addClickListener(ev -> this.moveRange(-1));
		this.btnForwardRange.addClickListener(ev -> this.moveRange(+1));
		this.dpStart.addValueChangeListener(this::onDatePickerValueChanged);
		this.dpEnd.addValueChangeListener(this::onDatePickerValueChanged);
	}
	
	protected void onComboBoxDateRangeValueChanged(final ComponentValueChangeEvent, D> ev)
	{
		if(!ev.isFromClient())
		{
			return;
		}
		this.onValueChange(model -> model.getDateRange().calcFor(model.getStart()));
	}
	
	protected void onDatePickerValueChanged(final ComponentValueChangeEvent ev)
	{
		if(!ev.isFromClient())
		{
			return;
		}
		this.onValueChange(model -> model.getDateRange().calcFor(ev.getValue()));
	}
	
	protected void moveRange(final int dif)
	{
		this.onValueChange(model -> model.getDateRange().moveDateRange(model.getStart(), dif));
	}
	
	protected void calcModel(final Optional optResult, final DateRangeModel model)
	{
		if(optResult.isEmpty())
		{
			return;
		}
		
		final DateRangeResult result = optResult.get();
		model.setStart(result.getStart());
		model.setEnd(result.getEnd());
	}
	
	protected void onValueChange(final Function, Optional> calcFunc)
	{
		final DateRangeModel model = this.getModelFromComponents();
		
		this.calcModel(calcFunc.apply(model), model);
		this.updateComponentsFromModel(model);
		
		final DateRangeModel oldValue = this.currentModel;
		this.setCurrentModel(model);
		
		this.fireValueChanged(oldValue, true);
	}
	
	protected DateRangeModel getModelFromComponents()
	{
		return new DateRangeModel<>(this.dpStart.getValue(), this.dpEnd.getValue(), this.cbDateRange.getValue());
	}
	
	protected void updateComponentsFromModel(final DateRangeModel model)
	{
		final boolean datepickerReadonly = !model.getDateRange().isSettable();
		this.dpStart.setReadOnly(datepickerReadonly);
		this.dpEnd.setReadOnly(datepickerReadonly);
		
		final boolean fastNavEnabled = model.getDateRange().isMovable();
		this.btnBackwardRange.setEnabled(fastNavEnabled);
		this.btnForwardRange.setEnabled(fastNavEnabled);
		
		final boolean allowRangeLimitExceeding =
			this.dateRangePicker.isAllowRangeLimitExceeding()
				// If it's not calcable we can't verify that the set value is correct (e.g. when it's a free value)
				&& model.getDateRange().isCalcable();
		this.dpEnd.setMin(allowRangeLimitExceeding ? null : model.getStart());
		this.dpStart.setMax(allowRangeLimitExceeding ? null : model.getEnd());
		
		this.cbDateRange.setValue(model.getDateRange());
		this.dpStart.setValue(model.getStart());
		this.dpEnd.setValue(model.getEnd());
	}
	
	protected void setCurrentModel(final DateRangeModel model)
	{
		this.currentModel = model;
	}
	
	protected void fireValueChanged(final DateRangeModel oldValue, final boolean isFromClient)
	{
		this.fireEvent(new DateRangeOverlayValueChangeEvent(this, oldValue, isFromClient));
	}
	
	@Override
	public void setItems(final Collection items)
	{
		Objects.requireNonNull(items);
		
		this.getCbDateRange().setItems(items);
	}
	
	// region UI Getter
	public ComboBox getCbDateRange()
	{
		return this.cbDateRange;
	}
	
	public DatePicker getDpStart()
	{
		return this.dpStart;
	}
	
	public DatePicker getDpEnd()
	{
		return this.dpEnd;
	}
	
	// endregion
	
	// region Manage Data externally
	public DateRangeModel getModel()
	{
		return this.currentModel;
	}
	
	public void setModel(final DateRangeModel model)
	{
		final DateRangeModel oldValue = this.currentModel;
		
		this.currentModel = model;
		this.updateComponentsFromModel(this.currentModel);
		
		this.fireValueChanged(oldValue, false);
	}
	
	public void setReadOnly(final boolean readOnly)
	{
		this.readOnly = readOnly;
		
		this.cbDateRange.setReadOnly(readOnly);
		
		this.btnBackwardRange.setEnabled(!readOnly);
		this.btnForwardRange.setEnabled(!readOnly);
		
		if(readOnly)
		{
			this.dpStart.setReadOnly(true);
			this.dpEnd.setReadOnly(true);
		}
		else
		{
			// Fix read-only if e.g. TODAY is selected
			this.updateComponentsFromModel(this.getModelFromComponents());
		}
	}
	
	public boolean isReadOnly()
	{
		return this.readOnly;
	}
	
	@SuppressWarnings({"unchecked", "rawtypes"})
	public Registration addValueChangeListener(final ComponentEventListener listener)
	{
		return this.addListener(DateRangeOverlayValueChangeEvent.class, (ComponentEventListener)listener);
	}
	
	// endregion
	
	public class DateRangeOverlayValueChangeEvent extends ComponentEvent>
	{
		private final DateRangeModel oldValue;
		private final boolean isFromClient;
		
		public DateRangeOverlayValueChangeEvent(
			final DateRangePickerOverlay source,
			final DateRangeModel oldValue,
			final boolean isFromClient)
		{
			super(source, false);
			this.oldValue = oldValue;
			this.isFromClient = isFromClient;
		}

		public DateRangeModel getOldValue()
		{
			return this.oldValue;
		}

		@Override
		public boolean isFromClient()
		{
			return this.isFromClient;
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy