diff --git a/Build/Sources/TypeScript/recycler/Resources/Public/TypeScript/Recycler.ts b/Build/Sources/TypeScript/recycler/Resources/Public/TypeScript/Recycler.ts new file mode 100644 index 0000000000000000000000000000000000000000..fb73d90e19d7bd4fa79129eb676e8158d8bdc570 --- /dev/null +++ b/Build/Sources/TypeScript/recycler/Resources/Public/TypeScript/Recycler.ts @@ -0,0 +1,594 @@ +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +import * as $ from 'jquery'; +import * as NProgress from 'nprogress'; +import Modal = require('TYPO3/CMS/Backend/Modal'); +import Notification = require('TYPO3/CMS/Backend/Notification'); +import Severity = require('TYPO3/CMS/Backend/Severity'); +import 'TYPO3/CMS/Backend/jquery.clearable'; + +enum RecyclerIdentifiers { + searchForm = '#recycler-form', + searchText = '#recycler-form [name=search-text]', + searchSubmitBtn = '#recycler-form button[type=submit]', + depthSelector = '#recycler-form [name=depth]', + tableSelector = '#recycler-form [name=pages]', + recyclerTable = '#itemsInRecycler', + paginator = '#recycler-index nav', + reloadAction = 'a[data-action=reload]', + massUndo = 'button[data-action=massundo]', + massDelete = 'button[data-action=massdelete]', + toggleAll = '.t3js-toggle-all', +} + +/** + * Module: TYPO3/CMS/Recycler/Recycler + * RequireJS module for Recycler + */ +class Recycler { + public elements: any = {}; // filled in getElements() + public paging: any = { + currentPage: 1, + totalPages: 1, + totalItems: 0, + itemsPerPage: TYPO3.settings.Recycler.pagingSize, + }; + public markedRecordsForMassAction: Array<string> = []; + public allToggled: boolean = false; + + /** + * Reloads the page tree + */ + public static refreshPageTree(): void { + if (top.TYPO3 && top.TYPO3.Backend && top.TYPO3.Backend.NavigationContainer && top.TYPO3.Backend.NavigationContainer.PageTree) { + top.TYPO3.Backend.NavigationContainer.PageTree.refreshTree(); + } + } + + constructor() { + $((): void => { + this.initialize(); + }); + } + + /** + * Gets required elements + */ + private getElements(): void { + this.elements = { + $searchForm: $(RecyclerIdentifiers.searchForm), + $searchTextField: $(RecyclerIdentifiers.searchText), + $searchSubmitBtn: $(RecyclerIdentifiers.searchSubmitBtn), + $depthSelector: $(RecyclerIdentifiers.depthSelector), + $tableSelector: $(RecyclerIdentifiers.tableSelector), + $recyclerTable: $(RecyclerIdentifiers.recyclerTable), + $tableBody: $(RecyclerIdentifiers.recyclerTable).find('tbody'), + $paginator: $(RecyclerIdentifiers.paginator), + $reloadAction: $(RecyclerIdentifiers.reloadAction), + $massUndo: $(RecyclerIdentifiers.massUndo), + $massDelete: $(RecyclerIdentifiers.massDelete), + $toggleAll: $(RecyclerIdentifiers.toggleAll), + }; + } + + /** + * Register events + */ + private registerEvents(): void { + // submitting the form + this.elements.$searchForm.on('submit', (e: JQueryEventObject): void => { + e.preventDefault(); + if (this.elements.$searchTextField.val() !== '') { + this.loadDeletedElements(); + } + }); + + // changing the search field + this.elements.$searchTextField.on('keyup', (e: JQueryEventObject): void => { + let $me = $(e.currentTarget); + + if ($me.val() !== '') { + this.elements.$searchSubmitBtn.removeClass('disabled'); + } else { + this.elements.$searchSubmitBtn.addClass('disabled'); + this.loadDeletedElements(); + } + }).clearable( + { + onClear: () => { + this.elements.$searchSubmitBtn.addClass('disabled'); + this.loadDeletedElements(); + }, + }, + ); + + // changing "depth" + this.elements.$depthSelector.on('change', (): void => { + $.when(this.loadAvailableTables()).done((): void => { + this.loadDeletedElements(); + }); + }); + + // changing "table" + this.elements.$tableSelector.on('change', (): void => { + this.paging.currentPage = 1; + this.loadDeletedElements(); + }); + + // clicking "recover" in single row + this.elements.$recyclerTable.on('click', '[data-action=undo]', this.undoRecord); + + // clicking "delete" in single row + this.elements.$recyclerTable.on('click', '[data-action=delete]', this.deleteRecord); + + this.elements.$reloadAction.on('click', (e: JQueryEventObject): void => { + e.preventDefault(); + $.when(this.loadAvailableTables()).done((): void => { + this.loadDeletedElements(); + }); + }); + + // clicking an action in the paginator + this.elements.$paginator.on('click', 'a[data-action]', (e: JQueryEventObject): void => { + e.preventDefault(); + + const $el: JQuery = $(e.currentTarget); + let reload: boolean = false; + + switch ($el.data('action')) { + case 'previous': + if (this.paging.currentPage > 1) { + this.paging.currentPage--; + reload = true; + } + break; + case 'next': + if (this.paging.currentPage < this.paging.totalPages) { + this.paging.currentPage++; + reload = true; + } + break; + case 'page': + this.paging.currentPage = parseInt($el.find('span').text(), 10); + reload = true; + break; + default: + } + + if (reload) { + this.loadDeletedElements(); + } + }); + + if (!TYPO3.settings.Recycler.deleteDisable) { + this.elements.$massDelete.show(); + } else { + this.elements.$massDelete.remove(); + } + + this.elements.$recyclerTable.on('show.bs.collapse hide.bs.collapse', 'tr.collapse', (e: JQueryEventObject): void => { + let $trigger = $(e.currentTarget).prev('tr').find('[data-action=expand]'), + $iconEl = $trigger.find('.t3-icon'), + removeClass, + addClass; + + switch (e.type) { + case 'show': + removeClass = 't3-icon-pagetree-collapse'; + addClass = 't3-icon-pagetree-expand'; + break; + case 'hide': + removeClass = 't3-icon-pagetree-expand'; + addClass = 't3-icon-pagetree-collapse'; + break; + default: + } + + $iconEl.removeClass(removeClass).addClass(addClass); + }); + + // checkboxes in the table + this.elements.$toggleAll.on('click', (): void => { + this.allToggled = !this.allToggled; + $('input[type="checkbox"]').prop('checked', this.allToggled).trigger('change'); + }); + this.elements.$recyclerTable.on('change', 'tr input[type=checkbox]', this.handleCheckboxSelects); + + this.elements.$massUndo.on('click', this.undoRecord); + this.elements.$massDelete.on('click', this.deleteRecord); + } + + /** + * Initialize the recycler module + */ + private initialize(): void { + NProgress.configure({parent: '.module-loading-indicator', showSpinner: false}); + + this.getElements(); + this.registerEvents(); + + if (TYPO3.settings.Recycler.depthSelection > 0) { + this.elements.$depthSelector.val(TYPO3.settings.Recycler.depthSelection).trigger('change'); + } else { + $.when(this.loadAvailableTables()).done((): void => { + this.loadDeletedElements(); + }); + } + } + + /** + * Handles the clicks on checkboxes in the records table + */ + private handleCheckboxSelects = (e: JQueryEventObject): void => { + const $checkbox = $(e.currentTarget); + const $tr = $checkbox.parents('tr'); + const table = $tr.data('table'); + const uid = $tr.data('uid'); + const record = table + ':' + uid; + + if ($checkbox.prop('checked')) { + this.markedRecordsForMassAction.push(record); + $tr.addClass('warning'); + } else { + const index = this.markedRecordsForMassAction.indexOf(record); + if (index > -1) { + this.markedRecordsForMassAction.splice(index, 1); + } + $tr.removeClass('warning'); + } + + if (this.markedRecordsForMassAction.length > 0) { + if (this.elements.$massUndo.hasClass('disabled')) { + this.elements.$massUndo.removeClass('disabled').removeAttr('disabled'); + } + if (this.elements.$massDelete.hasClass('disabled')) { + this.elements.$massDelete.removeClass('disabled').removeAttr('disabled'); + } + + const btnTextUndo = this.createMessage(TYPO3.lang['button.undoselected'], [this.markedRecordsForMassAction.length]); + const btnTextDelete = this.createMessage(TYPO3.lang['button.deleteselected'], [this.markedRecordsForMassAction.length]); + + this.elements.$massUndo.find('span.text').text(btnTextUndo); + this.elements.$massDelete.find('span.text').text(btnTextDelete); + + } else { + this.resetMassActionButtons(); + } + } + + /** + * Resets the mass action state + */ + private resetMassActionButtons(): void { + this.markedRecordsForMassAction = []; + this.elements.$massUndo.addClass('disabled').attr('disabled', true); + this.elements.$massUndo.find('span.text').text(TYPO3.lang['button.undo']); + this.elements.$massDelete.addClass('disabled').attr('disabled', true); + this.elements.$massDelete.find('span.text').text(TYPO3.lang['button.delete']); + } + + /** + * Loads all tables which contain deleted records. + * + */ + private loadAvailableTables(): JQueryXHR { + return $.ajax({ + url: TYPO3.settings.ajaxUrls.recycler, + dataType: 'json', + data: { + action: 'getTables', + startUid: TYPO3.settings.Recycler.startUid, + depth: this.elements.$depthSelector.find('option:selected').val(), + }, + beforeSend: () => { + NProgress.start(); + this.elements.$tableSelector.val(''); + this.paging.currentPage = 1; + }, + success: (data: any) => { + const tables: Array<JQuery> = []; + this.elements.$tableSelector.children().remove(); + $.each(data, (_, value) => { + const tableName = value[0]; + const deletedRecords = value[1]; + const tableDescription = value[2] ? value[2] : TYPO3.lang.label_allrecordtypes; + const optionText = tableDescription + ' (' + deletedRecords + ')'; + tables.push($('<option />').val(tableName).text(optionText)); + }); + + if (tables.length > 0) { + this.elements.$tableSelector.append(tables); + if (TYPO3.settings.Recycler.tableSelection !== '') { + this.elements.$tableSelector.val(TYPO3.settings.Recycler.tableSelection); + } + } + }, + complete: () => { + NProgress.done(); + }, + }); + } + + /** + * Loads the deleted elements, based on the filters + */ + private loadDeletedElements(): JQueryXHR { + return $.ajax({ + url: TYPO3.settings.ajaxUrls.recycler, + dataType: 'json', + data: { + action: 'getDeletedRecords', + depth: this.elements.$depthSelector.find('option:selected').val(), + startUid: TYPO3.settings.Recycler.startUid, + table: this.elements.$tableSelector.find('option:selected').val(), + filterTxt: this.elements.$searchTextField.val(), + start: (this.paging.currentPage - 1) * this.paging.itemsPerPage, + limit: this.paging.itemsPerPage, + }, + beforeSend: () => { + NProgress.start(); + this.resetMassActionButtons(); + }, + success: (data: any) => { + this.elements.$tableBody.html(data.rows); + this.buildPaginator(data.totalItems); + }, + complete: () => { + NProgress.done(); + }, + }); + } + + private deleteRecord = (e: JQueryEventObject): void => { + if (TYPO3.settings.Recycler.deleteDisable) { + return; + } + + const $tr = $(e.currentTarget).parents('tr'); + const isMassDelete = $tr.parent().prop('tagName') !== 'TBODY'; // deleteRecord() was invoked by the mass delete button + let records: Array<string>; + let message: string; + + if (isMassDelete) { + records = this.markedRecordsForMassAction; + message = TYPO3.lang['modal.massdelete.text']; + } else { + const uid = $tr.data('uid'); + const table = $tr.data('table'); + const recordTitle = $tr.data('recordtitle'); + records = [table + ':' + uid]; + message = table === 'pages' ? TYPO3.lang['modal.deletepage.text'] : TYPO3.lang['modal.deletecontent.text']; + message = this.createMessage(message, [recordTitle, '[' + records[0] + ']']); + } + + Modal.confirm(TYPO3.lang['modal.delete.header'], message, Severity.error, [ + { + text: TYPO3.lang['button.cancel'], + btnClass: 'btn-default', + trigger: function(): void { + Modal.dismiss(); + }, + }, { + text: TYPO3.lang['button.delete'], + btnClass: 'btn-danger', + trigger: () => { + this.callAjaxAction('delete', records, isMassDelete); + }, + }, + ]); + } + + private undoRecord = (e: JQueryEventObject): void => { + const $tr = $(e.currentTarget).parents('tr'); + const isMassUndo = $tr.parent().prop('tagName') !== 'TBODY'; // undoRecord() was invoked by the mass delete button + + let records: Array<string>; + let messageText: string; + let recoverPages: boolean; + if (isMassUndo) { + records = this.markedRecordsForMassAction; + messageText = TYPO3.lang['modal.massundo.text']; + recoverPages = true; + } else { + const uid = $tr.data('uid'); + const table = $tr.data('table'); + const recordTitle = $tr.data('recordtitle'); + + records = [table + ':' + uid]; + recoverPages = table === 'pages'; + messageText = recoverPages ? TYPO3.lang['modal.undopage.text'] : TYPO3.lang['modal.undocontent.text']; + messageText = this.createMessage(messageText, [recordTitle, '[' + records[0] + ']']); + + if (recoverPages && $tr.data('parentDeleted')) { + messageText += TYPO3.lang['modal.undo.parentpages']; + } + } + + let $message: JQuery = null; + if (recoverPages) { + $message = $('<div />').append( + $('<p />').text(messageText), + $('<div />', {class: 'checkbox'}).append( + $('<label />').append(TYPO3.lang['modal.undo.recursive']).prepend($('<input />', { + id: 'undo-recursive', + type: 'checkbox', + })), + ), + ); + } else { + $message = $('<p />').text(messageText); + } + + Modal.confirm(TYPO3.lang['modal.undo.header'], $message, Severity.ok, [ + { + text: TYPO3.lang['button.cancel'], + btnClass: 'btn-default', + trigger: function(): void { + Modal.dismiss(); + }, + }, { + text: TYPO3.lang['button.undo'], + btnClass: 'btn-success', + trigger: () => { + this.callAjaxAction( + 'undo', + typeof records === 'object' ? records : [records], + isMassUndo, + $message.find('#undo-recursive').prop('checked'), + ); + }, + }, + ]); + } + + /** + * @param {string} action + * @param {Object} records + * @param {boolean} isMassAction + * @param {boolean} recursive + */ + private callAjaxAction(action: string, records: Object, isMassAction: boolean, recursive: boolean = false): void { + let data: any = { + records: records, + action: '', + }; + let reloadPageTree: boolean = false; + if (action === 'undo') { + data.action = 'undoRecords'; + data.recursive = recursive ? 1 : 0; + reloadPageTree = true; + } else if (action === 'delete') { + data.action = 'deleteRecords'; + } else { + return; + } + + $.ajax({ + url: TYPO3.settings.ajaxUrls.recycler, + type: 'POST', + dataType: 'json', + data: data, + beforeSend: () => { + NProgress.start(); + }, + success: (responseData: any) => { + if (responseData.success) { + Notification.success('', responseData.message); + } else { + Notification.error('', responseData.message); + } + + // reload recycler data + this.paging.currentPage = 1; + + $.when(this.loadAvailableTables()).done((): void => { + this.loadDeletedElements(); + if (isMassAction) { + this.resetMassActionButtons(); + } + + if (reloadPageTree) { + Recycler.refreshPageTree(); + } + + // Reset toggle state + this.allToggled = false; + }); + }, + complete: () => { + Modal.dismiss(); + NProgress.done(); + }, + }); + } + + /** + * Replaces the placeholders with actual values + */ + private createMessage(message: string, placeholders: Array<any>): string { + if (typeof message === 'undefined') { + return ''; + } + + return message.replace( + /\{([0-9]+)\}/g, + function(_: string, index: any): string { + return placeholders[index]; + }, + ); + } + + + /** + * Build the paginator + */ + private buildPaginator(totalItems: number): void { + if (totalItems === 0) { + this.elements.$paginator.contents().remove(); + return; + } + + this.paging.totalItems = totalItems; + this.paging.totalPages = Math.ceil(totalItems / this.paging.itemsPerPage); + + if (this.paging.totalPages === 1) { + // early abort if only one page is available + this.elements.$paginator.contents().remove(); + return; + } + + const $ul = $('<ul />', {class: 'pagination pagination-block'}), + liElements = [], + $controlFirstPage = $('<li />').append( + $('<a />', {'data-action': 'previous'}).append( + $('<span />', {class: 't3-icon fa fa-arrow-left'}), + ), + ), + $controlLastPage = $('<li />').append( + $('<a />', {'data-action': 'next'}).append( + $('<span />', {class: 't3-icon fa fa-arrow-right'}), + ), + ); + + if (this.paging.currentPage === 1) { + $controlFirstPage.disablePagingAction(); + } + + if (this.paging.currentPage === this.paging.totalPages) { + $controlLastPage.disablePagingAction(); + } + + for (let i = 1; i <= this.paging.totalPages; i++) { + const $li = $('<li />', {class: this.paging.currentPage === i ? 'active' : ''}); + $li.append( + $('<a />', {'data-action': 'page'}).append( + $('<span />').text(i), + ), + ); + liElements.push($li); + } + + $ul.append($controlFirstPage, liElements, $controlLastPage); + this.elements.$paginator.html($ul); + } +} + +/** + * Changes the markup of a pagination action being disabled + */ +$.fn.disablePagingAction = function(): void { + $(this).addClass('disabled').find('.t3-icon').unwrap().wrap($('<span />')); +}; + +export = new Recycler(); diff --git a/Build/types/TYPO3/index.d.ts b/Build/types/TYPO3/index.d.ts index 1bc52076a3658c132af1c536648b5bdd3a6e8785..447b8df4390a49d1be4568336279577c38e21c6f 100644 --- a/Build/types/TYPO3/index.d.ts +++ b/Build/types/TYPO3/index.d.ts @@ -142,4 +142,5 @@ interface JQuery { // To be able to use jquery/autocomplete-slider we have to override the definition of jquerui autocomplete(options?: { [key: string]: any }): any; + disablePagingAction(): void; } diff --git a/typo3/sysext/recycler/Resources/Public/JavaScript/Recycler.js b/typo3/sysext/recycler/Resources/Public/JavaScript/Recycler.js index 38d2eecc02f8a4d004d70ee1fdddb29c3265a8da..1c4cb625bfa8557e86137310de4ef01a6cff16e5 100644 --- a/typo3/sysext/recycler/Resources/Public/JavaScript/Recycler.js +++ b/typo3/sysext/recycler/Resources/Public/JavaScript/Recycler.js @@ -10,597 +10,4 @@ * * The TYPO3 project - inspiring people to share! */ - -/** - * Module: TYPO3/CMS/Recycler/Recycler - * RequireJS module for Recycler - */ -define(['jquery', - 'nprogress', - 'TYPO3/CMS/Backend/Modal', - 'TYPO3/CMS/Backend/Notification', - 'TYPO3/CMS/Backend/Severity', - 'TYPO3/CMS/Backend/jquery.clearable' -], function($, NProgress, Modal, Notification, Severity) { - 'use strict'; - - /** - * - * @type {{identifiers: {searchForm: string, searchText: string, searchSubmitBtn: string, depthSelector: string, tableSelector: string, recyclerTable: string, paginator: string, reloadAction: string, massUndo: string, massDelete: string, toggleAll: string}, elements: {}, paging: {currentPage: number, totalPages: number, totalItems: number, itemsPerPage: number}, markedRecordsForMassAction: Array, allToggled: boolean}} - * @exports TYPO3/CMS/Recycler/Recycler - */ - var Recycler = { - identifiers: { - searchForm: '#recycler-form', - searchText: '#recycler-form [name=search-text]', - searchSubmitBtn: '#recycler-form button[type=submit]', - depthSelector: '#recycler-form [name=depth]', - tableSelector: '#recycler-form [name=pages]', - recyclerTable: '#itemsInRecycler', - paginator: '#recycler-index nav', - reloadAction: 'a[data-action=reload]', - massUndo: 'button[data-action=massundo]', - massDelete: 'button[data-action=massdelete]', - toggleAll: '.t3js-toggle-all' - }, - elements: {}, // filled in getElements() - paging: { - currentPage: 1, - totalPages: 1, - totalItems: 0, - itemsPerPage: TYPO3.settings.Recycler.pagingSize - }, - markedRecordsForMassAction: [], - allToggled: false - }; - - /** - * Gets required elements - */ - Recycler.getElements = function() { - Recycler.elements = { - $searchForm: $(Recycler.identifiers.searchForm), - $searchTextField: $(Recycler.identifiers.searchText), - $searchSubmitBtn: $(Recycler.identifiers.searchSubmitBtn), - $depthSelector: $(Recycler.identifiers.depthSelector), - $tableSelector: $(Recycler.identifiers.tableSelector), - $recyclerTable: $(Recycler.identifiers.recyclerTable), - $tableBody: $(Recycler.identifiers.recyclerTable).find('tbody'), - $paginator: $(Recycler.identifiers.paginator), - $reloadAction: $(Recycler.identifiers.reloadAction), - $massUndo: $(Recycler.identifiers.massUndo), - $massDelete: $(Recycler.identifiers.massDelete), - $toggleAll: $(Recycler.identifiers.toggleAll) - }; - }; - - /** - * Register events - */ - Recycler.registerEvents = function() { - // submitting the form - Recycler.elements.$searchForm.on('submit', function(e) { - e.preventDefault(); - if (Recycler.elements.$searchTextField.val() !== '') { - Recycler.loadDeletedElements(); - } - }); - - // changing the search field - Recycler.elements.$searchTextField.on('keyup', function() { - var $me = $(this); - - if ($me.val() !== '') { - Recycler.elements.$searchSubmitBtn.removeClass('disabled'); - } else { - Recycler.elements.$searchSubmitBtn.addClass('disabled'); - Recycler.loadDeletedElements(); - } - }).clearable( - { - onClear: function() { - Recycler.elements.$searchSubmitBtn.addClass('disabled'); - Recycler.loadDeletedElements(); - } - } - ); - - // changing "depth" - Recycler.elements.$depthSelector.on('change', function() { - $.when(Recycler.loadAvailableTables()).done(function() { - Recycler.loadDeletedElements(); - }); - }); - - // changing "table" - Recycler.elements.$tableSelector.on('change', function() { - Recycler.paging.currentPage = 1; - Recycler.loadDeletedElements(); - }); - - // clicking "recover" in single row - Recycler.elements.$recyclerTable.on('click', '[data-action=undo]', Recycler.undoRecord); - - // clicking "delete" in single row - Recycler.elements.$recyclerTable.on('click', '[data-action=delete]', Recycler.deleteRecord); - - Recycler.elements.$reloadAction.on('click', function(e) { - e.preventDefault(); - $.when(Recycler.loadAvailableTables()).done(function() { - Recycler.loadDeletedElements(); - }); - }); - - // clicking an action in the paginator - Recycler.elements.$paginator.on('click', 'a[data-action]', function(e) { - e.preventDefault(); - - var $el = $(this), - reload = false; - - switch ($el.data('action')) { - case 'previous': - if (Recycler.paging.currentPage > 1) { - Recycler.paging.currentPage--; - reload = true; - } - break; - case 'next': - if (Recycler.paging.currentPage < Recycler.paging.totalPages) { - Recycler.paging.currentPage++; - reload = true; - } - break; - case 'page': - Recycler.paging.currentPage = parseInt($el.find('span').text()); - reload = true; - break; - } - - if (reload) { - Recycler.loadDeletedElements(); - } - }); - - if (!TYPO3.settings.Recycler.deleteDisable) { - Recycler.elements.$massDelete.show(); - } else { - Recycler.elements.$massDelete.remove(); - } - - Recycler.elements.$recyclerTable.on('show.bs.collapse hide.bs.collapse', 'tr.collapse', function(e) { - var $trigger = $(e.currentTarget).prev('tr').find('[data-action=expand]'), - $iconEl = $trigger.find('.t3-icon'), - removeClass, - addClass; - - switch (e.type) { - case 'show': - removeClass = 't3-icon-pagetree-collapse'; - addClass = 't3-icon-pagetree-expand'; - break; - case 'hide': - removeClass = 't3-icon-pagetree-expand'; - addClass = 't3-icon-pagetree-collapse'; - break; - } - - $iconEl.removeClass(removeClass).addClass(addClass); - }); - - // 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); - }; - - /** - * Initialize the recycler module - */ - Recycler.initialize = function() { - NProgress.configure({parent: '.module-loading-indicator', showSpinner: false}); - - Recycler.getElements(); - Recycler.registerEvents(); - - if (TYPO3.settings.Recycler.depthSelection > 0) { - Recycler.elements.$depthSelector.val(TYPO3.settings.Recycler.depthSelection).trigger('change'); - } else { - $.when(Recycler.loadAvailableTables()).done(function() { - Recycler.loadDeletedElements(); - }); - } - }; - - /** - * Handles the clicks on checkboxes in the records table - */ - Recycler.handleCheckboxSelects = function() { - var $checkbox = $(this), - $tr = $checkbox.parents('tr'), - 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').removeAttr('disabled'); - } - if (Recycler.elements.$massDelete.hasClass('disabled')) { - Recycler.elements.$massDelete.removeClass('disabled').removeAttr('disabled'); - } - - 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(); - } - }; - - /** - * Resets the mass action state - */ - Recycler.resetMassActionButtons = function() { - Recycler.markedRecordsForMassAction = []; - Recycler.elements.$massUndo.addClass('disabled').attr('disabled', true); - Recycler.elements.$massUndo.find('span.text').text(TYPO3.lang['button.undo']); - Recycler.elements.$massDelete.addClass('disabled').attr('disabled', true); - Recycler.elements.$massDelete.find('span.text').text(TYPO3.lang['button.delete']); - }; - - /** - * Loads all tables which contain deleted records. - * - * @returns {Promise} - */ - Recycler.loadAvailableTables = function() { - return $.ajax({ - url: TYPO3.settings.ajaxUrls['recycler'], - dataType: 'json', - data: { - action: 'getTables', - startUid: TYPO3.settings.Recycler.startUid, - depth: Recycler.elements.$depthSelector.find('option:selected').val() - }, - beforeSend: function() { - NProgress.start(); - Recycler.elements.$tableSelector.val(''); - Recycler.paging.currentPage = 1; - }, - success: function(data) { - var tables = []; - Recycler.elements.$tableSelector.children().remove(); - $.each(data, function(_, value) { - var tableName = value[0], - deletedRecords = value[1], - tableDescription = value[2]; - - if (tableDescription === '') { - tableDescription = TYPO3.lang['label_allrecordtypes']; - } - var optionText = tableDescription + ' (' + deletedRecords + ')'; - tables.push($('<option />').val(tableName).text(optionText)) - }); - - if (tables.length > 0) { - Recycler.elements.$tableSelector.append(tables); - if (TYPO3.settings.Recycler.tableSelection !== '') { - Recycler.elements.$tableSelector.val(TYPO3.settings.Recycler.tableSelection); - } - } - }, - complete: function() { - NProgress.done(); - } - }); - }; - - /** - * Loads the deleted elements, based on the filters - * - * @returns {Promise} - */ - Recycler.loadDeletedElements = function() { - return $.ajax({ - url: TYPO3.settings.ajaxUrls['recycler'], - dataType: 'json', - data: { - action: 'getDeletedRecords', - depth: Recycler.elements.$depthSelector.find('option:selected').val(), - startUid: TYPO3.settings.Recycler.startUid, - table: Recycler.elements.$tableSelector.find('option:selected').val(), - filterTxt: Recycler.elements.$searchTextField.val(), - start: (Recycler.paging.currentPage - 1) * Recycler.paging.itemsPerPage, - limit: Recycler.paging.itemsPerPage - }, - beforeSend: function() { - NProgress.start(); - Recycler.resetMassActionButtons(); - }, - success: function(data) { - Recycler.elements.$tableBody.html(data.rows); - Recycler.buildPaginator(data.totalItems); - }, - complete: function() { - NProgress.done(); - } - }); - }; - - /** - * - */ - Recycler.deleteRecord = function() { - 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; - message = TYPO3.lang['modal.massdelete.text']; - } else { - var uid = $tr.data('uid'), - table = $tr.data('table'), - recordTitle = $tr.data('recordtitle'); - records = table + ':' + uid; - message = table === 'pages' ? TYPO3.lang['modal.deletepage.text'] : TYPO3.lang['modal.deletecontent.text']; - message = Recycler.createMessage(message, [recordTitle, '[' + records + ']']); - } - - Modal.confirm(TYPO3.lang['modal.delete.header'], message, Severity.error, [ - { - text: TYPO3.lang['button.cancel'], - btnClass: 'btn-default', - trigger: function() { - Modal.dismiss(); - } - }, { - text: TYPO3.lang['button.delete'], - btnClass: 'btn-danger', - trigger: function() { - Recycler.callAjaxAction('delete', typeof records === 'object' ? records : [records], isMassDelete); - } - } - ]); - }; - - /** - * - */ - Recycler.undoRecord = function() { - var $tr = $(this).parents('tr'), - isMassUndo = $tr.parent().prop('tagName') !== 'TBODY'; // undoRecord() was invoked by the mass delete button - - var records, messageText, recoverPages; - if (isMassUndo) { - records = Recycler.markedRecordsForMassAction; - messageText = TYPO3.lang['modal.massundo.text']; - recoverPages = true; - } else { - var uid = $tr.data('uid'), - table = $tr.data('table'), - recordTitle = $tr.data('recordtitle'); - - records = table + ':' + uid; - recoverPages = table === 'pages'; - messageText = recoverPages ? TYPO3.lang['modal.undopage.text'] : TYPO3.lang['modal.undocontent.text']; - messageText = Recycler.createMessage(messageText, [recordTitle, '[' + records + ']']); - - if (recoverPages && $tr.data('parentDeleted')) { - messageText += TYPO3.lang['modal.undo.parentpages']; - } - } - - var $message = null; - if (recoverPages) { - $message = $('<div />').append( - $('<p />').text(messageText), - $('<div />', {class: 'checkbox'}).append( - $('<label />').append(TYPO3.lang['modal.undo.recursive']).prepend($('<input />', { - id: 'undo-recursive', - type: 'checkbox' - })) - ) - ); - } else { - $message = $('<p />').text(messageText); - } - - Modal.confirm(TYPO3.lang['modal.undo.header'], $message, Severity.ok, [ - { - text: TYPO3.lang['button.cancel'], - btnClass: 'btn-default', - trigger: function() { - Modal.dismiss(); - } - }, { - 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); - } - } - ]); - }; - - /** - * - * @param {String} action - * @param {Object} records - * @param {Boolean} isMassAction - * @param {Boolean} recursive - */ - Recycler.callAjaxAction = function(action, records, isMassAction, recursive) { - var data = { - records: records, - action: '' - }, - reloadPageTree = false; - if (action === 'undo') { - data.action = 'undoRecords'; - data.recursive = recursive ? 1 : 0; - reloadPageTree = true; - } else if (action === 'delete') { - data.action = 'deleteRecords'; - } else { - return; - } - - $.ajax({ - url: TYPO3.settings.ajaxUrls['recycler'], - type: 'POST', - dataType: 'json', - data: data, - beforeSend: function() { - NProgress.start(); - }, - success: function(data) { - if (data.success) { - Notification.success('', data.message); - } else { - Notification.error('', data.message); - } - - // reload recycler data - Recycler.paging.currentPage = 1; - - $.when(Recycler.loadAvailableTables()).done(function() { - Recycler.loadDeletedElements(); - if (isMassAction) { - Recycler.resetMassActionButtons(); - } - - if (reloadPageTree) { - Recycler.refreshPageTree(); - } - - // Reset toggle state - Recycler.allToggled = false; - }); - }, - complete: function() { - Modal.dismiss(); - NProgress.done(); - } - }); - }; - - /** - * Replaces the placeholders with actual values - * - * @param {String} message - * @param {Array} placeholders - * @returns {*} - */ - Recycler.createMessage = function(message, placeholders) { - if (typeof message === 'undefined') { - return ''; - } - - return message.replace( - /\{([0-9]+)\}/g, - function(_, index) { - return placeholders[index]; - } - ); - }; - - /** - * Reloads the page tree - */ - Recycler.refreshPageTree = function() { - if (top.TYPO3 && top.TYPO3.Backend && top.TYPO3.Backend.NavigationContainer && top.TYPO3.Backend.NavigationContainer.PageTree) { - top.TYPO3.Backend.NavigationContainer.PageTree.refreshTree(); - } - }; - - /** - * Build the paginator - * - * @param {Number} totalItems - */ - Recycler.buildPaginator = function(totalItems) { - if (totalItems === 0) { - Recycler.elements.$paginator.contents().remove(); - return; - } - - Recycler.paging.totalItems = totalItems; - Recycler.paging.totalPages = Math.ceil(totalItems / Recycler.paging.itemsPerPage); - - if (Recycler.paging.totalPages === 1) { - // early abort if only one page is available - Recycler.elements.$paginator.contents().remove(); - return; - } - - var $ul = $('<ul />', {class: 'pagination pagination-block'}), - liElements = [], - $controlFirstPage = $('<li />').append( - $('<a />', {'data-action': 'previous'}).append( - $('<span />', {class: 't3-icon fa fa-arrow-left'}) - ) - ), - $controlLastPage = $('<li />').append( - $('<a />', {'data-action': 'next'}).append( - $('<span />', {class: 't3-icon fa fa-arrow-right'}) - ) - ); - - if (Recycler.paging.currentPage === 1) { - $controlFirstPage.disablePagingAction(); - } - - if (Recycler.paging.currentPage === Recycler.paging.totalPages) { - $controlLastPage.disablePagingAction(); - } - - for (var i = 1; i <= Recycler.paging.totalPages; i++) { - var $li = $('<li />', {class: Recycler.paging.currentPage === i ? 'active' : ''}); - $li.append( - $('<a />', {'data-action': 'page'}).append( - $('<span />').text(i) - ) - ); - liElements.push($li); - } - - $ul.append($controlFirstPage, liElements, $controlLastPage); - Recycler.elements.$paginator.html($ul); - }; - - /** - * Changes the markup of a pagination action being disabled - */ - $.fn.disablePagingAction = function() { - $(this).addClass('disabled').find('.t3-icon').unwrap().wrap($('<span />')); - }; - - $(Recycler.initialize); - - return Recycler; -}); +define(["require","exports","jquery","nprogress","TYPO3/CMS/Backend/Modal","TYPO3/CMS/Backend/Notification","TYPO3/CMS/Backend/Severity","TYPO3/CMS/Backend/jquery.clearable"],function(e,t,a,n,s,l,r){"use strict";var o,i;(i=o||(o={})).searchForm="#recycler-form",i.searchText="#recycler-form [name=search-text]",i.searchSubmitBtn="#recycler-form button[type=submit]",i.depthSelector="#recycler-form [name=depth]",i.tableSelector="#recycler-form [name=pages]",i.recyclerTable="#itemsInRecycler",i.paginator="#recycler-index nav",i.reloadAction="a[data-action=reload]",i.massUndo="button[data-action=massundo]",i.massDelete="button[data-action=massdelete]",i.toggleAll=".t3js-toggle-all";var c=function(){function e(){var e=this;this.elements={},this.paging={currentPage:1,totalPages:1,totalItems:0,itemsPerPage:TYPO3.settings.Recycler.pagingSize},this.markedRecordsForMassAction=[],this.allToggled=!1,this.handleCheckboxSelects=function(t){var n=a(t.currentTarget),s=n.parents("tr"),l=s.data("table")+":"+s.data("uid");if(n.prop("checked"))e.markedRecordsForMassAction.push(l),s.addClass("warning");else{var r=e.markedRecordsForMassAction.indexOf(l);r>-1&&e.markedRecordsForMassAction.splice(r,1),s.removeClass("warning")}if(e.markedRecordsForMassAction.length>0){e.elements.$massUndo.hasClass("disabled")&&e.elements.$massUndo.removeClass("disabled").removeAttr("disabled"),e.elements.$massDelete.hasClass("disabled")&&e.elements.$massDelete.removeClass("disabled").removeAttr("disabled");var o=e.createMessage(TYPO3.lang["button.undoselected"],[e.markedRecordsForMassAction.length]),i=e.createMessage(TYPO3.lang["button.deleteselected"],[e.markedRecordsForMassAction.length]);e.elements.$massUndo.find("span.text").text(o),e.elements.$massDelete.find("span.text").text(i)}else e.resetMassActionButtons()},this.deleteRecord=function(t){if(!TYPO3.settings.Recycler.deleteDisable){var n,l,o=a(t.currentTarget).parents("tr"),i="TBODY"!==o.parent().prop("tagName");if(i)n=e.markedRecordsForMassAction,l=TYPO3.lang["modal.massdelete.text"];else{var c=o.data("uid"),d=o.data("table"),g=o.data("recordtitle");n=[d+":"+c],l="pages"===d?TYPO3.lang["modal.deletepage.text"]:TYPO3.lang["modal.deletecontent.text"],l=e.createMessage(l,[g,"["+n[0]+"]"])}s.confirm(TYPO3.lang["modal.delete.header"],l,r.error,[{text:TYPO3.lang["button.cancel"],btnClass:"btn-default",trigger:function(){s.dismiss()}},{text:TYPO3.lang["button.delete"],btnClass:"btn-danger",trigger:function(){e.callAjaxAction("delete",n,i)}}])}},this.undoRecord=function(t){var n,l,o,i=a(t.currentTarget).parents("tr"),c="TBODY"!==i.parent().prop("tagName");if(c)n=e.markedRecordsForMassAction,l=TYPO3.lang["modal.massundo.text"],o=!0;else{var d=i.data("uid"),g=i.data("table"),p=i.data("recordtitle");n=[g+":"+d],l=(o="pages"===g)?TYPO3.lang["modal.undopage.text"]:TYPO3.lang["modal.undocontent.text"],l=e.createMessage(l,[p,"["+n[0]+"]"]),o&&i.data("parentDeleted")&&(l+=TYPO3.lang["modal.undo.parentpages"])}var m=null;m=o?a("<div />").append(a("<p />").text(l),a("<div />",{class:"checkbox"}).append(a("<label />").append(TYPO3.lang["modal.undo.recursive"]).prepend(a("<input />",{id:"undo-recursive",type:"checkbox"})))):a("<p />").text(l),s.confirm(TYPO3.lang["modal.undo.header"],m,r.ok,[{text:TYPO3.lang["button.cancel"],btnClass:"btn-default",trigger:function(){s.dismiss()}},{text:TYPO3.lang["button.undo"],btnClass:"btn-success",trigger:function(){e.callAjaxAction("undo","object"==typeof n?n:[n],c,m.find("#undo-recursive").prop("checked"))}}])},a(function(){e.initialize()})}return e.refreshPageTree=function(){top.TYPO3&&top.TYPO3.Backend&&top.TYPO3.Backend.NavigationContainer&&top.TYPO3.Backend.NavigationContainer.PageTree&&top.TYPO3.Backend.NavigationContainer.PageTree.refreshTree()},e.prototype.getElements=function(){this.elements={$searchForm:a(o.searchForm),$searchTextField:a(o.searchText),$searchSubmitBtn:a(o.searchSubmitBtn),$depthSelector:a(o.depthSelector),$tableSelector:a(o.tableSelector),$recyclerTable:a(o.recyclerTable),$tableBody:a(o.recyclerTable).find("tbody"),$paginator:a(o.paginator),$reloadAction:a(o.reloadAction),$massUndo:a(o.massUndo),$massDelete:a(o.massDelete),$toggleAll:a(o.toggleAll)}},e.prototype.registerEvents=function(){var e=this;this.elements.$searchForm.on("submit",function(t){t.preventDefault(),""!==e.elements.$searchTextField.val()&&e.loadDeletedElements()}),this.elements.$searchTextField.on("keyup",function(t){""!==a(t.currentTarget).val()?e.elements.$searchSubmitBtn.removeClass("disabled"):(e.elements.$searchSubmitBtn.addClass("disabled"),e.loadDeletedElements())}).clearable({onClear:function(){e.elements.$searchSubmitBtn.addClass("disabled"),e.loadDeletedElements()}}),this.elements.$depthSelector.on("change",function(){a.when(e.loadAvailableTables()).done(function(){e.loadDeletedElements()})}),this.elements.$tableSelector.on("change",function(){e.paging.currentPage=1,e.loadDeletedElements()}),this.elements.$recyclerTable.on("click","[data-action=undo]",this.undoRecord),this.elements.$recyclerTable.on("click","[data-action=delete]",this.deleteRecord),this.elements.$reloadAction.on("click",function(t){t.preventDefault(),a.when(e.loadAvailableTables()).done(function(){e.loadDeletedElements()})}),this.elements.$paginator.on("click","a[data-action]",function(t){t.preventDefault();var n=a(t.currentTarget),s=!1;switch(n.data("action")){case"previous":e.paging.currentPage>1&&(e.paging.currentPage--,s=!0);break;case"next":e.paging.currentPage<e.paging.totalPages&&(e.paging.currentPage++,s=!0);break;case"page":e.paging.currentPage=parseInt(n.find("span").text(),10),s=!0}s&&e.loadDeletedElements()}),TYPO3.settings.Recycler.deleteDisable?this.elements.$massDelete.remove():this.elements.$massDelete.show(),this.elements.$recyclerTable.on("show.bs.collapse hide.bs.collapse","tr.collapse",function(e){var t,n,s=a(e.currentTarget).prev("tr").find("[data-action=expand]").find(".t3-icon");switch(e.type){case"show":t="t3-icon-pagetree-collapse",n="t3-icon-pagetree-expand";break;case"hide":t="t3-icon-pagetree-expand",n="t3-icon-pagetree-collapse"}s.removeClass(t).addClass(n)}),this.elements.$toggleAll.on("click",function(){e.allToggled=!e.allToggled,a('input[type="checkbox"]').prop("checked",e.allToggled).trigger("change")}),this.elements.$recyclerTable.on("change","tr input[type=checkbox]",this.handleCheckboxSelects),this.elements.$massUndo.on("click",this.undoRecord),this.elements.$massDelete.on("click",this.deleteRecord)},e.prototype.initialize=function(){var e=this;n.configure({parent:".module-loading-indicator",showSpinner:!1}),this.getElements(),this.registerEvents(),TYPO3.settings.Recycler.depthSelection>0?this.elements.$depthSelector.val(TYPO3.settings.Recycler.depthSelection).trigger("change"):a.when(this.loadAvailableTables()).done(function(){e.loadDeletedElements()})},e.prototype.resetMassActionButtons=function(){this.markedRecordsForMassAction=[],this.elements.$massUndo.addClass("disabled").attr("disabled",!0),this.elements.$massUndo.find("span.text").text(TYPO3.lang["button.undo"]),this.elements.$massDelete.addClass("disabled").attr("disabled",!0),this.elements.$massDelete.find("span.text").text(TYPO3.lang["button.delete"])},e.prototype.loadAvailableTables=function(){var e=this;return a.ajax({url:TYPO3.settings.ajaxUrls.recycler,dataType:"json",data:{action:"getTables",startUid:TYPO3.settings.Recycler.startUid,depth:this.elements.$depthSelector.find("option:selected").val()},beforeSend:function(){n.start(),e.elements.$tableSelector.val(""),e.paging.currentPage=1},success:function(t){var n=[];e.elements.$tableSelector.children().remove(),a.each(t,function(e,t){var s=t[0],l=t[1],r=(t[2]?t[2]:TYPO3.lang.label_allrecordtypes)+" ("+l+")";n.push(a("<option />").val(s).text(r))}),n.length>0&&(e.elements.$tableSelector.append(n),""!==TYPO3.settings.Recycler.tableSelection&&e.elements.$tableSelector.val(TYPO3.settings.Recycler.tableSelection))},complete:function(){n.done()}})},e.prototype.loadDeletedElements=function(){var e=this;return a.ajax({url:TYPO3.settings.ajaxUrls.recycler,dataType:"json",data:{action:"getDeletedRecords",depth:this.elements.$depthSelector.find("option:selected").val(),startUid:TYPO3.settings.Recycler.startUid,table:this.elements.$tableSelector.find("option:selected").val(),filterTxt:this.elements.$searchTextField.val(),start:(this.paging.currentPage-1)*this.paging.itemsPerPage,limit:this.paging.itemsPerPage},beforeSend:function(){n.start(),e.resetMassActionButtons()},success:function(t){e.elements.$tableBody.html(t.rows),e.buildPaginator(t.totalItems)},complete:function(){n.done()}})},e.prototype.callAjaxAction=function(t,r,o,i){var c=this;void 0===i&&(i=!1);var d={records:r,action:""},g=!1;if("undo"===t)d.action="undoRecords",d.recursive=i?1:0,g=!0;else{if("delete"!==t)return;d.action="deleteRecords"}a.ajax({url:TYPO3.settings.ajaxUrls.recycler,type:"POST",dataType:"json",data:d,beforeSend:function(){n.start()},success:function(t){t.success?l.success("",t.message):l.error("",t.message),c.paging.currentPage=1,a.when(c.loadAvailableTables()).done(function(){c.loadDeletedElements(),o&&c.resetMassActionButtons(),g&&e.refreshPageTree(),c.allToggled=!1})},complete:function(){s.dismiss(),n.done()}})},e.prototype.createMessage=function(e,t){return void 0===e?"":e.replace(/\{([0-9]+)\}/g,function(e,a){return t[a]})},e.prototype.buildPaginator=function(e){if(0!==e)if(this.paging.totalItems=e,this.paging.totalPages=Math.ceil(e/this.paging.itemsPerPage),1!==this.paging.totalPages){var t=a("<ul />",{class:"pagination pagination-block"}),n=[],s=a("<li />").append(a("<a />",{"data-action":"previous"}).append(a("<span />",{class:"t3-icon fa fa-arrow-left"}))),l=a("<li />").append(a("<a />",{"data-action":"next"}).append(a("<span />",{class:"t3-icon fa fa-arrow-right"})));1===this.paging.currentPage&&s.disablePagingAction(),this.paging.currentPage===this.paging.totalPages&&l.disablePagingAction();for(var r=1;r<=this.paging.totalPages;r++){var o=a("<li />",{class:this.paging.currentPage===r?"active":""});o.append(a("<a />",{"data-action":"page"}).append(a("<span />").text(r))),n.push(o)}t.append(s,n,l),this.elements.$paginator.html(t)}else this.elements.$paginator.contents().remove();else this.elements.$paginator.contents().remove()},e}();return a.fn.disablePagingAction=function(){a(this).addClass("disabled").find(".t3-icon").unwrap().wrap(a("<span />"))},new c}); \ No newline at end of file