diff --git a/lib/outputcomponents.php b/lib/outputcomponents.php index 27bb8cafa88..850d4b2bb75 100644 --- a/lib/outputcomponents.php +++ b/lib/outputcomponents.php @@ -4569,6 +4569,19 @@ class action_menu implements renderable, templatable { } } + /** + * Add classes to the action menu for an easier styling. + * + * @param string $class The class to add to attributes. + */ + public function set_additional_classes(string $class = '') { + if (!empty($this->attributes['class'])) { + $this->attributes['class'] .= " ".$class; + } else { + $this->attributes['class'] = $class; + } + } + /** * Export for template. * diff --git a/lib/upgrade.txt b/lib/upgrade.txt index 13d2d7ec979..62fef4b040e 100644 --- a/lib/upgrade.txt +++ b/lib/upgrade.txt @@ -12,9 +12,6 @@ Declaration is as follow: * coursemodinfo cache uses the new `requirelockingbeforewrite` option, and rebuilding the cache now uses the cache lock API, rather than using the core lock factory directly. This allows the locks to be stored locally if the cache is stored locally, and avoids the risk of delays and timeouts when multiple nodes need to rebuild the cache locally, but are waiting for a central lock. - -=== 4.1 === - * Final deprecation and removal of the class \admin_setting_managelicenses, please use \tool_licensemanager\manager instead. * Final deprecation and removal of the function license_manager::add(). Please use license_manager::save() instead. * Final deprecation of the following functions behat_field_manager::get_node_type() and behat_field_manager::get_field() @@ -60,6 +57,7 @@ Declaration is as follow: * The function get_module_metadata() has been finally deprecated and can not be used anymore. * New DML driver method `$DB->sql_order_by_null` for sorting nulls sort nulls first when ascending and last when descending. * Allow plugins to callback on all pages just prior to the session start. +* New function set_additional_classes() has been implemented to add additional classes to action_menu. === 4.0 === diff --git a/mod/data/amd/build/selectpreset.min.js.map b/mod/data/amd/build/selectpreset.min.js.map index 6ec272a58b0..e7ee516bd6d 100644 --- a/mod/data/amd/build/selectpreset.min.js.map +++ b/mod/data/amd/build/selectpreset.min.js.map @@ -1 +1 @@ -{"version":3,"file":"selectpreset.min.js","sources":["../src/selectpreset.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript module to control the form responsible for selecting a preset.\n *\n * @module mod_data/selectpreset\n * @copyright 2021 Mihail Geshoski \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nconst selectors = {\n presetRadioButton: 'input[name=\"fullname\"]',\n selectPresetButton: 'input[name=\"selectpreset\"]',\n selectedPresetRadioButton: 'input[name=\"fullname\"]:checked',\n};\n\n/**\n * Initialize module.\n */\nexport const init = () => {\n const radioButton = document.querySelectorAll(selectors.presetRadioButton);\n\n // Initialize the \"Use preset\" button properly.\n disableUsePresetButton();\n\n radioButton.forEach((elem) => {\n elem.addEventListener('change', function(event) {\n event.preventDefault();\n // Enable the \"Use preset\" button when any of the radio buttons in the presets list is checked.\n disableUsePresetButton();\n });\n });\n\n};\n\n/**\n * Decide whether to disable or not the \"Use preset\" button.\n * When there is no preset selected, the button should be displayed disabled; otherwise, it will appear enabled as a primary button.\n *\n * @method\n * @private\n */\nconst disableUsePresetButton = () => {\n let selectPresetButton = document.querySelector(selectors.selectPresetButton);\n const selectedRadioButton = document.querySelectorAll(selectors.selectedPresetRadioButton);\n\n if (selectedRadioButton.length > 0) {\n // There is one preset selected, so the button should be enabled.\n selectPresetButton.removeAttribute('disabled');\n selectPresetButton.classList.remove('btn-secondary');\n selectPresetButton.classList.add('btn-primary');\n } else {\n // There is no any preset selected, so the button should be disabled.\n selectPresetButton.setAttribute('disabled', true);\n selectPresetButton.classList.remove('btn-primary');\n selectPresetButton.classList.add('btn-secondary');\n }\n};\n"],"names":["selectors","radioButton","document","querySelectorAll","disableUsePresetButton","forEach","elem","addEventListener","event","preventDefault","selectPresetButton","querySelector","length","removeAttribute","classList","remove","add","setAttribute"],"mappings":";;;;;;;;MAuBMA,4BACiB,yBADjBA,6BAEkB,6BAFlBA,oCAGyB,+CAMX,WACVC,YAAcC,SAASC,iBAAiBH,6BAG9CI,yBAEAH,YAAYI,SAASC,OACjBA,KAAKC,iBAAiB,UAAU,SAASC,OACrCA,MAAMC,iBAENL,sCAaNA,uBAAyB,SACvBM,mBAAqBR,SAASS,cAAcX,8BACpBE,SAASC,iBAAiBH,qCAE9BY,OAAS,GAE7BF,mBAAmBG,gBAAgB,YACnCH,mBAAmBI,UAAUC,OAAO,iBACpCL,mBAAmBI,UAAUE,IAAI,iBAGjCN,mBAAmBO,aAAa,YAAY,GAC5CP,mBAAmBI,UAAUC,OAAO,eACpCL,mBAAmBI,UAAUE,IAAI"} \ No newline at end of file +{"version":3,"file":"selectpreset.min.js","sources":["../src/selectpreset.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript module to control the form responsible for selecting a preset.\n *\n * @module mod_data/selectpreset\n * @copyright 2021 Mihail Geshoski \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nconst selectors = {\n presetRadioButton: 'input[name=\"fullname\"]',\n selectPresetButton: 'input[name=\"selectpreset\"]',\n selectedPresetRadioButton: 'input[name=\"fullname\"]:checked',\n};\n\n/**\n * Initialize module.\n */\nexport const init = () => {\n const radioButton = document.querySelectorAll(selectors.presetRadioButton);\n\n // Initialize the \"Use a preset\" button properly.\n disableUsePresetButton();\n\n radioButton.forEach((elem) => {\n elem.addEventListener('change', function(event) {\n event.preventDefault();\n // Enable the \"Use a preset\" button when any of the radio buttons in the presets list is checked.\n disableUsePresetButton();\n });\n });\n\n};\n\n/**\n * Decide whether to disable or not the \"Use a preset\" button.\n * When there is no preset selected, the button should be displayed disabled; otherwise, it will appear enabled as a primary button.\n *\n * @method\n * @private\n */\nconst disableUsePresetButton = () => {\n let selectPresetButton = document.querySelector(selectors.selectPresetButton);\n const selectedRadioButton = document.querySelectorAll(selectors.selectedPresetRadioButton);\n\n if (selectedRadioButton.length > 0) {\n // There is one preset selected, so the button should be enabled.\n selectPresetButton.removeAttribute('disabled');\n selectPresetButton.classList.remove('btn-secondary');\n selectPresetButton.classList.add('btn-primary');\n } else {\n // There is no any preset selected, so the button should be disabled.\n selectPresetButton.setAttribute('disabled', true);\n selectPresetButton.classList.remove('btn-primary');\n selectPresetButton.classList.add('btn-secondary');\n }\n};\n"],"names":["selectors","radioButton","document","querySelectorAll","disableUsePresetButton","forEach","elem","addEventListener","event","preventDefault","selectPresetButton","querySelector","length","removeAttribute","classList","remove","add","setAttribute"],"mappings":";;;;;;;;MAuBMA,4BACiB,yBADjBA,6BAEkB,6BAFlBA,oCAGyB,+CAMX,WACVC,YAAcC,SAASC,iBAAiBH,6BAG9CI,yBAEAH,YAAYI,SAASC,OACjBA,KAAKC,iBAAiB,UAAU,SAASC,OACrCA,MAAMC,iBAENL,sCAaNA,uBAAyB,SACvBM,mBAAqBR,SAASS,cAAcX,8BACpBE,SAASC,iBAAiBH,qCAE9BY,OAAS,GAE7BF,mBAAmBG,gBAAgB,YACnCH,mBAAmBI,UAAUC,OAAO,iBACpCL,mBAAmBI,UAAUE,IAAI,iBAGjCN,mBAAmBO,aAAa,YAAY,GAC5CP,mBAAmBI,UAAUC,OAAO,eACpCL,mBAAmBI,UAAUE,IAAI"} \ No newline at end of file diff --git a/mod/data/amd/src/selectpreset.js b/mod/data/amd/src/selectpreset.js index 1ac23b10959..6fd881c2957 100644 --- a/mod/data/amd/src/selectpreset.js +++ b/mod/data/amd/src/selectpreset.js @@ -33,13 +33,13 @@ const selectors = { export const init = () => { const radioButton = document.querySelectorAll(selectors.presetRadioButton); - // Initialize the "Use preset" button properly. + // Initialize the "Use a preset" button properly. disableUsePresetButton(); radioButton.forEach((elem) => { elem.addEventListener('change', function(event) { event.preventDefault(); - // Enable the "Use preset" button when any of the radio buttons in the presets list is checked. + // Enable the "Use a preset" button when any of the radio buttons in the presets list is checked. disableUsePresetButton(); }); }); @@ -47,7 +47,7 @@ export const init = () => { }; /** - * Decide whether to disable or not the "Use preset" button. + * Decide whether to disable or not the "Use a preset" button. * When there is no preset selected, the button should be displayed disabled; otherwise, it will appear enabled as a primary button. * * @method diff --git a/mod/data/classes/manager.php b/mod/data/classes/manager.php index 8e9ebac175a..02ded23f091 100644 --- a/mod/data/classes/manager.php +++ b/mod/data/classes/manager.php @@ -301,6 +301,19 @@ class manager { return new template($this, $templatecontent, $options); } + /** Check if the user can manage templates on the current context. + * + * @param int $userid the user id to check ($USER->id if null). + * @return bool if the user can manage templates on current context. + */ + public function can_manage_templates(?int $userid = null): bool { + global $USER; + if (!$userid) { + $userid = $USER->id; + } + return has_capability('mod/data:managetemplates', $this->context, $userid); + } + /** * Update the database templates. * diff --git a/mod/data/classes/output/action_bar.php b/mod/data/classes/output/action_bar.php index e4ca9bbe392..ae89ecaa58a 100644 --- a/mod/data/classes/output/action_bar.php +++ b/mod/data/classes/output/action_bar.php @@ -54,8 +54,11 @@ class action_bar { * @param bool $hasexportpreset Whether the export as preset button element should be rendered. * @return string The HTML code for the action bar. */ - public function get_fields_action_bar(bool $hasfieldselect = false, bool $hassaveaspreset = false, - bool $hasexportpreset = false): string { + public function get_fields_action_bar( + bool $hasfieldselect = false, + bool $hassaveaspreset = false, + bool $hasexportpreset = false + ): string { global $PAGE, $DB; $createfieldlink = new moodle_url('/mod/data/field.php', ['d' => $this->id]); @@ -70,19 +73,7 @@ class action_bar { $fieldselect = null; if ($hasfieldselect) { - // Get the list of possible fields (plugins). - $plugins = \core_component::get_plugin_list('datafield'); - $menufield = []; - - foreach ($plugins as $plugin => $fulldir) { - $menufield[$plugin] = get_string('pluginname', "datafield_{$plugin}"); - } - asort($menufield); - - $fieldselecturl = new moodle_url('/mod/data/field.php', ['d' => $this->id, 'mode' => 'new']); - $fieldselect = new \single_select($fieldselecturl, 'newtype', $menufield, null, get_string('newfield', 'data'), - 'fieldform'); - $fieldselect->set_label(get_string('newfield', 'mod_data'), ['class' => 'sr-only']); + $fieldselect = $this->get_create_fields(); } $saveaspresetbutton = null; @@ -105,12 +96,43 @@ class action_bar { } } $renderer = $PAGE->get_renderer('mod_data'); - $fieldsactionbar = new fields_action_bar($this->id, $urlselect, $fieldselect, $saveaspresetbutton, - $exportpresetbutton); + $fieldsactionbar = new fields_action_bar($this->id, $urlselect, null, $saveaspresetbutton, + $exportpresetbutton, $fieldselect); return $renderer->render_fields_action_bar($fieldsactionbar); } + /** + * Generate the output for the create a new field action menu. + * + * @return \action_menu Action menu to create a new field + */ + public function get_create_fields(): \action_menu { + // Get the list of possible fields (plugins). + $plugins = \core_component::get_plugin_list('datafield'); + $menufield = []; + foreach ($plugins as $plugin => $fulldir) { + $menufield[$plugin] = get_string('pluginname', "datafield_{$plugin}"); + } + asort($menufield); + + $fieldselect = new \action_menu(); + $fieldselect->set_menu_trigger(get_string('newfield', 'mod_data'), 'btn btn-secondary'); + $fieldselectparams = ['d' => $this->id, 'mode' => 'new']; + foreach ($menufield as $fieldtype => $fieldname) { + $fieldselectparams['newtype'] = $fieldtype; + $fieldselect->add(new \action_menu_link( + new \moodle_url('/mod/data/field.php', $fieldselectparams), + new \pix_icon('field/' . $fieldtype, $fieldname, 'data'), + $fieldname, + false + )); + } + $fieldselect->set_additional_classes('singlebutton'); + + return $fieldselect; + } + /** * Generate the output for the action selector in the view page. * diff --git a/mod/data/classes/output/fields_action_bar.php b/mod/data/classes/output/fields_action_bar.php index 83e3455e7a5..0ed542f03e1 100644 --- a/mod/data/classes/output/fields_action_bar.php +++ b/mod/data/classes/output/fields_action_bar.php @@ -48,12 +48,19 @@ class fields_action_bar implements templatable, renderable { * * @param int $id The database module id * @param \url_select $urlselect The URL selector object - * @param \single_select|null $fieldselect The field selector object or null + * @param null $unused This parameter has been deprecated since 4.0 and should not be used anymore. * @param \single_button|null $saveaspresetbutton The save as preset single button object or null * @param \single_button|null $exportpresetbutton The export preset single button object or null + * @param \action_menu|null $fieldselect The field selector object or null */ - public function __construct(int $id, \url_select $urlselect, ?\single_select $fieldselect = null, - ?\single_button $saveaspresetbutton = null, ?\single_button $exportpresetbutton = null) { + public function __construct(int $id, \url_select $urlselect, $unused = null, + ?\single_button $saveaspresetbutton = null, ?\single_button $exportpresetbutton = null, + ?\action_menu $fieldselect = null) { + + if ($unused !== null) { + debugging('Deprecated argument passed to fields_action_bar constructor', DEBUG_DEVELOPER); + } + $this->id = $id; $this->urlselect = $urlselect; $this->fieldselect = $fieldselect; diff --git a/mod/data/classes/output/zero_state_action_bar.php b/mod/data/classes/output/zero_state_action_bar.php index 64030ac9e8d..d92ea1a4baa 100644 --- a/mod/data/classes/output/zero_state_action_bar.php +++ b/mod/data/classes/output/zero_state_action_bar.php @@ -52,7 +52,7 @@ class zero_state_action_bar implements templatable, renderable { global $PAGE; $data = []; - if (has_capability('mod/data:managetemplates', $PAGE->context)) { + if ($this->manager->can_manage_templates()) { $instance = $this->manager->get_instance(); $params = ['d' => $instance->id, 'backto' => $PAGE->url->out(false)]; @@ -61,9 +61,8 @@ class zero_state_action_bar implements templatable, renderable { get_string('usepreset', 'mod_data'), 'get', true); $data['usepresetbutton'] = $usepresetbutton->export_for_template($output); - $createfieldlink = new moodle_url('/mod/data/field.php', $params); - $createfieldbutton = new \single_button($createfieldlink, - get_string('newfield', 'mod_data'), 'get', false); + $actionbar = new \mod_data\output\action_bar($instance->id, $PAGE->url); + $createfieldbutton = $actionbar->get_create_fields(); $data['createfieldbutton'] = $createfieldbutton->export_for_template($output); $params['action'] = 'import'; diff --git a/mod/data/edit.php b/mod/data/edit.php index 12b462732b8..6587516ada5 100644 --- a/mod/data/edit.php +++ b/mod/data/edit.php @@ -74,7 +74,7 @@ if (isguestuser()) { } /// Can't use this if there are no fields -if (has_capability('mod/data:managetemplates', $context)) { +if ($manager->can_manage_templates()) { if (!$manager->has_fields()) { redirect($CFG->wwwroot.'/mod/data/field.php?d='.$data->id); // Redirect to field entry. } diff --git a/mod/data/field.php b/mod/data/field.php index 4eb2e2c0255..1c89d2a3f4b 100644 --- a/mod/data/field.php +++ b/mod/data/field.php @@ -333,75 +333,79 @@ if (($mode == 'new') && (!empty($newtype))) { // Adding a new field. $field->display_edit_field(); } else { /// Display the main listing of all fields + $hasfields = $manager->has_fields(); + + // Check if it is an empty database with no fields. + if (!$hasfields) { + $PAGE->set_title($data->name); + echo $OUTPUT->header(); + echo $renderer->render_fields_zero_state($manager); + echo $OUTPUT->footer(); + // Don't check the rest of the options. There is no field, there is nothing else to work with. + exit; + } $fieldactionbar = $actionbar->get_fields_action_bar(true, true, true); data_print_header($course, $cm, $data, 'fields', $fieldactionbar); echo $OUTPUT->heading(get_string('managefields', 'data'), 2, 'mb-4'); - if (!$DB->record_exists('data_fields', array('dataid'=>$data->id))) { - echo $OUTPUT->notification(get_string('nofieldindatabase','data')); // nothing in database - echo $OUTPUT->notification(get_string('pleaseaddsome','data', 'preset.php?id='.$cm->id)); // link to presets + $table = new html_table(); + $table->head = [ + get_string('fieldname', 'data'), + get_string('type', 'data'), + get_string('required', 'data'), + get_string('fielddescription', 'data'), + get_string('action', 'data'), + ]; + $table->align = ['left', 'left', 'left', 'left']; + $table->wrap = [false,false,false,false]; - } else { //else print quiz style list of fields + $fieldrecords = $manager->get_field_records(); + $missingfieldtypes = []; + foreach ($fieldrecords as $fieldrecord) { - $table = new html_table(); - $table->head = array( - get_string('fieldname', 'data'), - get_string('type', 'data'), - get_string('required', 'data'), - get_string('fielddescription', 'data'), - get_string('action', 'data'), - ); - $table->align = array('left', 'left', 'left', 'left'); - $table->wrap = array(false,false,false,false); + $field = data_get_field($fieldrecord, $data); - if ($fff = $DB->get_records('data_fields', array('dataid'=>$data->id),'id')){ - $missingfieldtypes = []; - foreach ($fff as $ff) { + $baseurl = new moodle_url('/mod/data/field.php', array( + 'd' => $data->id, + 'fid' => $field->field->id, + 'sesskey' => sesskey(), + )); - $field = data_get_field($ff, $data); + $displayurl = new moodle_url($baseurl, array( + 'mode' => 'display', + )); - $baseurl = new moodle_url('/mod/data/field.php', array( - 'd' => $data->id, - 'fid' => $field->field->id, - 'sesskey' => sesskey(), - )); + $deleteurl = new moodle_url($baseurl, array( + 'mode' => 'delete', + )); - $displayurl = new moodle_url($baseurl, array( - 'mode' => 'display', - )); - - $deleteurl = new moodle_url($baseurl, array( - 'mode' => 'delete', - )); - - // It display a notification when the field type does not exist. - $deletelink = html_writer::link($deleteurl, $OUTPUT->pix_icon('t/delete', get_string('delete'))); - $editlink = html_writer::link($displayurl, $OUTPUT->pix_icon('t/edit', get_string('edit'))); - if ($field->type === 'unknown') { - $missingfieldtypes[] = $field->field->name; - $fieldnamedata = $field->field->name; - $fieltypedata = $field->field->type; - $fieldlinkdata = $deletelink; - } else { - $fieldnamedata = html_writer::link($displayurl, $field->field->name); - $fieltypedata = $field->image() . ' ' . $field->name(); - $fieldlinkdata = $editlink . ' ' . $deletelink; - } - - $table->data[] = [ - $fieldnamedata, - $fieltypedata, - $field->field->required ? get_string('yes') : get_string('no'), - shorten_text($field->field->description, 30), - $fieldlinkdata - ]; - } - if (!empty($missingfieldtypes)) { - echo $OUTPUT->notification(get_string('missingfieldtypes', 'data') . html_writer::alist($missingfieldtypes)); - } + // It display a notification when the field type does not exist. + $deletelink = html_writer::link($deleteurl, $OUTPUT->pix_icon('t/delete', get_string('delete'))); + $editlink = html_writer::link($displayurl, $OUTPUT->pix_icon('t/edit', get_string('edit'))); + if ($field->type === 'unknown') { + $missingfieldtypes[] = $field->field->name; + $fieldnamedata = $field->field->name; + $fieltypedata = $field->field->type; + $fieldlinkdata = $deletelink; + } else { + $fieldnamedata = html_writer::link($displayurl, $field->field->name); + $fieltypedata = $field->image() . ' ' . $field->name(); + $fieldlinkdata = $editlink . ' ' . $deletelink; + } + + $table->data[] = [ + $fieldnamedata, + $fieltypedata, + $field->field->required ? get_string('yes') : get_string('no'), + shorten_text($field->field->description, 30), + $fieldlinkdata + ]; + + if (!empty($missingfieldtypes)) { + echo $OUTPUT->notification(get_string('missingfieldtypes', 'data') . html_writer::alist($missingfieldtypes)); } - echo html_writer::table($table); } + echo html_writer::table($table); echo '
'; echo '
'; diff --git a/mod/data/lang/en/data.php b/mod/data/lang/en/data.php index 9d173356c80..f27a4c6afd3 100644 --- a/mod/data/lang/en/data.php +++ b/mod/data/lang/en/data.php @@ -78,7 +78,9 @@ $string['configenablerssfeeds'] = 'This switch will enable the possibility of RS $string['confirmdeletefield'] = 'You are about to delete this field, are you sure?'; $string['confirmdeleterecord'] = 'Are you sure you want to delete this entry?'; $string['confirmdeleterecords'] = 'Are you sure you want to delete these entries?'; -$string['createfields'] = 'Create your own fields to collect data, or use a preset which includes fields already.'; +$string['createactivity'] = 'Create your own fields to collect data, or use a preset which includes fields already.'; +$string['createfields'] = 'Create fields to collect different types of data.'; +$string['createtemplates'] = 'Create fields for your activity to generate a template, or import a preset with existing fields and templates.'; $string['csstemplate'] = 'CSS template'; $string['csvfailed'] = 'Unable to read the raw data from the CSV file'; $string['csvfile'] = 'CSV file'; @@ -291,7 +293,7 @@ $string['multientry'] = 'Repeated entry'; $string['multimenu'] = 'Menu (Multi-select)'; $string['multipletags'] = 'Multiple tags found! Template not saved'; $string['newentry'] = 'New entry'; -$string['newfield'] = 'Create a new field'; +$string['newfield'] = 'Create a field'; $string['newfield_help'] = 'A field allows the input of data. Each entry in a database activity can have multiple fields of multiple types such as a date field, which allows participants to select a day, month and year from a drop-down menu, a picture field, which allows participants to upload an image file, or a checkbox field, which allows participants to select one or more options. Each field must have a unique field name. The field description is optional.'; @@ -299,6 +301,8 @@ $string['noaccess'] = 'You do not have access to this page'; $string['nodefinedfields'] = 'New preset has no defined fields!'; $string['nofieldcontent'] = 'Field content not found'; $string['nofieldindatabase'] = 'There are no fields defined for this database.'; +$string['nofields'] = 'No fields yet'; +$string['nolisttemplate'] = 'List template is not yet defined'; $string['nomatch'] = 'No matching entries found!'; $string['nomaximum'] = 'No maximum'; $string['nopreviewavailable'] = 'No preview available for {$a}'; @@ -306,6 +310,7 @@ $string['norecords'] = 'No entries yet'; $string['notapproved'] = 'Pending approval'; $string['notapprovederror'] = 'Entry is not approved yet.'; $string['notinjectivemap'] = 'Not an injective map'; +$string['notemplates'] = 'Not templates yet'; $string['notopenyet'] = 'Sorry, this activity is not available until {$a}'; $string['number'] = 'Number'; $string['numberrssarticles'] = 'Entries in the RSS feed'; @@ -323,7 +328,6 @@ $string['page-mod-data-x'] = 'Any database activity module page'; $string['pagesize'] = 'Entries per page'; $string['participants'] = 'Participants'; $string['picture'] = 'Picture'; -$string['pleaseaddsome'] = 'Please create some below or choose a predefined set to get started.'; $string['pluginadministration'] = 'Database activity administration'; $string['pluginname'] = 'Database'; $string['portfolionotfile'] = 'Export to a portfolio rather than a file (csv and leap2a only)'; @@ -438,7 +442,7 @@ $string['uploadrecords_link'] = 'mod/data/import'; $string['url'] = 'URL'; $string['usedate'] = 'Include in search.'; $string['usepredefinedset'] = 'Use predefined set'; -$string['usepreset'] = 'Use preset'; +$string['usepreset'] = 'Use a preset'; $string['usestandard'] = 'Use a preset'; $string['usestandard_help'] = 'To use a preset available to the whole site, select it from the list. (If you have added a preset to the list using the save as preset feature then you have the option of deleting it.)'; $string['viewfromdate'] = 'Read only from'; @@ -455,6 +459,7 @@ $string['unsupportedexport'] = '({$a->fieldtype}) cannot be exported.'; $string['buttons'] = 'Actions'; $string['nolisttemplate'] = 'List template is not yet defined'; $string['nosingletemplate'] = 'Single template is not yet defined'; +$string['pleaseaddsome'] = 'Please create some below or choose a predefined set to get started.'; $string['blank'] = 'Blank'; $string['savetemplate'] = 'Save template'; $string['addedby'] = 'Added by'; diff --git a/mod/data/lang/en/deprecated.txt b/mod/data/lang/en/deprecated.txt index 3bbe0cf1cc1..6c7fa3037cb 100644 --- a/mod/data/lang/en/deprecated.txt +++ b/mod/data/lang/en/deprecated.txt @@ -2,6 +2,7 @@ unsupportedexport,mod_data buttons,mod_data nosingletemplate,mod_data nolisttemplate,mod_data +pleaseaddsome,mod_data blank,mod_data savetemplate,mod_data addedby,mod_data diff --git a/mod/data/preset/imagegallery/tests/behat/imagegallery_preset.feature b/mod/data/preset/imagegallery/tests/behat/imagegallery_preset.feature index 9bc936c7a24..eb8c25a3ca4 100644 --- a/mod/data/preset/imagegallery/tests/behat/imagegallery_preset.feature +++ b/mod/data/preset/imagegallery/tests/behat/imagegallery_preset.feature @@ -22,7 +22,7 @@ Feature: Users can use the Image gallery preset And I am on the "Mountain landscapes" "data activity" page logged in as teacher1 And I follow "Presets" And I click on "fullname" "radio" in the "Image gallery" "table_row" - And I click on "Use preset" "button" + And I click on "Use a preset" "button" And the following "mod_data > entries" exist: | database | user | title | description | image | | data1 | student1 | First image | This is the description text for image 1 | first.png | diff --git a/mod/data/preset/journal/tests/behat/journal_preset.feature b/mod/data/preset/journal/tests/behat/journal_preset.feature index c795ef15fbc..eb86652888b 100644 --- a/mod/data/preset/journal/tests/behat/journal_preset.feature +++ b/mod/data/preset/journal/tests/behat/journal_preset.feature @@ -22,7 +22,7 @@ Feature: Users can use the Journal preset And I am on the "Student reflections" "data activity" page logged in as teacher1 And I follow "Presets" And I click on "fullname" "radio" in the "Journal" "table_row" - And I click on "Use preset" "button" + And I click on "Use a preset" "button" And the following "mod_data > entries" exist: | database | user | Title | Content | | data1 | student1 | Reflection created by student | This is the content for the entry 1 | diff --git a/mod/data/preset/proposals/tests/behat/proposals_preset.feature b/mod/data/preset/proposals/tests/behat/proposals_preset.feature index 042a88f4df8..9d7c43c1abf 100644 --- a/mod/data/preset/proposals/tests/behat/proposals_preset.feature +++ b/mod/data/preset/proposals/tests/behat/proposals_preset.feature @@ -22,7 +22,7 @@ Feature: Users can use the Proposals preset And I am on the "Student projects" "data activity" page logged in as teacher1 And I follow "Presets" And I click on "fullname" "radio" in the "Proposals" "table_row" - And I click on "Use preset" "button" + And I click on "Use a preset" "button" And the following "mod_data > entries" exist: | database | user | Title | Summary | Content | Status | | data1 | student1 | Project created by student | Summary 1 | Content for entry 1 | Pending | diff --git a/mod/data/preset/resources/tests/behat/resources_preset.feature b/mod/data/preset/resources/tests/behat/resources_preset.feature index 79ca0bb652e..0f36842a7bb 100644 --- a/mod/data/preset/resources/tests/behat/resources_preset.feature +++ b/mod/data/preset/resources/tests/behat/resources_preset.feature @@ -22,7 +22,7 @@ Feature: Users can use the Resources preset And I am on the "Student resources" "data activity" page logged in as teacher1 And I follow "Presets" And I click on "fullname" "radio" in the "Resources" "table_row" - And I click on "Use preset" "button" + And I click on "Use a preset" "button" And the following "mod_data > entries" exist: | database | user | Title | Description | Type | Author | Web link | Cover | | data1 | student1 | My favourite book | Book content | Type1 | The book author | http://myfavouritebook.cat | first.png | diff --git a/mod/data/renderer.php b/mod/data/renderer.php index 42340dda612..14fab065188 100644 --- a/mod/data/renderer.php +++ b/mod/data/renderer.php @@ -154,6 +154,9 @@ class mod_data_renderer extends plugin_renderer_base { */ public function render_fields_action_bar(\mod_data\output\fields_action_bar $actionbar): string { $data = $actionbar->export_for_template($this); + $data['title'] = get_string('nofields', 'mod_data'); + $data['intro'] = get_string('createfields', 'mod_data'); + $data['noitemsimgurl'] = $this->output->image_url('nofields', 'mod_data')->out(); return $this->render_from_template('mod_data/fields_action_bar', $data); } @@ -233,7 +236,7 @@ class mod_data_renderer extends plugin_renderer_base { * * @return string The HTML output */ - public function render_zero_state(\mod_data\manager $manager): string { + public function render_database_zero_state(\mod_data\manager $manager): string { $actionbar = new \mod_data\output\zero_state_action_bar($manager); $data = $actionbar->export_for_template($this); if (empty($data)) { @@ -242,7 +245,7 @@ class mod_data_renderer extends plugin_renderer_base { $data['intro'] = get_string('comebacklater'); } else { $data['title'] = get_string('startbuilding', 'mod_data'); - $data['intro'] = get_string('createfields', 'mod_data'); + $data['intro'] = get_string('createactivity', 'mod_data'); } $data['noitemsimgurl'] = $this->output->image_url('nofields', 'mod_data')->out(); @@ -263,4 +266,43 @@ class mod_data_renderer extends plugin_renderer_base { return $this->render_from_template('mod_data/view_noentries', $data); } + + /** + * Renders the action bar for the zero state (no fields created) page. + * + * @param \mod_data\manager $manager The manager instance. + * + * @return string The HTML output + */ + public function render_fields_zero_state(\mod_data\manager $manager): string { + $data = [ + 'noitemsimgurl' => $this->output->image_url('nofields', 'mod_data')->out(), + 'title' => get_string('nofields', 'mod_data'), + 'intro' => get_string('createfields', 'mod_data'), + ]; + if ($manager->can_manage_templates()) { + $actionbar = new \mod_data\output\action_bar($manager->get_instance()->id, $this->page->url); + $createfieldbutton = $actionbar->get_create_fields(); + $data['createfieldbutton'] = $createfieldbutton->export_for_template($this); + } + + return $this->render_from_template('mod_data/zero_state', $data); + } + + /** + * Renders the action bar for the templates zero state (no fields created) page. + * + * @param \mod_data\manager $manager The manager instance. + * + * @return string The HTML output + */ + public function render_templates_zero_state(\mod_data\manager $manager): string { + $actionbar = new \mod_data\output\zero_state_action_bar($manager); + $data = $actionbar->export_for_template($this); + $data['title'] = get_string('notemplates', 'mod_data'); + $data['intro'] = get_string('createtemplates', 'mod_data'); + $data['noitemsimgurl'] = $this->output->image_url('nofields', 'mod_data')->out(); + + return $this->render_from_template('mod_data/zero_state', $data); + } } diff --git a/mod/data/styles.css b/mod/data/styles.css index 1e0da951a7e..1e9ace58ee6 100644 --- a/mod/data/styles.css +++ b/mod/data/styles.css @@ -188,3 +188,11 @@ .template-preview-content .data-field-html button { pointer-events: none; } + +#page-mod-data-view .whitebutton .btn-secondary, +#page-mod-data-field- .whitebutton .btn-secondary, +#page-mod-data-templates .whitebutton .btn-secondary { + background: white; + border-color: var(--primary); + color: var(--primary); +} diff --git a/mod/data/templates.php b/mod/data/templates.php index e22faab327c..2b1a46c9419 100644 --- a/mod/data/templates.php +++ b/mod/data/templates.php @@ -56,11 +56,6 @@ $PAGE->set_url($url); require_login($course, false, $cm); require_capability('mod/data:managetemplates', $context); -// Check if it is an empty database. -if (count($manager->get_field_records()) == 0) { - redirect($CFG->wwwroot.'/mod/data/field.php?d='.$instance->id); -} - $manager->set_template_viewed(); if ($useeditor !== null) { @@ -78,6 +73,15 @@ $PAGE->add_body_class('limitedwidth'); echo $OUTPUT->header(); +$renderer = $manager->get_renderer(); +// Check if it is an empty database with no fields. +if (!$manager->has_fields()) { + echo $renderer->render_templates_zero_state($manager); + echo $OUTPUT->footer(); + // Don't check the rest of the options. There is no field, there is nothing else to work with. + exit; +} + $actionbar = new \mod_data\output\action_bar($instance->id, $url); echo $actionbar->get_templates_action_bar(); @@ -104,7 +108,6 @@ if (($formdata = data_submitted()) && confirm_sesskey()) { } } -$renderer = $manager->get_renderer(); $templateeditor = new \mod_data\output\template_editor($manager, $mode); echo $renderer->render($templateeditor); diff --git a/mod/data/templates/fields_action_bar.mustache b/mod/data/templates/fields_action_bar.mustache index 13b82bde311..fb468c63782 100644 --- a/mod/data/templates/fields_action_bar.mustache +++ b/mod/data/templates/fields_action_bar.mustache @@ -87,11 +87,6 @@ {{>core/url_select}}
{{/urlselect}} - {{#fieldselect}} - - {{/fieldselect}}
{{#saveaspreset}} @@ -104,6 +99,9 @@ {{>core/single_button}}
{{/exportpreset}} + {{#fieldselect}} + {{>core/action_menu}} + {{/fieldselect}} diff --git a/mod/data/templates/zero_state.mustache b/mod/data/templates/zero_state.mustache index 2a5274e45b2..8236aea57b6 100644 --- a/mod/data/templates/zero_state.mustache +++ b/mod/data/templates/zero_state.mustache @@ -19,7 +19,7 @@ Context variables required for this template: * noitemsimgurl - The image url. * importpresetbutton stdClass - Import preset single button to be rendered - * createfieldbutton stdClass - Create a new field single button to be rendered + * createfieldbutton stdClass - Create a field action menu to be rendered * usepresetbutton stdClass - Use a preset single button to be rendered Example context (json): @@ -77,15 +77,17 @@ style="height: 70px; width: 70px;" >
{{{ title }}}
-

{{{ intro }}}

+ {{#intro}} +

{{{ intro }}}

+ {{/intro}} -
+
+ {{#createfieldbutton}} + {{>core/action_menu}} + {{/createfieldbutton}} {{#importpresetbutton}} {{>core/single_button}} {{/importpresetbutton}} - {{#createfieldbutton}} - {{>core/single_button}} - {{/createfieldbutton}} {{#usepresetbutton}} {{>core/single_button}} {{/usepresetbutton}} diff --git a/mod/data/tests/behat/behat_mod_data.php b/mod/data/tests/behat/behat_mod_data.php index 98b65434223..fa0ee3eeaab 100644 --- a/mod/data/tests/behat/behat_mod_data.php +++ b/mod/data/tests/behat/behat_mod_data.php @@ -53,7 +53,10 @@ class behat_mod_data extends behat_base { $fieldsstr = get_string('fields', 'mod_data'); $this->execute("behat_navigation::i_navigate_to_in_current_page_administration", $fieldsstr); - $this->execute('behat_forms::i_set_the_field_to', array('newtype', $this->escape($fieldtype))); + $this->execute('behat_general::i_click_on', [get_string('newfield', 'mod_data'), "button"]); + $this->execute('behat_general::i_click_on_in_the', + [$this->escape($fieldtype), "link", "#action_bar", "css_element"] + ); if (!$this->running_javascript()) { $this->execute('behat_general::i_click_on_in_the', diff --git a/mod/data/tests/behat/data_activity_completion.feature b/mod/data/tests/behat/data_activity_completion.feature index 09c6a7efa91..199ea9af2c1 100644 --- a/mod/data/tests/behat/data_activity_completion.feature +++ b/mod/data/tests/behat/data_activity_completion.feature @@ -1,4 +1,4 @@ -@mod @mod_data @core_completion +@mod @mod_data @core_completion @javascript Feature: View activity completion in the database activity In order to have visibility of database completion requirements As a student @@ -77,7 +77,6 @@ Feature: View activity completion in the database activity And I am on the "Music history" "data activity" page logged in as teacher1 And I select "Single view" from the "jump" singleselect And I set the field "rating" to "3" - And I press "Rate" And I log out When I am on the "Music history" "data activity" page logged in as student1 diff --git a/mod/data/tests/behat/data_activity_completion_pass_grade.feature b/mod/data/tests/behat/data_activity_completion_pass_grade.feature index 17994603d2b..a624cfb62f5 100644 --- a/mod/data/tests/behat/data_activity_completion_pass_grade.feature +++ b/mod/data/tests/behat/data_activity_completion_pass_grade.feature @@ -52,6 +52,7 @@ Feature: Completion pass grade | Field name | Instrument types | And I log out + @javascript Scenario: View automatic completion items as a teacher Given I am on the "Music history" "data activity" page logged in as teacher1 # We add an entry to let the user change to a different view. diff --git a/mod/data/tests/behat/data_presets.feature b/mod/data/tests/behat/data_presets.feature index e18f24b64cb..aea0d15eee1 100644 --- a/mod/data/tests/behat/data_presets.feature +++ b/mod/data/tests/behat/data_presets.feature @@ -63,10 +63,10 @@ Feature: Users can view and manage data presets And I should see "Delete" # Teachers can't delete the presets they haven't created. And I should not see "Actions" in the "Saved preset 1" "table_row" - # The "Use preset" button should be enabled only when a preset is selected. - And the "Use preset" "button" should be disabled + # The "Use a preset" button should be enabled only when a preset is selected. + And the "Use a preset" "button" should be disabled And I click on "fullname" "radio" in the "Image gallery" "table_row" - And the "Use preset" "button" should be enabled + And the "Use a preset" "button" should be enabled @javascript Scenario: Only users with the viewalluserpresets capability can see presets created by other users @@ -286,7 +286,7 @@ Feature: Users can view and manage data presets And I should see "My funny description goes here" And I should see "Test field name" And I should see "This is a short text" - Then "Use preset" "button" should exist + Then "Use a preset" "button" should exist @javascript Scenario: Teachers can export any saved preset diff --git a/mod/data/tests/behat/preview_preset.feature b/mod/data/tests/behat/preview_preset.feature index 489d4973099..0325bb65acf 100644 --- a/mod/data/tests/behat/preview_preset.feature +++ b/mod/data/tests/behat/preview_preset.feature @@ -138,8 +138,9 @@ Feature: Users can preview presets Scenario: Apply plugin preset from preview in database Given I follow "Presets" And I click on "Image gallery" "link" - When I click on "Use preset" "button" + When I click on "Use a preset" "button" Then I should see "image" + And I should see "image" And I should see "title" @javascript @_file_upload @@ -155,5 +156,5 @@ Feature: Users can preview presets And I click on "Save" "button" in the "Save all fields and templates as preset" "dialogue" When I follow "Presets" And I click on "Saved preset by teacher1" "link" - And I click on "Use preset" "button" + And I click on "Use a preset" "button" Then I should see "My URL field" diff --git a/mod/data/tests/behat/zero_state.feature b/mod/data/tests/behat/zero_state.feature index 10380dd2029..9bfdac7f624 100644 --- a/mod/data/tests/behat/zero_state.feature +++ b/mod/data/tests/behat/zero_state.feature @@ -1,4 +1,4 @@ -@mod @mod_data +@mod @mod_data @javascript Feature: Zero state page (no fields created) Background: @@ -15,17 +15,45 @@ Feature: Zero state page (no fields created) | activity | name | intro | course | idnumber | | data | Test database name | n | C1 | data1 | - @javascript - Scenario: Teachers see buttons to manage database when there is no field created + Scenario: Teachers see buttons to manage database when there is no field created on view page Given I am on the "Test database name" "data activity" page logged in as "teacher1" And "Import a preset" "button" should exist When I click on "Import a preset" "button" Then I should see "Import from zip file" And I am on the "Test database name" "data activity" page - And "Create a new field" "button" should exist - And I click on "Create a new field" "button" - And I should see "Manage fields" + And "Create a field" "button" should exist + And I click on "Create a field" "button" + And I click on "Short text" "link" + And I should see "Create a field" And I am on the "Test database name" "data activity" page - And "Use preset" "button" should exist - And I click on "Use preset" "button" + And "Use a preset" "button" should exist + And I click on "Use a preset" "button" And I should see "Presets" + + Scenario: Teachers see buttons to manage database when there is no field created on templates page + Given I am on the "Test database name" "data activity" page logged in as "teacher1" + And "Import a preset" "button" should exist + When I click on "Import a preset" "button" + Then I should see "Import from zip file" + And I am on the "Test database name" "data activity" page + And I click on "Templates" "link" + And "Create a field" "button" should exist + And I click on "Create a field" "button" + And I click on "Short text" "link" + And I should see "Create a field" + And I am on the "Test database name" "data activity" page + And I click on "Templates" "link" + And "Use a preset" "button" should exist + And I click on "Use a preset" "button" + And I should see "Presets" + + Scenario: Teachers see buttons to manage database when there is no field created on fields page + Given I am on the "Test database name" "data activity" page logged in as "teacher1" + And I click on "Fields" "link" + And "Import a preset" "button" should not exist + And "Use a preset" "button" should not exist + And "Create a field" "button" should exist + Then I should see "No fields yet" + And I click on "Create a field" "button" + And I click on "Short text" "link" + And I should see "Create a field" diff --git a/mod/data/upgrade.txt b/mod/data/upgrade.txt index fc941e61835..0dcb9efb11a 100644 --- a/mod/data/upgrade.txt +++ b/mod/data/upgrade.txt @@ -22,6 +22,8 @@ information provided here is intended especially for developers. - data_preset_existing_importer - data_preset_upload_importer * import_setting_mappings() function has been deprecated. Use importing_preset() instead. +* $fieldselect single_select type parameter has been deprecated for fields_action_bar class constructor, and a new action_menu + type parameter has been added. === 3.7 === * External functions get_entries, get_entry and search_entries now return an additional field "tags" containing the entry tags. diff --git a/mod/data/view.php b/mod/data/view.php index 13ec9f551a1..e28e0208838 100644 --- a/mod/data/view.php +++ b/mod/data/view.php @@ -242,7 +242,7 @@ echo $OUTPUT->header(); if (!$manager->has_fields()) { // It's a brand-new database. There are no fields. $renderer = $manager->get_renderer(); - echo $renderer->render_zero_state($manager); + echo $renderer->render_database_zero_state($manager); echo $OUTPUT->footer(); // Don't check the rest of the options. There is no field, there is nothing else to work with. exit; diff --git a/theme/boost/scss/moodle/core.scss b/theme/boost/scss/moodle/core.scss index 0a900e37334..dc562875387 100644 --- a/theme/boost/scss/moodle/core.scss +++ b/theme/boost/scss/moodle/core.scss @@ -18,7 +18,6 @@ $thin-scroll-bg-hover: $gray-700 !default; $font-size-xs: ($font-size-base * .75) !default; #region-main { - overflow-x: auto; overflow-y: visible; background-color: $body-bg; } diff --git a/theme/boost/style/moodle.css b/theme/boost/style/moodle.css index ee5593a925c..f89e9a55f31 100644 --- a/theme/boost/style/moodle.css +++ b/theme/boost/style/moodle.css @@ -9860,7 +9860,6 @@ a.text-dark:hover, a.text-dark:focus { */ /* core.less */ #region-main { - overflow-x: auto; overflow-y: visible; background-color: #fff; } diff --git a/theme/classic/style/moodle.css b/theme/classic/style/moodle.css index 17ceff5b1f8..f71d75697ef 100644 --- a/theme/classic/style/moodle.css +++ b/theme/classic/style/moodle.css @@ -9860,7 +9860,6 @@ a.text-dark:hover, a.text-dark:focus { */ /* core.less */ #region-main { - overflow-x: auto; overflow-y: visible; background-color: #fff; }