Building a Transport Package: The Right Way
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-premium extras with a clear conscience.
The newest version of MyComponent has not been released yet. It should be out soon. Consider this a preview
- What's New?
- Updating your build.transport.php file
- Upgrading from versions earlier than 3.0
- A Proper Transport Package
- General Usage
- Example Project
- The Project Config File
- MyComponent Utilities: A Quick Introduction
- Bootstrap
- ExportObjects
- ImportObjects
- LexiconHelper
- System Settings and Menus
- CheckProperties
- Build
- CreatePropertiesTable, CreateSettingsTable
- Delete Objects and Files
- Building and Installing the Example Project
- Customizing MyComponent
- Customizing the Tpl chunks
- MyComponent Workflows
- New Project
- Removing Stuff You Don't Need in the config File
- Moving an Existing Project to MyComponent
- Installing your Project Package
- Running Your Component
- Including Other Extras in Your Package
- CMP Support
- The Example CMP
- Processors
- Dashboards and Widgets
- Submitting Your Package
- Avoiding Trouble
MyComponent provides a development environment for creating MODX Extras. Once you've edited the config file, MyComponent will create all files, directories, and MODX objects for your Extra. It will also write your lexicon files for you and create the build file and resolvers.
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.
What's New?
The latest (long-overdue) version has hundreds of changes and improvements. In addition to MODX 2, this version now runs in MODX 3 (without deprecation notices). It also helps you create extras that run in both MODX 2 and MODX 3. The Example project contains a CMP that will run smoothly in either version. (If you already have the Example project installed, delete the files and objects in the MyComponent UI, and create a new version). Be very sure you've switched to the Example project before doing this.)
The new version of MyComponent also provides support for new objects that will work in both versions of MODX, including processors, dashboards, and widgets. It has far too many bug fixes and improvements to list here, as well as many new unit tests to check its integrity.
Updating your build.transport.php file
Important upgrade note. For safety, MyComponent doesn't overwrite existing build.transport.php
files even during upgrades. When new features and bug fixes are added to MyComponent, there is often a change to this file. For existing projects, you'll need to update the build.transport.php file. The best method is to simply rename the existing file in your project's _build
directory and run Bootstrap. You may also need to make minor changes to your project's config file, though usually not unless you were using a very old version of MyComponent or have subpackages (see below). You may also want to update your config file to take advantage of the new objects that MyComponent will handle.
If you have customized the build.transport.php
file, be sure to make a note of your changes, so you can apply them to the new file. If you use PhpStorm, you can highlight the two files with Ctrl-click and press Ctrl-d to see the differences (and move code from the old file to the new one).
This doesn't affect new projects, which will get the new file automatically.
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 Transport Package also needs a namespace with a corresponding namespace path so that MODX can find the lexicon strings. In addition, the snippet and any other elements in 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 (or other) 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 all the files for your extra 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've never tested 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 in the UI. Once that's done, 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, then ExportObjects. LexiconHelper writes your Lexicon files and CheckProperties makes sure any properties you use are present in your transport files. Finally, you run the Build utility to create the actual transport package.
After installing MyComponent, you can see the UI by viewing the MyComponent Resource. There, you can create a new project, work on an existing one, or switch to a different project you want to work on. You can also run most of the MyComponent utilities there, such as BootStrap, ImportObjects, and ExportObjects. More on those in a bit.
The 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 to execute 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 created 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. That's the file you'll edit (not the one in your project) to add or remove Elements, Resources, and other objects in your project. The config 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 edit the top of the tile with your name, project name, copyright, etc., then you 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: A Quick Introduction
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.
Bootstrap gets things off the ground by creating the files, directories, and MODX objects needed for your extra, based on the content of your config file. Bootstrap can also be used to add additional objects you discover you need at any point in a project. Bootstrap is non-destructive. Bootstrap will never modify any existing MODX objects or files.
ExportObjects creates or updates 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 snippets and plugins in their code (and that of files they include) and reports on whether those properties are included in their transport files.
Build creates the actual transport package .zip
file.
So, if you work in the Manager to update any part of your package, you'll run "Export Objects" to update the files. If you work on the files in a code editor, you'll run "Import Objects" to update the objects in the Manager and database.
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 skeleton versions of 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. Not doing so can result in overwriting new versions with old versions.
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 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.). Note that the relationships are described with names rather than IDs, because the IDs won't be determined until your extra is installed. MyComponent will translate the names into IDs when the package is installed. 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 or resources listed in the config file's 'exportResources'
array.
ExportObjects will now update the copyrights in most files created or updated during ExportObjects. Be sure to set the current year (or a range of years, like 2021-2025) in your config file before exporting.
ExportObjects now normalizes file endings, making sure each file ends with a single linefeed and no following spaces. This prevents spurious diff results.
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 what 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 what Resources belong to your project, it uses the 'exportResources'
member in the project config file. Be sure all your resources are listed there. Any missing ones won't get updated in the Manager when you run ImportObjects, and will not be exported when you run ExportObjects.
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 have changed 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. It's a good practice to always run ImportObjects before working on any code in the Manager. Otherwise, you may end up overwriting work done earlier on other files when you run ExportObjects.
For most changes, the recommended method is to edit the files (except for objects like Settings or Menus, which have no files), then run ImportObjects to update the objects in the Manager. There are few exceptions mentioned below, but always run "ImportObjects" before doing any work on your project in the Manager.
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 (always after ImportObjects).
When using a version control system (VCS) like git, it's a good idea to make sure all changes are committed before running ExportObjects. After running ExportObjects, always check the VCS (e.g., git diff
) to make sure there are no surprises. Occasionally, you may find that git diff exposes errors you made by editing files, and then running ExportObjects without having imported the changes into the Manager with ImportObjects. Sometimes there are trivial differences in whitespace or end-of-file characters. I generally commit these with the commit message "MC artifacts," so they don't show up again.
New System Settings: In the current version, any sensitive System Settings that should not be in your package or at GitHub (e.g., passwords, tokens, private keys, etc.) should be created manually in the core namespace. When exporting, MyComponent will ignore them completely because they are not in your project's namespace. New System Settings no longer need to be in the config file. If you put them in the config file, MyComponent will install them, but it's much easier to just create them in the Manager. See the section below on System Settings. The same is true of menus.
ImportObjects
As you might guess, this is the opposite of ExportObjects, though its 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 fields when you change them in the transport.elements.php files. To change the values in element fields, 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]]
In both cases above, LexiconHelper will create the following line in your lexicon file:
$_lang[mc_file_nf = 'File Not Found';
This will also work for Menus in your config file with things like:
'description' => 'menu_name_desc~~Description of Menu',
You can also handle lexicon string in Menus in the Manager by putting something like this in the lexicon_key
and description
fields (the lexicon_key field holds the key to the name of the menu option). Try to use keys that will be unique to your extra:
lexicon_key: my_menu_name~~Menu Name description: my_menu_description~~Description of Menu option
Lexicon strings in JavaScript files in this form will also be found:
text: _('[[+packageNameLower]]_change_category~~Change Category')
Note that for lexicon strings in the format above, you need to add a method to your controller file (which needs to extend a controller class like modManagerController
) like the one below:.
public function getLanguageTopics() { return array('example:default'); }
In the method above, the method must return an array.
Important: The techniques described above cannot be used for Dashboards or Contexts. For those, the name and description are not lexicon keys. They are permanent. The method also doesn't work for Settings of any kind. See the section below on lexicon strings for Settings.
If you want the lexicon strings to go to a specific lexicon file (topic), you can add $modx->lexicon->load(namespaceName:topicName);
. Where this isn't appropriate (such as JavaScriptFiles or data files), you can add that line in a comment and LexiconHelper will still see it, even though it won't be executed. If you don't add that code, LexiconHelper will put the lexicon strings in the default.inc.php
lexicon file.
LexiconHelper will find all instances where you use the MODX Lexicon in your project. 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 what 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 lexicon files. LexiconHelper can be safely run again after adding new lexicon strings.
Whether LexiconHelper modifies the lexicon files and removes the ~~ and its following string from the code files depends 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 (except for the Example project, where both are forced to true
. It is very rare, but there are cases where rewriting the code files will mess up one or more lexicon files. There's a tip below on how to avoid this.
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 (because of the extra quotation marks):
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 its 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 single or double quote 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.
System Settings and Menus
If your project has no new System Settings or Menus, you can skip this part. System Settings are really easy with MyComponent. They don't need to be in the config file. Just create them in the Manager and fill in all fields. In the Name, Description, and Area fields, put just the Name, Description and Area, in your default language, as you would like them to appear (do *not* user ~~
tokens or lexicon keys). As long as they are in a namespace associated with your project, MyComponent will create the proper transport files for them and Lexicon Helper will create the proper lexicon strings.
Steps for System Settings
- Before Creating any System Settings run Import Objects to make sure everything is current
- Do *not* use
'~~'
anywhere - Create your System Settings in the MODX manager as described in the paragraph above
- Run Export Objects
- Run Lexicon Helper
Remember that ImportObjects will generally import only elements and resources. For things like Menus, System Settings, and Widgets, Bootstrap will set them, and their lexicon stings, but to change them, you have to modify them in MODX and run ExportObjects.
The System Setting name
and description
fields don't actually exist. Lexicon keys for them are computed by MODX in the Manager based on the key of the System Setting. For example, a System Setting with the key delay
in the somenamespace
namespace will have its name and description lexicon keys in a language file (usually default.inc.php
) in this that namespace in this form:
$_lang['setting_delay'] = 'Delay'; $_lang['setting_delay_desc'] = 'Delay between cURL requests';
CheckProperties
This utility looks for cases in snippet and plugin code where you use a default property. It checks any usages against the transport 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 the snippet being analyzed has properties that are not used in the snippet code, CheckProperties will report this by listing "Properties in properties file not found in code". This means that either you have a property that is never used in your code, or you're using a non-standard alias for your scriptProperties
array. If it's the latter, you can update the alias list near the end of the project config file. If the report indicates "0 properties in code," it's normal to see that CheckProperties could not find a properties transport file.
If none of your snippets or plugins use properties, you can skip using this utility.
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 that 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.
These two do not run from the MyComponent UI, because there are settings you may want to change before running them. CreateSettingsTable can be run by putting a tag for it in a resource and viewing the resource. It will run based on the current project setting (set in the UI). CreatePropertiesTable can be run the same way, but you need to enter the name of the snippet in the $name variable at the top. Run it for every snippet that has properties.
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 seldom 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, or when you want to upload and test a new version of your project at another site where it's already installed.
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.
- Install or upgrade MyComponent. Clear the site cache so the MyComponent lexicon strings will be updated.
- If you are upgrading and want to see the new version of the Example project, in the UI, switch to the Example project (be sure you've switched! Click twice on "Change Project") and click on remove objects and files.
- View the MyComponent Resource. That will take you to the MyComponent UI. The current project should be set to "Example."
- 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.
- 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. - 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 objects in the Manager. You should see all the Resources and Elements. The Elements will be under the Example category. - 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.
- 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). - 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. If it does not tell you it updated a particular lexicion file, you can cut and paste the$_lang
code it provides - Edit the
default.inc.php
file and fill in any blank lexicon strings. - Run LexiconHelper again. You should see that those strings are no longer reported as empty.
- 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).
- 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. - 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.
- Since the Example objects were already installed, you won't see any new ones, but the Example CMP in the "Extras" menu will now be functional. Try it out. The processors are disabled, so it won't make any real changes to your site.
- Important: It's generally not recommended to install a package in the same site you use for MyComponent.
You may have noticed that we didn't run ImportObjects. That's only necessary if you make changes to the object code files for Resources or 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 Resources or Elements 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.
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)
Notice that Chunks in the Manager have precedence over their code files. When you modify a Chunk's code file, it will be ignored until you run ImportObjects.
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 before you do any development work 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 config file. First, the file 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. "Example" will be replaced with the name of your project.
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 in the master (Example) config file is the personal information at the top of the file (e.g., your email address, website, year, copyright, 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. Remember to update the copyright date at the beginning of each year.
MyComponent 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.
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.
When MyComponent creates a new project, it will show you a message with the location of the new config file fo your project. Make a note of the location.
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 except for the first line, 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 reminder! 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, and it is replaced by the "official" one whenever you run ExportObjects. 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 in the config File
Whenever you first edit the project config file, after creating a new project, 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. Which one 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. This is a good method if you think you might need them later.
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, but most of the time I use the first method above.
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 project 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.
Moving an Existing Project to MyComponent
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.
My usual technique in this situation is to create a new project in MyComponent. Then I edit the new config file to meet the needs of my project, the run Bootstrap. Be careful to use the existing object names (if they match, Bootstrap won't touch them — if they don't Bootstrap will create an extra file you don't want). Next, I run ExportObjects to write all the object files. Finally, I move any files that don't represent MODX objects (e.g., JS, CSS, and image files) into the proper location in the project structure MyComponent has created.
Installing your Project 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 Extras -> Installer 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, but don't install it.
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.
The safest and best solution is to always install the package in a completely separate MODX installation. I use MODX Cloud to do this. I have MODX test installations there for MODX 2, MODX 3, PHP 7, and PHP 8.
In the Manager of the other site, go to Extras -> Installer. Click on the down-arrow in the "Download" box, select "Upload Package," and follow the steps to upload you transport file in the core/packages
directory. Be sure to use the file that ends in .zip
. If the project has ever been installed locally, there will be two files for it. The .zip
file will be further down the page.
The steps above will work on any MODX site, though if it both sites are local, you can just copy your transport file from one core/packages
directory to the other, then select "Search Locally for Packages" in Package Manager. I don't recommend this method, though, because it's too easy to copy them in the wrong direction, especially late at night.
It's OK to install the Example package itself locally, if you're careful, but keep in mind that you don't want to mess with the files in the assets/components/yourcomponent
or core/components/yourcomponent
directories.
Even if you don't install the package in a separate installation of MODX during development, you should do it at least once before releasing the package to make sure that it installs and runs as it should. Occasionally, when your config file is incomplete, things will be missing from the build.
Running Your Component
If you install your component (even though I recommended against that above), 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}mycomponents/yourcomponent/core/components/yourcomponent/
You can load your class file in the snippet with code like this:
$path = $MODX->getOption('mc.core_path',null, $MODX->getOption('core_path').'components/yourcomponent/'); $iFile = $path . "model/yourcomponent/yourclass.inc.php"; require_once $iFile;
For you, the path will resolve to the area with your build script (because mc.core_path
will be found). For others who install the package, however, the system setting will come back empty (because it doesn't exist), so the core path will resolve to that of the installed component at core/components
.
Similarly, you can have a setting for your assets path:
{assets_path}mycomponents/yourcomponent/assets/components/yourcomponent/
or your assets URL:
{assets_url}mycomponents/yourcomponent/assets/components/yourcomponent/
Important: When using the method above, be sure to put the System Settings in the core
namespace, not the namespace of your project. Otherwise, it will be part of the project. When your users install your extra, the code will find the System Setting, and none of your project's paths will be correct.
If it's a large project, using the System Settings described above is cumbersome because you need to use the System Settings to load everything, everywhere. It's easy to miss some and changes you make to the files in the development area won't show up when you run ImportObjects.
Of course another method is to use static elements linked to your code files, 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. ALso, since MyComponent creates its own files for all MODX resources and elements, there's not much reason to use 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 at another site each time you make a change.
Including Other Extras in Your Package
This process has changed from earlier versions of MODX. It's now much easier to require the installation of other extras needed for your package and to specify the version that's necessary for your project to run.
If you would like to include other packages in your transport file, just add them to the requires
array in the Dependencies section of your project config file (assuming that they are available for the user's version of MODX at modx.com/extras
). MODX will install them as part of the installation process for your extra. Here's an example from the example config file:
/* Dependencies */ 'requires' => array( // 'Wayfinder' => '>=2.3.3', ),
Obviously, the lines with the package names you want included shouldn't be commented out.
Notice that you need to specify the version of any extras you're requiring. The line above will install the current version of Wayfinder as long as it's 2.3.3 or greater. The value is commented out in the example config file (and above) so that the extra won't be installed when you install the example project.
If a required version is already installed, it won't be installed during the installation of your project. You can have finer control by using the Composer version constraints described here.
Note that the dependencies operation requires at least MODX 2.4. Also, if you've used an older version of MyComponent, you may need to upgrade your config file and build.transport.php
file to handle required extras.
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.
The Example CMP
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 won't actually change anything at your site.
If you run Bootstrap, ImportObjects, ExportObjects, LexiconHelper, and Build and install with 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
.
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.
Processors
MyComponent will do its best to create processors for you, based on the name of the processor, that will work in both MODX 2 and MOD3. MODX 3 has literally hundreds of processors, many with very specific code, so it wasn't practical for MyComponent to handle all of them. It will do a fairly good job with create, update, getlist, and remove processors, but in any case, the processor it creates will probably need some editing. It will usually be much easier than creating a processor from scratch, but you're free to do that.
Once a processor has been created, MyComponent will never alter it, so your modifications will be safe no matter what MyComponent utilities you run (with the rare exception of LexiconHelper, which can mangle code if your $modx->lexicon()
calls are malformed, though this rarely happens).
Dashboards and Widgets
It's entirely possible to create Dashboards and Widgets in your config file (see the Example config file), but like Menus and System Settings, it's usually easier to just create them in the Manager and run ExportObjects
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, Menus, Widgets and other objects are not updated in that process. If you later run ExportObjects, you will lose your changes. All files in the _build
directory are updated based on the objects in the MODX site (except the build.transport.php
file and the config file). So editing them will have no effect. They will be overwritten the next time you run ExportObjects.
There is another exception to the rule above. You might have custom files of your own in the _build
directory that are not files MyComponent would write. You'd need custom code in build.transport.php
(which MyComponent will never touch) to install them with your package. For example, you might want to create one or more users, or a custom ACL entry. Editing those files would be required and should cause no problems.
Another common pitfall is to make changes to the code files for some objects and changes to other objects in MODX without running ImportObjects or ExportObjects. Whichever of those you run next, will wipe out some of your changes.
This happened to me recently on MyComponent itself, in spite of the warnings here. Late at night, I made some changes in the MODX Manager without running ImportObjects first. After running ExportObjects, one of my code files contained code from six months ago — overwriting the changes I had made in the interim. If you only have one project, this is less likely, but I have about four dozen projects and work on them all in MyComponent. While working on them, I often find bugs or improvements for MyComponent and make changes to it before I forget them. In this case, I made a change to a tpl Chunk in MyComponent, but I wanted to get back to working on the current project and forgot to run ImportObjects on MyComponent. Six months later, the old code in MODX was waiting to trip me up when I ran ExportObjects. Luckily, I had remembered to commit the file changes to Git, but I still needed to make notes on the more recent, uncommitted changes to that file before checking it out, so I could redo them. Without Git, this would have been a total disaster.
If you always run ImportObjects whenever you change a file, and always run ExportObjects when you change something in the MODX Manager, the problem described above will never happen to you IF your config file is up-to-date.
Always remember that ImportObjects will only import things that are listed in your config file. For elements, this means listed in the 'process' member *and* in the elements
array. For resources, in the exportResources array. ExportObjects will export everything in your project namespace or category, overwriting the code of any objects that have code files.
One more error, one I still make occasionally, is to use the down arrow in MyComponent to switch projects, but forget to click on the "Switch Projects" button. When you do this, you haven't really switched projects and any actions you take will be on the wrong project. If you've changed files but haven't run ImportObjects on the other project, for example, you'll be wiping out your changes. This is a belt-and-suspenders move, but I always click on Change Project twice. I don't perform any actions until I see "You're already on that project" and the project name is the one I want to work on. I also commit any project changes to git before switching projects in MyComponent.
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 and run ImportObjects, or paste them into the objects in the Manager 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 (or stash) all changed files just before running ExportObjects or LexiconHelper, and after running them, I run git status
and (if any changed files show up) 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 may still be 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