EXT 4.2 ComboBox使用XTemplate对结果进行分组

6
我正在尝试将从存储中获取的结果分组,以便在ComboBox内显示。我有一个ComboBox看起来像这样:enter image description here 我需要它看起来像这样:enter image description here 这意味着按类别(订单/发票)分组。我的ComboBox定义如下:
Ext.define('NG.view.searchcombo.Combo', {
    requires: ['Ext.form.field.ComboBox'],
    extend: 'Ext.form.ComboBox',
    alias: 'widget.searchcombo',
    minChars:3,
    fieldLabel: 'Choose Search',
    store: 'Search',
    displayField: 'name',
    valueField: 'id',
    typeAhead: false,
    hideLabel: true,
    hideTrigger:false,
    anchor: '100%',

    listConfig: {
        loadingText: 'Searching...',
        emptyText: 'No matching posts found.',

        // Custom rendering template for each item
        getInnerTpl: function() {
            return '<h3>{name} / {category}</h3>' +'{excerpt}' ;
        }
    },
    pageSize: 10,
    initComponent: function () {    

        this.callParent(arguments);
    }
});

我的数据如下:
[{
    "id": 1,
    "name": "one",
    "category": "invoice"
}, {
    "id": 2,
    "name": "two",
    "category": "invoice"
}, {
    "id": 3,
    "name": "one",
    "category": "order"
}, {
    "id": 4,
    "name": "two",
    "category": "order"
}, {
    "id": 5,
    "name": "three",
    "category": "invoice"
}, {
    "id": 6,
    "name": "four",
    "category": "invoice"
}, {
    "id": 7,
    "name": "three",
    "category": "order"
}, {
    "id": 8,
    "name": "four",
    "category": "order"
}, {
    "id": 9,
    "name": "five",
    "category": "invoice"
}, {
    "id": 10,
    "name": "six",
    "category": "invoice"
}, {
    "id": 11,
    "name": "five",
    "category": "order"
}, {
    "id": 12,
    "name": "six",
    "category": "order"
}, {
    "id": 13,
    "name": "seven",
    "category": "invoice"
}, {
    "id": 14,
    "name": "eight",
    "category": "invoice"
}, {
    "id": 15,
    "name": "seven",
    "category": "order"
}, {
    "id": 16,
    "name": "eight",
    "category": "order"
}]

我认为可以使用Ext.XTemplate来完成,但我对Ext.XTemplate不太熟悉。

5个回答

14

我希望有一个更简单的解决方案,所以我会分享我想出来的。

为了我的目的,我有一个要分组的 key ,它只是一个字符。我知道我想为每个键显示的标题,所以我预先对列表进行了排序,以确保类型放在一起,然后每次看到新的键时,我只需要渲染一个组标题即可。

myStore.sort('key', 'DESC');

Ext.create('Ext.form.field.ComboBox', {
  store: myStore,
  queryMode: 'local',
  displayField: 'name',
  valueField: 'id',
  listConfig: {
    cls: 'grouped-list'
  },
  tpl: Ext.create('Ext.XTemplate',
    '{[this.currentKey = null]}' +
    '<tpl for=".">',
      '<tpl if="this.shouldShowHeader(key)">' +
        '<div class="group-header">{[this.showHeader(values.key)]}</div>' +
      '</tpl>' +
      '<div class="x-boundlist-item">{name}</div>',
    '</tpl>',
    {
      shouldShowHeader: function(key){
        return this.currentKey != key;
      },
      showHeader: function(key){
        this.currentKey = key;
        switch (key) {
          case 's': return 'Structures';
          case 'f': return 'Filters';
          ...
        }
        return 'Other';
      }
    }
  )
});
使用以下 CSS:
.grouped-list .x-boundlist-item {
  padding: 1px 3px 0 10px
}

.grouped-list .group-header {
  padding: 4px;
  font-weight: bold;
  border-bottom: 1px solid #ddd;
}

而这些数据:

[
    { key: 's', name: '2014 Product Development' },
    { key: 'f', name: 'Message Filter' },
    { key: 's', name: '2014 Product Development (Little)' },
    { key: 's', name: 'Global Structure' },
    { key: 'f', name: 'My SW' }
]

我得到了一个像这样的漂亮的分组列表:


请问您能否提供一下绑定到组合框的样本数据?谢谢! - SharpCoder
这个解决方案的问题在于代码了解数据内容。这限制了它只能用于特定的数据集。也许数据应该携带有关标题的信息,例如记录是否实际上是标题。 - Tsahi Asher
@TsahiAsher 这是正确的。 这是我的情况的解决方案。 它可以轻松地适应包含“组”而不是“键”的情况,然后模板就会更新。 - Sean Adkinson
谢谢!运作得很好。但是,我得到了一个额外且不必要的“null”作为第一个标题!有什么办法可以避免它吗? 我认为这是由于'{[this.currentKey = null]}' +引起的。 - Saurabh Bayani
嗯,输出“null”是有道理的,但它可能只是被隐藏了。我认为你可以完全删除那个初始化行,因为它最初是null还是undefined并不重要。 - Sean Adkinson
@SaurabhBayani 你可以用 {%this.currentKey = null%} 进行替换,这样代码就会被添加到模板中但不会被写入页面。 - Tsahi Asher

8

这是一个扩展,它通过将Sean Adkinson的代码制作成可重复使用的组件来改善上面的答案。

我用 Ext 5.0.1 将 BoundList 替换为 GridPanel 时结果不尽相同,因此我使用了这个扩展。

需要注意的是,它不支持折叠组,但对我而言效果很棒。

在 Extjs 4.2.3 和 5.0.1 中进行了测试。

您可以在 Sencha 的 fiddle 中查看它。

希望能对某些人有所帮助。

Ext.define('Ext.ux.GroupComboBox', {
  extend: 'Ext.form.field.ComboBox',
  alias: 'widget.groupcombobox',
  /*
   * @cfg groupField String value of field to groupBy, set this to any field in your model
   */
  groupField: 'group',
  listConfig: {
    cls: 'grouped-list'
  },
  initComponent: function() {
    var me = this;
    me.tpl = new Ext.XTemplate([
      '{%this.currentGroup = null%}',
      '<tpl for=".">',
      '   <tpl if="this.shouldShowHeader(' + me.groupField + ')">',
      '       <div class="group-header">{[this.showHeader(values.' + me.groupField + ')]}</div>',
      '   </tpl>',
      '   <div class="x-boundlist-item">{' + me.displayField + '}</div>',
      '</tpl>', {
        shouldShowHeader: function(group) {
          return this.currentGroup != group;
        },
        showHeader: function(group) {
          this.currentGroup = group;
          return group;
        }
      }
    ]);
    me.callParent(arguments);
  }
});

