Commit 9113f9d2 authored by Mikel Martín Corrales's avatar Mikel Martín Corrales Committed by David Matamoros
Browse files

MDL-72588 reportbuilder: convert editing interface into a tab.



In preparation for further tabs in this interface, convert the report
editor to use the Dynamic tabs API.

At the same time, update the editor JS and it's modules to add listeners
on `document` instead of the report element itself, which will move in
and out of the current DOM in the future as tabs are switched.

Co-authored-by: Paul Holden's avatarPaul Holden <paulh@moodle.com>
parent b2dd94ab
......@@ -66,6 +66,7 @@ $string['deletefilterconfirm'] = 'Are you sure you want to delete the filter \'{
$string['deletereport'] = 'Delete report';
$string['deletereportconfirm'] = 'Are you sure you want to delete the report \'{$a}\' and all associated data?';
$string['editdetails'] = 'Edit details';
$string['editor'] = 'Editor';
$string['editreportcontent'] = 'Edit report content';
$string['editreportdetails'] = 'Edit report details';
$string['editreportname'] = 'Edit report name';
......
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
......@@ -40,12 +40,10 @@ let initialized = false;
* Initialise editor and all it's modules
*/
export const init = () => {
const reportElement = document.querySelector(reportSelectors.regions.report);
columnsEditorInit(reportElement, initialized);
conditionsEditorInit(reportElement, initialized);
filtersEditorInit(reportElement, initialized);
sortingEditorInit(reportElement, initialized);
columnsEditorInit(initialized);
conditionsEditorInit(initialized);
filtersEditorInit(initialized);
sortingEditorInit(initialized);
// Ensure we only add our listeners once (can be called multiple times by mustache template).
if (initialized) {
......@@ -60,6 +58,7 @@ export const init = () => {
if (toggleEditViewMode) {
event.preventDefault();
const reportElement = event.target.closest(reportSelectors.regions.report);
const pendingPromise = new Pending('core_reportbuilder/reports:get');
const toggledEditMode = toggleEditViewMode.dataset.editMode !== "1";
......@@ -68,10 +67,10 @@ export const init = () => {
getReport(reportElement.dataset.reportId, toggledEditMode)
.then(response => {
customjs = response.javascript;
return Templates.render('core_reportbuilder/custom_report', response);
return Templates.render('core_reportbuilder/local/dynamictabs/editor', response);
})
.then((html, js) => {
return Templates.replaceNodeContents(reportElement, html, js + customjs);
return Templates.replaceNode(reportElement, html, js + customjs);
})
.then(() => pendingPromise.resolve())
.catch(Notification.exception);
......
......@@ -40,15 +40,14 @@ import {addColumn, deleteColumn, reorderColumn} from 'core_reportbuilder/local/r
/**
* Initialise module
*
* @param {Element} reportElement
* @param {Boolean} initialized Ensure we only add our listeners once
*/
export const init = (reportElement, initialized) => {
export const init = (initialized) => {
if (initialized) {
return;
}
reportElement.addEventListener('click', event => {
document.addEventListener('click', event => {
// Add column to report.
const reportAddColumn = event.target.closest(reportSelectors.actions.reportAddColumn);
......@@ -56,6 +55,7 @@ export const init = (reportElement, initialized) => {
event.preventDefault();
const pendingPromise = new Pending('core_reportbuilder/columns:add');
const reportElement = reportAddColumn.closest(reportSelectors.regions.report);
addColumn(reportElement.dataset.reportId, reportAddColumn.dataset.uniqueIdentifier)
.then(data => publish(reportEvents.publish.reportColumnsUpdated, data))
......@@ -73,6 +73,7 @@ export const init = (reportElement, initialized) => {
if (reportRemoveColumn) {
event.preventDefault();
const reportElement = reportRemoveColumn.closest(reportSelectors.regions.report);
const columnHeader = reportRemoveColumn.closest(reportSelectors.regions.columnHeader);
const columnName = columnHeader.dataset.columnName;
......@@ -103,7 +104,8 @@ export const init = (reportElement, initialized) => {
var columnSortableList = new SortableList(`${reportSelectors.regions.reportTable} thead tr`, {isHorizontal: true});
columnSortableList.getElementName = element => Promise.resolve(element.data('columnName'));
$(reportElement).on(SortableList.EVENTS.DRAG, 'th[data-column-id]', (event, info) => {
$(document).on(SortableList.EVENTS.DRAG, `${reportSelectors.regions.report} th[data-column-id]`, (event, info) => {
const reportElement = event.target.closest(reportSelectors.regions.report);
const columnPosition = info.element.data('columnPosition');
const targetColumnPosition = info.targetNextElement.data('columnPosition');
......@@ -118,10 +120,10 @@ export const init = (reportElement, initialized) => {
});
});
$(reportElement).on(SortableList.EVENTS.DROP, 'th[data-column-id]', (event, info) => {
$(document).on(SortableList.EVENTS.DROP, `${reportSelectors.regions.report} th[data-column-id]`, (event, info) => {
if (info.positionChanged) {
const pendingPromise = new Pending('core_reportbuilder/columns:reorder');
const reportElement = event.target.closest(reportSelectors.regions.report);
const columnId = info.element.data('columnId');
const columnName = info.element.data('columnName');
const columnPosition = info.element.data('columnPosition');
......@@ -144,10 +146,11 @@ export const init = (reportElement, initialized) => {
});
// Initialize inplace editable listeners for column aggregation.
reportElement.addEventListener(inplaceEditableEvents.elementUpdated, event => {
document.addEventListener(inplaceEditableEvents.elementUpdated, event => {
const columnAggregation = event.target.closest('[data-itemtype="columnaggregation"]');
if (columnAggregation) {
const reportElement = columnAggregation.closest(reportSelectors.regions.report);
const columnHeader = columnAggregation.closest(reportSelectors.regions.columnHeader);
getString('columnaggregated', 'core_reportbuilder', columnHeader.dataset.columnName)
......
......@@ -58,11 +58,10 @@ const reloadSettingsConditionsRegion = (reportElement, templateContext) => {
/**
* Initialise conditions form, must be called on each init because the form container is re-created when switching editor modes
*
* @param {Element} reportElement
*/
const initConditionsForm = reportElement => {
const initConditionsForm = () => {
// Handle dynamic conditions form.
const reportElement = document.querySelector(reportSelectors.regions.report);
const conditionFormContainer = reportElement.querySelector(reportSelectors.regions.settingsConditions);
const conditionForm = new DynamicForm(conditionFormContainer, '\\core_reportbuilder\\form\\condition');
......@@ -99,22 +98,23 @@ const initConditionsForm = reportElement => {
/**
* Initialise module
*
* @param {Element} reportElement
* @param {Boolean} initialized Ensure we only add our listeners once
*/
export const init = (reportElement, initialized) => {
initConditionsForm(reportElement);
export const init = (initialized) => {
initConditionsForm();
if (initialized) {
return;
}
reportElement.addEventListener('click', event => {
document.addEventListener('click', event => {
// Add condition to report.
const reportAddCondition = event.target.closest(reportSelectors.actions.reportAddCondition);
if (reportAddCondition) {
event.preventDefault();
const reportElement = reportAddCondition.closest(reportSelectors.regions.report);
// Check if dropdown is closed with no condition selected.
if (reportAddCondition.value === '0') {
return;
......@@ -139,6 +139,7 @@ export const init = (reportElement, initialized) => {
if (reportRemoveCondition) {
event.preventDefault();
const reportElement = reportRemoveCondition.closest(reportSelectors.regions.report);
const conditionContainer = reportRemoveCondition.closest(reportSelectors.regions.activeCondition);
const conditionName = conditionContainer.dataset.conditionName;
......@@ -170,9 +171,10 @@ export const init = (reportElement, initialized) => {
{isHorizontal: false});
activeConditionsSortableList.getElementName = element => Promise.resolve(element.data('conditionName'));
$(reportElement).on(SortableList.EVENTS.DROP, reportSelectors.regions.activeCondition, (event, info) => {
$(document).on(SortableList.EVENTS.DROP, reportSelectors.regions.activeCondition, (event, info) => {
if (info.positionChanged) {
const pendingPromise = new Pending('core_reportbuilder/conditions:reorder');
const reportElement = event.target.closest(reportSelectors.regions.report);
const conditionId = info.element.data('conditionId');
const conditionPosition = info.element.data('conditionPosition');
......
......@@ -57,21 +57,22 @@ const reloadSettingsFiltersRegion = (reportElement, templateContext) => {
/**
* Initialise module
*
* @param {Element} reportElement
* @param {Boolean} initialized Ensure we only add our listeners once
*/
export const init = (reportElement, initialized) => {
export const init = (initialized) => {
if (initialized) {
return;
}
reportElement.addEventListener('click', event => {
document.addEventListener('click', event => {
// Add filter to report.
const reportAddFilter = event.target.closest(reportSelectors.actions.reportAddFilter);
if (reportAddFilter) {
event.preventDefault();
const reportElement = reportAddFilter.closest(reportSelectors.regions.report);
// Check if dropdown is closed with no filter selected.
if (reportAddFilter.value === '0') {
return;
......@@ -93,6 +94,7 @@ export const init = (reportElement, initialized) => {
if (reportRemoveFilter) {
event.preventDefault();
const reportElement = reportRemoveFilter.closest(reportSelectors.regions.report);
const filterContainer = reportRemoveFilter.closest(reportSelectors.regions.activeFilter);
const filterName = filterContainer.dataset.filterName;
......@@ -123,9 +125,10 @@ export const init = (reportElement, initialized) => {
var activeFiltersSortableList = new SortableList(`${reportSelectors.regions.activeFilters} ul`, {isHorizontal: false});
activeFiltersSortableList.getElementName = element => Promise.resolve(element.data('filterName'));
$(reportElement).on(SortableList.EVENTS.DROP, 'li[data-filter-id]', (event, info) => {
$(document).on(SortableList.EVENTS.DROP, `${reportSelectors.regions.report} li[data-filter-id]`, (event, info) => {
if (info.positionChanged) {
const pendingPromise = new Pending('core_reportbuilder/filters:reorder');
const reportElement = event.target.closest(reportSelectors.regions.report);
const filterId = info.element.data('filterId');
const filterPosition = info.element.data('filterPosition');
......
......@@ -88,10 +88,9 @@ const updateSorting = (reportElement, element, sortenabled, sortdirection) => {
/**
* Initialise module
*
* @param {Element} reportElement
* @param {Boolean} initialized Ensure we only add our listeners once
*/
export const init = (reportElement, initialized) => {
export const init = (initialized) => {
if (initialized) {
return;
}
......@@ -101,7 +100,7 @@ export const init = (reportElement, initialized) => {
.catch(Notification.exception)
);
reportElement.addEventListener('click', event => {
document.addEventListener('click', event => {
// Enable/disable sorting on columns.
const toggleSorting = event.target.closest(reportSelectors.actions.reportToggleColumnSort);
......@@ -109,6 +108,7 @@ export const init = (reportElement, initialized) => {
event.preventDefault();
const pendingPromise = new Pending('core_reportbuilder/sorting:toggle');
const reportElement = toggleSorting.closest(reportSelectors.regions.report);
const sortdirection = parseInt(toggleSorting.closest('li').dataset.columnSortDirection);
updateSorting(reportElement, toggleSorting, toggleSorting.checked, sortdirection)
......@@ -122,6 +122,7 @@ export const init = (reportElement, initialized) => {
event.preventDefault();
const pendingPromise = new Pending('core_reportbuilder/sorting:direction');
const reportElement = toggleSortDirection.closest(reportSelectors.regions.report);
const listElement = toggleSortDirection.closest('li');
const sortenabled = listElement.dataset.columnSortEnabled;
......@@ -142,9 +143,10 @@ export const init = (reportElement, initialized) => {
var columnsSortingSortableList = new SortableList(`${reportSelectors.regions.settingsSorting} ul`, {isHorizontal: false});
columnsSortingSortableList.getElementName = element => Promise.resolve(element.data('columnSortName'));
$(reportElement).on(SortableList.EVENTS.DROP, 'li[data-column-sort-id]', (event, info) => {
$(document).on(SortableList.EVENTS.DROP, `${reportSelectors.regions.report} li[data-column-sort-id]`, (event, info) => {
if (info.positionChanged) {
const pendingPromise = new Pending('core_reportbuilder/sorting:reorder');
const reportElement = event.target.closest(reportSelectors.regions.report);
const columnId = info.element.data('columnSortId');
const columnPosition = info.element.data('columnSortPosition');
......
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
declare(strict_types=1);
namespace core_reportbuilder\output\dynamictabs;
use core\output\dynamic_tabs\base;
use core_reportbuilder\local\models\report;
use core_reportbuilder\output\custom_report;
use core_reportbuilder\permission;
use renderer_base;
use stdClass;
/**
* Editor dynamic tab
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class editor extends base {
/**
* Export this for use in a mustache template context.
*
* @param renderer_base $output
* @return stdClass
*/
public function export_for_template(renderer_base $output) {
global $PAGE;
/** @var \core_reportbuilder\output\renderer $renderer */
$renderer = $PAGE->get_renderer('core_reportbuilder');
$reportpersistent = new report((int)$this->data['reportid']);
return (new custom_report($reportpersistent))->export_for_template($renderer);
}
/**
* The label to be displayed on the tab
*
* @return string
*/
public function get_tab_label(): string {
return get_string('editor', 'core_reportbuilder');
}
/**
* Check permission of the current user to access this tab
*
* @return bool
*/
public function is_available(): bool {
$reportpersistent = new report((int)$this->data['reportid']);
return permission::can_edit_report($reportpersistent);
}
/**
* Template to use to display tab contents
*
* @return string
*/
public function get_template(): string {
return 'core_reportbuilder/local/dynamictabs/editor';
}
}
......@@ -69,7 +69,7 @@ class renderer extends plugin_renderer_base {
protected function render_custom_report(custom_report $report): string {
$context = $report->export_for_template($this);
return $this->render_from_template('core_reportbuilder/custom_report', $context);
return $this->render_from_template('core_reportbuilder/local/dynamictabs/editor', $context);
}
/**
......
......@@ -24,9 +24,10 @@
declare(strict_types=1);
use core\output\dynamic_tabs;
use core_reportbuilder\manager;
use core_reportbuilder\output\custom_report;
use core_reportbuilder\permission;
use core_reportbuilder\output\dynamictabs\editor;
require_once(__DIR__ . '/../config.php');
require_once("{$CFG->libdir}/adminlib.php");
......@@ -40,9 +41,6 @@ permission::require_can_edit_report($report->get_report_persistent());
$PAGE->set_context($report->get_context());
/** @var \core_reportbuilder\output\renderer $renderer */
$renderer = $PAGE->get_renderer('core_reportbuilder');
$reportname = $report->get_report_persistent()->get_formatted_name();
$PAGE->navbar->add($reportname);
$PAGE->set_title($reportname);
......@@ -50,7 +48,13 @@ $PAGE->set_heading($reportname);
echo $OUTPUT->header();
$customreport = (new custom_report($report->get_report_persistent()));
echo $renderer->render($customreport);
// Add dynamic tabs.
$tabdata = ['reportid' => $reportid];
$tabs = [
new editor($tabdata),
];
echo $OUTPUT->render_from_template('core/dynamic_tabs',
(new dynamic_tabs($tabdata, $tabs))->export_for_template($OUTPUT));
echo $OUTPUT->footer();
......@@ -15,9 +15,9 @@
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template core_reportbuilder/custom_report
@template core_reportbuilder/local/dynamictabs/editor
Template for a custom report
Template for the custom report editor
Example context (json):
{
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment