function wfContentListController()

in public/components/content-list/content-list.js [95:519]


function wfContentListController($rootScope, $scope, $anchorScroll, statuses, priorities, sections, wfContentService, wfContentPollingService, wfContentItemParser, wfPresenceService, wfColumnService, wfPreferencesService, wfFiltersService) {

    $scope.presenceIsActive = false;
    $rootScope.$on("presence.connection.error", () => $scope.presenceIsActive = false);
    $rootScope.$on("presence.connection.retry", () => $scope.presenceIsActive = false);
    $rootScope.$on("presence.connection.open",  () => $scope.presenceIsActive = true);

    /*jshint validthis:true */

    $scope.resetFilters = function () {
        wfFiltersService.clearAll(false);
    };

    $scope.getSortDirection = columnName => {
      return columnName === $scope.sortColumn ? $scope.sortDirection[0] : undefined
    }

    $scope.sortColumn = undefined;
    $scope.sortFields = [];
    $scope.sortDirection = undefined;
    const defaultSortColName = 'priority';
    // If we'd prefer to allow people to remove the sort state entirely,
    // this list can be changed to ['desc', 'asc', undefined]
    const sortStates = ['asc', 'desc'];

    $scope.toggleSortState = (colName, sortFields) => {
      const column = $scope.columns.find(col => col.name === colName);

      if (!column) {
        return;
      }

      //If same column invert score
      if(colName === $scope.sortColumn){
        $scope.sortDirection = $scope.sortDirection.map(sortDirection => {
            return sortStates[(sortStates.indexOf(sortDirection) + 1) % sortStates.length];
        });
      }
      //If new column and default sort order array matches length of sort fields
      else if(column.defaultSortOrder && column.defaultSortOrder.length === sortFields.length){
        $scope.sortDirection = column.defaultSortOrder;
      }
      //Else create default sort
      else {
        $scope.sortDirection = sortFields.map(() => sortStates[0])
      }

      $scope.sortColumn = $scope.sortDirection ? colName : undefined;
      $scope.sortFields = $scope.sortDirection ? sortFields : undefined;


      applyNewContentState();
    };

    $scope.animationsEnabled = true;

    $rootScope.$on('getContent', () => {
        $scope.animationsEnabled = false;
    });

    $rootScope.$on('content.rendered', () => {
        $scope.animationsEnabled = true;
    });

    wfColumnService.getColumns().then((data) => {
        $scope.columns = data;

        // Apply sort defaults
        const column = $scope.columns.find(col => getSortField(col) === defaultSortColName);

        if (column) {
          $scope.toggleSortState(getSortField(column))
        }
    });

    $scope.getColumnTitle = function(col) {
        if (col.name !== 'presence') {
            return (col.title.length > 0) ? col.title : undefined;
        } else {
            return $scope.presenceIsActive ? col.title : col.unavailableTitle;
        }
    };

    wfColumnService.getContentItemTemplate().then((template) => {

        $rootScope.contentItemTemplate = template;
    });

    $scope.showColumnMenu = false;

    /**
     * If the user has not clicked in to the column configurator then show
     * the new labels against the configurator button and the new field.
     */
    (function displayNewIndicatorsIfNotSeenBefore () {

        $scope.showColumnMenuNewIndicator = false;
        wfPreferencesService.getPreference('ofwFieldNotYetSeen').then((data) => {

            $scope.showColumnMenuNewIndicator = data;
        }, () => {

            $scope.showColumnMenuNewIndicator = true;

            $scope.$watch('showColumnMenu', (newValue, oldValue) => {

                if (newValue !== oldValue) {
                    wfPreferencesService.setPreference('ofwFieldNotYetSeen', false);
                }
            }, false);
        });
    })();

    $scope.colChange = function () {
        wfColumnService.setColumns($scope.columns).then(() => {
            if (confirm('Configuring columns requires a refresh, reload the page?')) {
                window.location.reload();
            }
        });
    };
    $scope.colChangeSelect = function () {
        $scope.columnsEdited = true;
    };

    (function handleHeadlineVisibility (controller) {

        controller.showHeadline = false;

        wfPreferencesService.getPreference('showHeadline').then((data) => {
            controller.showHeadline = data;
            setUpWatch();
        }, setUpWatch);

        function setUpWatch () {
            $scope.$watch('contentList.showHeadline', (newValue) => {
                wfPreferencesService.setPreference('showHeadline', newValue);
            }, true);
        }
    })(this);

    $scope.toggleHeadlineDisplay = () => {
        this.showHeadline = ! this.showHeadline;
    }
    $scope.getHeadlineDisplay = () => this.showHeadline;

    this.newItem = function () {
        $scope.$emit('stub:create');
    };

    this.importItem = function () {
        $scope.$emit('content:import');
    };

    this.editItem = (contentItem) => {
        var prefix = 'content';
        $scope.$emit(prefix + ':edit', contentItem.item);
    };

    this.sections = sections;

    this.priorities = priorities;

    // Watch composer contentIds for Presence
    $scope.$watch('contentIds', (newIds, oldIds) => {
        if (newIds !== oldIds) {  // guards against initial render when newIds === oldIds === undefined
            wfPresenceService.subscribe(newIds);
        }
    }, true);


    // Infinite Scrolling functionality
    // See infinite-scroll http://sroze.github.io/ngInfiniteScroll/documentation.html
    // =============================================================================== //

    var INFINITE_SCROLL_STARTING_ITEMS = 50, // TODO Dynamically calculate optimal value based on container height
        INFINITE_SCROLL_ITEM_LOAD_INCREMENT = 20,
        INFINITE_SCROLL_LOAD_MORE_TRIGGER = 1.5; // Multiples of container height

    $scope.contentItemsLoadingThreshold = INFINITE_SCROLL_LOAD_MORE_TRIGGER;
    $scope.contentItemsDisplayed = INFINITE_SCROLL_STARTING_ITEMS;
    $scope.contentItemLoadingIncrement = INFINITE_SCROLL_ITEM_LOAD_INCREMENT;

    $scope.$on('getContent', () => {
        $scope.contentItemsDisplayed = INFINITE_SCROLL_STARTING_ITEMS; // reset when filters are applied
        $scope.infiniteScrollDisabled = false;
    });

    /**
     * Sort and trim the content to length over the status groups.
     * ie: If 100 items are requested you might get 20 from Stubs, 30 from Writers and 50 from Desk making 100 in total
     * @param content The content grouped by status
     * @param trimTo Amount of items to return
     * @returns {*}
     */
    $scope.getSortedAndTrimmedContent = (content, trimTo) => {
      const { content: newContent } = content.reduce(({ itemsRemaining, content }, group) => {
        // Avoid hydrating and sorting if we're not rendering any of these items
        if (!itemsRemaining) {
          return {
            itemsRemaining,
            content: content.concat({...group, items: []})
          }
        }

        const hydratedItems = (group.items || []).map(wfContentItemParser.parse);
        const sortedGroupItems = this.sortGroupItems(hydratedItems || [], $scope.sortFields, $scope.sortDirection);
        const newItemsRemaining = sortedGroupItems.length < itemsRemaining
          ? itemsRemaining - sortedGroupItems.length
          : 0;

        const newGroup = {
          ...group,
          items: sortedGroupItems.slice(0, itemsRemaining)
        };

        return {
          itemsRemaining: newItemsRemaining,
          content: content.concat(newGroup)
        }
      }, {
        itemsRemaining: trimTo,
        content: []
      });

      return newContent;
    }

    /**
     * Method called when the bottom of the list gets within
     * INFINITE_SCROLL_LOAD_MORE_TRIGGER * container.height pixels of the bottom of the container
     *
     * Increments the amount of items to display and the re-trims the originalContent object to length
     */
    $scope.moreContent = function () {
        $scope.infiniteScrollDisabled = true;
        $scope.contentItemsDisplayed += $scope.contentItemLoadingIncrement;
        $scope.animationsEnabled = false;
        applyNewContentState();
        $scope.infiniteScrollDisabled = false;
    };

    function applyNewContentState() {
        if ($scope.contentItemsDisplayed >= $scope.totalContentItems) {
            $scope.displayingEverything = true;
            $scope.infiniteScrollDisabled = true;
        } else {
            $scope.displayingEverything = false;
        }

        if ($scope.originalContent) {
          $scope.content = $scope.getSortedAndTrimmedContent($scope.originalContent, $scope.contentItemsDisplayed, $scope.totalContentItems);
        }
    }

    function parseContentForIds(content) {
        const contentItems = _.flatten(content.map((c) => c.items));
        const filteredItems = contentItems.filter((item) => item !== undefined);

        const contentItemIds = filteredItems.map(item => {
            if (item.composerId) {
                return item.composerId;
            }

            if (item.contentType && item.editorId) {
                return `${item.contentType}-${item.editorId}`;
            }
        });

        return contentItemIds.filter(Boolean);
    }

    // =============================================================================== //

    this.render = (response) => {
        var data = response.data;

        var grouped = data.content;

        // fixes https://docs.angularjs.org/error/$rootScope/inprog http://www.pro-tekconsulting.com/blog/solution-to-error-digest-already-in-progress-in-angularjs-2/
        if(!$scope.$$phase) {
            $scope.$apply(() => {
                $scope.totalContentItems = data.count.total;

                $scope.originalContent = statuses.map((status) => {
                    // TODO: status is currently stored as presentation text, eg: "Writers"
                    //       should be stored as an enum and transformed to presentation text
                    //       here in the front-end

                    return {
                        name: status.toLowerCase(),
                        title: status,
                        count: data.count[status],
                        items: grouped[status]
                    };

                });

                applyNewContentState();

                (function setUpPresenceContentIds () {
                    $scope.contentIds = parseContentForIds($scope.content);
                })();

                $scope.$emit('content.render', {
                    content: $scope.content
                });

            });
        }

        $scope.$emit('content.rendered');

    };

    this.renderError = (err) => {

        $scope.$apply(() => {
            var newError = new Error('Error rendering content: ' + (err.message || err));
            newError.name = err.name || 'Error';
            newError.cause = err;
            throw newError;
        });

    };

    this.sortGroupItems = (items, sortColumns, sortDirection) => {
      if (!$scope.sortFields || $scope.sortFields.length < 1) {
        return items;
      }

       function createIteratee(sortColumn) {
            return item => {
                const val = _.get(item, sortColumn);
                return typeof val === 'string' ? val.toLowerCase() : val;
            }
        }

      return _.orderBy(items, sortColumns.map(column => createIteratee(column)), sortDirection);
    }

    $scope.$on('contentItem.update', ($event, msg) => {

        // generally there'll only be one field to update, but iterate just incase
        // TODO: if multiple fields need updating, do it in a single API call

        for (var field in msg.data) {

            wfContentService.updateField(msg.contentItem.item, field, msg.data[field], msg.contentItem.contentType).then(() => {
                if (msg.data) {

                    if (msg.data.status) {
                        $scope.$emit('track:event', 'Content', 'Status changed', null, null, {
                            'Status transition': msg.oldValues.status + ' to ' + msg.data.status,
                            'Section': msg.contentItem.section,
                            'Content type': msg.contentItem.contentType
                        });
                    } else {
                        for (var fieldId in msg.data) {
                            $scope.$emit('track:event', 'Content', 'Edited', null, null, {
                                'Field': fieldId,
                                'Section': msg.contentItem.section,
                                'Content type': msg.contentItem.contentType
                            })
                        }
                    }

                }

                this.poller.refresh();

            }, (err) => {
                $scope.refreshContentError = err;

                $scope.$apply(() => {
                    var newError = new Error('Error updating content: ' + (err.message || err));
                    newError.name = err.name || 'Error';
                    newError.cause = err;
                    throw newError;
                });
            });
        }

    });

    $scope.$on('content.deleted', () => {
        this.poller.refresh();
    });


    // Start polling
    var poller = this.poller = new wfContentPollingService(function () {
        return wfContentService.getServerParams();
    });

    poller.onPoll((response) => {
        // catch race condition between determining the contentItemTemplate, and rendering content
        if ($rootScope.contentItemTemplate) {
            this.render(response);
        } else {
            wfColumnService.getContentItemTemplate().then(this.render.bind(this, response));
        }
    });
    poller.onError(this.renderError);

    poller.startPolling().then(function(){
        var myListener = $rootScope.$on('content.rendered',
            function(){
                $anchorScroll();
                myListener();
            });
    });

    $scope.$on('destroy', function () {
        poller.stopPolling();
    });

    // TODO: use more specific event names to trigger a refresh, eg: filterChanged, contentImported
    $scope.$on('getContent', this.poller.refresh.bind(this.poller));

    $scope.$on(EVENT_PREFERENCE_CHANGED, (_, { name, data }) => {
      if (name === 'compactView') {
        $scope.compactView = data;
      }
    })
}