/* * Ext GWT - Ext for GWT * Copyright(c) 2007-2009, Ext JS, LLC. * licensing@extjs.com * * http://extjs.com/license */ package com.extjs.gxt.ui.client.store; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import com.extjs.gxt.ui.client.Style.SortDir; import com.extjs.gxt.ui.client.data.ListLoadConfig; import com.extjs.gxt.ui.client.data.ListLoadResult; import com.extjs.gxt.ui.client.data.ListLoader; import com.extjs.gxt.ui.client.data.LoadEvent; import com.extjs.gxt.ui.client.data.Loader; import com.extjs.gxt.ui.client.data.ModelData; import com.extjs.gxt.ui.client.data.SortInfo; import com.extjs.gxt.ui.client.event.Listener; import com.extjs.gxt.ui.client.event.LoadListener; import com.extjs.gxt.ui.client.util.Util; import com.extjs.gxt.ui.client.widget.ListView; import com.extjs.gxt.ui.client.widget.form.ComboBox; /** * The store class encapsulates a client side cache of {@link ModelData} objects * which provide input data for Components such as the {@link ComboBox} and * {@link ListView ListView} * * <dl> * <dt><b>Events:</b></dt> * * <dd><b>Store.BeforeDataChanged</b> : StoreEvent(store)<br> * <div>Fires before the store's data is changed. Apply applies when a store is * bound to a loader.</div> * <ul> * <li>store : this</li> * </ul> * </dd> * * <dd><b>Store.DataChange</b> : StoreEvent(store)<br> * <div>Fires when the data cache has changed, and a widget which is using this * Store as a ModelData cache should refresh its view.</div> * <ul> * <li>store : this</li> * </ul> * </dd> * * <dd><b>Store.Filter</b> : StoreEvent(store)<br> * <div>Fires when filters are applied and removed from the store.</div> * <ul> * <li>store : this</li> * </ul> * </dd> * * <dd><b>Store.Sort</b> : StoreEvent(store)<br> * <div>Fires after the store's data has been changed due to sorting.</div> * <ul> * <li>store : this</li> * </ul> * </dd> * * <dd><b>Store.Add</b> : StoreEvent(store, models, index)<br> * <div>Fires when models have been added to the store.</div> * <ul> * <li>store : this</li> * <li>models : the added models</li> * <li>index : the index at which the model(s) were added</li> * </ul> * </dd> * * <dd><b>Store.Remove</b> : StoreEvent(store, model)<br> * <div>Fires when a model has been removed from the store.</div> * <ul> * <li>store : this</li> * <li>model : the model that was removed</li> * <li>index : the index at which the model was removed</li> * </ul> * </dd> * * <dd><b>Store.Update</b> : StoreEvent(store, model, record)<br> * <div>Fires when a model has been updated via its record.</div> * <ul> * <li>store : this</li> * <li>model : the model that was updated</li> * <li>record : the record that was updated</li> * <li>operation : the update operation being performed.</li> * </ul> * </dd> * * <dd><b>Store.Clear</b> : StoreEvent(store)<br> * <div>Fires when the data cache has been cleared.</div> * <ul> * <li>store : this</li> * </ul> * </dd> * * </dl> */ public class ListStore<M extends ModelData> extends Store<M> { protected ListLoader<ListLoadResult<M>> loader; protected ListLoadConfig config; /** * Creates a new store. */ public ListStore() { } /** * Creates a new store. * * @param loader the loader instance */ @SuppressWarnings("unchecked") public ListStore(ListLoader loader) { this.loader = loader; loader.addLoadListener(new LoadListener() { public void loaderBeforeLoad(LoadEvent le) { onBeforeLoad(le); } public void loaderLoad(LoadEvent le) { onLoad(le); } public void loaderLoadException(LoadEvent le) { onLoadException(le); } }); } /** * Adds the models to the store and fires the <i>Add</i> event. * * @param models the models to add */ public void add(List<? extends M> models) { insert(models, getCount()); } /** * Adds the model to the store and fires the <i>Add</i> event. * * @param model the model to add */ public void add(M model) { insert(model, getCount()); } /** * Get the model at the specified index. * * @param index the index of the model to find * @return the model at the passed index */ public M getAt(int index) { return index < all.size() ? all.get(index) : null; } /** * Gets the number of cached records. * * @return the number of models in the store's cache. */ public int getCount() { return all.size(); } /** * Returns the store's last processed load config if available. * * @return the load config */ public ListLoadConfig getLoadConfig() { return config; } /** * Returns the store's loader * * @return the loader or null */ public ListLoader<? extends ListLoadResult<M>> getLoader() { return loader; } /** * Returns a range of models between specified indices. * * @param start the starting index * @param end the ending index * @return the list of models */ public List<M> getRange(int start, int end) { List<M> temp = new ArrayList<M>(); for (int i = start; i <= end; i++) { temp.add(getAt(i)); } return temp; } /** * Returns the current sort direction. * * @return the sort direction */ public SortDir getSortDir() { return sortInfo.getSortDir(); } /** * Returns the current sort field. * * @return the sort field */ public String getSortField() { return sortInfo.getSortField(); } /** * Returns the current sort state of this store. * * @return the sort state */ public SortInfo getSortState() { return sortInfo; } /** * Returns the index of the model in this store. * * @param model the model * @return the index */ public int indexOf(M model) { for (int i = 0; i < all.size(); i++) { M m = all.get(i); if (equals(model, m)) { return i; } } return -1; } /** * Inserts the models to the store at the given index and fires the <i>Add</i> * event. * * @param models the models to insert * @param index the insert location */ public void insert(List<? extends M> models, int index) { insert(models, index, false); } /** * Inserts the model to the store at the given index and fires the <i>Add</i> * event. * * @param model the model to insert * @param index the insert location */ public void insert(M model, int index) { List<M> temp = new ArrayList<M>(); temp.add(model); insert(temp, index); } /** * Remove a item from the store and fires the <i>Remove</i> event. * * @param model the model to remove */ public void remove(M model) { int index = indexOf(model); if (all.remove(model)) { modified.remove(recordMap.get(model)); if (isFiltered()) { snapshot.remove(model); } unregisterModel(model); StoreEvent<M> se = createStoreEvent(); se.setModel(model); se.setIndex(index); fireEvent(Remove, se); } } /** * Sets the default sort column and order to be used by the next load * operation. * * @param field the name of the field to sort by * @param dir the sort direction */ public void setDefaultSort(String field, SortDir dir) { sortInfo = new SortInfo(field, dir); } /** * Ses the sort direction. * * @param dir the sort direction */ public void setSortDir(SortDir dir) { sortInfo.setSortDir(dir); } /** * Sets the sort field. * * @param field the sort field */ public void setSortField(String field) { sortInfo.setSortField(field); } /** * Sort the store. * * <p/> * If remote sorting is used, the sort is performed on the server, and the * cache is reloaded. If local sorting is used, the cache is sorted * internally. * * @param field the field to sort by * @param sortDir the sort dir */ public void sort(String field, SortDir sortDir) { SortInfo prev = new SortInfo(sortInfo.getSortField(), sortInfo.getSortDir()); if (sortDir == null) { if (sortInfo.getSortField() != null && !sortInfo.getSortField().equals(field)) { sortInfo.setSortDir(SortDir.NONE); } switch (sortInfo.getSortDir()) { case ASC: sortDir = SortDir.DESC; break; case DESC: case NONE: sortDir = SortDir.ASC; break; } } sortInfo.setSortField(field); sortInfo.setSortDir(sortDir); if (loader != null && loader.isRemoteSort()) { Listener<LoadEvent> l = new Listener<LoadEvent>() { public void handleEvent(LoadEvent le) { loader.removeListener(Sort, this); sortInfo = le.<ListLoadConfig> getConfig().getSortInfo(); fireEvent(Sort, createStoreEvent()); } }; loader.addListener(Loader.Load, l); loader.setSortDir(sortDir); loader.setSortField(field); if (!loader.load()) { loader.removeListener(Sort, l); sortInfo.setSortField(prev.getSortField()); sortInfo.setSortDir(prev.getSortDir()); } } else { applySort(false); fireEvent(DataChanged, createStoreEvent()); } } @SuppressWarnings("unchecked") @Override protected void applySort(boolean supressEvent) { if ((loader == null || !loader.isRemoteSort())) { storeSorter = storeSorter == null ? new StoreSorter() : storeSorter; Collections.sort(all, new Comparator<M>() { public int compare(M m1, M m2) { return storeSorter.compare(ListStore.this, m1, m2, sortInfo.getSortField()); } }); if (sortInfo.getSortDir() == SortDir.DESC) { Collections.reverse(all); } if (!supressEvent) { fireEvent(Sort, createStoreEvent()); } } } @SuppressWarnings("unchecked") protected void insert(List<? extends M> items, int index, boolean supressEvent) { if (items.size() > 0) { List<M> added = new ArrayList<M>(); if (storeSorter != null) { boolean defer = index == 0 && getCount() == 0; for (M m : items) { if (isFiltered()) { snapshot.add(m); if (!isFiltered(m, filterProperty)) { all.add(m); added.add(m); } } else { all.add(m); added.add(m); } applySort(true); int idx = indexOf(m); registerModel(m); if (!defer && !supressEvent && added.contains(m)) { StoreEvent evt = createStoreEvent(); evt.setModels(Util.createList(m)); evt.setIndex(idx); fireEvent(Add, evt); } } if (defer && !supressEvent && added.size() > 0) { StoreEvent evt = createStoreEvent(); evt.setModels(getModels()); evt.setIndex(index); fireEvent(Add, evt); } } else { if (!isFiltered()) { all.addAll(index, items); added.addAll(items); } else { snapshot.addAll(index, items); } for (M m : items) { registerModel(m); if (isFiltered() && !isFiltered(m, filterProperty)) { all.add(index, m); added.add(m); } } if (!supressEvent && added.size() > 0) { StoreEvent evt = createStoreEvent(); evt.setModels(added); evt.setIndex(index); fireEvent(Add, evt); } } } } protected void onBeforeLoad(LoadEvent le) { if (!fireEvent(BeforeDataChanged, createStoreEvent())) { le.setCancelled(true); } } @SuppressWarnings("unchecked") protected void onLoad(LoadEvent le) { this.config = (ListLoadConfig) le.getConfig(); Object data = le.getData(); removeAll(); if (data instanceof List) { List<M> list = (List) le.getData(); all = list; } else if (data instanceof ListLoadResult) { all = ((ListLoadResult) data).getData(); } for (M m : all) { registerModel(m); } if (le.<Object> getConfig() instanceof ListLoadConfig) { ListLoadConfig config = le.getConfig(); if (config.getSortInfo().getSortField() != null) { sortInfo = config.getSortInfo(); } else { sortInfo = new SortInfo(); } } if (filtersEnabled) { filtersEnabled = false; applyFilters(filterProperty); } if (storeSorter != null) { applySort(true); } fireEvent(DataChanged, createStoreEvent()); } protected void onLoadException(LoadEvent le) { } @SuppressWarnings("unchecked") protected void sortData(final String field, SortDir direction) { direction = direction == null ? SortDir.ASC : direction; storeSorter = storeSorter == null ? new StoreSorter() : storeSorter; Collections.sort(all, new Comparator<M>() { public int compare(M m1, M m2) { return storeSorter.compare(ListStore.this, m1, m2, field); } }); if (direction == SortDir.DESC) { Collections.reverse(all); } } }