2016-06-21 09:22:54 +02:00
/ *
* This is a global search field
* it loads the / cluster / resources on focus
* and displays the result in a floating grid
*
* it filters and sorts the objects by the algorithm in
* the customFilter function
*
* also it does accept key up / down and enter for input
* and it opens to ctrl + shift + f and ctrl + space
* /
Ext . define ( 'PVE.form.GlobalSearchField' , {
extend : 'Ext.form.field.Text' ,
alias : 'widget.pveGlobalSearchField' ,
emptyText : gettext ( 'Search' ) ,
enableKeyEvents : true ,
selectOnFocus : true ,
padding : '0 5 0 5' ,
grid : {
xtype : 'gridpanel' ,
focusOnToFront : false ,
floating : true ,
2018-01-15 15:18:09 +01:00
emptyText : Proxmox . Utils . noneText ,
2016-06-21 09:22:54 +02:00
width : 600 ,
2016-12-06 16:26:39 +01:00
height : 400 ,
2016-06-21 09:22:54 +02:00
scrollable : {
xtype : 'scroller' ,
y : true ,
x : false
} ,
store : {
model : 'PVEResources' ,
proxy : {
2017-12-11 14:46:51 +01:00
type : 'proxmox' ,
2016-06-21 09:22:54 +02:00
url : '/api2/extjs/cluster/resources'
}
} ,
plugins : {
ptype : 'bufferedrenderer' ,
trailingBufferZone : 20 ,
leadingBufferZone : 20
} ,
hideMe : function ( ) {
var me = this ;
2019-01-29 12:41:31 +01:00
if ( typeof me . ctxMenu !== 'undefined' && me . ctxMenu . isVisible ( ) ) {
return ;
}
2016-06-21 09:22:54 +02:00
me . hasFocus = false ;
if ( ! me . textfield . hasFocus ) {
me . hide ( ) ;
}
} ,
setFocus : function ( ) {
var me = this ;
me . hasFocus = true ;
} ,
listeners : {
rowclick : function ( grid , record ) {
var me = this ;
me . textfield . selectAndHide ( record . id ) ;
} ,
2019-01-29 12:41:31 +01:00
itemcontextmenu : function ( v , record , item , index , event ) {
var me = this ;
me . ctxMenu = PVE . Utils . createCmdMenu ( v , record , item , index , event ) ;
} ,
2016-06-21 09:22:54 +02:00
/* because of lint */
focusleave : {
fn : 'hideMe'
} ,
focusenter : 'setFocus'
} ,
columns : [
{
text : gettext ( 'Type' ) ,
dataIndex : 'type' ,
width : 100 ,
renderer : PVE . Utils . render _resource _type
} ,
{
text : gettext ( 'Description' ) ,
flex : 1 ,
dataIndex : 'text'
} ,
{
text : gettext ( 'Node' ) ,
dataIndex : 'node'
} ,
{
text : gettext ( 'Pool' ) ,
dataIndex : 'pool'
}
]
} ,
customFilter : function ( item ) {
var me = this ;
var match = 0 ;
var fieldArr = [ ] ;
var i , j , fields ;
// different types of objects have different fields to search
// for example, a node will never have a pool and vice versa
switch ( item . data . type ) {
case 'pool' : fieldArr = [ 'type' , 'pool' , 'text' ] ; break ;
case 'node' : fieldArr = [ 'type' , 'node' , 'text' ] ; break ;
case 'storage' : fieldArr = [ 'type' , 'pool' , 'node' , 'storage' ] ; break ;
default : fieldArr = [ 'name' , 'type' , 'node' , 'pool' , 'vmid' ] ;
}
if ( me . filterVal === '' ) {
item . data . relevance = 0 ;
return true ;
}
// all text is case insensitive and each word is
// searched alone
// for every partial match, the row gets
// 1 match point, for every exact match
// it gets 2 points
//
// results gets sorted by points (descending)
fields = me . filterVal . split ( /\s+/ ) ;
for ( i = 0 ; i < fieldArr . length ; i ++ ) {
var v = item . data [ fieldArr [ i ] ] ;
if ( v !== undefined ) {
v = v . toString ( ) . toLowerCase ( ) ;
for ( j = 0 ; j < fields . length ; j ++ ) {
if ( v . indexOf ( fields [ j ] ) !== - 1 ) {
match ++ ;
if ( v === fields [ j ] ) {
match ++ ;
}
}
}
}
}
// give the row the 'relevance' value
item . data . relevance = match ;
return ( match > 0 ) ;
} ,
updateFilter : function ( field , newValue , oldValue ) {
var me = this ;
// parse input and filter store,
// show grid
me . grid . store . filterVal = newValue . toLowerCase ( ) . trim ( ) ;
me . grid . store . clearFilter ( true ) ;
me . grid . store . filterBy ( me . customFilter ) ;
me . grid . getSelectionModel ( ) . select ( 0 ) ;
} ,
selectAndHide : function ( id ) {
var me = this ;
me . tree . selectById ( id ) ;
me . grid . hide ( ) ;
me . setValue ( '' ) ;
me . blur ( ) ;
} ,
onKey : function ( field , e ) {
var me = this ;
var key = e . getKey ( ) ;
switch ( key ) {
case Ext . event . Event . ENTER :
// go to first entry if there is one
if ( me . grid . store . getCount ( ) > 0 ) {
me . selectAndHide ( me . grid . getSelection ( ) [ 0 ] . data . id ) ;
}
break ;
case Ext . event . Event . UP :
me . grid . getSelectionModel ( ) . selectPrevious ( ) ;
break ;
case Ext . event . Event . DOWN :
me . grid . getSelectionModel ( ) . selectNext ( ) ;
break ;
case Ext . event . Event . ESC :
me . grid . hide ( ) ;
me . blur ( ) ;
break ;
}
} ,
loadValues : function ( field ) {
var me = this ;
var records = [ ] ;
me . hasFocus = true ;
me . grid . textfield = me ;
me . grid . store . load ( ) ;
me . grid . showBy ( me , 'tl-bl' ) ;
} ,
hideGrid : function ( ) {
var me = this ;
me . hasFocus = false ;
if ( ! me . grid . hasFocus ) {
me . grid . hide ( ) ;
}
} ,
listeners : {
change : {
fn : 'updateFilter' ,
buffer : 250
} ,
specialkey : 'onKey' ,
focusenter : 'loadValues' ,
focusleave : {
fn : 'hideGrid' ,
delay : 100
}
} ,
toggleFocus : function ( ) {
var me = this ;
if ( ! me . hasFocus ) {
me . focus ( ) ;
} else {
me . blur ( ) ;
}
} ,
initComponent : function ( ) {
var me = this ;
if ( ! me . tree ) {
throw "no tree given" ;
}
me . grid = Ext . create ( me . grid ) ;
me . callParent ( ) ;
/*jslint confusion: true*/
/*because shift is also a function*/
// bind ctrl+shift+f and ctrl+space
// to open/close the search
me . keymap = new Ext . KeyMap ( {
target : Ext . get ( document ) ,
binding : [ {
key : 'F' ,
ctrl : true ,
shift : true ,
fn : me . toggleFocus ,
scope : me
} , {
key : ' ' ,
ctrl : true ,
fn : me . toggleFocus ,
scope : me
} ]
} ) ;
// always select first item and
// sort by relevance after load
me . mon ( me . grid . store , 'load' , function ( ) {
me . grid . getSelectionModel ( ) . select ( 0 ) ;
me . grid . store . sort ( {
property : 'relevance' ,
direction : 'DESC'
} ) ;
} ) ;
}
} ) ;