/** * The Modern Grid's ViewOptions plugin produces a menu that slides in from the right (by default) * when you drag your finger or cursor right-to-left over the grid's column 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. * * @example * 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-222-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: [{ * type: 'gridviewoptions' * }], * columns: [ * { text: 'Name', dataIndex: 'name', width: 200}, * { text: 'Email', dataIndex: 'email', width: 250}, * { text: 'Phone', dataIndex: 'phone', width: 120} * ], * height: 200, * layout: 'fit', * fullscreen: true * ); * * Developers may modify the menu and its contents by overriding {@link #sheet} and * {@link #columnList} respectively. * */Ext.define('Ext.grid.plugin.ViewOptions', { extend: 'Ext.Component', alias: 'plugin.gridviewoptions' , requires: [ 'Ext.field.Toggle', 'Ext.dataview.NestedList', 'Ext.plugin.SortableList' ], config: { /** * @private */ grid: null, /** * The width of the menu */ sheetWidth: 320, /** * The configuration of the menu */ sheet: { baseCls: Ext.baseCSSPrefix + 'grid-viewoptions', xtype: 'sheet', items: [{ docked: 'top', xtype: 'titlebar', title: 'Customize', items: { xtype: 'button', text: 'Done', ui: 'action', align: 'right', role: 'donebutton' } }], hideOnMaskTap: false, enter: 'right', exit: 'right', modal: false, translatable: { translationMethod: 'csstransform' }, right: 0, layout: 'fit', stretchY: true }, /** * The column's configuration */ columnList: { xtype: 'nestedlist', title: 'Column', listConfig: { plugins: [{ type: 'sortablelist', handleSelector: '.' + Ext.baseCSSPrefix + 'column-options-sortablehandle' }], mode: 'MULTI', infinite: true, itemTpl: [ '<div class="' + Ext.baseCSSPrefix + 'column-options-itemwrap<tpl if="hidden"> {hiddenCls}</tpl>', '<tpl if="grouped"> {groupedCls}</tpl>">', '<div class="' + Ext.baseCSSPrefix + 'column-options-sortablehandle"></div>', '<tpl if="header">', '<div class="' + Ext.baseCSSPrefix + 'column-options-folder"></div>', '<tpl else>', '<div class="' + Ext.baseCSSPrefix + 'column-options-leaf"></div>', '</tpl>', '<div class="' + Ext.baseCSSPrefix + 'column-options-text">{text}</div>', '<tpl if="groupable && dataIndex">', '<div class="' + Ext.baseCSSPrefix + 'column-options-groupindicator"></div>', '</tpl>', '<div class="' + Ext.baseCSSPrefix + 'column-options-visibleindicator"></div>', '</div>' ], triggerEvent: null, bufferSize: 1, minimumBufferSize: 1 }, store: { type: 'tree', fields: [ 'id', 'text', 'dataIndex', 'header', 'hidden', 'hiddenCls', 'grouped', 'groupedCls', 'groupable' ], root: { text: 'Columns' } }, clearSelectionOnListChange: false }, /** * 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' }, /** * @private */ _hiddenColumnCls: Ext.baseCSSPrefix + 'column-options-hidden', /** * @private */ _groupedColumnCls: Ext.baseCSSPrefix + 'column-options-grouped', /** * Determines the menu's visibility when the grid is loaded. */ sheetVisible: false, init: function(grid) { this.setGrid(grid); }, updateGrid: function(grid, oldGrid) { if (oldGrid) { oldGrid.getHeaderContainer().renderElement.un({ dragstart: 'onDragStart', drag: 'onDrag', dragend: 'onDragEnd', longpress: 'onHeaderLongPress', scope: this }); oldGrid.getHeaderContainer().un({ columnadd: 'onColumnAdd', columnmove: 'onColumnMove', columnremove: 'onColumnRemove', scope: this }); } if (grid) { grid.getHeaderContainer().renderElement.on({ dragstart: 'onDragStart', drag: 'onDrag', dragend: 'onDragEnd', longpress: 'onHeaderLongPress', scope: this }); grid.getHeaderContainer().on({ columnadd: 'onColumnAdd', columnmove: 'onColumnMove', columnremove: 'onColumnRemove', columnhide: 'onColumnHide', columnshow: 'onColumnShow', 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 }); list.on({ dragsort: 'onColumnReorder', delegate: '> list', scope: this }); this.attachTapListeners(); } }, updateSheet: function(sheet) { var sheetWidth = this.getSheetWidth(); sheet.setWidth(sheetWidth); sheet.translate(sheetWidth); sheet.add(this.getColumnList()); }, onDoneButtonTap: function() { this.hideViewOptions(); }, onColumnReorder: 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({ itemtap: 'onListItemTap', 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(); }, onListItemTap: function(list, index, row, record, e) { var me = this, handled = false; if (Ext.fly(e.target).is(me.getVisibleIndicatorSelector())) { me.onVisibleIndicatorTap(row, record, index); handled = true; } else if (Ext.fly(e.target).is(me.getGroupIndicatorSelector())) { me.onGroupIndicatorTap(row, record, index); handled = true; } return !handled; }, onVisibleIndicatorTap: function(row, record) { var hidden = !record.get('hidden'), column = Ext.getCmp(record.get('id')); if (hidden) { column.hide(); record.set('hidden', true); } else { column.show(); record.set('hidden', false); } }, onGroupIndicatorTap: function(row, record) { var me = this, grouped = !record.get('grouped'), store = me.getGrid().getStore(), groupedRecord = me._groupedRecord; if (groupedRecord) { groupedRecord.set('grouped', false); } if (grouped) { store.setGrouper({ property: record.get('dataIndex') }); me._groupedRecord = record; record.set('grouped', true); } else { store.setGrouper(null); me._groupedRecord = null; record.set('grouped', false); } }, 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(headerContainer, column, header) { if (column.getIgnore() || this.isMoving) { return; } var me = this, nestedList = me.getColumnList(), store = nestedList.getStore(), parentNode = store.getRoot(), hiddenCls = me._hiddenColumnCls, grid = me.getGrid(), isGridGrouped = grid.getGrouped(), grouper = grid.getStore().getGrouper(), dataIndex = column.getDataIndex(), data = { id: column.getId(), text: column.getText(), groupable: isGridGrouped && column.getGroupable(), hidden: column.isHidden(), hiddenCls: hiddenCls, grouped: !!(isGridGrouped && grouper && grouper.getProperty() === dataIndex), groupedCls: me._groupedColumnCls, dataIndex: column.getDataIndex(), leaf: true }, idx, headerNode; if (header) { headerNode = parentNode.findChild('id', header.getId()); if (!headerNode) { idx = header.getParent().indexOf(header); headerNode = parentNode.insertChild(idx, { groupable: false, header: true, hidden: header.isHidden(), hiddenCls: hiddenCls, id: header.getId(), text: header.getText() }); } idx = header.indexOf(column); parentNode = headerNode; } else { idx = headerContainer.indexOf(column); } parentNode.insertChild(idx, data); }, onColumnMove: function(headerContainer, column, header) { this.onColumnRemove(headerContainer, column); this.onColumnAdd(headerContainer, column, header); }, onColumnRemove: function(headerContainer, column) { if (column.getIgnore() || this.isMoving) { return; } var root = this.getColumnList().getStore().getRoot(), record = root.findChild('id', column.getId(), true); if (record) { record.parentNode.removeChild(record, true); } }, onDragStart: function() { var sheetWidth = this.getSheetWidth(), sheet = this.getSheet(); if (!this.sheetVisible) { sheet.translate(sheetWidth); this.startTranslate = sheetWidth; } else { sheet.translate(0); this.startTranslate = 0; } }, onDrag: function(e) { this.getSheet().translate(Math.max(this.startTranslate + e.deltaX, 0)); }, onDragEnd: function(e) { var me = this; if (e.flick.velocity.x > 0.1) { me.hideViewOptions(); } else { me.showViewOptions(); } }, onHeaderLongPress: function(e) { if (!this.sheetVisible) { this.showViewOptions(); } }, hideViewOptions: function() { var sheet = this.getSheet(); this.getGrid().getHeaderContainer().setSortable(true); sheet.translate(this.getSheetWidth(), 0, {duration: 100}); sheet.getTranslatable().on('animationend', function() { if (sheet.getModal()) { sheet.getModal().destroy(); sheet.setModal(null); } sheet.hide(null); }, this, {single: true}); this.sheetVisible = false; }, showViewOptions: function() { var me = this, sheet = me.getSheet(), modal = null; me.setup(); if (!me.sheetVisible) { // Since we may have shown the header in response to a longpress we don't // want the succeeeding "tap" to trigger column sorting, so we temporarily // disable sort-on-tap while the ViewOptions are shown me.getGrid().getHeaderContainer().setSortable(false); me.updateListInfo(); sheet.show(); sheet.translate(0, 0, {duration: 100}); sheet.getTranslatable().on('animationend', function() { sheet.setModal(true); modal = sheet.getModal(); modal.element.onBefore({ tap: 'hideViewOptions', dragstart: 'onDragStart', drag: 'onDrag', dragend: 'onDragEnd', scope: me }); }, me, {single: true}); me.sheetVisible = true; } }, privates: { setup: function() { var me = this, sheet; if (me.doneSetup) { return; } me.doneSetup = true; sheet = me.getSheet(); me.getGrid().add(sheet); sheet.translate(me.getSheetWidth()); sheet.down('button[role=donebutton]').on({ tap: 'onDoneButtonTap', scope: me }); }, updateListInfo: function() { var grid = this.getGrid(), store = grid.getStore(), grouper = store.getGrouper(), grouperProp = grouper && grouper.getProperty(), headerContainer = grid.getHeaderContainer(); this.getColumnList().getStore().getRoot().cascadeBy(function(node) { var dataIndex = node.get('dataIndex'); node.set('grouped', dataIndex && dataIndex === grouperProp); }); } }});