PDA

View Full Version : View <-> Controller calling issues



DiscoBoy
20 May 2011, 4:58 AM
I have one general understanding problem with the View and Controller logic in ExtJS 4. My app has a Main view and an according Main controller (Application.js). The Main controller creates on start (init: (Ext.widget(...))) the Main view.

My Main view exists of a content and a menu region. Of course I want to have everything broken into smaller reusbale components, therefore my menu is a nested Menu view (in the Main view) with its own Menu controller.

So far, so fine! :>

When the Main view is created, my nested Menu view is also rendered, very nice(!). The problem is that there is no instance of its Menu controller! I have no clue what is the best option to call/invoke the controller for the corresponding view!? I would find it totally senseless to create instances of all possible controllers on application startup!

Also, does each view need its own controller? I would like to have one central controller which loads the menu configuration centrally as model and all views (Menu view) are bound to this model with a store, configured in the view.

So far, the new MVC does not seem to make any application setup more easy but just leaves a lot of new questions without a good best practice example beside the very basic MVC documentation (1 controller, 2 views)

My open questions are:


How to call/interact/communicate between different controllers/views

- Should I instantiate a controller which creates its view, so communication only between controllers? Example: Controller A ---says("Start your View Controller B")----> Controller B

- Should a rendered view automatically instantiate a controller instance if no instance exists? Example: I create a new nested Menu view, there's no controller yet, so automatically load theMenu controller



It's very confusing where to put your store/model

- A controller has options for configuring these (At least loads the script file and if store is set to autoLoad it also loads data)

- A view can also be configured with a store
Should i reference the controllers store here? Then how to call the controller from the view?

freshyseth
23 May 2011, 12:29 PM
These are ALL great questions! Can someone please explain. There seems to be a major disconnect for a lot of users new to Sencha w/ExtJS 4 that don't grasp the view/controller relationships. I've been trying to figure out for days now how to call a controller method from the handler of an action column button. Not even the premium support was very helpful in that regard.

polimux
24 May 2011, 4:32 AM
I have the same questions. Maybe a hint or best practice on how to use the Ext.app.EventBus would help too.

DiscoBoy
24 May 2011, 4:51 AM
I extended Saki's MessageBus for my projects in the past. It works still as plugin and allows me to send messages/data between components. What i'm not sure about still is whether I should hook my bus-plugin in a controller or the view. As I can send events via my bus and setup a controller to react on those events I decided to put the bus-plugin into the views. What remains is the question how a controller is invoked automatically if the view gets created....seems there are more people than me waiting for the right hints.

In general it's not the question of being unable to get it working somehow...but I prefer to follow ExtJS devs ideas - otherwise - i can replace my own code again in 4.1 because they come up with a solution for that...

freshyseth
24 May 2011, 6:08 AM
Thanks for bump @polimux and the reply @DiscoBoy. I believe I found your Event Bus info here in the forums. My concern is that as we are looking to develop an enterprise-level solution we don't want to deviate much from Sencha's core approach so we can work towards long-term support/enhancement of the system sticking to standards.

What I think would be a very handy piece of information is to show a basic forum or blog-type solution that shows a number of different interactions between all the pieces of an ExtJS MVC structured application.

This would cover views for lists, details (using information passed to it from a list), form submissions for posts/comments and how to properly "pass" information between the different components.

Throw a simple authentication solution on top of that to show how you can manage auth via a separate controller, yet, maintain access control in other controllers based on the auth status and/or permissions and you've pretty much answered all my questions.

Anyone have something to this effect that employs ExtJS MVC and not custom plug-ins or extended classes galore?

Siriru
24 May 2011, 7:29 AM
And if multiple instances of the same view are displayed, they shared the same instance of the store ? So if I reload data in a view, all other open views reloading too ?

polimux
24 May 2011, 11:43 PM
@freshyseth: Could you show me where you found on how to use the ExtJs 4.0 EventBus? I have searched but did not find anything specific. And like you and the other have stated, I would very much prefer to use it the "Sencha-Way" than to use the (undoubtedly good) implementation from Saki.

@Siriru: The answer to your question is yes. You can try it by just adding a second users.List to the Simple MVC-Example. You'll see that chaning the users details in one will also affect the second list.

Siriru
24 May 2011, 11:50 PM
So how to create multiple views which they don't share the same instance of the store ? I need to create Store in the view ?

freshyseth
25 May 2011, 5:32 AM
@polimux You'll have to ask @DiscoBoy where he's keeping it. I thought he had posted it in these forums, but after reviewing his forum posts I don't see it. He does mention it a couple of times, though, so I'm sure he can help you out with it.

DiscoBoy
25 May 2011, 6:16 AM
Yes indeed, i was asking this, but i also got no answer! The docs say (Ext.view.View)