//Example usage
var Restaurants = Ext.create('Ext.data.Store', {
  storeId: 'restaraunts',
  fields: ['name', 'cuisine'],
  sorters: ['cuisine', 'name'],
  groupField: 'cuisine',
  data: [{
    name: 'Cheesecake Factory',
    cuisine: 'American'
  }, {
    name: 'University Cafe',
    cuisine: 'American'
  }, {
    name: 'Creamery',
    cuisine: 'American'
  }, {
    name: 'Old Pro',
    cuisine: 'American'
  }, {
    name: 'Nola\'s',
    cuisine: 'Cajun'
  }, {
    name: 'House of Bagels',
    cuisine: 'Bagels'
  }, {
    name: 'The Prolific Oven',
    cuisine: 'Sandwiches'
  }, {
    name: 'La Strada',
    cuisine: 'Italian'
  }, {
    name: 'Buca di Beppo',
    cuisine: 'Italian'
  }, {
    name: 'Pasta?',
    cuisine: 'Italian'
  }, {
    name: 'Madame Tam',
    cuisine: 'Asian'
  }, {
    name: 'Sprout Cafe',
    cuisine: 'Salad'
  }, {
    name: 'Pluto\'s',
    cuisine: 'Salad'
  }, {
    name: 'Junoon',
    cuisine: 'Indian'
  }, {
    name: 'Bistro Maxine',
    cuisine: 'French'
  }, {
    name: 'Three Seasons',
    cuisine: 'Vietnamese'
  }, {
    name: 'Sancho\'s Taquira',
    cuisine: 'Mexican'
  }, {
    name: 'Reposado',
    cuisine: 'Mexican'
  }, {
    name: 'Siam Royal',
    cuisine: 'Thai'
  }, {
    name: 'Krung Siam',
    cuisine: 'Thai'
  }, {
    name: 'Thaiphoon',
    cuisine: 'Thai'
  }, {
    name: 'Tamarine',
    cuisine: 'Vietnamese'
  }, {
    name: 'Joya',
    cuisine: 'Tapas'
  }, {
    name: 'Jing Jing',
    cuisine: 'Chinese'
  }, {
    name: 'Patxi\'s Pizza',
    cuisine: 'Pizza'
  }, {
    name: 'Evvia Estiatorio',
    cuisine: 'Mediterranean'
  }, {
    name: 'Gyros-Gyros',
    cuisine: 'Mediterranean'
  }, {
    name: 'Mango Caribbean Cafe',
    cuisine: 'Caribbean'
  }, {
    name: 'Coconuts Caribbean Restaurant &amp; Bar',
    cuisine: 'Caribbean'
  }, {
    name: 'Rose &amp; Crown',
    cuisine: 'English'
  }, {
    name: 'Baklava',
    cuisine: 'Mediterranean'
  }, {
    name: 'Mandarin Gourmet',
    cuisine: 'Chinese'
  }, {
    name: 'Bangkok Cuisine',
    cuisine: 'Thai'
  }, {
    name: 'Darbar Indian Cuisine',
    cuisine: 'Indian'
  }, {
    name: 'Mantra',
    cuisine: 'Indian'
  }, {
    name: 'Janta',
    cuisine: 'Indian'
  }, {
    name: 'Starbucks',
    cuisine: 'Coffee'
  }, {
    name: 'Peet\'s Coffee',
    cuisine: 'Coffee'
  }, {
    name: 'Coupa Cafe',
    cuisine: 'Coffee'
  }, {
    name: 'Lytton Coffee Company',
    cuisine: 'Coffee'
  }, {
    name: 'Il Fornaio',
    cuisine: 'Italian'
  }, {
    name: 'Lavanda',
    cuisine: 'Mediterranean'
  }, {
    name: 'MacArthur Park',
    cuisine: 'American'
  }, {
    name: 'St Michael\'s Alley',
    cuisine: 'Californian'
  }, {
    name: 'Cafe Renzo',
    cuisine: 'Italian'
  }, {
    name: 'Miyake',
    cuisine: 'Sushi'
  }, {
    name: 'Sushi Tomo',
    cuisine: 'Sushi'
  }, {
    name: 'Kanpai',
    cuisine: 'Sushi'
  }, {
    name: 'Pizza My Heart',
    cuisine: 'Pizza'
  }, {
    name: 'New York Pizza',
    cuisine: 'Pizza'
  }, {
    name: 'Loving Hut',
    cuisine: 'Vegan'
  }, {
    name: 'Garden Fresh',
    cuisine: 'Vegan'
  }, {
    name: 'Cafe Epi',
    cuisine: 'French'
  }, {
    name: 'Tai Pan',
    cuisine: 'Chinese'
  }]
});

