Форк Rambox
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

235 lines
7.3 KiB

/**
* A specialized {@link Ext.util.KeyNav} implementation for navigating a {@link Ext.view.BoundList} using
* the keyboard. The up, down, pageup, pagedown, home, and end keys move the active highlight
* through the list. The enter key invokes the selection model's select action using the highlighted item.
*/
Ext.define('Ext.view.BoundListKeyNav', {
extend: 'Ext.view.NavigationModel',
alias: 'view.navigation.boundlist',
/**
* @cfg {Ext.view.BoundList} boundList (required)
* The {@link Ext.view.BoundList} instance for which key navigation will be managed.
*/
navigateOnSpace: true,
initKeyNav: function(view) {
var me = this,
field = view.pickerField;
// Add the regular KeyNav to the view.
// Unless it's already been done (we may have to defer a call until the field is rendered.
if (!me.keyNav) {
me.callParent([view]);
// Add ESC handling to the View's KeyMap to caollapse the field
me.keyNav.map.addBinding({
key: Ext.event.Event.ESC,
fn: me.onKeyEsc,
scope: me
});
}
// BoundLists must be able to function standalone with no bound field
if (!field) {
return;
}
if (!field.rendered) {
field.on('render', Ext.Function.bind(me.initKeyNav, me, [view], 0), me, {single: true});
return;
}
// BoundListKeyNav also listens for key events from the field to which it is bound.
me.fieldKeyNav = new Ext.util.KeyNav({
disabled: true,
target: field.inputEl,
forceKeyDown: true,
up: me.onKeyUp,
down: me.onKeyDown,
right: me.onKeyRight,
left: me.onKeyLeft,
pageDown: me.onKeyPageDown,
pageUp: me.onKeyPageUp,
home: me.onKeyHome,
end: me.onKeyEnd,
tab: me.onKeyTab,
space: me.onKeySpace,
enter: me.onKeyEnter,
A: {
ctrl: true,
// Need a separate function because we don't want the key
// events passed on to selectAll (causes event suppression).
handler: me.onSelectAllKeyPress
},
scope: me
});
},
processViewEvent: function(view, record, node, index, event) {
// Event is valid if it is from within the list
if (event.within(view.listWrap)) {
return event;
}
// If not from within the list, we're only interested in ESC.
// Defeat the NavigationModel's ignoreInputFields for that.
if (event.getKey() === event.ESC) {
if (Ext.fly(event.target).isInputField()) {
event.target = event.target.parentNode;
}
return event;
}
// Falsy return stops the KeyMap processing the event
},
enable: function() {
this.fieldKeyNav.enable();
this.callParent();
},
disable: function() {
this.fieldKeyNav.disable();
this.callParent();
},
onItemMouseDown: function(view, record, item, index, event) {
this.callParent([view, record, item, index, event]);
// Stop the mousedown from blurring the input field
event.preventDefault();
},
onKeyUp: function() {
var me = this,
boundList = me.view,
allItems = boundList.all,
oldItem = boundList.highlightedItem,
oldItemIdx = oldItem ? boundList.indexOf(oldItem) : -1,
newItemIdx = oldItemIdx > 0 ? oldItemIdx - 1 : allItems.getCount() - 1; //wraps around
me.setPosition(newItemIdx);
},
onKeyDown: function() {
var me = this,
boundList = me.view,
allItems = boundList.all,
oldItem = boundList.highlightedItem,
oldItemIdx = oldItem ? boundList.indexOf(oldItem) : -1,
newItemIdx = oldItemIdx < allItems.getCount() - 1 ? oldItemIdx + 1 : 0; //wraps around
me.setPosition(newItemIdx);
},
onKeyLeft: Ext.returnTrue,
onKeyRight: Ext.returnTrue,
onKeyTab: function(e) {
var view = this.view,
field = view.pickerField;
if (view.isVisible()) {
if (field.selectOnTab) {
this.selectHighlighted(e);
}
if (field.collapse) {
field.collapse();
}
}
// Tab key event is allowed to propagate to field
return true;
},
onKeyEnter: function(e) {
var view = this.view,
selModel = view.getSelectionModel(),
field = view.pickerField,
count = selModel.getCount();
// Stop the keydown event so that an ENTER keyup does not get delivered to
// any element which focus is transferred to in a select handler.
e.stopEvent();
this.selectHighlighted(e);
// Handle the case where the highlighted item is already selected
// In this case, the change event won't fire, so just collapse
if (!field.multiSelect && count === selModel.getCount() && field.collapse) {
field.collapse();
}
},
onKeySpace: function() {
if (this.navigateOnSpace) {
this.callParent(arguments);
}
// Allow to propagate to field
return true;
},
onKeyEsc: function() {
if (this.view.pickerField) {
this.view.pickerField.collapse();
}
},
/**
* Highlights the item at the given index.
* @param {Number} index
*/
focusItem: function(item) {
var me = this,
boundList = me.view;
if (typeof item === 'number') {
item = boundList.all.item(item);
}
if (item) {
item = item.dom;
boundList.highlightItem(item);
boundList.getScrollable().scrollIntoView(item, false);
}
},
/**
* Triggers selection of the currently highlighted item according to the behavior of
* the configured SelectionModel.
*/
selectHighlighted: function(e) {
var me = this,
boundList = me.view,
selModel = boundList.getSelectionModel(),
highlightedRec,
highlightedPosition = me.recordIndex;
// If all options have been filtered out, then do NOT add most recently highlighted.
if (boundList.all.getCount()) {
highlightedRec = me.getRecord();
if (highlightedRec) {
// Select if not already selected.
// If already selected, selecting with no CTRL flag will deselect the record.
if (e.getKey() === e.ENTER || !selModel.isSelected(highlightedRec)) {
selModel.selectWithEvent(highlightedRec, e);
// If the result of that selection is that the record is removed or filtered out,
// jump to the next one.
if (!boundList.store.data.contains(highlightedRec)) {
me.setPosition(Math.min(highlightedPosition, boundList.store.getCount() - 1));
}
}
}
}
},
destroy: function() {
Ext.destroy(this.fieldKeyNav);
this.callParent();
}
});