store (http://docs.sencha.com/ext-js/4-0/#) : Ext.data.Store@required The Ext.data.Store (http://docs.sencha.com/ext-js/4-0/#/api/Ext.data.Store) to bind this DataView to.


For me it seems the whole dynamic store binding works only for Views that have a Ext.selection.DataViewModel...then this model is bound to the store configured in the view....


The best is to browse the code of Ext.view.View.
I haven't tried it by myself anymore yet...am stuck at some other parts :-(

gelleneu
25 May 2011, 11:51 PM
And if multiple instances of the same view are displayed, they shared the same instance of the store ? So if I reload data in a view, all other open views reloading too ?

Not only that! The opposite case is more realistic: we have multiple Stores for the same Data!

Example? "Users" Ok, that is one Store. With one Model "User".

But now, you want to display the users in a hierarchical order, and display them in a Tree.

What do you need?

Right - an extra TreeStore "UserTree" - with an Extra Model "UserNode" for the same Server data, with the minimal difference of the "children" property and logic.

And why that? Because we have no adjacency functionality on our Standard Stores.
With that, wie would have ONE Store (with a parent_id). The data could be organised in a GridView as a list (parent / order logic ignored), or a tree (order logic used)...

Siriru
26 May 2011, 1:04 AM
Now I'm creating a store IN my view and I am ignoring the stores property of the controller. My question is : for one view <-> one controller or for many views <-> one controller ?

snoekie
26 May 2011, 4:10 AM
For communication between controllers you could also pass through the Application object.

So it would be Controller1 -> Application -> Controller2
Controller instantiation logic can be implemented on the Application.
I think a better pattern to follow would have been the HMVC (http://www.javaworld.com/javaworld/jw-07-2000/jw-0721-hmvc.html) or Hierarchical Model View Controller which has the concept of sub controllers.

Stores are indeed shared between views (which is useful in some cases but there should be a possibility to configure how stores are initiated). You could implement your own store manager on the controller.

Some insight into how Sencha propose we handle these kind of issues would be much appreciated. I think the new MVC pattern has great potential but with the limited examples and documentation it's not easy to build a big application using it.

@Siriu : It kind of depends on what relationships exists between the different views. For instance if you have a grid in your application that needs to be edited using a form in a window you could easily handle that in one controller. However if you have some other, completely unrelated component on your application you could use a seperate Controller for the logic that component needs

Siriru
26 May 2011, 4:36 AM
In fact I have a Panel with two Grids (and two stores).

Here one of this Grid :


Ext.define('Cc.view.absence.Grid', {
extend: 'Ext.grid.Panel',
alias: 'widget.absencegrid',

border:false,

initComponent: function() {
Ext.apply(this, {
store: Ext.create('Ext.data.Store',{
model: 'Cc.model.Absence',
autoLoad: false,
proxy: {
type: 'ajax',
reader: {
type: 'json'
}
},
sorters: {
property : 'startdate',
direction: 'DESC'
},

}),
columns: [
{header: 'Du', dataIndex: 'startdate', flex: 3, renderer: this.formatDate},
{header: 'Au', dataIndex: 'enddate', flex: 3, renderer: this.formatDate},
{header: 'Exercice', dataIndex: 'year', align: 'center', flex: 1},
{header: 'Statut', xtype:'templatecolumn', tpl:'<img src="../images/status-{statusid}.png" alt="{status}" title="{status}" />', align: 'center', flex: 1},
{header: 'Type d\'absence', dataIndex: 'absencetype', align: 'center', flex: 2},
{header: 'Commentaires', dataIndex: 'comment', flex: 6}
],
dockedItems: [{
xtype: 'toolbar',
dock: 'top',
items: [
{ xtype: 'tbfill'},
{ xtype: 'button', text: 'Rafraichir', action: 'refresh', iconCls: 'item-rafraichir', scope: this }
]
}]
});

this.callParent(arguments);
},

formatDate: function(date) {
if (!date) {
return '';
}
return Ext.Date.format(date, 'l d F Y - H:i');
}

});


I put this grid and an other one in a Panel :


Ext.define('Cc.view.absence.Panel', {
extend: 'Ext.panel.Panel',
alias: 'widget.absencepanel',

title: 'Mes absences',
iconCls: 'item-outils',
closable: true,
border: false,
disabled: true,
layout: 'border',

initComponent: function() {
this.grid = Ext.create('Cc.view.absence.Grid', {
region: 'center'
});
this.history = Ext.create('Cc.view.absence.History', {
region: 'south',
height: '25%'
});
Ext.apply(this, {
items: [
this.grid,
this.history
]
});

this.callParent(arguments);
},

getGrid: function(){
return this.grid;
},

getHistory: function(){
return this.history;
}
});

with getter.

In the controller I want to define the refresh action (of the grid store) :


Ext.define('Cc.controller.Absences', {
extend: 'Ext.app.Controller',

models: ['Absence', 'AbsenceHistory'],

views: ['absence.Panel', 'absence.Grid', 'absence.History', 'absence.Form'],

refs: [
{ ref: 'navigation', selector: 'navigation' },
{ ref: 'tabPanel', selector: 'tabpanel' },
{ ref: 'absencePanel', selector: 'absencepanel' },
{ ref: 'refreshButton', selector: 'absencepanel button[action=refresh]'},
{ ref: 'absenceGrid', selector: 'absencegrid' },
{ ref: 'absenceHistory', selector: 'absencehistory' },
],

init: function() {
console.log('Initialized Absence');
this.control({
'absencepanel button[action=refresh]': {
click: this.onClickRefreshButton
},
'absencegrid': {
selectionchange: this.viewHistory
},
'absencegrid > tableview': {
refresh: this.selectAbsence
},
});
},

selectAbsence: function(view) {
var first = this.getAbsenceGrid().getStore().getAt(0);
if (first) {
view.getSelectionModel().select(first);
}
},

viewHistory: function(grid, absences) {
var absence = absences[0],
store = this.getAbsenceHistory().getGrid().getStore();
if(absence){
store.getProxy().url = '/absence/' + absence.get('id') +'/history';
store.load();
}
},

onClickRefreshButton: function(view, record, item, index, e){
var store = this.getAbsenceGrid().getStore();
console.log(this.getAbsenceGrid());
store.load();
},
});

my problem is when I want to display two and more of this Panel (absence.Panel). When I click on one of refresh button, the action actually refresh only the first displayed view, even if I click on the button of the second view.

I'm trying to debug but I realized that getAbsenceGrid() method actually return the same view, always.

Here the controller where I create the view :


Ext.define('Cc.controller.Tools', {
extend: 'Ext.app.Controller',

stores: ['Tools', 'User' ],

models: ['Tool'],

views: [],

refs: [
{ ref: 'navigation', selector: 'navigation' },
{ ref: 'tabPanel', selector: 'tabpanel' },
{ ref: 'toolList', selector: 'toollist' },
{ ref: 'toolData', selector: 'toollist dataview' }
],

init: function() {
this.control({
'toollist dataview': {
itemclick: this.loadTab
},
});
},

onLaunch: function() {
var dataview = this.getToolData(),
store = this.getToolsStore();

dataview.bindStore(store);
},

loadTab: function(view, record, item, index, e){
var tabPanel = this.getTabPanel();
switch (record.get('tab')) {
case 'conge':
this.loadAbsenceForm(tabPanel);
break;

case 'absences':
this.loadAbsences(tabPanel);
break;

default:
break;
}
},

getUserId: function(){
var userStore = this.getUserStore();
var id = userStore.first().get('id')
return id;
},

loadAbsences: function(tabPanel){
var panel = Ext.create('Cc.view.absence.Panel',{
id: 'absence-panel'
}),
store = panel.getGrid().getStore();
panel.enable();
tabPanel.add(panel);
tabPanel.setActiveTab(panel);
store.getProxy().url = '/person/' + this.getUserId() +'/absences';
store.load();
},

loadAbsenceForm: function(tabPanel){
if(Ext.getCmp('absence-form')){
tabPanel.setActiveTab(Ext.getCmp('absence-from'));
}
else {
var form = Ext.create('Cc.view.absence.Form',{
id: 'absence-form'
});
form.enable();
tabPanel.add(form);
tabPanel.setActiveTab(form);
}
}
});

nayato
13 Jun 2011, 7:00 PM
@Siriru,

If you want this.get<MyRefName>() to create new instance each time it is called, you should specify forceCreate: true in the ref definition. I've checked source code on how refs are handled and I've noticed it in there. autoCreate is to create new instance and cache it for future calls, forceCreate is to force new instance creation without caching.

Siriru
13 Jun 2011, 11:34 PM
I do not want to create new instance each time I call this.get<MyRefName>() but I want this.get<MyRefName>() reference the right view, and not only the first created.

DiscoBoy
19 Jun 2011, 11:09 AM
I'm using for now. Ext.each(... and then a ComponentQuery for the views
The refs are so far also useless for me, as long as you have more than one view.

kleins
19 Jun 2011, 10:00 PM
I'd like to point you to this thread started by tobiu asking similar questions: http://www.sencha.com/forum/showthread.php?131671-Advanced-MVC-Best-Practices. I wrote a lengthy reply (#12 (http://www.sencha.com/forum/showthread.php?131671-Advanced-MVC-Best-Practices&p=615488&viewfull=1#post615488)) with my thoughts on the topic a couple of days ago.

@DiscoBoy, I think, it offers answers to your original questions, too.

I'd still be curious about comments and other people's thoughts. I agree with all the previous posters that it would be useful to get some insight about what the sencha people think about all this.