From 7935e4d4a4698c6fcf4d29f01a24a21e9318fa3b Mon Sep 17 00:00:00 2001
From: Michael Oehlhof <typo3@oehlhof.de>
Date: Thu, 15 Dec 2016 12:47:10 +0100
Subject: [PATCH] [FEATURE] EXT:Scheduler: Add sorting to the list of scheduler
 tasks

Extract the HTML markup from "listTaskAction" into Fluid

Resolves: #45535
Releases: master
Change-Id: Ibe8f3407a0260da50980fdabadacef9bd52ed13b
Reviewed-on: https://review.typo3.org/50972
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Joerg Boesche <typo3@joergboesche.de>
Reviewed-by: Daniel Goerz <ervaude@gmail.com>
Tested-by: Daniel Goerz <ervaude@gmail.com>
Reviewed-by: Stefan Neufeind <typo3.neufeind@speedpartner.de>
Tested-by: Stefan Neufeind <typo3.neufeind@speedpartner.de>
---
 ...Feature-45535-SortingForScheduler-list.rst |  20 +++
 .../Controller/SchedulerModuleController.php  | 162 +++---------------
 .../Backend/SchedulerModule/TaskList.html     | 158 +++++++++++++++++
 .../Backend/SchedulerModule/ListTasks.html    |   3 +-
 .../Resources/Public/JavaScript/Scheduler.js  |  19 +-
 5 files changed, 219 insertions(+), 143 deletions(-)
 create mode 100644 typo3/sysext/core/Documentation/Changelog/master/Feature-45535-SortingForScheduler-list.rst
 create mode 100644 typo3/sysext/scheduler/Resources/Private/Partials/Backend/SchedulerModule/TaskList.html

diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-45535-SortingForScheduler-list.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-45535-SortingForScheduler-list.rst
new file mode 100644
index 000000000000..22aa46dead9b
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/master/Feature-45535-SortingForScheduler-list.rst
@@ -0,0 +1,20 @@
+.. include:: ../../Includes.txt
+
+============================================
+Feature: #45535 - Sorting for scheduler-list
+============================================
+
+See :issue:`45535`
+
+Description
+===========
+
+It is now possible to sort the scheduler-list according to the table headings.
+
+
+Impact
+======
+
+The sorting of the scheduler-list table is done by using the jquery datatables plugin.
+
+.. index:: Backend, JavaScript
\ No newline at end of file
diff --git a/typo3/sysext/scheduler/Classes/Controller/SchedulerModuleController.php b/typo3/sysext/scheduler/Classes/Controller/SchedulerModuleController.php
index 8915fece5602..f4a9ce286d9d 100644
--- a/typo3/sysext/scheduler/Classes/Controller/SchedulerModuleController.php
+++ b/typo3/sysext/scheduler/Classes/Controller/SchedulerModuleController.php
@@ -399,20 +399,6 @@ class SchedulerModuleController
         return $this->view->render();
     }
 
-    /**
-     * Renders the task progress bar.
-     *
-     * @param float $progress Task progress
-     * @return string Progress bar markup
-     */
-    protected function renderTaskProgressBar($progress)
-    {
-        $progressText = $this->getLanguageService()->getLL('status.progress') . ':&nbsp;' . $progress . '%';
-        return '<div class="progress">'
-        . '<div class="progress-bar progress-bar-striped" role="progressbar" aria-valuenow="' . $progress . '" aria-valuemin="0" aria-valuemax="100" style="width: ' . $progress . '%;">' . $progressText . '</div>'
-        . '</div>';
-    }
-
     /**
      * Delete a task from the execution queue
      */
@@ -865,80 +851,23 @@ class SchedulerModuleController
         $this->getPageRenderer()->loadJquery();
         $this->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Scheduler/Scheduler');
         $this->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/Tooltip');
-        $table = [];
-        // Header row
-        $table[] =
-            '<thead><tr>'
-                . '<th><a class="btn btn-default" href="#" id="checkall" title="' . htmlspecialchars($this->getLanguageService()->getLL('label.checkAll')) . '">' . $this->moduleTemplate->getIconFactory()->getIcon('actions-document-select', Icon::SIZE_SMALL)->render() . '</a></th>'
-                . '<th>' . htmlspecialchars($this->getLanguageService()->getLL('label.id')) . '</th>'
-                . '<th>' . htmlspecialchars($this->getLanguageService()->getLL('task')) . '</th>'
-                . '<th>' . htmlspecialchars($this->getLanguageService()->getLL('label.type')) . '</th>'
-                . '<th>' . htmlspecialchars($this->getLanguageService()->getLL('label.frequency')) . '</th>'
-                . '<th>' . htmlspecialchars($this->getLanguageService()->getLL('label.parallel')) . '</th>'
-                . '<th>' . htmlspecialchars($this->getLanguageService()->getLL('label.lastExecution')) . '</th>'
-                . '<th>' . htmlspecialchars($this->getLanguageService()->getLL('label.nextExecution')) . '</th>'
-                . '<th></th>'
-            . '</tr></thead>';
+
+        $tasks = $temporaryResult;
 
         $registeredClasses = $this->getRegisteredClasses();
-        $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
-        $collapseIcon = $iconFactory->getIcon('actions-view-list-collapse', Icon::SIZE_SMALL)->render();
-        $expandIcon = $iconFactory->getIcon('actions-view-list-expand', Icon::SIZE_SMALL)->render();
         foreach ($temporaryResult as $taskIndex => $taskGroup) {
-            $collapseExpandIcons = '<span class="taskGroup_' . $taskIndex . '">' . $collapseIcon . '</span>'
-                . '<span class="taskGroup_' . $taskIndex . '" style="display: none;">' . $expandIcon . '</span>';
-            if (!empty($taskGroup['groupName'])) {
-                $groupText = '<strong>' . htmlspecialchars($taskGroup['groupName']) . '</strong>';
-                if (!empty($taskGroup['groupDescription'])) {
-                    $groupText .= '<br>' . nl2br(htmlspecialchars($taskGroup['groupDescription']));
-                }
-                $table[] = '<tr class="taskGroup" data-task-group-id="' . $taskIndex . '"><td colspan="8">' . $groupText . '</td><td style="text-align:right;">' . $collapseExpandIcons . '</td></tr>';
-            } else {
-                if (count($temporaryResult) > 1) {
-                    $table[] = '<tr class="taskGroup" data-task-group-id="0"><td colspan="8"><strong>' . htmlspecialchars($this->getLanguageService()->getLL('label.noGroup')) . '</strong></td><td style="text-align:right;">' . $collapseExpandIcons . '</td></tr>';
-                }
-            }
-
-            foreach ($taskGroup['tasks'] as $schedulerRecord) {
-                // Define action icons
-                $link = htmlspecialchars($this->moduleUri . '&CMD=edit&tx_scheduler[uid]=' . $schedulerRecord['uid']);
-                $editAction = '<a data-toggle="tooltip" data-container="body" class="btn btn-default" href="' . $link . '" title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:edit')) . '">' .
-                    $this->moduleTemplate->getIconFactory()->getIcon('actions-open', Icon::SIZE_SMALL)->render() . '</a>';
+            foreach ($taskGroup['tasks'] as $recordIndex => $schedulerRecord) {
                 if ((int)$schedulerRecord['disable'] === 1) {
                     $translationKey = 'enable';
-                    $icon = $this->moduleTemplate->getIconFactory()->getIcon('actions-edit-unhide', Icon::SIZE_SMALL);
                 } else {
                     $translationKey = 'disable';
-                    $icon = $this->moduleTemplate->getIconFactory()->getIcon('actions-edit-hide', Icon::SIZE_SMALL);
                 }
-                $toggleHiddenAction = '<a data-toggle="tooltip" data-container="body" class="btn btn-default" href="' . htmlspecialchars($this->moduleUri
-                    . '&CMD=toggleHidden&tx_scheduler[uid]=' . $schedulerRecord['uid']) . '" title="'
-                    . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:' . $translationKey))
-                    . '">' . $icon->render() . '</a>';
-                $deleteAction = '<a data-toggle="tooltip" data-container="body" class="btn btn-default t3js-modal-trigger" href="' . htmlspecialchars($this->moduleUri . '&CMD=delete&tx_scheduler[uid]=' . $schedulerRecord['uid']) . '" '
-                    . ' data-severity="warning"'
-                    . ' data-title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:delete')) . '"'
-                    . ' data-button-close-text="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:cancel')) . '"'
-                    . ' data-content="' . htmlspecialchars($this->getLanguageService()->getLL('msg.delete')) . '"'
-                    . ' title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:delete')) . '">' .
-                    $this->moduleTemplate->getIconFactory()->getIcon('actions-edit-delete', Icon::SIZE_SMALL)->render() . '</a>';
-                $stopAction = '<a data-toggle="tooltip" data-container="body" class="btn btn-default t3js-modal-trigger" href="' . htmlspecialchars($this->moduleUri . '&CMD=stop&tx_scheduler[uid]=' . $schedulerRecord['uid']) . '" '
-                    . ' data-severity="warning"'
-                    . ' data-title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:stop')) . '"'
-                    . ' data-button-close-text="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:cancel')) . '"'
-                    . ' data-content="' . htmlspecialchars($this->getLanguageService()->getLL('msg.stop')) . '"'
-                    . ' title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:stop')) . '">' .
-                    $this->moduleTemplate->getIconFactory()->getIcon('actions-close', Icon::SIZE_SMALL)->render() . '</a>';
-                $runAction = '<a class="btn btn-default" data-toggle="tooltip" data-container="body" href="' . htmlspecialchars($this->moduleUri . '&tx_scheduler[execute][]=' . $schedulerRecord['uid']) . '" title="' . htmlspecialchars($this->getLanguageService()->getLL('action.run_task')) . '">' .
-                    $this->moduleTemplate->getIconFactory()->getIcon('extensions-scheduler-run-task', Icon::SIZE_SMALL)->render() . '</a>';
-                $runCronAction = '<a class="btn btn-default" data-toggle="tooltip" data-container="body" href="' . htmlspecialchars($this->moduleUri . '&CMD=setNextExecutionTime&tx_scheduler[uid]=' . $schedulerRecord['uid']) . '" title="' . htmlspecialchars($this->getLanguageService()->getLL('action.run_task_cron')) . '">' .
-                    $this->moduleTemplate->getIconFactory()->getIcon('extensions-scheduler-run-task-cron', Icon::SIZE_SMALL)->render() . '</a>';
+                $tasks[$taskIndex]['tasks'][$recordIndex]['translationKey'] = $translationKey;
 
                 // Define some default values
                 $lastExecution = '-';
                 $isRunning = false;
                 $showAsDisabled = false;
-                $startExecutionElement = '<span class="btn btn-default disabled">' . $this->moduleTemplate->getIconFactory()->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>';
                 // Restore the serialized task and pass it a reference to the scheduler object
                 /** @var $task \TYPO3\CMS\Scheduler\Task\AbstractTask|ProgressProviderInterface */
                 $task = unserialize($schedulerRecord['serialized_task_object']);
@@ -946,6 +875,7 @@ class SchedulerModuleController
                 if ($class === '__PHP_Incomplete_Class' && preg_match('/^O:[0-9]+:"(?P<classname>.+?)"/', $schedulerRecord['serialized_task_object'], $matches) === 1) {
                     $class = $matches['classname'];
                 }
+                $tasks[$taskIndex]['tasks'][$recordIndex]['class'] = $class;
                 // Assemble information about last execution
                 if (!empty($schedulerRecord['lastexecution_time'])) {
                     $lastExecution = date($dateFormat, $schedulerRecord['lastexecution_time']);
@@ -956,19 +886,20 @@ class SchedulerModuleController
                     }
                     $lastExecution .= ' (' . $context . ')';
                 }
+                $tasks[$taskIndex]['tasks'][$recordIndex]['lastExecution'] = $lastExecution;
 
                 if (isset($registeredClasses[get_class($task)]) && $this->scheduler->isValidTaskObject($task)) {
+                    $tasks[$taskIndex]['tasks'][$recordIndex]['validClass'] = true;
                     // The task object is valid
                     $labels = [];
-                    $name = htmlspecialchars($registeredClasses[$class]['title'] . ' (' . $registeredClasses[$class]['extension'] . ')');
                     $additionalInformation = $task->getAdditionalInformation();
                     if ($task instanceof ProgressProviderInterface) {
                         $progress = round((float)$task->getProgress(), 2);
-                        $name .= $this->renderTaskProgressBar($progress);
-                    }
-                    if (!empty($additionalInformation)) {
-                        $name .= '<div class="additional-information">' . nl2br(htmlspecialchars($additionalInformation)) . '</div>';
+                        $tasks[$taskIndex]['tasks'][$recordIndex]['progress'] = $progress;
                     }
+                    $tasks[$taskIndex]['tasks'][$recordIndex]['classTitle'] = $registeredClasses[$class]['title'];
+                    $tasks[$taskIndex]['tasks'][$recordIndex]['classExtension'] = $registeredClasses[$class]['extension'];
+                    $tasks[$taskIndex]['tasks'][$recordIndex]['additionalInformation'] = $additionalInformation;
                     // Check if task currently has a running execution
                     if (!empty($schedulerRecord['serialized_executions'])) {
                         $labels[] = [
@@ -977,6 +908,7 @@ class SchedulerModuleController
                         ];
                         $isRunning = true;
                     }
+                    $tasks[$taskIndex]['tasks'][$recordIndex]['isRunning'] = $isRunning;
 
                     // Prepare display of next execution date
                     // If task is currently running, date is not displayed (as next hasn't been calculated yet)
@@ -995,6 +927,7 @@ class SchedulerModuleController
                             ];
                         }
                     }
+                    $tasks[$taskIndex]['tasks'][$recordIndex]['nextDate'] = $nextDate;
                     // Get execution type
                     if ($task->getType() === AbstractTask::TYPE_SINGLE) {
                         $execType = $this->getLanguageService()->getLL('label.type.single');
@@ -1007,17 +940,6 @@ class SchedulerModuleController
                             $frequency = $task->getExecution()->getCronCmd();
                         }
                     }
-                    // Get multiple executions setting
-                    if ($task->getExecution()->getMultiple()) {
-                        $multiple = $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:yes');
-                    } else {
-                        $multiple = $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:no');
-                    }
-                    // Define checkbox
-                    $startExecutionElement = '<label class="btn btn-default btn-checkbox"><input type="checkbox" name="tx_scheduler[execute][]" value="' . $schedulerRecord['uid'] . '" id="task_' . $schedulerRecord['uid'] . '"><span class="t3-icon fa"></span></label>';
-
-                    $actions = '<div class="btn-group" role="group">' . $editAction . $toggleHiddenAction . $deleteAction . '</div>';
-
                     // Check the disable status
                     // Row is shown dimmed if task is disabled, unless it is still running
                     if ($schedulerRecord['disable'] && !$isRunning) {
@@ -1027,13 +949,15 @@ class SchedulerModuleController
                         ];
                         $showAsDisabled = true;
                     }
-
-                    // Show no action links (edit, delete) if task is running
-                    if ($isRunning) {
-                        $actions = '<div class="btn-group" role="group">' . $stopAction . '</div>';
+                    $tasks[$taskIndex]['tasks'][$recordIndex]['execType'] = $execType;
+                    $tasks[$taskIndex]['tasks'][$recordIndex]['frequency'] = $frequency;
+                    // Get multiple executions setting
+                    if ($task->getExecution()->getMultiple()) {
+                        $multiple = $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:yes');
                     } else {
-                        $actions .= '&nbsp;<div class="btn-group" role="group">' . $runCronAction . $runAction . '</div>';
+                        $multiple = $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:no');
                     }
+                    $tasks[$taskIndex]['tasks'][$recordIndex]['multiple'] = $multiple;
 
                     // Check if the last run failed
                     if (!empty($schedulerRecord['lastexecution_failure'])) {
@@ -1052,52 +976,16 @@ class SchedulerModuleController
                             'description' => $labelDescription
                         ];
                     }
-                    // Format the execution status,
-                    // including failure feedback, if any
-                    $taskDesc = '';
-                    if ($schedulerRecord['description'] !== '') {
-                        $taskDesc = '<span class="description">' . nl2br(htmlspecialchars($schedulerRecord['description'])) . '</span>';
+                    $tasks[$taskIndex]['tasks'][$recordIndex]['labels'] = $labels;
+                    if ($showAsDisabled) {
+                        $tasks[$taskIndex]['tasks'][$recordIndex]['showAsDisabled'] = 'disabled';
                     }
-                    $taskName = '<span class="name"><a href="' . $link . '">' . $name . '</a></span>';
-
-                    $table[] =
-                        '<tr class="' . ($showAsDisabled ? 'disabled' : '') . ' taskGroup_' . $taskIndex . '">'
-                            . '<td><span class="t-span">' . $startExecutionElement . '</span></td>'
-                            . '<td class="right"><span class="t-span">' . $schedulerRecord['uid'] . '</span></td>'
-                            . '<td><span class="t-span">' . $this->makeStatusLabel($labels) . $taskName . $taskDesc . '</span></td>'
-                            . '<td><span class="t-span">' . $execType . '</span></td>'
-                            . '<td><span class="t-span">' . $frequency . '</span></td>'
-                            . '<td><span class="t-span">' . $multiple . '</span></td>'
-                            . '<td><span class="t-span">' . $lastExecution . '</span></td>'
-                            . '<td><span class="t-span">' . $nextDate . '</span></td>'
-                            . '<td class="nowrap"><span class="t-span">' . $actions . '</span></td>'
-                        . '</tr>';
-                } else {
-                    // The task object is not valid
-                    // Prepare to issue an error
-                    $executionStatusOutput = '<span class="label label-danger">'
-                        . htmlspecialchars(sprintf(
-                            $this->getLanguageService()->getLL('msg.invalidTaskClass'),
-                            $class
-                        ))
-                        . '</span>';
-                    $table[] =
-                        '<tr>'
-                            . '<td>' . $startExecutionElement . '</td>'
-                            . '<td class="right">' . $schedulerRecord['uid'] . '</td>'
-                            . '<td colspan="6">' . $executionStatusOutput . '</td>'
-                            . '<td class="nowrap"><div class="btn-group" role="group">'
-                                . '<span class="btn btn-default disabled">' . $this->moduleTemplate->getIconFactory()->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>'
-                                . '<span class="btn btn-default disabled">' . $this->moduleTemplate->getIconFactory()->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>'
-                                . $deleteAction
-                                . '<span class="btn btn-default disabled">' . $this->moduleTemplate->getIconFactory()->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>'
-                            . '</div></td>'
-                        . '</tr>';
                 }
             }
         }
 
-        $this->view->assign('table', '<table class="table table-striped table-hover">' . implode(LF, $table) . '</table>');
+        $this->view->assign('tasks', $tasks);
+        $this->view->assign('moduleUri', $this->moduleUri);
         $this->view->assign('now', $this->getServerTime());
 
         return $this->view->render();
diff --git a/typo3/sysext/scheduler/Resources/Private/Partials/Backend/SchedulerModule/TaskList.html b/typo3/sysext/scheduler/Resources/Private/Partials/Backend/SchedulerModule/TaskList.html
new file mode 100644
index 000000000000..c2a157f100eb
--- /dev/null
+++ b/typo3/sysext/scheduler/Resources/Private/Partials/Backend/SchedulerModule/TaskList.html
@@ -0,0 +1,158 @@
+<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
+      xmlns:core="http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers"
+      data-namespace-typo3-fluid="true">
+
+<f:for each="{tasks}" as="taskGroup" iteration="groupIterator">
+
+    <div class="tx_scheduler_mod1_table">
+        <f:if condition="{taskGroup.groupName} == ''">
+            <f:then>
+                <f:if condition="{tasks -> f:count()}>1">
+                    <table class="table table-striped table-hover" style="margin-bottom: -2px;">
+                        <tr class="taskGroup" data-task-group-id="0">
+                            <td><strong><f:translate key="label.noGroup" /></strong>
+                            </td><td style="text-align:right;">
+                            <span class="taskGroup_{groupIterator.index}"><core:icon identifier="actions-view-list-collapse" /></span>
+                            <span class="taskGroup_{groupIterator.index}" style="display: none;"><core:icon identifier="actions-view-list-expand" /></span>
+                        </td>
+                        </tr>
+                    </table>
+                </f:if>
+            </f:then>
+            <f:else>
+                <table class="table table-striped table-hover" style="margin-bottom: -2px;">
+                    <tr class="taskGroup" data-task-group-id="{groupIterator.index}">
+                        <td>
+                            <strong>{taskGroup.groupName}</strong>
+                            <f:if condition="{taskGroup.groupDescription} != ''">
+                                <f:format.nl2br>{taskGroup.groupDescription}</f:format.nl2br>
+                            </f:if>
+                        </td><td style="text-align:right;">
+                        <span class="taskGroup_{groupIterator.index}"><core:icon identifier="actions-view-list-collapse" /></span>
+                        <span class="taskGroup_{groupIterator.index}" style="display: none;"><core:icon identifier="actions-view-list-expand" /></span>
+                    </td>
+                    </tr>
+                </table>
+            </f:else>
+        </f:if>
+        <table class="table table-striped table-hover display">
+            <thead><tr class="taskGroup_{groupIterator.index}">
+                <th><span class="t-span"><a class="btn btn-default checkall" href="#" id="checkall" title="{f:translate(key:'label.checkAll')}"><core:icon identifier="actions-document-select" /></a></span></th>
+                <th><span class="t-span"><f:translate key="label.id" /></span></th>
+                <th><span class="t-span"><f:translate key="task" /></span></th>
+                <th><span class="t-span"><f:translate key="label.type" /></span></th>
+                <th><span class="t-span"><f:translate key="label.frequency" /></span></th>
+                <th><span class="t-span"><f:translate key="label.parallel" /></span></th>
+                <th><span class="t-span"><f:translate key="label.lastExecution" /></span></th>
+                <th><span class="t-span"><f:translate key="label.nextExecution" /></span></th>
+                <th><span class="t-span"></span></th>
+            </tr></thead>
+            <f:for each="{taskGroup.tasks}" as="taskRecord" iteration="taskIterator">
+                <f:if condition="{taskRecord.validClass}">
+                    <f:then>
+                        <tr class="{taskRecord.showAsDisabled} taskGroup_{groupIterator.index}">
+                            <td><span class="t-span"><label class="btn btn-default btn-checkbox"><input type="checkbox" name="tx_scheduler[execute][]" value="{taskRecord.uid}" id="task_{taskRecord.uid}"><span class="t3-icon fa"></span></label></span></td>
+                            <td class="right"><span class="t-span">{taskRecord.uid}</span></td>
+                            <td><span class="t-span">
+                            <f:for each="{taskRecord.labels}" as="label">
+                                <f:if condition="{taskRecord.validClass}">
+                                    <span class="label label-{label.class} pull-right" title="{label.desciption}">{label.text}</span>
+                                </f:if>
+                            </f:for>
+                            <span class="name">
+                                <a href="{moduleUri}&CMD=edit&tx_scheduler[uid]={taskRecord.uid}">{taskRecord.classTitle} ({taskRecord.classExtension})
+                                <f:if condition="{taskRecord.progress}">
+                                    <div class="progress">
+                                        <div class="progress-bar progress-bar-striped" role="progressbar" aria-valuenow="{taskRecord.progress}" aria-valuemin="0" aria-valuemax="100" style="width: {taskRecord.progress}%;">{f:translate(key:'label.checkAll')}:&nbsp;{taskRecord.progress}%</div>
+                                    </div>
+                                </f:if>
+                                <f:if condition="{taskRecord.additionalInformation}">
+                                    <div class="additional-information"><f:format.nl2br>{taskRecord.additionalInformation}</f:format.nl2br></div>
+                                </f:if>
+                                </a>
+                            </span>
+                            <f:if condition="{taskRecord.description} != ''">
+                                <span class="description"><f:format.nl2br>{taskRecord.description}</f:format.nl2br></span>
+                            </f:if>
+                            </span>
+                            </td>
+                            <td><span class="t-span">{taskRecord.execType}</span></td>
+                            <td><span class="t-span">{taskRecord.frequency}</span></td>
+                            <td><span class="t-span">{taskRecord.multiple}</span></td>
+                            <td><span class="t-span">{taskRecord.lastExecution}</span></td>
+                            <td><span class="t-span">{taskRecord.nextDate}</span></td>
+                            <td class="nowrap"><span class="t-span">
+                            <f:if condition="{taskRecord.isRunning}">
+                            <f:then>
+                                <div class="btn-group" role="group">
+                                    <a data-toggle="tooltip" data-container="body" class="btn btn-default t3js-modal-trigger" href="{moduleUri}&CMD=stop&tx_scheduler[uid]={taskRecord.uid}"
+                                       data-severity="warning"
+                                       data-title="{f:translate(key:'LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:stop')}"
+                                       data-button-close-text="{f:translate(key:'LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:cancel')}"
+                                       data-content="{f:translate(key:'msg.stop')}"
+                                       title="{f:translate(key:'LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:stop')}">
+                                        <core:icon identifier="actions-close" />
+                                    </a>
+                                </div>
+                            </f:then>
+                            <f:else>
+                                <div class="btn-group" role="group">
+                                    <a data-toggle="tooltip" data-container="body" class="btn btn-default" href="{moduleUri}&CMD=edit&tx_scheduler[uid]={taskRecord.uid}" title="{f:translate(key:'LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:edit')}">
+                                        <core:icon identifier="actions-open" />
+                                    </a>
+                                    <a data-toggle="tooltip" data-container="body" class="btn btn-default" href="{moduleUri}&CMD=toggleHidden&tx_scheduler[uid]={taskRecord.uid}" title="{f:translate(key:'LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:{taskRecord.translationKey}')}">
+                                        <f:if condition="{taskRecord.translationKey} == 'enable'">
+                                        <f:then>
+                                            <core:icon identifier="actions-edit-unhide" />
+                                        </f:then>
+                                        <f:else>
+                                            <core:icon identifier="actions-edit-hide" />
+                                        </f:else>
+                                        </f:if>
+                                    </a>
+                                    <a data-toggle="tooltip" data-container="body" class="btn btn-default t3js-modal-trigger" href="{moduleUri}&CMD=delete&tx_scheduler[uid]={taskRecord.uid}"
+                                       data-severity="warning"
+                                       data-title="{f:translate(key:'LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:delete')}"
+                                       data-button-close-text="{f:translate(key:'LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:cancel')}"
+                                       data-content="{f:translate(key:'msg.delete')}"
+                                       title="{f:translate(key:'LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:delete')}">
+                                        <core:icon identifier="actions-edit-delete" />
+                                    </a>
+                                </div>
+                                &nbsp;
+                                <div class="btn-group" role="group">
+                                    <a class="btn btn-default" data-toggle="tooltip" data-container="body" href="{moduleUri}&CMD=setNextExecutionTime&tx_scheduler[uid]={taskRecord.uid}" title="{f:translate(key:'action.run_task_cron')}">
+                                        <core:icon identifier="extensions-scheduler-run-task-cron" />
+                                    </a>
+                                    <a class="btn btn-default" data-toggle="tooltip" data-container="body" href="{moduleUri}&tx_scheduler[execute][]={taskRecord.uid}" title="{f:translate(key:'action.run_task')}">
+                                        <core:icon identifier="extensions-scheduler-run-task" />
+                                    </a>
+                                </div>
+                            </f:else>
+                            </f:if>
+                            </span>
+                            </td>
+                        </tr>
+                    </f:then>
+                    <f:else>
+                        <tr>
+                            <td><span class="btn btn-default disabled"><core:icon identifier="empty-empty" /></span></td>
+                            <td class="right">{taskRecord.uid}</td>
+                            <td colspan="6"><span class="label label-danger"><f:translate key="msg.invalidTaskClass" arguments="{0: '{taskRecord.class}'}" /></span></td>
+                            <td class="nowrap">
+                                <div class="btn-group" role="group">
+                                    <span class="btn btn-default disabled"><core:icon identifier="empty-empty" /></span>
+                                    <span class="btn btn-default disabled"><core:icon identifier="empty-empty" /></span>
+                                    $deleteAction
+                                    <span class="btn btn-default disabled"><core:icon identifier="empty-empty" /></span>
+                                </div>
+                            </td>
+                        </tr>
+                    </f:else>
+                </f:if>
+            </f:for>
+        </table>
+    </div>
+</f:for>
+<br />
+</html>
diff --git a/typo3/sysext/scheduler/Resources/Private/Templates/Backend/SchedulerModule/ListTasks.html b/typo3/sysext/scheduler/Resources/Private/Templates/Backend/SchedulerModule/ListTasks.html
index efc0e1971b81..35fbc1c2a5ba 100644
--- a/typo3/sysext/scheduler/Resources/Private/Templates/Backend/SchedulerModule/ListTasks.html
+++ b/typo3/sysext/scheduler/Resources/Private/Templates/Backend/SchedulerModule/ListTasks.html
@@ -1,4 +1,5 @@
-<f:format.raw>{table}</f:format.raw>
+<f:render partial="TaskList" arguments="{tasks: tasks, moduleUri: moduleUri}" />
+
 <button class="btn btn-default" name="go_cron" id="scheduler_executeselected">
 	<core:icon identifier="extensions-scheduler-run-task-cron" />
 	<f:translate key="label.cronjobSelected" />
diff --git a/typo3/sysext/scheduler/Resources/Public/JavaScript/Scheduler.js b/typo3/sysext/scheduler/Resources/Public/JavaScript/Scheduler.js
index 869391c3bc22..e53373716251 100644
--- a/typo3/sysext/scheduler/Resources/Public/JavaScript/Scheduler.js
+++ b/typo3/sysext/scheduler/Resources/Public/JavaScript/Scheduler.js
@@ -15,6 +15,7 @@
  * Module: TYPO3/CMS/Scheduler/Scheduler
  */
 define(['jquery',
+		'datatables',
 		'TYPO3/CMS/Backend/SplitButtons'
 		], function($, SplitButtons) {
 
@@ -23,7 +24,8 @@ define(['jquery',
 	 * @type {{}}
 	 * @exports TYPO3/CMS/Scheduler/Scheduler
 	 */
-	var Scheduler = {};
+	var Scheduler = {
+	};
 
 	var allCheckedStatus = false;
 
@@ -102,7 +104,7 @@ define(['jquery',
 	 * @returns {Boolean}
 	 */
 	Scheduler.checkOrUncheckAllCheckboxes = function(theSelector) {
-		theSelector.parents('.tx_scheduler_mod1').find(':checkbox').prop('checked', !allCheckedStatus);
+		theSelector.parents('.tx_scheduler_mod1_table').find(':checkbox').prop('checked', !allCheckedStatus);
 		allCheckedStatus = !allCheckedStatus;
 		return false;
 	};
@@ -125,7 +127,7 @@ define(['jquery',
 	 * @param {Object} theSelector
 	 */
 	Scheduler.toggleTaskGroups = function(theSelector) {
-		taskGroup = theSelector.data('task-group-id');
+		var taskGroup = theSelector.data('task-group-id');
 		var taskGroupClass= '.taskGroup_' + taskGroup;
 		$(taskGroupClass).toggleClass('taskGroup--close');
 	};
@@ -134,7 +136,7 @@ define(['jquery',
 	 * Registers listeners
 	 */
 	Scheduler.initializeEvents = function() {
-		$('#checkall').on('click', function() {
+		$('.checkall').on('click', function() {
 			Scheduler.checkOrUncheckAllCheckboxes($(this));
 		});
 
@@ -155,6 +157,11 @@ define(['jquery',
 		$('.taskGroup').on('click', function() {
 			Scheduler.toggleTaskGroups($(this));
 		});
+
+		$('table.display').DataTable( {
+			"paging":   false,
+			"searching": false
+		} );
 	};
 
 	/**
@@ -166,7 +173,9 @@ define(['jquery',
 			Scheduler.toggleFieldsByTaskType($taskType.val());
 		}
 		var $taskClass = $('#task_class');
-		Scheduler.actOnChangedTaskClass($taskClass);
+        if ($taskClass.length) {
+			Scheduler.actOnChangedTaskClass($taskClass);
+        }
 	};
 
 	$(Scheduler.initializeEvents);
-- 
GitLab