@coon-js/extjs-lib-comp
Version:
NPM package providing component library for ExtJS projects
490 lines (396 loc) • 12.1 kB
JavaScript
/**
* coon.js
* extjs-lib-comp
* Copyright (C) 2017-2021 Thorsten Suckow-Homberg https://github.com/coon-js/extjs-lib-comp
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
* USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/**
* Grid Feature which renders a menu on top of a grid row. Best used with
* grid configurations with enabled rowBodies.
*
*
* @example
*
* Ext.define('MyGrid', {
*
* extend : 'Ext.grid.Panel',
*
* features : [{
* ftype : 'cn_comp-gridfeature-rowflymenu',
* id : 'cn_mail-mailMessageFeature-rowFlyMenu',
* items : [{
* cls : 'fa fa-envelope-o',
* title : 'Mark as Unread',
* action : 'markunread',
* id : 'cn_mail-mailMessageFeature-rowFlyMenu-markUnread'
* }],
* alignTo : ['tr-tr', [-12, 4]]
* }],
*
* store : {
* fields : ['isRead', 'subject', 'to'],
* data : [{
* isRead : false, subject : 'Subject 1', to : 'to 1'
* }, {
* isRead : true, subject : 'Subject 2', to : 'to 2'
* }, {
* isRead : false, subject : 'Subject 3', to : 'to 3'
* }],
* },
*
* columns : [{
* text : 'Read',
* dataIndex : 'isRead',
* }, {
* text : 'Subject',
* dataIndex : 'subject'
* }, {
* text : 'To',
* dataIndex : 'to'
* }]
*
* });
*
* var myGrid = Ext.create('MyGrid', {
* renderTo : document.body
* ));
*
* Example css working with RowFlyMenu:
*
* .cn_comp-rowflymenu {
*
* position : absolute;
* z-index : 100000;
* background-color:inherit;
*
* .cn-item {
* margin : 4px 2px 2px 6px;
* cursor : pointer;
* float : left;
*
* &.cn-over {
* color : grey;
* }
* }
*
* }
*
*/
Ext.define("coon.comp.grid.feature.RowFlyMenu", {
extend: "Ext.grid.feature.Feature",
alias: "feature.cn_comp-gridfeature-rowflymenu",
cls: "cn_comp-rowflymenu",
/**
* Gets firec when an item of this menu was clicked that was configured
* with an action.
* @event itemclick
* @param this
* @param {HtmlElement} item
* @param {String} action
* @param {Ext.data.Model} record The record for which the menu was shown
*/
/**
* Gets fired when this menu is about to get shown.
* @event beforemenushow
* @param this
* @param {HtmlElement} item
* @param {Ext.data.Model} record The record for which the menu will be shown
*/
/**
* Maps item-ids to their actions.
* @type {Object}
* @private
*/
idToActionMap: null,
/**
* The menu that gets created from #items for this Feature.
* @type {Ext.Element}
* @private
*/
menu: null,
/**
* @cfg {Array} An array of element-configurations representing the items
* for the RowFlyMenu. See #processItems
* @cfg {Array} items
*/
items: null,
/**
* Lets you specify how the RowFlyMenu gets aligned to the rowitem for which
* it is being shown. If configured as an array, the array should comply
* with the specificatiosn of the alignTo()-method.
* @cfg {String/Array} alignTo
*/
alignTo: "tr-tr",
/**
* The record represented by the grid row for which the menu is currently
* shown.
* @tape {Ext.data.Model}
* @private
*/
currentRecord: null,
/**
* @inheritdoc
*/
init: function (grid) {
const me = this;
me.menu = me.buildMenu(me.items);
me.mon(me.menu, {
"tap": me.onMenuClick,
"mouseover": me.onMenuMouseOver,
"mouseout": me.onMenuMouseOut,
scope: me
});
delete me.items;
me.installListeners(grid);
me.callParent(arguments);
},
/**
* Callback for the menu's mouseover event.
*
* @param {Ext.Event}evt
* @param {HtmlElement} target
*/
onMenuMouseOver: function (evt, target) {
if (target.className.indexOf("cn-item") !== -1) {
this.grid.on("beforeselect", this.preventSelection);
Ext.fly(target).addCls("cn-over");
}
},
/**
* Callback for the menu's mouseout event.
*
* @param {Ext.Event}evt
* @param {HtmlElement} target
*/
onMenuMouseOut: function (evt, target) {
if (target.className.indexOf("cn-item") !== -1) {
this.grid.un("beforeselect", this.preventSelection);
Ext.fly(target).removeCls("cn-over");
}
},
/**
* Callback for the tap-event of this feature's menu. Will try to mao the
* event-source's id to an action (if specified for #items). If an action was
* found, the event #itemclick is fired with appropriate information.
*
* @param {Ext.Event} evt
* @param {HtmlElement} target
*/
onMenuClick: function (evt, target) {
const me = this,
idToAction = me.idToActionMap,
action = idToAction[target.id];
evt.stopEvent();
if (!action) {
return;
}
me.fireEvent("itemclick", me, target, action, me.currentRecord);
},
/**
* Callback for the itemmouseenter-event of this feature's grid. Makes sure
* the menu is shown and aligned properly to this event's HTML-source (item).
* Does nothing if this feature is disabled.
* Triggers the #beforemenushow-event. If any listener returns false, te
* menu will not get shown.
*
* @param {Ext.view.Table} view
* @param {Ext.data.Model} record
* @param {HtmlElement} item
* @param {Number} index
* @param {Ext.Event} e
* @param {Object} eOpts
*
* @private
*/
onItemMouseEnter: function (view, record, item, index, e, eOpts) {
const me = this;
if (me.disabled) {
return;
}
e.stopEvent();
me.currentRecord = record;
if (me.fireEvent("beforemenushow", me, item, record) === false) {
me.currentRecord = null;
return;
}
Ext.fly(item).appendChild(me.menu);
me.menu.show();
me.menu.alignTo.apply(me.menu, [item].concat(me.alignTo));
},
/**
* Callback for the itemmouseleave-event of this feature's grid. Makes sure
* the menu is hidden again.
* Does nothing if this feature is disabled.
*
* @param {Ext.view.Table} view
* @param {Ext.data.Model} record
* @param {HtmlElement} item
* @param {Number} index
* @param {Ext.Event} e
* @param {Object} eOpts
*
* @private
*
* @see detachMenuAndUnset
*/
onItemMouseLeave: function (view, record, item, index, e, eOpts) {
const me = this;
if (me.disabled) {
return;
}
e.stopEvent();
me.detachMenuAndUnset();
},
/**
* Installs the callbacks for itemmouseenter- and itemmouseleave-events of
* the specified grid.
*
* @param {Ext.grid.Panel} grid
*
* @private
*/
installListeners: function (grid) {
const me = this;
grid.view.on("beforerefresh", me.onBeforeGridViewRefresh, me);
grid.on("itemmouseenter", me.onItemMouseEnter, me);
grid.on("itemmouseleave", me.onItemMouseLeave, me);
grid.on("beforeitemkeydown", me.onBeforeItemKeyDown, me);
},
/**
* Processes the specified items and creates an Ext.dom.Element wrapping the
* native HTMLElement. The created Ext.Element's skipGarbageCollection-property
* is explicitly set to true to prevent it from being removed by ExtJS'
* (DOM-)GarbageCollector.
*
* @param {Array} items The items for this RowFlyMenu
*
* @return {Ext.dom.Element}
*
* @see #processItems
*
* @private
*/
buildMenu: function (items) {
const me = this;
let childs = me.processItems(items), el;
let re = Ext.Element.create({
tag: "div",
cls: me.cls,
children: childs
}, true);
el = Ext.create("Ext.Element", re);
el.skipGarbageCollection = true;
return el;
},
/**
* Processes configuration items and returns them decorated with needed
* informations for this class.
* Fills the idToActionMap-object.
*
* @param {Array} items
*
* @private
*/
processItems: function (items) {
const me = this;
let childs = [], item;
me.idToActionMap = {};
for (let i = 0, len = items.length; i < len; i++) {
item = Ext.apply({}, items[i]);
item.tag = item.tag ? item.tag : "div";
item.cls = item.cls ? "cn-item " + item.cls : "cn-item";
item.id = item.id ? item.id : Ext.id();
if (item.action) {
me.idToActionMap[item.id] = item.action;
delete item.action;
}
childs.push(item);
}
return childs;
},
/**
* @inheritdoc
*/
disable: function () {
const me = this;
me.detachMenuAndUnset();
me.callParent(arguments);
},
/**
* Callback for the grid view's beforerefresh-event.
* Delegates to #detachMenu
*
* @private
*
* @see detachMenuAndUnset
*/
onBeforeGridViewRefresh: function () {
const me = this;
me.detachMenuAndUnset();
},
/**
* Detaches the #menu from any parent it currently has and sets the
* #currentRecord for this menu to null.
*
*/
detachMenuAndUnset: function () {
const me = this,
menu = me.menu;
if (menu.dom.parentNode) {
Ext.fly(menu.dom.parentNode).removeChild(menu);
}
me.grid.un("beforeselect", me.preventSelection);
me.currentRecord = null;
},
/**
* Makes sure #menu gets destroyed.
*
* @inheritdoc
*/
destroy: function () {
const me = this;
if (me.menu) {
me.menu.destroy();
me.menu = null;
}
return me.callParent(arguments);
},
/**
* Named helper function used by the beforeselect-listener of this menu.
* Makes sure no grid item is selected when the menu is clicked.
*
* @returns {boolean} false
*
* @private
*/
preventSelection: function () {
return false;
},
/**
* Named helper function to make sure itemkeydown invokes detachMenuAndUnset
* to make sure #preventSelection does not block key navigation and selection.
*
* @private
*/
onBeforeItemKeyDown: function () {
this.detachMenuAndUnset();
}
});