Package com.google.gwt.user.cellview.client

Source Code of com.google.gwt.user.cellview.client.DataGrid$Style

/*
* Copyright 2011 Google Inc.
*
* 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.google.gwt.user.cellview.client;

import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.dom.client.Style.Overflow;
import com.google.gwt.dom.client.Style.TableLayout;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.dom.client.TableColElement;
import com.google.gwt.dom.client.TableElement;
import com.google.gwt.dom.client.TableSectionElement;
import com.google.gwt.event.dom.client.ScrollEvent;
import com.google.gwt.event.dom.client.ScrollHandler;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.resources.client.CssResource.ImportedWithPrefix;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.resources.client.ImageResource.ImageOptions;
import com.google.gwt.user.cellview.client.LoadingStateChangeEvent.LoadingState;
import com.google.gwt.user.client.ui.CustomScrollPanel;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HeaderPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.RequiresResize;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.ProvidesKey;

/**
* A tabular view with a fixed header and footer section and a scrollable data
* section in the middle. This widget supports paging and columns.
*
* <p>
* <h3>Columns</h3> The {@link Column} class defines the
* {@link com.google.gwt.cell.client.Cell} used to render a column. Implement
* {@link Column#getValue(Object)} to retrieve the field value from the row
* object that will be rendered in the {@link com.google.gwt.cell.client.Cell}.
* </p>
*
* <p>
* <h3>Headers and Footers</h3> A {@link Header} can be placed at the top
* (header) or bottom (footer) of the {@link DataGrid}. You can specify a header
* as text using {@link #addColumn(Column, String)}, or you can create a custom
* {@link Header} that can change with the value of the cells, such as a column
* total. The {@link Header} will be rendered every time the row data changes or
* the table is redrawn. If you pass the same header instance (==) into adjacent
* columns, the header will span the columns.
* </p>
*
* <p>
* <h3>Examples</h3>
* <dl>
* <dt>Trivial example</dt>
* <dd>{@example com.google.gwt.examples.cellview.CellTableExample}</dd>
* <dt>FieldUpdater example</dt>
* <dd>{@example com.google.gwt.examples.cellview.CellTableFieldUpdaterExample}</dd>
* <dt>Key provider example</dt>
* <dd>{@example com.google.gwt.examples.view.KeyProviderExample}</dd>
* </dl>
* </p>
*
* @param <T> the data type of each row
*/
public class DataGrid<T> extends AbstractCellTable<T> implements RequiresResize {

  /**
   * A ClientBundle that provides images for this widget.
   */
  public interface Resources extends ClientBundle {
    /**
     * The loading indicator used while the table is waiting for data.
     */
    @Source("cellTableLoading.gif")
    @ImageOptions(flipRtl = true)
    ImageResource dataGridLoading();

    /**
     * Icon used when a column is sorted in ascending order.
     */
    @Source("sortAscending.png")
    @ImageOptions(flipRtl = true)
    ImageResource dataGridSortAscending();

    /**
     * Icon used when a column is sorted in descending order.
     */
    @Source("sortDescending.png")
    @ImageOptions(flipRtl = true)
    ImageResource dataGridSortDescending();

    /**
     * The styles used in this widget.
     */
    @Source(Style.DEFAULT_CSS)
    Style dataGridStyle();
  }

  /**
   * Styles used by this widget.
   */
  @ImportedWithPrefix("gwt-CellTable")
  public interface Style extends CssResource {
    /**
     * The path to the default CSS styles used by this resource.
     */
    String DEFAULT_CSS = "com/google/gwt/user/cellview/client/DataGrid.css";

    /**
     * Applied to every cell.
     */
    String dataGridCell();

    /**
     * Applied to even rows.
     */
    String dataGridEvenRow();

    /**
     * Applied to cells in even rows.
     */
    String dataGridEvenRowCell();

    /**
     * Applied to the first column.
     */
    String dataGridFirstColumn();

    /**
     * Applied to the first column footers.
     */
    String dataGridFirstColumnFooter();

