Building a Transport Package: The Right Way

I could tell you how many hours it takes to develop a MODX extra Transport Package complete with a build script, properties, multiple MODX elements, internationalized strings, error checks, and then fully test it, but you wouldn't believe me. If you use this extra and like it, please consider donating. The suggested donation for this extra is $20.00, but any amount you want to give is fine (really). For a one-time donation of $50.00 you can use all of my non-commercial extras with a clear conscience.


PayPal

Much of the code in this package was inspired by various MODX developer's build scripts for existing MODX Revolution components and by the structure of the MODX Package Manager code.


Important upgrade note. For safety, MyComponent doesn't overwrite existing build.transport.php files. When new features are added to MyComponent, there is often a change to this file. For existing projects that use any new or updated features, you'll need to update it manually, either by copying the file from the core/components/mycomponent/elements/chunks/ directory to your project directory, or by simply deleting the file in your project's _build directory and running Bootstrap.

This doesn't affect new projects, which will get the new file automatically.


Version 3.1.0-3.2.2

Nothing has changed from versions 3.0.x that should affect existing projects. A number of bugs have been fixed and there are some fun new features.

Most notable, Version 3.0 has support for Custom Manager Pages (CMPs) and subpackages are now installed when your package is installed (see below). In addition, LexiconHelper has been completely refactored and vastly improved. It no longer uses the project config file. Instead, it walks through all of the project directories and analyzes all files that might contain lexicon strings. It's also smarter about detecting which lexicon file to use for which files and about handling properties and settings files. The handling of quotation marks in object names, properties, and elsewhere is also much more reliable.

Version 3.2.2 is now fully compatible with MODX 2.3.x. The handling of menu items has been updated. (If you had trouble with Menu items in version 3.2.1, set the namespace of the Menu item to the namespace of your extra — 3.2.2 will do this automatically.) CMPs are now fully class-based, simpler, and more secure. Version 3.2.2 also has a number of bug fixes as well giving you the ability to specify methods for class files and list specific CSS and JS files you want to create (see the new example.config.php file). It also features better handling of static elements, and many other improvements.


Upgrading from versions earlier than 3.0

Major changes were made in version 3.0. If you are upgrading from an earlier version, the best practice is to uninstall and remove MyComponent and install the newest version, which is infinitely more powerful and easier to use.

A Proper Transport Package

In creating a Transport Package, it's not enough to just install the various pieces of the component. Even a simple snippet may have properties. Those properties should usually be installed as default properties of the snippet. The properties also need descriptions, which should be presented as lexicon strings so that they can be translated into other languages. Any messages displayed by the snippet (e.g., prompts and error messages) should also be in the form of MODX lexicon strings.

The snippet also needs a namespace with a corresponding namespace path so that MODX can find the lexicon strings. In addition, the snippet and any other parts of the package should be in a separate category named for your package. The files in the package also need to have appropriate comments at the top saying who wrote them, what they do, and in the case of executable files, a short version of the GPL license.

All that sounds a lot more difficult than it is. The MyComponent package is designed to make it as easy as possible by doing most of the work for you.

MyComponent assumes that you will put the files for any MODX add-ons you develop under the assets/mycomponents/ directory. When located there, MODX will leave them alone during site upgrades and they will not interfere with your component when you install it on the same site. (MyComponent itself, will be installed in the core/components/mycomponent/ directory.) If you develop an add-on called MySnippet, all of its files would then go in the assets/mycomponents/mysnippet/ directory.

It's possible to have your project stored in a different location by changing the paths in the project directory, but it's not recommended. I believe it works, but I never test it because all my projects are in the assets/mycomponents/ directory.

Because of the complexity of the process of creating an extra for MODX Revolution, MyComponent sounds about 10 times as complicated as it is. Please don't be scared off by how difficult this all sounds. MyComponent is actually *very* easy to use once you get used to it. It will let you create a transport package for your extra in a fraction of the time it would take you to do it all manually, so it's well worth the effort to learn how to use MyComponent.

General Usage

MyComponent can be used in a variety of ways, but the basic process is to create a new project (either in the UI, or by manually creating a new project config file and editing the current.project.php file). Once the new project exists, you edit the project config file to meet your needs and run Bootstrap to get things started. After that, you either finish your project in the MODX Manager and run ExportObjects, or finish it by editing the files in your favorite editor and run ImportObjects (or some combination of those two processes). LexiconHelper writes your Lexicon files and CheckProperties makes sure any properties you use are present in your properties file. Finally, you run the Build utility to create the actual transport package.

