[FEATURE] Add button to select all records 49/54849/18
authorŁukasz Uznański <l.uznanski@macopedia.pl>
Wed, 29 Nov 2017 09:37:15 +0000 (10:37 +0100)
committerJigal van Hemert <jigal.van.hemert@typo3.org>
Tue, 6 Mar 2018 06:34:32 +0000 (07:34 +0100)
Add and handle button to select all records from all pages in recycler.
Right now, there is pagination, which means that you can select 50 records max.

Resolves: #81310
Releases: master
Change-Id: Icfc0c93e5cff5cd9573a6a39b615ce0c6e1d273c
Reviewed-on: https://review.typo3.org/54849
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Tobi Kretschmann <tobi@tobishome.de>
Tested-by: Tobi Kretschmann <tobi@tobishome.de>
Reviewed-by: Joerg Boesche <typo3@joergboesche.de>
Tested-by: Joerg Boesche <typo3@joergboesche.de>
Reviewed-by: Steffen Frese <steffenf14@gmail.com>
Reviewed-by: Jigal van Hemert <jigal.van.hemert@typo3.org>
Tested-by: Jigal van Hemert <jigal.van.hemert@typo3.org>
typo3/sysext/core/Documentation/Changelog/master/Feature-54849-AddButtonToSelectAllRecordsInEXTrecycler.rst [new file with mode: 0644]
typo3/sysext/recycler/Classes/Controller/DeletedRecordsController.php
typo3/sysext/recycler/Classes/Controller/RecyclerAjaxController.php
typo3/sysext/recycler/Classes/Controller/RecyclerModuleController.php
typo3/sysext/recycler/Resources/Private/Language/locallang.xlf
typo3/sysext/recycler/Resources/Private/Templates/RecyclerModule/Index.html
typo3/sysext/recycler/Resources/Public/JavaScript/Recycler.js

diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-54849-AddButtonToSelectAllRecordsInEXTrecycler.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-54849-AddButtonToSelectAllRecordsInEXTrecycler.rst
new file mode 100644 (file)
index 0000000..024f607
--- /dev/null
@@ -0,0 +1,20 @@
+.. include:: ../../Includes.txt
+
+==================================================================
+Feature: #54849 - Add button to select all records in EXT:recycler
+==================================================================
+
+See :issue:`54849`
+
+Description
+===========
+
+Add button to select all records from all pages in EXT:recycler.
+
+
+Impact
+======
+
+All TYPO3 installations where EXT:recycler is enabled.
+
+.. index:: Backend, ext:recycler
\ No newline at end of file
index d6554d3..7dbfbc6 100644 (file)
@@ -50,12 +50,10 @@ class DeletedRecordsController
      * Transforms the rows for the deleted records
      *
      * @param array $deletedRowsArray Array with table as key and array with all deleted rows
-     * @param int $totalDeleted Number of deleted records in total
      * @return array JSON array
      */
-    public function transform($deletedRowsArray, $totalDeleted)
+    public function transform($deletedRowsArray)
     {
-        $total = 0;
         $jsonArray = [
             'rows' => []
         ];
@@ -65,7 +63,6 @@ class DeletedRecordsController
             $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
 
             foreach ($deletedRowsArray as $table => $rows) {
-                $total += count($deletedRowsArray[$table]);
                 foreach ($rows as $row) {
                     $pageTitle = $this->getPageTitle((int)$row['pid']);
                     $backendUserName = $this->getBackendUser((int)$row[$GLOBALS['TCA'][$table]['ctrl']['cruser_id']]);
@@ -92,7 +89,29 @@ class DeletedRecordsController
                 }
             }
         }
-        $jsonArray['total'] = $totalDeleted;
+        return $jsonArray;
+    }
+
+    /**
+     * Transforms the rows for the deleted records
+     *
+     * @param array $deletedRowsArray Array with table as key and array with all deleted rows
+     * @return array JSON array
+     */
+    public function transformSmallAddTotal(array $deletedRowsArray): array
+    {
+        $jsonArray = [];
+        $total = 0;
+        if (is_array($deletedRowsArray)) {
+            foreach ($deletedRowsArray as $table => $rows) {
+                foreach ($rows as $row) {
+                    $key = $table . ':' . $row['uid'];
+                    $jsonArray['rows'][$key] = 1;
+                    $total++;
+                }
+            }
+        }
+        $jsonArray['total'] = $total;
         return $jsonArray;
     }
 
