Custom sections/trees/Pages/Dialogs in Umbraco v7
As promised as a follow up on the example project i posted I’ll also outline the different steps.
Step 1: Petapoco
The custom tree needs some data to work with and to do that we’ll use the db and since Umbraco is shipped with Petapoco we can take advantage of that. So first I’ll define the POCO:
1 [TableName("People")] 2 public class Person 3 { 4 public Person(){} 5 6 [PrimaryKeyColumn(AutoIncrement = true)] 7 public int Id { get; set; } 8 public string FirstName { get; set; } 9 public string LastName { get; set; } 10 11 public override string ToString() 12 { 13 return FirstName + " " + LastName; 14 } 15 }
Then make sure this table is created if it doesn’t exist
1 public class RegisterEvents : ApplicationEventHandler 2 { 3 protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) 4 { 5 var db = applicationContext.DatabaseContext.Database; 6 7 //Check if the DB table does NOT exist 8 if (!db.TableExist("People")) 9 { 10 //Create DB table - and set overwrite to false 11 db.CreateTable<Person>(false); 12 } 13 } 14 }
Step 2: The Api Controller
Next we’ll create the controller that will contain the different methods for crud operations with our data (again using Petapoco)
1 [PluginController("Example")] 2 public class PersonApiController : UmbracoAuthorizedJsonController 3 { 4 public IEnumerable<Person> GetAll() 5 { 6 7 var query = new Sql().Select("*").From("people"); 8 return DatabaseContext.Database.Fetch<Person>(query); 9 } 10 11 public Person GetById(int id) 12 { 13 14 var query = new Sql().Select("*").From("people").Where<Person>(x => x.Id == id); 15 return DatabaseContext.Database.Fetch<Person>(query).FirstOrDefault(); 16 17 } 18 19 public Person PostSave(Person person) 20 { 21 if (person.Id > 0) 22 DatabaseContext.Database.Update(person); 23 else 24 DatabaseContext.Database.Save(person); 25 26 return person; 27 } 28 29 public int DeleteById(int id) 30 { 31 return DatabaseContext.Database.Delete<Person>(id); 32 } 33 34 }
Step 3: The section
We’ll place our new tree in a new section so let’s create a new section
1 [Application("example", "Example","icon-people", 15)] 2 public class Section: IApplication {}
Basicly you need a class and decorate that with the Application attribute, that needs an application alias, name, icon and sort order.
Make sure to give your user access to this section otherwise it won’t show up yet.
Step 4: The tree
Next we’ll create the tree that will list data from our custom People table
1 [Tree("example", "peopleTree", "People")] 2 [PluginController("Example")] 3 public class PeopleTreeController: TreeController 4 { 5 6 7 protected override Umbraco.Web.Models.Trees.TreeNodeCollection GetTreeNodes(string id, System.Net.Http.Formatting.FormDataCollection queryStrings) 8 { 9 //check if we’re rendering the root node’s children 10 if (id == Constants.System.Root.ToInvariantString()) 11 { 12 var ctrl = new PersonApiController(); 13 var nodes = new TreeNodeCollection(); 14 15 foreach (var person in ctrl.GetAll()) 16 { 17 var node = CreateTreeNode( 18 person.Id.ToString(), 19 "-1", 20 queryStrings, 21 person.ToString(), 22 "icon-user", 23 false); 24 25 nodes.Add(node); 26 27 } 28 return nodes; 29 } 30 31 //this tree doesn’t suport rendering more than 1 level 32 throw new NotSupportedException(); 33 } 34 35 protected override Umbraco.Web.Models.Trees.MenuItemCollection GetMenuForNode(string id, System.Net.Http.Formatting.FormDataCollection queryStrings) 36 { 37 //not worying about menu atm 38 var menu = new MenuItemCollection(); 39 return menu; 40 } 41 }
So we return a TreeNodeCollection and create a TreeNode for each item in our data source (People table). So when we deploy this we should have a tree listing the data in the table now.
The menu is emtpy atm but we’ll get to that later on.
Step 5: AngularJS resource
Before we start with the edit page and the dialogs we’ll create an AngularJS resource that interacts with the APIController and that we will be able to inject in our future AngularJS controllers
1 angular.module("umbraco.resources") 2 .factory("personResource", function ($http) { 3 return { 4 getById: function (id) { 5 return $http.get("backoffice/Example/PersonApi/GetById?id=" + id); 6 }, 7 save: function (person) { 8 return $http.post("backoffice/Example/PersonApi/PostSave", angular.toJson(person)); 9 }, 10 deleteById: function(id) { 11 return $http.delete("backoffice/Example/PersonApi/DeleteById?id=" + id); 12 } 13 }; 14 });
Step 6: Package manifest
And of course make sure Umbraco knows about this new file by adding a package manifest
1 { 2 javascript: [ 3 ‘~/App_Plugins/Example/person.resource.js‘ 4 ] 5 }
Step 7: The edit page controller
The AngularJS controller used by the edit page, will fetch a person by it’s id using the resource and will also save a person
1 angular.module("umbraco").controller("People.PersonEditController", 2 function ($scope, $routeParams, personResource, notificationsService) { 3 4 $scope.loaded = false; 5 6 if ($routeParams.id == -1) { 7 $scope.person = {}; 8 $scope.loaded = true; 9 } 10 else{ 11 //get a person id -> service 12 personResource.getById($routeParams.id).then(function (response) { 13 $scope.person = response.data; 14 $scope.loaded = true; 15 }); 16 } 17 18 $scope.save = function (person) { 19 personResource.save(person).then(function (response) { 20 $scope.person = response.data; 21 22 notificationsService.success("Success", person.firstName + " " + person.lastName + " has been saved"); 23 }); 24 }; 25 });
of course also make sure to update your manifest file to include this js file
Step 8: the edit page view
The actual edit page, will show an input for the firstName and lastName
1 <form name="personForm" 2 ng-controller="People.PersonEditController" 3 ng-show="loaded" 4 ng-submit="save(person)" 5 val-form-manager> 6 <umb-panel> 7 <umb-header> 8 9 <div class="span7"> 10 <umb-content-name placeholder="@placeholders_entername" 11 ng-model="person.firstName" /> 12 </div> 13 14 <div class="span5"> 15 <div class="btn-toolbar pull-right umb-btn-toolbar"> 16 <umb-options-menu ng-show="currentNode" 17 current-node="currentNode" 18 current-section="{{currentSection}}"> 19 </umb-options-menu> 20 </div> 21 </div> 22 </umb-header> 23 24 <div class="umb-panel-body umb-scrollable row-fluid"> 25 <div class="tab-content form-horizontal" style="padding-bottom: 90px"> 26 <div class="umb-pane"> 27 28 29 30 <umb-control-group label="First name" description="Person’s first name’"> 31 <input type="text" class="umb-editor umb-textstring" ng-model="person.firstName" required /> 32 </umb-control-group> 33 34 <umb-control-group label="Last name" description="Person’s last name’"> 35 <input type="text" class="umb-editor umb-textstring" ng-model="person.lastName" required /> 36 </umb-control-group> 37 38 39 <div class="umb-tab-buttons" detect-fold> 40 <div class="btn-group"> 41 <button type="submit" data-hotkey="ctrl+s" class="btn btn-success"> 42 <localize key="buttons_save">Save</localize> 43 </button> 44 </div> 45 </div> 46 47 </div> 48 </div> 49 </div> 50 51 </umb-panel> 52 </form>
Step 9: Update the treecontroller with menu items
So we also want some actions on our tree, a refresh and create on the root and a delete on the items
1 protected override Umbraco.Web.Models.Trees.MenuItemCollection GetMenuForNode(string id, System.Net.Http.Formatting.FormDataCollection queryStrings) 2 { 3 var menu = new MenuItemCollection(); 4 5 if (id == Constants.System.Root.ToInvariantString()) 6 { 7 // root actions 8 menu.Items.Add<CreateChildEntity, ActionNew>(ui.Text("actions", ActionNew.Instance.Alias)); 9 menu.Items.Add<RefreshNode, ActionRefresh>(ui.Text("actions", ActionRefresh.Instance.Alias), true); 10 return menu; 11 } 12 else 13 { 14 15 menu.Items.Add< ActionDelete>(ui.Text("actions", ActionDelete.Instance.Alias)); 16 17 } 18 return menu; 19 }
Step 10: The delete controller
For create we don’t need a controller and view since it uses a convention but for the delete one we do.
1 angular.module("umbraco") 2 .controller("People.PersonDeleteController", 3 function ($scope, personResource, navigationService) { 4 $scope.delete = function (id) { 5 personResource.deleteById(id).then(function () { 6 navigationService.hideNavigation(); 7 8 }); 9 10 }; 11 $scope.cancelDelete = function () { 12 navigationService.hideNavigation(); 13 }; 14 });
Step 11: The delete view
1 <div class="umb-pane" ng-controller="People.PersonDeleteController"> 2 <p> 3 Are you sure you want to delete {{currentNode.name}} ? 4 </p> 5 6 <div> 7 <div class="umb-pane btn-toolbar umb-btn-toolbar"> 8 <div class="control-group umb-control-group"> 9 <a href="" class="btn btn-link" ng-click="cancelDelete()"><localize key="general_cancel">Cancel</localize></a> 10 <a href="" class="btn btn-primary" ng-click="delete(currentNode.id)"><localize key="general_ok">OK</localize></a> 11 </div> 12 </div> 13 </div> 14 </div>
And those are all the different steps invoved in creating your own section/tree/page/dialog in Umbraco v7. The complete project is available on github.
Great post!
Thank you!
Sören
Nice walkthrough, thanks!
It’s openned a find source window. What is ProfiledDbConnection.cs?
@Rafael, something in the Umbraco core, just hit continue and it should be fine
I noticed there is a dashboard file (peopledashboard.html) just under the “backoffice/” folder. I followed this blog post and your code on GitHub, but I think I’ve missed how to tell Umbraco that this HTML file is the dashboard for the section. I get a blank page with [My Section Name] at the top.
Hello,
I was following your post, which btw helped me a lot but I still got kinda stuck. I renamed some of your controls, customizing them for my needs. Among other things I had to change the edit view, so that it matches my model. But this view seems to work only if I’m inserting first and last name like it was in your case. For example I changed the first name to email, and now when I want to insert something the field keeps resetting. Like it can’t bind to a model or something.
Any suggestions, ideas?
Greetings
Quick question, as I am avoiding using $scope and use controllerAs syntax style, is it possible to hookup the save method, without injecting $scope into the angular controller?
for anyone looking at this and wondering what happened to the dashboard, what you have to do as far as i understand is to add it in the Dashboard.config in the config folder
and could look like this
custom
/app_plugins/custom/defaultview.html
Just note that for this to work, your has to be the same as your applications alias in all lowercase so in this case it would be example
Thanks for making my job much easier. Great post.
Hi,
Is this article still applicable for the current release of Umbraco?
Thanks
@Bilal yup it should be but there might be some more directives available in current versions
This whole process has been made a lot easier with UI-O-Matic, check it out http://www.nibble.be/?p=528
What folders do these files all go?