Example Project

MyComponent installs with a built-in project called Example that you can play with to get familiar with the various MyComponent utilities. The utilities can be run from the command line, or from inside any good code editor. There is also a User Interface for them that you can see by viewing the "MyComponent" Resource (which executes the MyComponent snippet). The rest of this tutorial will assume that you're using the User Interface, but the principles are the same for the command-line utilities. The command-line utility files are in the core/components/mycomponent/elements/snippets/ directory

If you view the MyComponent Resource in the front end, you'll see a set of radio buttons, one for each of the MyComponent utilities, and a Submit button for executing them. Note that as a security precaution, you must be logged in to the Manager in order to use any of the utilities. When you run any of the utilities, a log of the results will appear below the form. You may have to scroll down to see it.

The Project Config File

Every project used with MyComponent must have its own project config file. The file is always named for the lowercase package name of your Extra (packageNameLower.config.php). The files are always stored in the core/components/mycomponent/_build/config directory. The file for the Example project is called example.config.php. If you look at it now, you'll see all the objects that are part of that package.

The Example project config file contains every kind of MODX object that MyComponent can handle. Many of them won't be used in a typical component, but they are there as examples in case you need them. When you create a new project, MyComponent will copy the Example config file to make a new project config file for your extra. Once that's done, you just remove the stuff you don't need and edit the rest to meet your needs.


Be careful, the project config file used by MyComponent is in the core/components/mycomponent/_build/config/ directory, so be sure to modify that one for your project rather than the one under the project's _build directory.

MyComponent Utilities

The basic utilities in the MyComponent package are Bootstrap, ExportObjects, ImportObjects, LexiconHelper, CheckProperties, and Build. Each one plays a separate role in the creation of a transport package. Here's a quick introduction. Be sure to read the more detailed information on them below.

Basically, Bootstrap gets things off the ground by creating the files, directories, and MODX objects needed for your extra. Bootstrap can also be used to add additional objects you discover you need at any point in a project.

ExportObjects creates code files and build files based on the project as it exists in the MODX Manager.

ImportObjects updates elements and resources in the Manager based on changes in their code files.

LexiconHelper writes your Lexicon Files for you and checks to make sure that there are no empty, missing, or unused lexicon strings.

CheckProperties looks for references to default properties of elements in code and produces code to be pasted in to the properties file for the build.

Build creates the actual transport package .zip file.

Let's look at the utilities in more detail.

Bootstrap

The Bootstrap utility should always be run at the beginning of any new project, but only after editing the project config file to meet your needs. Bootstrap creates the necessary files and directories for your project and also creates the MODX objects (e.g., snippets, chunks, etc.) specified in the project config file.

Bootstrap is completely non-destructive. It will not overwrite any existing MODX objects or their code files (though it will update some specific build files). You can re-run Bootstrap at any time during the development of your extra. The usual reason for doing this is discovering that you need another object (a new snippet, chunk, resource, template, etc.) for your project. Add the new object to the project config file and run Bootstrap. MyComponent will create the new object and any necessary files for it to be included in the build.

It's important to keep your project config file up-to-date with all objects used in your project.

For a first look, view the MyComponent Resource from the Manager. With the current package set to Example, check the "Bootstrap" radio button in the UI and click on the "Submit" button. If you scroll down, you should see a log of all the actions performed by the Bootstrap utility.

Follow up by looking at the directories and files created by Bootstrap. They will be in the assets/mycomponents/example/ directory. All MyComponent projects will be placed in assets/mycomponents/ unless you specify a different target directory in the project config file (not recommended).

You may notice that a copy of the project config file is placed in the project directory. This is a copy used by the build process, but it's not the real project config file and you should never edit it. It is ignored by all the other MyComponent utilities except the Build utility, which uses a small part of it. The *real* project config file (the one used by MyComponent) is always in the core/components/mycomponent/_build/config/ directory.

Once you've looked at all the files and directories created by Bootstrap, go back the the MODX Manager and look at the objects created. You should see all the objects specified in the Example project config file. Notice too that the project config file specifies a number of characteristics of some of the objects (e.g., parent, template, category, etc.). You should see these relationships in the Manager as well.