index 7a2759c..fac6a2f 100644 (file)
@@ -54,7 +54,7 @@ class RecyclerAjaxController
         $this->conf['filterTxt'] = GeneralUtility::_GP('filterTxt') ? GeneralUtility::_GP('filterTxt') : '';
         $this->conf['startUid'] = GeneralUtility::_GP('startUid') ? (int)GeneralUtility::_GP('startUid') : 0;
         $this->conf['depth'] = GeneralUtility::_GP('depth') ? (int)GeneralUtility::_GP('depth') : 0;
-        $this->conf['records'] = GeneralUtility::_GP('records') ? GeneralUtility::_GP('records') : null;
+        $this->conf['records'] = json_decode(GeneralUtility::_GP('records') ? GeneralUtility::_GP('records') : '[]', true);
         $this->conf['recursive'] = GeneralUtility::_GP('recursive') ? (bool)GeneralUtility::_GP('recursive') : false;
     }
 
@@ -94,11 +94,13 @@ class RecyclerAjaxController
                 $deletedRowsArray = $model->getDeletedRows();
 
                 $model = GeneralUtility::makeInstance(DeletedRecords::class);
-                $totalDeleted = $model->getTotalCount($this->conf['startUid'], $this->conf['table'], $this->conf['depth'], $this->conf['filterTxt']);
+                $model->loadData($this->conf['startUid'], $this->conf['table'], $this->conf['depth'], null, $this->conf['filterTxt']);
+                $deletedRowsArrayAll = $model->getDeletedRows();
 
                 /* @var $controller DeletedRecordsController */
                 $controller = GeneralUtility::makeInstance(DeletedRecordsController::class);
-                $recordsArray = $controller->transform($deletedRowsArray, $totalDeleted);
+                $recordsArray = $controller->transform($deletedRowsArray);
+                $recordsArrayAll = $controller->transformSmallAddTotal($deletedRowsArrayAll);
 
                 $modTS = $this->getBackendUser()->getTSConfig('mod.recycler');
                 $allowDelete = $this->getBackendUser()->isAdmin() ? true : (bool)$modTS['properties']['allowDelete'];
@@ -109,7 +111,8 @@ class RecyclerAjaxController
                 $view->assign('total', $recordsArray['total']);
                 $content = [
                     'rows' => $view->render(),
-                    'totalItems' => $recordsArray['total']
+                    'totalItems' => $recordsArrayAll['total'],
+                    'allTheRows' => $recordsArrayAll['rows']
                 ];
                 break;
             case 'undoRecords':
index edd9528..06a003e 100644 (file)
@@ -55,7 +55,7 @@ class RecyclerModuleController extends ActionController
     /**
      * @var int
      */
