Issues

A Tour of the (Backoffice) Tours

If you have installed Umbraco 7.8 or later recently you have probably have seen the “Getting started” tour popup that is part of Umbraco when you first login.

If you haven’t tried this cool new functionality yet you should definitely try it out.

But I think a video shows more than a thousand words, so have a look a the video below to see tours in action if you haven’t seen them yet.

Umbraco 7.8 Back Office Tours from Umbraco on Vimeo.

Pretty awesome, right ? In my opinion, when done right, tours can replace editor manuals and training.

We all have clients that call us because they forgot how to do something in the back office. You explain it to them, but 3 months later they have to do the exact same thing... and they forgot... and call you again.

Creating a tour showing them how to do it will save them and you those phone calls. Everybody wins.

Creating Tours

I won’t go into too much detail here on how to create and edit tours, because I want to focus on doing some of the cool things you can do with tours. What you need to know is that tours are stored in JSON files on your server. The structure of the JSON and where the files need to be located is all documented : https://our.umbraco.com/documentation/Extending/Backoffice-Tours/

But if you are like me, you probably don’t like to edit JSON files using a text editor. That’s why I created a package that installs a UI in the back office for creating and editing tours. You can download it here: https://our.umbraco.com/projects/backoffice-extensions/tour-editor/

Or if you prefer nuget: Install-Package Our.Umbraco.TourEditor

Here is a video of the package in action. It shows how I create tour to show the user how to change his password in 4 minutes!

Custom Steps

One of the cool things that I mentioned before is the ability to use your own Angular views and controllers to display a tour step. A common use case is to validate user input before the tour can continue to the next step.

Let’s see it in action. In this tour the user is required to enter the correct name of a JavaScript file they are creating.

So let's have a look on how to set this up. You will need to create an Angular view in a new directory: /App_Plugins/customsteps. Let's call the view requiredinput.html. Then copy and paste in the following code into your newly created view:

<div ng-controller="TourDemo.RequiredInputController as vm">
    <umb-tour-step on-close="model.endTour()">
        <umb-tour-step-header title="model.currentStep.title">
        </umb-tour-step-header>
        <umb-tour-step-content content="model.currentStep.content">
            <div ng-if="vm.error" class="color-red" style="margin-top: 5px;">Please enter <b>{{vm.inputvalue}}</b> in the field</div>
        </umb-tour-step-content>
        <umb-tour-step-footer class="flex justify-between items-center">
            <umb-tour-step-counter current-step="model.currentStepIndex + 1" total-steps="model.steps.length">
            </umb-tour-step-counter>
            <div>
                <umb-button size="xs" button-style="success" type="button" action="vm.initNextStep()" label="Next"></umb-button>
            </div>
        </umb-tour-step-footer>
    </umb-tour-step>
</div>

As you can see we are using a lot of Angular directives like umb-tour-step, umb-tour-step-header, etc. This helps us to keep the same look and feel as all other tour steps. The code inside the body of umb-tour-step-content is what will show error message when the user has not filled in the correct input.

The next thing is to create an Angular controller for the view. Create a field called requiredinput-controller.js in the same folder as you created the view. Then copy and paste in the following code:

(function () {
    'use strict';
    function RequiredInputController($scope) {
        var vm = this;		
        var element = angular.element($scope.model.currentStep.element);
        vm.inputvalue = $scope.model.currentStep.customProperties.input;
        vm.error = false;
        vm.initNextStep = initNextStep;
        function initNextStep() {
            if (element.val().toLowerCase() === vm.inputvalue.toLowerCase()) {
                $scope.model.nextStep();
            } else {
                vm.error = true;
            }
        }
    }
    angular.module('umbraco')
	.controller('TourDemo.RequiredInputController', RequiredInputController);
}());

The last thing we need to do is register this custom step in the Umbraco backoffice. We do this by creating a file called package.manifest into the same folder where you created your Angular view and controller. Then copy and paste in the following code:

{  
  "javascript": [
    "~/App_Plugins/CustomSteps/requiredinput-controller.js",    
  ] 
}

So now we have everything in place for using this custom view as a step in your tour.

You will need to set 2 properties on the step JSON object where you want to use this view.

First is the "view" property. Give the value: "/App_Plugins/CustomSteps/requiredinput.html"

The next one is the "customProperties" property. Give it the value:

"customProperties": {
	"input": "HelloWorld"
}

But to make it easier for you I have prepared a tour that you can download and place in the folder /config/BackOfficeTours: https://raw.githubusercontent.com/dawoe/umbraco-a-tour-of-the-tour/master/Config/BackOfficeTours/requiredinputdemo.json

Now you will see a tour called "Created Script file" in the group "Javascript files". If you take this tour you will see the custom step in action when you are asked to enter the file name.

If we take a closer inspection of the controller code we can see that $scope.model is actually the tour JSON object extended with some methods like nextStep, endTour, completeTour, and disableTour, and a property called currentStep. All these contain the JSON data from your tour file. So if we do $scope.model.currentStep.customProperties we get the JSON object defined above.