There are a lot of things you don't have to worry about with MyComponent. It puts all files in the proper locations and gives them standard names. All relationships between objects are handled automatically. Resources get the proper Templates. TVs are attached to the proper Templates. Plugins are attached to their events. Property Sets are attached to Elements. Elements get put in the proper categories. MyComponent handles this all automatically behind the scenes. Unless you're doing something very exotic, you should never have to directly edit a transport or build file (though you can if you want to).

ExportObjects

This utility creates code and build files for all the objects in your project based on the objects that exist in the MODX Manager. It will only process the objects listed in the 'process' member of the array in the project config file.

It will skip snippets or plugins with single-line "include" statement as described above. More specifically, it will skip any snippet or plugin that contains exactly one semicolon and also contains the word "include".

While Bootstrap uses the project config file to decide which objects and files to create, ExportObjects (with one exception) uses the categories and namespaces of the actual objects. The exception is Resources. Since there is no way for MyComponent to know which Resources belong to your project, it uses the 'exportResources' member in the project config file.

If any exported elements have default properties, ExportObjects will automatically create a properties file that will be used in the build to transport the properties.

Unlike Bootstrap, ExportObjects will overwrite the code files for any objects that are processed unless the 'dryRun' member of the project config file is set to true. If that's the case, ExportObjects will report what files it would be changing without actually changing them. Be careful — if you work on the code of a file that is represented by an object in MODX, be sure to run ImportObjects before running ExportObjects or your work will be overwritten by the content of the MODX object.

The main function of ExportObjects is to let you create or modify MODX objects in the Manager, test them, then export them to files for the build phase of package creation. ExportObjects will not only update the code files for elements and resources, it will also create or update *all* the necessary files for making a transport package for your project.

You should always run Bootstrap at least once after editing the project config file (and before running ExportObjects for the first time). It's also a good practice to run ExportObjects as a last step before running Build.

As of Version 3.2.0, there are two changes in ExportObjects that help keep sensitive values out of your package. First, property sets are not exported unless they are specified in the project config file (see the Example project config file). This allows you to create property sets containing API keys or values that are only relevant to your local install without having them go into the package. Second, values for new System Settings in the config file will override the values from the database. With this change, you can now set an API key, password, or secret key in a System Setting or Property Set without having it end up at GitHub or in your package.

ImportObjects

As you might guess, this is the opposite of ExportObjects, though it's use is more limited. ImportObjects updates any elements or resources in MODX that are listed in the project config file based on the code files on the disk. If you make changes to a snippet's code file, for example, you can use ImportObjects to update the snippet. It's important to remember that ImportObjects processes *only* the elements listed in the 'process' member of the project config file *and* it only processes elements that exist in the 'elements' member. As of version 3.2.0, ImportObjects also processes any resources listed in the 'exportResources' member of the config file.

Note that ImportObjects will not update element descriptions when you change them in the transport.elements.php file. To change element descriptions, you need to modify them in MODX and run ExportObjects.

One common use of ImportObjects is to update elements in MODX after LexiconHelper has modified the language strings in the files.

LexiconHelper

LexiconHelper will create your lexicon files for you (no, really). To get the full benefit of LexiconHelper, you need to put your lexicon strings in this form:

'lexicon_key~~Actual Lexicon string'

For example, you might have this in the code of a snippet:

$message = $modx->lexicon('mc_file_nf~~File Not Found');

Or you might have this language tag in a resource or chunk:

[[%mc_file_nf~~File Not Found]]

LexiconHelper will find all instances where you use the MODX Lexicon. It will create a language string and write it into the appropriate lexicon file for you (and optionally remove the part after the ~~ from the file). The only case where this will fail is if you have a file that uses more than one lexicon topic. If that's the case, LexiconHelper doesn't know which file to write the various language strings to. In that case LexiconHelper outputs the lexicon strings to the screen so you can paste them into the appropriate files.

Whether LexiconHelper modifies the lexicon files and removes the ~~ and it's following string from the code files depend on the following two settings in the project config file:

rewriteLexiconFiles
rewriteCodeFiles

By default the first one is true and the second one is false. Generally, you want to run LexiconHelper as the last step before doing the final build. I usually leave rewriteCodeFiles off until the very end of a project.


Important warning! If any of your $modx->lexicon() calls are malformed, rewriteCodeFiles can trash some of your code. Always back up all your code files, including any properties files (preferably with Git) before running LexiconHelper with rewriteCodeFiles = true.

LexiconHelper's search algorithm for lexicon strings is very smart but it's not perfect. It will correctly handle single quotes inside double quotes and vice versa (assuming the code is valid PHP), but it will fail on a line like this:

