Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
ContentBox itself is a ColdBox application, with 3 special modules that do all of the hard work. Below you will see the shell of the ColdBox app, with the key folders expanded and bolded.
Although ContentBox has a large number of folders and files, most customization activities will be done in a few key locations. This should make navigating the ContentBox source files easier.
We recommend using modules for extending ContentBox. Modules are easy to install, and can be managed through the ContentBox admin, activating and deactivating as needed. Modules can contain widgets, interceptors, admin or front end menu items, including handlers and views.
Themes are self contained folders, within ContentBox's themes folder. Themes themselves are module like, with a few added features, which means you can include modules in your themes for truly deep and powerful themes.
Traditional ColdBox Modules can be installed outside of ContentBox's control and life-cycle with ColdBox's normal conventions.
Initial ContentBox Website Install
coldbox ( The ColdBox core, including system and ColdBox dependencies )
config ( The ColdBox app config folder )
handlers ( The ColdBox app handlers folder )
layouts ( The ColdBox app layouts folder )
models ( The ColdBox app layouts folder )
modules ( The ColdBox app layouts folder )
contentbox
content ( default location for the media manager - customizable through Admin settings )
email_templates ( Email templates for adding authors, comments, pages, blog posts and many more )
i18n ( i18N properties files for DE, US, SV, IT, BR currently )
models ( Core ContentBox Models and Services )
modules ( Modules here are always loaded no matter what. These are core sub-modules of the ContentBox core module )
modules_user ( ContentBox life-cycle controlled modules. Manually installed or downloaded from Forgebox. These Modules can be activated and deactivated through the admin )
Hello ( Hello World Sample Module )
themes ( ContentBox Themes, manually installed or downloaded from Forgebox )
default ( Default ContentBox Theme )
tmp
updates ( Location for downloaded and manually installed patches )
widgets ( Default ContentBox Widgets - Override-able )
Application.cfc
ModuleConfig.cfc
contentbox-admin ( ContentBox Admin - removable for security reasons )
contentbox-ui ( Contentbox UI Module )
modules_app ( Home for all of your custom ColdBox modules, outside of ContentBox's lifecycle )
views ( The ColdBox app layouts folder )
.htaccess
Application.cfc ( The main ColdBox Application.cfc )
box.json
favicon.ico
index.cfm
license.txt
readme.md
robots.txt
server.json
In developing your user interface, the majority of your time will be spent creating content in the admin and modifying files from within your custom theme, located in the [ContentBox Module Home]/themes
directory.
With state-of-the art development tools like CommandBox to assist with scaffolding and dependency management and Coldbox Elixr, it's easy to build out your theme in a fraction of the time it might otherwise take.
A typical theme directory structure might be:
css
fonts
js
For more information on the theme directory structure and configuration options see ContentBox Theme Development.
In addition, many front-end focused modules and libraries are available on Forgebox and can be installed from the CommandBox CLI and used immediately in your templates, widgets and views. For example, to install a Twitter feed widget for ContentBox, simply run box install ID=cbwidget-tweetfeed directory=widgets
from your theme directory and it will automatically be available for use in the admin. You may browse the ever-growing list of Forgebox libraries and modules on the Forgebox site or run forgebox help
from within CommandBox to begin exploring the available options.
templates (The folder that contains optional templates for collection rendering that are used using the quick rendering methods in the CB Helper. See below for additional information)
category.cfm (The template used to display an iteration of entry categories using coldbox collection rendering)
comment.cfm (The template used to display an iteration of entry or page comments using coldbox collection rendering)
entry.cfm (The template used to display an iteration of entries in the home page using coldbox collection rendering)
The individual files included in the templates
directory are a single .cfm
files used by ContentBox to iterate over a collection (usually entries or categories or comments) and render out all of them in uniformity.
Please refer to ColdBox Collection Rendering for more information.
Each template receives the following variables:
_counter
(A variable created for you that tells you in which record we are currently looping on)
_items
(A variable created for you that tells you how many records exist in the collection)
{templateName}
The name of the object you will use to display: entry, comment, category
Layout Local CallBack Functions:
onActivation()
onDelete()
onDeactivation()
UDF ( User Defined Functions ) are a great way to add more power to your theme, and your theme settings.
Below are some common UDFs you might want to use in your themes, or templates on how you could create some of your own.
This is a useful method for standardizing loads HTML for the fieldHelp Modal. The fieldHelp setting using this function would look like this
loadHelpFile( 'cbBootswatchTheme.html' );
Since the helpFilePath
is not passed in this example, it defaults to the Theme's includes/help/
folder.
Note: One concern when using this is relative pathing of assets, since the modal is generated in one folder, and the includes are in another folder.
This function gives you an array of Media Manager folders, so you could use in a optionsUDF setting, to allow the Theme Settings form to display a dropdown box with a list of folders. This would be great for a slide show, where the users select the folder, and all of the contents of the folder could be shown in the slideshow.
Bootstrap has a set of pre-defined styles, used in alerts, buttons, and many other styles you can use in your website. Having a UDF like this, allows you to create a setting that the User can select the button style type.
We use BootSwatch in several Themes, because it gives the user of the theme a lot of complete styles, in one theme. This UDF shows you how you can return a list as an array, so a user could select the BootSwatch they would like to use.
You can declare settings for your Themes that ContentBox will manage for you. The form itself is built from the Theme.cfc file itself.
The value is an array of structures with the following keys:
name
: The name of the setting (required), the setting is saved as cb_layoutname_settingName
defaultValue
: The default value of the setting (required)
required
: Whether the setting is required or not. Defaults to false
type
: The type of the HTMl control (text=default, textarea, boolean, select, color)
label
: The HTML label of the control (defaults to name)
title
: The HTML title of the control (defaults to empty string)
options
: The select box options. Can be a list or array of values or an array of name-value pair structures
optionsUDF
: The select box options. This points to a UDF that returns a list or array of values or an array of name-value pair structures. Example: getColors not getColors()
group
: lets you group inputs under a Group name - settings should be in order for groupings to work as expected
groupIntro
: Lets you add a description for a group of fields
fieldDescription
: Lets you add a description for an individual field
fieldHelp
: Lets you add a chunk of HTML for a Modal, openable by the User by clicking on question mark next to the field label. Recommended use is to readFiles from the ./includes/help
directory, with a helper function, for example: loadHelpFile( 'cbBootswatchTheme.html' );
Below is a annotated screenshot showing most of the visible elements generated in the Theme Settings Admin form from the Theme Settings configuration structure.
ContentBox for some time has had a ContentBox site title, keywords and descriptions. Most themes also overrode those values with Blog Post and Page Titles, and if you use the SEO tab in the editor, you can override the Keywords and descriptions too.
In ContentBox 3.1, we added some additional logic into the default Theme ( as a reference ) to allow even more flexibility with Title, Keywords, Meta etc. In previous versions, logic checked to see if the content was a blog, or a page, and how to determine if overrides were used or not. Now the CBHelper contains all of that logic, and you can just request the Title, Description or Keywords from CBHelper.
This is in addition to already existing functions like:
<base href="#cb.siteBaseURL()#" />
One common issue with using the theme for your module is the way Meta Tags are generated. Having your own module, its hard to set your Title, Keywords and Description. As of ContentBox 3.1, since we have added the SEO and Meta functions to CBHelper, you can actually set and retrieve that data easily. To add Meta to your modules, you can now add this to your Module Handler Events.
views (The folder that contains views for rendering)
archives.cfm (MANDATORY: The view used to render out blog archives.)
entry.cfm (MANDATORY: The view used to render out a single blog entry with comments, etc.)
error.cfm (MANDATORY: The view used to display errors when they ocurr in your blog or pages)
index.cfm (MANDATORY: The view used to render out the home page where all blog entries are rendered)
notfound.cfm (The view used to display messages to users when a blog entry requested was not found in our system.)
page.cfm (MANDATORY: The view used to render out individual pages.)
maintenance.cfm (OPTIONAL: Used when in maintenance mode)
layouts (The folder that contains layouts in your theme)
blog.cfm (Mandatory layout used for all blog views by convention)
pages.cfm (Mandatory layout used for all pages by convention)
maintenance.cfm (Optional used when in maintenance mode, else defaults to pages)
search.cfm (Optional used when doing searches, else defaults to pages)
By default the includes
directory of your theme is used to store your static assets.
Because these assets are called from your customized theme, however, you may use any directory conventions you prefer. If you plan to use customized paths or CDN links in your template, it's best to create a setting in your Theme.cfc
which will then be configured through the admin.
In the Front End of the Website, the Theme takes over the rendering. To ensure all Assets are added correctly from various modules, for every theme, we recommend using the htmlHelper's addAsset()
method.
Since the SuperType ( which all ColdBox items inherit from ) implements addAsset() you can call it directly from almost any file in your application.
addAsset( "#prc.cbroot#/includes/css/#css#.css" );
You might have noticed, that even in the Admin Asset Management methods explained here, the actual implementation of the cssFullAppendList and cssAppendList use the addAsset to add the CSS to the head of the HTML page.
This applies to both CSS and JS. The main downside to this approach is that all assets added with addAsset are added to the head. The order of the assets is not strictly ( or easily ) controlled either.
We recommend using a similar approach to the admin, using an array, and outputting in the theme. We will be standardizing an approach soon, so all themes can easy implement these features, with a single line cbhelper
method.
A theme is composed of the following pieces
ThemeDirectory
Theme.cfc (The CFC that models and configures your theme implementation)
layouts (The folder that contains layouts in your theme)
blog.cfm (Mandatory layout used for all blog views by convention)
pages.cfm (Mandatory layout used for all pages by convention)
maintenance.cfm (Optional used when in maintenance mode, else defaults to pages)
search.cfm (Optional used when doing searches, else defaults to pages)
views (The folder that contains views for rendering)
archives.cfm (MANDATORY: The view used to render out blog archives.)
entry.cfm (MANDATORY: The view used to render out a single blog entry with comments, etc.)
error.cfm (MANDATORY: The view used to display errors when they ocurr in your blog or pages)
index.cfm (MANDATORY: The view used to render out the home page where all blog entries are rendered)
notfound.cfm (The view used to display messages to users when a blog entry requested was not found in our system.)
page.cfm (MANDATORY: The view used to render out individual pages.)
maintenance.cfm (OPTIONAL: Used when in maintenance mode)
templates (The folder that contains optional templates for collection rendering that are used using the quick rendering methods in the CB Helper. See below for additional information)
category.cfm (The template used to display an iteration of entry categories using coldbox collection rendering)
comment.cfm (The template used to display an iteration of entry or page comments using coldbox collection rendering)
entry.cfm (The template used to display an iteration of entries in the home page using coldbox collection rendering)
widgets (A folder that can contain layout specific widgets which override core ContentBox widgets)
ContentBox's modular architecture, combined with the awesome power of the MVC development framework, allows for a familiar, yet unrestricted, platform from which to develop great web applications.
If you're a Front End Developer, you'll appreciate the straightforward configuration and pre-defined ( but highly customizable! ) asset conventions.
Within the framework, itself ContentBox is comprised of three modules:
contentbox
- Includes the the underlying libraries for content organization and presentation
contentbox-admin
- Provides the administrative interface for managing your CMS
contentbox-ui
- Provides shared libraries for the user interface which are used for presentation and which may be extended to your application requirements.
Browse through our area-specific development documentation to get started:
ContentBox allows you to override global site settings dynamically. This approach is great for container based deployments, but also for traditional overrides for testing or staging environments.
You can override any runtime setting for ContentBox via a configuration structure in your main ColdBox.cfc
configuration file. This will allow developers to override any runtime setting for any site.
All you need to do is create the a contentbox
structure in your configure()
or any tier method, with the name of the site (default
is the default site) and then any setting name-value pair.
You can also override any runtime ContentBox setting by passing them via Docker/Java Runtime variables.
Since these are string keys we can now use our fancy structures like the settings above, so you must adhere to our recognition pattern:
Here is an example on adding a custom media root:
This will allow especially Docker environments to override settings as environments or even provide secrets. More is coming, so stay tuned to our updates.
During the ContentBox request, common variables and super-type methods are made available to all templates and views used in that request. When developing themes and modules for ContentBox, the following methods and variables are available for use in customization:
#cb#
The cb
object contains a variety of common methods for retrieving ContentBox content and settings. Below are some common methods used to retrieve and create HTML content:
cb.siteName()
: retrieves the configured name of Contentbox installation
cb.siteTagline()
: retrieves the configured tagline for the ContentBox installation
cb.siteDescription()
: retrieves the configured description for the Contentbox installation
cb.siteKeywords()
: retrieves the configured global keywords
cb.siteEmail()
: retrieves the default site email
cb.siteOutgoingEmail()
: retreives the configured email used in outbound deliveries
cb.quickView( viewTemplate )
: Renders a view partial located in the [theme home]/views
directory. A .cfm
file extension is not required.
cb.mainView( args )
: Renders the main view for the handler action. A struct containing the args necessary for view rendering should be passed in. By default, this struct is available in your layout and is also named args
.
cb.getCurrentEntries()
:
cb.getCurrentEntriesCount()
:
cb.getCurrentCategories()
:
cb.getCurrentPage()
:
cb.getCurrentComments()
: Returns the published comments for the current entry
cb.getCurrentCommentsCount()
: Returns the count of published comments for the current entry
cb.getCurrentRelatedContent()
: Returns an array of content related to the active post or entry
cb.getCurrentCustomFields()
: Returns a structure of custom fields for the active post or entry
cb.getCustomField( string fieldName, any defaultValue )
: Returns the value of a custom field for the active post or entry
cb.widget( string widgetName, struct args )
: executes a named widget's renderit
method and returns the HTML. A structure of arguments may be passed.
cb.getWidget( string widgetName )
: retreives the associated [widget][2] object
cb.themeSetting( string settingName, any defaultValue )
: retrieves a theme setting by name. A default value may be specified
cb.isCommentsEnabled()
: returns a true|false value of whether site comments are enabled and if the current entry accepts comments
cb.quickSearchForm()
: returns the HTML of a standard ContentBox Search Form according to the SearchForm widget
cb.getSearchResults()
: returns an array of results for an active search
cb.linkAdmin()
: creates a link to the ContentBox admin
cb.linkAdminLogin()
: creates a link to the ContentBox administration login form
cb.linkAdminLogout()
: creates a link to logout of ContentBox administration
cb.linkBlog()
: creates a link to the site's blog
cb.linksSelf()
: creates a link to the current page
cb.linkPageRSS( any categoryFilter)
: creates a link to the RSS feed applicable to the active request
cb.linkSiteRSS( any categoryFilter )
: creates a link to the site's RSS feed
cb.linkCategory( string categorySlug )
: creates a link to a specific category page
cb.quickCategoryLinks( string categorySlug )
: creates an HTML unordered list of category links
cb.themeRoot()
: returns the location of your currently defined theme in the application, great for assets, cfincludes, etc
cb.siteRoot()
: returns the site root location using your configured module's entry point
cb.siteBaseURL()
: returns the site's SES base URL
cb.adminRoot()
: returns the root location of the admin using your configured module's entry point
widgetRoot()
: returns the location of your widgets, great for assets and includes
A number of built-in interception points are avaialble within the ContentBox request, which may be run using the cb.event()
method. For examples of how these are used, see the default theme layout files. Examples include:
cb.event( "cbui_beforeHeadEnd" )
cb.event( "cbui_afterBodyStart" )
cb.event( "cbui_afterContent" )
cb.event( "cbui_beforeBodyEnd" )
cb.themeName()
: returns the currently active theme name
cb.layoutName()
: returns the currently active layout name
cb.isPrintFormat()
: returns true if you are in printing or exporting format
Easily Render Captcha Images - ContentBox supports native captcha support and now your themes can render out a nice captcha image by using the new ContentBox helper method:
cb.renderCaptcha()
method.
[2]: /content/using/managers/widgets.html
Modules are self contained bundles of code, that contain their own configuration, routes, handlers, views, widgets, interceptors, and can contain other modules. They are a vital part of ContentBox, and the ease in which you can use, and develop for ContentBox.
ContentBox itself is made up of 3 separate Modules ( and their submodules ), ContentBox, ContentBox-Admin and ContentBox-UI. One of the best security features of ContentBox is the fact that you can remove the Admin module from your production installs, removing the ability for the admin to get hacked, because it is not even present on your production server.
To view a Module, with routing and ColdBox modules entry points, you can simply hit a url/route on your site, and the module is ready to go.
For example:
That is all you have to do. You don't need to build a special widget to display the module, the module can be accessed by the entry point in the module configuration, and its as easy as that.
Using contentbox's Module Manager can help you install modules from Forgebox, Commandbox can help you install modules from Forgebox into wherever you need them, and of course, get your hands dirty and write some code too. You can either build one, copy the source manually, install with CommandBox, install via ContentBox itself.
Depending on the type of module, there are different methods and locations to install the module into.
/modules
- ColdBox Modules - git ignored, controlled by CommandBox
/modules_app
- ColdBox App Modules - Your application global modules - included in your git repo
/modules/contentbox/modules
- ContentBox Always Load Modules
/modules/contentbox/modules_user
- ContentBox Admin Managed Modules
You can manage ContentBox Modules through the ContentBox Administration. Click on the ForgeBox tab, you can search, locate and install modules directly.
If you are not using CommandBox yet, you should be. It is not only great for easily spinning up CFML Servers ( multiple server types ), but CommandBox's true strength is using it to manage your projects and packages, similar to 'npm' for node.
At Ortus Solutions, we use CommandBox to manage all of our CFML Projects. We track all of our dependencies, we do not commit any CommandBox module to our Git repos, CommandBox will install dependencies from the CLI during our build process.
Let's walk through installing a ColdBox module, to scan our views for Business Logic, there is a module for that. If you look on Forgebox, you'll find Business Logic Scanner by Brad Wood.
To install, we cd into the site root, and type
box install Business-Logic-Scanner
Once CommandBox is done, you can log into ContentBox, and click 'Modules > Manage' and activate the module ( or reinit the app ).
Now, you have quickly installed a module, and activated it... time to use the module.
Absolutely, you can just copy a module into your modules folder, and sometimes, your modules will not be on Forgebox... a perfect example, Commercial Products, like DataBoss.
For a customer recently, who has a large custom database, we decided we wanted to use Databoss to help our customer view, add and edit data. DataBoss is a commercial product of Ortus Solutions (description below) and a complete Application in a Module. You can download a trial of Databoss, and drop into your Modules folder, activate it, and now you have a full DataBoss application running on your /databoss route/url.
cb.contentStore( string slug )
: retrieves the HTML output of a item by its slug
cb.contentStoreObject( string slug )
: retrieves the associated object
Modules are one of the key components of everything ContentBox. One frequently asked question is: how do we theme our custom module output? Theming your custom module, so it uses the same theme/layout as the rest of your ContentBox site is vital if you are going to be extending your site with your own modules. For consistency and flexibility, you do not want your module to be tightly coupled to the current theme, making it harder to change themes. We have a helper to help you with that.
To use a theme, requires a lot of different pieces behind the scenes. Usually in the process of displaying a ContentBox blog post, or page, the UI Module that handles the process, prepares the UI Request, setting up the settings, themes etc. This has been made available to you through the ContentBox CB Helper class. The first step is to get an injection or request the ContentBox helper object via its injection DSL: CBHelper@cb
. You most likely will do this in your custom module handlers:
The helper sports the prepareUIRequest() method which you will use to tell ColdBox to leverage the ContentBox module theme instead of a traditional ColdBox layout. Here is the method signature:
This method accepts one argument called layout which by default is the pages layout from the chosen theme. If the ContentBox selected theme has more than 1 rendering layout, then you can even chose it here.
The string for the name of the layout allows you to pick and choose the layout inside the theme. Pages is a common one, but you could have specifically designed layouts for different parts of your modules, or prefer the blog layout for example. Here is a link to the documentation on PrepareUIRequest: http://apidocs.ortussolutions.com/contentbox/3.0.0-rc/contentbox/models/system/CBHelper.html#prepareUIRequest()
We tried hard to make this as simple as possible, so you can drop a module in and have it using the theme in seconds. Try it out and go build cool stuff!
If you are planning to build a module, a great way to get started is using Scaffolding a Module using CommandBox. CommandBox has a lot of commands, the one we will be using in this case is the ColdBox command. The ColdBox command inside of CommandBox allows you to do a lot of things, including coldbox create for all of these items:
view
app-wizard
orm-virtual-service
unit
integration-test
interceptor-test
orm-entity
orm-service
app
interceptor
orm-event-handler
model
layout
handler
bdd
module
orm-crud
model-test
controller
We are of course interested in creating a module. Lets look at that command
coldbox create module
The params for this command are the following:
There is also a flag for script: --script
Lets create a module with the command, and see what it produces. Note: Be in the root of your app so the command knows where to put the files. If you are in a subfolder, the command might get lost.
Looking at the arguments in more detail
name=customModule2
author="Gavin Pickin"
authorURL="http://www.gpickin.com"
description="Custom Module 2"
version="1.2.3"
cfmapping="customModule2Mapping"
modelNamespace="customModuleName"
directory="modules_app"
script=true
The command outputs this:
Here is a visual display of the directory structure
The command creates a Module Config file with all your information. It creates a default handler, called Home.cfc
in the handlers folder. It creates a models folder, with just a placeholder file. It creates a views folder, with the home
folder to match the handler, and creates a default index.cfm
view.
It is a great way to get started. You can use many of the other ColdBox commands to create additional handlers, views, layouts. Next time you want to create a module, instead of remembering the conventions, and what files you need, scaffold it with CommandBox's ColdBox create command.
ColdBox modules all work with ContentBox, so what will you create?
Depending on the type of module, there are different methods and locations to install the module into.
/modules
- ColdBox Modules - git ignored, controlled by CommandBox
/modules_app
- ColdBox App Modules - Your application global modules - included in your git repo
/modules/contentbox/modules
- ContentBox Always Load Modules
/modules/contentbox/modules_user
- ContentBox Admin Managed Modules
There are 4 locations because each location has a different set of conventions, and different behaviors. Depending on what your module is, and what it will do, helps you determine where you should install / create it. The other reason the location matters, is because of source control. If your module interacts with ContentBox's life cycle, ie adding admin menu items when the module is loaded, the module is considered a ContentBox module, and should be installed in one of the last 2 locations.
Applications can have many dependencies, and keeping them out of your repo, and managing them with package managers, like Node, Bower, CommandBox, can keep your apps clean and light. Your gitignore file can get out of hand if you are individually adding and removing modules. To make life easier, we have 2 ColdBox App locations, 1 is /modules, which is controlled by CommandBox, and should be set to ignored. This means you can install hundreds of ColdBox modules, and do not have to worry about them filling up your repo. Note:Since these files in the /modules folder are not in source control, editing these files is pointless. Your changes will never get committed, and therefore deploy to any other developers, or staging / production servers. If a module requires you to edit files inside the module to work, you should install the module to the ColdBox Apps Modules location ( below ).
This location is not ignored by git, it is a part of your source control repo. These modules can be created by you, or manually installed, or installed by CommandBox by using an alternative location ( read below for when you should do this ). There modules behave just like modules installed into the modules folder, the main difference is source control. Do you want the module to be embedded in your repo, or linked, using commandbox to resolve the module as a dependency.
When installing modules with CommandBox, they are usually installed to the /modules folder, but sometimes you should install modules to the modules_app folder. The main reason, if the module requires configuration changes to the files, those files need to be in source control. So the best location for that module, is inside of the modules_app folder. Not all modules need file changes to get the module working, so install those normally, but if a module requires those types of changes, a simple change to the install command will change the installation path. ? 1
box install Business-Logic-Scanner modules_app
The last parameter is the installation directory. This will install the module to that directory. Note: If you are creating a module, and you know someone will need to modify the module to get it to work, you can set the directory in your modules box.json so when commandbox installs your module, it knows it should be in that folder by default.
This one is pretty self explanatory, they are ContentBox modules that always load. This location is stored for core ContentBox submodules, that are not controllable by the admin. My advice is, do not develop modules in here. Since these modules always load, trying to use a normal module in here, can have loading order issues. For example, if you design a module to add menu items to the admin, this module will have issues loading the adminMenuService@cb since the contentbox-admin module hasn't been loaded when these modules are being loaded. Its best to put those modules in the next location - ContentBox Admin Managed Modules
This is where you should create modules that require the contentbox lifecycle to function correctly. A perfect example of the contentbox lifecycle, is after the core admin module has been initialized, it then loads these modules. The lifecycle fires events, that your modules can listen to, for example, onLoad and onUnload which are perfect for adding and removing menu items from the main menu item for your customer admin modules. If you module was not in this folder, your modules would not be able to intercept these events, making simple tasks, harder to do.
Another great aspect of having ContentBox managed modules, is that logged in users can activate, deactivate, or reload modules. This is the best way to extend contentbox, you can tie the module into all the internal events of Contentbox. In a future post, we'll be showing you show to make your own Admin Module where you can add menu items like the screenshot below... the last main menu item is Mapigator, which has submenu items, added dynamically by using modules in this folder.
Whether you have used ColdBox before or not, using Modules with ContentBox and ColdBox is fairly straightforward, in fact, I think its a great way to dip your toes into using both technologies. Working with Modules is like everything else in ColdBox, you work with conventions, but you have control.
As stated in our Module Conventions page, we showed you how there are 4 locations for modules in ContentBox. Depending on how your module will work, you should choose the appropriate location. In this example, we're going to build a custom ColdBox module, which it not managed by ContentBox Admin's Module Manager.
We'll be working in modules_app
folder. These modules we create are for developers eyes, and will not show up in the 'Manage Modules' administration menu. These are modules that you wouldn't want someone accidentally turning on / off, for example, a key part of the website.
When you install a module from the 'module manager' in the admin, ContentBox installs those modules into another location.
There are a few ways to build a module, including CommandBox scaffholding, where you are asked a few questions, and then it generates a module, with all of the folders you need and some you might want.
In this example, we will start simple, and add more files as we build. First, we need to know a few things about the module to get started... your folder name, and then some details to add into your Module Config file.
Folder Name for the Module
This is the folder you put the module in. It does not have to match anything else with the module, so you can use something meaningful... you can set the entry point / link / route manually in the Module Config file.
Entry point - What link / route would you hit to pull up this module?
In my example, I want to be able to go to /customModule and be able to pull up this module. I have created modules for /profile and /pay and /help in the past.
Namespace for the Module
This is a namespace for any models initialized in this module. Say you have a UserService.cfc in your module, you would ask for this service from wirebox with userService@customModule if that is the namespace you would like to use. I usually make the entry point and namespace and the folder all match, unless the entry point should be shorter or different, for whatever reasons.
CFMapping for the Module
This is an automated mapping for the module. I usually have this match the folder, and namespace.
Version
Start at 1.0.0, you can bump it up as you go, but this is usually not vital unless you're planning to publish on Forgebox or something. We can look at that in a future post.
For my example, lets keep it super simple, and call the app customModule, and we'll keep everything the same, folder, entrypoint, namespace, mapping.
Create Folder
Lets create a folder /modules_app/customModule/
Create ModuleConfig.cfc
Next, lets create a file called ModuleConfig.cfc - this is the config file that ColdBox loads to setup the module, and get it ready to use. It can setup a lot of things, including the mapping, entry point, and a lot more. We'll start simple, here is a gist for my bare bones moduleConfig file.
https://gist.github.com/gpickin/f7b0a53353230d326ef31d1d3d4996ef
Once you have your folder, and your moduleConfig file, you can reinit your app, and then try hitting your entrypoint.
Error - so lets see what we need next to be able to get this Module Working.
If you read the error message, you'll see that the event customModule:home.index is not a valid registered event.
What does this mean? This means the entry point worked, and found your module, but its looking for an event to run. If you look in your routes in your config, you'll see some basic routes setup for you.
These routes are children routes of the entrypoint.
So /customModule/
is added to the apps main routing table, by setting the entry point above. These children routes build on top of that.
So if someone hits the child route / then its the same as /customModule/
- and if that pattern is matched, that route says we want to use the 'Home" handler, and the "index" action. That is why you see the error looking for that, its because this is the default event for this module. You will also see there is another route pattern below, /:handler/:action
- this includes placeholders. So this will match anything with 2 or more values, and then it will use the first value as the handler name, and the second piece as the action.
/customModule/profile/view/ would be the customModule with the profile handler and the view action.
/customModule/product/add/ would be the customModule with the product handler and the add action.
We could change our default to be something else, but for now, lets keep it simple. Home Handler and Index action.
This is where ColdBox conventions really shine. You might think you have to create a Handler folder, and create the handler named Home.cfc and then put a function inside of it called index... which is correct but you don't HAVE to. ColdBox's conventions requires less than that to create an event. In fact, the lowest point of entry for creating an event, is just to create a view.
Lets make a folder called views ( the convention for where your views live ) /modules_app/customModule/views/
To create the view for home.index, we need to create an index.cfm. Conventions tell us, to make a folder inside views to match the handler, and then place the action's view in that folder. So lets create /modules_app/customModule/views/home/index.cfm
We'll put in an <h1>My Custom Module</h1> and see what happens when we hit the entry point now.
Amazing, just 2 files, and we have a working module. You could paste in your legacy spaghetti code right in here, and it would work. Of course, we probably want to dress it up a little more, and add some more functionality, so lets look at the layout first. If we view the source, you'll see there is more than just the h1 tag we added.
ContentBox makes it easy to create your own Admin Modules, add Menu Items into the Admin Interface, use ContentBox Admin Users and Permissions instead of building your own security by extending ContentBox.
At it's core, ContentBox is an Application running on top of ColdBox as a framework, but with all of the great features ContentBox has, ORM, Pages, Blog Posts, Categories, Comments, Subscriptions, Admin User Interface, Permissions, Modules, and much more, you can treat ContentBox like an Application Framework, and just extend it, with Modules. With almost every real Application, you will need to add front end modules, and then admin modules.
When you are building admin modules, you need to build / install those modules into the modules/contentbox/modules_user
folder or modules/contentbox/modules
folder. Read Module Conventions to know when to use what folder... depending if the module will be custom, vs a shared forgebox module, you should put them in different locations.
One big benefits with ContentBox, is how easy it is to use the base admin, and extend it to make it your own admin. Why build a normal website the hard way, when ContentBox already has all the login, password reset, security roles and permissions build for you. Can you simply use the admin, and add more buttons for your admin modules.
This page walks through building a simple module in the ContentBox admin. We're going to make a custom module, that will only live with this application, and will not be shared with forgebox, so we want this to be included in our source code. So we will build this module in this folder:
/modules/contentbox/modules_user/
The full path of our module, which will be called mySecrets would be
/modules/contentbox/modules_user/mySecrets
Now, if we go to the Module Manager in the backend, and rescan for modules, you will see it doesn't show up yet.
First, we need to create a module config. This is similar to a normal ColdBox module, with a few extra splashes of flavor for ContentBox. Since it is a CFC, we can use the pseudo contructor to set the module properties. For mySecrets, it looks something like this.
The only required function for the ModuleConfig.cfc is the configure() function. This is where you can set ( or inherit ) settings, parent settings, layout settings, datasources, webservices, routes, interceptorSettings, custom Interceptors and Interception Points, model bindings ( mappings ). A shell of your configure method might look like this.
This is the minimum you need for the module to show up in the Manage Modules screen. If we rescan now, you'll see it detects our Module.
When you click the Thumb Up icon to activate, it activates the module.
Since these modules are inside of ContentBox, Contentbox manages loading and unloading the module, as well as activating and deactivating the modules, so you need those functions to listen for those events. ModuleConfig.cfc is one big interceptor, so you can catch all sorts of interception points, but these are the four needed for a ContentBox Module to function to its fullest.
This is a bare bones template for a module controlled by ContentBox, but right now it doesn't really do anything... in fact if you tried to hit the module, the default route for / would look for home handler with index action/view but we haven't created those yet. None of the functions above do anything, so all the activation and deactivation does, is make the module available or not. If you try and access /mysecrets ( the entry point ) with the module deactivated you see this
When the module is activated, and you hit the entry point, you will get the error that the event is not a valid registered event
Here is the gist of step 1. ModuleConfig.CFC for Step 1 - https://gist.github.com/gpickin/545242a3da6c2805efb4e6cac82ce1ad
As you can see, we have our own routes setup for the module in the ModuleConfig.cfc. The default action is handler=home and action=index. We'll need to create this event, or update our module route to point to the new one. Let's keep it simple, and we'll make a home.index event.
ColdBox and therefore ContentBox calls the views folder 'views'. Since our action is home.index, we need a folder inside of 'views' called home, and an index.cfm file inside of that.
Inside the index.cfm, just put some simple html, like <h1>my Secrets</h1>
Lets reload our application with /?fwreinit=1
or Reload Application from the admin cog dropdown menu. Now lets try hitting out entrypoint /mysecrets
and you'll see something like this.
Wait, why did that work? ColdBox uses conventions to find the view, even without the handler. For legacy conversion or simple views, you don't even need the handler. Wait, I wasn't logged into the admin, how did that not ask me to login?
If you look at the url, i was using /mysecrets
which is actually the front end url... so that worked.
What is the admin entrypoint for this module?: /cbadmin/modules/mysecrets/
Now what happens when we hit that url
It bounces me to the login page, like it should. Ok, so when I login, then what happens?
In the admin, its throwing an error, telling me the handler doesn't exist. Strange that it worked in the front end right? The admin has a little more security, so it requires a handler. One of the pitfalls of not using handlers is that the ColdBox Lifecycle special actions like pre and post handlers etc cannot run, because it bypasses a lot of the lifecycle and just spits out the view. Keep that in mind when you are having issues with anything out of the ordinary and you don't have a handler, or have a handler without a handler event for the event you're executing.
We need to follow conventions ( unless you want to complicate things and override the conventions ) and make a folder called 'handlers' for our handler. We can just add the Home.cfc directly into the 'handlers' folder, and now your folder tree structure will look like this
We'll keep our handler very simple, just a shell, so we'll add this to our Home.cfc
Now we have added the handler, lets reinit the application and see what it looks like now.
Another note: If you hit http://127.0.0.1:61805/cbadmin/module/mysecrets/ you will get a strange handler error... because it thinks the module isn't a module, but its an action of a parent module... so make sure you use the full url, like below.
http://127.0.0.1:61805/cbadmin/module/mysecrets/home/index
Now, you will see the following
Now our module is working, in the admin, we need to add some menu buttons so users can access your module.
One common issue with using the theme for your module is the way Meta Tags are generated. Having your own module, its hard to set your Title, Keywords and Description.
In ContentBox 3.1, coming out very soon, you are able to do this inside your Module Handler Events
To make Asset management easier in the Admin, we have provided a few arrays, that you can append your assets to. We have JS and CSS arrays, as well as convention based, and full path arrays.
We have two options for working with Javascript in the admin.
jsAppendList
This expects the name of the js file ( without the .js extension ) which is located in the ContentBox includes/js folder. This is a simple convention based approach, so you can easily just append a file, for example arrayAppend( jsAppendList, 'dragula' )
to load /includes/js/dragula.js
jsFullAppendList
This expects the full path of the js file ( including the .js extension ), including the location. This is useful, because you may be using grunt or gulp to compile files, store them in different locations, or actually link out to a CDN for some of your scripts.
For example:
arrayAppend( jsFullAppendList, '/includes/js/dragula.js' );
arrayAppend( jsFullAppendList, 'https://code.jquery.com/jquery-3.1.1.min.js' );
Inside of /modules/contentbox-admin/layouts/inc/HTMLBodyEnd.cfm
you will see the following code.
We have two options for working with CSS in the admin.
cssAppendList
This expects the name of the CSS file ( without the .css extension ) which is located in the ContentBox includes/css folder. This is a simple convention based approach, so you can easily just append a file, for example arrayAppend( cssAppendList, 'dragula' )
to load /includes/css/dragula.css
cssFullAppendList
This expects the full path of the css file ( including the .css extension ), including the location. This is useful, because you may be using grunt or gulp to compile files, store them in different locations, or actually link out to a CDN for some of your scripts.
For example:
arrayAppend( cssFullAppendList, '/includes/css/dragula.css' );
arrayAppend( cssFullAppendList, 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' );
Inside of /modules/contentbox-admin/layouts/inc/HTMLHead.cfm
you will see the following code.
Whether you are in a blog post, page, or a front end module ( anything using the Theme ), ContentBox makes it easy to work with the currently logged in User. In ContentBox, a user is called an Author. You can use these terms interchangeably.
During the PrepareUIRequest, that runs whenever we setup the ContentBox core, Theme etc, ContentBox gets the currently logged in user and stores it in the PRC
( Private Request Collection )`
To access the user, you can use this variable
prc.oCurrentAuthor
You can dump this variable out, but I would limit the number of rows deep you go, as this object has objects which has objects, and can give you a heap space error.
<cfdump var="#prc.oCurrentAuthor#" top=3>
or writeDump( var=prc.oCurrentAuthor, top=3 );
Author First Name prc.oCurrentAuthor.getFirstName()
Author LastName prc.oCurrentAuthor.getLastName()
Author Email prc.oCurrentAuthor.getEmail()
Author Username prc.oCurrentAuthor.getUserame()
Author Created Date prc.oCurrentAuthor.getCreatedDate()
Author Modified Date prc.oCurrentAuthor.getModifiedDate()
Author Last Login Date prc.oCurrentAuthor.getLastLogin()
Author Biography prc.oCurrentAuthor.getFirstName()
Author Preferences prc.oCurrentAuthor.getFirstName()
Author Role prc.oCurrentAuthor.getRole().getName()
prc.oCurrentAuthor.entries()
returns an Array of entries / blog posts
prc.oCurrentAuthor.getNumberOfEntries()
returns a count of entries / blog posts
prc.oCurrentAuthor.pages()
returns an Array of pages
prc.oCurrentAuthor.getNumberOfEntries()
returns a count of entries / blog posts
prc.oCurrentAuthor.getPermissions()
returns an Array of A La Carte Permissions
prc.oCurrentAuthor.getPermissionsList()
returns an string containing a list of A La Carte Permissions
To add Menu Items for your Admin Modules into the Admin Interface, you need to update the onLoad
and onUnload
functions of your ModuleConfig.cfc
file.
In this example, you'll start by adding the Menu Item by updating onLoad
:
Next, you'll update the onUnload
function to remove the Menu Item when deactivating our Admin Module:
Now reload the app (?fwreinit=1
). If you activate the mySecrets
Admin Module, you should see mySecrets
appear in the Modules
sub menu items.
Now that your module menu button is working in the admin, you need to add some metadata to your module.
Widgets can range from very simple to very complex, depending on what your goal with the widget is.
Let's build a simple widget, that takes no parameters or arguments from the user, it simply outputs a ContentBox Logo, wrapped in a link to the ContentBoxCMS webpage.
There are several things a Widget needs to work with ContentBox. We'll cover all of these items with a simple example. In other sections we'll look at more complex options you have as a Widget builder.
File in the right Location
CFC Definition and Extension of the BaseWidget
Widget Properties
RenderIt() Function
Remember: Widgets are loaded into memory when the Application is started. To make changes, please reinit your app, or Reload the App from the admin menu to see those changes.
First, you need to create a CFC, in a Widget loading location. In this example we're going to create a ContentBox Logo and Link. So we'll call it ContentBoxBadge.cfc
There are several locations for loading widgets, the main ContentBox core location is:
modules/contentbox/widgets
For now, that is a good location.
See the init line includes the name of the CFC. ContentBoxBadge function init()
If you CFC was called GoogleLink your init line would be: GoogleLink function init()
Now when we look in the Widget list, and find our widget... this is what we'll see.
For ContentBox to know more about your widget, you should add some Properties to your widget. This is done in the Init()
and can include the following.
Name: The Name of the Widget.
Version: A meaningful version number
Description: A short description of the widget and its use
Author: Author of the Widget
AuthorURL: The URL of the Author of the widget
Icon: This is the icon used by ContentBox. Icons are Font Awesome Icons. Refer to Font Awesome Website for a full list.
Category: This is the grouping the widget will be placed in. This is completely customizable, but the default groups are:
Blog
ColdBox
Content
Layout
Miscellaneous
Utilities
Now when we load Reload the app, and look at the widget list, you will see the details are filled out.
When you build a Widget, they are built to render something out to the user. To use a Widget, you must have a renderIt() function... and it must return something to the RenderData function that calls it.
At it's simplest, you can add this function under your Init()
Once we reload the app, we can test our Widget from the Widget Manager.
Lets build a logo and link in a string, and return that.
Now we reload, and then test it again
Lets add our widget into a page. Lets browse in the admin to Content > Sitemap
and click on a page. Find the location in the text you would like to add your widget, and click the Green ContentBox Widget icon ( circled below ).
Pick the Widget out of the list, you can filter the widgets, or select by category ( as we defined in our Widget properties )
Click anywhere on the Widget itself, and the Insert Widget Dialog will open like the screen below.
If there were arguments, you could adjust them here. If this is the widget you want, and the preview looks good, click Insert Widget
. Back to Widgets
allows you to return to the Widget list to look for a different widget. Cancel
returns you to the Content Editors.
Once inserted, click Publish, and then you'll see a Widget placeholder like this.
If you right click on the widget, you can get a Widget Context menu like below. You can edit, or remove a Widget through that context menu, or just double click the Widget placeholder to edit directly.
This example has no arguments or parameters to change, but if you did, you would be able to edit those here, and click Update Widget
to save those changes.
You can preview the page using our Responsive Previewer which allows you to see what your page will look like, in desktop, tablet and phone views ( horizontal and vertical ).
When you are happy, ensure you save / publish your page to keep your changes. Once saved, you can view it on the front end of the website.
Widgets are required to provide one function called renderIt()
, but they can actually provide more than one render option. If you add another function, then the display of the Widget Form changes.
Now you will see a Public Methods
menu, with a list of public functions in ths CFC. Below you can see the required function renderIt()
and the 2 additional functions. listOfPages()
and listOfCategories()
When you select a different Method, the options change, depending on the function. This means you could have 1 widget, with several different display options... each with their own set of arguments. Packaging them inside a single Widget CFC allows them to easily share functions.
Above, you can see the renderIt()
function has 1 arugment, with label, hint, required etc.
The
listOfPages()
function has 1 argument, a plain text field
numberOfPages
, as you can see below.
The
listOfCategories()
function has 1 argument, a plain text field
numberOfCategories
, as you can see below.
If you add a UDF to your Widget, to be able to dynamically create the select drop down for your Widget arguments, you might notice an unexpected side effect. This function / method shows up in the Select a Method
drop down box.
Your first thought might be, to use a private function. The Widget form builder needs to call the UDF to generate the Select Boxes, so it cannot be a private function.
The solution, add Meta data about the function... including cbignore
. You can add this with the function meta data in a comment.
Or you could add this meta data with the inline meta style.
With either of these two options, you will see the list of Options provided by the UDF, but you will not see the Public Methods
option, as there is only 1 public rendering function.
Widgets do not need to have arguments or parameters, but most Widgets are best designed with some customization available to the user.
Let us add a few arguments to our Simple Widget.
Our starting point looks like this.
We want to give the user an option to change the target of the link. To start we will add the urlTarget
argument to the renderIt()
and then use that argument in our link.
When the user tries to use the widget now, they'll see this added to the Widget selector.
You will see the argument name urlTarget
shown, with the type (any)
. We can add some more options to the arguments to make it more user friendly, including:
Label
Required
Hint
Type
Default
Options
OptionsUDF
MultiOptions
MultiOptionsUDF
These are all added via the JavaDoc syntax for the RenderIt() function. Below is an example of the label being used.
The Label is used when you want to have a more meaningful label for the form field than the name of the argument itself. In our example, by default, the label is urlTarget
, but we can change it to something like This is my Label
The Widget Form now shows this label instead of the Argument name
Required does what it says, it makes a field a required field. This will show a label and add the necessary validation.
To make a field required, add Javadoc Meta comment @urlTarget.required
to the function meta.
A label goes a long way in helping the user understand the purpose of a field, but you can also use Hints to make this crystal clear.
To add a field hint, add Javadoc Meta comment @urlTarget.hint
to the function meta.
Argument Type is very important. By default, all arguments are considered strings, and are shown as a Text Input. The type tells the Widget form builder how to display the argument field. The logic is processed in this order.
Boolean - as a yes / no select drop down
Numeric / String with Options - Select drop down with items from the options list.
Numeric / String with OptionsUDF - Select drop down with items from the options UDF
Numeric / String with MultiOptions - Multiple Select drop down with items from the options list.
Numeric / String with MultiOptionsUDF - Multiple Select drop down with items from the options UDF
Numeric / String - Text Input box
Argument Type can be defined by the Function definition, or with the Meta Data. Below you can see these argument types being set inside the function itself.
You can also use the Meta Data approach that most of the argument options require.
You can set a default Value for the field.
To add a default value for an argument, add Javadoc Meta comment @urlTarget.default _blank
to the function meta.
Using Labels, Hints and Required fields helps the user, but in some cases its best if you can give your users a set list of options to pick from. This will provide the user a select drop down box, instead of a text box. This ensures there are no typos.
To add options for an argument, add Javadoc Meta comment @urlTarget.options _self,_blank,_top,_parent
to the function meta.
As you can see, the Default value even works with the options drop down, so you can have the default, pre-selected.
Option lists work great for some use cases, but sometimes you might need some dynamic information. This allows you to write a function, and the Widget form builder calls that function for you to load the options. To use an optionsUDF instead of options you need to do 2 things.
1- To add options for an argument via UDF, add Javadoc Meta comment @urlTarget.optionsUDF getTargetTypes
to the function meta. The function should be the name without the parenthesis.
2- Add the new function. The new function can be named anything ( other than RenderIt - the default function for a widget ). The function must return an array of elements that can be converted to a string. Here is the UDF function that matches the previous list of options, but this time, we added one, to be sure it is from the UDF.
Reload and test the widget, we see the list of Target Types.
You might see a side effect of adding the function. Now you will see another option, Public Methods
with a list of public functions in ths CFC. Widgets are required to implement the default RenderIt()
function, but the Widget can actually have several methods.
To hide this function ( since this is outputting an array, not a string ), you cannot use a private function ( otherwise the UDF cannot be called by the Form Builder ), you need to add some meta data to ignore the function.
or you could use the inline meta
With either of these two options, you will see the list of Options provided by the UDF, but you will not see the Public Methods
option, as there is only 1 public rendering function.
Using Labels, Hints and Required fields helps the user, but in some cases its best if you can give your users a set list of options to pick from, especially if they user could/should pick one or more options. This will provide the user a multi select drop down box, instead of a text box. This ensures there are no typos.
To add options for an argument, add Javadoc Meta comment @urlTarget.multioptions _self,_blank,_top,_parent
to the function meta.
MultiOptionsUDF works the same as OptionsUDF except the user can select none, one or more of the options.
Add the meta to the RenderIt() function
Add the UDF ( include the cbignore meta data so the Widget Form builder ignores the function )
Widgets are small pieces of software that you can add to your ContentBox website to perform a specific function. There are several Widgets built into ContentBox that are used for various parts of your website, and you can insert widgets into blog posts and pages to make your website even more dynamic.
Widgets are one of the ways ContentBox is extendable, you can install modules and themes that overwrite existing widgets, or are brand new... or you can create or customize your own widgets.
Widgets are maintained through the Administrator under Look & Feel > Widgets
. You can managing existing widgets, upload new widgets, or download widgets from Forgebox.
In this section of the documentation, you will learn how to develop your own widgets.