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

com.vaadin.flow.component.ironlist.IronList Maven / Gradle / Ivy

There is a newer version: 23.5.5
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.ironlist;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import com.vaadin.flow.component.ClientCallable;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Focusable;
import com.vaadin.flow.component.HasSize;
import com.vaadin.flow.component.HasStyle;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.dependency.NpmPackage;
import com.vaadin.flow.data.binder.HasDataProvider;
import com.vaadin.flow.data.provider.ArrayUpdater;
import com.vaadin.flow.data.provider.ArrayUpdater.Update;
import com.vaadin.flow.data.provider.CompositeDataGenerator;
import com.vaadin.flow.data.provider.DataCommunicator;
import com.vaadin.flow.data.provider.DataProvider;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.data.renderer.Renderer;
import com.vaadin.flow.data.renderer.Rendering;
import com.vaadin.flow.data.renderer.TemplateRenderer;
import com.vaadin.flow.dom.DisabledUpdateMode;
import com.vaadin.flow.dom.Element;
import com.vaadin.flow.function.ValueProvider;
import com.vaadin.flow.internal.JsonSerializer;
import com.vaadin.flow.internal.JsonUtils;
import com.vaadin.flow.server.Command;
import com.vaadin.flow.shared.Registration;

import elemental.json.JsonValue;

