What I want to achieve is very simple, I want to have a main menu all across my application, with the same functionality for all the views.
I would like to create a view that contains exclusively the menu portion plus its own viewcontroller. What would be the [best] way to achieve this?
I am using ExtJS 5 implementing the MVVM paradigm.
Thanks!
This is a pretty broad question about how to architect the app that is pretty difficult to answer without knowing more about other parts of the app.
Generally, anything application global (that is not the application container/viewport) is probably easiest to implement with MVC. Menu controller (MVC controller) would listen to menu events and then it would drill down the component hierarchy to call components' API methods to execute the actions.
I could be more specific if I knew the app.
I would create a main view, in which you define the fixed parts of the application, as well as a container with layout 'fit' to hold the changing "views". Instead of layout 'fit', this could also be a tab panel or something. Nothing prevents you from add behaviour to the fixed part of this main view using a view controller for it of course.
Pretty straightforward in fact. Then, you'll change the current app view by putting it into the main view's central container. You'll need some kind of decision logic and configuration data to define the available views in your application. This would probably best to wrap that in a single place dedicated to this very task only, that is an app controller (rather than the view controller).
Here's an example fiddle, and bellow is the reasoning explaining the different parts of the code:
So, you'd start with a view like that:
Ext.define('My.view.Main', {
extend: 'Ext.container.Container',
xtype: 'main', // you can do that in ext5 (like in touch)
// shortcut for that:
//alias: 'widget.main',
controller: 'main',
layout: 'border',
items: [{
xtype: 'panel',
region: 'west',
layout: {type: 'vbox', align: 'stretch', padding: 5},
defaults: {
margin: 5
},
items: [{
xtype: 'button',
text: "Say Hello",
handler: 'sayHello'
}]
},{
// target for app's current view (that may change)
xtype: 'container',
region: 'center',
layout: 'fit'
}]
});
Ext.define('My.view.MainController', {
extend: 'Ext.app.ViewController',
alias: 'controller.main',
sayHello: function() {
Ext.Msg.alert("Controller says:", "Hello :-)");
}
});
Then, you set this main view as the "viewport" of your application. I also add a method to change the center view. I think the Application instance is a good place for that, but you could move this method to another dedicated app controller...
Ext.application({
name : 'My', // app namespace
// in real life, Main view class would lie in another file,
// so you need to require it
views: ['Main'],
// from ext 5.1, this is the config to auto create main view
mainView: 'My.view.Main',
// we also register a ref for easy retrieval of the main view
// (the value 'main' is the xtype of the main view -- this is
// a component query)
refs: {
main: 'main'
},
setCenterRegion: function(cmp) {
// getter generated by refs config
// you could another layout in the main view, and another component query of course
var center = this.getMain().down('[region=center]');
// replace the current center component with the one provided
center.removeAll();
center.add(cmp);
}
});
So, now, you can change the view with code like this:
My.getApplication().setCenterRegion(myView);
You could wire it through the ViewController of the main view, and using it as handlers in your view. For example, in your ViewController:
changeView: function() {
// retrieve the next view, this is application specific obviously
var myView = ...
// My.getApplication() gives you the instance created by
// Ext.getApplication in namespace 'My'
My.getApplication().setCenterRegion(myView);
}
And, in your main view, use an item like this:
{
xtype: 'button',
text: "Change view (by view controller)",
handler: 'changeView'
}
That could be fine for simple applications, nevertheless that seems like mixing of concern. Deciding about application level view swapping seems more like an application controller's business. So, I would rather recommend to put the changeView method in an app controller, and exposes it to components with a component query, like this:
Ext.define('My.controller.Main', {
extend: 'Ext.app.Controller',
config: {
control: {
// components will have to match this component query
// to be animated with the change view behaviour
'#changeView': {
click: 'changeView'
}
}
},
changeView: function() {
My.getApplication().setCenterRegion(/*
...
*/);
}
});
And you would hook the behaviour to components in any view like this:
{
xtype: 'button',
text: "Change view (by app controller)",
// will be matched by the controller component query
itemId: 'changeView'
}
Related
I have this button class defined as follows :
Ext.define('Mine.view.buttons', {
extend: 'Ext.container.Container',
alias: 'widget.MyClass',
requires: [
'Mine.view.Main',
'Mine.view.newDashboard'
],
xtype: 'myclass',
items: [{
xtype: 'button',
html: '<img src=\"../../iDeviceMap.png\" width="76" height="76"/>',
text: '',
width: 76,
height: 76,
handler: function() {
this.redirectTo('main', true);
}]
});
Which i embed in a Panel view along with other components (composing the main view) defined in another class.
My problem is that this.redirectTo('main', true); doesn't work. How to do the redirection with the right "this" that of the main view where the button is defined (say Mine.view.Main) ?
Q2 : how to call the parent view (Mine.view.Main) from the handler of the button?
Thanks.
The button handler should be a function in the controller. The method in the controller with then call the redirectTo method.
Or you could get the panel's controller in the button's handler and call it from there.... best to do it in the controller so if you write a different view for a phone or tablet each view can share the controller.
A fiddle showing switching panels
Same fiddle so you can see the hash change
EDIT: #ltes - I wrote a fiddle that did what you wanted it to do. You ask a question about two methods. It would be much faster for you to simple review the documentation on each of these methods.
Ext.create method
Ext.Container down method
You don't understand the tool kit you are using. Ext.Create will create a new object this.getView().down and up() look in the hierarchy to find a component. If you want to replace a panel then use the remove() and add() method on the panel. YOu don't have to execute Ext.create when you add you can simply pass a config:
panel.add({
xtype: 'panel',
title: 'This is my new panel',
html: 'new panel data'
}
how do I point this menu item click to launch a method in a controller.
The item click is being hit successfully but the error message states No method named "onDownloadTopdayRecapContextButton" on ExtApplication4.view.main.MainController. That is the problem, you can see the view's controller is portalRealtime-portalRealtime.
So somehow its pointing to the wrong controller. Can someone show me what I am doing wrong?
menu code
var contextMenuTopday = Ext.create('Ext.menu.Menu', {
items: [{
text: 'Download Topday Recap',
iconCls: 'downloadIcon',
listeners: {
click: 'onDownloadTopdayRecapContextButton'
}
grid menu is held in
Ext.define('ExtApplication4.view.portalRealtime.PortalRealtime', {
extend: 'Ext.panel.Panel',
xtype: 'app-portalRealtime',
itemId: 'portalRealtimeItemID',
requires: [
'ExtApplication4.view.portalRealtime.PortalRealtimeController',
'Ext.form.action.StandardSubmit'
],
controller: 'portalRealtime-portalRealtime',
title: 'Main Portal',
layout: {
type: 'vbox'
},
items: [
//i deleted some grid code here
collapsible: true,
collapseDirection: 'left',
listeners: {
itemcontextmenu: function (view, rec, node, index, e) {
e.stopEvent();
contextMenuTopday.showAt(e.getXY());
return false;
}
{
You are creating the context menu outside of your view, so it does not inherit your controller.
Before using the below code please scroll to the bottom of the answer for a better solution, but this hopefully shows what is the cause of your issue.
If this doesn't solve your issue, please comment and provide a more complete code example, and I will update my answer
In these cases you can pass a controller manually, but you need to pass as a parent, as you get all kinds of problems if you re-use the same controller on multiple components (when you destroy one for example, it destroys the controller, leaving the other without)
So you could create from within your view like so:
Ext.define('ExtApplication4.view.portalRealtime.PortalRealtime', {
initComponent:function(){
this.callParent(arguments);
this.contextMenuTopday = Ext.create('Ext.menu.Menu', {
controller:{
parent: this.getController()
},
items: [{
text: 'Download Topday Recap',
iconCls: 'downloadIcon',
listeners: {
click: 'onDownloadTopdayRecapContextButton'
}
}]
});
}
Then rather than use a variable to access the context menu you can access the contextMenuTopday property, as you are within a child item you may need to traverse to your actual view, the simplest way of doing this is via the up method available on components, you would need to make sure you include an xtype to do this:
Ext.define('ExtApplication4.view.portalRealtime.PortalRealtime', {
xtype:'portalrealtime'
Then from within the context menu you can do:
itemcontextmenu: function (view, rec, node, index, e) {
this.up('portalrealtime').contextMenuTopday.showAt(e.getXY());
}
A better way
Best illustrated looking at this fiddle: https://fiddle.sencha.com/#view/editor&fiddle/1qpn
Define your menu as its own class:
Ext.define('Example.ContextMenu', {
xtype:'testmenu',
extend:'Ext.menu.Menu',
items: [{
text: 'Download Topday Recap',
iconCls: 'downloadIcon',
listeners: {
click: 'onDownloadTopdayRecapContextButton'
}
}]
});
Use a method on your controller for the itemcontextmenu event (This is good anyway as it provides a better separation of concerns):
itemcontextmenu: 'showContextMenu'
Then add a a few new methods to your portalRealtime-portalRealtime controller:
getContextMenu:function(){
if(!this.contextMenu){
this.contextMenu = this.getView().add({xtype:'testmenu'});
}
return this.contextMenu;
},
showContextMenu:function (view, rec, node, index, e) {
// we can't use showAt now we have added this to our view, as it would be positioned relatively.
this.getContextMenu().show().setPagePosition(e.getXY());
}
What we are doing here is adding the context menu to the view, so it inherits the controller (and a viewmodel if provided).
The best way to call methods on your controller for listeners/button handlers etc is to just specify the method name as a string i.e.:
listeners:{
itemcontextmenu: 'showContextMenu'
}
This will automatically look up the responsible controller and use the correct method.
If you need to call from within a component you will find that this.getController() fails unless you call on the actual component the controller is attached to - i.e. you are calling from a child component. In these cases you can use this.lookupController() to find the inherited/responsible controller and then call any methods from here e.g. this.lookupController().myMethod()
My problem is, when i click on the Button, the following Error is displayed:
TypeError: EntryForm.show is not a function
Main.js
Ext.define('MyApp.view.main.Main', {
extend: 'Ext.container.Container',
.....
do Stuff
.....
tbar: [{
text: 'Button',
handler: function(){
var mask = Ext.create('MyApp.view.main.EntryForm');
mask.show(this);
EntryForm.js
Ext.define('MyApp.view.main.EntryForm',{
extend: 'Ext.Widget',
....
generate my items for the form
....
When I use the EntryForm widget in the main.js class and use the create instead of the define and save it in a variable it works without problems.
I do not fully understand your question, but I think you need to add the EntryForm view to the Viewport before trying to show it, e.g.
var mask = Ext.create('MyApp.view.main.EntryForm');
Ext.Viewport.add(mask);
If EntryForm is set to hidden by default, then you would still need the
mask.show();
This link may also help http://training.figleaf.com/tutorials/senchacomplete/chapter2/lesson3/5.cfm
What's the proper way of setting the (title of) a detail view with an Ext navigation view?
1st approach
In the Sencha list tutorial (video) the controller does
this.getMain().push({
xtype: 'presidentdetail',
title: record.fullName(),
data: record.getData()
});
and the detail view is a regular Ext.Panel with a tpl.
2nd approach
However, the navigation view example that ships with the Sencha Touch download (code here) takes a totally different approach. Here the controller essentially does
this.showContact = Ext.create('AddressBook.view.contact.Show');
this.showContact.setRecord(record);
this.getMain().push(this.showContact);
and the detail view contains quite a bit of code I don't understand (yet)
Ext.define('AddressBook.view.contact.Show', {
extend: 'Ext.Container',
...
config: {
title: 'Information',
baseCls: 'x-show-contact',
layout: 'vbox',
items: [
{
id: 'content',
tpl: ...
},
...
},
updateRecord: function (newRecord) {
if (newRecord) {
this.down('#content').setData(newRecord.data);
..
}
}
});
Confusion
For a Sencha/Ext rookie like me this is confusing. What advantages does one approach have over the other? How are they different in what they achieve?
The first approach updates the title of the detail view (which effectively is the title in the navigation bar) in the controller. I have not yet found out how to do the same using second approach. Any hints?
In ExtJS 4 the new feature is Model-View-Controller architecture. Yet ExtJs4 examples are made for some simplified cases and thus violating that ideology.
I have created a standard hierarchy of MVC javascript files (controller, view, model, store). In view file, I have at some point a button.
If you check the examples online, it's typically such code:
items: [
{
xtype: 'button',
styleHtmlContent: true,
text: 'Upload image',
flex: 1,
formBind: false,
handler: function() {...}
...
}]
so even ExtJS4 examples are "suggesting" to put executable part inside form definition. While MVC does require all action to reside in controller javascript file.
Another issue is the actioncolumn in a table. Below is a definition of the column in standard table configuration:
xtype: 'actioncolumn',
width: 50,
fixed: true,
altText: 'Actions',
items: [
{
icon: '/delete.png',
tooltip: 'Delete',
handler:function (grid, rowIndex,colIndex){
...}
The thing is, here handler accepts some grid-specific parameters!
Just for reminder, in extjs 4 canonical example of controller file is something like
Ext.define('MyApp.controller.mainController', {
extend: 'Ext.app.Controller',
onButtonClick: function(button, e, options) {
// alert("Button has been clicked "+button);
// TODO which button?
},
init: function() {
this.control({
"button": {
click: this.onButtonClick
}
});
}
});
My question is how to rearrange the code in order to:
1. remove handler: directives from any button on screen
2. attach actions to buttons in the controller file, including, of course, separate action for separate buttons.
3. Do the same for actioncolumn table columns, and successfully recognise which action on which row and column was triggered (that is, completely transfer function call from "handler" to controller file).
Thanks in advance, Askar
The ExtJs examples are not all up-to-date with the MVC architecture. What's more, MVC is not a compulsory design - some users might prefer not using it, so still important to show examples without MVC.
Pretty much everything you've asked is answered in this docs tutorial.
With regards to 3: I think it is some misleading from your part. Although you do define `this.onButtonClick' in your controller, the called function will still get all the parameters, just like your button handler method would - the caller component is usually the first parameter delivered with each event. You can, of course, just define more specific selectors using actions or ids in your controller. Again see the link above for an example.