A guide that shows you how you can create a custom dashboard in Umbraco CMS.
What is a Dashboard?
A Dashboard is a tab on the right-hand side of a section eg. the Getting Started dashboard in the Content section:
Why provide a Custom Dashboard for editors?
It is generally considered good practice to provide a custom dashboard to welcome your editors to the backoffice of your site. You can provide information about the site and/or provide a helpful gateway to common functionality the editors will use. This guide will show the basics of creating a custom 'Welcome Message' dashboard. The guide will also show how you can go a little further to provide interaction using AngularJS.
The finished dashboard will give the editors an overview of which pages and media files they've worked on most recently.
Prerequisites
This tutorial uses AngularJS with Umbraco, so it does not cover AngularJS itself, there are tons of resources on that already here:
At the end of this guide, we should have a friendly welcoming dashboard displaying a list of the editor's recent site updates.
Step 1: Creating and configuring the Dashboard
Create a new folder inside our site's /App_Plugins folder. call it CustomWelcomeDashboard
Create an HTML file inside this folder called WelcomeDashboard.html. The HTML file will contain a fragment of an HTML document and does not need <html><head><body> entities.
Add the following HTML to the WelcomeDashboard.html:
WelcomeDashboard.html
<divclass="welcome-dashboard"> <h1>Welcome to Umbraco</h1> <p>We hope you find the experience of editing your content with Umbraco enjoyable and delightful. If you discover any problems with the site please report them to the support team at <a href="mailto:">support@popularumbracopartner.com</a></p>
<p>You can put anything here...</p></div>
Similar to a property editor you will now register the dashboard in a package.manifest file.
4. Add a new file inside the ~/App_Plugins/CustomWelcomeDashboard folder called package.manifest:
Add a tab called 'WelcomeDashboard' to the 'Content' section of the Umbraco site, use the WelcomeDashboard.html as the content (view) of the dashboard and don't allow 'translators', but do allow 'admins' to see it.
The order in which the tab will appear in the Umbraco Backoffice depends on its weight. To make our Custom Welcome message the first Tab the editors see, make sure the weight is less than the default dashboards. Read more about the default weights.
You can specify multiple controls to appear on a particular tab and multiple tabs in a particular section.
Step 2: Add Language Keys
After registering your dashboard, it will appear in the backoffice - however, it will have its dashboard alias [WelcomeDashboard] wrapped in square brackets. This is because it is missing a language key. The language key allows people to provide a translation of the dashboard name in multilingual environments. To remove the square brackets - add a language key:
Create a Lang folder in your custom dashboard folder
Create a package-specific language file: ~/App_Plugins/CustomWelcomeDashboard/Lang/en-US.xml
The App_Plugins version of the Lang directory is case-sensitive on Linux systems, so make sure that it starts with a capital L.
The localize tag will be replaced with the translated keywords. We have some default text inside the tags above, which can be removed. It would usually not be visible after the translation is applied.
As for the localize tag syntax in HTML, the area alias is combined with the key alias - so if you want to translate:
The stylesheet will be loaded and applied to our dashboard. Add images and HTML markup as required.
One caveat is that the package.manifest file is loaded into memory when Umbraco starts up. If you are adding a new stylesheet or JavaScript file you will need to start and stop your application for it to be loaded.
For version 9 and above
If the title doesn't change color, Smidge may be caching the CSS and JavaScript. To clear the cache and get it to load in the new JavaScript and CSS, you can configure the Runtime minification settings in the appsettings.json file. When you reload the page, you'll see the colorful title.
Once done, we should receive the 'Hello world' alert every time the dashboard is reloaded in the content section.
Step 5: Using Umbraco Angular Services and Directives
Umbraco has a fine selection of angular directives, resources, and services that you can use in your custom property editors and dashboards.
The details are in the Backoffice UI. For this example, it would be nice to welcome the editor by name. To achieve this we can use the userService to customize our dashboard welcome message and increase friendliness.
Inject the userService into our AngularJS controller:
Use the userService's promise based getCurrentUser() method to get the details of the currently logged-in user:
customwelcomedashboard.controller.js
angular.module("umbraco").controller("CustomWelcomeDashboardController",function ($scope, userService) {var vm =this;vm.UserName ="guest";var user =userService.getCurrentUser().then(function(user) {console.log(user);vm.UserName =user.name; });});
Notice you can use console.log() to write out to the browser console window what is being returned by the promise. This helps to debug, but also understand what properties are available to use.
Update the view to incorporate the current user's name in our Welcome Message:
An editor may find it useful to see a list of articles they have been editing along with a link to load and continue editing. This could be instead of having to remember and find the item again in the Umbraco Content Tree.
We can make use of Umbraco's Angular resource for retrieving audit log information.
We add logResource to the method and use the getPagedUserLog method to return a list of activities the current user has performed recently.
Add a property on our ViewModel to store the log information:
customwelcomedashboard.controller.js
vm.UserLogHistory = [];
Add to our WelcomeDashboard.html View some markup using angular's ng-repeat to display a list of these log entries:
WelcomeDashboard.html
<h2>We know what you edited last week...</h2><ul> <li ng-repeat="logEntry in vm.UserLogHistory.items">{{logEntry.nodeId}} - {{logEntry.logType}} - {{logEntry.timestamp | date:'medium'}}</li>
</ul>
Populate the array of entries using the logResource In our controller.
The getPagedUserLog method expects to receive a JSON object containing information to filter the log by:
customwelcomedashboard.controller.js
var userLogOptions = { pageSize:10, pageNumber:1, orderDirection:"Descending", sinceDate:newDate(2018,0,1)};
These options should retrieve the last ten activities for the current user in descending order since the start of 2018.
Pass the options into the getPagedUserLog like so:
It's nearly all we need but missing information about the item that was saved and published.
We can use the entityResource, another Umbraco Angular resource to enable us to retrieve more information about an entity given its id.
Inject the following code into our angular controller:
customwelcomedashboard.controller.js
angular.module("umbraco").controller("CustomWelcomeDashboardController", function ($scope, userService, logResource, entityResource) {
We need to loop through the log items from the logResource. Since this includes everything, we need to filter out activities we're not interested in eg, Macro Saves, or DocType Saves. Generally, we need the entry in the log to have a nodeId, a logType of 'save' and an entity type of Media or Content.
The entityResource has a getById method that accepts the ID of the item and the entity type to retrieve useful information about the entity. For example, its Name and Icon.
The getById method is supported on the following entity types:
Document (content)
Media
Member Type
Member Group
Media Type
Document Type
Member
Data Types
This needs to be defined before we loop through the entries.
Putting this together it will look like this:
customwelcomedashboard.controller.js
logResource.getPagedUserLog(userLogOptions).then(function (response) {console.log(response);vm.UserLogHistory = response;// define the entity types that we care about, in this case only content and mediavar supportedEntityTypes = ["Document","Media"];// define an empty array "nodes we know about"var nodesWeKnowAbout = [];// define an empty array "filtered log entries"var filteredLogEntries = [];// loop through the entries in the User Log Historyangular.forEach(response.items,function (item) {// if the item is already in our "nodes we know about" array, skip to the next log entryif (nodesWeKnowAbout.includes(item.nodeId)) {return; }// if the log entry is not for an entity type that we care about, skip to the next log entryif (!supportedEntityTypes.includes(item.entityType)) {return; }// if the user did not save or publish, skip to the next log entryif (item.logType !=="Save"&&item.logType !=="Publish") {return; }// if the item does not have a valid nodeId, skip to the next log entryif (item.nodeId <0) {return; }// now, push the item's nodeId to our "nodes we know about" arraynodesWeKnowAbout.push(item.nodeId);// use entityResource to retrieve details of the content/media itemvar ent =entityResource.getById(item.nodeId,item.entityType).then(function (ent) {console.log(ent);item.Content = ent; });// get the edit urlif (item.entityType ==="Document") {item.editUrl ="content/content/edit/"+item.nodeId; }if (item.entityType ==="Media") {item.editUrl ="media/media/edit/"+item.nodeId; }// push the item to our "filtered log entries" arrayfilteredLogEntries.push(item);// end of loop });// populate the view with our "filtered log entries" arrayvm.UserLogHistory.items = filteredLogEntries;// end of function });
Update the view to use the additional retrieved entity information:
WelcomeDashboard.html
<h2>We know what you edited last week...</h2><ulclass="unstyled"> <ling-repeat="logEntry in vm.UserLogHistory.items"> <i class="{{logEntry.Content.icon}}"></i> <a href="/Umbraco/#/{{logEntry.editUrl}}">{{logEntry.Content.name}} <span ng-if="logEntry.comment">- {{logEntry.comment}}</span></a> - <span class="text-muted">(Edited on: {{logEntry.timestamp | date:'medium'}})</span>
</li></ul>
We now have a list of recently saved content and media on our Custom Dashboard:
The URL /umbraco/#/content/content/edit/1234 is the path to open up a particular entity (with id 1234) ready for editing.
The logResource has a few bugs prior to version 8.1.4. If you are on a lower version this may not give the expected result.
Creating a shortcut for creating new content
A key user journeys an editor will make in the backoffice is to create content. If it is a person's job to create new blog entries, why not create a handy shortcut to help them achieve this common task?
We can add a shortcut to allow the users to add a blog post.
To do this we add the following code to our view:
WelcomeDashboard.html
<div> <aclass="btn btn-primary btn-large"href="/umbraco/#/content/content/edit/1065?doctype=BlogPost&create=true"> <iclass="icon-edit"></i> Create New Blog Post </a></div>
1065 is the ID of our blog section and blogPost is the alias of the type of document we want to create.
At this point we are done with the tutorial, your files should contain this:
CustomWelcomeDashboardController
customwelcomedashboard.controller.js
// Some angular.module("umbraco").controller("CustomWelcomeDashboardController", function ($scope, userService, logResource, entityResource) {
var vm =this;vm.UserName ="guest";var user =userService.getCurrentUser().then(function (user) {console.log(user);vm.UserLogHistory = [];vm.UserName =user.name; });var userLogOptions = { pageSize:10, pageNumber:1, orderDirection:"Descending", sinceDate:newDate(2018,0,1) };logResource.getPagedUserLog(userLogOptions).then(function (response) {console.log(response);vm.UserLogHistory = response;// define the entity types that we care about, in this case only content and mediavar supportedEntityTypes = ["Document","Media"];// define an empty array "nodes we know about"var nodesWeKnowAbout = [];// define an empty array "filtered log entries"var filteredLogEntries = [];// loop through the entries in the User Log Historyangular.forEach(response.items,function (item) {// if the item is already in our "nodes we know about" array, skip to the next log entryif (nodesWeKnowAbout.includes(item.nodeId)) {return; }// if the log entry is not for an entity type that we care about, skip to the next log entryif (!supportedEntityTypes.includes(item.entityType)) {return; }// if the user did not save or publish, skip to the next log entryif (item.logType !=="Save"&&item.logType !=="Publish") {return; }// if the item does not have a valid nodeId, skip to the next log entryif (item.nodeId <0) {return; }// now, push the item's nodeId to our "nodes we know about" arraynodesWeKnowAbout.push(item.nodeId);// use entityResource to retrieve details of the content/media itemvar ent =entityResource.getById(item.nodeId,item.entityType).then(function (ent) {console.log(ent);item.Content = ent; });// get the edit urlif (item.entityType ==="Document") {item.editUrl ="content/content/edit/"+item.nodeId; }if (item.entityType ==="Media") {item.editUrl ="media/media/edit/"+item.nodeId; }// push the item to our "filtered log entries" arrayfilteredLogEntries.push(item);// end of loop });// populate the view with our "filtered log entries" arrayvm.UserLogHistory.items = filteredLogEntries;// end of function });});
WelcomeDashboard.html
WelcomeDashboard.html
<divclass="welcome-dashboard"ng-controller="CustomWelcomeDashboardController as vm"> <h1><localizekey="welcomeDashboard_heading">Default heading</localize> {{vm.UserName}}</h1> <p><localizekey="welcomeDashboard_bodytext">Default bodytext</localize></p> <p><localizekey="welcomeDashboard_copyright">Default copyright</localize></p> <h2>We know what you edited last week...</h2> <ulclass="unstyled"> <ling-repeat="logEntry in vm.UserLogHistory.items"> <i class="{{logEntry.Content.icon}}"></i> <a href="/Umbraco/#/{{logEntry.editUrl}}">{{logEntry.Content.name}} <span ng-if="logEntry.comment">- {{logEntry.comment}}</span></a> - <span class="text-muted">(Edited on: {{logEntry.timestamp | date:'medium'}})</span>
</li> </ul> <div> <aclass="btn btn-primary btn-large"href="/umbraco/#/content/content/edit/1065?doctype=BlogPost&create=true"> <iclass="icon-edit"></i> Create New Blog Post </a> </div></div>
Extending with Custom External Data
You can create custom Angular services/resources to interact with your own serverside data using theUmbracoAuthorizedJsonController.
With all of the steps completed, you should have a functional dashboard that will let the logged-in user see the changes they made! Hopefully, this tutorial has given you some ideas on what is possible to do when creating a dashboard.
Remember to check out the Angular API docs for more info on all of the resources and services you can find for the backoffice.