DEV Community

Jesper Mayntzhusen
Jesper Mayntzhusen

Posted on

Umbraco backoffice listview + infinite editing - part 3

Hi my name is Jesper 👋

More than a year ago I started a series of blogposts I never finished, now I've been promised cake if I do - so here is the third and final part to the guide about creating a custom listview with infinite editing in Umbraco 8!

In part 2 we left off with a fully functioning with pagination - if you forgot where we left off, don't worry.. I pretty much had to start from scratch as well 😊

Here is the starting point:

In ~/App_Plugins/ListViews:

package.manifest
{
  "dashboards": [
    {
      "alias": "myCustomDashboard",
      "view": "/App_Plugins/ListViews/dashboard.html",
      "sections": [ "content" ],
      "weight": 15
    }
  ],
  "javascript": [
    "~/App_Plugins/ListViews/dashboard.controller.js"
  ]
}

dashboard.html
<div ng-controller="MyDashboardController as vm">
    <h1>Upcoming publish/unpublish events!</h1>

    <div class="umb-table" ng-show="!vm.loading && vm.weHaveRecords">
        <div class="umb-table-head">
            <div class="umb-table-row">
                <!-- We leave this first on empty in the header & show icons in the body -->
                <div class="umb-table-cell"></div>
                <div class="umb-table-cell umb-table__name">Name</div>
                <div class="umb-table-cell">Publish time</div>
                <div class="umb-table-cell">Culture</div>
            </div>
        </div>

        <div class="umb-table-body">
            <div class="umb-table-row -selectable" ng-repeat="item in vm.records">
                <div class="umb-table-cell">
                    <i class="umb-table-body__icon umb-table-body__fileicon icon-document" ng-if="item.ScheduleInfo.FullSchedule[0].Action == 0"></i>
                    <i class="umb-table-body__icon umb-table-body__fileicon icon-document-dashed-line color-grey" ng-if="item.ScheduleInfo.FullSchedule[0].Action == 1"></i>
                </div>
                <div class="umb-table-cell umb-table__name">{{item.Content.Name}}</div>
                <div class="umb-table-cell">{{item.ScheduleInfo.FullSchedule[0].Date | date : medium}}</div>
                <div class="umb-table-cell">{{item.ScheduleInfo.FullSchedule[0].Culture}}</div>
            </div>
        </div>
    </div>

    <div class="flex justify-center" ng-show="!vm.loading && vm.weHaveRecords">
        <umb-pagination page-number="vm.pagination.pageNumber"
                        total-pages="vm.pagination.totalPages"
                        on-next="vm.nextPage"
                        on-prev="vm.prevPage"
                        on-change="vm.changePage"
                        on-go-to-page="vm.goToPage">
        </umb-pagination>
    </div>

    <umb-box ng-if="!vm.weHaveRecords && !vm.loading">
        <umb-box-content>
            No records at this point!
        </umb-box-content>
    </umb-box>

    <umb-load-indicator ng-if="vm.loading">
    </umb-load-indicator>
</div>

dashboard.controller.js
(function () {
    "use strict";

    function DashboardController($http) {

        var vm = this;

        function init() {
            getContentForRelease(0, vm.recordsPerPage);
        }

        vm.nextPage = nextPage;
        vm.prevPage = prevPage;
        vm.changePage = changePage;
        vm.goToPage = goToPage;
        vm.recordsPerPage = 2;

        function nextPage(pageNumber) {
            vm.loading = true;
            var offset = (pageNumber - 1) * vm.recordsPerPage;
            getContentForRelease(offset, vm.recordsPerPage)
        }

        function prevPage(pageNumber) {
            vm.loading = true;
            var offset = (pageNumber - 1) * vm.recordsPerPage;
            getContentForRelease(offset, vm.recordsPerPage)
        }

        function changePage(pageNumber) {
            vm.loading = true;
            var offset = (pageNumber - 1) * vm.recordsPerPage;
            getContentForRelease(offset, vm.recordsPerPage)
        }

        function goToPage(pageNumber) {
            vm.loading = true;
            var offset = (pageNumber - 1) * vm.recordsPerPage;
            getContentForRelease(offset, vm.recordsPerPage)
        }

        function getContentForRelease(offset, limit) {
            vm.loading = true;
            $http({
                method: 'GET',
                url: '/Umbraco/backoffice/api/Dashboard/GetContentForReleaseNextWeek?offset=' + offset + '&limit=' + limit,
                headers: {
                    'Content-Type': 'application/json'
                }
            }).then(function (response) {
                console.log(response); // logging the response so we know what to do next!

                if (response.data.ScheduledNodes.length > 0) {
                    vm.weHaveRecords = true;
                    vm.records = response.data.ScheduledNodes;
                } else {
                    vm.weHaveRecords = false;
                }

                var totalPages = Math.ceil(response.data.TotalRecords / limit);

                vm.pagination = {
                    pageNumber: (offset / vm.recordsPerPage) + 1,
                    totalPages: totalPages
                };

                vm.loading = false;
            });
        }

        init();
    }

    angular.module("umbraco").controller("MyDashboardController", DashboardController);

})();

lang/en-US.xml
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<language>
    <area alias="dashboardTabs">
        <key alias="myCustomDashboard">Custom Dashboard</key>
    </area>
</language>

In your class library:

~/Controllers/DashboardController.cs
using ListViews.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Web.WebApi;

namespace ListViews.Controllers
{
    public class DashboardController : UmbracoAuthorizedApiController
    {
        private readonly IContentService _contentService;

        public DashboardController(IContentService contentService)
        {
            _contentService = contentService;
        }

        public PagedContent GetContentForReleaseNextWeek(int offset = 0, int limit = 10)
        {
            var endDate = DateTime.Now.AddDays(7);

            var response = _contentService.GetContentForRelease(endDate);

            var contentWithScheduledInfo = new List<ContentWithScheduledInfo>();

            foreach (IContent item in response)
            {
                contentWithScheduledInfo.Add(new ContentWithScheduledInfo
                {
                    Content = item,
                    ScheduleInfo = item.ContentSchedule
                });
            }

            var pagedContent = new PagedContent
            {
                // filter the result set with offset and limit
                ScheduledNodes = contentWithScheduledInfo.Skip(offset).Take(limit).ToList(),
                TotalRecords = contentWithScheduledInfo.Count
            };

            return pagedContent;
        }
    }

}

~/Models/ContentWithScheduledInfo.cs
using Umbraco.Core.Models;

namespace ListViews.Models
{
    public class ContentWithScheduledInfo
    {
        public IContent Content { get; set; }
        public ContentScheduleCollection ScheduleInfo { get; set; }
    }
}

~/Models/PagedContent.cs
using System.Collections.Generic;

namespace ListViews.Models
{
    public class PagedContent
    {
        public List<ContentWithScheduledInfo> ScheduledNodes { get; set; }
        public int TotalRecords { get; set; }
    }
}

Step 6 - Hooking up infinite editing

It's actually surprisingly simple to set up infinite editing - here is what we need to do!

Firstly, we add a click event on each row in the listview, so in the dashboard html file we change out the first line within the umb-table-body, from:

<div class="umb-table-row -selectable" ng-repeat="item in vm.records">
Enter fullscreen mode Exit fullscreen mode

to:

<div class="umb-table-row -selectable" ng-repeat="item in vm.records" ng-click="vm.open(item)">
Enter fullscreen mode Exit fullscreen mode

in other words - we add the ng-click event listener. Note how we pass in the item, which is the model we get from our API controller.

Next we go to the dashboard controller, where we need to set up this new vm.open function:

function DashboardController($http, editorService) {

        var vm = this;

        function init() {
            getContentForRelease(0, vm.recordsPerPage);
        }

        vm.nextPage = nextPage;
        vm.prevPage = prevPage;
        vm.changePage = changePage;
        vm.goToPage = goToPage;
        vm.recordsPerPage = 2;
        vm.open = open;

        function open(item) {
            // do stuff
        }
Enter fullscreen mode Exit fullscreen mode

So we've added the following:

  • New open function
  • Added the editorService to be injected in the constructor (we will need this in a second)
  • Assigned vm.open to the new open function

Next let's see what we need the open function to do:

function open(item) {
    var options = {
        id: [node id here],
        submit: function (model) {
            editorService.close();
        },
        close: function () {
            editorService.close();
        }
    };
    editorService.contentEditor(options);
}
Enter fullscreen mode Exit fullscreen mode

That's how easy it is! We set up some config for the editor in an options object, we need to give it the id of the content node we want to open in the infinite editor, then we need to tell it to close the editor again when we submit or close the window.

Note: Curious about other options for the contentEditor? Check them out here!

Final bit is to get the ID to pass in - luckily our item model has the IContent model wrapped in it:
Image description

So we can just set it like this:

id: item.Content.Id,
Enter fullscreen mode Exit fullscreen mode

Final result - and ensuring that it actually saves the values:
Image description

Note: While testing and writing this post I found an issue with nodes having a listview, so if that isn't really working as expected. See the issue here.

Outro

Thanks for reading so far!

If you like this post, have any feedback, suggestions, improvements to my hacky code or anything else please let me know on Twitter - @jespermayn

Top comments (0)