    /**
     * Applied to the first column headers.
     */
    String dataGridFirstColumnHeader();

    /**
     * Applied to footers cells.
     */
    String dataGridFooter();

    /**
     * Applied to headers cells.
     */
    String dataGridHeader();

    /**
     * Applied to the hovered row.
     */
    String dataGridHoveredRow();

    /**
     * Applied to the cells in the hovered row.
     */
    String dataGridHoveredRowCell();

    /**
     * Applied to the keyboard selected cell.
     */
    String dataGridKeyboardSelectedCell();

    /**
     * Applied to the keyboard selected row.
     */
    String dataGridKeyboardSelectedRow();

    /**
     * Applied to the cells in the keyboard selected row.
     */
    String dataGridKeyboardSelectedRowCell();

    /**
     * Applied to the last column.
     */
    String dataGridLastColumn();

    /**
     * Applied to the last column footers.
     */
    String dataGridLastColumnFooter();

    /**
     * Applied to the last column headers.
     */
    String dataGridLastColumnHeader();

    /**
     * Applied to odd rows.
     */
    String dataGridOddRow();

    /**
     * Applied to cells in odd rows.
     */
    String dataGridOddRowCell();

    /**
     * Applied to selected rows.
     */
    String dataGridSelectedRow();

    /**
     * Applied to cells in selected rows.
     */
    String dataGridSelectedRowCell();

    /**
     * Applied to header cells that are sortable.
     */
    String dataGridSortableHeader();

    /**
     * Applied to header cells that are sorted in ascending order.
     */
    String dataGridSortedHeaderAscending();

    /**
     * Applied to header cells that are sorted in descending order.
     */
    String dataGridSortedHeaderDescending();

    /**
     * Applied to the table.
     */
    String dataGridWidget();
  }

  /**
   * A simple widget wrapper around a table element.
   */
  static class TableWidget extends Widget {
    private final TableColElement colgroup;
    private TableSectionElement section;
    private final TableElement tableElem;

    public TableWidget() {
      // Setup the table.
      tableElem = Document.get().createTableElement();
      tableElem.setCellSpacing(0);
      tableElem.getStyle().setTableLayout(TableLayout.FIXED);
      tableElem.getStyle().setWidth(100.0, Unit.PCT);
      setElement(tableElem);

      // Add the colgroup.
      colgroup = Document.get().createColGroupElement();
      tableElem.appendChild(colgroup);
    }

    public void addColumnStyleName(int index, String styleName) {
      ensureTableColElement(index).addClassName(styleName);
    }

    /**
     * Get the {@link TableColElement} at the specified index, creating it if
     * necessary.
     *
     * @param index the column index
     * @return the {@link TableColElement}
     */
    public TableColElement ensureTableColElement(int index) {
      // Ensure that we have enough columns.
      for (int i = colgroup.getChildCount(); i <= index; i++) {
        colgroup.appendChild(Document.get().createColElement());
      }
      return colgroup.getChild(index).cast();
    }

    public void removeColumnStyleName(int index, String styleName) {
      if (index >= colgroup.getChildCount()) {
        return;
      }
      ensureTableColElement(index).removeClassName(styleName);
    }

    /**
     * Hide columns that aren't used in the table.
     *
     * @param start the first unused column index
     */
    void hideUnusedColumns(int start) {
      /*
       * Set the width to zero for all col elements that appear after the last
       * column. Clearing the width would cause it to take up the remaining
       * width in a fixed layout table.
       */
      int colCount = colgroup.getChildCount();
      for (int i = start; i < colCount; i++) {
        setColumnWidth(i, "0px");
      }
    }

    void setColumnWidth(int column, String width) {
      if (width == null) {
        ensureTableColElement(column).getStyle().clearWidth();
      } else {
        ensureTableColElement(column).getStyle().setProperty("width", width);
      }
    }
  }

  /**
   * Adapter class to convert {@link Resources} to
   * {@link AbstractCellTable.Resources}.
   */
  private static class ResourcesAdapter implements AbstractCellTable.Resources {

    private final DataGrid.Resources resources;
    private final StyleAdapter style;

    public ResourcesAdapter(DataGrid.Resources resources) {
      this.resources = resources;
      this.style = new StyleAdapter(resources.dataGridStyle());
    }

    @Override
    public ImageResource sortAscending() {
      return resources.dataGridSortAscending();
    }

    @Override
    public ImageResource sortDescending() {
      return resources.dataGridSortDescending();
    }

    @Override
    public AbstractCellTable.Style style() {
      return style;
    }
  }

  /**
   * Adapter class to convert {@link Style} to {@link AbstractCellTable.Style}.
   */
  private static class StyleAdapter implements AbstractCellTable.Style {
    private final DataGrid.Style style;

    public StyleAdapter(DataGrid.Style style) {
      this.style = style;
    }

    @Override
    public String cell() {
      return style.dataGridCell();
    }

    @Override
    public String evenRow() {
      return style.dataGridEvenRow();
    }

    @Override
    public String evenRowCell() {
      return style.dataGridEvenRowCell();
    }

    @Override
    public String firstColumn() {
      return style.dataGridFirstColumn();
    }

    @Override
    public String firstColumnFooter() {
      return style.dataGridFirstColumnFooter();
    }

    @Override
    public String firstColumnHeader() {
      return style.dataGridFirstColumnHeader();
    }

    @Override
    public String footer() {
      return style.dataGridFooter();
    }

    @Override
    public String header() {
      return style.dataGridHeader();
    }

    @Override
    public String hoveredRow() {
      return style.dataGridHoveredRow();
    }

    @Override
    public String hoveredRowCell() {
      return style.dataGridHoveredRowCell();
    }

    @Override
    public String keyboardSelectedCell() {
      return style.dataGridKeyboardSelectedCell();
    }

    @Override
    public String keyboardSelectedRow() {
      return style.dataGridKeyboardSelectedRow();
    }

    @Override
    public String keyboardSelectedRowCell() {
      return style.dataGridKeyboardSelectedRowCell();
    }

    @Override
    public String lastColumn() {
      return style.dataGridLastColumn();
    }

    @Override
    public String lastColumnFooter() {
      return style.dataGridLastColumnFooter();
    }

    @Override
    public String lastColumnHeader() {
      return style.dataGridLastColumnHeader();
    }

    @Override
    public String oddRow() {
      return style.dataGridOddRow();
    }

    @Override
    public String oddRowCell() {
      return style.dataGridOddRowCell();
    }

    @Override
    public String selectedRow() {
      return style.dataGridSelectedRow();
    }

    @Override
    public String selectedRowCell() {
      return style.dataGridSelectedRowCell();
    }

    @Override
    public String sortableHeader() {
      return style.dataGridSortableHeader();
    }

    @Override
    public String sortedHeaderAscending() {
      return style.dataGridSortedHeaderAscending();
    }

    @Override
    public String sortedHeaderDescending() {
      return style.dataGridSortedHeaderDescending();
    }

    @Override
    public String widget() {
      return style.dataGridWidget();
    }
  }

  private static final int DEFAULT_PAGESIZE = 50;
  private static Resources DEFAULT_RESOURCES;

  /**
   * Create the default loading indicator using the loading image in the
   * specified {@link Resources}.
   *
   * @param resources the resources containing the loading image
   * @return a widget loading indicator
   */
  private static Widget createDefaultLoadingIndicator(Resources resources) {
    ImageResource loadingImg = resources.dataGridLoading();
    if (loadingImg == null) {
      return null;
    }
    Image image = new Image(loadingImg);
    image.getElement().getStyle().setMarginTop(30.0, Unit.PX);
    return image;
  }

  private static Resources getDefaultResources() {
    if (DEFAULT_RESOURCES == null) {
      DEFAULT_RESOURCES = GWT.create(Resources.class);
    }
    return DEFAULT_RESOURCES;
  }

  final TableWidget tableData;
  final TableWidget tableFooter;
  final TableWidget tableHeader;
  private final FlexTable emptyTableWidgetContainer;
  private final HeaderPanel headerPanel;
  private final FlexTable loadingIndicatorContainer;
  private final Style style;
  private final Element tableDataContainer;
  private final ScrollPanel tableDataScroller;
  private final SimplePanel tableFooterContainer;
  private final Element tableFooterScroller;
  private final SimplePanel tableHeaderContainer;
  private final Element tableHeaderScroller;

  /**
   * Constructs a table with a default page size of 50.
   */
  public DataGrid() {
    this(DEFAULT_PAGESIZE);
  }

  /**
   * Constructs a table with the given page size.
   *
   * @param pageSize the page size
   */
  public DataGrid(final int pageSize) {
    this(pageSize, getDefaultResources());
  }

  /**
   * Constructs a table with the given page size and the given
   * {@link ProvidesKey key provider}.
   *
   * @param pageSize the page size
   * @param keyProvider an instance of ProvidesKey<T>, or null if the record
   *          object should act as its own key
   */
  public DataGrid(int pageSize, ProvidesKey<T> keyProvider) {
    this(pageSize, getDefaultResources(), keyProvider);
  }

  /**
   * Constructs a table with the given page size with the specified
   * {@link Resources}.
   *
   * @param pageSize the page size
   * @param resources the resources to use for this widget
   */
  public DataGrid(int pageSize, Resources resources) {
    this(pageSize, resources, null);
  }

  /**
   * Constructs a table with the given page size, the specified
   * {@link Resources}, and the given key provider.
   *
   * @param pageSize the page size
   * @param resources the resources to use for this widget
   * @param keyProvider an instance of ProvidesKey<T>, or null if the record
   *          object should act as its own key
   */
  public DataGrid(int pageSize, Resources resources, ProvidesKey<T> keyProvider) {
    this(pageSize, resources, keyProvider, createDefaultLoadingIndicator(resources));
  }

  /**
   * Constructs a table with the given page size, the specified
   * {@link Resources}, and the given key provider.
   *
   * @param pageSize the page size
   * @param resources the resources to use for this widget
   * @param keyProvider an instance of ProvidesKey<T>, or null if the record
   *          object should act as its own key
   * @param loadingIndicator the widget to use as a loading indicator, or null
   *          to disable
   */
  public DataGrid(int pageSize, Resources resources, ProvidesKey<T> keyProvider,
      Widget loadingIndicator) {
    super(new HeaderPanel(), pageSize, new ResourcesAdapter(resources), keyProvider);
    headerPanel = (HeaderPanel) getWidget();

    // Inject the stylesheet.
    this.style = resources.dataGridStyle();
    this.style.ensureInjected();

    // Create the header and footer widgets..
    tableHeader = new TableWidget();
    tableHeader.section = tableHeader.tableElem.createTHead();
    tableFooter = new TableWidget();
    tableFooter.section = tableFooter.tableElem.createTFoot();

    /*
     * Wrap the header and footer widgets in a div because we cannot set the
     * min-width of a table element. We set the width/min-width of the div
     * container instead.
     */
    tableHeaderContainer = new SimplePanel(tableHeader);
    tableFooterContainer = new SimplePanel(tableFooter);

    /*
     * Get the element that wraps the container so we can adjust its scroll
     * position.
     */
    headerPanel.setHeaderWidget(tableHeaderContainer);
    tableHeaderScroller = tableHeaderContainer.getElement().getParentElement();
    headerPanel.setFooterWidget(tableFooterContainer);
    tableFooterScroller = tableFooterContainer.getElement().getParentElement();

    /*
     * Set overflow to hidden on the scrollable elements so we can change the
     * scrollLeft position.
     */
    tableHeaderScroller.getStyle().setOverflow(Overflow.HIDDEN);
    tableFooterScroller.getStyle().setOverflow(Overflow.HIDDEN);

    // Create the body.
    tableData = new TableWidget();
    if (tableData.tableElem.getTBodies().getLength() > 0) {
      tableData.section = tableData.tableElem.getTBodies().getItem(0);
    } else {
      tableData.section = Document.get().createTBodyElement();
      tableData.tableElem.appendChild(tableData.section);
    }
    tableDataScroller = new CustomScrollPanel(tableData);
    tableDataScroller.setHeight("100%");
    headerPanel.setContentWidget(tableDataScroller);
    tableDataContainer = tableData.getElement().getParentElement();

    /*
     * CustomScrollPanel applies the inline block style to the container
     * element, but we want the container to fill the available width.
     */
    tableDataContainer.getStyle().setDisplay(Display.BLOCK);

    /*
     * Create the containers for the empty table message and loading indicator.
     * The containers are centered tables that contain one cell, which aligns
     * the widget in the center of the panel.
     */
    emptyTableWidgetContainer = new FlexTable();
    emptyTableWidgetContainer.getElement().setAttribute("align", "center");
    loadingIndicatorContainer = new FlexTable();
    loadingIndicatorContainer.getElement().setAttribute("align", "center");

    // Set the loading indicator.
    setLoadingIndicator(loadingIndicator); // Can be null.

    // Synchronize the scroll positions of the three tables.
    tableDataScroller.addScrollHandler(new ScrollHandler() {
      @Override
      public void onScroll(ScrollEvent event) {
        int scrollLeft = tableDataScroller.getHorizontalScrollPosition();
        tableHeaderScroller.setScrollLeft(scrollLeft);
        tableFooterScroller.setScrollLeft(scrollLeft);
      }
    });
  }

  /**
   * Constructs a table with a default page size of 50, and the given
   * {@link ProvidesKey key provider}.
   *
   * @param keyProvider an instance of ProvidesKey<T>, or null if the record
   *          object should act as its own key
   */
  public DataGrid(ProvidesKey<T> keyProvider) {
    this(DEFAULT_PAGESIZE, keyProvider);
  }

  @Override
  public void addColumnStyleName(int index, String styleName) {
    tableHeader.addColumnStyleName(index, styleName);
    tableFooter.addColumnStyleName(index, styleName);
    tableData.addColumnStyleName(index, styleName);
  }

  /**
   * Clear the width of the tables in this widget, which causes them to fill the
   * available width.
   *
   * <p>
   * The table width is not the same as the width of this widget. If the tables
   * are narrower than this widget, there will be a gap between the table and
   * the edge of the widget. If the tables are wider than this widget, a
   * horizontal scrollbar will appear so the user can scroll horizontally.
   * </p>
   *
   * @see #setMinimumTableWidth(double, Unit)
   * @see #setTableWidth(double, Unit)
   */
  public void clearTableWidth() {
    tableHeaderContainer.getElement().getStyle().clearWidth();
    tableFooterContainer.getElement().getStyle().clearWidth();
    tableDataContainer.getStyle().clearWidth();
  }

  @Override
  public void onResize() {
    headerPanel.onResize();
  }

  @Override
  public void removeColumnStyleName(int index, String styleName) {
    tableHeader.removeColumnStyleName(index, styleName);
    tableFooter.removeColumnStyleName(index, styleName);
    tableData.removeColumnStyleName(index, styleName);
  }

  @Override
  public void setEmptyTableWidget(Widget widget) {
    emptyTableWidgetContainer.setWidget(0, 0, widget);
    super.setEmptyTableWidget(widget);
  }

  @Override
  public void setLoadingIndicator(Widget widget) {
    loadingIndicatorContainer.setWidget(0, 0, widget);
    super.setLoadingIndicator(widget);
  }

  /**
   * Set the minimum width of the tables in this widget. If the widget become
   * narrower than the minimum width, a horizontal scrollbar will appear so the
   * user can scroll horizontally.
   *
   * <p>
   * Note that this method is not supported in IE6 and earlier versions of IE.
   * </p>
   *
   * @param value the width
   * @param unit the unit of the width
   * @see #setTableWidth(double, Unit)
   */
  public void setMinimumTableWidth(double value, Unit unit) {
    /*
     * The min-width style attribute doesn't apply to tables, so we set the
     * min-width of the element that contains the table instead. The table width
     * is fixed at 100%.
     */
    tableHeaderContainer.getElement().getStyle().setProperty("minWidth", value, unit);
    tableFooterContainer.getElement().getStyle().setProperty("minWidth", value, unit);
    tableDataContainer.getStyle().setProperty("minWidth", value, unit);
  }

  /**
   * Set the width of the tables in this widget. By default, the width is not
   * set and the tables take the available width.
   *
   * <p>
   * The table width is not the same as the width of this widget. If the tables
   * are narrower than this widget, there will be a gap between the table and
   * the edge of the widget. If the tables are wider than this widget, a
   * horizontal scrollbar will appear so the user can scroll horizontally.
   * </p>
   *
   * <p>
   * If your table has many columns and you want to ensure that the columns are
   * not truncated, you probably want to use
   * {@link #setMinimumTableWidth(double, Unit)} instead. That will ensure that
   * the table is wide enough, but it will still allow the table to expand to
   * 100% if the user has a wide screen.
   * </p>
   *
   * <p>
   * Note that setting the width in percentages will not work on older versions
   * of IE because it does not account for scrollbars when calculating the
   * width.
   * </p>
   *
   * @param value the width
   * @param unit the unit of the width
   * @see #setMinimumTableWidth(double, Unit)
   */
  public void setTableWidth(double value, Unit unit) {
    /*
     * The min-width style attribute doesn't apply to tables, so we set the
     * min-width of the element that contains the table instead. For
     * consistency, we set the width of the container as well.
     */
    tableHeaderContainer.getElement().getStyle().setWidth(value, unit);
    tableFooterContainer.getElement().getStyle().setWidth(value, unit);
    tableDataContainer.getStyle().setWidth(value, unit);
  }

  @Override
  protected void doSetColumnWidth(int column, String width) {
    if (width == null) {
      tableData.ensureTableColElement(column).getStyle().clearWidth();
      tableHeader.ensureTableColElement(column).getStyle().clearWidth();
      tableFooter.ensureTableColElement(column).getStyle().clearWidth();
    } else {
      tableData.ensureTableColElement(column).getStyle().setProperty("width", width);
      tableHeader.ensureTableColElement(column).getStyle().setProperty("width", width);
      tableFooter.ensureTableColElement(column).getStyle().setProperty("width", width);
    }
  }

  @Override
  protected void doSetHeaderVisible(boolean isFooter, boolean isVisible) {
    if (isFooter) {
      headerPanel.setFooterWidget(isVisible ? tableFooterContainer : null);
    } else {
      headerPanel.setHeaderWidget(isVisible ? tableHeaderContainer : null);
    }
  }

  @Override
  protected TableSectionElement getTableBodyElement() {
    return tableData.section;
  }

  @Override
  protected TableSectionElement getTableFootElement() {
    return tableFooter.section;
  }

  @Override
  protected TableSectionElement getTableHeadElement() {
    return tableHeader.section;
  }

  /**
   * Called when the loading state changes.
   *
   * @param state the new loading state
   */
  @Override
  protected void onLoadingStateChanged(LoadingState state) {
    Widget message = tableData;
    if (state == LoadingState.LOADING) {
      // Loading indicator.
      message = loadingIndicatorContainer;
    } else if (state == LoadingState.LOADED && getPresenter().isEmpty()) {
      // Empty table.
      message = emptyTableWidgetContainer;
    }

    // Switch out the message to display.
    tableDataScroller.setWidget(message);

    // Fire an event.
    super.onLoadingStateChanged(state);
  }

  @Override
  protected void refreshColumnWidths() {
    super.refreshColumnWidths();

    // Hide unused col elements in the colgroup.
    int columnCount = getColumnCount();
    tableHeader.hideUnusedColumns(columnCount);
    tableData.hideUnusedColumns(columnCount);
    tableFooter.hideUnusedColumns(columnCount);
  }
}
TOP

Related Classes of com.google.gwt.user.cellview.client.DataGrid$Style

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.
eate', 'UA-20639858-1', 'auto'); ga('send', 'pageview');