Blog

Mura Plugins Boot Camp: Day 6 - Frameworks

September 22, 2015 by Grant Shepert

In my previous blog posts on plugin development, I've reviewed the basic building blocks of a Mura CMS plugin. While this model is fine for building smaller plugins, there may come a time when you want to build something more substantial. In those cases, you would be well advised to look at using a development framework.

In this post, I'm going to do just that, by migrating the Event Register plugin into FW/1 (a.k.a. framework/one). The FW/1 development framework is an ideal base for Mura plugin development* for several reasons:

  1. It is a lightweight framework, unencumbered by Mura-duplicating functionality (caching, url creation, permissions, etc.) that you will find in larger frameworks
  2. It's easy to learn
  3. It's multi-application 'subsystem' model meshes well with Mura's display object architecture
  4. There is a great deal of documentation and a fantastic 'seed' plugin that will speed your development

Frameworks Overview (sorta)

We have a lot of ground to cover here, but a brief frameworks is in order.

The idea behind frameworks is that they will help accelerate your development by providing a consistent foundation and toolset, and will make it easier for teams and next-iteration developers to work on the codebase (fear not, no spaghetti code lurks here!). While many companies claim to have "rolled their own" frameworks, I am a strong advocate for choosing an existing, open source framework like ColdBox, CFWheels, or in this case, FW/1. I say this in respect to the latter point I made about teams and next-iteration developers, as choosing a community-distributed framework will allow you to save time and resources when training new employees.

FW/1 Overview (also sorta)

FW/1 was created by Sean Corfield as an answer to the rhetorical question, why do frameworks have to be so $^$$%@#@ complicated? That answer was FW/1, a single-page (single CFC, to be specific) framework that does away with most of the tack-on functionality that often bloats bigger frameworks. By stripping away caching (well, most of it), permissions, validation, etc. we are left with a very straight-forward but deceptively powerful foundation upon which we can frame our application.

FW/1 is an MVC divided into three main components: controllers, views and layouts. Controllers are the traffic cops that process application requests, often handing the majority of the work off to service factories (a post for another day). Views are the rendered results of a user request, such as a form or the processed contents of said and submitted form. Layouts render the html backdrop in which we will hang these results.

FW/1 is a context-based framework. In simple terms, this means there is a specific methodology to the way we lay out our application in terms of files and folders, and this in turn defines the contextual method in which we interact with them. This symbiotic structure removes the guesswork of where you should place what bit of code or markup, and you will quickly come to appreciate FW/1 for this if nothing else.

If you want to read more about FW/1, I'd suggest you visit the GitHub Repo and review the application documentation wiki.

MuraFW1

MuraFW1 is the FW/1 'seed' plugin developed by Blue River employee Steve Withington. The plugin is useful in a number of ways. First, it acts as a sort of self-documenting example of how FW/1 plugins work inside of Mura, and second it is a great starting point for building your own FW/1-based Mura plugins. Admittedly you'll have to whack away a few weeds to get it pared down enough for your own plugin, but that in itself is a good lesson of the many interaction points where Mura and plugins intersect.

I'm not going to weigh down this post any further by covering the ample documentation on FW/1 or MuraFW1 plugins themselves. Instead, I'm going to dive right in and start migrating our plugin.

Event Register ala FW/1

In my previous plugin example, designed to let somebody register for a calendar event, we had a single display object that used a Class Extension to store event registration information. If we wanted to expand this application into something more sophisticated, say a conference management system, I'd have to create and register numerous new display objects and then wire them all together manually. No thank you.

Instead, my first step would be to migrate what I've already created into the MuraFW1 plugin. These are the steps I'd take:

Install the base MuraFW1 plugin in my development version of Mura

This will give me a starting point that I can trim down in preparation for building my application starter plugin

Trim away the example directories

MuraFW1 has a lot of example code, and I only need a small part of it. There are three example mini-apps in there, in folders labeled app1, app2 and app3. Each of these FW/1 'subsystem' directories represents a dynamic display object that will render on the front-end of Mura. I'm going to delete all of them.

Create a new FW/1 subsystem/ Mura Display Object directory

You can name the directory whatever you like, but I usually error on the side of obvious and name mine "frontend". I'll also create the three contextual directories I will use later: controllers, views, and layouts.

Edit the Mura includes/displayObjects.cfc

This is the core handler for my plugin framework. Every FW/1 subsystem has to be registered here. In my case I only have "frontend", so I'll delete "dspMuraFW1App1" etc. and add a new one, as per below:

public any function dspFrontend($) {
return getApplication().doAction('frontend:main.default');
}
Edit the Mura plugins/config.xml.cfm

This file contains the plugin configuration. Settings you will want to update include provider, providerURL and displayobjects. The first two are pretty self-explanatory. For the displayobjects section, I want this:

<displayobjects location="global">
   <displayobject name="Event Register" displaymethod="dspFrontend" component="includes.displayObjects" persist="false" />
</displayobjects>

Note that the "dspEventRegister" refers to the function I created in the displayObjects.cfc (see above).

Also, I would take the time to give your plugin a user-friendly name:

<name>My Super-fantabulous Event Register Plugin</name>

... or something more humble and appropriate, as you see fit. The <package/> value will be drawn from the FW/1 configuration (see next).

Edit the FW/1 includes/fw1config.cfm (or, not)

This file contains the basic FW/1 configuration, rebuilt slightly to work within Mura. If you are familiar with FW/1, you will find all of your development/deployment settings here.

For this example, there's only one very crucial setting that has to be changed:

variables.framework.package = 'EventRegisterFW1';

The FW/1 package name must be unique to the plugin, as this will also be the name Mura uses to register the plugin, store the object mappings, name the install directory, and so on. So, again, it is very crucial to name the package accordingly:

  1. uniquely, i.e. "CompanyNamePlugin", as only one package name can be installed per Mura instance
  2. variable-friendly, i.e. no leading number, spaces, etc. The package name is referenced in variable context in a number of places, such as mappings (i.e. #expandPath('eventRegister')# ) and in loading the plugin itself (i.e. #$.getPlugin('eventRegister')# ).

For example, I've named this plugin "EventRegisterFW1" so that it doesn't conflict with the earlier "EventRegister" example plugin.

Note the Mura includes/eventHandler.cfm

There are a few events that need to be fired in order to wire in and instantiate my FW/1 plugin. Those functions are located here (you'll find a reference to this object in the plugins/config.xml.cfm file). You can also use this file to register your own Mura events, but I would recommend leaving this file alone and registering your own custom event handler as needed.

Clean up the Administrator

If you've spent a bit of time exploring the MuraFW1 plugin, you'll notice the plugin includes a sample administrator (click on modules > plugins > MuraFW1). This has been wired into the plugin via the specially-named "admin" FW/1 subsystem. It behaves essentially as a normal FW/1 application, contained within the "wrapper" of the Mura CMS administrator. You're plugin might not need an administrator or all, or perhaps it will reside almost entirely within the Mura admin.

This said, you will want to build out your plugin's administrator within this folder. For the purpose of this example, since the example plugin won't need an administrator it I'm just going to leave it as is.

Zip, move, remove plugin and reinstall

You want to be a little cautious in this next step. First, zip up the contents of the MuraFW1 directory (don't include the MuraFW1 parent directory, just select the contents). Then move the zip file to a safe location (don't forget to do this!!!!). Then go to the Mura Administrator and under settings > plugins, and delete the MuraFW1 plugin. We want to do this because when we reinstall, Mura will install our seed plugin within a packageName'd folder. That said, click on "add plugin" and select your zip file and follow the setup instructions.

You now have a starting point from which you can develop your new FW/1 plugin. Congratulations! It's a good idea at this point to initiate some kind of source control. i.e. Git or SVN, to track your changes.

Migration

Now that I've finished prepping my plugin, it's time to migrate the original functionality. I'm going to be referring a lot to the example plugin (see below for the link) so you might want to download that now if you haven't already. I'll also be assuming you have reviewed the FW/1 documentation, so if I refer to a concept or function you aren't familiar with, you should review the terminology.

Controllers

In the context of Mura, the controller becomes the "display object traffic cop". If you recall, in the includes/displayObjects.cfc I named "frontend:main.default" as the display object starting point. In FW/1, that means the frontend/controllers/main.cfc > default() function will be called first (and ignored gracefully if it doesn't).

So, in order to control our application, we should have a main.cfc located in the frontend/controllers folder. For every possible context of this display object, we will likely want to add a controller function. For example, if we had a registration form to fill out, you would want a corresponding "registration()" function. If the form posted to another context, i.e. to process the registration, you would create a "process()" function.

The code within these functions could do all of the heavy lifting, i.e. contain queries and funtional code, but best practice dictates that this be moved into a service factory of some kind (this is the M, or Model, of an MVC framework).

You'll also notice before() and init() functions in the CFC. These will help set up the Mura <> FW/1 interactions and handy shortcuts to the Mura Scope, a.k.a. $. You'll also notice that the controllers extend mura.cfobject. This also helps wire the FW/1 requests to the Mura application scope.

Views

When the initial "frontend:main.default" is loaded, the latter part of that request refers to the view as well as the controller function. In this case, FW/1 will look for the frontend/views/default.cfm file (which will fail in an error if it isn't present). This file is mandatory (more or less, but for the sake of this argument lets just say it is), and will be so for every front-end request context. Following the above example, there would also be a registration.cfm and process.cfm file in this directory as well.

For the purpose of migrating our previous code into the FW/1 version, we are going to take the content of the display_objects/display_eventinfo.cfm file and copy into the frontend/views/main/default.cfm file, with a few modifications:

<cfif rc.$('content').getValue('allowRegistration')>
   <ul class="er-eventinfo">
      <li>
         <b>Attendee Limit: </b> #rc.$('content').getValue('attendeeLimit')#
      </li>
      <li>
         <b>Location: </b> #rc.$('content').getValue('location')#
      </li>
   </ul>
</cfif>

In the above example, we have added the "rc" prefix to the Mura Scope. Why? Because within our FW/1 plugin, the mura scope has been placed within Framework One's "request context". This ensures that the Mura Scope you are accessing is within the context of whatever Mura site it has been instantiated under. This is usually only important in specific cases, such as remote calls, but is recommended best practice.

Layouts

Generally speaking, most of your plugin layout is going to be handled by the Mura theme. In fact, as a plugin developer you should keep that in mind when you design your application's layout framework. If you ever distribute the plugin, you will have to consider that your plugin's layout will have to play nice with the Mura Theme.

With this in mind, you should consider only adding enough general styling in the top-level layout to ensure the plugin is self-contained within it's own div and styling elements.

One place contextual Layouts are very handy is in configuring one-off views, like a "print" or "mobile" view for a page, or designing custom configurators. In these cases, using a contextual layout template will give you the flexibility you desire.

In Conclusion

The steps required in migrating a basic, include- or cfc-style display object plugin into FW/1 are relatively straight-forward. Doing so will give you the immediate benefits of a structured, easily expanded and iterated-upon application that will be much easier to develop in a team environment.

We haven't reached the end quite yet, though. In this post I've made a few cursory references to the all-important "M" part of MVC, the Model. Using models to isolate the core business logic of your application is an important step in maximizing the flexibility and iterative potential of your application.

If you've never worked with service factories, it can take a bit of a mental adjustment to adapt to this advanced concept, but hopefully in the next and final blog post of this series, I can shed a little light on this topic via DI/1 (if it sounds similar to FW/1, you aren't far wrong ... Sean Corfield developed this little gem of a service factory as well). This will also conclude the dusty first "MuraORM" post I started a few months ago, as the two concepts will merge together and hopefully give you a decent starting point on building advanced Mura CMS plugins.

Until then!

Event Register FW/1

* note that these reasons are specific to building Mura CMS plugins, and isn't in any way a commentary on the many awesome CFML frameworks available

Additional Resources

Mura CMS Documentation: Plugins http://docs.getmura.com/v6/back-end/plugins

This Mura CMS Blog entry is part of the "Mura Plugins Boot Camp" series by Grant Shepert:

About the Author

Grant Shepert has been a CFML developer for nearly 20 years, and started using Mura CMS the year it was released as an Open Source project. Since then he has written dozens of plugins, spoken about Mura CMS in conventions around the world, contributed code to the core project in numerous ways (his favorite being the FormBuilder), acted as Mura instructor, mentor and evangelist and, when time has permitted, written a blog post or two.

Comments

Post a Comment

Required Field