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

com.vaadin.flow.component.tabs.Tabs Maven / Gradle / Ivy

There is a newer version: 24.6.0
Show newest version
/*
 * Copyright 2000-2017 Vaadin Ltd.
 *
 * 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 com.vaadin.flow.component.tabs;

import java.io.Serializable;
import java.util.Locale;
import java.util.Objects;
import java.util.stream.Stream;

import com.vaadin.flow.component.AttachEvent;
import com.vaadin.flow.component.ClientCallable;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ComponentEvent;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.HasOrderedComponents;
import com.vaadin.flow.component.HasSize;
import com.vaadin.flow.component.Synchronize;
import com.vaadin.flow.shared.Registration;

/**
 * Server-side component for the {@code vaadin-tabs} element.
 * 

* {@link Tab} components can be added to this component with the * {@link #add(Tab...)} method or the {@link #Tabs(Tab...)} constructor. The Tab * components added to it can be selected with the * {@link #setSelectedIndex(int)} or {@link #setSelectedTab(Tab)} methods. * Removing the selected tab from the component changes the selection to the * next available tab. *

* Note: Adding or removing Tab components via the Element API, * eg. {@code tabs.getElement().insertChild(0, tab.getElement()); }, doesn't * update the selected index, so it may cause the selected tab to change * unexpectedly. * * @author Vaadin Ltd. */ public class Tabs extends GeneratedVaadinTabs implements HasOrderedComponents, HasSize { private static final String SELECTED = "selected"; private transient Tab selectedTab; /** * The valid orientations of {@link Tabs} instances. */ public enum Orientation { HORIZONTAL, VERTICAL } /** * Constructs an empty new object with {@link Orientation#HORIZONTAL * HORIZONTAL} orientation. */ public Tabs() { setSelectedIndex(-1); getElement().addPropertyChangeListener(SELECTED, event -> updateSelectedTab(event.isUserOriginated())); } /** * Constructs a new object enclosing the given tabs, with * {@link Orientation#HORIZONTAL HORIZONTAL} orientation. * * @param tabs * the tabs to enclose */ public Tabs(Tab... tabs) { this(); add(tabs); } /** * Adds the given tabs to the component. * * @param tabs * the tabs to enclose */ public void add(Tab... tabs) { add((Component[]) tabs); } @Override public void add(Component... components) { boolean wasEmpty = getComponentCount() == 0; HasOrderedComponents.super.add(components); if (wasEmpty) { assert getSelectedIndex() == -1; setSelectedIndex(0); } else { updateSelectedTab(false); } } /** * {@inheritDoc} *

* Removing components before the selected tab will decrease the * {@link #getSelectedIndex() selected index} to avoid changing the selected * tab. Removing the selected tab will select the next available tab. */ @Override public void remove(Component... components) { int lowerIndices = (int) Stream.of(components).map(this::indexOf) .filter(index -> index >= 0 && index < getSelectedIndex()) .count(); HasOrderedComponents.super.remove(components); // Prevents changing the selected tab int newSelectedIndex = getSelectedIndex() - lowerIndices; // In case the last tab was removed if (newSelectedIndex > 0 && newSelectedIndex >= getComponentCount()) { newSelectedIndex = getComponentCount() - 1; } if (getComponentCount() == 0) { newSelectedIndex = -1; } if (newSelectedIndex != getSelectedIndex()) { setSelectedIndex(newSelectedIndex); } else { updateSelectedTab(false); } } /** * {@inheritDoc} *

* This will reset the {@link #getSelectedIndex() selected index} to zero. */ @Override public void removeAll() { HasOrderedComponents.super.removeAll(); if (getSelectedIndex() > -1) { setSelectedIndex(-1); } else { updateSelectedTab(false); } } /** * {@inheritDoc} *

* Replacing the currently selected tab will make the new tab selected. */ @Override public void replace(Component oldComponent, Component newComponent) { HasOrderedComponents.super.replace(oldComponent, newComponent); updateSelectedTab(false); } /** * An event to mark that the selected tab has changed. */ public static class SelectedChangeEvent extends ComponentEvent { public SelectedChangeEvent(Tabs source, boolean fromClient) { super(source, fromClient); } } @Override protected void onAttach(AttachEvent attachEvent) { getElement().getNode().runWhenAttached(ui -> ui.beforeClientResponse( this, context -> ui.getPage().executeJavaScript( "$0.addEventListener('items-changed', " + "function(){ this.$server.updateSelectedTab(true); });", getElement()))); } /** * Adds a listener for {@link SelectedChangeEvent}. * * @param listener * the listener to add, not null * @return a handle that can be used for removing the listener */ public Registration addSelectedChangeListener( ComponentEventListener listener) { return addListener(SelectedChangeEvent.class, listener); } /** * Gets the zero-based index of the currently selected tab. * * @return the zero-based index of the selected tab, or -1 if none of the * tabs is selected */ @Synchronize(property = SELECTED, value = "selected-changed") public int getSelectedIndex() { return getElement().getProperty(SELECTED, -1); } /** * Selects a tab based on its zero-based index. * * @param selectedIndex * the zero-based index of the selected tab, -1 to unselect all */ public void setSelectedIndex(int selectedIndex) { getElement().setProperty(SELECTED, selectedIndex); } /** * Gets the currently selected tab. * * @return the selected tab, or {@code null} if none is selected */ public Tab getSelectedTab() { int selectedIndex = getSelectedIndex(); if (selectedIndex < 0) { return null; } Component selectedComponent = getComponentAt(selectedIndex); if (!(selectedComponent instanceof Tab)) { throw new IllegalStateException( "Illegal component inside Tabs: " + selectedComponent); } return (Tab) selectedComponent; } /** * Selects the given tab. * * @param selectedTab * the tab to select, {@code null} to unselect all * @throws IllegalArgumentException * if {@code selectedTab} is not a child of this component */ public void setSelectedTab(Tab selectedTab) { if (selectedTab == null) { setSelectedIndex(-1); return; } int selectedIndex = indexOf(selectedTab); if (selectedIndex < 0) { throw new IllegalArgumentException( "Tab to select must be a child: " + selectedTab); } setSelectedIndex(selectedIndex); } /** * Gets the orientation of this tab sheet. * * @return the orientation */ public Orientation getOrientation() { String orientation = getElement().getProperty("orientation"); if (orientation != null) { return Orientation.valueOf(orientation.toUpperCase(Locale.ROOT)); } return Orientation.HORIZONTAL; } /** * Sets the orientation of this tab sheet. * * @param orientation * the orientation */ public void setOrientation(Orientation orientation) { getElement().setProperty("orientation", orientation.name().toLowerCase()); } /** * Sets the flex grow property of all enclosed tabs. The flex grow property * specifies what amount of the available space inside the layout the * component should take up, proportionally to the other components. *

* For example, if all components have a flex grow property value set to 1, * the remaining space in the layout will be distributed equally to all * components inside the layout. If you set a flex grow property of one * component to 2, that component will take twice the available space as the * other components, and so on. *

* Setting to flex grow property value 0 disables the expansion of the * component. Negative values are not allowed. * * @param flexGrow * the proportion of the available space the enclosed tabs should * take up * @throws IllegalArgumentException * if {@code flexGrow} is negative */ public void setFlexGrowForEnclosedTabs(double flexGrow) { if (flexGrow < 0) { throw new IllegalArgumentException( "Flex grow property must not be negative"); } getChildren().forEach(tab -> ((Tab) tab).setFlexGrow(flexGrow)); } @ClientCallable private void updateSelectedTab(boolean changedFromClient) { if (getSelectedIndex() < -1) { setSelectedIndex(-1); return; } Tab currentlySelected = getSelectedTab(); if (Objects.equals(currentlySelected, selectedTab)) { return; } if (currentlySelected == null || currentlySelected.isEnabled()) { selectedTab = currentlySelected; getChildren().filter(Tab.class::isInstance).map(Tab.class::cast) .forEach(tab -> tab.setSelected(false)); if (selectedTab != null) { selectedTab.setSelected(true); } fireEvent(new SelectedChangeEvent(this, changedFromClient)); } else { updateEnabled(currentlySelected); setSelectedTab(selectedTab); } } private void updateEnabled(Tab tab) { boolean enabled = tab.isEnabled(); Serializable rawValue = tab.getElement().getPropertyRaw("disabled"); if (rawValue instanceof Boolean) { // convert the boolean value to a String to force update the // property value. Otherwise since the provided value is the same as // the current one the update don't do anything. tab.getElement().setProperty("disabled", enabled ? null : Boolean.TRUE.toString()); } else { tab.setEnabled(enabled); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy