/** * The Modern Grid's ViewOptions plugin produces a menu that slides in from the right * (by default) when you longpress on the grid headers. The menu displays the column * header names which represents the order of the grid's columns. This allows users to * easily reorder the grid's columns by reordering the rows. Items may be dragged by * grabbing the furthest left side of the row and moving the item vertically. * * Once the columns are ordered to your liking, you may then close the menu by tapping the * "Done" button. * * ```javascript * @example({ framework: 'extjs' }) * var store = Ext.create('Ext.data.Store', { * fields: ['name', 'email', 'phone'], * data: [{ * name: 'Lisa', * email: '[email protected]', * phone: '555-111-1224' * }, { * name: 'Bart', * email: '[email protected]', * phone: '555-111-1234' * }, { * name: 'Homer', * email: '[email protected]', * phone: '555-222-1244' * }, { * name: 'Marge', * email: '[email protected]', * phone: '555-222-1254' * }] * }); * * Ext.create('Ext.grid.Grid', { * store: store, * plugins: { * gridviewoptions: true * }, * columns: [{ * text: 'Name', * dataIndex: 'name', * width: 200 * }, { * text: 'Email', * dataIndex: 'email', * width: 250 * }, { * text: 'Phone', * dataIndex: 'phone', * width: 120 * }], * fullscreen: true * }); * ``` * ```html * @example({framework: 'ext-web-components', packages:['ext-web-components'], tab: 1 }) * <ext-container width="100%" height="100%"> * <ext-grid * shadow="true" * height="100%" * onready="basicgrid.onGridReady" * plugins='["gridviewoptions"]' * > * <ext-column text="Name" dataIndex="name" flex="1" sortable="false"></ext-column> * <ext-column text="Email" dataIndex="email" flex="1"></ext-column> * <ext-column text="Phone" dataIndex="phone" flex="1"></ext-column> * </ext-grid> * </ext-container> * ``` * ```javascript * @example({framework: 'ext-web-components', tab: 2, packages: ['ext-web-components']}) * import '@sencha/ext-web-components/dist/ext-container.component'; * import '@sencha/ext-web-components/dist/ext-grid.component'; * import '@sencha/ext-web-components/dist/ext-column.component'; * * Ext.require('Ext.grid.plugin.ViewOptions'); * * export default class BasicGridComponent { * constructor() { * this.store = new Ext.data.Store({ * data: [ * { "name": "Lisa", "email": "[email protected]", "phone": "555-111-1224" }, * { "name": "Bart", "email": "[email protected]", "phone": "555-222-1234" }, * { "name": "Homer", "email": "[email protected]", "phone": "555-222-1244" }, * { "name": "Marge", "email": "[email protected]", "phone": "555-222-1254" } * ] * }); * } * * onGridReady(event) { * this.basicGridCmp = event.detail.cmp; * this.basicGridCmp.setStore(this.store); * } * } * * window.basicgrid = new BasicGridComponent(); * ``` * ```javascript * @example({framework: 'ext-react', packages:['ext-react']}) * import React, { Component } from 'react' * import { ExtGrid, ExtColumn } from '@sencha/ext-react'; * * Ext.require('Ext.grid.plugin.ViewOptions'); * * export default class MyExample extends Component { * * store = new Ext.data.Store({ * data: [ * { "name": "Lisa", "email": "[email protected]", "phone": "555-111-1224" }, * { "name": "Bart", "email": "[email protected]", "phone": "555-222-1234" }, * { "name": "Homer", "email": "[email protected]", "phone": "555-222-1244" }, * { "name": "Marge", "email": "[email protected]", "phone": "555-222-1254" } * ] * }); * * render() { * return ( * <ExtGrid * layout="fit" * store={this.store} * plugins={[ 'gridviewoptions' ]} * > * <ExtColumn text="Name" dataIndex="name" flex={1} sortable={false} /> * <ExtColumn text="Email" dataIndex="email" flex={1} /> * <ExtColumn text="Phone" dataIndex="phone" flex={1} /> * </ExtGrid> * ) * } * } * ``` * ```javascript * @example({framework: 'ext-angular', packages:['ext-angular']}) * import { Component } from '@angular/core' * declare var Ext: any; * * Ext.require('Ext.grid.plugin.ViewOptions'); * @Component({ * selector: 'app-root-1', * styles: [` * `], * template: ` * <ExtContainer layout="fit"> * <ExtGrid * [height]="'500px'" * [store]="store" * plugins="gridviewoptions" * > * <ExtColumn text="Name" dataIndex="name" flex="1" [sortable]="false"></ExtColumn> * <ExtColumn text="Email" dataIndex="email" flex="1"></ExtColumn> * <ExtColumn text="Phone" dataIndex="phone" flex="1"></ExtColumn> * </ExtGrid> * </ExtContainer> * ` * }) * export class AppComponent { * store = new Ext.data.Store({ * data: [ * { "name": "Lisa", "email": "[email protected]", "phone": "555-111-1224" }, * { "name": "Bart", "email": "[email protected]", "phone": "555-222-1234" }, * { "name": "Homer", "email": "[email protected]", "phone": "555-222-1244" }, * { "name": "Marge", "email": "[email protected]", "phone": "555-222-1254" } * ] * }); * } * ``` * * Developers may modify the menu and its contents by overriding {@link #sheet} and * {@link #columnList} respectively. * */Ext.define('Ext.grid.plugin.ViewOptions', { extend: 'Ext.plugin.Abstract', alias: 'plugin.gridviewoptions', requires: [ 'Ext.dataview.NestedList', 'Ext.dataview.plugin.SortableList', 'Ext.grid.plugin.ViewOptionsListItem' ], config: { /** * @private */ grid: null, /** * The width of the menu */ sheetWidth: 320, /** * The configuration of the menu */ sheet: { lazy: true, $value: { xtype: 'sheet', cls: Ext.baseCSSPrefix + 'gridviewoptions', items: [{ docked: 'top', xtype: 'titlebar', title: 'Customize', items: [{ xtype: 'button', text: 'Done', ui: 'action', align: 'right', role: 'donebutton' }] }], hidden: true, hideOnMaskTap: true, enter: 'right', exit: 'right', modal: true, right: 0, layout: 'fit', stretchY: true } }, /** * The column's configuration */ columnList: { lazy: true, $value: { xtype: 'nestedlist', title: 'Columns', clearSelectionOnListChange: false, listConfig: { triggerEvent: null, infinite: true, mode: 'MULTI', variableHeights: true, scrollToTopOnRefresh: false, plugins: { sortablelist: { source: { handle: '.' + Ext.baseCSSPrefix + 'column-options-sortablehandle' } } }, itemConfig: { xtype: 'viewoptionslistitem' }, itemTpl: '{text}' }, store: { type: 'tree', fields: [ 'id', 'text', 'dataIndex', 'header', 'hidden', 'hideable', 'grouped', 'groupable' ], root: { text: 'Columns' } } } }, /** * The CSS class responsible for displaying the visibility indicator. */ visibleIndicatorSelector: '.' + Ext.baseCSSPrefix + 'column-options-visibleindicator', /** * The CSS class responsible for displaying the grouping indicator. */ groupIndicatorSelector: '.' + Ext.baseCSSPrefix + 'column-options-groupindicator' }, init: function(grid) { this.setGrid(grid); }, destroy: function() { this.destroyMembers('sheet', 'columnList'); this.callParent(); }, updateGrid: function(grid, oldGrid) { if (oldGrid) { oldGrid.getHeaderContainer().renderElement.un({ contextmenu: 'onHeaderContextMenu', longpress: 'onHeaderLongPress', scope: this }); oldGrid.un({ columnadd: 'onColumnAdd', columnmove: 'onColumnMove', columnremove: 'onColumnRemove', columnhide: 'onColumnHide', columnshow: 'onColumnShow', scope: this }); } if (grid) { grid.getHeaderContainer().renderElement.on({ contextmenu: 'onHeaderContextMenu', longpress: 'onHeaderLongPress', scope: this }); } }, applySheet: function(sheet) { if (sheet && !sheet.isComponent) { sheet = Ext.factory(sheet, Ext.Sheet); } return sheet; }, applyColumnList: function(list) { if (list && !list.isComponent) { list = Ext.factory(list, Ext.Container); } return list; }, updateColumnList: function(list) { if (list) { list.on({ listchange: 'onListChange', scope: this }); // For each item in the nested list, we want to handle the reorder list.on('dragsort', 'onColumnDrag', this, { delegate: '> list' }); this.attachTapListeners(); } }, updateSheet: function(sheet) { sheet.setWidth(this.getSheetWidth()); sheet.add(this.getColumnList()); sheet.on('hide', 'onSheetHide', this); }, onDoneButtonTap: function() { this.getSheet().hide(); }, onColumnDrag: function(list, row, newIndex) { var column = Ext.getCmp(row.getRecord().get('id')), parent = column.getParent(), siblings = parent.getInnerItems(), i, ln, sibling; for (i = 0, ln = newIndex; i < ln; i++) { sibling = siblings[i]; if (!sibling.isHeaderGroup && sibling.getIgnore()) { newIndex += 1; } } this.isMoving = true; parent.insert(newIndex, column); this.isMoving = false; }, attachTapListeners: function() { var activeList = this.getColumnList().getActiveItem(); if (!activeList.hasAttachedTapListeners) { activeList.onBefore({ childtap: 'onListChildTap', scope: this }); activeList.hasAttachedTapListeners = true; } }, onListChange: function(nestedList, list) { var store = list.getStore(), activeNode = store.getNode(), records = activeNode.childNodes, ln = records.length, i, column, record; for (i = 0; i < ln; i++) { record = records[i]; column = Ext.getCmp(record.getId()); record.set('hidden', column.isHidden()); } this.attachTapListeners(); }, onListChildTap: function(list, location) { var me = this, handled = false, e = location.event; if (Ext.fly(e.target).is(me.getVisibleIndicatorSelector())) { me.onVisibleIndicatorTap(location.row, location.record); handled = true; } else if (Ext.fly(e.target).is(me.getGroupIndicatorSelector())) { me.onGroupIndicatorTap(location.row, location.record); handled = true; } return !handled; }, onVisibleIndicatorTap: function(row, record) { var hidden = !record.get('hidden'), column = Ext.getCmp(record.get('id')), len = this.getGrid().getVisibleColumns().length; if (len > 1 || (len && column.isHidden())) { column.setHidden(hidden); record.set('hidden', hidden); } }, onGroupIndicatorTap: function(row, record) { var me = this, grouped = !record.get('grouped'), store = me.getGrid().getStore(); // Clear everything this.getListRoot().cascade(function(node) { node.set('grouped', false); }); if (grouped) { store.setGrouper({ property: record.get('dataIndex') }); record.set('grouped', true); } else { store.setGrouper(null); } }, onColumnHide: function(headerContainer, column) { var nestedList = this.getColumnList(), activeList = nestedList.getActiveItem(), store = activeList.getStore(), record = store.getById(column.getId()); if (record) { record.set('hidden', true); } }, onColumnShow: function(headerContainer, column) { var nestedList = this.getColumnList(), activeList = nestedList.getActiveItem(), store = activeList.getStore(), record = store.getById(column.getId()); if (record) { record.set('hidden', false); } }, onColumnAdd: function(grid, column) { var me = this, nestedList, mainHeaderCt, header, store, parentNode, isGridGrouped, grouper, dataIndex, data, idx, headerNode; if (column.getIgnore() || this.isMoving) { return; } nestedList = me.getColumnList(); mainHeaderCt = grid.getHeaderContainer(); header = column.getParent(); store = nestedList.getStore(); parentNode = store.getRoot(); isGridGrouped = grid.getGrouped(); grouper = grid.getStore().getGrouper(); dataIndex = column.getDataIndex(); data = { id: column.getId(), text: column.getText() || '\xA0', groupable: isGridGrouped && column.canGroup(), hidden: column.isHidden(), hideable: column.getHideable(), grouped: !!(isGridGrouped && grouper && grouper.getProperty() === dataIndex), dataIndex: column.getDataIndex(), leaf: true }; if (header !== mainHeaderCt) { headerNode = parentNode.findChild('id', header.getId()); if (!headerNode) { idx = header.getParent().indexOf(header); headerNode = parentNode.insertChild(idx, { groupable: false, header: true, hidden: header.isHidden(), id: header.getId(), text: header.getText() }); } idx = header.indexOf(column); parentNode = headerNode; } else { idx = mainHeaderCt.indexOf(column); } parentNode.insertChild(idx, data); }, onColumnMove: function(headerContainer, column, header) { this.onColumnRemove(headerContainer, column); this.onColumnAdd(headerContainer, column, header); }, onColumnRemove: function(headerContainer, column) { var root, record; if (column.getIgnore() || this.isMoving) { return; } root = this.getListRoot(); record = root.findChild('id', column.getId(), true); if (record) { record.parentNode.removeChild(record, true); } }, onHeaderContextMenu: function(e) { // Stop context menu from being triggered by a longpress e.preventDefault(); }, onHeaderLongPress: function(e) { if (!this.getSheet().isVisible()) { this.showViewOptions(); } }, hideViewOptions: function() { var me = this, sheet = me.getSheet(); me.getGrid().getHeaderContainer().setSortable(me.cachedSortable); delete me.cachedSortable; sheet.hide(); }, onSheetHide: function() { this.hideViewOptions(); }, showViewOptions: function() { var me = this, sheet = me.getSheet(), header; me.setup(); if (!sheet.isVisible()) { // Since we may have shown the header in response to a longpress we don't // want the succeeding "tap" to trigger column sorting, so we temporarily // disable sort-on-tap while the ViewOptions are shown header = me.getGrid().getHeaderContainer(); me.cachedSortable = header.getSortable(); header.setSortable(false); me.updateListInfo(); sheet.show(); } }, privates: { getListRoot: function() { return this.getColumnList().getStore().getRoot(); }, setup: function() { var me = this, grid = me.getGrid(), sheet, root; if (me.doneSetup) { return; } me.doneSetup = true; root = this.getListRoot(); root.removeAll(); grid.getColumns().forEach(function(leaf) { me.onColumnAdd(grid, leaf); }); // Don't track the events until the first show, it's easier to // build it from scratch. grid.on({ columnadd: 'onColumnAdd', columnmove: 'onColumnMove', columnremove: 'onColumnRemove', columnhide: 'onColumnHide', columnshow: 'onColumnShow', scope: me }); sheet = me.getSheet(); sheet.down('button[role=donebutton]').on({ tap: 'onDoneButtonTap', scope: me }); }, updateListInfo: function() { var grid = this.getGrid(), store = grid.getStore(), grouper = store.getGrouper(), isGridGrouped = grid.getGrouped(), grouperProp = grouper && grouper.getProperty(); this.getColumnList().getStore().getRoot().cascade(function(node) { var grouped = false, dataIndex; if (isGridGrouped) { dataIndex = node.get('dataIndex'); grouped = dataIndex && dataIndex === grouperProp; } node.set('grouped', dataIndex && grouped); }); } }});