3.0 source code

This commit is contained in:
agolybev
2015-04-28 17:59:00 +03:00
parent c69fd34bdd
commit 7b3b2248e5
16311 changed files with 1445974 additions and 3108429 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,254 @@
/**
* @aside video list
* @aside guide list
*
* IndexBar is a component used to display a list of data (primarily an alphabet) which can then be used to quickly
* navigate through a list (see {@link Ext.List}) of data. When a user taps on an item in the {@link Ext.IndexBar},
* it will fire the {@link #index} event.
*
* Here is an example of the usage in a {@link Ext.List}:
*
* @example phone portrait preview
* Ext.define('Contact', {
* extend: 'Ext.data.Model',
* config: {
* fields: ['firstName', 'lastName']
* }
* });
*
* var store = new Ext.data.JsonStore({
* model: 'Contact',
* sorters: 'lastName',
*
* grouper: {
* groupFn: function(record) {
* return record.get('lastName')[0];
* }
* },
*
* data: [
* {firstName: 'Tommy', lastName: 'Maintz'},
* {firstName: 'Rob', lastName: 'Dougan'},
* {firstName: 'Ed', lastName: 'Spencer'},
* {firstName: 'Jamie', lastName: 'Avins'},
* {firstName: 'Aaron', lastName: 'Conran'},
* {firstName: 'Dave', lastName: 'Kaneda'},
* {firstName: 'Jacky', lastName: 'Nguyen'},
* {firstName: 'Abraham', lastName: 'Elias'},
* {firstName: 'Jay', lastName: 'Robinson'},
* {firstName: 'Nigel', lastName: 'White'},
* {firstName: 'Don', lastName: 'Griffin'},
* {firstName: 'Nico', lastName: 'Ferrero'},
* {firstName: 'Jason', lastName: 'Johnston'}
* ]
* });
*
* var list = new Ext.List({
* fullscreen: true,
* itemTpl: '<div class="contact">{firstName} <strong>{lastName}</strong></div>',
*
* grouped : true,
* indexBar : true,
* store: store,
* hideOnMaskTap: false
* });
*
*/
Ext.define('Ext.dataview.IndexBar', {
extend: 'Ext.Component',
alternateClassName: 'Ext.IndexBar',
/**
* @event index
* Fires when an item in the index bar display has been tapped.
* @param {Ext.dataview.IndexBar} this The IndexBar instance
* @param {String} html The HTML inside the tapped node.
* @param {Ext.dom.Element} target The node on the indexbar that has been tapped.
*/
config: {
baseCls: Ext.baseCSSPrefix + 'indexbar',
/**
* @cfg {String} direction
* Layout direction, can be either 'vertical' or 'horizontal'
* @accessor
*/
direction: 'vertical',
/**
* @cfg {Array} letters
* The letters to show on the index bar.
* @accessor
*/
letters: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'],
ui: 'alphabet',
/**
* @cfg {String} listPrefix
* The prefix string to be used at the beginning of the list.
* E.g: useful to add a "#" prefix before numbers.
* @accessor
*/
listPrefix: null
},
// @private
itemCls: Ext.baseCSSPrefix + '',
updateDirection: function(newDirection, oldDirection) {
var baseCls = this.getBaseCls();
this.element.replaceCls(baseCls + '-' + oldDirection, baseCls + '-' + newDirection);
},
getElementConfig: function() {
return {
reference: 'wrapper',
classList: ['x-centered', 'x-indexbar-wrapper'],
children: [this.callParent()]
};
},
updateLetters: function(letters) {
this.innerElement.setHtml('');
if (letters) {
var ln = letters.length,
i;
for (i = 0; i < ln; i++) {
this.innerElement.createChild({
html: letters[i]
});
}
}
},
updateListPrefix: function(listPrefix) {
if (listPrefix && listPrefix.length) {
this.innerElement.createChild({
html: listPrefix
}, 0);
}
},
// @private
initialize: function() {
this.callParent();
this.innerElement.on({
touchstart: this.onTouchStart,
touchend: this.onTouchEnd,
touchmove: this.onTouchMove,
scope: this
});
},
// @private
onTouchStart: function(e, t) {
e.stopPropagation();
this.innerElement.addCls(this.getBaseCls() + '-pressed');
this.pageBox = this.innerElement.getPageBox();
this.onTouchMove(e);
},
// @private
onTouchEnd: function(e, t) {
this.innerElement.removeCls(this.getBaseCls() + '-pressed');
},
// @private
onTouchMove: function(e) {
var point = Ext.util.Point.fromEvent(e),
target,
pageBox = this.pageBox;
if (!pageBox) {
pageBox = this.pageBox = this.el.getPageBox();
}
if (this.getDirection() === 'vertical') {
if (point.y > pageBox.bottom || point.y < pageBox.top) {
return;
}
target = Ext.Element.fromPoint(pageBox.left + (pageBox.width / 2), point.y);
}
else {
if (point.x > pageBox.right || point.x < pageBox.left) {
return;
}
target = Ext.Element.fromPoint(point.x, pageBox.top + (pageBox.height / 2));
}
if (target) {
this.fireEvent('index', this, target.dom.innerHTML, target);
}
},
destroy: function() {
var me = this,
elements = Array.prototype.slice.call(me.innerElement.dom.childNodes),
ln = elements.length,
i = 0;
for (; i < ln; i++) {
Ext.removeNode(elements[i]);
}
this.callParent();
}
}, function() {
//<deprecated product=touch since=2.0>
/**
* @member Ext.dataview.IndexBar
* @method isHorizontal
* Returns `true` when direction is horizontal.
* @removed 2.0.0
*/
Ext.deprecateMethod(this, 'isHorizontal', null, "Ext.dataview.IndexBar.isHorizontal() has been removed");
/**
* @member Ext.dataview.IndexBar
* @method isVertical
* Returns `true` when direction is vertical.
* @removed 2.0.0
*/
Ext.deprecateMethod(this, 'isVertical', null, "Ext.dataview.IndexBar.isVertical() has been removed");
/**
* @member Ext.dataview.IndexBar
* @method refresh
* Refreshes the view by reloading the data from the store and re-rendering the template.
* @removed 2.0.0
*/
Ext.deprecateMethod(this, 'refresh', null, "Ext.dataview.IndexBar.refresh() has been removed");
/**
* @member Ext.dataview.IndexBar
* @cfg {Boolean} alphabet
* `true` to use the letters property to show a list of the alphabet.
* @removed 2.0.0
*/
Ext.deprecateProperty(this, 'alphabet', null, "Ext.dataview.IndexBar.alphabet has been removed");
/**
* @member Ext.dataview.IndexBar
* @cfg {Boolean} itemSelector
* A simple CSS selector for items.
* @removed 2.0.0
*/
Ext.deprecateProperty(this, 'itemSelector', null, "Ext.dataview.IndexBar.itemSelector has been removed");
/**
* @member Ext.dataview.IndexBar
* @cfg {Boolean} store
* The store to be used for displaying data on the index bar.
* @removed 2.0.0
*/
Ext.deprecateProperty(this, 'store', null, "Ext.dataview.IndexBar.store has been removed");
//</deprecated>
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,16 @@
/**
* @private - To be made a sample
*/
Ext.define('Ext.dataview.ListItemHeader', {
extend: 'Ext.Component',
xtype : 'listitemheader',
config: {
/**
* @cfg
* @inheritdoc
*/
baseCls: Ext.baseCSSPrefix + 'list-header',
docked: 'top'
}
});

View File

@@ -0,0 +1,846 @@
/**
* NestedList provides a miller column interface to navigate between nested sets
* and provide a clean interface with limited screen real-estate.
*
* @example miniphone preview
* var data = {
* text: 'Groceries',
* items: [{
* text: 'Drinks',
* items: [{
* text: 'Water',
* items: [{
* text: 'Sparkling',
* leaf: true
* }, {
* text: 'Still',
* leaf: true
* }]
* }, {
* text: 'Coffee',
* leaf: true
* }, {
* text: 'Espresso',
* leaf: true
* }, {
* text: 'Redbull',
* leaf: true
* }, {
* text: 'Coke',
* leaf: true
* }, {
* text: 'Diet Coke',
* leaf: true
* }]
* }, {
* text: 'Fruit',
* items: [{
* text: 'Bananas',
* leaf: true
* }, {
* text: 'Lemon',
* leaf: true
* }]
* }, {
* text: 'Snacks',
* items: [{
* text: 'Nuts',
* leaf: true
* }, {
* text: 'Pretzels',
* leaf: true
* }, {
* text: 'Wasabi Peas',
* leaf: true
* }]
* }]
* };
*
* Ext.define('ListItem', {
* extend: 'Ext.data.Model',
* config: {
* fields: [{
* name: 'text',
* type: 'string'
* }]
* }
* });
*
* var store = Ext.create('Ext.data.TreeStore', {
* model: 'ListItem',
* defaultRootProperty: 'items',
* root: data
* });
*
* var nestedList = Ext.create('Ext.NestedList', {
* fullscreen: true,
* title: 'Groceries',
* displayField: 'text',
* store: store
* });
*
* @aside guide nested_list
* @aside example nested-list
* @aside example navigation-view
*/
Ext.define('Ext.dataview.NestedList', {
alternateClassName: 'Ext.NestedList',
extend: 'Ext.Container',
xtype: 'nestedlist',
requires: [
'Ext.List',
'Ext.TitleBar',
'Ext.Button',
'Ext.XTemplate',
'Ext.data.StoreManager',
'Ext.data.NodeStore',
'Ext.data.TreeStore'
],
config: {
/**
* @cfg
* @inheritdoc
*/
cls: Ext.baseCSSPrefix + 'nested-list',
/**
* @cfg {String/Object/Boolean} cardSwitchAnimation
* Animation to be used during transitions of cards.
* @removed 2.0.0 please use {@link Ext.layout.Card#animation}
*/
/**
* @cfg {String} backText
* The label to display for the back button.
* @accessor
*/
backText: 'Back',
/**
* @cfg {Boolean} useTitleAsBackText
* `true` to use title as a label for back button.
* @accessor
*/
useTitleAsBackText: true,
/**
* @cfg {Boolean} updateTitleText
* Update the title with the currently selected category.
* @accessor
*/
updateTitleText: true,
/**
* @cfg {String} displayField
* Display field to use when setting item text and title.
* This configuration is ignored when overriding {@link #getItemTextTpl} or
* {@link #getTitleTextTpl} for the item text or title.
* @accessor
*/
displayField: 'text',
/**
* @cfg {String} loadingText
* Loading text to display when a subtree is loading.
* @accessor
*/
loadingText: 'Loading...',
/**
* @cfg {String} emptyText
* Empty text to display when a subtree is empty.
* @accessor
*/
emptyText: 'No items available.',
/**
* @cfg {Boolean/Function} onItemDisclosure
* Maps to the {@link Ext.List#onItemDisclosure} configuration for individual lists.
* @accessor
*/
onItemDisclosure: false,
/**
* @cfg {Boolean} allowDeselect
* Set to `true` to allow the user to deselect leaf items via interaction.
* @accessor
*/
allowDeselect: false,
/**
* @deprecated 2.0.0 Please set the {@link #toolbar} configuration to `false` instead
* @cfg {Boolean} useToolbar `true` to show the header toolbar.
* @accessor
*/
useToolbar: null,
/**
* @cfg {Ext.Toolbar/Object/Boolean} toolbar
* The configuration to be used for the toolbar displayed in this nested list.
* @accessor
*/
toolbar: {
docked: 'top',
xtype: 'titlebar',
ui: 'light',
inline: true
},
/**
* @cfg {String} title The title of the toolbar
* @accessor
*/
title: '',
/**
* @cfg {String} layout
* @hide
* @accessor
*/
layout: {
type: 'card',
animation: {
type: 'slide',
duration: 250,
direction: 'left'
}
},
/**
* @cfg {Ext.data.TreeStore/String} store The tree store to be used for this nested list.
*/
store: null,
/**
* @cfg {Ext.Container} detailContainer The container of the `detailCard`.
* @accessor
*/
detailContainer: undefined,
/**
* @cfg {Ext.Component} detailCard to provide a final card for leaf nodes.
* @accessor
*/
detailCard: null,
/**
* @cfg {Object} backButton The configuration for the back button used in the nested list.
*/
backButton: {
ui: 'back',
hidden: true
},
/**
* @cfg {Object} listConfig An optional config object which is merged with the default
* configuration used to create each nested list.
*/
listConfig: null,
// @private
lastNode: null,
// @private
lastActiveList: null
},
/**
* @event itemtap
* Fires when a node is tapped on.
* @param {Ext.dataview.NestedList} this
* @param {Ext.dataview.List} list The Ext.dataview.List that is currently active.
* @param {Number} index The index of the item tapped.
* @param {Ext.dom.Element} target The element tapped.
* @param {Ext.data.Record} record The record tapped.
* @param {Ext.event.Event} e The event object.
*/
/**
* @event itemdoubletap
* Fires when a node is double tapped on.
* @param {Ext.dataview.NestedList} this
* @param {Ext.dataview.List} list The Ext.dataview.List that is currently active.
* @param {Number} index The index of the item that was tapped.
* @param {Ext.dom.Element} target The element tapped.
* @param {Ext.data.Record} record The record tapped.
* @param {Ext.event.Event} e The event object.
*/
/**
* @event containertap
* Fires when a tap occurs and it is not on a template node.
* @param {Ext.dataview.NestedList} this
* @param {Ext.dataview.List} list The Ext.dataview.List that is currently active.
* @param {Ext.event.Event} e The raw event object.
*/
/**
* @event selectionchange
* Fires when the selected nodes change.
* @param {Ext.dataview.NestedList} this
* @param {Ext.dataview.List} list The Ext.dataview.List that is currently active.
* @param {Array} selections Array of the selected nodes.
*/
/**
* @event beforeselectionchange
* Fires before a selection is made.
* @param {Ext.dataview.NestedList} this
* @param {Ext.dataview.List} list The Ext.dataview.List that is currently active.
* @param {HTMLElement} node The node to be selected.
* @param {Array} selections Array of currently selected nodes.
* @deprecated 2.0.0 Please listen to the {@link #selectionchange} event with an order of `before` instead.
*/
/**
* @event listchange
* Fires when the user taps a list item.
* @param {Ext.dataview.NestedList} this
* @param {Object} listitem The new active list.
*/
/**
* @event leafitemtap
* Fires when the user taps a leaf list item.
* @param {Ext.dataview.NestedList} this
* @param {Ext.List} list The subList the item is on.
* @param {Number} index The index of the item tapped.
* @param {Ext.dom.Element} target The element tapped.
* @param {Ext.data.Record} record The record tapped.
* @param {Ext.event.Event} e The event.
*/
/**
* @event back
* @preventable doBack
* Fires when the user taps Back.
* @param {Ext.dataview.NestedList} this
* @param {HTMLElement} node The node to be selected.
* @param {Ext.dataview.List} lastActiveList The Ext.dataview.List that was last active.
* @param {Boolean} detailCardActive Flag set if the detail card is currently active.
*/
/**
* @event beforeload
* Fires before a request is made for a new data object.
* @param {Ext.dataview.NestedList} this
* @param {Ext.data.Store} store The store instance.
* @param {Ext.data.Operation} operation The Ext.data.Operation object that will be passed to the Proxy to
* load the Store.
*/
/**
* @event load
* Fires whenever records have been loaded into the store.
* @param {Ext.dataview.NestedList} this
* @param {Ext.data.Store} store The store instance.
* @param {Ext.util.Grouper[]} records An array of records.
* @param {Boolean} successful `true` if the operation was successful.
* @param {Ext.data.Operation} operation The associated operation.
*/
constructor: function (config) {
if (Ext.isObject(config)) {
if (config.getTitleTextTpl) {
this.getTitleTextTpl = config.getTitleTextTpl;
}
if (config.getItemTextTpl) {
this.getItemTextTpl = config.getItemTextTpl;
}
}
this.callParent(arguments);
},
onItemInteraction: function () {
if (this.isGoingTo) {
return false;
}
},
applyDetailContainer: function (config) {
if (!config) {
config = this;
}
return config;
},
updateDetailContainer: function (newContainer, oldContainer) {
if (newContainer) {
newContainer.onBefore('activeitemchange', 'onBeforeDetailContainerChange', this);
newContainer.onAfter('activeitemchange', 'onDetailContainerChange', this);
}
},
onBeforeDetailContainerChange: function () {
this.isGoingTo = true;
},
onDetailContainerChange: function () {
this.isGoingTo = false;
},
/**
* Called when an list item has been tapped.
* @param {Ext.List} list The subList the item is on.
* @param {Number} index The id of the item tapped.
* @param {Ext.Element} target The list item tapped.
* @param {Ext.data.Record} record The record which as tapped.
* @param {Ext.event.Event} e The event.
*/
onItemTap: function (list, index, target, record, e) {
var me = this,
store = list.getStore(),
node = store.getAt(index);
me.fireEvent('itemtap', this, list, index, target, record, e);
if (node.isLeaf()) {
me.fireEvent('leafitemtap', this, list, index, target, record, e);
me.goToLeaf(node);
}
else {
this.goToNode(node);
}
},
onBeforeSelect: function () {
this.fireEvent.apply(this, [].concat('beforeselect', this, Array.prototype.slice.call(arguments)));
},
onContainerTap: function () {
this.fireEvent.apply(this, [].concat('containertap', this, Array.prototype.slice.call(arguments)));
},
onSelectionChange: function () {
this.fireEvent.apply(this, [].concat('selectionchange', this, Array.prototype.slice.call(arguments)));
},
onItemDoubleTap: function () {
this.fireEvent.apply(this, [].concat('itemdoubletap', this, Array.prototype.slice.call(arguments)));
},
onStoreBeforeLoad: function () {
var loadingText = this.getLoadingText(),
scrollable = this.getScrollable();
if (loadingText) {
this.setMasked({
xtype: 'loadmask',
message: loadingText
});
//disable scrolling while it is masked
if (scrollable) {
scrollable.getScroller().setDisabled(true);
}
}
this.fireEvent.apply(this, [].concat('beforeload', this, Array.prototype.slice.call(arguments)));
},
onStoreLoad: function (store, records, successful, operation) {
this.setMasked(false);
this.fireEvent.apply(this, [].concat('load', this, Array.prototype.slice.call(arguments)));
if (store.indexOf(this.getLastNode()) === -1) {
this.goToNode(store.getRoot());
}
},
/**
* Called when the backButton has been tapped.
*/
onBackTap: function () {
var me = this,
node = me.getLastNode(),
detailCard = me.getDetailCard(),
detailCardActive = detailCard && me.getActiveItem() == detailCard,
lastActiveList = me.getLastActiveList();
this.fireAction('back', [this, node, lastActiveList, detailCardActive], 'doBack');
},
doBack: function (me, node, lastActiveList, detailCardActive) {
var layout = me.getLayout(),
animation = (layout) ? layout.getAnimation() : null;
if (detailCardActive && lastActiveList) {
if (animation) {
animation.setReverse(true);
}
me.setActiveItem(lastActiveList);
me.setLastNode(node.parentNode);
me.syncToolbar();
}
else {
this.goToNode(node.parentNode);
}
},
updateData: function (data) {
if (!this.getStore()) {
this.setStore(new Ext.data.TreeStore({
root: data
}));
}
},
applyStore: function (store) {
if (store) {
if (Ext.isString(store)) {
// store id
store = Ext.data.StoreManager.get(store);
} else {
// store instance or store config
if (!(store instanceof Ext.data.TreeStore)) {
store = Ext.factory(store, Ext.data.TreeStore, null);
}
}
// <debug>
if (!store) {
Ext.Logger.warn("The specified Store cannot be found", this);
}
//</debug>
}
return store;
},
storeListeners: {
rootchange: 'onStoreRootChange',
load: 'onStoreLoad',
beforeload: 'onStoreBeforeLoad'
},
updateStore: function (newStore, oldStore) {
var me = this,
listeners = this.storeListeners;
listeners.scope = me;
if (oldStore && Ext.isObject(oldStore) && oldStore.isStore) {
if (oldStore.autoDestroy) {
oldStore.destroy();
}
oldStore.un(listeners);
}
if (newStore) {
me.goToNode(newStore.getRoot());
newStore.on(listeners);
}
},
onStoreRootChange: function (store, node) {
this.goToNode(node);
},
applyBackButton: function (config) {
return Ext.factory(config, Ext.Button, this.getBackButton());
},
applyDetailCard: function (config, oldDetailCard) {
if (config === null) {
return Ext.factory(config, Ext.Component, oldDetailCard);
} else {
return Ext.factory(config, Ext.Component);
}
},
updateBackButton: function (newButton, oldButton) {
if (newButton) {
var me = this;
newButton.on('tap', me.onBackTap, me);
newButton.setText(me.getBackText());
me.getToolbar().insert(0, newButton);
}
else if (oldButton) {
oldButton.destroy();
}
},
applyToolbar: function (config) {
return Ext.factory(config, Ext.TitleBar, this.getToolbar());
},
updateToolbar: function (newToolbar, oldToolbar) {
var me = this;
if (newToolbar) {
newToolbar.setTitle(me.getTitle());
if (!newToolbar.getParent()) {
me.add(newToolbar);
}
}
else if (oldToolbar) {
oldToolbar.destroy();
}
},
updateUseToolbar: function (newUseToolbar, oldUseToolbar) {
if (!newUseToolbar) {
this.setToolbar(false);
}
},
updateTitle: function (newTitle) {
var me = this,
toolbar = me.getToolbar();
if (toolbar) {
if (me.getUpdateTitleText()) {
toolbar.setTitle(newTitle);
}
}
},
/**
* Override this method to provide custom template rendering of individual
* nodes. The template will receive all data within the Record and will also
* receive whether or not it is a leaf node.
* @param {Ext.data.Record} node
* @return {String}
*/
getItemTextTpl: function (node) {
return '{' + this.getDisplayField() + '}';
},
/**
* Override this method to provide custom template rendering of titles/back
* buttons when {@link #useTitleAsBackText} is enabled.
* @param {Ext.data.Record} node
* @return {String}
*/
getTitleTextTpl: function (node) {
return '{' + this.getDisplayField() + '}';
},
/**
* @private
*/
renderTitleText: function (node, forBackButton) {
if (!node.titleTpl) {
node.titleTpl = Ext.create('Ext.XTemplate', this.getTitleTextTpl(node));
}
if (node.isRoot()) {
var initialTitle = this.getInitialConfig('title');
return (forBackButton && initialTitle === '') ? this.getInitialConfig('backText') : initialTitle;
}
return node.titleTpl.applyTemplate(node.data);
},
/**
* Method to handle going to a specific node within this nested list. Node must be part of the
* internal {@link #store}.
* @param {Ext.data.NodeInterface} node The specified node to navigate to.
*/
goToNode: function (node) {
if (!node) {
return;
}
var me = this,
activeItem = me.getActiveItem(),
detailCard = me.getDetailCard(),
detailCardActive = detailCard && me.getActiveItem() == detailCard,
reverse = me.goToNodeReverseAnimation(node),
firstList = me.firstList,
secondList = me.secondList,
layout = me.getLayout(),
animation = (layout) ? layout.getAnimation() : null,
list;
//if the node is a leaf, throw an error
if (node.isLeaf()) {
throw new Error('goToNode: passed a node which is a leaf.');
}
//if we are currently at the passed node, do nothing.
if (node == me.getLastNode() && !detailCardActive) {
return;
}
if (detailCardActive) {
if (animation) {
animation.setReverse(true);
}
list = me.getLastActiveList();
list.getStore().setNode(node);
node.expand();
me.setActiveItem(list);
}
else {
if (firstList && secondList) {
//firstList and secondList have both been created
activeItem = me.getActiveItem();
me.setLastActiveList(activeItem);
list = (activeItem == firstList) ? secondList : firstList;
list.getStore().setNode(node);
node.expand();
if (animation) {
animation.setReverse(reverse);
}
me.setActiveItem(list);
list.deselectAll();
}
else if (firstList) {
//only firstList has been created
me.setLastActiveList(me.getActiveItem());
me.setActiveItem(me.getList(node));
me.secondList = me.getActiveItem();
}
else {
//no lists have been created
me.setActiveItem(me.getList(node));
me.firstList = me.getActiveItem();
}
}
me.fireEvent('listchange', this, me.getActiveItem());
me.setLastNode(node);
me.syncToolbar();
},
/**
* The leaf you want to navigate to. You should pass a node instance.
* @param {Ext.data.NodeInterface} node The specified node to navigate to.
*/
goToLeaf: function (node) {
if (!node.isLeaf()) {
throw new Error('goToLeaf: passed a node which is not a leaf.');
}
var me = this,
card = me.getDetailCard(node),
container = me.getDetailContainer(),
sharedContainer = container == this,
layout = me.getLayout(),
animation = (layout) ? layout.getAnimation() : false;
if (card) {
if (container.getItems().indexOf(card) === -1) {
container.add(card);
}
if (sharedContainer) {
if (me.getActiveItem() instanceof Ext.dataview.List) {
me.setLastActiveList(me.getActiveItem());
}
me.setLastNode(node);
}
if (animation) {
animation.setReverse(false);
}
container.setActiveItem(card);
me.syncToolbar();
}
},
/**
* @private
* Method which updates the {@link #backButton} and {@link #toolbar} with the latest information from
* the current node.
*/
syncToolbar: function (forceDetail) {
var me = this,
detailCard = me.getDetailCard(),
node = me.getLastNode(),
detailActive = forceDetail || (detailCard && (me.getActiveItem() == detailCard)),
parentNode = (detailActive) ? node : node.parentNode,
backButton = me.getBackButton();
//show/hide the backButton, and update the backButton text, if one exists
if (backButton) {
backButton[parentNode ? 'show' : 'hide']();
if (parentNode && me.getUseTitleAsBackText()) {
backButton.setText(me.renderTitleText(node.parentNode, true));
}
}
if (node) {
me.setTitle(me.renderTitleText(node));
}
},
updateBackText: function (newText) {
this.getBackButton().setText(newText);
},
/**
* @private
* Returns `true` if the passed node should have a reverse animation from the previous current node.
* @param {Ext.data.NodeInterface} node
*/
goToNodeReverseAnimation: function (node) {
var me = this,
lastNode = me.getLastNode();
if (!lastNode) {
return false;
}
return (!lastNode.contains(node) && lastNode.isAncestor(node)) ? true : false;
},
/**
* @private
* Returns the list config for a specified node.
* @param {HTMLElement} node The node for the list config.
*/
getList: function (node) {
var me = this,
nodeStore = Ext.create('Ext.data.NodeStore', {
recursive: false,
node: node,
rootVisible: false,
model: me.getStore().getModel()
});
node.expand();
return Ext.Object.merge({
xtype: 'list',
pressedDelay: 250,
autoDestroy: true,
store: nodeStore,
onItemDisclosure: me.getOnItemDisclosure(),
allowDeselect: me.getAllowDeselect(),
variableHeights: false,
listeners: [
{ event: 'itemdoubletap', fn: 'onItemDoubleTap', scope: me },
{ event: 'itemtap', fn: 'onItemInteraction', scope: me, order: 'before'},
{ event: 'itemtouchstart', fn: 'onItemInteraction', scope: me, order: 'before'},
{ event: 'itemtap', fn: 'onItemTap', scope: me },
{ event: 'beforeselectionchange', fn: 'onBeforeSelect', scope: me },
{ event: 'containertap', fn: 'onContainerTap', scope: me },
{ event: 'selectionchange', fn: 'onSelectionChange', order: 'before', scope: me }
],
itemTpl: '<span<tpl if="leaf == true"> class="x-list-item-leaf"</tpl>>' + me.getItemTextTpl(node) + '</span>'
}, this.getListConfig());
}
}, function () {
//<deprecated product=touch since=2.0>
/**
* @member Ext.dataview.NestedList
* @method getSubList
* Returns the subList for a specified node.
* @removed 2.0.0
*/
Ext.deprecateMethod(this, 'getSubList', null, "Ext.dataview.NestedList.getSubList() has been removed");
/**
* @member Ext.dataview.NestedList
* @cfg {Number} clearSelectionDelay
* Number of milliseconds to show the highlight when going back in a list.
* @removed 2.0.0
*/
Ext.deprecateProperty(this, 'clearSelectionDelay', null, "Ext.dataview.NestedList.clearSelectionDelay has been removed");
//</deprecated>
});

View File

@@ -0,0 +1,311 @@
/**
* @private
*/
Ext.define('Ext.dataview.component.Container', {
extend: 'Ext.Container',
requires: [
'Ext.dataview.component.DataItem'
],
/**
* @event itemtouchstart
* Fires whenever an item is touched
* @param {Ext.dataview.component.Container} this
* @param {Ext.dataview.component.DataItem} item The item touched
* @param {Number} index The index of the item touched
* @param {Ext.EventObject} e The event object
*/
/**
* @event itemtouchmove
* Fires whenever an item is moved
* @param {Ext.dataview.component.Container} this
* @param {Ext.dataview.component.DataItem} item The item moved
* @param {Number} index The index of the item moved
* @param {Ext.EventObject} e The event object
*/
/**
* @event itemtouchend
* Fires whenever an item is touched
* @param {Ext.dataview.component.Container} this
* @param {Ext.dataview.component.DataItem} item The item touched
* @param {Number} index The index of the item touched
* @param {Ext.EventObject} e The event object
*/
/**
* @event itemtap
* Fires whenever an item is tapped
* @param {Ext.dataview.component.Container} this
* @param {Ext.dataview.component.DataItem} item The item tapped
* @param {Number} index The index of the item tapped
* @param {Ext.EventObject} e The event object
*/
/**
* @event itemtaphold
* Fires whenever an item is tapped
* @param {Ext.dataview.component.Container} this
* @param {Ext.dataview.component.DataItem} item The item tapped
* @param {Number} index The index of the item tapped
* @param {Ext.EventObject} e The event object
*/
/**
* @event itemsingletap
* Fires whenever an item is doubletapped
* @param {Ext.dataview.component.Container} this
* @param {Ext.dataview.component.DataItem} item The item singletapped
* @param {Number} index The index of the item singletapped
* @param {Ext.EventObject} e The event object
*/
/**
* @event itemdoubletap
* Fires whenever an item is doubletapped
* @param {Ext.dataview.component.Container} this
* @param {Ext.dataview.component.DataItem} item The item doubletapped
* @param {Number} index The index of the item doubletapped
* @param {Ext.EventObject} e The event object
*/
/**
* @event itemswipe
* Fires whenever an item is swiped
* @param {Ext.dataview.component.Container} this
* @param {Ext.dataview.component.DataItem} item The item swiped
* @param {Number} index The index of the item swiped
* @param {Ext.EventObject} e The event object
*/
constructor: function() {
this.itemCache = [];
this.callParent(arguments);
},
//@private
doInitialize: function() {
this.innerElement.on({
touchstart: 'onItemTouchStart',
touchend: 'onItemTouchEnd',
tap: 'onItemTap',
taphold: 'onItemTapHold',
touchmove: 'onItemTouchMove',
singletap: 'onItemSingleTap',
doubletap: 'onItemDoubleTap',
swipe: 'onItemSwipe',
delegate: '> .' + Ext.baseCSSPrefix + 'data-item',
scope: this
});
},
//@private
initialize: function() {
this.callParent();
this.doInitialize();
},
onItemTouchStart: function(e) {
var me = this,
target = e.getTarget(),
item = Ext.getCmp(target.id);
item.on({
touchmove: 'onItemTouchMove',
scope : me,
single: true
});
me.fireEvent('itemtouchstart', me, item, me.indexOf(item), e);
},
onItemTouchMove: function(e) {
var me = this,
target = e.getTarget(),
item = Ext.getCmp(target.id);
me.fireEvent('itemtouchmove', me, item, me.indexOf(item), e);
},
onItemTouchEnd: function(e) {
var me = this,
target = e.getTarget(),
item = Ext.getCmp(target.id);
item.un({
touchmove: 'onItemTouchMove',
scope : me
});
me.fireEvent('itemtouchend', me, item, me.indexOf(item), e);
},
onItemTap: function(e) {
var me = this,
target = e.getTarget(),
item = Ext.getCmp(target.id);
me.fireEvent('itemtap', me, item, me.indexOf(item), e);
},
onItemTapHold: function(e) {
var me = this,
target = e.getTarget(),
item = Ext.getCmp(target.id);
me.fireEvent('itemtaphold', me, item, me.indexOf(item), e);
},
onItemSingleTap: function(e) {
var me = this,
target = e.getTarget(),
item = Ext.getCmp(target.id);
me.fireEvent('itemsingletap', me, item, me.indexOf(item), e);
},
onItemDoubleTap: function(e) {
var me = this,
target = e.getTarget(),
item = Ext.getCmp(target.id);
me.fireEvent('itemdoubletap', me, item, me.indexOf(item), e);
},
onItemSwipe: function(e) {
var me = this,
target = e.getTarget(),
item = Ext.getCmp(target.id);
me.fireEvent('itemswipe', me, item, me.indexOf(item), e);
},
moveItemsToCache: function(from, to) {
var me = this,
dataview = me.dataview,
maxItemCache = dataview.getMaxItemCache(),
items = me.getViewItems(),
itemCache = me.itemCache,
cacheLn = itemCache.length,
pressedCls = dataview.getPressedCls(),
selectedCls = dataview.getSelectedCls(),
i = to - from,
item;
for (; i >= 0; i--) {
item = items[from + i];
if (cacheLn !== maxItemCache) {
me.remove(item, false);
item.removeCls([pressedCls, selectedCls]);
itemCache.push(item);
cacheLn++;
}
else {
item.destroy();
}
}
if (me.getViewItems().length == 0) {
this.dataview.showEmptyText();
}
},
moveItemsFromCache: function(records) {
var me = this,
dataview = me.dataview,
store = dataview.getStore(),
ln = records.length,
xtype = dataview.getDefaultType(),
itemConfig = dataview.getItemConfig(),
itemCache = me.itemCache,
cacheLn = itemCache.length,
items = [],
i, item, record;
if (ln) {
dataview.hideEmptyText();
}
for (i = 0; i < ln; i++) {
records[i]._tmpIndex = store.indexOf(records[i]);
}
Ext.Array.sort(records, function(record1, record2) {
return record1._tmpIndex > record2._tmpIndex ? 1 : -1;
});
for (i = 0; i < ln; i++) {
record = records[i];
if (cacheLn) {
cacheLn--;
item = itemCache.pop();
this.updateListItem(record, item);
}
else {
item = me.getDataItemConfig(xtype, record, itemConfig);
}
item = this.insert(record._tmpIndex, item);
delete record._tmpIndex;
}
return items;
},
getViewItems: function() {
return this.getInnerItems();
},
updateListItem: function(record, item) {
if (item.updateRecord) {
if (item.getRecord() === record) {
item.updateRecord(record);
} else {
item.setRecord(record);
}
}
},
getDataItemConfig: function(xtype, record, itemConfig) {
var dataview = this.dataview,
dataItemConfig = {
xtype: xtype,
record: record,
itemCls: dataview.getItemCls(),
defaults: itemConfig,
dataview: dataview
};
return Ext.merge(dataItemConfig, itemConfig);
},
doRemoveItemCls: function(cls) {
var items = this.getViewItems(),
ln = items.length,
i = 0;
for (; i < ln; i++) {
items[i].removeCls(cls);
}
},
doAddItemCls: function(cls) {
var items = this.getViewItems(),
ln = items.length,
i = 0;
for (; i < ln; i++) {
items[i].addCls(cls);
}
},
updateAtNewIndex: function(oldIndex, newIndex, record) {
this.moveItemsToCache(oldIndex, oldIndex);
this.moveItemsFromCache([record]);
},
destroy: function() {
var me = this,
itemCache = me.itemCache,
ln = itemCache.length,
i = 0;
for (; i < ln; i++) {
itemCache[i].destroy();
}
this.callParent();
}
});

View File

@@ -0,0 +1,154 @@
/**
* A DataItem is a container for {@link Ext.dataview.DataView} with useComponents: true. It ties together
* {@link Ext.data.Model records} to its contained Components via a {@link #dataMap dataMap} configuration.
*
* For example, lets say you have a `text` configuration which, when applied, gets turned into an instance of an
* Ext.Component. We want to update the {@link #html} of a sub-component when the 'text' field of the record gets
* changed.
*
* As you can see below, it is simply a matter of setting the key of the object to be the getter of the config
* (getText), and then give that property a value of an object, which then has 'setHtml' (the html setter) as the key,
* and 'text' (the field name) as the value. You can continue this for a as many sub-components as you wish.
*
* dataMap: {
* // When the record is updated, get the text configuration, and
* // call {@link #setHtml} with the 'text' field of the record.
* getText: {
* setHtml: 'text'
* },
*
* // When the record is updated, get the userName configuration, and
* // call {@link #setHtml} with the 'from_user' field of the record.
* getUserName: {
* setHtml: 'from_user'
* },
*
* // When the record is updated, get the avatar configuration, and
* // call `setSrc` with the 'profile_image_url' field of the record.
* getAvatar: {
* setSrc: 'profile_image_url'
* }
* }
*/
Ext.define('Ext.dataview.component.DataItem', {
extend: 'Ext.Container',
xtype : 'dataitem',
config: {
baseCls: Ext.baseCSSPrefix + 'data-item',
defaultType: 'component',
/**
* @cfg {Ext.data.Model} record The model instance of this DataItem. It is controlled by the Component DataView.
* @accessor
*/
record: null,
/**
* @cfg {String} itemCls
* An additional CSS class to apply to items within the DataView.
* @accessor
*/
itemCls: null,
/**
* @cfg dataMap
* The dataMap allows you to map {@link #record} fields to specific configurations in this component.
*
* For example, lets say you have a `text` configuration which, when applied, gets turned into an instance of an Ext.Component.
* We want to update the {@link #html} of this component when the 'text' field of the record gets changed.
* For example:
*
* dataMap: {
* getText: {
* setHtml: 'text'
* }
* }
*
* In this example, it is simply a matter of setting the key of the object to be the getter of the config (`getText`), and then give that
* property a value of an object, which then has `setHtml` (the html setter) as the key, and `text` (the field name) as the value.
*/
dataMap: {},
/*
* @private dataview
*/
dataview: null,
items: [{
xtype: 'component'
}]
},
updateBaseCls: function(newBaseCls, oldBaseCls) {
var me = this;
me.callParent(arguments);
},
updateItemCls: function(newCls, oldCls) {
if (oldCls) {
this.removeCls(oldCls);
}
if (newCls) {
this.addCls(newCls);
}
},
doMapData: function(dataMap, data, item) {
var componentName, component, setterMap, setterName;
for (componentName in dataMap) {
setterMap = dataMap[componentName];
component = this[componentName]();
if (component) {
for (setterName in setterMap) {
if (data && component[setterName] && data[setterMap[setterName]]) {
component[setterName](data[setterMap[setterName]]);
}
}
}
}
if (item) {
// Bypassing setter because sometimes we pass the same object (different properties)
item.updateData(data);
}
},
/**
* Updates this container's child items, passing through the `dataMap`.
* @param newRecord
* @private
*/
updateRecord: function(newRecord) {
if (!newRecord) {
return;
}
this._record = newRecord;
var me = this,
dataview = me.dataview || this.getDataview(),
data = dataview.prepareData(newRecord.getData(true), dataview.getStore().indexOf(newRecord), newRecord),
items = me.getItems(),
item = items.first(),
dataMap = me.getDataMap();
if (!item) {
return;
}
if (dataMap) {
this.doMapData(dataMap, data, item);
}
/**
* @event updatedata
* Fires whenever the data of the DataItem is updated.
* @param {Ext.dataview.component.DataItem} this The DataItem instance.
* @param {Object} newData The new data.
*/
me.fireEvent('updatedata', me, data);
}
});

View File

@@ -0,0 +1,143 @@
/**
* A DataItem is a container for {@link Ext.dataview.DataView} with useComponents: true. It ties together
* {@link Ext.data.Model records} to its contained Components via a {@link #dataMap dataMap} configuration.
*
* For example, lets say you have a `text` configuration which, when applied, gets turned into an instance of an
* Ext.Component. We want to update the {@link #html} of a sub-component when the 'text' field of the record gets
* changed.
*
* As you can see below, it is simply a matter of setting the key of the object to be the getter of the config
* (`getText`), and then give that property a value of an object, which then has 'setHtml' (the `html` setter) as the key,
* and 'text' (the field name) as the value. You can continue this for a as many sub-components as you wish.
*
* dataMap: {
* // When the record is updated, get the text configuration, and
* // call {@link #setHtml} with the 'text' field of the record.
* getText: {
* setHtml: 'text'
* },
*
* // When the record is updated, get the userName configuration, and
* // call {@link #setHtml} with the 'from_user' field of the record.
* getUserName: {
* setHtml: 'from_user'
* },
*
* // When the record is updated, get the avatar configuration, and
* // call `setSrc` with the 'profile_image_url' field of the record.
* getAvatar: {
* setSrc: 'profile_image_url'
* }
* }
*/
Ext.define('Ext.dataview.component.ListItem', {
extend: 'Ext.dataview.component.DataItem',
xtype : 'listitem',
config: {
baseCls: Ext.baseCSSPrefix + 'list-item',
dataMap: null,
body: {
xtype: 'component',
cls: 'x-list-item-body'
},
disclosure: {
xtype: 'component',
cls: 'x-list-disclosure',
hidden: true,
docked: 'right'
},
header: {
xtype: 'component',
cls: 'x-list-header',
html: ' ',
hidden: true
},
tpl: null,
items: null
},
applyBody: function(body) {
if (body && !body.isComponent) {
body = Ext.factory(body, Ext.Component, this.getBody());
}
return body;
},
updateBody: function(body, oldBody) {
if (body) {
this.add(body);
} else if (oldBody) {
oldBody.destroy();
}
},
applyHeader: function(header) {
if (header && !header.isComponent) {
header = Ext.factory(header, Ext.Component, this.getHeader());
}
return header;
},
updateHeader: function(header, oldHeader) {
if (header) {
this.element.getFirstChild().insertFirst(header.element);
} else if (oldHeader) {
oldHeader.destroy();
}
},
applyDisclosure: function(disclosure) {
if (disclosure && !disclosure.isComponent) {
disclosure = Ext.factory(disclosure, Ext.Component, this.getDisclosure());
}
return disclosure;
},
updateDisclosure: function(disclosure, oldDisclosure) {
if (disclosure) {
this.add(disclosure);
} else if (oldDisclosure) {
oldDisclosure.destroy();
}
},
updateTpl: function(tpl) {
this.getBody().setTpl(tpl);
},
updateRecord: function(record) {
var me = this,
dataview = me.dataview || this.getDataview(),
data = record && dataview.prepareData(record.getData(true), dataview.getStore().indexOf(record), record),
dataMap = me.getDataMap(),
body = this.getBody(),
disclosure = this.getDisclosure();
me._record = record;
if (dataMap) {
me.doMapData(dataMap, data, body);
} else if (body) {
body.updateData(data || null);
}
if (disclosure && record && dataview.getOnItemDisclosure()) {
var disclosureProperty = dataview.getDisclosureProperty();
disclosure[(data.hasOwnProperty(disclosureProperty) && data[disclosureProperty] === false) ? 'hide' : 'show']();
}
/**
* @event updatedata
* Fires whenever the data of the DataItem is updated.
* @param {Ext.dataview.component.DataItem} this The DataItem instance.
* @param {Object} newData The new data.
*/
me.fireEvent('updatedata', me, data);
}
});

View File

@@ -0,0 +1,313 @@
/**
* @private
*/
Ext.define('Ext.dataview.element.Container', {
extend: 'Ext.Component',
/**
* @event itemtouchstart
* Fires whenever an item is touched
* @param {Ext.dataview.element.Container} this
* @param {Ext.dom.Element} item The item touched
* @param {Number} index The index of the item touched
* @param {Ext.EventObject} e The event object
*/
/**
* @event itemtouchmove
* Fires whenever an item is moved
* @param {Ext.dataview.element.Container} this
* @param {Ext.dom.Element} item The item moved
* @param {Number} index The index of the item moved
* @param {Ext.EventObject} e The event object
*/
/**
* @event itemtouchend
* Fires whenever an item is touched
* @param {Ext.dataview.element.Container} this
* @param {Ext.dom.Element} item The item touched
* @param {Number} index The index of the item touched
* @param {Ext.EventObject} e The event object
*/
/**
* @event itemtap
* Fires whenever an item is tapped
* @param {Ext.dataview.element.Container} this
* @param {Ext.dom.Element} item The item tapped
* @param {Number} index The index of the item tapped
* @param {Ext.EventObject} e The event object
*/
/**
* @event itemtaphold
* Fires whenever an item is tapped
* @param {Ext.dataview.element.Container} this
* @param {Ext.dom.Element} item The item tapped
* @param {Number} index The index of the item tapped
* @param {Ext.EventObject} e The event object
*/
/**
* @event itemsingletap
* Fires whenever an item is singletapped
* @param {Ext.dataview.element.Container} this
* @param {Ext.dom.Element} item The item singletapped
* @param {Number} index The index of the item singletapped
* @param {Ext.EventObject} e The event object
*/
/**
* @event itemdoubletap
* Fires whenever an item is doubletapped
* @param {Ext.dataview.element.Container} this
* @param {Ext.dom.Element} item The item doubletapped
* @param {Number} index The index of the item doubletapped
* @param {Ext.EventObject} e The event object
*/
/**
* @event itemswipe
* Fires whenever an item is swiped
* @param {Ext.dataview.element.Container} this
* @param {Ext.dom.Element} item The item swiped
* @param {Number} index The index of the item swiped
* @param {Ext.EventObject} e The event object
*/
doInitialize: function() {
this.element.on({
touchstart: 'onItemTouchStart',
touchend: 'onItemTouchEnd',
tap: 'onItemTap',
taphold: 'onItemTapHold',
touchmove: 'onItemTouchMove',
singletap: 'onItemSingleTap',
doubletap: 'onItemDoubleTap',
swipe: 'onItemSwipe',
delegate: '> div',
scope: this
});
},
//@private
initialize: function() {
this.callParent();
this.doInitialize();
},
updateBaseCls: function(newBaseCls, oldBaseCls) {
var me = this;
me.callParent([newBaseCls + '-container', oldBaseCls]);
},
onItemTouchStart: function(e) {
var me = this,
target = e.getTarget(),
index = me.getViewItems().indexOf(target);
Ext.get(target).on({
touchmove: 'onItemTouchMove',
scope : me,
single: true
});
me.fireEvent('itemtouchstart', me, Ext.get(target), index, e);
},
onItemTouchEnd: function(e) {
var me = this,
target = e.getTarget(),
index = me.getViewItems().indexOf(target);
Ext.get(target).un({
touchmove: 'onItemTouchMove',
scope : me
});
me.fireEvent('itemtouchend', me, Ext.get(target), index, e);
},
onItemTouchMove: function(e) {
var me = this,
target = e.getTarget(),
index = me.getViewItems().indexOf(target);
me.fireEvent('itemtouchmove', me, Ext.get(target), index, e);
},
onItemTap: function(e) {
var me = this,
target = e.getTarget(),
index = me.getViewItems().indexOf(target);
me.fireEvent('itemtap', me, Ext.get(target), index, e);
},
onItemTapHold: function(e) {
var me = this,
target = e.getTarget(),
index = me.getViewItems().indexOf(target);
me.fireEvent('itemtaphold', me, Ext.get(target), index, e);
},
onItemDoubleTap: function(e) {
var me = this,
target = e.getTarget(),
index = me.getViewItems().indexOf(target);
me.fireEvent('itemdoubletap', me, Ext.get(target), index, e);
},
onItemSingleTap: function(e) {
var me = this,
target = e.getTarget(),
index = me.getViewItems().indexOf(target);
me.fireEvent('itemsingletap', me, Ext.get(target), index, e);
},
onItemSwipe: function(e) {
var me = this,
target = e.getTarget(),
index = me.getViewItems().indexOf(target);
me.fireEvent('itemswipe', me, Ext.get(target), index, e);
},
updateListItem: function(record, item) {
var me = this,
dataview = me.dataview,
store = dataview.getStore(),
index = store.indexOf(record),
data = dataview.prepareData(record.getData(true), index, record);
data.xcount = store.getCount();
data.xindex = typeof data.xindex === 'number' ? data.xindex : index;
item.innerHTML = dataview.getItemTpl().apply(data);
},
addListItem: function(index, record) {
var me = this,
dataview = me.dataview,
store = dataview.getStore(),
data = dataview.prepareData(record.getData(true), index, record),
element = me.element,
childNodes = element.dom.childNodes,
ln = childNodes.length,
wrapElement;
data.xcount = typeof data.xcount === 'number' ? data.xcount : store.getCount();
data.xindex = typeof data.xindex === 'number' ? data.xindex : index;
wrapElement = Ext.Element.create(this.getItemElementConfig(index, data));
if (!ln || index == ln) {
wrapElement.appendTo(element);
} else {
wrapElement.insertBefore(childNodes[index]);
}
},
getItemElementConfig: function(index, data) {
var dataview = this.dataview,
itemCls = dataview.getItemCls(),
cls = dataview.getBaseCls() + '-item';
if (itemCls) {
cls += ' ' + itemCls;
}
return {
cls: cls,
html: dataview.getItemTpl().apply(data)
};
},
doRemoveItemCls: function(cls) {
var elements = this.getViewItems(),
ln = elements.length,
i = 0;
for (; i < ln; i++) {
Ext.fly(elements[i]).removeCls(cls);
}
},
doAddItemCls: function(cls) {
var elements = this.getViewItems(),
ln = elements.length,
i = 0;
for (; i < ln; i++) {
Ext.fly(elements[i]).addCls(cls);
}
},
// Remove
moveItemsToCache: function(from, to) {
var me = this,
items = me.getViewItems(),
i = to - from,
item;
for (; i >= 0; i--) {
item = items[from + i];
Ext.get(item).destroy();
}
if (me.getViewItems().length == 0) {
this.dataview.showEmptyText();
}
},
// Add
moveItemsFromCache: function(records) {
var me = this,
dataview = me.dataview,
store = dataview.getStore(),
ln = records.length,
i, record;
if (ln) {
dataview.hideEmptyText();
}
for (i = 0; i < ln; i++) {
records[i]._tmpIndex = store.indexOf(records[i]);
}
Ext.Array.sort(records, function(record1, record2) {
return record1._tmpIndex > record2._tmpIndex ? 1 : -1;
});
for (i = 0; i < ln; i++) {
record = records[i];
me.addListItem(record._tmpIndex, record);
delete record._tmpIndex;
}
},
// Transform ChildNodes into a proper Array so we can do indexOf...
getViewItems: function() {
return Array.prototype.slice.call(this.element.dom.childNodes);
},
updateAtNewIndex: function(oldIndex, newIndex, record) {
this.moveItemsToCache(oldIndex, oldIndex);
this.moveItemsFromCache([record]);
},
destroy: function() {
var elements = this.getViewItems(),
ln = elements.length,
i = 0;
for (; i < ln; i++) {
Ext.get(elements[i]).destroy();
}
this.callParent();
}
});

View File

@@ -0,0 +1,135 @@
/**
* @private
*/
Ext.define('Ext.dataview.element.List', {
extend: 'Ext.dataview.element.Container',
updateBaseCls: function(newBaseCls) {
var me = this;
me.itemClsShortCache = newBaseCls + '-item';
me.headerClsShortCache = newBaseCls + '-header';
me.headerClsCache = '.' + me.headerClsShortCache;
me.headerItemClsShortCache = newBaseCls + '-header-item';
me.footerClsShortCache = newBaseCls + '-footer-item';
me.footerClsCache = '.' + me.footerClsShortCache;
me.labelClsShortCache = newBaseCls + '-item-label';
me.labelClsCache = '.' + me.labelClsShortCache;
me.disclosureClsShortCache = newBaseCls + '-disclosure';
me.disclosureClsCache = '.' + me.disclosureClsShortCache;
me.iconClsShortCache = newBaseCls + '-icon';
me.iconClsCache = '.' + me.iconClsShortCache;
this.callParent(arguments);
},
hiddenDisplayCache: Ext.baseCSSPrefix + 'hidden-display',
getItemElementConfig: function(index, data) {
var me = this,
dataview = me.dataview,
itemCls = dataview.getItemCls(),
cls = me.itemClsShortCache,
config, iconSrc;
if (itemCls) {
cls += ' ' + itemCls;
}
config = {
cls: cls,
children: [{
cls: me.labelClsShortCache,
html: dataview.getItemTpl().apply(data)
}]
};
if (dataview.getIcon()) {
iconSrc = data.iconSrc;
config.children.push({
cls: me.iconClsShortCache,
style: 'background-image: ' + iconSrc ? 'url("' + newSrc + '")' : ''
});
}
if (dataview.getOnItemDisclosure()) {
config.children.push({
cls: me.disclosureClsShortCache + ' ' + ((data[dataview.getDisclosureProperty()] === false) ? me.hiddenDisplayCache : '')
});
}
return config;
},
updateListItem: function(record, item) {
var me = this,
dataview = me.dataview,
extItem = Ext.fly(item),
innerItem = extItem.down(me.labelClsCache, true),
data = dataview.prepareData(record.getData(true), dataview.getStore().indexOf(record), record),
disclosureProperty = dataview.getDisclosureProperty(),
hasDisclosureProperty = data && data.hasOwnProperty(disclosureProperty),
iconSrc = data && data.hasOwnProperty('iconSrc'),
disclosureEl, iconEl;
innerItem.innerHTML = dataview.getItemTpl().apply(data);
if (hasDisclosureProperty) {
disclosureEl = extItem.down(me.disclosureClsCache);
disclosureEl[data[disclosureProperty] === false ? 'addCls' : 'removeCls'](me.hiddenDisplayCache);
}
if (dataview.getIcon()) {
iconEl = extItem.down(me.iconClsCache, true);
iconEl.style.backgroundImage = iconSrc ? 'url("' + iconSrc + '")' : '';
}
},
doRemoveHeaders: function() {
var me = this,
headerClsShortCache = me.headerItemClsShortCache,
existingHeaders = me.element.query(me.headerClsCache),
existingHeadersLn = existingHeaders.length,
i = 0,
item;
for (; i < existingHeadersLn; i++) {
item = existingHeaders[i];
Ext.fly(item.parentNode).removeCls(headerClsShortCache);
Ext.get(item).destroy();
}
},
doRemoveFooterCls: function() {
var me = this,
footerClsShortCache = me.footerClsShortCache,
existingFooters = me.element.query(me.footerClsCache),
existingFootersLn = existingFooters.length,
i = 0;
for (; i < existingFootersLn; i++) {
Ext.fly(existingFooters[i]).removeCls(footerClsShortCache);
}
},
doAddHeader: function(item, html) {
item = Ext.fly(item);
if (html) {
item.insertFirst(Ext.Element.create({
cls: this.headerClsShortCache,
html: html
}));
}
item.addCls(this.headerItemClsShortCache);
},
destroy: function() {
this.doRemoveHeaders();
this.callParent();
}
});