Umbraco

Beyond Umbraco 2 – Hooking AngularJS (again)

MVP Lennard Fonteijn is BACK to tell you about a new technique

Written by: MVP and Arlanet CTO Lennard Fonteijn

For the second entry in the "Beyond Umbraco" series, Arlanet CTO and our MVP Lennard Fonteijn is back to show us how to hook an existing AngularJS and extending it with your own logic 🦯✨ Just another way this innovative community can implement custom functionality in the friendliest, most bendiest CMS there is 😉

In my last blog post, I showed you how to find and hook a directive in the Umbraco Backoffice in order to manipulate the template it renders. 

While writing that post, I already had a second case in mind! I’m happy to continue with you onto the second entry in the “Beyond Umbraco” series.

In this blog post, I want to show you how to hook an existing AngularJS controller and extend it with your own logic. While I call it a technique, that description doesn’t do it complete justice. It’s actually a bunch of tricks combined that make this work. Each trick on its own can have multiple applications in general. In the end, these tricks solve the same problem I introduced in the last post: Prevent complete duplication of existing features while allowing you to add your own tweaks.

 

Mini-Umbraco Case Study: Custom Tag Editor

For one of my clients, I built an extension into the Media section to allow free categorization and filtering through 50GB of media based on tags. This extension heavily relies on a custom Media Type with a bunch of Tags property editors. Overall, the client was really pleased with the implementation, but mentioned that the Tags property editor didn’t work as expected when copy/pasting texts containing spaces.

The case to outline the technique is as follows:

  • Umbraco has a Tags property editor. It works fine, but you want to be able to copy/paste a line of text and parse the spaces as separate tags.

To keep some steps short and to the point, I’ll assume you’ve read the previous post. If not, go ahead and do that now for more background!

Let’s take a look…

I opened up one of the media items, used Chrome Inspector on one of the Tag editors and found the ng-controller attribute of the property editor I was looking for: Umbraco.PropertyEditors.TagsController

I then searched for this controller in the Umbraco GitHub repository, but couldn’t get any results related to it. Ugh! Unfortunately, I’ve seen this happen occasionally. I know it exists, yet, GitHub for some reason hasn’t properly figured that out. You are now left with a few options:

  • Adjust the search term to be slightly less specific.
  • Click through the repository files until you find what you are looking for; there is logic behind how it’s structured.
  • Clone the repository, open up the solution and use a solution-wide search to find what you need.

I chose the first option and looked for just “TagsController” instead, which gave me the results I was looking for:

Embed 1. Snippet of tags.html found in Umbraco 8.11.1

 

This taught me the Tag property editor is actually implemented as a directive with the name “umb-tags-editor”. Going by AngularJS naming conventions, I would now have to look for “umbTagsEditor” to find the controller behind it, which brought me to this file:

Embed 2. Snippet of umbtagseditor.directive.js found in Umbraco 8.11.1

 

And also the related view:

Embed 3. Snippet of umb-tags-editor.html found in Umbraco 8.11.1

 

In the view, I found the textfield that handles the input, which had a few important pieces of information: ng-model, ng-keydown and ng-blur.

Based on this, I can make the following assumptions:

  • ng-model: All input is stored into the variable “vm.tagToAdd”.
  • ng-keydown: “vm.addTagOnEnter” is called every key press.
  • ng-blur: If you click outside the input and effectively unfocus the textfield, then “vm.addTag” is called.

I made a note of this, dove into the controller code and quickly found all the pieces of the puzzle:

Embed 4. Another snippet of umbtagseditor.directive.js found in Umbraco 8.11.1

 

I can see every time addTagOnEnter is called by the keydown-event, it will check if the keycode of the pressed key is equal to 13, which is the number for the Enter key (see https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode). 

If Enter is pressed, the addTag function will be called, which will then call addTagInternal with vm.tagToAdd as an argument. I also know that addTag is called when the textfield is unfocused, so this means the addTag function is the way in to add support for spaces!

Hooking the controller

Now that I know what I want to change in the original code, I can make a plan for it. Because this is a directive, I can use the same technique I described in the previous blog post. To briefly reiterate the steps:

  • Make an App_Plugin to load a JavaScript file.
  • Hook into the configuration-phase of the “umbraco.directices”-module.
  • Add a decorator to directive, which is “umbTagsEditorDirective” in this case. (Don’t forget to append “Directive” behind it!)

This results in the following code:

Unlike the previous blog post, I am not going to manipulate the template-property this time around. Instead, I’m going to manipulate the controller-property.

There are a few things to consider before doing so:

  • Function versus Object
  • Scoping/Closures

 

Function versus Object

In this case, the controller-property is a reference to the controller function I found earlier, which in turn contains the addTag function. During the configuration phase of a directive, an instance (object) of the controller does not exist yet. This means I can’t just do something like “directive.controller.addTag” to get a reference to the addTag-function.

Scoping/Closures

Next up is the problem of scoping. I have to “extend” the original function (much like a class in Object-Oriented Programming). Both JavaScript itself and AngularJS have multiple ways of doing this:

  • call() or apply()
  • angular.extend() in combination with either of the following AngularJS services:
    • $controller
    • $componentController

 

…And variations thereof. In the end, all of these approaches have pros and cons in their usage and effectiveness. For example, the $componentController is just a wrapper around the $controller, which is part of the ngMock framework meant for unit testing. 

You should consider if you want to rely on that in production code. On the other hand, you also have to consider if you want to rely on template or controller manipulation for production code. Whatever works and results in the least amount of work, right?

Initially, I used the angular.extend() in combination with the $controller, doing similar things as the $componentController does. While this seemed to work at a first glance, it failed to properly respect the directive bindings causing all sorts of weird issues. So I went with apply() instead.

At the TODO in the code above, add the following:

This takes the references to the original controller function, overrides it with our own version and inside it calls the original controller function using apply on the special “this” variable. Whenever AngularJS wants to instantiate a controller for this directive, it will first call our function, after which I call the original function. 

By passing “this” as the first argument, I am effectively extending the current object with whatever the original controller code is doing. The “arguments” simply pass all incoming arguments as an array to the original controller code. 

Keep in mind this has to match the original controller code exactly! If you intend on injecting additional AngularJS services for use in your own logic later on, it’s better to use call() instead and pass all required arguments manually.

If you were to do a console.log(this) after calling the apply(), it would look exactly like the original and also contains the addTag function I want to override.

 

Hooking the function

It’s time to add my own logic! Just like C#, JavaScript has the concept of public- and private-modifiers when extending classes, even though it is not as apparent.

When I extend the original controller, I only get public access to the properties the original code explicitly added to “this”-variable (or by proxy using the vm-variable). Because the original code does “vm.addTag = addTag”, “this” now contains a public property which is a reference to the private function addTag. Anything not added to “this” is private and can never be accessed, no matter how hard you try.

Luckily, the vm.addTag is exactly what I need, so let’s hook that too using the same trick as we did with the controller:

  • Store the original reference in a variable
  • Replace the property with a new implementation
  • Call the original function when needed

This trick is similar to one in the Reverse Engineering world called a “trampoline hook.” You redirect a function call to another function and then back to the original. In a Reverse Engineering situation you would, however, replace the actual function in memory, whereas I am “just” replacing a property referencing the actual function. This is going to pose a problem later on, but I’ll get back to that!

Below the apply-call in your code, paste the following:

Inside the addTag function I store a reference to the current “this” as “vm.” I do this because “this” changes depending on which scope you are in. Next, I split the tagToAdd variable by a space character and iterate over the resulting array using forEach. Each iteration calls addSingleTag and passes a tag as an argument. Inside addSingleTag I set the tagToAdd to the passed value and call the original function. I need to use the “vm” from the outer scope here instead of “this,” because “this” has a different value as part of the Each-call.

When I go to my Tags property in Umbraco, type “hello umbraco” and unfocus the textfield. It adds “hello” and “umbraco” as two separate tags. Hurray, I’m done!

Well… not quite. When I remove the tags, type “hello umbraco” again and press Enter, I only get one tag with a space. Shoot! What is going on here? Isn’t addTagOnEnter supposed to call addTag?

 

I’ve got 99 problems, but Umbraco ain’t one.

Let’s take a brief look at the addTagOnEnter code again:

Embed 5. Another snippet of umbtagseditor.directive.js found in Umbraco 8.11.1

 

At a first glance, this seems okay. When Enter is pressed, it calls addTag. Remember my story about “trampoline hooks” and how I ominously predicted problems? What I did when I hooked the vm.addToTag function was replace the reference to the private addToTag method. But unlike a real trampoline function, I didn’t actually replace the function itself! Mostly, because I can’t… private means it’s really private! This means that when the original code calls a private method directly, and not through the vm.addToTag property, it just skips our hook.

To fix this, I could open an Issue on the Umbraco Issue Tracker and complain that my hacks are not working because this controller was never made with extensibility in mind. And I might, some day. But both of us are short on time and this blog post is already long enough, so let’s just fix it ourselves!

Unfortunately, I have to duplicate a bit of code now…  

Make a copy of the code from umbtagseditor.directive.js above. Add it to your code below the apply() and replace the addTag call with vm.addTag instead, like so:

If you end up having to do this, it’s good practice to add a comment above the code, stating that you literally copied it from somewhere else. While this might feel silly, you are now also in control of the key-down event, which opens up some additional possibilities for extension, but I’ll leave that to your imagination.

If you do the same test as before, typing “hello umbraco” followed by pressing Enter, it now correctly creates two tags. Finally!

Wrapping up

With a lot of words, I showed you a bunch of things:

  • Multiple ways of inheriting code as if they are classes in Object-Oriented Programming.
  • “Overriding” existing public functions, while still being able to call the original one.
  • That scoping can be annoying, but that a little responsible copy/pasting goes a long way.

Regardless of whether or not you are extending Umbraco functionality: if you use AngularJS in your daily development, you now at least know how to extend (your own) controllers and how to open them up for inheritance.

Sources

GitHub repository: https://github.com/LennardF1989/BeyondUmbraco

Gists: https://gist.github.com/LennardF1989/d37f0a6e3159ba3581999117ad717c70

 

MVP 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.

 

 

We're so excited to offer our MVPs the chance to showcase their work on our blog. Want to see your name here next?

BECOME AN UMBRACO MVP