Ext.create('Ext.container.Viewport', {
  items: Ext.create('Ext.ux.GroupComboBox', {
    fieldLabel: 'Restaurants',
    name: 'txtRestaurant',
    forceSelection: true,
    editable: false,
    queryMode: 'local',
    triggerAction: 'all',
    multiSelect: true,
    groupField: 'cuisine',
    displayField: 'name',
    valueField: 'name',
    store: Restaurants,
    width: 400
  })
}).show();
.grouped-list .x-boundlist-item {
  padding: 1px 3px 0 10px;
}
.grouped-list .group-header {
  padding: 4px;
  font-weight: bold;
  border-bottom: 1px solid #ddd;
}


我喜欢这个。我在模板中修复了一个小错误(等待审核),如果模板使用<ul>而不是<div>,我会更喜欢它。 - Tsahi Asher
不错的扩展。你能帮我解决以下问题吗?当我在Ext 5.0.0中使用它时,每次输入筛选条件时groupFields都会翻倍。请看一下您fiddle上的这个分支:https://fiddle.sencha.com/#fiddle/ugi - Christiaan Westerbeek
这很好,感谢您清理我的解决方案,使其更具可重用性 :+1: - Sean Adkinson

1

0

我已经实现了一个带有网格作为列表组件的组合框的自己版本。你可以在GitHub上获取它,我也放了一些在线示例

第三个示例与您尝试实现的内容非常接近。

这里有一个更加贴近的示例。你只需要完成样式即可:

Ext.widget('gridpicker', {

    queryMode: 'local'
    ,displayField: 'name'

    ,store: {
        fields: ['name', 'group']
        ,proxy: {type: 'memory', reader: 'array'}
        ,data: ...
        ,groupField: 'group'
        ,sorters: {property: 'name', order: 'ASC'}
    }

    ,gridConfig: {
        features: [{
            ftype:'grouping'
            ,groupHeaderTpl: '{name}'
            ,collapsible: false
        }]
        ,columns: [{
            width: 30
            ,renderer: function(value, md, record, rowIndex) {
                return '<img src="..." />';
            }
        },{
            dataIndex: 'name'
            ,flex: 1
        }]
    }
});

0

这是对我有效的代码:

如果您正在使用Sencha Architect,请将createPicker添加到Override中,并手动创建listConfig作为Object。

{
    xtype: 'combobox',
    createPicker: function() {
        var me = this,
            picker,
            menuCls = Ext.baseCSSPrefix + 'menu',
            opts = Ext.apply({
                selModel: {
                    mode: me.multiSelect ? 'SIMPLE' : 'SINGLE'
                },
                floating: true,
                hidden: true,
                ownerCt: me.ownerCt,
                cls: me.el.up('.' + menuCls) ? menuCls : '',
                store: me.store,
                displayField: me.displayField,
                focusOnToFront: false,
                pageSize: me.pageSize
            }, me.listConfig, me.defaultListConfig);

        // NOTE: we simply use a grid panel
        //picker = me.picker = Ext.create('Ext.view.BoundList', opts);


        picker = me.picker = Ext.create('Ext.grid.Panel', opts);

        // hack: pass getNode() to the view
        picker.getNode = function() {
            picker.getView().getNode(arguments);
        };

        me.mon(picker.getView(), {
            refresh: me.onListRefresh,
            scope: me
        });
        me.mon(picker, {
            itemclick: me.onItemClick,
            //            refresh: me.onListRefresh,
            scope: me
        });

        me.mon(picker.getSelectionModel(), {
            selectionChange: me.onListSelectionChange,
            scope: me
        });

        return picker;
    },
    listConfig: {
        columns: [{
            xtype: "gridcolumn",
            dataIndex: "id",
            text: "Id"
        }, {
            xtype: "gridcolumn",
            dataIndex: "name",
            text: "Name"
        }],
        features: [{
            ftype: "grouping"
        }]
    },
    fieldLabel: 'Label',
    queryMode: 'local',
    store: 'myTestStore'
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接