Sencha touch 2 custom list item button tap not fired - sencha-touch-2

I am developing a Hybrid Mobile App over sencha touch 2.
Now I was in a need of a custom component to be specific a custom list item consisting of a button along with.
My view has rendered as i wanted to but the button that is added to the list item is not firing the TAP event as expected. Instead on every tap, the ITEMTAP event is fired which is creating a bit of mess.
Can someone suggest me where to look to make this work ?
Below is the code for the custom component that i created:
var listView = {
xtype : "list",
id : "desk-list-search-results",
cls : "desk-list-search-results-cls",
selectedCls : "",
defaultType:"desksearchlistitem",
store : "deskstore",
flex : 2
};
This is the code for the custom component
Ext.define("MyApp.view.DeskSearchListItem",{
extend:"Ext.dataview.component.ListItem",
requires:["Ext.Button"],
alias:"widget.desksearchlistitem",
initialize:function()
{
this.callParent(arguments);
},
config:{
layout:{
type:"hbox",
align:"left"
},
cls:'x-list-item desk-search-list-item',
title:{
cls:"desk-list-item",
flex:0,
styleHtmlContent:true,
style:"align:left;"
},
image:{
cls:"circle_image",
width:"28px",
height:"28px"
},
button:{
cls:'x-button custom-button custom-font bookdesk-button',
flex:0,
text:"Book",
width:"113px",
height:"46px",
hidden:true
},
dataMap:{
getTitle:{
setHtml:'title'
}
}
},
applyButton:function(config)
{
return Ext.factory(config,Ext.Button,this.getButton());
},
updateButton:function(newButton,oldButton)
{
if(newButton)
{
this.add(newButton);
}
if(oldButton)
{
this.remove(oldButton);
}
},
applyTitle:function(config)
{
return Ext.factory(config,Ext.Component,this.getTitle());
},
updateTitle:function(newTitle,oldTitle)
{
if(newTitle)
{
this.add(newTitle);
}
if(oldTitle)
{
this.remove(oldTitle);
}
},
applyImage:function(config)
{
return Ext.factory(config,Ext.Component,this.getImage());
},
updateImage:function(newImage,oldImage)
{
if(newImage)
{
this.add(newImage);
}
if(oldImage)
{
this.remove(oldImage);
}
}
})

Finally got a solution to this,
It can be done into the view using the listeners object.
Here is the code sample for it.
listeners:{
initialize:function()
{
var dataview = this;
dataview.on("itemtap",function(list,index,target,record,event){
event.preventDefault();
});
dataview.on("itemswipe",function(list,index,target,record,event){
if(event.direction === "right")
{
var buttonId = target.element.down(".bookdesk-button").id;
var buttonEl = Ext.ComponentQuery.query("#"+buttonId)[0];
if(Ext.isObject(buttonEl))
{
buttonEl.setZIndex(9999);
buttonEl.show({
showAnimation:{
type:"slideIn",
duration:500
}
});
var listeners = {
tap:function(btn,e,opt)
{
console.log("Button Tapped");
}
};
buttonEl.setListeners(listeners);
}
else
{
console.log("This is not a valid element");
}
}
});
}
}
Thanks Anyways.

Related

Titanium Alloy: Actionbar & DrawerMenu

Recently I started working on a multi-platform application using Titanium Alloy.
One of the things I'd like to achieve is having a menu button in the actionbar (infront of the appicon).
When pressed, it toggles the drawermenu.
After a little investigation I failed to find a widget / module that could offer me both. So I decided to use a combination of com.alcoapps.actionbarextras and com.mcongrove.slideMenu.
Both a custom actionbar and a drawer option seem to functionate as they appear they should.
The problem is however, that it does show the 'menu' image, it is clickable, but I have no idea how to attach an event to it.
I've tried several ways, like binding the event to the entire actionbar of what so ever.. But I can't seem to find the appropriate binding to accomplish this.
index.js
var abextras = require('com.alcoapps.actionbarextras');
$.MainWindow.on('open', function(evt) {
// set extras once the Activity is available
abextras.title = "Test Window";
abextras.homeAsUpIcon = "/images/menu.png";
//abextras.titleColor = "#840505";
//abextras.subtitle = "for some extra action";
//abextras.subtitleFont = "Chunkfive.otf";
//abextras.subtitleColor = "#562A2A";
//abextras.backgroundColor = "#F49127";
var activity = evt.source.activity;
if (activity) {
activity.onCreateOptionsMenu = function(e) {
e.menu.clear();
activity.actionBar.displayHomeAsUp = true;
//abextras.setHomeAsUpIcon("/images/menu.png");
//activity.actionBar.addEventListener("click", function(ev) {
// console.log("HI");
//});
};
}
/*
// now set the menus
evt.source.activity.onCreateOptionsMenu = function(e) {
// aboutBtn and creditsBtn will be displayed in the menu overflow
aboutBtn = e.menu.add({
title : "About",
showAsAction : Ti.Android.SHOW_AS_ACTION_NEVER
});
aboutBtn.addEventListener("click", function(e) {
console.log('Clicked on About');
});
creditsBtn = e.menu.add({
title : "Credits",
showAsAction : Ti.Android.SHOW_AS_ACTION_NEVER
});
creditsBtn.addEventListener("click", function(e) {
console.log('Clicked on Credits');
});
// create the Share intent and add it to the ActionBar
var intent = Ti.Android.createIntent({
action : Ti.Android.ACTION_SEND,
type : 'text/plain'
});
intent.putExtra(Ti.Android.EXTRA_TEXT, 'Hello world!');
abextras.addShareAction({
menu : e.menu,
//intent : intent
});
};
*/
});
function doClick(e) {
alert($.label.text);
}
// Create our node items
var nodes = [{
menuHeader : "My Tabs",
id : 0,
title : "Home",
image : "/images/home.png"
}, {
id : 1,
title : "Contact",
image : "/images/phone.png"
}, {
id : 2,
title : "Settings",
image : "/images/gear.png"
}];
// Initialize the slide menu
$.SlideMenu.init({
nodes : nodes,
color : {
headingBackground : "#000",
headingText : "#FFF"
}
});
// Set the first node as active
$.SlideMenu.setIndex(0);
// Add an event listener on the nodes
$.SlideMenu.Nodes.addEventListener("click", handleMenuClick);
// Handle the click event on a node
function handleMenuClick(_event) {
if ( typeof _event.row.id !== "undefined") {
// Open the corresponding controller
openScreen(_event.row.id);
}
}
function openMenu() {
$.AppWrapper.animate({
left : "300dp",
duration : 250,
curve : Ti.UI.ANIMATION_CURVE_EASE_IN_OUT
});
$.SlideMenu.Wrapper.animate({
left : "0dp",
duration : 250,
curve : Ti.UI.ANIMATION_CURVE_EASE_IN_OUT
});
toggleMenu();
}
function closeMenu() {
$.AppWrapper.animate({
left : "0dp",
duration : 250,
curve : Ti.UI.ANIMATION_CURVE_EASE_IN_OUT
});
$.SlideMenu.Wrapper.animate({
left : "-300dp",
duration : 250,
curve : Ti.UI.ANIMATION_CURVE_EASE_IN_OUT
});
toggleMenu();
}
function toggleMenu() {
//
console.log($.AppWrapper.left);
}
$.AppWrapper.addEventListener("swipe", function(_event) {
if (_event.direction == "right") {
openMenu();
} else if (_event.direction == "left") {
closeMenu();
}
});
$.MainWindow.open();
//$.index.open();
index.xml
<Alloy>
<Window class="container" id="MainWindow">
<Require type="widget" src="com.mcongrove.slideMenu" id="SlideMenu" />
<View id="AppWrapper">
<Label text="Profile View" />
</View>
</Window>
</Alloy>
I hope people with more knowledge about Titanium and/or these modules could guide me.
Kind Regards, larssy1
After contacting the creator of the widget, the outcome is as the following:
var activity = evt.source.activity;
if (activity){
activity.actionBar.onHomeIconItemSelected = function() {
// your callback here
alert('I was clicked');
}
}

titanium display hundreds of rows with tableviewrow

using these lines of code I can display aproximately hundreds of tableviewrows in a tableview. The problem is that the window is opening in 3 seconds (android device). I guess there's some optimization I have to do in order to display the table in less than 1 sec.
any advice about it?
thanks in advance
EDIT
lines of code
module.exports.draw = function(){
els = [];
var cocktails = Ti.App.cocktails;
for(var i=0;i<cocktails.length;i++){
els.push({
type: 'Ti.UI.View',
searchableText : cocktails[i].nome,
properties : {
cocktail_id:cocktails[i].id,
borderColor:"#eee",
borderWidth: 1,
height: 100,
nome:cocktails[i].nome
},
childTemplates : [
{
type: 'Ti.UI.Label',
bindId : cocktails[i].id,
properties : {
text: cocktails[i].nome,
cocktail_id:cocktails[i].id,
color:"#000",
left:30,
zIndex:10,
top:10,
font:{
fontSize:20,
fontWeight:'bold'
}
},
events : {
click : function(e) {
Ti.App.fireEvent("render",{pag:'prepare',id:e.bindId});
}
}
},
{
type : 'Ti.UI.Label',
properties : {
left:30,
color:"#999",
top:50,
cocktail_id:cocktails[i].id,
text:cocktails[i].ingTxt != undefined?cocktails[i].ingTxt:''
},
bindId:cocktails[i].id,
events : {
click : function (e){
Ti.App.fireEvent("render",{pag:'prepare',id:e.bindId});
}
}
}
]
});
}
var search = Ti.UI.createSearchBar({
height:50,
width:'100%'
});
search.addEventListener('cancel', function(){
search.blur();
});
var content = Ti.UI.createListView({sections:[Ti.UI.createListSection({items: els})],searchView:search});
search.addEventListener('change', function(e){
content.searchText = e.value;
});
return content;
};
You need to look into lazy loading
http://www.appcelerator.com/blog/2013/06/quick-tip-cross-platform-tableview-lazy-loading/
As thiswayup suggests, lazy loading would probably be a very good idea.
If you do not wan't to use lazy loading you could do this:
function getListWindow( items ) {
var firstLayout = true;
var win = Ti.UI.createWindow({
//Your properties here.
});
var list = Ti.UI.createTableView({
data : [],
//other properties here
});
win.add(list);
win.addEventListener('postlayout', function() {
if(firstLayout) {
firstLayout = false;
var rows = [];
//Assuming the items argument is an array.
items.forEach(function( item ) {
rows.push( Ti.UI.createTableViewRow({
//Properties here based on item
}));
});
list.data = rows;
}
});
return win;
}
Doing this will open your window right away and load the rows after the window is shown. Showing a loader while the rows are being generated would probably be a good idea.

How to hide button is Sencha Touch 2.1

I've found similar questions on this site but none of the answers has been working for me. This is button I want to hide (view):
{
xtype: 'button',
id: 'btn_messenger',
text: 'Messenger'
}
Controller for that view has init function:
init : function() {
var me = this;
me.callParent();
me.hideMessengerButton(me);
}
This is the function that should hide button:
hideMessengerButton: function(me) {
var user = me.getLoggedUser(); // return user or undefined
if (user == undefined) {
Ext.select('#btn_messenger').hide(); // Does nothing
}
}
I've tried these options:
Ext.getCmp('btn_messenger').hide(); // Ext.getCmp('btn_messenger') returns undefined
Ext.getCmp('#btn_messenger').hide(); // Ext.getCmp('#btn_messenger') returns undefined
In controller's refs there is btn_messenger: '#btn_messenger', so I've tried:
this.getBtn_messenger().hide() // this.getBtn_messenger() returns undefined
Thanks for help in advance.
PS.: I don't know if this matters but the view mentioned above is not the main view. It is pushed after button tap on the main view.
EDIT:
Here is the controller:
Ext.define('First.controller.HomePage', {
extend : 'First.controller.Controller',
requires : ['First.view.Main', 'First.view.HomePage'],
config : {
refs : {
pnl_home: 'pnl_home',
btn_messenger : '#btn_messenger'
},
control : {
btn_messenger : {
tap : 'btn_openMessenger'
}
pnl_home: {
show: 'hideMessengerButton'
}
},
},
init : function() {
var me = this;
me.callParent();
},
btn_openMessenger : function() {
Ext.Msg.alert('Open', 'Messenger');
},
/**
* Hide meseenger button when user's not logged in
*/
hideMessengerButton : function() {
var me = this;
var user = me.getLoggedUser();
if (user == undefined) {
me.getBtn_messenger().setHidden(true);
}
}
});
http://docs.sencha.com/touch/2-1/#!/api/Ext.Button-method-setHidden
in your action
replaces refs
refs : {
btnMessenger: '#btn_messenger'
},
then when you need this button on action use next
this.getBtnMessenger().setHidden(true)
also on init button are not yet added to dom as i remember
lauch : function() {
this.hideMessengerButton();
},
hideMessengerButton: function() {
var user = this.getLoggedUser(); // return user or undefined
if (user == undefined) {
this.getBtnMessenger().setHidden(true); // Does nothing
}
}
I've added pnl_home: 'pnl_home', to refs and called show event on the panel. Modified code can be found above, in my initial post.

Routing/Modularity in Dojo (Single Page Application)

I worked with backbone before and was wondering if there's a similar way to achieve this kind of pattern in dojo. Where you have a router and pass one by one your view separately (like layers) and then you can add their intern functionality somewhere else (e.g inside the view) so the code is very modular and can be change/add new stuff very easily. This code is actually in jquery (and come from a previous project) and it's a "common" base pattern to develop single application page under jquery/backbone.js .
main.js
var AppRouter = Backbone.Router.extend({
routes: {
"home" : "home"},
home: function(){
if (!this.homeView) {
this.homeView= new HomeView();
}
$('#content').html(this.homeView.el);
this.homeView.selectMenuItem('home-link');
}};
utils.loadTemplate(['HomeView'], function() {
app = new AppRouter();
Backbone.history.start();
});
utils.js
loadTemplate: function(views, callback) {
var deferreds = [];
$.each(views, function(index, view) {
if (window[view]) {
deferreds.push($.get('tpl/' + view + '.html', function(data) {
window[view].prototype.template = _.template(data);
}));
} else {
alert(view + " not found");
}
});
$.when.apply(null, deferreds).done(callback);
}};
HomeView.js
window.HomeView = Backbone.View.extend({
initialize:function () {
this.render();
},
render:function () {
$(this.el).html(this.template());
return this;
}
});
And basically, you just pass the html template. This pattern can be called anywhere with this link:
<li class="active"><i class="icon-home"></i> Dashboard</li>
Or, what is the best way to implement this using dojo boilerplate.
The 'boilerplate' on this subject is a dojox.mvc app. Reference is here.
From another aspect, see my go at it a while back, ive setup an abstract for 'controller' which then builds a view in its implementation.
Abstract
Then i have an application controller, which does following on its menu.onClick
which fires loading icon,
unloads current pane (if forms are not dirty)
loads modules it needs (defined 'routes' in a main-menu-store)
setup view pane with a new, requested one
Each view is either simply a server-html page or built with a declared 'oocms' controller module. Simplest example of abstract implementation here . Each implements an unload feature and a startup feature where we would want to dereference stores or eventhooks in teardown - and in turn, assert stores gets loaded etc in the setup.
If you wish to use templates, then base your views on the dijit._TemplatedMixin
edit
Here is a simplified clarification of my oocms setup, where instead of basing it on BorderLayout, i will make it ContentPanes:
Example JSON for the menu, with a single item representing the above declared view
{
identifier: 'view',
label: 'name',
items: [
{ name: 'myForm', view: 'App.view.MyForm', extraParams: { foo: 'bar' } }
]
}
Base Application Controller in file 'AppPackagePath/Application.js'
Note, the code has not been tested but should give a good impression of how such a setup can be implemented
define(['dojo/_base/declare',
"dojo/_base/lang",
"dijit/registry",
"OoCmS/messagebus", // dependency mixin which will monitor 'notify/progress' topics'
"dojo/topic",
"dojo/data/ItemFileReadStore",
"dijit/tree/ForestStoreModel",
"dijit/Tree"
], function(declare, lang, registry, msgbus, dtopic, itemfilereadstore, djforestmodel, djtree) {
return declare("App.Application", [msgbus], {
paneContainer: NULL,
treeContainer: NULL,
menuStoreUrl: '/path/to/url-list',
_widgetInUse: undefined,
defaultPaneProps: {},
loading: false, // ismple mutex
constructor: function(args) {
lang.mixin(this, args);
if(!this.treeContainer || !this.paneContainer) {
console.error("Dont know where to place components")
}
this.defaultPaneProps = {
id: 'mainContentPane'
}
this.buildRendering();
},
buildRendering: function() {
this.menustore = new itemfilereadstore({
id: 'appMenuStore',
url:this.menuStoreUrl
});
this.menumodel = new djforestmodel({
id: 'appMenuModel',
store: this.menustore
});
this.menu = new djtree( {
model: this.menumodel,
showRoot: false,
autoExpand: true,
onClick: lang.hitch(this, this.paneRequested) // passes the item
})
// NEEDS a construct ID HERE
this.menu.placeAt(this.treeContainer)
},
paneRequested: function(item) {
if(this.loading || !item) {
console.warn("No pane to load, give me a menustore item");
return false;
}
if(!this._widgetInUse || !this._widgetInUse.isDirty()) {
dtopic.publish("notify/progress/loading");
this.loading = true;
}
if(typeof this._widgetInUse != "undefined") {
if(!this._widgetInUse.unload()) {
// bail out if widget says 'no' (isDirty)
return false;
}
this._widgetInUse.destroyRecursive();
delete this._widgetInUse;
}
var self = this,
modules = [this.menustore.getValue(item, 'view')];
require(modules, function(viewPane) {
self._widgetInUse = new viewPane(self.defaultProps);
// NEEDS a construct ID HERE
self._widgetInUse.placeAt(this.paneContainer)
self._widgetInUse.ready.then(function() {
self.paneLoaded();
})
});
return true;
},
paneLoaded: function() {
// hide ajax icons
dtopic.publish("notify/progress/done");
// assert widget has started
this._widgetInUse.startup();
this.loading = false;
}
})
})
AbstractView in file 'AppPackagePath/view/AbstractView.js':
define(["dojo/_base/declare",
"dojo/_base/Deferred",
"dojo/_base/lang",
"dijit/registry",
"dijit/layout/ContentPane"], function(declare, deferred, lang, registry, contentpane) {
return declare("App.view.AbstractView", [contentpane], {
observers: [], // all programmatic events handles should be stored for d/c on unload
parseOnLoad: false,
constructor: function(args) {
lang.mixin(this, args)
// setup ready.then resolve
this.ready = new deferred();
// once ready, create
this.ready.then(lang.hitch(this, this.postCreate));
// the above is actually not nescessary, since we could simply use onLoad in contentpane
if(typeof this.content != "undefined") {
this.set("content", this.content);
this.onLoad();
} else if(typeof 'href' == "undefined") {
console.warn("No contents nor href set in construct");
}
},
startup : function startup() {
this.inherited(arguments);
},
// if you override this, make sure to this.inherited(arguments);
onLoad: function() {
dojo.parser.parse(this.contentNode);
// alert the application, that loading is done
this.ready.resolve(null);
// and call render
this.render();
},
render: function() {
console.info('no custom rendering performed in ' + this.declaredClass)
},
isDirty: function() { return false; },
unload: function() {
dojo.forEach(this.observers, dojo.disconnect);
return true;
},
addObserver: function() {
// simple passthrough, adding the connect to handles
var handle = dojo.connect.call(dojo.window.get(dojo.doc),
arguments[0], arguments[1], arguments[2]);
this.observers.push(handle);
}
});
});
View implementation sample in file 'AppPackagePath/view/MyForm.js':
define(["dojo/_base/declare",
"dojo/_base/lang",
"App/view/AbstractView",
// the contentpane href will pull in some html
// in the html can be markup, which will be renderered when ready
// pull in requirements here
"dijit/form/Form", // markup require
"dijit/form/Button" // markup require
], function(declare, lang, baseinterface) {
return declare("App.view.MyForm", [baseinterface], {
// using an external HTML file
href: 'dojoform.html',
_isDirty : false,
isDirty: function() {
return this._isDirty;
},
render: function() {
var self = this;
this.formWidget = dijit.byId('embeddedForm') // hook up with loaded markup
// observer for children
dojo.forEach(this.formWidget._getDescendantFormWidgets(), function(widget){
if(! lang.isFunction(widget.onChange) )
console.log('unable to observe ' + widget.id);
self.addObserver(widget, 'onChange', function() {
self._isDirty = true;
});
});
//
},
// #override
unload: function() {
if(this.isDirty()) {
var go = confirm("Sure you wish to leave page before save?")
if(!go) return false;
}
return this.inherited(arguments);
}
})
});

