Stand With Ukraine. Stop Putin. Stop War.

The MODX Manager interface is a very rich interface: there's right click options all over the place, modal windows are pretty much second nature and dynamic validation and AJAX submissions make it a very interactive experience. The design is easy for clients to understand, yet it offers the power users even more power to do the things they need to do. All of that is powered by a library called ExtJS, developed by Sencha. As a very powerful JavaScript framework it generates virtually everything you see in the MODX manager. A newer version is also used for the MODX Cloud Dashboard.

I've been learning ExtJS since 2010, every since I got interested in MODX Revolution development and extending, bending and plainly hacking the sh*t out of it. Tutorials, lots of code gazing and client projects, and over two years later I feel like I'm comfortable with what it can do and understanding the often cryptic errors. At some point, it starts becoming a source of frustration. And I can boil that frustration down to one sentence..

I love ExtJS for what it does, but I hate the way it does it.
~ Mark Hamstra

ExtJS is a dream

Don't leave yet! At least hear me out on this one. After having spent 2+ years learning ExtJS and its implementation within the Revolution manager, I am convinced it's a developers' dream. I totally understand why it was chosen to power Revolution back in the day.

  1. Rapid prototyping and interface building. Just define (basically) a big array, and you have a fully functional interface, completely blended in with the rest of the manager.
  2. Packs a large amount of widgets that are infinitely configurable and customizable.
  3. Through its object oriented philosophy, you can easily reuse other definitions in an object oriented fashion and save the hard work. Define a widget once, use it everywhere.
  4. ExtJS makes stuff work and look fancy across browsers.

In a nutshell, ExtJS is an insanely powerful framework that makes fancy stuff easy to achieve. It takes time to learn (and I bet a more senior developer would pick it up much faster than I did), but once you bridged the learning curve you have a lot of power at your fingertips.