echo $this->modx->lexicon('mc_file_nf~~File Not Found') . ': ' . $filename;

Because LexiconHelper processes the file a line at a time, the solution is easy. Just make sure that each $modx->lexicon() is on it's own line with a hard return at the end and no quotation marks after the call. In other words, the code above should look like this:

echo $this->modx->lexicon('mc_file_nf~~File Not Found')
    . ': ' . $filename;

It's fine to have code ahead of and behind the $modx->lexicon() call as long as there are no quotation marks following the call on the line.

It's a good practice to run LexiconHelper with $rewriteCodeFiles => false until you have corrected all the lexicon references in your files. If the references are malformed, you'll see it in the LexiconHelper output. Once they are all correct and all strings have been added to the lexicon files, you can set $rewriteCodeFiles => true and run LexiconHelper to remove all the ~~ strings in the lexicon references. Be sure to do this (or remove them manually) before running final Build prior to releasing your package or the ~~ strings will end up in your package.

If your editor has a regular expression search, here's a handy regex that will find a common LexiconHelper error:

lexicon\('[^\)]+\.

The error is to put some extra code inside the $modx->lexicon() argument. Here's an example:

$output .= $modx->lexicon('mc_file_nf~~File Not Found' . ': ' . $fileName);

The error is that the $fileName is part of the argument to the lexicon() call. It's easy to do, especially if your editor has auto-completion for parentheses. The correct code would be:

$output .= $modx->lexicon('mc_file_nf~~File Not Found') . ': ' . $fileName;

The regex will turn up any cases where a period occurs before the closing ) of the lexicon() call. If it doesn't find anything, you haven't made that mistake. Note that the regex can give you false positives if your lexicon string contains a period, so not all matches are errors. It's worth checking, though, because the error will result in incorrect lexicon strings that you may not notice before releasing your package.

If your project doesn't have any lexicon strings (e.g., a simple utility snippet or plugin), you can skip using LexiconHelper altogether.


LexiconHelper has been completely refactored for MyComponent 3.1.0. It no longer uses the config file to select files, instead, it searches your entire development directory and checks all files that might have lexicon strings in them. It will find lexicon strings in all resources and elements, JavaScript files, controllers, processors, and connectors. In addition, it will process lexicon strings correctly for properties and settings.

CheckProperties

This utility looks for cases in code where you use a default property. It checks any usages against the properties file(s) in the project's _build directory. CheckProperties uses the 'scriptPropertiesAliases' member near the end of the project config file to tell it how to find usages. It already knows about most common aliases for the $scriptProperties array, but feel free to add ones you use if they're not there already.

If none of your resources or elements use properties, you can skip using this utility.

Note that your project may have properties that you don't want included in the default properties. If so, don't add those to the build files.

Build

If your package is going to be distributed to the public, the final step is to submit your Transport Package .zip file at https://modx.com/extras

Of course, you'll need a transport.zip file first. That's the job of the Build utility. Build runs the build.transport.php file which actually creates the .zip file. The build.transport.php file is universal (or almost universal). It examines the _build directory for your project and creates a package based on the files it finds. At present, it will not handle users or permission-related objects, but will do just about anything else (see the example.config.php file for examples of what it *will* handle).

The build script will also create or update any minimized JS file in your project if you have that option set in the project config file, so you can count on them always being up to date in the package. As of MyComponent 3.1.0, you have the option of using either JsMin or JsMinPlus to minimize your files. JsMinPlus uses a source-level JS parser so it is a little slower, but more reliable. You can also choose to create a js-min-all file which will combine all the JS files in your project into a single minimized JS file.

CreatePropertiesTable, CreateSettingsTable

CreatePropertiesTable finds all properties used in the code of a snippet or plugin and their included classes and creates a table to paste into your docs. CreateSettings does the same thing with settings. Because they work on a variety of files, you'll need to edit the beginning of the code to tell it which files you want processed.

Delete Objects and Files

These are rarely used. Their only purpose is to completely remove project objects and/or files so that you can start over from scratch. I use them often in developing MyComponent, but they are almost never necessary when building another project, except at the very beginning when you find that your project config file is not correct and you want to start over.

Building and Installing the Example Project