Add a custom button in column header dropdown menus {EXTJS 4}

I want a button in column header dropdown menu of grid in extjs4.
so that i can add or delete columns which are linked in database.
Any help will be appreciated...
Thankyou..:)
Couple of months ago I had the same problem. I've managed to solve it by extending Ext.grid.header.Container (I've overrided getMenuItems method). However, recently, I've found another solution which requires less coding: just add menu item manualy after grid widget is created.
I'll post the second solution here:
Ext.create('Ext.grid.Panel', {
// ...
listeners: {
afterrender: function() {
var menu = this.headerCt.getMenu();
menu.add([{
text: 'Custom Item',
handler: function() {
var columnDataIndex = menu.activeHeader.dataIndex;
alert('custom item for column "'+columnDataIndex+'" was pressed');
}
}]);
}
}
});
Here is demo.​
UPDATE
Here is demo for ExtJs4.1.
From what I have been seeing, you should avoid the afterrender event.
Context:
The application I am building uses a store with a dynamic model. I want my grid to have a customizable model that is fetched from the server (So I can have customizable columns for my customizable grid).
Since the header wasn't available to be modified (since the store gets reloaded and destroys the existing menu that I modified - using the example above). An alternate solution that has the same effect can be executed as such:
Ext.create('Ext.grid.Panel', {
// ...
initComponent: function () {
// renders the header and header menu
this.callParent(arguments);
// now you have access to the header - set an event on the header itself
this.headerCt.on('menucreate', function (cmp, menu, eOpts) {
this.createHeaderMenu(menu);
}, this);
},
createHeaderMenu: function (menu) {
menu.removeAll();
menu.add([
// { custom item here }
// { custom item here }
// { custom item here }
// { custom item here }
]);
}
});
For people who would like to have not just one "standard" column menu but have an individual columnwise like me, may use the following
initComponent: function ()
{
// renders the header and header menu
this.callParent(arguments);
// now you have access to the header - set an event on the header itself
this.headerCt.on('menucreate', function (cmp, menu, eOpts) {
menu.on('beforeshow', this.showHeaderMenu);
}, this);
},
showHeaderMenu: function (menu, eOpts)
{
//define array to store added compoents in
if(this.myAddedComponents === undefined)
{
this.myAddedComponents = new Array();
}
var columnDataIndex = menu.activeHeader.dataIndex,
customMenuComponents = this.myAddedComponents.length;
//remove components if any added
if(customMenuComponents > 0)
{
for(var i = 0; i < customMenuComponents; i++)
{
menu.remove(this.myAddedComponents[i][0].getItemId());
}
this.myAddedComponents.splice(0, customMenuComponents);
}
//add components by column index
switch(columnDataIndex)
{
case 'xyz': this.myAddedComponents.push(menu.add([{
text: 'Custom Item'}]));
break;
}
}
I took #nobbler's answer an created a plugin for this:
Ext.define('Ext.grid.CustomGridColumnMenu', {
extend: 'Ext.AbstractPlugin',
init: function (component) {
var me = this;
me.customMenuItemsCache = [];
component.headerCt.on('menucreate', function (cmp, menu) {
menu.on('beforeshow', me.showHeaderMenu, me);
}, me);
},
showHeaderMenu: function (menu) {
var me = this;
me.removeCustomMenuItems(menu);
me.addCustomMenuitems(menu);
},
removeCustomMenuItems: function (menu) {
var me = this,
menuItem;
while (menuItem = me.customMenuItemsCache.pop()) {
menu.remove(menuItem.getItemId(), false);
}
},
addCustomMenuitems: function (menu) {
var me = this,
renderedItems;
var menuItems = menu.activeHeader.customMenu || [];
if (menuItems.length > 0) {
if (menu.activeHeader.renderedCustomMenuItems === undefined) {
renderedItems = menu.add(menuItems);
menu.activeHeader.renderedCustomMenuItems = renderedItems;
} else {
renderedItems = menu.activeHeader.renderedCustomMenuItems;
menu.add(renderedItems);
}
Ext.each(renderedItems, function (renderedMenuItem) {
me.customMenuItemsCache.push(renderedMenuItem);
});
}
}
});
This is the way you use it (customMenu in the column config let you define your menu):
Ext.define('MyGrid', {
extend: 'Ext.grid.Panel',
plugins: ['Ext.grid.CustomGridColumnMenu'],
columns: [
{
dataIndex: 'name',
customMenu: [
{
text: 'My menu item',
menu: [
{
text: 'My submenu item'
}
]
}
]
}
]
});
The way this plugin works also solves an issue, that the other implementations ran into. Since the custom menu items are created only once for each column (caching of the already rendered version) it will not forget if it was checked before or not.