In this first edition of my envisioned “Beyond Umbraco'' series, I would like to introduce cool tricks you can do with Umbraco. The first entry in this series is about hooking AngularJS in the backoffice.
Why would you want to do this? you might wonder.
And rightfully so: You can do quite a lot with AngularJS in the backoffice already. Umbraco exposes an API which allows you to manipulate the tree, show notifications, show overlays, and load content, among other things. While this API is really great, it only really allows you to reuse what is already in place, not so much to extend it.
You’ve probably run into situations where an implementation in the backoffice was just right!...except for that one little thing. If you are a programmer who doesn’t like to reinvent the wheel (and who does?), you probably looked through the Umbraco source code on Github only to find that the thing you were looking for is deeply hidden away in some AngularJS directive. If that is the case, you have a few options:
- Make a feature request on GitHub, in the hopes that someone will implement this for you at some point,
- Grab the code from GitHub, implement it yourself and make a Pull Request to get it in a future update,
- Find an Our Umbraco package that does something similar to what you are looking for.
Regardless of the option of your choice, you could potentially end up wasting time on nonsense.
What if I tell you there is actually a fourth option? One that, to some extent, prevents complete duplication of existing features but still allows you to add your own tweaks?
There May Be Another Way...
I will now present a case to outline this elusive fourth option.
- The Content section has a language dropdown in the tree. You want to (re)use a dropdown like this in another section as well.
While this might sound like a useful addition to the Umbraco project as a whole, there are plenty of times you run into cases that cannot be turned into a generic Pull Request for other Umbraco users to enjoy (e.g. very client-specific wishes).
The goal here is to give you new insights on how to approach and solve these kinds of client-specific wishes without resorting to any of the earlier mentioned options.
Mini-Umbraco Case Study: Custom Language Dropdown
I was working on a new section in Umbraco with a localized tree that would show different node-titles based on the locale of the current backoffice user. During a product review, my client expressed interest in seeing a language dropdown above the tree just like the Content section has. So I replied, “The tree is stock Umbraco functionality. I can probably turn it on; I’ll investigate and get back to you.”
Figure 1. Screenshot of the localization dropdown in the Content section
Looking for a way in
I logged in to the Umbraco backoffice and selected the Content section. I used the Chrome Inspector to pick the tree and found the class-name “umb-language-picker”. I used that as a search term on Github and found “umb-navigation.html”:
Embed 1. Snippet of umb-navigation.html found in Umbraco 8.11.1
With all the extendability that Umbraco offers, it was quite surprising to find that the language dropdown seen in the Content section is actually hard-wired to only ever appear on the Content section, as shown above with the “ng-if”. A perfectly reasonable reaction would be: “Well, shoot… better inform the client.” Personally, I don’t like giving up that fast… there must be a way!
Scouting for other options
One option would be to completely ditch the stock Umbraco tree implementation I wrote, turn the section into an application with no tree at all, and add my own implementation of a tree renderer. While this would work, the resources involved in creating and maintaining the new tree implementation would potentially be so high that it’s not really a feasible choice for just a single dropdown.
The other option relies on the fact that pretty much everything in the backoffice is written with AngularJS, backed by some APIs in Umbraco itself. AngularJS actually has a few neat tricks up its sleeve to hook into existing code and bend it to your will.
This is the route that I took.
After a bit of browsing through the source code, I found that the umb-navigation.html above is injected in a page using the “umbNavigation”-directive, found here:
Embed 2. Snippet of umbnavigation.directive.js found in Umbraco 8.11.1
Now, knowing which directive is responsible for rendering the tree, I can put AngularJS to work. I want to get a hold of the umbNavigation-directive before it’s rendered and inject my own logic depending on the Umbraco Section I’m in.
A custom plugin
Create a new folder in your Umbraco-project, for example: “~/App_Plugins/TreeExtensions”. In the “TreeExtensions”-folder create a package.manifest file with the following content:
Next, create a “decorators.js”-file in the same folder.
Decorating the directive
AngularJS works with modules to separate logic into namespaces to keep things neat and tidy. All core logic is located in the “umbraco”-module while all directives are under the “umbraco.directives”-module.
Each module goes through a “configuration”-phase to which you can add your own logic.
Open up the “decorators.js”-file from earlier and add the following:
This piece of code hooks into the configuration-phase of the “umbraco.directives”-module. Once the configuration-phase kicks off, AngularJS will inject an instance of the $provide-service. This service allows you to register a decorator (also see: https://docs.angularjs.org/api/auto/service/$provide#decorator).
At TODO #1, add the following:
In the code above I attempt to decorate the umbNavigation-directive. As per AngularJS convention, I take the name of the registered directive I established earlier and append “Directive” behind it.
Next, I ask AngularJS to inject a $delegate service, which actually contains an array of all applicable directives. By taking the first index, I effectively get the registered directive I found in the code earlier. If you were to console.log that reference, you would find it contains the template-property I showed you earlier.
What I want to do now is modify the original template to make it do my bidding. There are two ways you can go about this right now (all of which I might classify as “hacky” from now on because you make assumptions based on code that is not in your control. This means it can stop working when a different or newer version of Umbraco is used).
- Replace the “currentSection === 'content' &&” in the ng-if from earlier with something else. An empty string would make the language dropdown show up on all trees in Umbraco, not just your custom section.
- Inject your own version of the language dropdown for ultimate control over how it works.
This is how you would implement option 1 at TODO #2 above:
Being slightly more specific than necessary in this case prevents inadvertently breaking other parts of the template.
I actually went with option 2, as that allowed me to do much more than just selecting a language. This happened to be another request from my client as well.
Injecting your own template
Another cool feature of AngularJS is the ability to load any external HTML file into your application by use of a “ng-include”-attribute.
Create a “dropdown.html” in the “TreeExtensions”-plugin from earlier and paste the below code:
Modify the ng-if to your liking (I chose to use the Media section), then in your “decorators.js” add the following at TODO #2:
After the section specified in your ng-if loads, you should see a nicely working language dropdown above your tree, just like the Content section!
Adding a controller to your template
So far, option 2 sounds like a lot of work to basically get the same result as option 1. So let’s see where this approach shines: Adding your own controller to control how the dropdown works.
Open up the “dropdown.html”, and on the “div” of the ng-if add an additional attribute, ng-controller=”BeyondUmbraco.TreeExtensions”.
Next, you need to add a new controller-registration to the “decorators.js”-file that matches the name you used in the ng-controller attribute above. Be sure to place this code below all existing code!
With this controller in place, the following will happen:
- The dropdown contains “Hello” and “Umbraco” as an option.
- “Hello” will be selected by default.
- When you select a new option, a notification will show with the current selection mentioned.
Figure 3. Screenshot of the custom dropdown in the Media section
You are obviously free to start making the HTML your own! If you are not going to show languages in the dropdown, then you might want to update the naming of the variables and so on to be more applicable to your situation. You can now also inject other useful Umbraco services in the controller to do as you please when selecting something from the dropdown.
What Have We Learned?
From this point on, the sky's the limit when it comes to customization of the Umbraco Backoffice. This “fourth option” is now part of your arsenal to make great solutions!
Pretty much everything in Umbraco is a directive, so the solution outlined above works in a lot of situations.
Case in point: Don’t disregard client wishes immediately. Go through the Umbraco source and try to figure out how things work.
GitHub repository: https://github.com/LennardF1989/BeyondUmbraco
Arlanet CTO Lennard Fonteijn
About the Author
Lennard is the CTO of Arlanet, part of 4NG. He is a passionate developer with over a decade of experience in building websites, games and other software. He has been working with Umbraco since version 4.0, is an Umbraco MVP, an Ucommerce MVP and teaches students how to be a Software Engineer at the Amsterdam University of Applied Sciences. In his spare-time, what's left of it besides his numerous personal projects at least, he enjoys to play a game or two, maybe three.