EXTJS 6.7 - class properties not cleared on destroy - extjs

I've created a fiddle just to simulate my problem with class property not being reset on window destroy.
How to test:
Open fiddle, press OPEN button, ADD 3 panels, close ext window, press OPEN button again, and add a some more panels.
Panel numbers represent the length of the _panels array property in window.
Now to the problem.
As you can see panel NUMBER when adding new panels is not reset. So if you add 3 panels and close the window, reopen the window panels count shows 3 and then 4 and then 5 instead of 0 1 2 ...
My question is, why?
Fiddle example
Kind regards
Armando
EDIT : so one can see the solution
I ended fixing my application to work like this fiddle. I moved properties to constructor.
constructor: function() {
Ext.apply(this, {
width: 800,
height: 600,
layout: 'vbox',
_panels : []
});
this.callParent(arguments);
},

When you define
Ext.define('TestWindow', {
extend: 'Ext.window.Window',
_panel: []
});
The TestWindow definition class gets empty array property (no-primitive datatype). When you create an instance by var win = Ext.create('TestWindow'), the instance gets that property. However, when you set:
onDestroy: function() {
this._panels = [];
},
it sets empty array to property _panels of the instance win, not on the definition class TestWindow; TestWindow keeps the existing mutated _panel. And when next time you create new instance, it gets same _panel from class definition.
I understand you did it for demo purpose to show the problem. However, I prefer to let framework do all heavy-lifting (create and destroy etc):
Ext.define('TestWindow', {
extend: 'Ext.window.Window',
width: 800,
height: 600,
defaultListenerScope: true,
layout: 'vbox',
initComponent: function() {
this._panels = [];
this.callParent(arguments);
},
addPanel: function() {
console.log(this._panels.length);
var panels = this._panels;
panels.push(this.add({
xtype: 'panel',
title: 'Panel ' + panels.length,
height: 50,
width: '100%'
}));
},
tbar: [{
xtype : 'button',
text: 'add',
handler: 'addPanel'
}]
});

The simple answer for this is prototypal inheritance (see this MDN article). Basically your non-primitives will carry over to new instances because they exist on your prototype class, and because they're non-primitives, it's the same exact reference that's used. To fix this, I would recommend wrapping your _panels variable in the config block, like below, and encourage you to use the appropriate set/get methods, instead of accessing it directly:
config: {
_panels : []
}

A less than correct answer if the prototype behavior in the previous answer is "A feature/intentional bad code/ legacy code with unintended consequences"
Manually overwrite the prototype Value, following instantiation of the panel

Related

Sencha ExtJS MVC - data source specified at run time

I want to write a proof-of-concept app along these lines:
View
- a URL text input field at top with Go button
- a big DIV underneath consisting of the rest of the view
Controller
- upon Go pressed, validate the URL text
- set up the URL to the data source
- read data from the data source
- create a nested DIV element for each data, apply CSS rules
- add the element to the big DIV
Model
- define the fields
- define the default ordering
CSS
- define the styles
First, does what I have written above work within ExtJS or will I be fighting the framework? In particular, inserting plain HTML as element nodes.
Second, does anyone know of an existing project under GPL which could act as starting point? So far what I've seen are flashy examples with URLs hard-coded and set to auto-load.
There's nothing scary or otherwise disturbing in what you've written.
Although not much advertised, ExtJS handles custom HTML & CSS pretty well. You can set some using the html or tpl config options. The latter is powered by XTemplates, so you can do loops, etc. When using these options and/or custom CSS, Ext will calculate its layouts around the rendered result, accounting for your custom style automatically. Now, in practice, that's a whole lot of work for the framework, and it doesn't always work as expected, and it won't work at all on some browsers (like not so old IE, of course). One big pitfall you should be aware of is that you should always use integer value in px for your CSS, since if a dimension resolve to a decimal value in px, Ext will choke on that.
That said, since you're apparently going to back your data with a model, you should probably use a dataview. That's a component that let you use custom HTML over a regular Ext store. It then provides goodies for item selection, paging, etc. It's the base class of other advanced data views, like Ext grids.
Regarding URLs, you don't necessarily have to hardcode them in the model's proxy (or elsewhere). You can pass an URL to an existing store's load method (as documented here).
Finally, I don't know of existing projects, but your POC is really straightforward, so here's a fiddle to get you started. The code is not 100% clean, in particular defining everything in a single file, and thus missing all the requires... But it illustrates most of the points you've asked about. Read the docs about the components / methods that are used to learn how to go beyond this.
Here's the fiddle's code:
Ext.define('Foo.model.Item', {
extend: 'Ext.data.Model',
fields: ['name']
});
Ext.define('Foo.view.MainController', {
extend: 'Ext.app.ViewController',
alias: 'controller.main',
onGo: function() {
var view = this.getView(),
url = view.down('textfield').getValue(),
dataview = view.down('dataview'),
store = dataview.getStore();
if (this.isValidUrl(url)) {
store.load({url: url});
} else {
Ext.Msg.alert(
"Invalid URL",
"This URL cannot be loaded here: " + url
);
}
},
// private
isValidUrl: function(url) {
return ['data1.json', 'data2.json'].indexOf(url) !== -1;
}
});
Ext.define('Foo.view.Main', {
extend: 'Ext.Panel',
xtype: 'main',
controller: 'main',
layout: {
type: 'vbox',
align: 'stretch'
},
items: [{
xtype: 'container',
layout: 'hbox',
margin: 3,
defaults: {
margin: 3
},
items: [{
flex: 1,
xtype: 'textfield',
emptyText: "Valid inputs are 'data1.json' or 'data2.json'",
listeners: {
specialkey: function(field, e) {
if (e.getKey() === e.ENTER) {
// custom event, for the fun of it
field.fireEvent('enterkey', field, e);
}
},
// the custom can be bound to controller in "modern ext" way
enterkey: 'onGo'
}
},{
xtype: 'button',
text: "Go",
handler: 'onGo'
}]
},{
flex: 1,
xtype: 'dataview',
margin: '0 6 6 6',
cls: 'my-dataview', // for CSS styling
store: {
model: 'Foo.model.Item',
autoLoad: false
// default proxy is ajax and default reader is json,
// which is cool for us
},
tpl: '<tpl for=".">' + '<div class="item">{name}</div>' + '</tpl>',
itemSelector: '.item'
}]
});
Ext.application({
name : 'Foo',
mainView: 'Foo.view.Main'
});
Some CSS for the data view:
.my-dataview .item {
padding: 1em;
border: 1px solid cyan;
color: magenta;
float: left;
margin: 6px;
width: 100px;
}
And an example JSON response (this is the bare minimum to be functional... read about proxies & reader to go further):
[{
name: 'Foo'
},{
name: 'Bar'
},{
name: 'Baz'
}]

Grabbing a Extjs component

Please help understanding why the commented code below does not work on ExtJs 3.4:
var mywin=new Ext.Window({
width : 200,
height: 150,
title : 'Accordion',
layout: 'accordion',
border: false,
items: [
panel1,
panel2
]
}).show();
<!--Ext.getCmp('mywin').add({ - THIS DOES NOT WORK ,while below works-->
mywin.add({
title: 'Appended panel',
id: 'addedPanel',
html : 'Add Me!'
});
mywin.doLayout();
mywin is a reference to a window object that you created. This is just a normal JS construct using variable assignment.
Ext.getCmp('mywin') attempts to look up a component that has an id property of mywin. It's typically a good idea to avoid using Ext.getCmp unless you'll only ever be creating once instance of the component, since it must be globally unique.
Ext.getCmp('x') works only if x is id of some component(Panel or window whatever you want to use). Just provide an id field(id:'component_Id') and use Ext.getCmp on the id of component.
In many scenarios you can also use lookupReference, please check extjs docs for it.
You can try using the following for getting the reference to your window (although you already have it in your mywin variable):
var winInstance = Ext.ComponentQuery.query('mywin')[0];
winInstance.add({
title: 'Appended panel',
id: 'addedPanel',
html : 'Add Me!'
});
But the problem was you were trying to reference your window component using the name of the variable, so like it's mentioned in previous answers, you would need to use an itemId: 'mywin' or id: 'mywin', since as it stands there is really no component with an itemId or id with that name.

How to check whether xtype object exists or not in extjs4.1

I am getting an object using Ext.component.Query. I need to check whether the object exists or not. If object exists, I need to remove the object. Can anybody tell me how to do this?
Thanks
As other posters have mentioned, the method you're looking for is Ext.ComponentQuery, which returns an array which you can then check the length of via length, which will in turn tell you if the object exists or not. If the object exists, it can be destroyed via the destroy() method of the Ext.AbstractComponent
I have made a jsFiddle example demonstrating what you're trying to do here: http://jsfiddle.net/mPYPw/
Code from the fiddle:
Ext.create('Ext.panel.Panel', {
name : 'myPanel',
title: 'Panel 1',
width: 200,
html: '<b>Its a panel!</b>',
renderTo: Ext.getBody()
});
Ext.create('Ext.panel.Panel', {
name : 'myPanel',
title: 'Panel 2',
width: 200,
html: 'Look, another panel!',
renderTo: Ext.getBody(),
dockedItems: [{
xtype: 'toolbar',
dock : 'bottom',
items: [{
text: 'Destroy all panels!',
handler: function(){
// Here we can query for the panels
var panels = Ext.ComponentQuery.query('panel[name=myPanel]'),
trees = Ext.ComponentQuery.query('treepanel');
// #param {Ext.panel.Panel[]} panels Array of panel components
if(panels.length > 0){
alert("About to destroy " + panels.length + " Panels!");
Ext.each(panels, function(panel){
panel.destroy();
});
}
// There are no tree panels
if(!trees.length){
alert("There are no tree panels to destroy!");
}
}
}]
}]
});
Simple check with Ext.ComponentQuery
var check = Ext.ComponentQuery.query('yourXtype');
if (check.length > 0)
//do something
else
//do other something
I know the documentation was messed up on one of the versions... I don't know if it is still the same in Ext JS 4.2, but in 4.1.1 you can query for Ext JS objects by xtype using something similar to this:
Ext.ComponentQuery.query('xtype');
i.e.
Ext.ComponentQuery.query('gridpanel');
I think Ext.ComponentQuery-method-query explains it.
FIRST NOTE THAT Ext.getCmp("id") is suitable for small Apps..
If u tend to hav a big app u could go for a Component Query .
This can be done in two ways Either u could use a "Xtype" or "component Id"(note component id must be prefixed with a #).

Why doesn't a simple click: function()... work in ExtJS?

When the user clicks on this element, I want it to show an alert.
However, when I click on the DIV that this Panel generates, nothing happens.
How can I make an alert execute when the user clicks on the following panel?
var content = new Ext.Panel({
region:'center',
margins:'5 0 5 5',
cls:'empty',
bodyStyle:'background:ivory; font-size: 13pt',
html:'<p id="test123">This is where the content goes for each selection.</p>',
click: function() {
alert('was clicked');
}
});
You haven't accepted an answer, so I'll assume you're still unclear on this. Here are a few pointers...
First, as coded your Panel will render as a plain square. If you're expecting it to look like a Panel, you should give it a title (so the title bar will render).
Second, as mentioned, click is not a Panel event (it's an Element event). So you have several ways of getting to the behavior you want. You can manually attach a listener to the underlying DOM element after the Panel is rendered:
Ext.get('txest123').on('click', function(){
alert('foo');
});
You could also do as I mentioned in the comments of another answer to generically handle any body click:
// .body is a Panel property for the body element
content.body.on('click', function(){
alert('foo');
});
If you really want to restrict the click to only the child p you could add a check:
// e is the event object, t is the target DOM node
content.body.on('click', function(e,t){
if(t.id == 'txest123'){
alert('clicked the p');
}
});
If I was coding this, I'd probably do something more like this:
var content = new Ext.Panel({
region:'center',
renderTo: document.body,
margins:'5 0 5 5',
cls:'empty',
title: 'My Panel',
id: 'txest123',
bodyStyle:'background:ivory; font-size: 13pt',
html:'This is where the content goes for each selection.',
listeners: {
'render': {
fn: function() {
this.body.on('click', this.handleClick, this);
},
scope: content,
single: true
}
},
handleClick: function(e, t){
alert(this.id); // the panel
alert(t.innerHTML); // the clicked el
}
});
Now the id is on the Panel (where it should be) and you can use Panel and/or Element methods to access child elements as needed. It's best to keep id's at the highest level possible. You'll notice too that the callback function is executed in the scope of the Panel (scope:this) so that inside handleClick you can treat this as the Panel itself and access any of its properties or methods.
So, without knowing exactly what you're trying to achieve, I can't provide you with the exact code you need. However, this should hopefully give you some ideas.
EDIT: I meant to say this originally... in your code (as posted) you are not actually rendering the Panel. As I mentioned in my answer to your related question, if you are adding the Panel as an item to a container that is lazy-rendered, the Panel's DOM won't be available for selection until after the container has rendered it. In my code above I added renderTo so that I don't have this issue, but if you're not doing that you'll have to wait until the Panel is rendered at some time later to access it.
The Panel Component does not expose a click event, so the one you're passing into the config never gets fired.
Try putting an id on your Ext.Panel object and then getting its element using Ext.get(). Then add a click event through on():
var content = new Ext.Panel({
id: 'myPanel',
region:'center',
margins:'5 0 5 5',
cls:'empty',
bodyStyle:'background:ivory; font-size: 13pt',
html:'<p id="txest123">This is where the content goes for each selection.</p>'
});
Ext.get('myPanel').on('click', function() {alert('You clicked me');});
The following sample is a bit rough but it works for me. It is a panel with a box component, which is showing a thumbnail. When clicking on the thumbnail, it is showing a lightbox with slimbox2. Not pretty, but very effective. The hardcoded images are just for test here.
var panel = new Ext.Panel({
title : 'Image',
header : false,
frame : true,
border : false,
bodyStyle : 'padding : 5px',
width : 125,
items : [{
xtype : 'box',
height : 115,
width : 115,
listeners : {
'render': function() {
var id = Ext.id(this);
Ext.fly(id).addListener('click', function () {
jQuery.slimbox('thisisnotanimage', 'CBX');
});
}
},
autoEl: {
tag : 'div',
html : 'somehtmltagstuff'
}
}
]
});
According to the API, click is not a valid event for Panels... However, you should still be able to add the click event to the underlying DIV element.
Ext.fly(e.id).addListener('click', Ext.getCmp(e.id) , this);
I believe you need something like:
var content = new Ext.Panel({
region:'center',
margins:'5 0 5 5',
cls:'empty',
bodyStyle:'background:ivory; font-size: 13pt',
html:'<p id="test123">This is where the content goes for each selection.</p>',
listeners: {
click: function() {
alert('was clicked');
}
}
});

Ext.form.combobox inside ext.window displays values at top left of screen

I have a combobox inside of a ext.panel, inside of an ext.window. When I click the down arrow to show the possible SELECT options, the options pop up at the top-left of the browser window, instead of below the SELECT box. The funny thing is if I attach the drugDetailsPanel (see code below) to a div on the page (instead of inside an ext.window), the combobox works correctly. This also happens when I change ext.panel to ext.form.formpanel, by the way.
Any ideas?
My code:
drugDetailsPanel = new Ext.Panel({
layout:'form',
id:'drug-details-panel',
region:'center',
title:'Drug Details',
height:200,
collapsed:false,
collapsible:false,
items:[
new Ext.form.ComboBox({
fieldLabel:'What is the status of this drug?',
typeAhead:false,
store:drugStatusStore,
displayField:'lookup',
mode:'remote',
triggerAction:'all',
editable:false,
allowBlank:false,
emptyText:'Select a status..',
name:'/drug/drug-status',
id:'drug-status'
})
]
});
newDrugWindow = new Ext.Window({
title: 'Add Drug',
closable:true,
width:650,
height:650,
//border:false,
plain:true,
layout: 'border',
items: [drugDetailsPanel],
closeAction:'hide',
modal:true,
buttons: [
{
text:'Close',
disabled:false,
handler: function(){
newDrugWindow.hide();
}
},
{
text:'Save Drug',
handler: function(){
newDrugDialog.hide();
}
}]
});
Try to add shim: true to combo-box control.
Older versions of Ext had issues like this in certain browsers (FF 2.x) in certain situations dealing with nested positioning, the specifics of which escape me now. If that's the case, search the Ext forums for more info. If not, then I'm not sure...
This forum thread helped me: http://www.sencha.com/forum/showthread.php?177677-IE-displays-combobox-dropdown-in-the-top-left-corner-of-browser-window
Just give the combobox a (unique) name. Giving the combobox an inputId should also help
Seems like IE does not respect the position of the element if it does not have an explicit name/inputId. This thread goes more deeply into it: http://www.sencha.com/forum/showthread.php?154412-Combo-Box-options-appears-in-Top-Left-Corner-in-IE-9

Resources