ExtJS window animatetarget - extjs

I'm using animateTarget to animate the showing and hiding of a window. However i can't seem to set any animation options for this like duration and easing.
Only setting the duration and easing would be sufficient for me in this specific use case.
I'm using ExtJS 4.1
thnx!

It is possible to configure this animation but not with just additional configuration properties applied to a Ext.window.Window at creation. The animation is done with the animate method of the Ext.Element and is of type Ext.fx.Anim (see this link for config details)
You will need to extend from Ext.window.Window and override the afterShow() and onHide() private eventhandler. Within these you can modify the appropriate configs. Here's a example where I extended the duration from default 250ms to 500ms. And here's a working JSFiddle life-demo
Ext.define('Ext.ux.Window',{
extend: 'Ext.window.Window',
alias: 'widget.animatedwindow',
initComponent: function() {
this.callParent(arguments);
},
afterShow: function(animateTarget, cb, scope) {
var me = this,
fromBox,
toBox,
ghostPanel;
// Default to configured animate target if none passed
animateTarget = me.getAnimateTarget(animateTarget);
// Need to be able to ghost the Component
if (!me.ghost) {
animateTarget = null;
}
// If we're animating, kick of an animation of the ghost from the target to the *Element* current box
if (animateTarget) {
toBox = me.el.getBox();
fromBox = animateTarget.getBox();
me.el.addCls(me.offsetsCls);
ghostPanel = me.ghost();
ghostPanel.el.stopAnimation();
// Shunting it offscreen immediately, *before* the Animation class grabs it ensure no flicker.
ghostPanel.el.setX(-10000);
me.ghostBox = toBox;
ghostPanel.el.animate({
from: fromBox,
to: toBox,
duration: 500,
listeners: {
afteranimate: function() {
delete ghostPanel.componentLayout.lastComponentSize;
me.unghost();
delete me.ghostBox;
me.el.removeCls(me.offsetsCls);
me.onShowComplete(cb, scope);
}
}
});
}
else {
me.onShowComplete(cb, scope);
}
},
onHide: function(animateTarget, cb, scope) {
var me = this,
ghostPanel,
toBox,
activeEl = Ext.Element.getActiveElement();
// If hiding a Component which is focused, or contains focus: blur the focused el.
if (activeEl === me.el || me.el.contains(activeEl)) {
activeEl.blur();
}
// Default to configured animate target if none passed
animateTarget = me.getAnimateTarget(animateTarget);
// Need to be able to ghost the Component
if (!me.ghost) {
animateTarget = null;
}
// If we're animating, kick off an animation of the ghost down to the target
if (animateTarget) {
ghostPanel = me.ghost();
ghostPanel.el.stopAnimation();
toBox = animateTarget.getBox();
ghostPanel.el.animate({
to: toBox,
duration: 500,
listeners: {
afteranimate: function() {
delete ghostPanel.componentLayout.lastComponentSize;
ghostPanel.el.hide();
me.afterHide(cb, scope);
}
}
});
}
me.el.hide();
if (!animateTarget) {
me.afterHide(cb, scope);
}
}
});

Related

DropTarget for Ext Window

I need to implement drop target for Ext Window. The windows can be dropped anywhere but when they are dropped inside DropZone then i need to call a function that will change it's layout. The problem is that the drop events are not called.
Here is how i define drop target (it is a floating panel, below code called in initComponent):
this.on('render', function(w) {
w.dropTarget = Ext.create('Ext.dd.DropTarget', w.getEl());
w.dropTarget.nofifyDrop = function(source, evt, data) {
console.log('notifyDrop');
};
w.dropTarget.notifyEnter = function() {
console.log('notifyEnter');
};
},this);
Here is how i define drop source. When window has draggable config then it has dd property that i am trying to configure
listeners:{
scope:this,
close:function(w) {
this.removeWindow(w);
},
show:function(w) {
console.log('win', w.dd);
w.dd.afterValidDrop = function() {
console.log('after valid drop');
}
w.dd.onDragDrop = function() {
console.log('on drag drop');
}
w.dd.onStartDrag = function() {
console.log('starting drag');
}
},
The problem is that not a single function is called, no action logged into console. When i switch from window to panel with floating and draggable config then the events from DropTarget are called (notifyDrop and notifyEnter) but events from panel are not. How to properly configure Drag&Drop for Window with draggable config or maybe there is a better way to implement what i need.
I also tried to disable draggable for window or panel and specify DropTarget and DragSource myself:
Droptarget now have ddGroup:
this.on('render', function(w) {
w.dropTarget = Ext.create('Ext.dd.DropTarget', w.getEl(),{
ddGroup:'winDDGroup',
notifyDrop:function(source, evt, data) {
},
notifyEnter:function() {
console.log('notifyEnter');
}
});
},this);
And drag source (ext window):
var overrides = {
startDrag: function(e) {
console.log('starting drag');
//shortcut to access our element later
if (!this.el) {
this.el = Ext.get(this.getEl());
}
//add a css class to add some transparency to our div
this.el.addCls('selected');
//when we drop our item on an invalid place we need to return it to its initial position
this.initialPosition = this.el.getXY();
},
onDrag: function(e) {
//this.el.moveTo(e.getPageX() - 32, e.getPageY() - 32);
},
onDragEnter: function(e, id) {
console.log('onDragEnter', id);
Ext.fly(id).addCls('valid-zone');
},
onDragOver: function(e, id) {
console.log('onDragOver', id);
Ext.fly(id).addCls('valid-zone');
},
onDragOut: function(e, id) {
console.log('onDragOut');
},
onDragDrop: function(e, id) {
console.log('on drag Drop');
// change the item position to absolute
this.el.dom.style.position = 'absolute';
//move the item to the mouse position
this.el.moveTo(e.getPageX() - 32, e.getPageY() - 32);
Ext.fly(id).removeCls('valid-zone');
},
onInvalidDrop: function() {
console.log('invalid drop');
this.el.removeCls('valid-zone');
//this.el.moveTo(this.initialPosition[0], this.initialPosition[1]);
},
endDrag: function(e, id) {
console.log('end drag');
this.el.removeCls('selected');
//Ext.fly(id).removeCls('drop-target');
//this.el.highlight();
}
};
....
listeners:{
scope:this,
close:function(w) {
this.removeWindow(w);
},
render:function(w) {
var dd = Ext.create('Ext.dd.DragSource', w.getEl(), {
ddGroup:'winDDGroup',
//isTarget: false
});
Ext.apply(dd, overrides);
},
}
Now when i start dragging the window events are fired but dragging the window over drop zone have no effect. DropTarget events are not fired.
Edit: Again when i switch from window to panel then events from both source and target are fired. Still i need the drag sources to be windows.
I had a similar issue when trying to manipulate the proxy and the drag handle that may help.
Maybe you should look into DragTracker or DragDrop components. Dragtracker will provide you with events for dragstart and drag end among others that you can use to implement your drop logic.
This was roughly my implementation (for panels, but maybe it'll help, i cant really look into it in great depth)
//add the listener to afterrender, possibly other
window.on('afterrender', me._setupDragDrop, window);
//do your magic
_setupDragDrop : function(win){
win.dd = new Ext.dd.DragDrop(win.id);
win.dd.setHandleElId(win);
win.dragTracker = new Ext.dd.DragTracker({
onBeforeStart: function(e) {
//do stuff
},
onStart: function(e) {
//do stuff
},
onDrag: function(e) {
//do stuff
},
onEnd: function(e) {
//do stuff
}
});
win.dragTracker.initEl(win.el);
},
Hope this helps.

Drag and Drop stops working for an item which is once dragged

I have an ExtJS treepanel in Container A on page and there's another Container B.
The treepanel is initialized with items via Ajax call. In the viewConfig of treepanel, I've added itemadd event listener, in which I register added items as DragSource. Following is the itemadd event listener within viewConfig of treepanel.
'itemadd': function(records, node){
// Iterate over each child record of parent node in treepanel.
Ext.each(records, function(record){
var dragSource,
field,
fieldId;
field = Ext.query('[id='+record.data.listId+']')[0]; // I've manually added 'listId' which has unique value gained from Ext.id() for each record item.
fieldId = field.id;
dragSource = new Ext.dd.DragSource(field, {
isTarget : false
});
dragSource.dragData = {
record: record
};
});
}
And in the items of Container B, I have added a View named MyView which extends Ext.container.Container internally. So in the afterrender of Container B, I'm registering itself as a DropTarget. Here's how I do that;
'afterrender': function(containerMain) {
var dropZone,
myView = Ext.ComponentQuery.query("myview")[0];
dropZone = new Ext.dd.DropTarget(containerMain.getEl()); // Register Container B as DropTarget.
dropZone.notifyDrop = function(source, event, params) {
myView.doSomething(params.record); // This method will handle data of dropped record and internally show something on UI.
};
}
Now the here's problem described in step-by-step usage.
I drag an item from treepanel into Container B for first time and it works fine as intended.
I don't allow to add duplicate items again into MyView, I have a Remove button for each item added to remove it from MyView.
I remove the item which I added.
I try to drag it again from treepanel but it is no longer a draggable item.
Though I can still drag another item from same treepanel and add it, but not the first one which I added and removed earlier (and same thing would happen for all items which I once add to MyView. Note that removing of item is not necessary to reproduce the issue, only adding it causes this.
So what is getting wrong here?
Any help would be great.
I suggest to use the Ext.tree.plugin.TreeViewDragDropView (ptype: treeviewdragdrop) plugin for the tree instead of handling it manually.
Example for the treepanel:
var treePanel = Ext.create('Ext.tree.Panel', {
(...),
viewConfig: {
plugins: {
ptype: 'treeviewdragdrop',
ddGroup: "sameGroup",
enableDrop: false
}
}
});
For the panel:
var panel = Ext.create('Ext.panel.Panel', {
(...),
listeners: {
'afterrender': function(component) {
var dropZone = Ext.create('Ext.dd.DropTarget', component.body.dom, {
ddGroup: 'sameGroup',
notifyDrop: function(source, event, params) {
var myView = panel.up('myview');
myView.doSomething(source, event, params);
return true;
}
});
}
}
});

Prevent Rendering (hide and/or disable) of a Component at construction time

Background: Our app is always packed as a whole but through the users access some serverside actions may be restricted. We know which actions are allowed the time the app starts. We now want to hide all the views (panels, buttons, etc) from the user to which he lacks the access to.
For that we have written a plugin which can be applied to any Component. But here comes the problems:
Here is what we try to run against the plugin host:
if (cmp['setVisible']) cmp.setVisible(false); else cmp.hidden = true;
if (cmp['disable']) cmp.disable(); else cmp.disabled = true;
cmp.on('beforerender', function() { return false; })
First we thought the earlier we do this the better. So we tried to run it at construction time of the plugin. But that was not possible because the listeners (of the host) seems to be not ready yet (the component tries to fire the hide event). So we moved it into the init method of the plugin which does not throw a error but just worked partly. Only the beforerender event got really applied but it only aborted the rendering of the child. so we ended up with a broken looking component (the borders are there and the content not). If we commented the event registration out the host stayed untouched. We also tested the use of only the hidden:true and disabled:true with no luck.
So how can we prevent rendering of component in the correct way?
Edit:
The component should be flagged as disabled and hidden because we cannot prevent the creation of the component. The snipped I got from my colleague was wrong so the call of setVisible(false) worked, we guess disable() also. But the component get stilled rendered and we seem not really able to veto this without ending up with a half rendered component.
Answer by #AlexTokarev
I tried what #AlexTokarev suggested. For that I added the following lines into the Plugin-Constructor
cmp.hidden = true;
cmp.autoShow = false; // I know this may do nothing for non floating but I added it anyway
cmp.autoRender = true;
Based on debugging I know that the settings get applied really early (at the Ext.AbstractComponent.constructor), but I still ending up with a hidden and rendered component.
Comment by #sbgoran
In one Testcase we use a column-layout in which all containers extend from the same class. As soon as I add our plugin (with the beforerender event returning false configuration) to one of this extending containers (the plugin is directly added to class definition (as ptype)) all containers within this columns look broken (only borders are rendered and in the content a small grey box in the upper left corner.). So the aborted rendering affect all child items of the column when only one child item get the rendering canceled.
**Sample Code **
First I want to note that we are looking for a way to do this in general cause as far as we know the rendering in ExtJS is one thing. I can ask to setup a demo but I think this will not be that easy because we are using the Ext.app.portal.Panel for the failing example. but the plugin should work for any sort of Component. First I will add some demo code:
We have a view which is placed into a Viwport with border layout
Ext.define('MVC.view.Application',{
extend:'Ext.tab.Panel',
alias:'widget.appview',
region: 'center',
activeTab: 1
});
Within the Controller we fill this
var portal = this.portalRef = Ext.widget('portalpanel', {
title: 'Employee',
portalCols: 2
});
portal.addPortlet(0,['employee','employee2','employee3']);
portal.addPortlet(1,['employee4','employee5']);
app.appviewmain.add(portal);
Here is the portal panel
Ext.define('MVC.app.portal.PortalPanel', {
extend: 'Ext.panel.Panel',
alias: 'widget.portalpanel',
requires: [
'Ext.layout.container.Column',
'Ext.app.portal.PortalDropZone',
'Ext.app.portal.PortalColumn'
],
portalCols: 2,
portalColCfg: {
defaults: {
closable: false,
draggable: false,
collapsible: false,
header: false,
bodyStyle: {
background: '#fff',
padding: '10px'
}
},
items: []
},
addPortlet: function(idx, portlets) {
if (idx > this.portalCols || idx < 0)
return;
var portalCol = this.items.getAt(idx);
function insertPortlet(portlet) {
if (Ext.isString(portlet)) {
portlet = { xtype: portlet };
}
portalCol.add(portlet);
};
if (Ext.isArray(portlets)) {
var len = portlets.length,
i = 0;
for(;i<len;i++) {
insertPortlet(portlets[i]);
}
} else {
insertPortlet(portlets);
}
},
initPortal: function() {
var cfg = this.portalColCfg,
i = 0,
cols = [];
for (;i<this.portalCols;i++) {
cols.push(Ext.clone(cfg));
}
this.items = cols;
},
cls: 'x-portal',
bodyCls: 'x-portal-body',
defaultType: 'portalcolumn',
autoScroll: true,
manageHeight: false,
initComponent : function() {
var me = this;
// init only if nothing is defined
if (!me.items)
me.initPortal();
// Implement a Container beforeLayout call from the layout to this Container
me.layout = {
type : 'column'
};
me.callParent();
me.addEvents({
validatedrop: true,
beforedragover: true,
dragover: true,
beforedrop: true,
drop: true
});
},
// Set columnWidth, and set first and last column classes to allow exact CSS targeting.
beforeLayout: function() {
var items = this.layout.getLayoutItems(),
len = items.length,
firstAndLast = ['x-portal-column-first', 'x-portal-column-last'],
i, item, last;
for (i = 0; i < len; i++) {
item = items[i];
item.columnWidth = 1 / len;
last = (i == len-1);
if (!i) { // if (first)
if (last) {
item.addCls(firstAndLast);
} else {
item.addCls('x-portal-column-first');
item.removeCls('x-portal-column-last');
}
} else if (last) {
item.addCls('x-portal-column-last');
item.removeCls('x-portal-column-first');
} else {
item.removeCls(firstAndLast);
}
}
return this.callParent(arguments);
},
// private
initEvents : function(){
this.callParent();
this.dd = Ext.create('Ext.app.portal.PortalDropZone', this, this.dropConfig);
},
// private
beforeDestroy : function() {
if (this.dd) {
this.dd.unreg();
}
this.callParent();
}
});
And here is the Portlet
Ext.define('Ext.app.portal.Portlet', {
extend: 'Ext.panel.Panel',
alias: 'widget.portlet',
layout: 'fit',
anchor: '100%',
frame: true,
closable: true,
collapsible: true,
animCollapse: true,
draggable: {
moveOnDrag: false
},
cls: 'x-portlet',
initComponent : function() {
this.callParent();
},
// Override Panel's default doClose to provide a custom fade out effect
// when a portlet is removed from the portal
doClose: function() {
if (!this.closing) {
this.closing = true;
this.el.animate({
opacity: 0,
callback: function(){
var closeAction = this.closeAction;
this.closing = false;
this.fireEvent('close', this);
this[closeAction]();
if (closeAction == 'hide') {
this.el.setOpacity(1);
}
},
scope: this
});
}
}
});
Here is a sample view
Ext.define('MVC.view.employee.Employee',{
extend:'Ext.app.portal.Portlet',
alias:'widget.employee',
plugins: [{ptype: 'directbound', accessRoute: 'Employee.Read'}],
items: [
/*A form with some fields*/
]
});
Here's the plugin
Ext.define('MVC.direct.plugins.DirectBound',{
extend: 'Ext.AbstractPlugin',
alternateClassName: ['MVC.direct.DirectBound'],
alias: 'plugin.directbound',
/**
* #cfg {int} blockMode Indicates the way in which the Component gets blocked
* options
* 0 hide and disable
* 1 disable
*/
blockMode: 1,
constructor: function(config) {
var me = this,
cmp = config['cmp'],
route;
me.parseRoute(route);
// check for access
if (!me.checkAccess()) {
if (me.blockMode === 0) {
cmp.hidden = true;
cmp.autoShow = false;
cmp.autoRender = true;
}
me.diabled = true;
}
me.callParent(arguments);
}
/* some more methods */
});
Here's the column Layout
Ext.define('MVC.app.portal.PortalColumn', {
extend: 'Ext.container.Container',
alias: 'widget.portalcolumn',
requires: [
'Ext.layout.container.Anchor',
'MVC.app.portal.Portlet'
],
layout: 'anchor',
defaultType: 'portlet',
cls: 'x-portal-column'
// This is a class so that it could be easily extended
// if necessary to provide additional behavior.
});
Have you tried to set autoRender: true in your optional components? Here's the doc: http://docs.sencha.com/extjs/4.2.2/#!/api/Ext.AbstractComponent-cfg-autoRender
You can try with hide and show functions and also try with "added" event listener, which will be called after adding the component to container.
Try something like this for your plugin:
Ext.define('MVC.direct.plugins.DirectBound',{
extend: 'Ext.AbstractPlugin',
alternateClassName: ['MVC.direct.DirectBound'],
alias: 'plugin.directbound',
/**
* #cfg {int} blockMode Indicates the way in which the Component gets blocked
* options
* 0 hide and disable
* 1 disable
*/
blockMode: 1,
constructor: function(config) {
var me = this,
cmp = config['cmp'],
route;
me.parseRoute(route);
// Try to define beforerender callback on component and return false if
// component should not be visible
cmp.on('beforerender', function() {
if (!me.checkAccess()) {
if (me.blockMode === 0) {
return false;
}
// Not sure what this do for you but it wont disable component
// if you want your component disabled here try cmp.disable()
me.diabled = true;
}
});
// Maybe this code is not needed anymore
// check for access
if (!me.checkAccess()) {
if (me.blockMode === 0) {
cmp.hidden = true;
cmp.autoShow = false;
cmp.autoRender = true;
}
me.diabled = true;
}
// This should stay for sure
me.callParent(arguments);
}
/* some more methods */
});

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);
}

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

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());
;)

Resources