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

com.vaadin.client.ui.combobox.ComboBoxConnector Maven / Gradle / Ivy

Go to download

Vaadin is a web application framework for Rich Internet Applications (RIA). Vaadin enables easy development and maintenance of fast and secure rich web applications with a stunning look and feel and a wide browser support. It features a server-side architecture with the majority of the logic running on the server. Ajax technology is used at the browser-side to ensure a rich and interactive user experience.

There is a newer version: 8.27.1
Show newest version
/*
 * Copyright 2000-2016 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.client.ui.combobox;

import java.util.List;
import java.util.Objects;
import java.util.logging.Logger;

import com.vaadin.client.Profiler;
import com.vaadin.client.annotations.OnStateChange;
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.connectors.AbstractListingConnector;
import com.vaadin.client.connectors.data.HasDataSource;
import com.vaadin.client.data.DataChangeHandler;
import com.vaadin.client.data.DataSource;
import com.vaadin.client.ui.HasErrorIndicator;
import com.vaadin.client.ui.HasRequiredIndicator;
import com.vaadin.client.ui.SimpleManagedLayout;
import com.vaadin.client.ui.VComboBox;
import com.vaadin.client.ui.VComboBox.ComboBoxSuggestion;
import com.vaadin.client.ui.VComboBox.DataReceivedHandler;
import com.vaadin.shared.EventId;
import com.vaadin.shared.Registration;
import com.vaadin.shared.communication.FieldRpc.FocusAndBlurServerRpc;
import com.vaadin.shared.data.DataCommunicatorConstants;
import com.vaadin.shared.data.selection.SelectionServerRpc;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.combobox.ComboBoxConstants;
import com.vaadin.shared.ui.combobox.ComboBoxServerRpc;
import com.vaadin.shared.ui.combobox.ComboBoxState;
import com.vaadin.ui.ComboBox;

import elemental.json.JsonObject;

@Connect(ComboBox.class)
public class ComboBoxConnector extends AbstractListingConnector
        implements HasRequiredIndicator, HasDataSource, SimpleManagedLayout,
        HasErrorIndicator {

    private ComboBoxServerRpc rpc = getRpcProxy(ComboBoxServerRpc.class);
    private SelectionServerRpc selectionRpc = getRpcProxy(
            SelectionServerRpc.class);

    private FocusAndBlurServerRpc focusAndBlurRpc = getRpcProxy(
            FocusAndBlurServerRpc.class);

    private Registration dataChangeHandlerRegistration;

    @Override
    protected void init() {
        super.init();
        getWidget().connector = this;
    }

    @Override
    public void onStateChanged(StateChangeEvent stateChangeEvent) {
        super.onStateChanged(stateChangeEvent);

        Profiler.enter("ComboBoxConnector.onStateChanged update content");

        getWidget().readonly = isReadOnly();
        getWidget().updateReadOnly();

        // not a FocusWidget -> needs own tabindex handling
        getWidget().tb.setTabIndex(getState().tabIndex);

        getWidget().suggestionPopup.updateStyleNames(getState());

        // TODO if the pop up is opened, the actual item should be removed from
        // the popup (?)
        getWidget().nullSelectionAllowed = getState().emptySelectionAllowed;
        // TODO having this true would mean that the empty selection item comes
        // from the data source so none needs to be added - currently
        // unsupported
        getWidget().nullSelectItem = false;

        // make sure the input prompt is updated
        getWidget().updatePlaceholder();

        getDataReceivedHandler().serverReplyHandled();

        // all updates except options have been done
        getWidget().initDone = true;

        Profiler.leave("ComboBoxConnector.onStateChanged update content");
    }

    @OnStateChange("emptySelectionCaption")
    private void onEmptySelectionCaptionChange() {
        List suggestions = getWidget().currentSuggestions;
        if (!suggestions.isEmpty() && isFirstPage()) {
            suggestions.remove(0);
            addEmptySelectionItem();
        }
    }

    @OnStateChange({ "selectedItemKey", "selectedItemCaption", "selectedItemIcon" })
    private void onSelectionChange() {
        getDataReceivedHandler().updateSelectionFromServer(
                getState().selectedItemKey, getState().selectedItemCaption,
                getState().selectedItemIcon);
    }

    @Override
    public VComboBox getWidget() {
        return (VComboBox) super.getWidget();
    }

    private DataReceivedHandler getDataReceivedHandler() {
        return getWidget().getDataReceivedHandler();
    }

    @Override
    public ComboBoxState getState() {
        return (ComboBoxState) super.getState();
    }

    @Override
    public void layout() {
        VComboBox widget = getWidget();
        if (widget.initDone) {
            widget.updateRootWidth();
        }
    }

    @Override
    public void setWidgetEnabled(boolean widgetEnabled) {
        super.setWidgetEnabled(widgetEnabled);
        getWidget().enabled = widgetEnabled;
        getWidget().tb.setEnabled(widgetEnabled);
    }

    /*
     * These methods exist to move communications out of VComboBox, and may be
     * refactored/removed in the future
     */

    /**
     * Send a message about a newly created item to the server.
     *
     * This method is for internal use only and may be removed in future
     * versions.
     *
     * @since 8.0
     * @param itemValue
     *            user entered string value for the new item
     */
    public void sendNewItem(String itemValue) {
        rpc.createNewItem(itemValue);
        getDataReceivedHandler().clearPendingNavigation();
    }

    /**
     * Send a message to the server set the current filter.
     *
     * This method is for internal use only and may be removed in future
     * versions.
     *
     * @since 8.0
     * @param filter
     *            the current filter string
     */
    protected void setFilter(String filter) {
        if (!Objects.equals(filter, getWidget().lastFilter)) {
            getDataReceivedHandler().clearPendingNavigation();

            rpc.setFilter(filter);
        }
    }

    /**
     * Send a message to the server to request a page of items with the current
     * filter.
     *
     * This method is for internal use only and may be removed in future
     * versions.
     *
     * @since 8.0
     * @param page
     *            the page number to get or -1 to let the server/connector
     *            decide based on current selection (possibly loading more data
     *            from the server)
     * @param filter
     *            the filter to apply, never {@code null}
     */
    public void requestPage(int page, String filter) {
        setFilter(filter);

        if (page < 0) {
            if (getState().scrollToSelectedItem) {
                // TODO this should be optimized not to try to fetch everything
                getDataSource().ensureAvailability(0, getDataSource().size());
                return;
            } else {
                page = 0;
            }
        }
        int adjustment = getWidget().nullSelectionAllowed && "".equals(filter)
                ? 1 : 0;
        int startIndex = Math.max(0,
                page * getWidget().pageLength - adjustment);
        int pageLength = getWidget().pageLength > 0 ? getWidget().pageLength
                : getDataSource().size();
        getDataSource().ensureAvailability(startIndex, pageLength);
    }

    /**
     * Send a message to the server updating the current selection.
     *
     * This method is for internal use only and may be removed in future
     * versions.
     *
     * @since 8.0
     * @param selectionKey
     *            the current selected item key
     */
    public void sendSelection(String selectionKey) {
        // map also the special empty string option key (from data change
        // handler below) to null
        selectionRpc.select("".equals(selectionKey) ? null : selectionKey);
        getDataReceivedHandler().clearPendingNavigation();
    }

    /**
     * Notify the server that the combo box received focus.
     *
     * For timing reasons, ConnectorFocusAndBlurHandler is not used at the
     * moment.
     *
     * This method is for internal use only and may be removed in future
     * versions.
     *
     * @since 8.0
     */
    public void sendFocusEvent() {
        boolean registeredListeners = hasEventListener(EventId.FOCUS);
        if (registeredListeners) {
            focusAndBlurRpc.focus();
            getDataReceivedHandler().clearPendingNavigation();
        }
    }

    /**
     * Notify the server that the combo box lost focus.
     *
     * For timing reasons, ConnectorFocusAndBlurHandler is not used at the
     * moment.
     *
     * This method is for internal use only and may be removed in future
     * versions.
     *
     * @since 8.0
     */
    public void sendBlurEvent() {
        boolean registeredListeners = hasEventListener(EventId.BLUR);
        if (registeredListeners) {
            focusAndBlurRpc.blur();
            getDataReceivedHandler().clearPendingNavigation();
        }
    }

    @Override
    public void setDataSource(DataSource dataSource) {
        super.setDataSource(dataSource);
        dataChangeHandlerRegistration = dataSource
                .addDataChangeHandler(new PagedDataChangeHandler(dataSource));
    }

    @Override
    public void onUnregister() {
        super.onUnregister();
        dataChangeHandlerRegistration.remove();
    }

    @Override
    public boolean isRequiredIndicatorVisible() {
        return getState().required && !isReadOnly();
    }

    private void refreshData() {
        updateCurrentPage();

        int start = getWidget().currentPage * getWidget().pageLength;
        int end = getWidget().pageLength > 0 ? start + getWidget().pageLength
                : getDataSource().size();

        getWidget().currentSuggestions.clear();

        if (getWidget().getNullSelectionItemShouldBeVisible()) {
            // add special null selection item...
            if (isFirstPage()) {
                addEmptySelectionItem();
            } else {
                // ...or leave space for it
                start = start - 1;
            }
            // in either case, the last item to show is
            // shifted by one, unless no paging is used
            if (getState().pageLength != 0) {
                end = end - 1;
            }
        }

        updateSuggestions(start, end);
        getWidget().setTotalSuggestions(getDataSource().size());

        getDataReceivedHandler().dataReceived();
    }

    private void updateSuggestions(int start, int end) {
        for (int i = start; i < end; ++i) {
            JsonObject row = getDataSource().getRow(i);
            if (row != null) {
                String key = getRowKey(row);
                String caption = row.getString(DataCommunicatorConstants.NAME);
                String style = row.getString(ComboBoxConstants.STYLE);
                String untranslatedIconUri = row
                        .getString(ComboBoxConstants.ICON);
                ComboBoxSuggestion suggestion = getWidget().new ComboBoxSuggestion(
                        key, caption, style, untranslatedIconUri);
                getWidget().currentSuggestions.add(suggestion);
            } else {
                // there is not enough options to fill the page
                return;
            }
        }
    }

    private boolean isFirstPage() {
        return getWidget().currentPage == 0;
    }

    private void addEmptySelectionItem() {
        if (isFirstPage()) {
            getWidget().currentSuggestions.add(0,
                    getWidget().new ComboBoxSuggestion("",
                            getState().emptySelectionCaption, null, null));
        }
    }

    private void updateCurrentPage() {
        // try to find selected item if requested
        if (getState().scrollToSelectedItem && getState().pageLength > 0
                && getWidget().currentPage < 0
                && getWidget().selectedOptionKey != null) {
            // search for the item with the selected key
            getWidget().currentPage = 0;
            for (int i = 0; i < getDataSource().size(); ++i) {
                JsonObject row = getDataSource().getRow(i);
                if (row != null) {
                    String key = getRowKey(row);
                    if (getWidget().selectedOptionKey.equals(key)) {
                        if (getWidget().nullSelectionAllowed) {
                            getWidget().currentPage = (i + 1)
                                    / getState().pageLength;
                        } else {
                            getWidget().currentPage = i / getState().pageLength;
                        }
                        break;
                    }
                }
            }
        } else if (getWidget().currentPage < 0) {
            getWidget().currentPage = 0;
        }
    }

    private static final Logger LOGGER = Logger
            .getLogger(ComboBoxConnector.class.getName());

    private class PagedDataChangeHandler implements DataChangeHandler {

        private final DataSource dataSource;

        public PagedDataChangeHandler(DataSource dataSource) {
            this.dataSource = dataSource;
        }

        @Override
        public void dataUpdated(int firstRowIndex, int numberOfRows) {
            // NOOP since dataAvailable is always triggered afterwards
        }

        @Override
        public void dataRemoved(int firstRowIndex, int numberOfRows) {
            // NOOP since dataAvailable is always triggered afterwards
        }

        @Override
        public void dataAdded(int firstRowIndex, int numberOfRows) {
            // NOOP since dataAvailable is always triggered afterwards
        }

        @Override
        public void dataAvailable(int firstRowIndex, int numberOfRows) {
            refreshData();
        }

        @Override
        public void resetDataAndSize(int estimatedNewDataSize) {
            if (getState().pageLength == 0) {
                if (getWidget().suggestionPopup.isShowing()) {
                    dataSource.ensureAvailability(0, estimatedNewDataSize);
                }
                // else lets just wait till the popup is opened before
                // everything is fetched to it. this could be optimized later on
                // to fetch everything if in-memory data is used.
            } else {
                // reset data: clear any current options, set page to 0
                getWidget().currentPage = 0;
                getWidget().currentSuggestions.clear();
                dataSource.ensureAvailability(0, getState().pageLength);
            }
        }

    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy