Package com.sencha.gxt.widget.core.client

Source Code of com.sencha.gxt.widget.core.client.ListView$ListViewAppearance

/**
* Sencha GXT 3.1.0-beta - Sencha for GWT
* Copyright(c) 2007-2014, Sencha, Inc.
* licensing@sencha.com
*
* http://www.sencha.com/products/gxt/license/
*/
package com.sencha.gxt.widget.core.client;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;

import com.google.gwt.cell.client.Cell;
import com.google.gwt.cell.client.Cell.Context;
import com.google.gwt.cell.client.ValueUpdater;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.EventTarget;
import com.google.gwt.dom.client.Node;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.impl.FocusImpl;
import com.sencha.gxt.core.client.GXT;
import com.sencha.gxt.core.client.GXTLogConfiguration;
import com.sencha.gxt.core.client.ValueProvider;
import com.sencha.gxt.core.client.dom.CompositeElement;
import com.sencha.gxt.core.client.dom.DomHelper;
import com.sencha.gxt.core.client.dom.XDOM;
import com.sencha.gxt.core.client.dom.XElement;
import com.sencha.gxt.core.client.resources.CommonStyles;
import com.sencha.gxt.core.client.util.Util;
import com.sencha.gxt.core.shared.event.GroupingHandlerRegistration;
import com.sencha.gxt.data.shared.ListStore;
import com.sencha.gxt.data.shared.ModelKeyProvider;
import com.sencha.gxt.data.shared.PropertyAccess;
import com.sencha.gxt.data.shared.event.StoreAddEvent;
import com.sencha.gxt.data.shared.event.StoreClearEvent;
import com.sencha.gxt.data.shared.event.StoreDataChangeEvent;
import com.sencha.gxt.data.shared.event.StoreFilterEvent;
import com.sencha.gxt.data.shared.event.StoreHandlers;
import com.sencha.gxt.data.shared.event.StoreRecordChangeEvent;
import com.sencha.gxt.data.shared.event.StoreRemoveEvent;
import com.sencha.gxt.data.shared.event.StoreSortEvent;
import com.sencha.gxt.data.shared.event.StoreUpdateEvent;
import com.sencha.gxt.data.shared.loader.BeforeLoadEvent;
import com.sencha.gxt.data.shared.loader.LoadExceptionEvent;
import com.sencha.gxt.data.shared.loader.LoadExceptionEvent.LoadExceptionHandler;
import com.sencha.gxt.data.shared.loader.Loader;
import com.sencha.gxt.widget.core.client.cell.DefaultHandlerManagerContext;
import com.sencha.gxt.widget.core.client.event.RefreshEvent;
import com.sencha.gxt.widget.core.client.event.RefreshEvent.HasRefreshHandlers;
import com.sencha.gxt.widget.core.client.event.RefreshEvent.RefreshHandler;
import com.sencha.gxt.widget.core.client.event.SelectEvent;
import com.sencha.gxt.widget.core.client.event.XEvent;
import com.sencha.gxt.widget.core.client.tips.QuickTip;

/**
* A {@link ListView} provides support for displaying a list of data. The list
* view gets its data from a {@link ListStore}. Each model in the store is
* rendered as an item in the list view. Any updates to the store are
* automatically pushed to the list view.
* <p/>
* In GXT version 3, {@link ModelKeyProvider}s and {@link ValueProvider}s
* provide the interface between your data model, the list store and the list
* view. This enables a list view to work with data of any object type. The list
* view uses a value provider, passed to the constructor, to get the value to
* display for each model in the list view.
* <p/>
* You can provide your own implementation of these interfaces, or you can use a
* Sencha supplied generator to create them for you automatically. A generator
* runs at compile time to create a Java class that is compiled to JavaScript.
* The Sencha supplied generator can create classes for interfaces that extend
* the {@link PropertyAccess} interface. The generator transparently creates the
* class at compile time and the {@link GWT#create(Class)} method returns an
* instance of that class at run time. The generated class is managed by GWT and
* GXT and you generally do not need to worry about what the class is called,
* where it is located, or other similar details.
* <p/>
* To customize the appearance of the item in the list view, provide a cell
* implementation using {@link ListView#setCell(Cell)}.
* <p/>
* The following code snippet illustrates the creation of a simple list view
* with local data for test purposes. For more practical examples that show how
* to customize list views to display images or use other types of cells, see
* the ListView, Advanced ListView and DateCell ListView examples in the online
* Explorer demo.
* </p>
* <p/>
* <pre>
*   // Generate the key provider and value provider for the Data class
*   DataProperties dp = GWT.create(DataProperties.class);
*
*   // Create the store that the contains the data to display in the grid
*   ListStore<Data> s = new ListStore<Test.Data>(dp.key());
*   s.add(new Data("name1", "value1"));
*   s.add(new Data("name2", "value2"));
*   s.add(new Data("name3", "value3"));
*   s.add(new Data("name4", "value4"));
*
*   // Create the tree using the store and value provider for the name field
*   ListView<Data, String> t = new ListView<Data, String>(s, dp.name());
*
*   // Add the tree to a container
*   RootPanel.get().add(t);
* </pre>
* <p/>
* To use the Sencha supplied generator to create model key providers and value
* providers, extend the <code>PropertyAccess</code> interface, parameterized
* with the type of data you want to access (as shown below) and invoke the
* <code>GWT.create</code> method on its <code>class</code> member (as shown in
* the code snippet above). This creates an instance of the class that can be
* used to initialize the list view and list store. In the following code
* snippet we define a new interface called <code>DataProperties</code> that
* extends the <code>PropertyAccess</code> interface and is parameterized with
* <code>Data</code>, a Plain Old Java Object (POJO).
* <p/>
* <pre>
* public interface DataProperties extends PropertyAccess<Data> {
*   &#64;Path("name")
*   ModelKeyProvider<Data> key();
*   ValueProvider&lt;Data, String> name();
*   ValueProvider&lt;Data, String> value();
* }
*
* public class Data {
*   private String name;
*   private String value;
*
*   public Data(String name, String value) {
*     super();
*     this.name = name;
*     this.value = value;
*   }
*   public String getName() {
*     return name;
*   }
*   public String getValue() {
*     return value;
*   }
*   public void setName(String name) {
*     this.name = name;
*   }
*   public void setValue(String value) {
*     this.value = value;
*   }
* }
* </pre>
*
* @param <M> the model type
* @param <N> the cell data type
*/
public class ListView<M, N> extends Component implements HasRefreshHandlers {

  /**
   * The appearance of a list view.
   *
   * @param <M> the model type
   */
  public interface ListViewAppearance<M> {

    /**
     * Returns the cell's parent element if it exists. Default implementation
     * returns the cell's root element.
     *
     * @param item the cell whose parent is to be returned
     * @return the root from which other elements can be found
     */
    Element findCellParent(XElement item);

    /**
     * Returns the matching element.
     *
     * @param child the element or any child element
     * @return the parent element
     */
    Element findElement(XElement child);

    /**
     * Returns the child elements.
     *
     * @param parent the parent element
     * @return the child elements
     */
    List<Element> findElements(XElement parent);

    /**
     * Handles a cursor over event.
     *
     * @param item the item affected by the cursor
     * @param over true if the cursor is over the item
     */
    void onOver(XElement item, boolean over);

    /**
     * Handles a select event.
     *
     * @param item the item affected by the select
     * @param select true if the item is selected
     */
    void onSelect(XElement item, boolean select);

    /**
     * Renders the container.
     *
     * @param builder the builder
     */
    void render(SafeHtmlBuilder builder);

    /**
     * Optionally renders extra markup at the end of the the list.
     *
     * @param builder the builder
     */
    void renderEnd(SafeHtmlBuilder builder);

    /**
     * Renders a single item.
     *
     * @param builder the builder
     * @param content the item content
     */
    void renderItem(SafeHtmlBuilder builder, SafeHtml content);

  }


  private static final Logger logger = Logger.getLogger(ListView.class.getName());

  private final ListViewAppearance<M> appearance;
  protected XElement focusEl;
  protected final FocusImpl focusImpl = FocusImpl.getFocusImplForPanel();

  protected int rowSelectorDepth = 5;
  protected ListStore<M> store;

  final protected ValueProvider<? super M, N> valueProvider;

  private CompositeElement all;

  private Cell<N> cell;

  private boolean enableQuickTip = true;
  private HandlerRegistration storeHandlersRegistration;
  private GroupingHandlerRegistration loadHandlerRegistration;
  private SafeHtml loadingHtml;

  private XElement overElement;
  private QuickTip quickTip;
  private boolean selectOnHover;
  private ListViewSelectionModel<M> sm;
  private StoreHandlers<M> storeHandlers;
  private boolean trackMouseOver = true;

  /**
   * Creates a new list view.
   *
   * @param store the store
   * @param valueProvider the value provider
   */
  public ListView(ListStore<M> store, ValueProvider<? super M, N> valueProvider) {
    this(store, valueProvider, GWT.<ListViewAppearance<M>>create(ListViewAppearance.class));
  }

  /**
   * Creates a new list view.
   *
   * @param store the store
   * @param valueProvider the value provider
   * @param cell the cell
   */
  public ListView(ListStore<M> store, ValueProvider<? super M, N> valueProvider, Cell<N> cell) {
    this(store, valueProvider, GWT.<ListViewAppearance<M>>create(ListViewAppearance.class));
    setCell(cell);
  }

  /**
   * Creates a new list view.
   *
   * @param store the store
   * @param valueProvider the value provider
   * @param appearance the appearance
   */
  public ListView(final ListStore<M> store, ValueProvider<? super M, N> valueProvider, ListViewAppearance<M> appearance) {
    this.appearance = appearance;
    this.valueProvider = valueProvider;
    setSelectionModel(new ListViewSelectionModel<M>());

    SafeHtmlBuilder markupBuilder = new SafeHtmlBuilder();
    appearance.render(markupBuilder);

    setElement((Element) XDOM.create(markupBuilder.toSafeHtml()));

    all = new CompositeElement();
    setAllowTextSelection(false);

    storeHandlers = new StoreHandlers<M>() {

      @Override
      public void onAdd(StoreAddEvent<M> event) {
        ListView.this.onAdd(event.getItems(), event.getIndex());
      }

      @Override
      public void onClear(StoreClearEvent<M> event) {
        refresh();
      }

      @Override
      public void onDataChange(StoreDataChangeEvent<M> event) {
        refresh();
      }

      @Override
      public void onFilter(StoreFilterEvent<M> event) {
        refresh();
      }

      @Override
      public void onRecordChange(StoreRecordChangeEvent<M> event) {
        if (ListView.this.valueProvider == event.getProperty()) {
          ListView.this.onUpdate(event.getRecord().getModel(), store.indexOf(event.getRecord().getModel()));
        }
      }

      @Override
      public void onRemove(StoreRemoveEvent<M> event) {
        ListView.this.onRemove(event.getItem(), event.getIndex());
      }

      @Override
      public void onSort(StoreSortEvent<M> event) {
        refresh();
      }

      @Override
      public void onUpdate(StoreUpdateEvent<M> event) {
        List<M> items = event.getItems();
        for (int i = 0; i < items.size(); i++) {
          M item = items.get(i);
          ListView.this.onUpdate(item, store.indexOf(item));
        }
      }
    };

    setStore(store);

    ensureFocusElement();

    sinkEvents(Event.ONCLICK | Event.ONDBLCLICK | Event.MOUSEEVENTS | Event.ONKEYDOWN);
  }

  @Override
  public HandlerRegistration addRefreshHandler(RefreshHandler handler) {
    return addHandler(handler, RefreshEvent.getType());
  }

  /**
   * Returns the matching element.
   *
   * @param element the element or any child element
   * @return the parent element
   */
  public Element findElement(Element element) {
    return appearance.findElement(element.<XElement>cast());
  }

  /**
   * Returns the element's index.
   *
   * @param element the element or any child element
   * @return the element index or -1 if no match
   */
  public int findElementIndex(Element element) {
    Element elem = findElement(element);
    if (elem != null) {
      return indexOf(elem);
    }
    return -1;
  }

  @Override
  public void focus() {
    if (GXT.isGecko()) {
      Scheduler.get().scheduleDeferred(new ScheduledCommand() {
        @Override
        public void execute() {
          if (ListView.this.focusEl != null) {
            ListView.this.focusImpl.focus(ListView.this.focusEl);
          }
        }
      });
    } else if (focusEl != null) {
      focusImpl.focus(focusEl);
    }
  }

  public ListViewAppearance<M> getAppearance() {
    return appearance;
  }

  /**
   * Returns the view's cell.
   *
   * @return the cell, or null if none specified
   */
  public Cell<N> getCell() {
    return cell;
  }

  /**
   * Returns the element at the given index.
   *
   * @param index the index
   * @return the element
   */
  public XElement getElement(int index) {
    return all.getElement(index).<XElement>cast();
  }

  /**
   * Returns all of the child elements.
   *
   * @return the elements
   */
  public List<Element> getElements() {
    return all.getElements();
  }

  /**
   * Returns the number of models in the view.
   *
   * @return the count
   */
  public int getItemCount() {
    return store == null ? 0 : store.size();
  }

  /**
   * Returns the view's loading HTML.
   *
   * @return the loading text
   */
  public SafeHtml getLoadingHtml() {
    return loadingHtml;
  }

  /**
   * Returns the view's quick tip instance.
   *
   * @return the quicktip instance or null if not enabled
   */
  public QuickTip getQuickTip() {
    return quickTip;
  }

  /**
   * Returns the view's selection model.
   *
   * @return the selection model
   */
  public ListViewSelectionModel<M> getSelectionModel() {
    return sm;
  }

  /**
   * Returns true if select on hover is enabled.
   *
   * @return the select on hover state
   */
  public boolean getSelectOnOver() {
    return selectOnHover;
  }

  /**
   * Returns the combo's store.
   *
   * @return the store
   */
  public ListStore<M> getStore() {
    return store;
  }

  /**
   * Returns the index of the element.
   *
   * @param element the element
   * @return the index
   */
  public int indexOf(Element element) {
    if (element == null) {
      return -1;
    }
    if (element.getPropertyString("viewIndex") != null) {
      return Util.parseInt(element.getPropertyString("viewIndex"), -1);
    }
    return all.indexOf(element);
  }

  /**
   * Returns true if quicktips are enabled.
   *
   * @return true for enabled
   */
  public boolean isEnableQuickTips() {
    return enableQuickTip;
  }

  /**
   * Returns true if items are highlighted on mouse over.
   *
   * @return the track mouse state
   */
  public boolean isTrackMouseOver() {
    return trackMouseOver;
  }

  /**
   * Moves the current selections down one level.
   */
  public void moveSelectedDown() {
    List<M> sel = getSelectionModel().getSelectedItems();

    Collections.sort(sel, new Comparator<M>() {
      public int compare(M o1, M o2) {
        return store.indexOf(o1) < store.indexOf(o2) ? 1 : 0;
      }
    });

    for (M m : sel) {
      int idx = store.indexOf(m);
      if (idx < (store.size() - 1)) {
        store.remove(m);
        store.add(idx + 1, m);
      }
    }
    getSelectionModel().select(sel, false);
  }

  /**
   * Moves the current selections up one level.
   */
  public void moveSelectedUp() {
    List<M> sel = getSelectionModel().getSelectedItems();

    Collections.sort(sel, new Comparator<M>() {
      public int compare(M o1, M o2) {
        return store.indexOf(o1) > store.indexOf(o2) ? 1 : 0;
      }
    });

    for (M m : sel) {
      int idx = store.indexOf(m);
      if (idx > 0) {
        store.remove(m);
        store.add(idx - 1, m);
      }
    }
    getSelectionModel().select(sel, false);
  }

  @Override
  public void onBrowserEvent(Event event) {
    if (!isEnabled()) {
      return;
    }
    if (cell != null) {
      CellWidgetImplHelper.onBrowserEvent(this, event);
    }

    handleEventForCell(event);

    super.onBrowserEvent(event);

    switch (event.getTypeInt()) {
      case Event.ONMOUSEOVER:
        onMouseOver(event);
        break;
      case Event.ONMOUSEOUT:
        onMouseOut(event);
        break;
      case Event.ONMOUSEDOWN:
        onMouseDown(event);
        break;
    }
  }

  /**
   * Refreshes the view by reloading the data from the store and re-rendering
   * the template.
   */
  public void refresh() {
    if (!isOrWasAttached()) {
      return;
    }
    getElement().setInnerHTML("");
    getElement().repaint();

    List<M> models = store == null ? new ArrayList<M>() : store.getAll();
    all.removeAll();

    SafeHtmlBuilder sb = new SafeHtmlBuilder();
    bufferRender(models, sb);

    SafeHtml markup = sb.toSafeHtml();
    getElement().setInnerHTML(markup.asString());

    SafeHtmlBuilder esb = new SafeHtmlBuilder();
    appearance.renderEnd(esb);
    DomHelper.insertHtml("beforeend", getElement(), esb.toSafeHtml().asString());

    List<Element> elems = appearance.findElements(getElement());

    all = new CompositeElement(elems);
    updateIndexes(0, -1);

    ensureFocusElement();

    fireEvent(new RefreshEvent());
  }

  /**
   * Refreshes an individual node's data from the store.
   *
   * @param index the items data index in the store
   */
  public void refreshNode(int index) {
    onUpdate(store.get(index), index);
  }

  @Override
  public void setAllowTextSelection(boolean enable) {
    allowTextSelection = enable;

    getElement().setClassName(CommonStyles.get().unselectableSingle(), !enable);
  }

  /**
   * Optionally sets the view's cell. If a cell is not provided, toString is
   * called on the value returned by the view's value provider.
   *
   * @param cell the cell
   */
  public void setCell(Cell<N> cell) {
    this.cell = cell;

    if (cell != null) {
      CellWidgetImplHelper.sinkEvents(this, cell.getConsumedEvents());
    }
  }

  /**
   * True to enable quicktips (defaults to true, pre-render).
   *
   * @param enableQuickTip true to enable quicktips
   */
  public void setEnableQuickTips(boolean enableQuickTip) {
    assertPreRender();
    this.enableQuickTip = enableQuickTip;
  }

  /**
   * Sets the loader. Used for interacting with before load and load exception events.
   * <p/>
   * <ul>
   * <li>Used with {@link #setLoadingHtml(SafeHtml)} or {@link #setLoadingText(String)}.</li>
   * <li>{@link #onBeforeLoad()} is called before a load and sets the loading HTML.</li>
   * <li>{@link #onLoadError(com.sencha.gxt.data.shared.loader.LoadExceptionEvent)} is called on load exception.</li>
   * </ul>
   *
   * @param loader is the list loader
   */
  @SuppressWarnings({"unchecked", "rawtypes"})
  public void setLoader(Loader<?, ?> loader) {
    if (loadHandlerRegistration != null) {
      loadHandlerRegistration.removeHandler();
      loadHandlerRegistration = null;
    }
    if (loader != null) {
      if (loadHandlerRegistration == null) {
        loadHandlerRegistration = new GroupingHandlerRegistration();
      }
      loadHandlerRegistration.add(loader.addBeforeLoadHandler(new BeforeLoadEvent.BeforeLoadHandler() {
        @Override
        public void onBeforeLoad(BeforeLoadEvent event) {
          ListView.this.onBeforeLoad();
        }
      }));
      loadHandlerRegistration.add(loader.addLoadExceptionHandler(new LoadExceptionHandler() {
        @Override
        public void onLoadException(LoadExceptionEvent event) {
          refresh();
          ListView.this.onLoadError(event);
        }
      }));
    }
  }

  /**
   * Sets the html loading text to be displayed during a load request.
   * <p/>
   * <ul>
   * <li>The {@link #setLoader(Loader)} has to be set for this use.
   * <li>The css class name 'loading-indicator' can style the loading HTML.
   * </ul>
   *
   * @param loadingHtml the loading html
   */
  public void setLoadingHtml(SafeHtml loadingHtml) {
    this.loadingHtml = loadingHtml;
  }

  /**
   * Sets the text loading text to be displayed during a load request.
   * <p/>
   * <ul>
   * <li>The {@link #setLoader(Loader)} has to be set for this use.
   * <li>The css class name 'loading-indicator' can style the loading text.
   * </ul>
   *
   * @param loadingText the loading text
   */
  public void setLoadingText(String loadingText) {
    if (loadingText != null) {
      this.loadingHtml = SafeHtmlUtils.fromString(loadingText);
    } else {
      this.loadingHtml = null;
    }
  }

  /**
   * Sets the selection model.
   *
   * @param sm the selection model
   */
  public void setSelectionModel(ListViewSelectionModel<M> sm) {
    if (this.sm != null) {
      this.sm.bindList(null);
    }
    this.sm = sm;
    if (sm != null) {
      sm.bindList(this);
    }
  }

  /**
   * True to select the item when mousing over a element (defaults to false).
   *
   * @param selectOnHover true to select on mouse over
   */
  public void setSelectOnOver(boolean selectOnHover) {
    this.selectOnHover = selectOnHover;
  }

  /**
   * Changes the data store bound to this view and refreshes it.
   *
   * @param store the store to bind this view
   */
  public void setStore(ListStore<M> store) {
    if (this.store != null) {
      storeHandlersRegistration.removeHandler();
    }
    if (store != null) {
      storeHandlersRegistration = store.addStoreHandlers(storeHandlers);
    }
    this.store = store;
    sm.bindList(this);

    if (store != null) {
      refresh();
    }
  }

  /**
   * True to highlight items when the mouse is over (defaults to true).
   *
   * @param trackMouseOver true to highlight rows on mouse over
   */
  public void setTrackMouseOver(boolean trackMouseOver) {
    this.trackMouseOver = trackMouseOver;
  }

  protected void bufferRender(List<M> models, SafeHtmlBuilder sb) {
    for (int i = 0, len = models.size(); i < len; i++) {
      M m = models.get(i);
      SafeHtmlBuilder cellBuilder = new SafeHtmlBuilder();
      N v = getValue(m);
      if (cell == null) {
        String text = null;
        if (v != null) {
          text = v.toString();
        }
        cellBuilder.append(Util.isEmptyString(text) ? SafeHtmlUtils.fromTrustedString("&#160;")
            : SafeHtmlUtils.fromString(text));
      } else {
        Context context = new Context(i, 0, store.getKeyProvider().getKey(m));
        cell.render(context, v, cellBuilder);
      }

      appearance.renderItem(sb, cellBuilder.toSafeHtml());
    }
  }

  protected boolean cellConsumesEventType(Cell<N> cell, String eventType) {
    Set<String> consumedEvents = cell.getConsumedEvents();
    return consumedEvents != null && consumedEvents.contains(eventType);
  }

  protected void focusItem(int index) {
    XElement elem = getElement(index);
    if (elem != null) {
      elem.scrollIntoView(getElement(), false);
      focusEl.setXY(elem.getXY());
    }
    focus();
  }

  protected N getValue(M m) {
    N value;
    if (store.hasRecord(m)) {
      value = store.getRecord(m).getValue(valueProvider);
    } else {
      value = valueProvider.getValue(m);
    }
    return value;
  }

  protected void handleEventForCell(Event event) {
    // Get the event target.
    EventTarget eventTarget = event.getEventTarget();
    if (cell == null || !Element.is(eventTarget)) {
      return;
    }
    final XElement target = event.getEventTarget().cast();

    int rowIndex = findElementIndex(appearance.findElement(target));

    final M m = getStore().get(rowIndex);
    Element cellParent = appearance.findCellParent(getElement(rowIndex));

    if (m != null && cellParent != null) {
      DefaultHandlerManagerContext context = new DefaultHandlerManagerContext(rowIndex, 0,
          getStore().getKeyProvider().getKey(m), ComponentHelper.ensureHandlers(this));

      if (cellConsumesEventType(cell, event.getType())) {
        cell.onBrowserEvent(context, cellParent, getValue(m), event, new ValueUpdater<N>() {

          @Override
          public void update(N value) {
            ListView.this.getStore().getRecord(m).addChange(valueProvider, value);
          }
        });
      }
    }
  }

  protected void onAdd(List<M> models, final int index) {
    if (!isOrWasAttached()) {
      return;
    }

    boolean empty = all.getCount() == 0;
    // add on sorted store, fires multiple adds while store has all models
    // before firing
    if (empty && models.size() == store.size()) {
      refresh();
      return;
    }
    SafeHtmlBuilder sb = new SafeHtmlBuilder();
    bufferRender(models, sb);

    Element d = Document.get().createDivElement();
    d.setInnerHTML(sb.toSafeHtml().asString());
    List<Element> list = appearance.findElements(d.<XElement>cast());

    final Element ref = index == 0 ? null : all.getElement(index - 1);
    final Element n = ref == null ? null : ref.getParentElement();

    for (int i = list.size() - 1; i >= 0; i--) {
      Element e = list.get(i);
      if (index == 0) {
        getElement().insertFirst(e);
      } else {
        Node next = ref == null ? null : ref.getNextSibling();
        if (next == null) {
          n.appendChild(e);
        } else {
          n.insertBefore(e, next);
        }
      }
    }

    all.insert(Util.toElementArray(list), index);
    updateIndexes(index, -1);
  }

  @Override
  protected void onAfterFirstAttach() {
    super.onAfterFirstAttach();
    if (enableQuickTip) {
      quickTip = new QuickTip(this);
    }
    refresh();
  }

  /**
   * Is called before a load when a {@link #setLoader(Loader) and {@link #setLoadingHtml(SafeHtml) or
   * {@link #setLoadingText(String)} are used the list will display the loading HTML.
   * <p/>
   * <ul>
   * <li>The {@link #setLoader(Loader)} has to be set for this to be called.
   * <li>The css class name 'loading-indicator' can style the loading HTML or text.
   * </ul>
   */
  protected void onBeforeLoad() {
    if (loadingHtml != null) {
      getElement().setInnerHTML("<div class='loading-indicator'>" + loadingHtml.asString() + "</div>");
      all.removeAll();
    }
  }

  /**
   * Is called on loader exception. Override to add error handling.
   * <p/>
   * <ul>
   * <li>The {@link #setLoader(Loader)} has to be set for this to be called.
   * </ul>
   *
   * @param event is the load exception event
   */
  protected void onLoadError(LoadExceptionEvent<?> event) {
    if (GXTLogConfiguration.loggingIsEnabled()) {
      logger.severe("The ListView had a load exception");
    }
  }

  @Override
  protected void onFocus(Event event) {
    super.onFocus(event);
  }

  protected void onHighlightRow(int index, boolean highLight) {
    XElement e = getElement(index);
    if (e != null) {
      e.setClassName("x-view-highlightrow", highLight);
    }
  }