/**
 * Component that encapsulates the functionality of the {@code }
 * webcomponent.
 * 

* It supports {@link DataProvider}s to load data asynchronously and * {@link TemplateRenderer}s to render the markup for each item. *

* For this component to work properly, it needs to have a well defined * {@code height}. It can be an absolute height, like {@code 100px}, or a * relative height inside a container with well defined height. *

* For list rendered in grid layout (setting {@link #setGridLayout(boolean)} * with true), the {@code width} of the component also needs to be * well defined. * * * @author Vaadin Ltd. * * @param * the type of the items supported by the list * @see iron-list * webcomponent documentation */ @Tag("iron-list") @NpmPackage(value = "@polymer/iron-list", version = "3.0.2") @JsModule("@polymer/iron-list/iron-list.js") @JsModule("./flow-component-renderer.js") @JsModule("./ironListConnector.js") @JsModule("./ironListStyles.js") public class IronList extends Component implements HasDataProvider, HasStyle, HasSize, Focusable> { private final class UpdateQueue implements Update { private transient List queue = new ArrayList<>(); private UpdateQueue(int size) { enqueue("$connector.updateSize", size); } @Override public void set(int start, List items) { enqueue("$connector.set", start, items.stream().collect(JsonUtils.asArray())); } @Override public void clear(int start, int length) { enqueue("$connector.clear", start, length); } @Override public void commit(int updateId) { getDataCommunicator().confirmUpdate(updateId); queue.forEach(Runnable::run); queue.clear(); } private void enqueue(String name, Serializable... arguments) { queue.add(() -> getElement().callJsFunction(name, arguments)); } } private final ArrayUpdater arrayUpdater = new ArrayUpdater() { @Override public Update startUpdate(int sizeChange) { return new UpdateQueue(sizeChange); } @Override public void initialize() { initConnector(); } }; private final Element template; private Renderer renderer; private String originalTemplate; private boolean rendererChanged; private boolean templateUpdateRegistered; private final CompositeDataGenerator dataGenerator = new CompositeDataGenerator<>(); private Registration dataGeneratorRegistration; private transient T placeholderItem; private final DataCommunicator dataCommunicator = new DataCommunicator<>( dataGenerator, arrayUpdater, data -> getElement().callJsFunction("$connector.updateData", data), getElement().getNode()); /** * Creates an empty list. */ public IronList() { dataGenerator.addDataGenerator( (item, jsonObject) -> renderer.getValueProviders() .forEach((property, provider) -> jsonObject.put( property, JsonSerializer.toJson(provider.apply(item))))); template = new Element("template"); getElement().appendChild(template); setRenderer(String::valueOf); } private void initConnector() { getUI().orElseThrow(() -> new IllegalStateException( "Connector can only be initialized for an attached IronList")) .getPage().executeJs( "window.Vaadin.Flow.ironListConnector.initLazy($0)", getElement()); } @Override public void setDataProvider(DataProvider dataProvider) { Objects.requireNonNull(dataProvider, "The dataProvider cannot be null"); getDataCommunicator().setDataProvider(dataProvider, null); } /** * Returns the data provider of this list. * * @return the data provider of this list, not {@code null} */ public DataProvider getDataProvider() { //NOSONAR return getDataCommunicator().getDataProvider(); } /** * Returns the data communicator of this list. * * @return the data communicator, not {@code null} */ public DataCommunicator getDataCommunicator() { return dataCommunicator; } /** * Sets a renderer for the items in the list, by using a * {@link ValueProvider}. The String returned by the provider is used to * render each item. * * @param valueProvider * a provider for the label string for each item in the list, not * null */ public void setRenderer(ValueProvider valueProvider) { Objects.requireNonNull(valueProvider, "The valueProvider must not be null"); this.setRenderer(TemplateRenderer. of("[[item.label]]") .withProperty("label", valueProvider)); } /** * Sets a renderer for the items in the list, by using a * {@link TemplateRenderer}. The template returned by the renderer is used * to render each item. *

* When set, a same renderer is used for the placeholder item. See * {@link #setPlaceholderItem(Object)} for details. * * @param renderer * a renderer for the items in the list, not null */ public void setRenderer(Renderer renderer) { Objects.requireNonNull(renderer, "The renderer must not be null"); if (dataGeneratorRegistration != null) { dataGeneratorRegistration.remove(); dataGeneratorRegistration = null; } Rendering rendering = renderer.render(getElement(), dataCommunicator.getKeyMapper(), template); if (rendering.getDataGenerator().isPresent()) { dataGeneratorRegistration = dataGenerator .addDataGenerator(rendering.getDataGenerator().get()); } this.renderer = renderer; rendererChanged = true; registerTemplateUpdate(); getDataCommunicator().reset(); } /** * Sets an item to be shown as placeholder in the list while the real data * in being fetched from the server. *

* Setting a placeholder item improves the user experience of the list while * scrolling, since the placeholder uses the same renderer set with * {@link #setRenderer(TemplateRenderer)}, maintaining the same height for * placeholders and actual items. *

* When no placeholder item is set (or when set to null), an * empty placeholder element is created with 100px of width and * 18px of height. *

* Note: when using {@link ComponentTemplateRenderer}s, the component used * for the placeholder is statically stamped in the list. It can not be * modified, nor receives any events. * * @param placeholderItem * the item used as placeholder in the list, while the real data * is being fetched from the server */ public void setPlaceholderItem(T placeholderItem) { this.placeholderItem = placeholderItem; getElement().callJsFunction("$connector.setPlaceholderItem", JsonSerializer.toJson(placeholderItem)); registerTemplateUpdate(); } /** * Gets the placeholder item of this list, or null if none has * been set. * * @return the placeholder item */ public T getPlaceholderItem() { return placeholderItem; } private void registerTemplateUpdate() { if (templateUpdateRegistered) { return; } templateUpdateRegistered = true; /* * The actual registration is done inside another beforeClientResponse * registration to make sure it runs last, after ComponentRenderer and * BasicRenderes have executed their rendering operations, which also * happen beforeClientResponse and might be registered after this. */ runBeforeClientResponse( () -> runBeforeClientResponse(() -> updateTemplateInnerHtml())); } private void runBeforeClientResponse(Command command) { getElement().getNode() .runWhenAttached(ui -> ui.getInternals().getStateTree() .beforeClientResponse(getElement().getNode(), context -> command.execute())); } private void updateTemplateInnerHtml() { templateUpdateRegistered = false; if (rendererChanged) { originalTemplate = template.getProperty("innerHTML"); rendererChanged = false; } String placeholderTemplate; if (placeholderItem == null) { /* * When a placeholderItem is not set, there should be still a * placeholder element with a non 0 size to avoid issues when * scrolling. */ placeholderTemplate = "

"; } else if (renderer instanceof ComponentRenderer) { @SuppressWarnings("unchecked") ComponentRenderer componentRenderer = (ComponentRenderer) renderer; Component component = componentRenderer .createComponent(placeholderItem); component.getElement().setEnabled(isEnabled()); placeholderTemplate = component.getElement().getOuterHTML(); } else { placeholderTemplate = originalTemplate; } /** * The placeholder is used by the client connector to create temporary * elements that are populated on demand (when the user scrolls to that * item). */ template.setProperty("innerHTML", String.format( //@formatter:off "" + "" + "" + "", //@formatter:on placeholderTemplate, originalTemplate)); } /** * Gets whether this list is rendered in a grid layout instead of a linear * list. * * @return true if the list renders itself as a grid, * false otherwise */ public boolean isGridLayout() { return getElement().getProperty("grid", false); } /** * Sets this list to be rendered as a grid. Note that for the grid layout to * work properly, the component needs to have a well defined {@code width} * and {@code height}. * * @param gridLayout * true to make the list renders itself as a grid, * false to make it render as a linear list */ public void setGridLayout(boolean gridLayout) { getElement().setProperty("grid", gridLayout); } @Override public void onEnabledStateChanged(boolean enabled) { super.onEnabledStateChanged(enabled); /* * Rendered component's enabled state needs to be updated via * rerendering */ setRenderer(renderer); } @ClientCallable(DisabledUpdateMode.ALWAYS) private void setRequestedRange(int start, int length) { getDataCommunicator().setRequestedRange(start, length); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy