The previous post (Part II) showed how to populate the content tree of a custom Umbraco backoffice section. This one presents a way for content managers to quickly handle each post pending approval, which means we’re going to create our own AngularJS controller and view.
AngularJS is a MVC javascript framework mainly mantained by Google which primary goal is to aid in the development of single-page applications. It’s use on the Umbraco backoffice was the most notable change when Umbraco 7 was first released.
When the content manager selects one node pending approval we want him to be able to take three quick actions:
- get some information about that node;
- quickly access it in the content section;
- quickly publish it.
New Angular controllers and views must be placed in a specific Umbraco directory, following a specific directory structure:
- the main directory must reside at ~\App_Plugins\ApproveIt – the plugin name
- the views and controllers must reside at App_Plugins\ApproveIt\backoffice\approvalTree\ – the tree controller name
First things first:
Create a package manifest so that Umbraco knows there are new javascript files available, and place it in the root of App_Plugins\ApproveIt directory.
{ javascript: [ '~/App_Plugins/ApproveIt/backoffice/approvalTree/edit.controller.js', '~/App_Plugins/ApproveIt/approval.resource.js' ] }
Then we must create the javascript files referenced by the manifest and the respective angular view.
angular.module("umbraco.resources") .factory("approvalResource", function ($http) { return { getById: function (id) { return $http.get("backoffice/ApproveIt/ApprovalApi/GetById?id=" + id); }, publish: function (id) { return $http.post("backoffice/ApproveIt/ApprovalApi/PostPublish?id=" + id); } }; });
This javascript file provides a new Angular Service that can then be used by Angular controllers. This service is no more than a facade for the ApprovalApiController created on Part II. The methods it connects to are created further on.
angular.module("umbraco").controller("Approval.ApprovalEditController", function ($scope, $routeParams, approvalResource, notificationsService, navigationService) { $scope.loaded = false; if ($routeParams.id == -1) { $scope.node = {}; $scope.loaded = true; } else { approvalResource.getById($routeParams.id).then(function (response) { $scope.node = response.data; $scope.loaded = true; }); } $scope.publish = function (node) { approvalResource.publish(node.Id).then(function (response) { $scope.node = response.data; $scope.contentForm.$dirty = false; navigationService.syncTree({ tree: 'approvalTree', path: [-1, -1], forceReload: true }); notificationsService.success("Success", node.Name + " has been published"); }); }; });
This Angular controller is called when the Approve It section is being used:
- If no node is selected (for instance, when we access this section for the first time) we do nothing;
- If a node is selected ($routeParams.id != -1) we request the node details through our approval.resource service facade
- Finally, the quick publish method is also implemented here. This methods calls the ApprovalResource service which then invokes the publish method of the ApprovalApiController.
<form name="contentForm" ng-controller="Approval.ApprovalEditController" ng-show="loaded" ng-submit="publish(node)" val-form-manager> <umb-panel> <umb-header> <div class="span7"> <umb-content-name placeholder="@placeholders_entername" ng-model="node.Name" /> </div> <div class="span5"> <div class="btn-toolbar pull-right umb-btn-toolbar"> <umb-options-menu ng-show="currentNode" current-node="currentNode" current-section="{{currentSection}}"> </umb-options-menu> </div> </div> </umb-header> <div class="umb-panel-body umb-scrollable row-fluid"> <div class="tab-content form-horizontal" style="padding-bottom: 90px"> <div class="umb-pane"> <umb-control-group label="Name" description="Content's name"> <input type="text" class="umb-editor umb-textstring" ng-model="node.Name" required /> </umb-control-group> <div class="umb-tab-buttons" detect-fold> <div class="btn-group"> <a class="btn" ng-href="#/content/content/edit/{{node.Id}}"> Show </a> </div> <div class="btn-group"> <button type="submit" data-hotkey="ctrl+s" class="btn btn-success"> Publish </button> </div> </div> </div> </div> </div> </umb-panel> </form>
The node view presents the waiting approval page name and two buttons: one to quickly navigate to it’s details and another to publish it.
Finally, we need to update the ApprovalApiController.cs to include two new methods:
public IContent GetById(int id) { IContent content = ApplicationContext.Services.ContentService.GetById(id); return content; } public IContent PostPublish(int id) { IContent node = ApplicationContext.Services.ContentService.GetById(id); ApplicationContext.Services.ContentService.Publish(node); return node; }
- GetById: returns the node details to the angular view
- PostPublish: publishes the node being viewed
By following these steps we are able to create a complete new Umbraco backoffice section that provides us with a way to manage every site content waiting approval for publishing:
If you want to see how this plugin as been evolving, you can check its source code in github: https://github.com/ViGiLnT/ApproveIt. You can also download the plugin ready to be installed at Umbraco’s extensions repository: https://our.umbraco.org/projects/backoffice-extensions/approve-it/. Other Umbraco 7 tutorials and information can be found here: http://umbraco.github.io/Belle.
Some truly select blog posts on this web site , saved to fav. bdfdeeeddeegdced
Thanks! I’ll try to update this more often.
Excellent work on this series Andre! Why doesn’t Umbraco have this in the official documentation? Without tutorials like this, the Umbraco documentation is truly incomplete!!
Thanks Ibrahim! The information regarding sections is a bit scattered all over the place. It is getting better though: https://github.com/umbraco/UmbracoDocs.
Hi Andre,
Great post really helped me see a new/better approach to a problem I am having. Could you help me with the rest of the solution? What I need is very basic I want a section that has a “Refresh Video Data Button” and when clicked this button will actually re save all nodes of a certain type. Could you help me with this, as i get a little lost with the views and controllers in angular.
Thanks,
James
Hi James. Thanks for the feedback!
About your question, I made another umbraco package which also is in github: https://github.com/ViGiLnT/donutcachecleaner. This package only has 1 button aswell, only the logic is different from what you want. There are a number of ways to get all nodes of a single document type, you can do it with Examine (faster search), or you can do it with IPublishedContent (easier). From the moment you have the nodes you can save them with ContentService.
Hi i did every thing right and every thing work fine but the last thing where you click the content i got this error
Error: Argument ‘ApprovalEditController’ is not a function, got undefined
Hi Ali,
Sorry for the late reponse but I was on vacation.
That kind of error usually happens for one of these reasons:
– Forgot to add the javascript file to the package.manifest
– Mistyped the name of the controller on the html view or the javascript controller
– Cache! Try setting the debug to true in the compilation section of the web.config and/or restarting the IIS
Great blog,
thank you
Thanks Ronen! Glad you enjoyed it!
is it possible to create a factory of services and re-use it to different plugins (not just one)?
Hi Denise. I’m sure you can. What’s the use case?