We launched new forums in March 2019—join us there. In a hurry for help with your website? Get Help Now!
    • 9207 ☆ A M B ☆
    • 2,475 Posts
    I'm starting to build heavier and heavier Add-Ons with sometimes extensive Custom Manager Pages, and I'm struggling more and more to find examples of how to use ModExt JS. It's sometimes tiresome to deconstruct existing plugins ... sometimes I have some success with this, but I hate the "black-box coding" approach: I don't really know why I'm doing anything or how any part of the massive JS configurations really affect anything in the CMP. In short, if every aspect of those objects are not fully documented with examples, it prevents me (and others like me) from using them.

    I'm specifically looking for the following:

    1. A full description of the superclass constructor: what it is, why we need it, and some full examples including variations so we can see some of the attributes in action (E.g. see http://rtfm.modx.com/display/revolution20/MODExt+MODx+Object or https://forums.modx.com/forums/thread/18572/more-modext-woes#dis-post-101958)

    2. An example CMP that uses ModExt JS to draw a simple page (e.g. a terms of use page). I know this can/should be done much more simply via normal page requests, but something this simple would be a perfect demonstration for introducing connectors and tying into the CSS classes that ModExt JS provides.

    Those two things should be really very basic, but I'm struggling to make my code work or to cannibalize existing examples into something simple enough to wrap my head around it.

    Or, can anyone point me to a good reference?

    Thanks in advance. [ed. note: Everettg_99 last edited this post 13 years, 8 months ago.]
      • 18373 ☆ A M B ☆
      • 3,141 Posts
      This may be a good read: https://www.markhamstra.com/modx-blog/2012/01/modext-versus-extjs-and-working-with-their-docs/

      The reason only so little is documented here: http://rtfm.modx.com/display/revolution20/MODExt is because most of it is "plain" ExtJS which is fully documented here: http://docs.sencha.com/ext-js/3-4/ and the web is full of examples.

      I think the ClientConfig admin page is fairly straightforward in terms of ExtJS used..

      Here's the "section", basically the thing that builds the page and references "widgets" through the xtype method to display where it's needed: https://github.com/Mark-H/ClientConfig/blob/master/assets/components/clientconfig/js/mgr/sections/admin.js
      The widgets it uses (which includes grids and windows for the settings and groups) are in the widgets directory: https://github.com/Mark-H/ClientConfig/tree/master/assets/components/clientconfig/js/mgr/widgets
        Mark Hamstra • Developer spending his days working on Premium Extras and a MODX Site Dashboard with the ability to remotely upgrade MODX and extras to make the MODX world a little better.

        Tweet me @mark_hamstra, check my infrequent blog at markhamstra.com, my slightly more frequent ramblings at MODX.today or see code at Github.
        • 9207 ☆ A M B ☆
        • 2,475 Posts
        Hmmm.... not entirely helpful, unfortunately. It's in the right direction, but man... we are missing step by step explanations here that highlight the various parts of Ext JS.
          • 28042 ☆ A M B ☆
          • 24,524 Posts
          Yeah, when it was first decided to use extjs I worked with it a bit to create a fairly basic sortable, searchable grid display. It was not a pleasant experience, and that's one really big reason why it took me so long to switch from Evo to Revo, and why I'm still a bit uneasy about that. Here's a line-by-line explanation of the code for that grid display http://sottwell.pogwatch.com/datagrid-2.html
            Studying MODX in the desert - http://sottwell.com
            Tips and Tricks from the MODX Forums and Slack Channels - http://modxcookbook.com
            Join the Slack Community - http://modx.org
            • 11055 ☆ A M B ☆
            • 3,112 Posts
            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. laugh

            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. laugh [ed. note: goldsky last edited this post 13 years, 2 months ago.]
              Rico
              Genius is one percent inspiration and ninety-nine percent perspiration. Thomas A. Edison
              MODx is great, but knowing how to use it well makes it perfect!

              www.virtudraft.com

              Security, security, security! | Indonesian MODx Forum | MODx Revo's cheatsheets | MODx Evo's cheatsheets

              Author of Easy 2 Gallery 1.4.x, PHPTidy, spieFeed, FileDownload R, Upload To Users CMP, Inherit Template TV, LexRating, ExerPlan, Lingua, virtuNewsletter, Grid Class Key, SmartTag, prevNext

              Maintainter/contributor of Babel

              Because it's hard to follow all topics on the forum, PING ME ON TWITTER @_goldsky if you need my help.