If you are new to MyComponent, or would like to see the new features in action, building and examining the Example project is a good exercise. Just follow the steps below.

  1. Install or upgrade MyComponent. Clear the site cache so the MyComponent lexicon strings will be updated.
  2. Preview the MyComponent Resource. That will take you to the MyComponent UI. The current project should be set to "Example."
  3. Check the "Bootstrap" radio option and click on the "submit" button. Once Bootstrap has finished, you can scroll down to see the log showing everything it has done. Marvel at how much it did and how little time it took.
  4. Take a look at Example project config file (core/components/mycomponent/_build/config/example.config.php). This is similar to the one you will use for your project, though yours will probably be much simpler. The Example config file contains an example of almost everything you might want to do with MyComponent.
  5. Examine the project files in the assets/mycomponents/example/ directory. This directory contains all the files that are part of the Example project. Look at the Example files in the Manager. You should see all the Resources and Elements. The Elements will be under the Example category.
  6. Go back to the MyComponent UI (preview the MyComponent Resource again if you've closed it). Select the "ExportObjects" radio option and then click on the "Submit" button. Scroll down and look at the log to see what ExportObjects did.
  7. In the project directory, you can now look in the _build directory to see the transport and resolver files that will be used to build the Example transport package file. Note that if you had modified any of the Example objects in the Manager before running ExportObjects, the files would reflect your changes (try it if you like — you can make changes in the Manager and run ExportObjects again).
  8. Go back to the MyComponent UI and run LexiconHelper. When it's finished, you can examine the on-screen log and look at the lexicon files in the assets/mycomponents/example/core/components/example/lexicon/en/ directory.
  9. Edit the default.inc.php file and fill in the blank lexicon strings. Use these three strings: "Example CMP", "Delete", and "Are you sure?". These strings are used by the Example CMP. If they're empty, the CMP will still work, but some messages will be blank.
  10. Run LexiconHelper again. You should see that those strings are no longer reported as empty.
  11. You can run CheckProperties if you like, but it won't show much since the Example project doesn't have any (unless you created some).
  12. In the MyComponent UI with the Example project selected, select "Build" and click on the "Submit" button. This will build the Example transport file in the core/packages directory. This is the file you submit to MODX when you want to distribute your package.
  13. Go to Package Manager, click on the down arrow at the upper left of the grid and select "Search Locally for Packages." This will place the Example transport package in the grid. Reload the page (this is only necessary with certain browsers to prevent seeing a blank console in the next step). Click on the "Install" button for the Example package. Clear the site cache and reload the Manager Page.
  14. Since the Example objects were already installed, you won't see any new ones, but the Example CMP will now be functional. Try it out on the Components menu. The processors are disabled, so it won't make any real changes to your site.

You may have noticed that we didn't run ImportObjects. That's only necessary if you make changes to the object code files for elements in the project directory and want to import those changes into the Manager before running ExportObjects. ImportObjects is handy if you want to work with the files rather than the objects in MODX, but always make sure any objects you change are listed in the project config file before running ImportObjects, or they won't be updated in the Manager.

Customizing MyComponent

Any of the Tpl chunks used by MyComponent (as well as the example.config.php file) can be customized. Rather than modifying the chunks (or files) directly, you should duplicate them and prefix the name with 'my' so your changes will survive upgrades to MyComponent.

Customizing the Tpl chunks

If you'd like to modify any of the MyComponent Tpl chunks, duplicate them and prefix the name with "my". MyComponent will always look first for the prefixed version before using the default chunk or file. Here is the search order for the various Tpl chunks/files (the files are in the core/components/mycomponent/elements/chunks directory):

myTplName (chunk)
myTplName (file)
TplName (chunk)
TplName (file)

Each chunk in the MyComponent category is duplicated as a file in the chunks directory mentioned above. If you prefer to work with the files, simply delete or rename the chunks in the MODX Manager and MyComponent will use the files in the chunks directory.

The first chunk you'll want to modify is the example.config.php chunk. It's used as a template for all new projects. Duplicate it and call the chunk myexample.config.php (or remove the chunk and rename the example.config.php file in the core/components/mycomponent/elements/chunks directory to myexample.config.php).

There are two important things to remember when editing the file. First, the file is mainly a large PHP array and it must be valid PHP in order for MyComponent to function. Use a good code editor and edit carefully. Second, do *not* change the word Example anywhere — it's necessary for the file to work properly as a template for new projects.

Keep in mind that the example.config.php file is a template for all future project config files. When you create a new project, the example.config.php file will be your starting point. You edit it to meet the needs of whatever project you are currently working on *after* you create the new project in the UI. Once you have it the way you want it, you should never need to edit it again.

What you mainly want to change is the personal information at the top of the file (e.g., your email address, web site, etc.). You may also want to remove some of the example objects (especially the second category) if you're sure that you will never need them. You'll always have the original example.config.php file to refer to.

Workflows

MyComponent is designed to work with a variety of workflows. The odds are good that no matter how you prefer to work, MyComponent will make your life easier. These workflows assume that you have created a duplicate of the example.config.php Tpl chunk (or file) called myexample.config.php and modified the information at the top of it to include your information. Important: Do *not* change the word "Example" anywhere in the file. When you create a new project, all instances of "Example" will automatically be changed for you. Keep in mind that this config. file is a template for *all* projects, not just your current one, so it should not be modified for any specific project.

There's a great workflow chart contributed by MODX user, thingstodo, here.

New Project

If your component is new, the process is very simple. In the UI, type the name of your new project in the form field next to the "New Project" button, then click on the button.

What happens when you click on the "New Project" button? MyComponent creates a copy of the example.config.php Tpl chunk (or, more likely, the myexample.config.php Tpl chunk) and places it in the MyComponent _build/config directory. It renames the file after your new project and replaces all instances of the word "example" with the case-appropriate name of your project.

Once that has occurred, you can edit the new file (projectName.config.php) to meet the needs of your project and then run Bootstrap. Comments in the project config file will (hopefully) tell you all you need to know about the various members. You can generally delete any object types that you don't need in your project. Note that all config files are a single large PHP array. They must contain valid PHP code in order for MyComponent to run, so using a good code editor like PhpStorm can be really helpful.


Important! Although there is a copy of your project config file in your project directory, you should never edit that file. Most of it is ignored by the MyComponent utilities. The "official" project config file is the one in the core/components/mycomponent/_build/config directory and that's always the one you should modify.

If you make changes to the project config file and they have no effect, you're probably editing the wrong one. Just edit the right one and MyComponent will overwrite the one in the project directory with it when you run ExportObjects.

Removing Stuff You Don't Need

Whenever you first edit the project config file, you'll be doing two things: removing the objects you don't need and editing the ones you do need. You shouldn't have to edit any of the stuff at the top, because you did that when you created the myexample.config.php chunk or file.

There are three methods for removing unwanted objects from the file and which you use is a matter of personal preference. One method is to change them to empty arrays. If you don't need any snippets, for example, you can change the snippets section to this:

$snippets => array(),

Be sure to use => instead of = and to end the line with a comma rather than a semicolon.

The safest way to make the change is to locate and put the cursor on the opening parenthesis of the array. Hopefully, you have a code editor that will highlight the matching closing parenthesis (if not, by all means get one). Just highlight and delete everything between the two parentheses, leaving the parentheses themselves and the following comma.

The second method is to simply remove the member altogether, so the entire section below would be removed:

$snippets => (
  blah
  blah
  blah
),

The third method is to comment out the stuff you don't want using /* ... */ comment tags. This is handy if you'll want to add the objects in later, but you have to be careful. You can't have comments inside of comments in PHP, so you'll have to either delete any comments in the section or change them to // comments.

I tend to use a combination of these methods depending on my future plans for the project.

Once you're finished editing the project config file, you can run Bootstrap and all your Resources, Elements, and other objects and all necessary files for the project will be created for you. You can work on them in the Manager until you get things working the way you want. Once the component is finished, you can run ExportObjects, LexiconHelper, and Build and your Extra will be ready to release.

Note that any time you discover that your project needs another object, you can add it to the project config file and re-run Bootstrap. Bootstrap will not modify any existing files or objects, so it's always safe to do this and it's a good practice because it keeps your config file current.

Existing Project

If your finished component already exists in MODX and the files are in the proper directories, you can edit the project config file and run Bootstrap, ExportObjects, LexiconHelper, and Build and your extra will be ready to release.

It's important that the files be in the proper locations and named properly so that MyComponent can find them. The easiest way to see where things go is to run Bootstrap on the Example project and look at the assets/mycomponents/example/ directory.

Normally, MyComponent uses standard naming conventions for all element code files:

snippet1.snippet.php
plugin1.plugin.php
template1.template.html
chunk1.chunk.html

For a new project, Bootstrap will create the files using this standard naming convention.

For an existing project, you can rename your files to match the format above, or you can specify the filename as a field for any element in the project config file (see the examples in example.config.php) if your file names don't match the standard.

Install the Package

Once you have run the Build utility, the transport.zip file will be in the core/packages directory (the one for the main MODX install). If you go to System | Package Management in the MODX Manager and select "Search Locally for Packages," your extra should show up in the grid where it can be installed like any other package. You may not want to do this, however.

There is a potential source of confusion here. Let's say your package is called yourcomponent. If you install your package in the same install of MODX that contains your _build directory, you'll have duplicate files in

assets/mycomponents/yourcomponent/assets/yourcomponent

and

assets/yourcomponent

as well as

core/mycomponents/yourcomponent/core/yourcomponent

and

    core/yourcomponent

It's easy to get confused and edit the wrong file when you're working on the package. In addition, the objects created by Bootstrap or ImportObjects already exist in the Manager so you won't know whether they were installed by your packages or were already there.

Often, the safest and best solution is to install the package a completely separate MODX installation. To do that, just copy the transport.zip file for your extra from the core/packages directory to the core/packages directory of the other MODX install. Then, in Package Manager, search locally for packages and install the package as usual. Even if you don't do this during development, you should do it at least once before releasing the package to make sure that it installs and runs as it should.

Running Your Component

If you install your component it should run. If you need to make changes to any parts of it, remember that those changes won't show up when you run it until you uninstall, rebuild, and reinstall because MODX is running the version you installed, not the version you are building in the project's development directory.

There's a way around that, thanks to a method developed by Shaun McCormick, but you have to remember what you're doing or it's easy to get confused.

If you would like to actually run the code for plugins and snippets that your build script is packaging, you can create new System Settings with the paths and URLs for that version. Then in the code of your snippets and plugins, get that path with a line like this (this example has "mc." as a prefix — you would use your own prefix):

$path = $MODX->getOption('mc.core_path',null,
    $MODX->getOption('core_path').'components/yourcomponent/');

If, for example, you set the mc.core_path system setting to:

{assets_path}components/yourcomponent/core/components/yourcomponent/

You can load your class file in the snippet with code like this:

$iFile = $path . "model/yourcomponent/yourclass.inc.php";
require_once $iFile;

For you, the path will resolve to the area with your build script. For others who install the package, however, the system setting will come back empty (because it doesn't exist), so the path will resolve to that of the installed component.

You can also replace the snippet and plugin code with something like this:

return include $MODX->getOption('mc.core_path') .
    'core/components/yourcomponent/elements/snippets/yoursnippet.snippet.php';

Of course another method is to use static elements, though I generally don't recommend doing that. Any class files will be loaded from the installed location in core/packages but the snippets and plugins will come from your build location. That can be very confusing. It's also very easy to forget what you're doing and lose work with static elements.

If you use any of these techniques, don't forget that you are running off the build files. If any files are missing or mis-referenced in the build script, you won't know about it until you run your component in a MODX install that doesn't have the system settings.

If that's all too confusing, just skip it and remember to uninstall, build, and install your package each time you make a change.

Including Other Extras in Your Package

If you would like to include other packages in your transport file, just put the transport.zip files for those extras in this directory:

assets/mycomponents/yourcomponent/_build/subpackages/

Note that you don't have to do anything with the project config file for this. The MyComponent build.transport.php file will detect the packages and will automatically add them to the package and create resolvers and validators for each one.


If you have existing projects, remember that the build.transport.php file is never overwritten once it exists. When you upgrade MyComponent, you may need the new version of that file for each project. Either copy the file from the core/components/mycomponent/elements/chunks/ directory to the assets/components/yourcomponent/_build directory, or (easier) just delete the old build.transport.php file in your project's development directory and run Bootstrap for each project.


The transport files for the other extras will be transferred to the core/packages/ directory when your extra is installed and will be automatically installed, one-by-one, but only if the included extras are newer versions or not installed already.

If you have played with installing, reinstalling, and uninstalling the Articles extra, which uses the same method to install Quip, Archivist, and some other extras, you my have noticed that things can get a little funky in the process. You may see some disturbing and convoluted error messages in red in the console. This is unavoidable in the current version of Package Manager.

It happens mostly on reinstall of the main package. The validators fail because the subpackages are already installed. The main package will reinstall fine, but the subpackages won't, though as far as I can tell, no harm is done. You can reinstall the subpackages one by one if you need to.

Something similar can happen when you uninstall the main package, especially if you've uninstalled any subpackage first. Again, as far as I can tell, the right things happen in spite of the error messages.

CMP Support

One of the challenges of creating a full-featured Custom Manager Page (CMP) in MODX is getting all the necessary files in the right places. As of MyComponent 3.1.0, the Example project config file contains a cmpFiles section. The Example project contains a semi-working CMP, complete with an action file, a connector, a controller, processors, and all the necessary modExt JS files. If you run Bootstrap, ExportObjects, and Build and install the Example extra, you'll see the Example CMP on the Components menu (after refreshing the Manager page). MyComponent uses the new class-based files (which I like a lot) for any CMPs.


In order for the Example CMP to display proper messages and prompts, you need to run LexiconHelper and then fill in the blank lexicon strings in the Example project's default.inc.php file *before* running build.transport.php.

The Example CMP is a grid-based utility (kind of like Batcher) that lets you perform bulk actions on elements. The grid will be updated, but the processors are disabled (with a return statement at the beginning), so playing with it it won't actually change anything at your site.

To create your own CMP, you can modify the $cmpFiles section of the project config file and MyComponent will create all the necessary files and put them in the right places. Some will be skeleton files, some will be usable as is, and some will be usable after you modify them to meet your needs. Depending on the nature of your CMP, you may have to make a lot of modifications, but it beats creating all the files from scratch.

Submitting Your Package

As we said earlier, when you run build.transport.php the Transport Package .zip file will be created in your site's core/packages directory. The final step is to submit that .zip file at https://modx.com/extras. You'll need to log in and then click on the "submit a new extra" link.

Be sure to fill in all the appropriate sections of the submission form and designate the file you attach as a Transport Package. If you have installed the package on your site, there will also be another file with the same name, but without the .zip extension. Be sure to submit the .zip file rather than the other file.

Avoiding Trouble

MyComponent is a complex and powerful extra. I've tried to make it difficult to shoot yourself in the foot with it, but you can still do it. Here is one common way. You make some changes to the files in the _build directory of your project (perhaps adding a new System Setting or changing the fields of a Resource). You run ImportObjects, but System Settings and Resource fields are not updated in that process. If you later run ExportObjects, you will lose your changes. Remember that ImportObjects only processes elements that are defined in the project config file *and* listed in the 'process' member of the array.

There are several ways to avoid this kind of trouble. One is to always add new objects to the Project Config File and run Bootstrap. Then, *always* make modifications that will affect the build files in MODX rather than the build files. That way ExportObjects will always reflect your changes (this is generally what I do). The second way is to stop using MyComponent at some point in the project and rely solely on the files. You can still run LexiconHelper as needed, but stop running ImportObjects and ExportObjects. Note that this warning doesn't apply to the code files for elements, which can be imported with ImportObjects before running ExportObjects, but the warning below does.

Another common pitfall is to make changes to the code files for elements and then run ExportObjects, which will overwrite your changes with the content of the elements in MODX. Running ImportObjects first will prevent that, but *only* if the elements are listed in the 'process' member *and* in the $elements array. Whenever you create a new object, be sure to add it to the project config before running anything.

If you install your extra in another version of MODX for testing, remember that changes you make there will never make it into your package unless you cut-and-paste them into the project files or (better) paste them into MODX and run ExportObjects.

Important Tip — If your package creates new System Settings (or User or Context Settings), be sure to add a prefix to their keys. That way you'll be sure they won't collide with any existing settings. "Creating" a System Setting called core_path, for example, can trash your site.

Whatever method you use, it really helps to have a version control system, such as Git, in place. I commit any changes just before running ExportObjects or LexiconHelper, and after running them, do a git status and sometimes git diff before committing the changes to make sure no unwanted changes have occurred.

 

My book, MODX: The Official Guide - Digital Edition is now available here. The paper version of the book is available from Amazon.

If you have the book and would like to download the code, you can find it here.

If you have the book and would like to see the updates and corrections page, you can find it here.

MODX: The Official Guide is 772 pages long and goes far beyond this web site in explaining beginning and advanced MODX techniques. It includes detailed information on:

  • Installing MODX
  • How MODX Works
  • Working with MODX resources and Elements
  • Using Git with MODX
  • Using common MODX add-on components like SPForm, Login, getResources, and FormIt
  • MODX security Permissions
  • Customizing the MODX Manager
  • Using Form Customization
  • Creating Transport Packages
  • MODX and xPDO object methods
  • MODX System Events
  • Using PHP with MODX

Go here for more information about the book.

Thank you for visiting BobsGuides.com

  —  Bob Ray