  protected void onMouseDown(Event e) {
    int index = indexOf(e.getEventTarget().<Element>cast());
    if (index != -1) {
      fireEvent(new SelectEvent());
    }
  }

  protected void onMouseOut(Event ce) {
    if (overElement != null) {
      if (!ce.<XEvent>cast().within(overElement, true)) {
        appearance.onOver(overElement, false);
        overElement = null;
      }
    }
  }

  protected void onMouseOver(Event ce) {
    if (selectOnHover || trackMouseOver) {
      Element target = ce.getEventTarget().<Element>cast();
      target = findElement(target);
      if (target != null) {
        int index = indexOf(target);
        if (index != -1) {
          if (selectOnHover) {
            sm.select(index, false);
          } else {
            if (target != overElement) {
              appearance.onOver(target.<XElement>cast(), true);
              overElement = target.<XElement>cast();
            }
          }
        }
      }
    }
  }

  protected void onRemove(M data, int index) {
    if (all != null) {
      Element e = getElement(index);
      if (e != null) {
        appearance.onOver(e.<XElement>cast(), false);
        if (overElement == e) {
          overElement = null;
        }

        getSelectionModel().deselect(index);

        e.removeFromParent();
        all.remove(index);
        updateIndexes(index, -1);
      }
    }
  }

  @Override
  protected void onResize(int width, int height) {
    super.onResize(width, height);
    constrainFocusElement();
  }

  protected void onSelectChange(M model, boolean select) {
    if (all != null) {
      int index = store.indexOf(model);
      if (index != -1 && index < all.getCount()) {
        XElement e = getElement(index);
        if (e != null) {
          appearance.onSelect(e, select);
          appearance.onOver(e.<XElement>cast(), false);
        }
      }
    }
  }

  protected void onUpdate(M model, int index) {
    Element original = all.getElement(index);
    if (original != null) {
      List<M> list = Collections.singletonList(model);
      SafeHtmlBuilder sb = new SafeHtmlBuilder();
      bufferRender(list, sb);

      DomHelper.insertBefore(original, sb.toSafeHtml().asString());

      all.replaceElement(original, Element.as(original.getPreviousSibling()));
      original.removeFromParent();

    }
    sm.onRowUpdated(model);
  }

  protected void updateIndexes(int startIndex, int endIndex) {
    List<Element> elems = all.getElements();
    endIndex = endIndex == -1 ? elems.size() - 1 : endIndex;
    for (int i = startIndex; i <= endIndex; i++) {
      elems.get(i).setPropertyInt("viewIndex", i);
    }
  }

  private void constrainFocusElement() {
    int scrollLeft = getElement().getScrollLeft();
    int scrollTop = getElement().getScrollTop();
    int left = getElement().getWidth(true) / 2 + scrollLeft;
    int top = getElement().getHeight(true) / 2 + scrollTop;
    focusEl.setLeftTop(left, top);
  }

  private void ensureFocusElement() {
    if (focusEl != null) {
      focusEl.removeFromParent();
    }
    focusEl = (XElement) getElement().appendChild(focusImpl.createFocusable());
    focusEl.addClassName(CommonStyles.get().noFocusOutline());
    if (focusEl.hasChildNodes()) {
      focusEl.getFirstChildElement().addClassName(CommonStyles.get().noFocusOutline());
      com.google.gwt.dom.client.Style focusElStyle = focusEl.getFirstChildElement().getStyle();
      focusElStyle.setBorderWidth(0, Unit.PX);
      focusElStyle.setFontSize(1, Unit.PX);
      focusElStyle.setPropertyPx("lineHeight", 1);
    }
    focusEl.setLeft(0);
    focusEl.setTop(0);
    focusEl.makePositionable(true);
    focusEl.addEventsSunk(Event.FOCUSEVENTS);

  }

}
TOP

Related Classes of com.sencha.gxt.widget.core.client.ListView$ListViewAppearance

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.