Extended ExtJS Class can't find custom listener function - "oe is undefined" - extjs

I'm adding a custom context menu to a TreePanel.
This was all working when I had a separate function for the context menu, but I was having problems where the context menu items would end up doubled/tripling up if I clicked on one of the options and then viewed the context menu again.
I had a look around for other contextmenu examples and came up with this one by Aaron Conran I pretty much "stole" it wholesale with a few additions, tacking the function directly into the Ext.ext.treePanel config. This gave me an error about "oe is undefined" which seemed to refer to "contextmenu: this.onContextMenu" in the tree config.
I figured it was probably something to do with the way I was defining all of this, so I decided to look at extending Ext.ext.TreePanel with my function in it as a learning exercise as much as anything.
Unfortunately, having managed to sort out extending TreePanel I'm now back to getting "oe is undefined" when the page tries to build the TreePanel. I've had a look around and I'm not really sure whats causing the problem, so any help or suggestions would be greatly appreciated.
Here is the code that is used to define/build the tree panel. I hope its not too horrible.
siteTree = Ext.extend(Ext.tree.TreePanel,{
constructor : function(config){
siteTree.superclass.constructor.call(this, config);
},
onContextMenu: function(n,e){
if (!this.contextMenu){
console.log('treeContextMenu',n,e);
if (n.parentNode.id == 'treeroot'){
var menuitems = [{text:'Add Child',id:'child'}];
} else {
var menuitems =
[{text:'Add Child',id:'child'},
{text:'Add Above',id:'above'},
{text:'Add Below',id:'below'}];
}
this.contextMenu = new Ext.menu.Menu({
id:'treeContextMenu',
defaults :{
handler : treeContextClick,
fqResourceURL : n.id
},
items : menuitems
});
}
var xy = e.getXY();
this.contextMenu.showAt(xy);
}
});
var treePanel = new siteTree({
id: 'tree-panel',
title : 'Site Tree',
region : 'center',
height : 300,
minSize: 150,
autoScroll: true,
// tree-specific configs:
rootVisible: false,
lines: false,
singleExpand: true,
useArrows: true,
dataUrl:'admin.page.getSiteTreeChildren?'+queryString,
root: {
id: 'treeroot',
nodeType: 'async',
text: 'nowt here',
draggable: false
},
listeners:{
contextmenu: this.onContextMenu
}
});
As a total aside; Is there a better way to do this in my context menu function?
if (n.parentNode.id == 'treeroot') {
Basically, if the clicked node is the top level I only want to give the user an add Child option, not add above/below.
Thanks in advance for your help

In your instantiation of your siteTree class you have:
listeners: {
contextmenu: this.onContextMenu
}
However, at the time of the instantiation this.onContextMenu is not pointing to the onContextMenu method you defined in siteTree.
One way of fixing it is to call the method from within a wrapper function:
listeners: {
contextmenu: function() {
this.onContextMenu();
}
}
Assuming you don't override the scope in the listeners config 'this' will be pointing to the siteTree instance at the time the listener is executed.
However, since you are already defining the context menu in the siteTree class, you may as well define the listener there:
constructor: function( config ) {
siteTree.superclass.constructor.call(this, config);
this.on('contextmenu', this.onContextMenu);
}
Ensuring the context menu is removed with the tree is also a good idea. This makes your siteTree definition:
var siteTree = Ext.extend(Ext.tree.TreePanel, {
constructor: function( config ) {
siteTree.superclass.constructor.call(this, config);
this.on('contextmenu', this.onContextMenu);
this.on('beforedestroy', this.onBeforeDestroy);
},
onContextMenu: function( node, event ) {
/* create and show this.contextMenu as needed */
},
onBeforeDestroy: function() {
if ( this.contextMenu ) {
this.contextMenu.destroy();
delete this.contextMenu;
}
}
});

I had this problem yesterday. The issue with the duplicate and triplicate items in the context menu is due to extjs adding multiple elements to the page with the same ID. Each time you call this.contextMenu.showAt(xy) you are adding a div with the ID 'treeContextMenu' to the page. Most browsers, IE especially, deal with this poorly. The solution is to remove the old context menu before adding the new one.
Here is an abridged version of my code:
var old = Ext.get("nodeContextMenu");
if(!Ext.isEmpty(old)) {
old.remove();
}
var menu = new Ext.menu.Menu({
id:'nodeContextMenu',
shadow:'drop',
items: [ ... ]
});
menu.showAt(e.xy);

I suggest never using hardcoded IDs. #aplumb suggests cleaning the DOM to reuse an existing ID. OK, but I suggest you cleanup the DOM when you no longer need the widgets/elements in the DOM and you should never reuse an ID.
var someId = Ext.id( null, 'myWidgetId' );
var someElement = new SuperWidget({
id: someId,
...
});

Just to add to owlness's answer
This bit here:
listeners: {
contextmenu: this.onContextMenu
}
Gets executed when the javascript file is loaded. this at that stage is most likely pointing to the window object.

A simple way to fix it is adding the listener on hide event of context menu, so you destroy him.
new Ext.menu.Menu(
{
items:[...],
listeners: { hide: function(mn){ mn.destroy(); } }
}
).show(node.ui.getAnchor());
;)

Related

override messagebox and add icons to the default buttons

Does anyone know here how to override the messagebox to put icons for the buttons? i.e: check icon for YES/OK, cross button for NO, etc.
I've tried to override the makeButton function of Ext.window.MessageBox but it doesn't seem to work and doesn't even hit the debugger:
Ext.override(Ext.window.MessageBox, {
makeButton: function (btnIdx) {
debugger;
var btnId = this.buttonIds[btnIdx];
return new Ext.button.Button({
handler: this.btnCallback,
itemId: btnId,
scope: this,
text: this.buttonText[btnId],
minWidth: 75,
iconCls: ['check', 'no', 'cancel', 'blah'][btnId]
});
}
});
As #scebotari66 have stated, Ext.Msg and Ext.MessageBox are singletons of Ext.window.MessageBox. So when you override Ext.window.MessageBox.makeButton, this will have no effect if you are using the singletons for this class.
However, there is a way to apply your overrides to Ext.window.MessageBox to the singleton. Guess how.
(drumroll)
tantantanan!
Ext.MessageBox = Ext.Msg = new Ext.window.MessageBox();
Yep, that's correct. You just need to re-assign the singleton after your override.
So:
Ext.override(Ext.window.MessageBox, {
makeButton: function (btnIdx) {
var btnId = this.buttonIds[btnIdx];
return new Ext.button.Button({
handler: this.btnCallback,
itemId: btnId,
scope: this,
text: this.buttonText[btnId],
iconCls: ['okbutton', 'yesbutton', 'closebutton', 'cancelbutton'][btnIdx],
minWidth: 75 //or you can also remove this to make the icons close to the label
});
}
});
//re-assign singleton to apply overrides
Ext.MessageBox = Ext.Msg = new Ext.window.MessageBox();
Next time you call Ext.Msg.alert(), your icons are now showing too.
I hope you find this helpful.
NOTE: The iconCls config should be in the order [ok, yes, no, cancel]
As you can see from the source code, the makeButton method is called from initComponent of Ext.window.MessageBox.
I assume that you are using the Ext.MessageBox (or Ext.Msg) singleton instance for displaying message boxes. This instance is created in the callback function immediately after the Ext.window.MessageBox is created (check the third argument from Ext.define). This also means that it happens before your override.
So you can directly override the buttons of the singleton instance like so:
Ext.Msg.msgButtons.ok.setIconCls(okBtnCls);
Ext.Msg.msgButtons.yes.setIconCls(yesBtnCls);
Ext.Msg.msgButtons.no.setIconCls(noBtnCls);
Ext.Msg.msgButtons.cancel.setIconCls(cancelBtnCls);
You can also rely on your makeButton override if you will show message boxes by creating a new instance of the class:
var myMsg = Ext.create('Ext.window.MessageBox', {
closeAction: 'destroy'
}).show({
title: 'Custom MessageBox Instance',
message: 'I can exist along with Ext.Msg'
});

Need to set class/id values on buttons in Extjs MessageBox

Our testing team require IDs or class values to be set on the HTML elements in our message popup boxes. This is for their automated tests.
I can pass in a class value for the dialog panel by passing in a cls value like so:
Ext.Msg.show({
title:'Reset Grid Layout',
msg: 'Are you sure that you want to reset the grid layout?',
cls:'Reset-Grid-Layout-Message',
buttons: Ext.Msg.YESNO,
fn: function (response) {
if (response == 'yes') {
}
},
icon: Ext.window.MessageBox.QUESTION
});
Now we also need it on the buttons, and also on the text being displayed. Is there some way of getting a cls value onto the buttons?
I was thinking it may be possible to expand the button parameter into something like :
buttons : [{name:'but1', cls:'asdf'}, {name:'but2', cls:'asdf2'}]
But google is not giving me back anything useful.
If your testing team uses Selenium for their automated test, adding ids/classes in every component could be difficult for both of you.
Overriding components in Ext is a good solution, but I don't recommend this because it will affect all your components. Unless you know what you're doing.
I suggest, extend Ext.window.MessageBox and generate classes for your buttons based on your parent cls.
// Put this somewhere like /custom/messagebox.js
Ext.define('App.MyMessageBox', {
extend: 'Ext.window.MessageBox'
,initConfig: function(config) {
this.callParent(arguments);
}
,makeButton: function(btnIdx) {
var me = this;
var btnId = me.buttonIds[btnIdx];
return new Ext.button.Button({
handler: me.btnCallback
,cls: me.cls + '-' + btnId
,itemId: btnId
,scope: me
,text: me.buttonText[btnId]
,minWidth: 75
});
}
});
To use:
App.Box = new App.MyMessageBox({
cls:'reset-grid-layout'
}).show({
title:'Reset Grid Layout'
,msg: 'Are you sure that you want to reset the grid layout?'
,buttons: Ext.Msg.YESNO
,icon: Ext.window.MessageBox.QUESTION
});
Your buttons will have reset-grid-layout-yes and reset-grid-layout-no class.
You can do the same with other components you have. Check out the Fiddle. https://fiddle.sencha.com/#fiddle/7qb
You should refer to the API
cls : String A CSS class string to apply to the button's main element.
Overrides: Ext.AbstractComponent.cls
You can also use the filter on right side (not the one in the right top corner). Just type cls and you will see all properties, methods and events containing cls (note that you see by default just public members, use the menu on the right of this searchfield to extend this)
Edit
If you just need it for testing purpose I would recommend to override the responsible method. This should work (untested!)
Ext.window.MessageBox.override({
buttonClasses: [
'okCls', 'yesCls', 'noCls', 'cancelCls'
],
makeButton: function(btnIdx) {
var btnId = this.buttonIds[btnIdx];
var btnCls = this.buttonClasses[btnIdx];
return new Ext.button.Button({
handler: this.btnCallback,
cls: btnCls,
itemId: btnId,
scope: this,
text: this.buttonText[btnId],
minWidth: 75
});
}
});

TreePanel not expanding ExtJS 3

This is my code -
var myTree = {
containerScroll: "true",
width: 500,
root: new Ext.tree.AsyncTreeNode({
id: "source",
text: "Root",
expanded: true,
draggable: true,
expandable: true
}),
loader: new Ext.tree.TreeLoader({
dataUrl: 'page.php?action=Get_Tree',
preloadChildren: true,
expandAll: function () {}
}),
xtype: "treepanel",
loadMask: {
msg: 'Loading...'
},
maskDisabled: false,
id: "tree",
listeners: {
click: function (node, event) {
// Render entity data in right panel
handleAction(node);
}
}
}
when user clicks on any node of Tree, I fetch the data from DB and rendering the another panel using handleAction method. In my case, I need to show node's info in another panel and update it. On update handler, I need to change node's text/id as its updated now.
This works fine but then I need to refresh the tree so that, I can view updated node. But, it doesn't work for me :(.
This is my code for updating node -
var tree = Ext.getCmp('tree');
var id = req.responseText.split('#')[1];
var myId = 'Spec#' + entityId;
var node = tree.getNodeById(myId);
node.id = 'Spec#' + id;
node.text = updated_spec_id;
tree.enable();
tree.getLoader().dataUrl = 'page.php?todo=Get_Tree';
tree.getLoader().load(tree.root);
// This I have tried but not succeed.
tree.getView().refresh();
tree.expandAll();
node.expand();
tree.getRootNode().expand(true);
However, when I manually expand it using mouse click, node is updated.
Please help. Thanks in advance.
Have you tried doLayout function? Excerpt from doc:
If the Container is already rendered when add is called, you may need to call doLayout to refresh the view which causes any unrendered child Components to be rendered. This is required so that you can add multiple child components if needed while only refreshing the layout once.
It seems to me, your tree doing normal behaviour. Because, you had used click event, therefore this event fire when you click the node.