-    protected $recordsPageLimit = 50;
+    protected $recordsPageLimit = 25;
 
     /**
      * @var int
index e0812d7..abc493c 100644 (file)
                        <trans-unit id="button.delete">
                                <source>Delete</source>
                        </trans-unit>
+                       <trans-unit id="button.selectall">
+                               <source>Select all records from all pages</source>
+                       </trans-unit>
+                       <trans-unit id="button.deselectall">
+                               <source>Deselect all</source>
+                       </trans-unit>
+                       <trans-unit id="button.selectallamount">
+                               <source>Select all records ({0}) from all pages</source>
+                       </trans-unit>
+                       <trans-unit id="button.selectallamountrest">
+                               <source>Select the rest of the records ({0}) from all pages</source>
+                       </trans-unit>
                        <trans-unit id="button.deleteselected">
                                <source>Delete {0} records</source>
                        </trans-unit>
index 053e91c..d916dfd 100644 (file)
                                </tbody>
                        </table>
                </div>
+               <div class="progress progress-bar-notice alert-loading" style="display: none">
+                       <div class="t3js-progressbar progress-bar progress-bar-striped active m-3"
+                                          role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100"
+                                          style="width: 100%; height:25px; ">
+                               <span class="sr-only">Loading...</span>
+                       </div>
+               </div>
                <div>
-                       <button class="btn btn-default disabled" data-action="massundo">
-                               <core:icon identifier="actions-edit-undo" />
-                               <span class="text">
-                                       <f:translate key="button.undo" />
-                               </span>
-                       </button>
-                       <f:if condition="{allowDelete}">
-                               <button class="btn btn-default disabled" data-action="massdelete">
-                                       <core:icon identifier="actions-edit-delete" />
+
+                       <div>
+                               <button class="btn btn-default disabled" data-action="selectall">
+                                       <core:icon identifier="actions-document-select" />
+                                       <span class="text">
+                                               <f:translate key="LLL:EXT:recycler/Resources/Private/Language/locallang.xlf:button.selectall"/>
+                                       </span>
+                               </button>
+                               <button class="btn btn-default disabled" data-action="deselectall">
                                        <span class="text">
-                                               <f:translate key="button.delete" />
+                                               <f:translate key="LLL:EXT:recycler/Resources/Private/Language/locallang.xlf:button.deselectall"/>
                                        </span>
                                </button>
-                       </f:if>
+                       </div>
+                       <div>
+                               <button class="btn btn-default disabled" data-action="massundo">
+                                       <core:icon identifier="actions-edit-undo" />
+                                       <span class="text">
+                                               <f:translate key="button.undo" />
+                                       </span>
+                               </button>
+                               <f:if condition="{allowDelete}">
+                                       <button class="btn btn-default disabled" data-action="massdelete">
+                                               <core:icon identifier="actions-edit-delete" />
+                                               <span class="text">
+                                                       <f:translate key="button.delete" />
+                                               </span>
+                                       </button>
+                               </f:if>
+                       </div>
                </div>
                <nav>
                </nav>
index 1128c58..f2a3432 100644 (file)
@@ -41,7 +41,10 @@ define(['jquery',
       reloadAction: 'a[data-action=reload]',
       massUndo: 'button[data-action=massundo]',
       massDelete: 'button[data-action=massdelete]',
-      toggleAll: '.t3js-toggle-all'
+      selectAll: 'button[data-action=selectall]',
+      deselectAll: 'button[data-action=deselectall]',
+      toggleAll: '.t3js-toggle-all',
+      progressBar: '#recycler-index .progress.progress-bar-notice.alert-loading'
     },
     elements: {}, // filled in getElements()
     paging: {
@@ -70,7 +73,10 @@ define(['jquery',
       $reloadAction: $(Recycler.identifiers.reloadAction),
       $massUndo: $(Recycler.identifiers.massUndo),
       $massDelete: $(Recycler.identifiers.massDelete),
-      $toggleAll: $(Recycler.identifiers.toggleAll)
+      $selectAll: $(Recycler.identifiers.selectAll),
+      $deselectAll: $(Recycler.identifiers.deselectAll),
+      $toggleAll: $(Recycler.identifiers.toggleAll),
+      $progressBar: $(Recycler.identifiers.progressBar)
     };
   };
 
@@ -108,6 +114,7 @@ define(['jquery',
     // changing "depth"
     Recycler.elements.$depthSelector.on('change', function() {
       $.when(Recycler.loadAvailableTables()).done(function() {
+        Recycler.clearMarked();
         Recycler.loadDeletedElements();
       });
     });
@@ -115,6 +122,7 @@ define(['jquery',
     // changing "table"
     Recycler.elements.$tableSelector.on('change', function() {
       Recycler.paging.currentPage = 1;
+      Recycler.clearMarked();
       Recycler.loadDeletedElements();
     });
 
@@ -159,6 +167,7 @@ define(['jquery',
 
       if (reload) {
         Recycler.loadDeletedElements();
+        Recycler.loadMarked();
       }
     });
 
@@ -189,14 +198,31 @@ define(['jquery',
     });
 
     // checkboxes in the table
-    Recycler.elements.$toggleAll.on('click', function() {
-      Recycler.allToggled = !Recycler.allToggled;
-      $('input[type="checkbox"]').prop('checked', Recycler.allToggled).trigger('change');
-    });
     Recycler.elements.$recyclerTable.on('change', 'tr input[type=checkbox]', Recycler.handleCheckboxSelects);
 
-    Recycler.elements.$massUndo.on('click', Recycler.undoRecord);
-    Recycler.elements.$massDelete.on('click', Recycler.deleteRecord);
+    Recycler.elements.$toggleAll.on('click', Recycler.toggleAll);
+
+    Recycler.elements.$massUndo.on('click', function() {
+        if (!$(this).hasClass('disabled')) {
+          Recycler.undoRecord();
+        }
+    });
+    Recycler.elements.$massDelete.on('click', function() {
+        if (!$(this).hasClass('disabled')) {
+          Recycler.deleteRecord();
+        }
+    });
+    Recycler.elements.$selectAll.on('click', function() {
+        if (!$(this).hasClass('disabled')) {
+          Recycler.selectAll();
+        }
+    });
+    Recycler.elements.$deselectAll.on('click', function() {
+        if (!$(this).hasClass('disabled')) {
+          Recycler.deselectAll();
+        }
+
+    });
   };
 
   /**
@@ -226,46 +252,40 @@ define(['jquery',
       table = $tr.data('table'),
       uid = $tr.data('uid'),
       record = table + ':' + uid;
-
     if ($checkbox.prop('checked')) {
-      Recycler.markedRecordsForMassAction.push(record);
-      $tr.addClass('warning');
-    } else {
-      var index = Recycler.markedRecordsForMassAction.indexOf(record);
-      if (index > -1) {
-        Recycler.markedRecordsForMassAction.splice(index, 1);
-      }
-      $tr.removeClass('warning');
-    }
-
-    if (Recycler.markedRecordsForMassAction.length > 0) {
-      if (Recycler.elements.$massUndo.hasClass('disabled')) {
-        Recycler.elements.$massUndo.removeClass('disabled');
-      }
-      if (Recycler.elements.$massDelete.hasClass('disabled')) {
-        Recycler.elements.$massDelete.removeClass('disabled');
+      if (!Recycler.markedRecordsForMassAction[record]) {
+        Recycler.addRecord(record);
+        $tr.addClass('warning');
       }
-
-      var btnTextUndo = Recycler.createMessage(TYPO3.lang['button.undoselected'], [Recycler.markedRecordsForMassAction.length]),
-        btnTextDelete = Recycler.createMessage(TYPO3.lang['button.deleteselected'], [Recycler.markedRecordsForMassAction.length]);
-
-      Recycler.elements.$massUndo.find('span.text').text(btnTextUndo);
-      Recycler.elements.$massDelete.find('span.text').text(btnTextDelete);
-
     } else {
-      Recycler.resetMassActionButtons();
+      if (!!Recycler.markedRecordsForMassAction[record]) {
+        Recycler.subtractRecord(record);
+        $tr.removeClass('warning');
+      }
     }
+    Recycler.selectAllRefresh();
   };
 
+
   /**
    * Resets the mass action state
    */
   Recycler.resetMassActionButtons = function() {
-    Recycler.markedRecordsForMassAction = [];
+    if (!!Recycler.markedRecordsForMassAction) {
+      Recycler.persistMarked(Recycler.markedRecordsForMassAction);
+    } else {
+      Recycler.markedRecordsForMassAction = {};
+    }
+
     Recycler.elements.$massUndo.addClass('disabled');
     Recycler.elements.$massUndo.find('span.text').text(TYPO3.lang['button.undo']);
     Recycler.elements.$massDelete.addClass('disabled');
     Recycler.elements.$massDelete.find('span.text').text(TYPO3.lang['button.delete']);
+
+    Recycler.elements.$selectAll.addClass('disabled');
+    Recycler.elements.$selectAll.find('span.text').text(TYPO3.lang['button.selectall']);
+    Recycler.elements.$deselectAll.addClass('disabled');
+    Recycler.elements.$deselectAll.find('span.text').text(TYPO3.lang['button.deselectall']);
   };
 
   /**
@@ -286,6 +306,7 @@ define(['jquery',
         NProgress.start();
         Recycler.elements.$tableSelector.val('');
         Recycler.paging.currentPage = 1;
+        Recycler.markedRecordsCounter = 0;
       },
       success: function(data) {
         var tables = [];
@@ -336,13 +357,24 @@ define(['jquery',
       beforeSend: function() {
         NProgress.start();
         Recycler.resetMassActionButtons();
+        Recycler.selectAllDataShort = [];
+        Recycler.currentDataCount = 0;
+
+        /** if there are any checkboxes and corresponding buttons, hide them while new content arrives */
+        Recycler.showLoading();
       },
       success: function(data) {
+        var totalItems = data.totalItems;
+
         Recycler.elements.$tableBody.html(data.rows);
-        Recycler.buildPaginator(data.totalItems);
+        Recycler.buildPaginator(totalItems);
+        Recycler.currentDataCount = totalItems;
+
+        Recycler.selectAllDataShort = data.allTheRows;
       },
       complete: function() {
         NProgress.done();
+        Recycler.selectAllRefresh();
       }
     });
   };
@@ -354,13 +386,12 @@ define(['jquery',
     if (TYPO3.settings.Recycler.deleteDisable) {
       return;
     }
-
     var $tr = $(this).parents('tr'),
       isMassDelete = $tr.parent().prop('tagName') !== 'TBODY'; // deleteRecord() was invoked by the mass delete button
 
     var records, message;
     if (isMassDelete) {
-      records = Recycler.markedRecordsForMassAction;
+      records = Recycler.returnProperMarkedArray();
       message = TYPO3.lang['modal.massdelete.text'];
     } else {
       var uid = $tr.data('uid'),
@@ -382,7 +413,11 @@ define(['jquery',
         text: TYPO3.lang['button.delete'],
         btnClass: 'btn-danger',
         trigger: function() {
-          Recycler.callAjaxAction('delete', typeof records === 'object' ? records : [records], isMassDelete);
+          Recycler.callAjaxAction(
+            'delete',
+            typeof records === 'object' ? records : [records],
+            isMassDelete
+          )
         }
       }
     ]);
@@ -397,7 +432,7 @@ define(['jquery',
 
     var records, messageText, recoverPages;
     if (isMassUndo) {
-      records = Recycler.markedRecordsForMassAction;
+      records = Recycler.returnProperMarkedArray();
       messageText = TYPO3.lang['modal.massundo.text'];
       recoverPages = true;
     } else {
@@ -427,7 +462,7 @@ define(['jquery',
         )
       );
     } else {
-      $message = messageText;
+      $message = $('<div />').text(messageText);
     }
 
     Modal.confirm(TYPO3.lang['modal.undo.header'], $message, Severity.ok, [
@@ -441,7 +476,13 @@ define(['jquery',
         text: TYPO3.lang['button.undo'],
         btnClass: 'btn-success',
         trigger: function() {
-          Recycler.callAjaxAction('undo', typeof records === 'object' ? records : [records], isMassUndo, $message.find('#undo-recursive').prop('checked') ? 1 : 0);
+           Recycler.callAjaxAction(
+            'undo',
+            // typeof records === 'object' ? records : [records],
+            records,
+            isMassUndo,
+            $message.find('#undo-recursive').prop('checked') ? 1 : 0
+          );
         }
       }
     ]);
@@ -456,10 +497,12 @@ define(['jquery',
    */
   Recycler.callAjaxAction = function(action, records, isMassAction, recursive) {
     var data = {
-        records: records,
+        records: JSON.stringify(records),
         action: ''
       },
-      reloadPageTree = false;
+      reloadPageTree = false,
+      oldCount = Recycler.markedRecordsCounter,
+      error = 0;
     if (action === 'undo') {
       data.action = 'undoRecords';
       data.recursive = recursive ? 1 : 0;
@@ -470,18 +513,23 @@ define(['jquery',
       return;
     }
 
+
     $.ajax({
       url: TYPO3.settings.ajaxUrls['recycler'],
       dataType: 'json',
       data: data,
+      method: 'POST',
       beforeSend: function() {
         NProgress.start();
+        /** if there are any checkboxes and corresponding buttons, hide them while new content arrives */
+        Recycler.showLoading();
       },
       success: function(data) {
         if (data.success) {
           Notification.success('', data.message);
         } else {
           Notification.error('', data.message);
+          error = 1;
         }
 
         // reload recycler data
@@ -489,16 +537,22 @@ define(['jquery',
 
         $.when(Recycler.loadAvailableTables()).done(function() {
           Recycler.loadDeletedElements();
-          if (isMassAction) {
-            Recycler.resetMassActionButtons();
+          if (isMassAction && !error) {
+              Recycler.clearMarked();
+          } else {
+            if (!error) {
+              if (!!Recycler.markedRecordsForMassAction[records]) {
+                Recycler.subtractRecord(records);
+                oldCount--;
+              }
+              Recycler.markedRecordsCounter = oldCount;
+            }
           }
 
           if (reloadPageTree) {
             Recycler.refreshPageTree();
           }
 
-          // Reset toggle state
-          Recycler.allToggled = false;
         });
       },
       complete: function() {
@@ -593,6 +647,235 @@ define(['jquery',
   };
 
   /**
+   * Select all records
+   */
+  Recycler.selectAll = function() {
+    if (Recycler.currentDataCount > 0) {
+      Recycler.elements.$selectAll.addClass('disabled');
+
+      Recycler.markedRecordsForMassAction = {};
+
+      Recycler.markedRecordsCounter = Recycler.currentDataCount;
+      Recycler.markedRecordsForMassAction = $.extend(true, {}, Recycler.selectAllDataShort);
+
+      Recycler.elements.$selectAll.removeClass('disabled');
+
+      Recycler.selectAllRefresh();
+    }
+  };
+
+  /**
+   * Deselect all records and return everything to clean state
+   */
+  Recycler.deselectAll = function() {
+    Recycler.elements.$deselectAll.addClass('disabled');
+
+    Recycler.clearMarked();
+    Recycler.resetMassActionButtons();
+    Recycler.selectAllRefresh();
+
+    Recycler.elements.$selectAll.removeClass('disabled');
+  };
+
+  /**
+   * Adjusts mass action buttons to user's action
+   */
+  Recycler.selectAllRefresh = function() {
+    var totalItems, btnTextSelectAll = '',
+      btnDisabledArr = ['$deselectAll', '$massUndo', '$massDelete'];
+
+    Recycler.hideLoading();
+    Recycler.persistMarked(Recycler.markedRecordsForMassAction);
+    Recycler.refreshCheckboxes();
+
+    /** if any checkboxes are checked change mass action buttons state */
+    if (Recycler.markedRecordsCounter > 0) {
+      var recordsLength = Recycler.markedRecordsCounter,
+        btnTextDelete = Recycler.createMessage(TYPO3.lang['button.deleteselected'], [recordsLength]),
+        btnTextUndo = Recycler.createMessage(TYPO3.lang['button.undoselected'], [recordsLength]);
+
+      /** if there are any records unselected show the amount */
+      if (!!Recycler.currentDataCount && ( (Recycler.currentDataCount-Recycler.markedRecordsCounter) > 0 )) {
+        if (Recycler.markedRecordsCounter === 0) {
+          btnTextSelectAll = Recycler.createMessage(TYPO3.lang['button.selectallamount'], [Recycler.currentDataCount]);
+
+        } else {
+          var rest = Recycler.currentDataCount - Recycler.markedRecordsCounter;
+
+          btnTextSelectAll = Recycler.createMessage(TYPO3.lang['button.selectallamountrest'], [rest]);
+        }
+      } else {
+        btnTextSelectAll = Recycler.createMessage(TYPO3.lang['button.selectall'])
+      }
+
+      /** if total amount of records from ajax is bigger than amount of currently selected records enable selectall */
+      if (!!Recycler.currentDataCount && (Recycler.currentDataCount > Recycler.markedRecordsCounter)) {
+        if (Recycler.elements.$selectAll.hasClass('disabled')) {
+          Recycler.elements.$selectAll.removeClass('disabled');
+        }
+      } else {
+        Recycler.elements.$selectAll.addClass('disabled');
+      }
+
+      /** enable mass action buttons (without selectall)*/
+      $.each(btnDisabledArr, (function(index, value) {
+        if (Recycler.elements[value].hasClass('disabled')) {
+          Recycler.elements[value].removeClass('disabled');
+        }
+      }));
+
+      Recycler.elements.$selectAll.find('span.text').text(btnTextSelectAll);
+      Recycler.elements.$massDelete.find('span.text').text(btnTextDelete);
+      Recycler.elements.$massUndo.find('span.text').text(btnTextUndo);
+
+    } else {
+
+      /** default states of mass action buttons if none checkboxes are checked */
+      if (!!Recycler.currentDataCount) {
+        totalItems = Recycler.currentDataCount;
+        btnTextSelectAll = Recycler.createMessage(TYPO3.lang['button.selectallamount'], [totalItems])
+      } else {
+        btnTextSelectAll = Recycler.createMessage(TYPO3.lang['button.selectall'])
+      }
+
+      /** disable all action buttons (without selectall) */
+      $.each(btnDisabledArr, (function(index, value) {
+          if (!Recycler.elements[value].hasClass('disabled')) {
+              Recycler.elements[value].addClass('disabled');
+          }
+      }));
+
+      Recycler.elements.$massUndo.find('span.text').text(TYPO3.lang['button.undo']);
+      Recycler.elements.$massDelete.find('span.text').text(TYPO3.lang['button.delete']);
+      Recycler.elements.$selectAll.find('span.text').text(btnTextSelectAll);
+      Recycler.elements.$selectAll.removeClass('disabled');
+    }
+  };
+
+  /**
+   * Show feedback while loading new content
+   */
+  Recycler.showLoading = function() {
+    Recycler.elements.$recyclerTable.parent().hide();
+    Recycler.elements.$progressBar.show();
+    Recycler.resetMassActionButtons();
+  };
+
+  Recycler.hideLoading = function() {
+    Recycler.elements.$recyclerTable.parent().show();
+    Recycler.elements.$progressBar.hide();
+  };
+
+  /**
+   * Check and uncheck checkboxes based on Recycler.markedRecordsForMassAction obj
+   */
+  Recycler.refreshCheckboxes = function() {
+    var $checkboxes = Recycler.elements.$tableBody.find('input[type="checkbox"]');
+    $.each($checkboxes, function(index, value) {
+      var $checkbox = $(value),
+        tableUid = Recycler.createTableUid($checkbox);
+
+      if (!!Recycler.markedRecordsForMassAction[tableUid]) {
+        $checkbox.prop('checked', true).parents('tr').addClass('warning');
+      } else {
+        $checkbox.prop('checked', false).parents('tr').removeClass('warning');
+      }
+    });
+  };
+
+  /**
+   * Toggles checkboxes of all records from current page
+   */
+  Recycler.toggleAll = function() {
+    var $checkboxes = Recycler.elements.$tableBody.find('input[type="checkbox"]'),
+        markedRecordsOnThisPage = Recycler.countMarkedRecordsOnThisPage(),
+        allToggled = (markedRecordsOnThisPage === $checkboxes.length);
+
+    $.each($checkboxes, function(index, value) {
+        var tableUid = Recycler.createTableUid($(value));
+      if (!Recycler.markedRecordsForMassAction[tableUid]) {
+        if (!allToggled) {
+          Recycler.addRecord(tableUid);
+        }
+      } else {
+        if (allToggled) {
+          Recycler.subtractRecord(tableUid);
+        }
+      }
+    });
+    Recycler.selectAllRefresh();
+  };
+
+  Recycler.subtractRecord = function(tableUid) {
+    delete Recycler.markedRecordsForMassAction[tableUid];
+    Recycler.markedRecordsCounter--;
+  };
+
+  Recycler.addRecord = function(tableUid) {
+    /** it should have truthy value */
+    Recycler.markedRecordsForMassAction[tableUid] = 1;
+    Recycler.markedRecordsCounter++;
+  };
+
+  /**
+   * Function to store Recycler.markedRecordsForMassAction
+   * @param data
+   */
+  Recycler.persistMarked = function(data) {
+    Recycler.persist = {};
+    Recycler.persist = data;
+  };
+  /**
+   * Function to load Recycler.markedRecordsForMassAction from Recycler.persist
+   */
+  Recycler.loadMarked = function() {
+    Recycler.markedRecordsForMassAction = Recycler.persist;
+    Recycler.persist = {};
+  };
+
+  /**
+   *  clear everything about selecting records
+   */
+  Recycler.clearMarked = function() {
+    Recycler.markedRecordsForMassAction = {};
+    Recycler.persist = {};
+    Recycler.markedRecordsCounter = 0;
+  };
+
+  /**
+   * Changing obj into proper array for ajax
+   * @returns {string[]}
+   */
+  Recycler.returnProperMarkedArray = function() {
+    return Object.keys(Recycler.markedRecordsForMassAction);
+  };
+
+  /**
+   * Counts checkboxes which have corresponding entry in Recycler.markedRecordsForMassAction
+   * @returns {number}
+   */
+  Recycler.countMarkedRecordsOnThisPage = function() {
+    var $checkboxes = Recycler.elements.$tableBody.find('input[type="checkbox"]'),
+      countOnPage = 0;
+    $.each($checkboxes, function(index,value) {
+      var tableUid = Recycler.createTableUid($(value));
+
+      if (!!Recycler.markedRecordsForMassAction[tableUid]) {
+      countOnPage++;
+      }
+    });
+    return countOnPage;
+  };
+
+  Recycler.createTableUid = function($row) {
+    var $checkbox = $($row),
+      $tr = $checkbox.parents('tr'),
+      table = $tr.data('table'),
+      uid = $tr.data('uid');
+    return table + ':' + uid;
+  };
+
+  /**
    * Changes the markup of a pagination action being disabled
    */
   $.fn.disablePagingAction = function() {
@@ -600,6 +883,5 @@ define(['jquery',
   };
 
   $(Recycler.initialize);
-
   return Recycler;
 });