How to Design Reusable Components for Umbraco: 6 Steps to Better, More Fun Code
Heads Up!
This article is several years old now, and much has happened since then, so please keep that in mind while reading it.
The key is to think about the component parts of your project in a certain way - to consider the code you are creating from a larger perspective than the immediate need at hand.
Why would you want to do this? I see several reasons:
- Deliver a more functional and elegant solution to your current project - which would be easier to update if the project requirements expand.
- Write code once, and use it on a future project - saving time and maximizing profitability.
- Do more interesting coding - who wants to solve the SAME problem over and over again? Boring! Let's do something a little more challenging, and learn new skills and IMPROVE our coding skills with each project.
Follow these steps when architecting a piece of functionality for your current project, and see how large a library of reusable code you can create and use in your next project!
1 - Define the necessary functionality for this project's need
In the example of a basic website "Contact Us" form, let's suppose that our current project requirements include the following:
- The form will have a name field, an email field, and a message field.
- There will be a drop-down box of 3 different inquiry categories ("Technical Support", "Sales Support", "Other") which will determine who at the company should receive the message.
- When submitted, an email needs to go out to the appropriate person, based on the drop-down, with the form data included.
- The Content Editors need to be able to edit the email addresses which will receive the emails, in case of future staff changes.
- We are not planning on using Umbraco Forms (fka "Contour")
2 - Next, think about the same sort of general functionality being used by a different company/website.
What might be different about their needs or workflow? What parts of the UI would be different? Where would the common functionality possibly diverge? Some possibilities for our Contact form:
- They might want a first name and last name field separately, or other additional fields
- They will certainly have different categories or departments where emails should be routed than the three specific options on this current project.
- Perhaps some of those emails might need to go into an automated system, rather than to a person's inbox, and need to be formatted in a specific way which is different between types.
- Perhaps there could be a need for the person filling out the form to receive a copy of the message as well.
- Maybe we don't need the data to go to an email box at all, but to be added to a database, or sent to a 3rd party system via an API.
3 - Now we need to start thinking a bit about the implementation and breaking out the parts which would need to be configurable.
How can we abstract the functionality in a way that allows exact code reuse, with configuration options? The key here is not to explode out all the possibilities to the point that you are building something which will be just as complicated to use in a new project as to start from scratch, but would allow for reasonable re-use in generally similar use-cases. Perhaps it makes sense to create a completely "pluggable" architecture, or perhaps you can create something with quite a bit of functionality to handle 80% of your common use cases. You don't want to lose sight of the immediate goal - which is to create a contact form for a specific project right now, and you don't want to go overboard with a "perfectly abstracted architecture" which will take too much time to create now, and too much code to manage for the future. We want to go for a middle ground of reusable, with simple, flexible configuration. So start brainstorming ideas about how you could build it in a reusable/configurable way:
- Each category for the drop down could be separately configured with its own email address, message template, and additional options related to how the message is handled - perhaps a custom "from" address or subject line, etc.
- The fields that are displayed on the form could be configurable.
During this process, don't get too hung up on the difficulty of implementing each possible feature, but do consider that IF you wanted to implement the feature, where you would need to build it in.
For instance, would we want to make our Contact form so flexible that it could send an email, pass data via a web service, or input data into a database? It would certainly be possible for it to have that much flexibility, so IF it did, where would that need to be configured? We could configure it on the form itself - or we could configure it at the level of the separate categories. The advantage of being configured on the categories is that different categories on the same form could do different things, like selecting 'Sales' could send the data into a CRM system via a web service or custom function, while selecting 'Support' would add data to an internal SQL database, or send an email into a ticketing system. The disadvantage would be complexity and possibly having to duplicate settings in actual usage which has simpler requirements.
In terms of configurable form fields, you might not be able to abstract all the possibilities in a way which wouldn't be much too complex for the current project's needs, so that might be something you don't make configurable right now, but IF you wanted to have that, it could be configurable at the "form" level - so that all the defined categories would use the same form fields (which makes sense from a UI perspective - the categories are just a drop-down on a single form). If a website needed more flexibility, we could add more than one "form" to the site, each with its own form fields, and separate set of categories. These are the things you need to consider during this "design" phase.
4 - Build for your immediate needs - but with the possible end in mind.
Based on the ideas generated in the previous step, you can start to build out the functionality for the current project. Since right now our needs are somewhat simple - just sending emails to different addresses - we don't need to add on (and code!) all the possible alternative options, but we might leave the possibility of adding those items open in the future.
For our hypothetical example, Let's start by creating the DocumentTypes we'd need. We will have two:
- Contact Form - Since we aren't making the fields configurable right now, this won't need any special properties, it will just allow for child nodes of our second DocumentType - but in the future we could add additional form-level options here.
- Contact Category - Properties will include: "Category Display Name" (textstring), "To Address" (textstring) and "Subject Line" (textstring). In the future we could add additional properties to handle other processing options, such as API Url, Database Stored Procedure name, Message Format (with "merge" fields delegated), etc. Along with a drop-down of available processing options (Email, Api, DB, etc.)
When we program the partial view/controller which will render and handle the form, we can use a looping structure to read out the child "Contact Category" nodes - fetching their "Category Display Name" to use in the Category drop-down, and perhaps the Node ID as the "Value" of the drop-down. On form Submit, the selected "Contact Category" node can be looked-up via its ID, and the email message constructed using the specific "To Address" and "Subject Line". If you have extra time, you might write out the code to handle generating the message from the form fields using a generic field-looping structure, rather than calling specific fields by name - which would make it easier for you to implement the custom fields functionality in a future iteration. If you don't have time now, you can add some code comments about it.
5 - Keep track of the pieces needed to re-create the component in another project
You will want to devise some sort of storage/documentation mechanism to keep all the necessary "bits" for reuse together. Depending on your organization's code management structure/systems, this could take many possible forms. You could create a package with the DocumentTypes and files needed, which would make insertion into a new project simple... Or you might export the DocumentTypes as UDT files, and copy the other code files into a directory together inside a git project. Whatever your system includes, add some code comments and notes so that you know what the component can currently do - and what it could possibly do in the future with certain enhancements and additions.
6 - As you have time or future project needs, refine the component with the additional functionality/customization you have designed.
When your next project has a somewhat similar requirement, rather than starting from scratch, copy over your component bits, and spend your time building in the extra functionality. (Perhaps this project needs to support the "Message Format" option for automatically parsed emails sent to a ticketing or CRM system, or needs to support an API call for this Category, and a basic email for this other Category...)
When adding the new functionality, do your best to maintain the original functionality, and only enhance it, with additional configuration options and dynamism. Once complete, update the component in your separate code management system, so the next project will start with this enhanced version.
As you begin to consider every design/code challenge this way, you will slowly be building up a library of "plug-n-play" components which will save you time and effort in the future, and can be shared across your development team to improve everyone's work.
Heather Floyd has been involved in website and software development for over 15 years and using Umbraco since 2006, specializing in information architecture and development efficiency. Heather speaks internationally about Umbraco and is a co-organizer for the NYC Umbraco Meetup group. Find her online at HeatherFloyd.com and @HFloyd.
Heather Floyd
Heather is on Twitter as @HFloyd