PDA

View Full Version : MVC: one controller with multiple instances of a view



Wilky
2 Sep 2011, 2:53 PM
Hi guys,
I have a new question for you.
Well, how can I handle multiple instances of a view with a single controller in the MVC architecture of ExtJS 4.0.2a?

This is the example:

The View:


Ext.define ('MA.view.Win' , {
extend: 'Ext.window.Window' ,
alias: 'widget.mawin' ,

width: 300 ,
height: 300 ,

items: [{
xtype: 'button' ,
text: 'Push me'
}]
});


The Controller:


Ext.define ('MA.controller.Win' , {
extend: 'Ext.app.Controller' ,

views: ['Win'] ,

init: function () {
this.control ({
'mawin > button': {
click: function (button) { alert (button.up('window').getId ()); }
}
});
}
});


And now the instances:


for (var i=0; i<5; i++) {
var win = Ext.widget ('mawin' , {
title: 'Sample ' + i ,
id: i
});

win.show ();
}


Now if I click on the 'Push me' button of a random window, the controller shows '4' (the id of the latest window created) instead of the right id (maybe '3' or '0').
So, how can I handle different instances of a view with a single controller?
Maybe, I can't?

Thanks in advance.

Cyaz,
Wilk

mitchellsimoens
3 Sep 2011, 6:08 AM
Believe this is because you are specifying your items as a config like that. Arrays and Objects defined like that will be shared among all instances of the defined class. Try something like this:


Ext.define ('MA.view.Win' , {
extend: 'Ext.window.Window',
alias: 'widget.mawin',

width: 300,
height: 300,

initComponent: function() {
var me = this;

Ext.apply(me, {
items : me.buildItems()
});

me.callParent(arguments);
},

buildItems: function() {
return [
{
xtype : 'button'
text : 'Push Me'
}
];
}
});

This will work because the buildItems method will create a fresh items array for every instance.

Wilky
3 Sep 2011, 12:14 PM
Thanks for your answer but it doesn't work.
It returns the following error on the console:



me.dockedItems is undefined

me.dockedItems.insert(pos + i, item);



of ext-all-debug.js.

Are you sure that your code is right?

skirtle
3 Sep 2011, 5:49 PM
@Wilky. Mitchell's code was just an example, you need to iron out the kinks yourself. Looks to me like it's missing this.callParent(). I'm not convinced it will solve your problem though.

I'm confused by your original code. Firstly, it isn't syntactically valid, the closing brace and parenthetical for the call to this.control are missing. Secondly, as far as I can tell the variable win is the button, not the window, so it alerts the button id. Taking both of these factors into account I'm left wondering whether the code you've posted is actually the same code that's running in your browser?

Could you come up with a complete, minimal test case for your problem? Something we can drop into a skeleton project and just see run?

@mitchellsimoens. As of ExtJS 4, defining items or toolbars on the prototype using xtypes does seem to work. Obviously if you put an instantiated component on the prototype it'll all go boom but an xtype config should share fine. It would go horribly wrong if a component modified its config object but there seems to have been a concerted effort to ensure components don't do that any more. Most of the time I still use the pattern you've described here but it is nice to have the option.

Wilky
3 Sep 2011, 7:03 PM
Thanks skirtle for your report: I just edited my code and now it's correct.
You can setup a MVC-tree with these files and then run the app: what you will see is that every buttons point to the last window created.

I haven't found a solution yet, even if I did a lot of tests.

skirtle
3 Sep 2011, 9:25 PM
I've done what I can with the snippets you've provided. It would be much easier if you gave a complete test case. My test case is below. It works fine in 4.0.2 and 4.0.5. Give it a go. Drop it into an HTML file along with ext-all-debug.js and ext-all.css. It shouldn't need anything else.


Ext.onReady(function() {
Ext.define ('MA.view.Win' , {
extend: 'Ext.window.Window' ,
alias: 'widget.mawin',

height: 300,
width: 300,
items: [{
text: 'Push me',
xtype: 'button'
}]
});

Ext.define('MA.controller.Win', {
extend: 'Ext.app.Controller' ,

views: ['Win'] ,

init: function () {
this.control ({
'mawin > button': {
click: function (button) {
alert(button.up('window').getId());
}
}
});
}
});

Ext.application({
controllers: ['Win'],
name: 'MA',
launch: function() {
for (var i = 0 ; i < 5 ; i++) {
var win = Ext.widget('mawin', {
id: 'win-' + i,
title: 'Sample ' + i
});

win.showAt(i * 50, i * 50);
}
}
});
});

