I'll give my shot.
I'll give an example of
ExerPlan, a CMP I built which has uncommon layout, eg: stacking layout, grid, panel, tab, tree, hbox, vbox, column,
you_name_it.
At the end, I have the
26 JS files for the widgets.
You can try to dig its code to get the idea here.
So, here is my basic explanation.
Rules of Thumb:
1. Each widget has its own file, which will be called by
core/components/mypackage/controllers/home.class.php
2. Follow the breadcrumbs, start from the panel.home.js
(or home.panel.js), where it begins, as the starting page of your CMP.
3. When you feel don't fit with the MODx's Ext object, just use Ext right away. (Sometimes I use Ext.window, if I don't need form panel in MODx.window).
MODExt has its own magic.
1. Beginning
There are 2 JS files as the base:
1.
The object definition.
2.
The index layout which calls the object in the home.panel.js.
From your home.panel.js,
design your layout.
Tab? Border? Column? Panel?
At anywhere you want to make a preview, just use html: 'test' property to the config.
eg:
MyPackage.panel.Home = function(config) {
config = config || {};
Ext.apply(config, {
id: 'mypackage-panel-home',
baseCls: 'modx-formpanel',
cls: 'container',
layout: 'border',
defaults: {
collapsible: false,
bodyStyle: 'padding: 15px',
border: false,
autoHeight: true
},
bodyStyle: 'min-height: 500px;', // required as layout:'border' is absolute position
items: [
{
region: 'north',
html: 'north'
}, {
region: 'center',
html: 'center'
}, {
region: 'south',
html: 'south'
}
]
});
MyPackage.panel.Home.superclass.constructor.call(this, config);
};
Ext.extend(MyPackage.panel.Home, MODx.Panel);
Ext.reg('mypackage-panel-home', MyPackage.panel.Home);
2. Improving
Then when you start to create more feature, just create a new widget you need.
For instance, grid in the center.
MyPackage.panel.Home = function(config) {
config = config || {};
Ext.apply(config, {
id: 'mypackage-panel-home',
baseCls: 'modx-formpanel',
cls: 'container',
layout: 'border',
defaults: {
collapsible: false,
bodyStyle: 'padding: 15px',
border: false,
autoHeight: true
},
bodyStyle: 'min-height: 500px;', // required as layout:'border' is absolute position
items: [
{
region: 'north',
html: 'north'
}, {
region: 'center',
// html: 'center'
xtype : 'mypackage-grid-users' // <===== example
}, {
region: 'south',
html: 'south'
}
]
});
MyPackage.panel.Home.superclass.constructor.call(this, config);
};
Ext.extend(MyPackage.panel.Home, MODx.Panel);
Ext.reg('mypackage-panel-home', MyPackage.panel.Home);
Now you need to create a new JS file, to contain this so called
"mypackage-grid-users" widget.
Eg:
MyPackage.grid.Users = function(config) {
config = config || {};
Ext.applyIf(config, {
id: 'mypackage-grid-users',
url: MyPackage.config.connectorUrl,
baseParams: {
action: 'mgr/users/getList'
},
fields: ['id', 'user', 'description', 'sort'],
paging: true,
remoteSort: true,
anchor: '97%',
autoExpandColumn: 'description',
save_action: 'mgr/users/updateFromGrid',
autosave: true,
columns: [
{
header: _('id'),
dataIndex: 'id',
sortable: true,
hidden: true,
width: 40
}, {
header: _('mypackage.user'),
dataIndex: 'user',
sortable: true,
width: 100,
editor: {
xtype: 'textfield'
}
}, {
header: _('mypackage.description'),
dataIndex: 'description',
sortable: false,
editor: {
xtype: 'textarea'
}
}
]
]
});
MyPackage.grid.Users.superclass.constructor.call(this, config);
};
Ext.extend(MyPackage.grid.Users, MODx.grid.Grid);
Ext.reg('mypackage-grid-users', MyPackage.grid.Users);
Save this.
Add this file to be called in the
core/components/mypackage/controllers/home.class.php.
Refresh.
3. Extending
Now you have a basic grid, just like
what Doodles has.
Then you want to create top buttons, bottom buttons, right-click event, etc, then it requires you to create some JS functions.
ExtJs use tbar for top buttons, and bbar for bottom ones.
Originally, it has contextmenu for the right-click event, but here is where MODExt gives its magic, just use getMenu() function to create it.
To create a function to this object, you create it at the extending part.
MyPackage.grid.Users = function(config) {
config = config || {};
Ext.applyIf(config, {
id: 'mypackage-grid-users',
url: MyPackage.config.connectorUrl,
baseParams: {
action: 'mgr/users/getList'
},
fields: ['id', 'user', 'description', 'sort'],
paging: true,
remoteSort: true,
anchor: '97%',
autoExpandColumn: 'description',
save_action: 'mgr/users/updateFromGrid',
autosave: true,
columns: [
{
header: _('id'),
dataIndex: 'id',
sortable: true,
hidden: true,
width: 40
}, {
header: _('mypackage.user'),
dataIndex: 'user',
sortable: true,
width: 100,
editor: {
xtype: 'textfield'
}
}, {
header: _('mypackage.description'),
dataIndex: 'description',
sortable: false,
editor: {
xtype: 'textarea'
}
}
]
});
MyPackage.grid.Users.superclass.constructor.call(this, config);
};
Ext.extend(MyPackage.grid.Users, MODx.grid.Grid, {
getMenu: function() {
return [
{
text: _('mypackage.remove'),
handler: this.removeUser
}
];
},
removeUser: function() {
MODx.msg.confirm({
title: _('mypackage.user_remove'),
text: _('mypackage.user_remove_confirm'),
url: this.config.url,
params: {
action: 'mgr/users/remove',
id: this.menu.record.id
},
listeners: {
'success': {
fn: this.refresh,
scope: this
}
}
});
}
});
Ext.reg('mypackage-grid-users', MyPackage.grid.Users);
This right-click menu has a function to remove a user, where its function also must be defined in here.
So, at this point, you can see that this file has 2 major sections:
1. at the top, it's only a config section
( MyPackage.grid.Users = function(config) { ..... )
2. at the bottom, there are methods, which can be your own or any method to extend or even override MODx's.
( Ext.extend(MyPackage.grid.Users, MODx.grid.Grid, { ..... )
4. Beyond
Once you can grab this basic concept, you have everything in your hands.
Anyway,
When you have a widget, you don't have to specified a full config.
You can have them partially, but then define the rest config when you call the widget.
Eg:
I'll add a top bar to add a new user, and also an update function for a contextmenu, which will call the same Window widget.
Window widget:
MyPackage.window.User = function(config) {
config = config || {};
Ext.applyIf(config, {
url: MyPackage.config.connectorUrl,
preventRender: false,
fields: [
{
xtype: 'hidden',
name: 'id'
}, {
xtype: 'textfield',
fieldLabel: _('mypackage.user'),
name: 'user'
}, {
xtype: 'textarea',
fieldLabel: _('mypackage.description'),
name: 'description'
}, {
xtype: 'textarea',
fieldLabel: _('mypackage.sort'),
name: 'sort'
}
]
});
MyPackage.window.User.superclass.constructor.call(this, config);
};
Ext.extend(MyPackage.window.User, MODx.Window);
Ext.reg('mypackage-window-user', MyPackage.window.User);
What are missing in this widget if you compare them with other tutorials?
1. title
2. baseParams
These are what we are about to define differently on our different calls.
MyPackage.grid.Users = function(config) {
config = config || {};
Ext.applyIf(config, {
id: 'mypackage-grid-users',
url: MyPackage.config.connectorUrl,
baseParams: {
action: 'mgr/users/getList'
},
fields: ['id', 'user', 'description', 'sort'],
paging: true,
remoteSort: true,
anchor: '97%',
autoExpandColumn: 'description',
save_action: 'mgr/users/updateFromGrid',
autosave: true,
columns: [
{
header: _('id'),
dataIndex: 'id',
sortable: true,
hidden: true,
width: 40
}, {
header: _('mypackage.user'),
dataIndex: 'user',
sortable: true,
width: 100,
editor: {
xtype: 'textfield'
}
}, {
header: _('mypackage.description'),
dataIndex: 'description',
sortable: false,
editor: {
xtype: 'textarea'
}
}
],
tbar: [
{
title: 'mypackage.create_user',
handler: {
// THIS ONE !!!!!
xtype: 'mypackage-window-user',
title: _('mypackage.create_user'),
baseParams: {
action: 'mgr/user/create'
},
blankValues: true
}
}
]
});
MyPackage.grid.Users.superclass.constructor.call(this, config);
};
Ext.extend(MyPackage.grid.Users, MODx.grid.Grid, {
getMenu: function() {
return [
{
text: _('mypackage.update'),
handler: this.updateUser
}, {
text: _('mypackage.remove'),
handler: this.removeUser
}
];
},
removeUser: function() {
MODx.msg.confirm({
title: _('mypackage.user_remove'),
text: _('mypackage.user_remove_confirm'),
url: this.config.url,
params: {
action: 'mgr/users/remove',
id: this.menu.record.id
},
listeners: {
'success': {
fn: this.refresh,
scope: this
}
}
});
},
updateUser: function(btn, e) {
if (!this.updateUserWindow) {
this.updateUserWindow = MODx.load({
// AND THIS ONE !!!!!
xtype: 'mypackage-window-user',
title: _('mypackage.user_update'),
baseParams: {
action: 'mgr/users/update'
},
record: this.menu.record,
listeners: {
'success': {
fn: this.refresh,
scope: this
}
}
});
}
this.updateUserWindow.setValues(this.menu.record);
this.updateUserWindow.show(e.target);
}
});
Ext.reg('mypackage-grid-users', MyPackage.grid.Users);
So by only defining 1 (one) window widget, you can use it for 2 purposes.
Well, yeah, I found that this case usually happens for window widget, but not others :p
Wow. A long post........
I hope this is what you're asking. 
At last, you can then make 1 file for those widget files if you like to.
But I just like to keep it that way.
Easier to maintain.
[ed. note: goldsky last edited this post 13 years, 2 months ago.]