Issues

Customizing the Umbraco Tour in a Few Simple Steps!

Hey there!

When you're in the Umbraco backoffice you might have seen the tour that pops up when you first log in. It's meant to show you the basics. But what if you want to add your own touch, like a special language picker or a cool pop-up for new users? That's what we're going to do here!

Why should you customize the tour?

  • You can show off your cool stuff: Make sure your users don't miss the special tools or workflows you've built.
  • Easy onboarding:  Help new people get started quickly and feel right at home.
  • Make it user-friendly: Add handy things like language choices to make everyone feel comfortable.

Note: This customization overrides Umbraco's default intro tour. By implementing these customizations, you’ll replace the default intro tour.

Here's how I customized the tour

Creating the Custom Tour

Follow these Umbraco docs to create your custom tour. I created my own view and specified the tour.json file as follows:

{
  "view": "/App_Plugins/languageSelection/languageSelectionView.html",
  "customProperties": { "controller": "languageSelectionController" }
}

Enhancing User Experience with Language Selection

Adding the Language Selection feature, this allows users to choose their preferred language on their first login. This functionality ensures that new users are greeted in their preferred language, making the overall user experience more comfortable and intuitive right from the start.

Why include this feature?

The goal is to streamline onboarding and allow users to start in the language they're most comfortable with. In a globalized user base, offering language options early ensures that content managers can interact with the CMS in their native or preferred language, improving productivity and reducing friction. By placing this as part of the introductory tour, users can select a language, save it, and not have to revisit that choice again.

To handle the language selection, I wrote a controller and view for this feature:

(function () {
    'use strict';

    function langController($scope, $http, $routeParams, userService, notificationsService) {
        $scope.page = {};

        $scope.init = function () {
            $http.get('/umbraco/backoffice/LanguageSelection/LanguageSelectionApi/GetAllLanguages').then(function (response) {
                $scope.languages = response.data;
            }, function (error) {
                console.error('Error fetching languages:', error);
            });

            userService.getCurrentUser().then(function (user) {
                $scope.page.Id = user.id;
                $scope.page.Language = user.language;

                if ($scope.page.Language && $scope.languages.length) {
                    var language = $scope.languages.find(lang => lang.isoCode === $scope.page.Language);
                    $scope.page.LanguageName = language ? language.name : "Unknown";
                }
            });
        };

        $scope.save = function (model) {
            if (!model.Language) {
                notificationsService.warning("Please select a language before saving.");
                return;
            }

            var content = {
                Id: model.Id,
                Language: $scope.page.Language,
                TourData: JSON.stringify([{ "alias": "umbIntroIntroduction", "completed": false, "disabled": true }])
            };

            $http.post("/umbraco/backoffice/LanguageSelection/LanguageSelectionApi/SaveLanguage", content).then(function (response) {
                if (response.data === "Language updated successfully.") {
                    window.location.reload();
                } else {
                    notificationsService.error("Error", "Failed to save language.");
                }
            }, function (error) {
                console.error('Save error:', error);
            });
        };

        $scope.init();
    }

    angular.module('umbraco').controller('LangController', ['$scope', '$http', '$routeParams', 'userService', 'notificationsService', langController]);

})();

View (HTML):

<div>
	<select class="umb-select umb-property-editor" ng-model="page.Language" ng-options="language.isoCode as language.name for language in languages" required>
        <option value="">Select your preferred Language</option>
    </select>

    <umb-editor-footer-content-right>
        <button class="btn umb-button__button btn-success umb-button-- umb-outline" ng-click="save(page)">Save</button>

    </umb-editor-footer-content-right>
</div>

Note: Controlling Tour Display - The TourData property is used to control whether the tour should be shown or not. By setting it to:

[
  { "alias": "umbIntroIntroduction", "completed": false, "disabled": true }
]

This configuration ensures that the introduction tour (umbIntroIntroduction) will not show again after the user has completed it.

Controller (C#):

using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.BackOffice.Controllers;
using Umbraco.Cms.Web.Common.Attributes;

namespace ExampleNamespace.Controllers
{
    [PluginController("LanguageSelection")]
    public class LanguageSelectionApiController : UmbracoAuthorizedApiController
    {
        private readonly IUserService _userService;

        public LanguageSelectionApiController(IUserService userService)
        {
            _userService = userService;
        }

        [HttpPost]
        public IActionResult SaveLanguage(UserChangeModel model)
        {
            try
            {
                var user = _userService.GetUserById(model.Id);
                if (user == null)
                {
                    return NotFound("User not found.");
                }

                user.Language = model.Language;
                user.TourData = model.TourData;
                _userService.Save(user);

                return Ok("Language updated successfully.");
            }
            catch (Exception ex)
            {
                return StatusCode(500, $"Error updating language: {ex.Message}");
            }
        }

        [HttpGet]
        public IActionResult GetAllLanguages()
        {
            var languages = new List
            {
        
          //Add language list here (e.g., new { isoCode = "en-US", name = "English" })
            };

            return Ok(languages);
        }

        public class UserChangeModel
        {
            public int Id { get; set; }
            public string Language { get; set; }
            public string TourData { get; set; }
        }
    }
}

 

Displaying the Popup

To ensure the popup only shows the first time a user logs in, create a composer to filter out the default tour so we can use our newly created view:

public class TourComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.TourFilters().AddFilterByFile("getting-started");
    }
}

This defined composer filters out the default "getting-started" tour.

Then we can use the intro alias in our custom tour.

[
  {
    "name": "language selection",
    "alias": "umbIntroIntroduction",
    "group": "Switch between different languages",
    "groupOrder": 1,
    "allowDisable": true,
    "culture": "en-US",
    "contentType": "",
    "requiredSections": [
      "content"
    ]
  }
]

🎉 And the result looks like this:

 

Acknowledgements

I’d like to  thank  Melvin van Rookhuizen for his brilliant idea behind the language selection feature H5YR! :)

Happy Customizing!

Bishal Tim

Bishal Tim is from Nepal. He works with with .NET Core and Umbraco, specializing in building custom packages and web solutions that work. Whether it's a sleek website or a complex application, he focuses on making it scalable, reliable, and user-friendly. When he's not behind his computer he enjoys playing basketball.

comments powered by Disqus