Pro tip: if you install my Tour Editor package mentioned above you will already have this view installed and ready for use. Just change the view property of your step to: "/App_Plugins/TourEditor/backoffice/tours/views/requiredinput.html".

Starting tours from your own views

If you create a tour it will appear in the help drawer (if you have access to all the sections needed for the tour). But in some cases it would be nice to be able to start a tour from a dashboard, for example or from a prevalue editor you build for your awesome property editor.

Luckily that is possible. Below you will see a video of a tour started by clicking on a button on the dashboard.

So to do this the first step is to download this tour file and place it in the /config/BackOfficeTours folder of your website: https://raw.githubusercontent.com/dawoe/umbraco-a-tour-of-the-tour/master/Config/BackOfficeTours/user-management.json

Now create folder called HelpDashboard in your App_Plugins folder and copy all the files found here in to the folder: https://github.com/dawoe/umbraco-a-tour-of-the-tour/tree/master/App_Plugins/HelpDashboard

The final step to get this dashboard working is to register it with Umbraco. Add this snippet to your /config/Dashboards.config file:

<section alias="TourDemo.HelpDashBoard">
<areas>
  <area>content</area>
</areas>
<tab caption="Help">
      <control>/App_Plugins/HelpDashBoard/starthelp.html</control>
  <control>/App_Plugins/HelpDashBoard/changepassword.html</control>
</tab>
</section>

If you now log into the back office you will see a Help dashboard in the content section. Both buttons on there will start a tour. But they work differently.

If you look at both controller functions in controllers.js you will see that we inject tourService (https://our.umbraco.com/apidocs/ui/#/api/umbraco.services.tourService). This service you can use to get tours and to start, end, and complete them from code.

So if you look at the startHelp function in ChangePasswordController you will see that we retrieve the tour by getting it by alias from the service. When the tour is returned by the promise we start the tour using tourService.

This is awesome because you now can start a tour from basically anywhere. But there is a downside to doing this. It requires you to have the tour created in a JSON file and that means it will also show up in the help drawer. And sometimes you just don’t want that, for example when you have a complex prevalue editor for a data type.

But luckily you can pass your own JSON object to the tour service without it being in a JSON file. That’s what I do in ShowHelpController.

Hiding Tours

Maybe in some cases you want a tour not to be available. For example you want the “Getting Started” tour that ships with Umbraco not be available. Or maybe hide a tour that ships with a property editor. You could just delete the tour files from disk, but than you have the risk that they are back again after an upgrade.

To filter out tours you need to write some C# code. You can copy and paste this file (https://github.com/dawoe/umbraco-a-tour-of-the-tour/blob/master/App_Code/Events.cs) into the App_Code folder (you may have to create that folder) and uncomment line 9. After your restart your Umbraco application the “Getting Started” tours will be gone. This because we told Umbraco to not include all the tours in the getting-started file.

TourFilterResolver.Current has three methods :

  • AddFilterByFile : this will exclude all tours from the give json file (you don’t need to a the .json extension)
  • AddFilterByPlugin : this will exclude all tours from a plugin folder. You need to pass in the foldername of the plugin
  • AddFilterByAlias : this will exclude the tour with the given alias

So knowing that we can filter tours, we now actually have the ability to replace the tour that automatically starts when you login in to the back office for the first time. You can use the code example above to hide all the tours in that ship with Umbraco. And you create a tour with the alias “umbIntroIntroduction”. This is at the moment hard coded in Umbraco to start this tour.

What's Next

We all can agree that the tour functionality is great, but there is still room for improvement. Here are some ideas that I have to make it even more powerful.

Doctype specific tours.

I already created a PR (https://github.com/umbraco/Umbraco-CMS/pull/2635) and issue (http://issues.umbraco.org/issue/U4-11353) for this. If you want to create a tour to show a editor how what the fields on a doctype mean it can become very difficult very quickly. You will need to setup the tour so the user clicks through the tree to find a item of that doctype. And sometimes editors have different start nodes, so this becomes almost impossible to do. So that’s what I created this PR. You can now target a tour to multiple doctypes. And each doctype can have multiple tours assigned. There are videos in the issue showing the end result and also one that shows the first version.

Autostart tours

We already saw in this article that you could do this, but it feels like a hack. So it would be awesome that you could mark a tour, or maybe multiple to auto start when a user logs in and has not taken the tour yet.

Hide tours from the drawer

If you don’t want a tour to be in the drawer you can’t put in the JSON file, but have to construct the JSON object in JavaScript and pass that to the tour service. But wouldn’t it be nice that you could store it in a JSON file, but just have option to not show it in the drawer?

I would love to hear your ideas on how to improve.

That’s it for now... So happy touring and see you next time!

Dave Woestenborghs

Dave is a developer at DotControl, a full service internet agency located in Heerlen and Rotterdam (The Netherlands), and a 5 time Umbraco MVP. As an Umbraco Certified Professional, Dave has been using the Friendly CMS since version 3, and loves being part of its community. When not programming, you can find him spending time with his family, or playing some music.

comments powered by Disqus