/**
* @class Ext.IndexBar
* @extends Ext.DataPanel
* IndexBar is a component used to display a list of data (primarily an {@link #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.
*
* Screenshot:
*
*
* Example code:
* Here is an example of the usage in a {@link Ext.List}:
*
Ext.regModel('Contact', {
fields: ['firstName', 'lastName']
});
var store = new Ext.data.JsonStore({
model : 'Contact',
sorters: 'lastName',
getGroupString : 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: 'Michael', lastName: 'Mullany'},
{firstName: 'Abraham', lastName: 'Elias'},
{firstName: 'Jay', lastName: 'Robinson'},
{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: 'Michael', lastName: 'Mullany'},
{firstName: 'Abraham', lastName: 'Elias'},
{firstName: 'Jay', lastName: 'Robinson'}
]
});
var list = new Ext.List({
tpl: '<tpl for="."><div class="contact">{firstName} <strong>{lastName}</strong></div></tpl>',
itemSelector: 'div.contact',
singleSelect: true,
grouped : true,
indexBar : true,
store: store,
floating : true,
width : 350,
height : 370,
centered : true,
modal : true,
hideOnMaskTap: false
});
list.show();
*
* Alternatively you can initate the {@link Ext.IndexBar} component manually in a custom component by using something
* similar to the following example:
*
*
var indexBar = new Ext.IndexBar({
dock : 'right',
overlay : true,
alphabet: true
});
*
* @constructor
* Create a new IndexBar
* @param {Object} config The config object
* @xtype indexbar
*/
Ext.IndexBar = Ext.extend(Ext.DataView, {
/**
* @cfg {String} componentCls Base CSS class
* Defaults to 'x-indexbar'
*/
componentCls: 'x-indexbar',
/**
* @cfg {String} direction Layout direction, can be either 'vertical' or 'horizontal'
* Defaults to 'vertical'
*/
direction: 'vertical',
/**
* @cfg {String} tpl Template for items
*/
tpl: '{value}
',
/**
* @cfg {String} itemSelector Required. A simple CSS selector (e.g. div.x-indexbar-item for items
*/
itemSelector: 'div.x-indexbar-item',
/**
* @cfg {Array} letters
* The letters to show on the index bar. Defaults to the English alphabet, A-Z.
*/
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'],
/**
* @cfg {String} listPrefix
* The prefix string to be appended at the beginning of the list. E.g: useful to add a "#" prefix before numbers
*/
listPrefix: '',
/**
* @cfg {Boolean} alphabet true to use the {@link #letters} property to show a list of the alphabet. Should not be used
* in conjunction with {@link #store}.
*/
/**
* @cfg {Ext.data.Store} store
* The store to be used for displaying data on the index bar. The store model must have a value field when using the
* default {@link #tpl}. If no {@link #store} is defined, it will create a store using the IndexBarModel model.
*/
// We dont want the body of this component to be sized by a DockLayout, thus we set the layout to be an autocomponent layout.
componentLayout: 'autocomponent',
scroll: false,
// @private
initComponent : function() {
// No docking and no sizing of body!
this.componentLayout = this.getComponentLayout();
if (!this.store) {
this.store = new Ext.data.Store({
model: 'IndexBarModel'
});
}
if (this.alphabet == true) {
this.ui = this.ui || 'alphabet';
}
if (this.direction == 'horizontal') {
this.horizontal = true;
}
else {
this.vertical = true;
}
this.addEvents(
/**
* @event index
* Fires when an item in the index bar display has been tapped
* @param {Ext.data.Model} record The record tapped on the indexbar
* @param {HTMLElement} target The node on the indexbar that has been tapped
* @param {Number} index The index of the record tapped on the indexbar
*/
'index'
);
Ext.apply(this.renderData, {
componentCls: this.componentCls
});
Ext.apply(this.renderSelectors, {
body: '.' + this.componentCls + '-body'
});
Ext.IndexBar.superclass.initComponent.call(this);
},
renderTpl : [''],
getTargetEl : function() {
return this.body;
},
// @private
afterRender : function() {
Ext.IndexBar.superclass.afterRender.call(this);
if (this.alphabet === true) {
this.loadAlphabet();
}
if (this.vertical) {
this.el.addCls(this.componentCls + '-vertical');
}
else if (this.horizontal) {
this.el.addCls(this.componentCls + '-horizontal');
}
},
// @private
loadAlphabet : function() {
var letters = this.letters,
len = letters.length,
data = [],
i, letter;
for (i = 0; i < len; i++) {
letter = letters[i];
data.push({key: letter.toLowerCase(), value: letter});
}
this.store.loadData(data);
},
/**
* Refreshes the view by reloading the data from the store and re-rendering the template.
*/
refresh: function() {
var el = this.getTargetEl(),
records = this.store.getRange();
el.update('');
if (records.length < 1) {
if (!this.deferEmptyText || this.hasSkippedEmptyText) {
el.update(this.emptyText);
}
this.all.clear();
} else {
this.tpl.overwrite(el, this.collectData(records, 0));
this.all.fill(Ext.query(this.itemSelector, el.dom));
this.updateIndexes(0);
}
this.hasSkippedEmptyText = true;
this.fireEvent('refresh');
},
collectData : function() {
var data = Ext.IndexBar.superclass.collectData.apply(this, arguments);
if (this.listPrefix.length > 0) {
data.unshift({
key: '',
value: this.listPrefix
});
}
return data;
},
// @private
initEvents : function() {
Ext.IndexBar.superclass.initEvents.call(this);
this.mon(this.el, {
touchstart: this.onTouchStart,
touchend: this.onTouchEnd,
touchmove: this.onTouchMove,
scope: this
});
},
// @private
onTouchStart : function(e, t) {
e.stopEvent();
this.el.addCls(this.componentCls + '-pressed');
this.pageBox = this.el.getPageBox();
this.onTouchMove(e);
},
// @private
onTouchEnd : function(e, t) {
e.stopEvent();
this.el.removeCls(this.componentCls + '-pressed');
},
// @private
onTouchMove : function(e) {
e.stopEvent();
// Temporary fix since touchstart does not have these properties passed from the gesture Manager
if (!e.hasOwnProperty('pageY')) {
if (e.hasOwnProperty('event'))
e = e.event;
e = (e.touches && e.touches.length > 0) ? e.touches[0] : e;
}
var target,
record,
pageBox = this.pageBox;
if (!pageBox) {
pageBox = this.pageBox = this.el.getPageBox();
}
if (this.vertical) {
if (e.pageY > pageBox.bottom || e.pageY < pageBox.top) {
return;
}
target = Ext.Element.fromPoint(pageBox.left + (pageBox.width / 2), e.pageY);
}
else if (this.horizontal) {
if (e.pageX > pageBox.right || e.pageX < pageBox.left) {
return;
}
target = Ext.Element.fromPoint(e.pageX, pageBox.top + (pageBox.height / 2));
}
if (target) {
record = this.getRecord(target.dom);
if (record) {
this.fireEvent('index', record, target, this.indexOf(target));
}
}
},
/**
* Method to determine whether this Sortable is currently disabled.
* @return {Boolean} the disabled state of this Sortable.
*/
isVertical : function() {
return this.vertical;
},
/**
* Method to determine whether this Sortable is currently sorting.
* @return {Boolean} the sorting state of this Sortable.
*/
isHorizontal : function() {
return this.horizontal;
}
});
Ext.reg('indexbar', Ext.IndexBar);
Ext.regModel('IndexBarModel', {
fields: ['key', 'value']
});