ExtJS is a nightmare

  1. It's obviously a JavaScript framework, and ExtJS builds the interface from JavaScript definitions. There are exceptions where you can take existing DOM elements as basis, but you still need to use (and load) all the JavaScript to use the interface as intended. If an error occurs somewhere, ExtJS tends to decide to show an empty page instead.
  2. ExtJS builds elements with a shitload (yes, that's a technical term) of divs and tables. Here's an example an actual "Save" button. My guess is that it does that to make it look good cross-browser, but it clutters up the DOM and makes it more difficult to cleanly create a custom theme with crazy inheritance and royal class-soups. Truth be told, this seems improved in ExtJS 4.
  3. Similar to the previous point, sizes are *very* often defined inline (style="width: 134px;"). This could be helpful, but it isn't one bit if you try to build a more fluid layout.
  4. The ExtJS learning curve is quite steep. In the context of MODX Revolution, this harms community involvement as the amount of developers improving the core is small, and takes a long time to grow. In a proprietary environment (such as MODX Cloud) this isn't a huge concern as you can invest in training and hire the right people, but I'd argue it is a huge problem in the open source scene.
  5. The ExtJS licensing has historically been vague, at least in our context.
  6. ExtJS is slow. Not only does it have to load a few megs would worth of javascript before it can even start building your interface, but it's not particularly fast in doing that in many cases.

Despite being a big proponent of ExtJS for the reasons mentioned earlier, over time I have become more frustrated with the cons of the framework that I believe are doing serious harm to community involvement in Revolution. Learning ExtJS has been a great business decision (it made up probably a good 85% of my freelance work), but as I'm a big community fan I'd rather see something a bit more.. approachable.

The Ideal Framework

So, taken the above considerations, what would be The Ideal Framework for a MODX manager? Here's my personal list of requirements.

  1. Easy to get started with, plenty of existing documentation and tutorials.
  2. Easy to extend and capable of Object Oriented paradigms to cut on development time (rapid prototyping) and code duplication.
  3. Large set of available widgets to build interfaces with, but with a lean core.
  4. Fast.
  5. Progressively enhanced so that users with old browsers can still get a proper experience. As it's a backend I'm fine with it requiring JavaScript, but regular HTML and CSS should provide a baselevel experience.

In my mind (and I can hear sighs coming), jQuery fits a large number of those requirements. Getting started with it is easy, documentation and tutorials are everywhere and the amount of widgets (plugins) available for jQuery is enormous. It easily lets you use progressive enhancements paradigms and considering JavaScript itself is an object oriented language, yes that applies to jQuery as well.

Does just sticking jQuery into the manager provide a maintainable product? No; it needs more thought. The manager is big and complex and just filling up a footer with $(document).ready() tags wont make things better than ExtJS. But I do think a properly implemented object oriented "wrapper" could definitely be what MODX needs.

And that's what I started mocking up.

Introducing a jQuery Based Manager

If you follow me on Github, you may have noticed some recent commits to a new alternative-manager branch in my Revolution fork. This contains a Proof of Concept of how I think a jQuery-object-oriented-wrapper-framework could provide a better experience for the MODX Manager.

So far only the manager login and dashboard has been (mostly) converted, but it's a lot of work. Everything that can be represented as plain HTML/CSS is being converted to that, while other bits of HTML are being used to load up widgets based on a "data-role" attribute.

My first impression of this proof of concept is that it would definitely be powerful, lean and extensible enough to replace a heavyweight like ExtJS, without getting stung by the downsides of the framework. So far (at an early state, obviously), my jQuery alternative sure feels smoother and faster than the ExtJS one too. It needs a lot of work, and tearing out ExtJS of the manager sure is more work than just removing the files, but my belief is that in the end it would be worth it.

So now what?

Now.. I want your feedback. What do you think about ExtJS; and my jQuery-based alternative? Would you support (and better yet; help out) an initiative like this, or am I being a pity fool? Do you want to see a demo?

Depending on your feedback, I want to publish a couple blog posts in the coming weeks on some of the decisions I've made so far and what I think the implications of them are.

Just to prevent any rumors: no I was not asked to work on this for my job at MODX, nor does this proof of concept mean that jQuery is what will power MODX in the future. It's me being curious if jQuery could be a practical alternative and at some point a few weeks ago deciding there's only one way to find out..


UPDATE T+45mins: Despite publishing this article at a time where people are either enjoying a nice drink or a comfortable bed, the response (in the comments, on twitter and on skype) has been awesome! Please do keep it going and let me know anything you'd like to know about the jQuery manager in the comments below. I'll try to answer as much questions about that in a follow up post in the next week or so. Of course, feel free to just go ahead and have a look at the JavaScript. I'll also look into making a public demo available.

Working on my latest project, a (hotel) property management tool for MODX Revolution, I wanted to open a "Pick a Room" window from a "Check Inwindow with tabs, where some of the values in the Check In window would be used to filter the available rooms. Basically it would pre-filter on the check in and check out dates, as well as room type chosen in the reservation process. It took me a bit of debugging before I found the right function in the MODext extension to ExtJS - so I figured I'd share with my honored readers and fellow developers :)

What the Docs tell us

MODx.Window is a MODext implementation of a regular ExtJS Ext.Window object, and the MODx.Window documentation tells us the following:

MODExt Windows are a convenient way to display record data from a Grid or AJAX request for editing. Windows automatically include a FormPanel which you can add form fields (and other components) to. Submitting/saving a Window actually submits the FormPanel, and initiates an AJAX request to your connector.

So compared the Ext.Window, we already have an added formpanel (in this case, the MODext implementation of a form panel) which we can interact with. Awesome! So now let's just get the Window, for example using Ext.getCmp('window-id') or even simply referencing "this" in the handler (assuming you specified scope: this as well), and use our beloved getValues() function on that.

Right? Sorry: function getValues is not defined there!

What the Docs should have told us

While there's definitely a form panel available they've hidden it in plain sight. In most applications this doesn't matter as you would just submit the window which initiates the request to the connector, but if you, like me, want to work with the form's values before submitting to open another window or perform some other kind of logic with the values, you'll need to specifically get the formpanel.

What's going on there:

  1. In our field definition, where my button is which needs to open the window, I defined the "scope: this" to force the scope of the handler to be the FrontDeskMan.window.CheckIn object, instead of the button.
  2. The handler is set to this.openRoomPicker, which is added as extension to the object later on.
  3. Line 18 is where it gets interesting:
    1. First we reference "this", which is the window (use console.log(this) to make sure!).
    2. From "this" we get the "fp" object, which, as you may have guessed by now, stands for FormPanel.
    3. From this panel we use getForm() to get the actual form.
    4. From this form we use getValues() to get an object with the current values in the window.
  4. After that we load the room picker window by it's registered xtype, and pass it two extra properties which will help it identify the form to set the room in, as well as the record to use in the grid in that window. The room picker window "decodes" these values to where they are needed.
  5. We use win.setValues(record) to fill the form inside the window (which provides options to filter by) with the values we already know. While normally you can simply pass the "record" configuration object, in this case my roompicker window uses a column layout with a form layout and a grid, and the "record" config object only works when the entire form is in the "main formpanel", so directly defined as fields and not as tabs in the window or other fancy layouts.
  6. We show the new window.

Hope this helps someone!

For those interested or totally flabbergasted by this weird setup of mine, here's a screenshot of what is going on:

ExtJS is probably one of the biggest challenges when it comes to building components in MODX. It's not mandatory to use it, but as most people will want their components to blend in perfectly, it's an easy choice to make... This blog post will hopefully shine some light on how modExt and ExtJS blend together, and how to use the ExtJS API Docs to keep you going. Oh, and I'm throwing in some ExtJS tips & tricks here and there for the careful reader.

What exactly *is* modExt?

Real simply put you could say modExt is the same as ExtJS, but then with some additions (extensions) by the MODX Core Team and the actual back-end theme installed by default. As ExtJS is a class (or object) based JavaScript framework, so is modExt. It's important to remember that you're not limited to just the modExt implementation - no, you have the entire ExtJS framework at your disposal. Parts of that ExtJS framework have been extended (remember it's an object-based framework? Long live OOP!) to provide some specific functionality. This extended functionality is available through the "MODx" JavaScript variable - all non-overriden functions and classes are available in the "Ext" variable.

Why is it important to realize modExt is just an extension?

Well, if you are anything like me when I first started with components, you have been staring at the modExt documentation for ages but never actually got anything working out of that information. Which now makes sense, as it only lists the (most important) extended classes - not everything you can use!

That's where the ExtJS 3.4.0 API Docs come in. And boy, they are huge! And, if you know where to look, they're definitely very, very valuable.

Getting Practical: taking bdListings as an example

Just to catch up (I assume that if you made it this far into the post, you at least have been working with modExt/ExtJS a bit and understand its general syntax), here's an example of a grid using the modExt implementation of an ExtJS grid which I'll be tearing apart to show the difference between modExt and ExtJS. As it's a quite long example I have cut out some pieces (denoted by /* ... */), you can see the complete source on Github if you want.

bdListings.grid.TargetGroups = function(config) {
    config = config || {};
    Ext.applyIf(config,{
        url: bdListings.config.connector_url,
        id: 'grid-targetgroups',
        baseParams: {
            action: 'mgr/targetgroups/getlist'
        },
        params: [],
        viewConfig: {
            forceFit: true,
            enableRowBody: true
        },
        tbar: [{
            xtype: 'button',
            text: _('bdlistings.create',{ what: _('bdlistings.target') } ),
            handler: function() {
                win = new bdListings.window.TargetGroups();
                win.show();
            }
        }],
        paging: true,
        primaryKey: 'id',
        remoteSort: true,
        sortBy: 'order',
        fields: [
            {name: 'id', type: 'int'},
            {name: 'name', type: 'string'},
            {name: 'sortorder', type: 'int'}
        ],
        columns: [{
			header: _('id'),
			dataIndex: 'id',
			sortable: true,
			width: 1,
            hidden: true
		},{
			/* ... */
		}]
    });
    bdListings.grid.TargetGroups.superclass.constructor.call(this,config);
};
Ext.extend(bdListings.grid.TargetGroups,MODx.grid.Grid,{
    getMenu: function() {
        var r = this.getSelectionModel().getSelected();
        var d = r.data;

        var m = [];
        m.push({
            text: _('bdlistings.update',{what: _('bdlistings.target')}),
            handler: function () {
                win = new bdListings.window.TargetGroups();
                win.setValues(d);
                win.show();
            }
        },'-',{
			/* ... */
        });

        if (m.length > 0) {
            this.addContextMenuItem(m);
        }
    }
});
Ext.reg('bdlistings-grid-targetgroups',bdListings.grid.TargetGroups);

What I want to highlight with this example are the parts given to you by modExt, and what is actually plain ExtJS. I'll just go through the code from top to bottom and highlight a few things of importance.

  • Line 4, 6-9, 26-39. In vanilla ExtJS, every Grid has a data store, which holds the data definitions and values. As MODX components mostly work with AJAX connectors which return JSON, you get the opportunity to instantiate a store without assigning one manually. This works by defining the "url" property (which in this case is the value of the bdListings.config.connector_url, pointing to assets/components/bdlistings/connector.php which acts as our, well, connector). Beyond the url property, we also need to define our action (we can do this as a baseParam or as a regular configuration option - both work), the different fieldnames we expect (line 26-30) and what columns we want to display (line 31-39). These variables are used to create an Ext.data.JsonStore which works seamlessly with the JSON array returned by processors. Conclusion: these properties are a shortcut to an Ext.data.JsonStore, but if you prefer you can still define them manually as you will find all over the Sencha forums and Documentation.
  • Line 16,50. To add easy internationalization (commonly shortened to "i18n" for "starts with an i, then 18 characters, and ends with a n"), you will notice a tiny function with the following signature: _('key',{arrayOf: 'options'}). This is a JavaScript function available throughout the MODX Manager that checks the loaded Lexicons for the key you want. If there's any placeholders in them, you can specify the values for those via the second parameter. Conclusion: this is MODX specific and you wont find it in the ExtJS forums/docs.
  • Line 14-20: While toolbars (defined as a "tbar" or "bbar" for bottom toolbars) are not specific to modExt and they work the same as the ExtJS tbar configuration, there are cases where modExt throws in some bonuses. For example when extending a MODx.tree.Tree you will see that there is already a toolbar defined. Anything you add to the tbar configuration is added after it. If you don't want the default buttons (expand, collapse and refresh) you can set the useDefaultToolbar property to a boolean false and it will only load your own tbar config. Anything else is inherited from ExtJS. Conclusion: works the same as ExtJS, but in some components these will have default configurations.
  • Line 43 shows we are using our own object bdLisings.grid.TargetGroups from the MODx.grid.Grid object. If we were to look at the MODx.grid.Grid documentation, one of the first things we'll see documented is that it extends the Ext.grid.EditorGridPanel object (in turn extended from the Ext.grid.GridPanel object), which means we can use all of that object's properties and functionality with the added stuff from the MODx.grid.Grid documentation. Conclusion: Check out the Ext.grid.EditorGridPanel documentation often!
  • Line 44-63 is a true genius (and also mentioned in the MODx.grid.Grid documentation), and is what shows your right click context menu without having to mess with "rowcontextmenu" listeners, mouse positions and what not (boy has that taken me debug time before!). Basically you instantiate a new Array (in the core often called "m" and use m.push() with the Ext.menu.Item configuration options for every item you want to add to the menu. In the end you can (line 60-62 in this example) call the components' addContextMenuItem method, or (as of MODX 2.1) simply return the array "m". On line 56, the dash will be transformed into a subtle border between the menu items. Note that you can access the right-clicked item in the grid as "this.menu.record" in case you need conditional menus. Conclusion: This is one of the modExt extensions I'm really fond off.

I think my point would be clear... use the ExtJS 3.4 Documentation! Now, on to the next question.. how do we use that?

How to work with the ExtJS Documentation

The ExtJS documentation is huge and really has all the answers you need. Just formulate the questions right!

First find out what the class name is of what you are looking to use. Perhaps you are looking at someone else's code, check out the Ext.extend() line in that case. To related back to the grid example, we saw that extended MODx.grid.Grid which in turn extends Ext.grid.EditorGridPanel. That's the info we need! Open up the ExtJS docs and simply navigate the right tree. Ext > grid > EditorGridPanel. You'll see a few things:

  1. Usually a description of what the class does and some examples. An inheritance chart to the right to see where it's coming from.
  2. Then the config options, which is what you will most often work with. They usually list the default and possible values and, depending on the option, also some code examples.
  3. Somewhere down (halfway the EditorGridPanel doc) you'll find the Properties section. These are variables available "to the outside" when you have a reference to an object. For example "this" depending on the context or "Ext.getCmp('id-of-my-grid')" from anywhere else. You can use this to check the state or configuration of an object.
  4. Further down you'll find the Methods, you can call these on the object. The methods may change its configuration (eg addClass), or return a value to you (eg getPosition). Whenever I have a reference to an object, I like to send it to the console with console.log, allowing me to see that list of methods in Firebug when I need it. (Chrome's Dev Tools doesn't seem to show the available methods).
  5. Waaaay at the botton you'll find a list of Events. These are being triggered by the component or it's parent objects and allow you to hook into certain stuff going on. On top of listing the events, it also lists the parameters it can pass to your callback function.

So basically that's all the information you can possible need about the ExtJS objects. Now get cracking with that component!

Note: I know that doesn't even come close to everything you may need to know, but I think it would've helped me back when I started to have someone point me to a list of events and configuration options for modExt objects.. I've used a bookmark to check what events are bubbling up for months, and abusing console.log into eternity, but having an actual list is amazing! ;)

Oh, one last thing about modExt..

It's very helpful to check out what modExt offers you, and what default configuration may be set if you are extending MODx objects. You can find all the modExt files in /manager/assets/modext/. As there's quite a lot of files in there, I'd like to highlight what you can find in the different folders (relative to the /modext/ folder):

  • core - contains the building blocks of modExt. The actual MODx object in modx.js, the layout builder in modx.layout.js and the base component in modx.component.js. Not the best place to start with modExt if you'd ask me.
  • sections - in the terminology used by the MODX team for both the MODX Core and a number of Extras, a "section" is what kicks off building the interface, by loading an xtype and possibly setting variables. Not too interesting as an extra developer imo.
  • util - contains a few extra xtypes/components you can use including a date+time field, a lightbox and the core upload dialog. The utilities.js file also contains 3rd party components such as superboxselect and some other little gems. Something to check out some time.
  • widgets - now it's getting interesting! A widget can be anything from a panel, grid or window to an individual combobox. The base objects are located in this widgets/core directory, so you can find the MODx.grid.Grid definition there, as well as MODx.tree.Tree and the MODx.Console as well as many more. Their applications in the MODX Manager are in all the other folders under widget, so if you see something in the Manager you want to mimic.. the odds are they're in the widges folder :)
  • workspace - the workspace is special as it's widgets and sections in one folder, specifically for the workspace (basically Package Manager and everything around it).

Don't be afraid to check out the source of the Core or other Components!

For whatever reason, sometimes it's seems complicated to get a working tabbed window that is also a form using ExtJS/modExt. And then when you do, the height of the shadow doesn't resize when the window does when you change tabs. Here's one way to pull all of that off and how I got this working in the bdListings component.

All of the below assumes you know the basics of ExtJS, and that all you need is a gentle push in the right direction to get this working in your own custom manager pages in MODX. If you're not yet at that level, I suggest reading up on stuff.

By default, the MODx.window includes a FormPanel, meaning that it is expecting fields. So while it may sounds more logical to actually use an items or components config option, the window will be expecting fields.

So let's give that to the window, shall we? I've included some default options you probably want here as well.

So what you're seeing there is a few things.

  1. We're passing a title, connector URL and action, closeAction (which in this case destroys the window on close), and the arguement to use autoheight. Note that autoHeight only seems to affect the first load.
  2. We add a field with xtype modx-tabs, which is how we would normally be using the tabs in MODX anyway.
  3. The modx-tabs is given some config options (not all would be neccessary):
    • Use autoHeight
    • Do not defer (=delay) rendering until we open the tab, and force a layout. These options make sure the markup is always avialable, which is essential when you are going to be posting the form and using validation.
    • Give it a 98% width and some padding, as well as a border for looks.
    • Tell the underlying items (the individual tabs) to use the following defaults:
      • Use no border, automatic height and some padding for looks.
      • Use the "form" type layout, this is crucial to display labels and fields correctly.
      • Do not defer rendering & force layout.

Now, not all of those options should be neccessary but I haven't had the motivation to figure out which ones or what combination get the right effect. Maybe someone can add to that in the comments? Thanks!

Now that we've got the basic tabpanel set up, we should add the tabs and some forms. Here's an example of that (which starts off where the last code block ended):

So we're using the known items object here, and we also give this individual tab a title from our Lexicon file. Then we add two items to this tab, namely an "id" statictextfield and a "title" textfield. The statictextfield can be used to just display text that cannot be modified by the average user (of course us web devs could just use Firebug to go in and change the field to a textfield should we want to :) ), and we also tell that to submit the value in it to make sure our processor has access to the ID when saving.

We can repeat the above block for more tabs, more items, etc etc. Just finish off the rest of the configuration like usual, making sure all brackets are closed properly. I personally use PhpStorm and that's pretty awesome at figuring out if something is missing or wrong.

Making sure the window resizes when changing tabs

Now that we've got working tabs in our window you will notice that if they are different in height, you will see the background shade doesn't resize along. For this we can use the syncSize function on the window. Whenever you change a tab, the "tabchange" event fires on the tabpanel which is what we'll need.

Add this code to the tabpanel configuration, ie the modx-tabs xtype we loaded.

What this does is that it listens for the tabchange event, and if that happens we fire the syncSize function. But as that is a function on the window object, and not the tab object, we will need to specify the scope:this to the listener option which makes sure that if we reference "this" it's actually the window object.

You can find a complete example with all these different parts in the bdListings repository.