Wilky
4 Sep 2011, 4:29 AM
Wops, that's my fault.
You're: it works fine but I missed the problem.
Look at this example:



Ext.onReady(function() {
Ext.define ('MA.view.Win' , {
extend: 'Ext.window.Window' ,
alias: 'widget.mawin',

height: 300,
width: 300,
items: [{
text: 'Push me',
xtype: 'button'
}]
});

Ext.define('MA.controller.Win', {
extend: 'Ext.app.Controller' ,

views: ['Win'] ,

init: function () {
var myVar = 0;
this.control ({
'mawin' : {
show : function () { myVar++; }
} ,
'mawin > button': {
click: function (button) {
alert(myVar);
}
}
});
}
});

Ext.application({
controllers: ['Win'],
name: 'MA',
launch: function() {
for (var i = 0 ; i < 5 ; i++) {
var win = Ext.widget('mawin', {
id: 'win-' + i,
title: 'Sample ' + i
});

win.showAt(i * 50, i * 50);
}
}
});
});


As you can see, the result displayed will always '5' instead of '0', '1', ...
How can I setup unshared variables?

Cyaz

PS: thanks for your explanation!

skirtle
4 Sep 2011, 9:47 AM
Here are a couple of ways you could do it:


init: function () {
var myVar = 0;

this.control({
'mawin': {
show: function(win) {
win.myVar = myVar++;
}
} ,
'mawin > button': {
click: function(button) {
alert(button.up('window').myVar);
}
}
});
}


init: function () {
var myVar = 0;
var map = {};

this.control({
'mawin': {
show: function(win) {
map[win.getId()] = myVar++;
}
} ,
'mawin > button': {
click: function(button) {
alert(map[button.up('window').getId()]);
}
}
});
}

mitchellsimoens
5 Sep 2011, 7:17 AM
@Wilky. Mitchell's code was just an example, you need to iron out the kinks yourself. Looks to me like it's missing this.callParent(). I'm not convinced it will solve your problem though.

I'm confused by your original code. Firstly, it isn't syntactically valid, the closing brace and parenthetical for the call to this.control are missing. Secondly, as far as I can tell the variable win is the button, not the window, so it alerts the button id. Taking both of these factors into account I'm left wondering whether the code you've posted is actually the same code that's running in your browser?

Could you come up with a complete, minimal test case for your problem? Something we can drop into a skeleton project and just see run?

It was missing a callParent call so I fixed the code so no one who looks at it gets lost.


@mitchellsimoens. As of ExtJS 4, defining items or toolbars on the prototype using xtypes does seem to work. Obviously if you put an instantiated component on the prototype it'll all go boom but an xtype config should share fine. It would go horribly wrong if a component modified its config object but there seems to have been a concerted effort to ensure components don't do that any more. Most of the time I still use the pattern you've described here but it is nice to have the option.

The Object will probably just get transformed from a simple Object (with xtype and other configs) to an actual Component Object so that Object will still get shared. You're saying that Ext JS 3 created a new Object and everything worked fine but in Ext JS 4 the behavior is not the same?

Wilky
5 Sep 2011, 9:13 AM
That works!
Thank you a lot, Skirtle ;)

skirtle
5 Sep 2011, 3:55 PM
The Object will probably just get transformed from a simple Object (with xtype and other configs) to an actual Component Object so that Object will still get shared. You're saying that Ext JS 3 created a new Object and everything worked fine but in Ext JS 4 the behavior is not the same?

It has changed but ExtJS 4 is better.

The array of xtype config objects will be on the prototype for the class. When each instance is created it will read the items property from this and use it to create a mixed collection of components. This mixed collection is then saved in the items property of the instance, which will hide the items property on the prototype but not modify it.

As a result, everything works fine.

There's no reason this couldn't have worked in ExtJS 3 but there was one critical line that screwed it up.

Specifying child items on the prototype in this way strikes me as quite a nice use for xtypes. It isn't always applicable though, if for example you need a reference to this to pass to the child item then you have to go back to using initComponent.