How to change a tooltip's position in ExtJs 3.3

i want to change a tooltip's position to show it upon a button. I tried the method below as mentioned in ExtJs's forum. It doesn't work, i can't override the getTargetXY method, the tooltip is shown always in the same position. Do you have any solution ?
this.desktopButton = new Ext.Button({
icon: "arrow_in.png",
scope: this,
handler: this.desktopButtonHandler,
tooltip: new Ext.ToolTip({
text: 'Message',
getTargetXY: function () {
return [100, 100];
}
})
});
Ext elements can only be passed configuration options as specified in the documentation; getTargetXY is not one of those options.
If you want to override that method, you have two choices:
Override all Ext Tooltips to use your new function
Extend the existing Tooltip class to support overriding that method
I would not recommend overriding the method, as that could have other consequences. I will, however, explain how to do both.
To override the tooltip:
Ext.override(Ext.Tooltip, {
getTargetXY: function() {
return [100, 100]
}
});
To extend the tooltip:
MyToolTip = Ext.Extend(Ext.Tooltip, {
constructor: function(config) {
var config = config || {};
if (config.getTargetXY) {
this.getTargetXY = config.getTargetXY;
}
MyToolTip.superclass.constructor.call(this, config);
}
});
Note that setting 'targetXY' may prove unhelpful, as Ext JS may override this setting (depending on the view size).
Overriding the "showAt" method can prevent this:
showAt:function() {
var xy = [this.getTargetXY()[0],this.getTargetXY()[1]-this.height]
MyTooltip.superclass.showAt.call(this, xy);
}

How do I hide the top toolbar of a Ext.Panel? (ExtJS 2.0)

For some reason Ext.Panel.getTopToolbar() is returning an array of objects (the elements of the toolbar, but NOT the toolbar itself) and not an Ext.Toolbar. Because of that, I can't manage to hide an already set toolbar. How should I proceed?
Sample code:
function (panel)
{
alert(panel.getTopToolbar()); // displays the list of elements in the toolbar
panel.getTopToolbar().hide(); // error: "hide" is not a function
}
It should work, so it sounds like maybe you used topToolbar as a config instead of using tbar as the config? If you set a tbar config it gets instantiated and saved as topToolbar which is the Ext.Toolbar instance exposed by getTopToolbar(). If you overwrote topToolbar directly you might see this issue.
You might find this block of code in Panel.onRender (you'll have to include that file directly) and set a breakpoint in Firebug to see what's happening:
if(this.tbar && this.topToolbar){
if(this.topToolbar instanceof Array){
this.topToolbar = new Ext.Toolbar(this.topToolbar);
}
this.topToolbar.render(this.tbar);
}
panel.getTopToolbar().setVisible(false);
In 4.2.1 what works for me is:
var topToolbar = Ext.create('Ext.toolbar.Toolbar', {
dock: 'top',
width: 'auto',
id: 'mytoolbar',
hidden: true,
items: [...]
});
var p = Ext.create('App.view.MyCustomPanel', {
html: 'test',
});
if (userCanSeeToolbar) {
p.addDocked(topToolbar);
}
Then dynamically I can show/hide the top toolbar:
/* if (userCanSeeToolbar) { */
p.getDockedComponent('mytoolbar').show();
p.getDockedComponent('mytoolbar').hide();

Resources