Umbraco backoffice listview + infinite editing - part 3
Tags: umbraco, dotnet
Published: 2022-02-26 19:47:42

_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:

{% details _package.manifest_ %} 
 
```javascript
{
  "dashboards": [
    {
      "alias": "myCustomDashboard",
      "view": "/App_Plugins/ListViews/dashboard.html",
      "sections": [ "content" ],
      "weight": 15
    }
  ],
  "javascript": [
    "~/App_Plugins/ListViews/dashboard.controller.js"
  ]
}
```

{% enddetails %}

{% details _dashboard.html_ %} 
 
```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>
```

{% enddetails %}

{% details _dashboard.controller.js_ %} 
 
```javascript
(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);

})();
```

{% enddetails %}

{% details _lang/en-US.xml_ %} 
 
```xml
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<language>
	<area alias="dashboardTabs">
		<key alias="myCustomDashboard">Custom Dashboard</key>
	</area>
</language>
```

{% enddetails %}

In your class library:

{% details _~/Controllers/DashboardController.cs_ %} 
 
```csharp
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;
        }
    }

}
```

{% enddetails %}

{% details _~/Models/ContentWithScheduledInfo.cs_ %} 
 
```csharp
using Umbraco.Core.Models;

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

{% enddetails %}

{% details _~/Models/PagedContent.cs_ %} 
 
```csharp
using System.Collections.Generic;

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

{% enddetails %}

## 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:

```html
<div class="umb-table-row -selectable" ng-repeat="item in vm.records">
```
to:
```html
<div class="umb-table-row -selectable" ng-repeat="item in vm.records" ng-click="vm.open(item)">
```
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:

```js
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
        }
```

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:

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

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!](https://github.com/umbraco/Umbraco-CMS/blob/release-8.18.0/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js#L347-L355)

Final bit is to get the ID to pass in - luckily our item model has the IContent model wrapped in it:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pee39i84arndew9ezey7.png)
 
So we can just set it like this:
```js
id: item.Content.Id,
```

Final result - and ensuring that it actually saves the values:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ayv4op8ltxs7547hcnfx.gif)
 
>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](https://github.com/umbraco/Umbraco-CMS/issues/12064).

## 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