From 555bca9ebb251732d37f6ffc8465256150ef4631 Mon Sep 17 00:00:00 2001 From: Mark Nelson Date: Mon, 13 Jun 2016 19:39:57 +0800 Subject: [PATCH] Added ability to edit an element on the rearrange page Increased the Moodle version required as this feature uses the AMD module 'core/fragment' which was introduced in 3.1. --- amd/build/dialogue.min.js | 1 + amd/build/rearrange-area.min.js | 1 + amd/src/dialogue.js | 100 ++++++++++++++++++ amd/src/rearrange-area.js | 148 +++++++++++++++++++++++++++ classes/edit_element_form.php | 17 ++-- classes/element.php | 5 +- classes/external.php | 164 ++++++++++++++++++++++++++++++ db/services.php | 42 ++++++++ element/image/classes/element.php | 3 +- lib.php | 18 ++++ rearrange.php | 12 ++- styles.css | 8 ++ version.php | 6 +- 13 files changed, 510 insertions(+), 15 deletions(-) create mode 100644 amd/build/dialogue.min.js create mode 100644 amd/build/rearrange-area.min.js create mode 100644 amd/src/dialogue.js create mode 100644 amd/src/rearrange-area.js create mode 100644 classes/external.php create mode 100644 db/services.php diff --git a/amd/build/dialogue.min.js b/amd/build/dialogue.min.js new file mode 100644 index 0000000..7538d6a --- /dev/null +++ b/amd/build/dialogue.min.js @@ -0,0 +1 @@ +define(["core/yui"],function(a){var b=function(b,c,d,e,f){this.yuiDialogue=null;var g=this;"undefined"==typeof f&&(f=!1),a.use("moodle-core-notification","timers",function(){var h="480px";f&&(h="800px"),g.yuiDialogue=new M.core.dialogue({headerContent:b,bodyContent:c,draggable:!0,visible:!1,center:!0,modal:!0,width:h}),g.yuiDialogue.after("visibleChange",function(b){b.newVal?"undefined"!=typeof d&&a.soon(function(){d(g),g.yuiDialogue.centerDialogue()}):"undefined"!=typeof e&&a.soon(function(){e(g)})}),g.yuiDialogue.show()})};return b.prototype.close=function(){this.yuiDialogue.hide(),this.yuiDialogue.destroy()},b.prototype.getContent=function(){return this.yuiDialogue.bodyNode.getDOMNode()},b}); \ No newline at end of file diff --git a/amd/build/rearrange-area.min.js b/amd/build/rearrange-area.min.js new file mode 100644 index 0000000..22a0898 --- /dev/null +++ b/amd/build/rearrange-area.min.js @@ -0,0 +1 @@ +define(["jquery","core/yui","core/fragment","mod_customcert/dialogue","core/notification","core/str","core/templates","core/ajax"],function(a,b,c,d,e,f,g,h){var i=function(b){this._node=a(b),this._setEvents()};return i.prototype.CUSTOMCERT_REF_POINT_TOPLEFT=0,i.prototype.CUSTOMCERT_REF_POINT_TOPCENTER=1,i.prototype.CUSTOMCERT_REF_POINT_TOPRIGHT=2,i.prototype._setEvents=function(){this._node.on("click",".element",this._editElement.bind(this))},i.prototype._editElement=function(a){var g=a.currentTarget.id.substr(8),h=this._node.attr("data-contextid"),i={elementid:g};c.loadFragment("mod_customcert","editelement",h,i).done(function(a,c){f.get_string("editelement","mod_customcert").done(function(e){b.use("moodle-core-formchangechecker",function(){new d(e,"
",this._editElementDialogueConfig.bind(this,g,a,c),void 0,!0)}.bind(this))}.bind(this))}.bind(this)).fail(e.exception)},i.prototype._editElementDialogueConfig=function(b,c,d,e){g.replaceNode("#elementcontent",c,d);var f=a(e.getContent());f.on("click","#id_submitbutton",function(c){M.core_formchangechecker.reset_form_dirty_state(),this._saveElement(b).always(function(){this._getElementHTML(b).always(function(c){var d=this._node.find("#element-"+b),f=a("#id_refpoint").val(),g="";f==this.CUSTOMCERT_REF_POINT_TOPLEFT?g="refpoint-left":f==this.CUSTOMCERT_REF_POINT_TOPCENTER?g="refpoint-center":f==this.CUSTOMCERT_REF_POINT_TOPRIGHT&&(g="refpoint-right"),d.empty().append(c),d.removeClass(),d.addClass("element "+g),e.close()}.bind(this))}.bind(this)),c.preventDefault()}.bind(this)),f.on("click","#id_cancel",function(a){e.close(),a.preventDefault()}.bind(this))},i.prototype._getElementHTML=function(a){var b=this._node.attr("data-templateid"),c=h.call([{methodname:"mod_customcert_get_element_html",args:{templateid:b,elementid:a}}]);return c[0]},i.prototype._saveElement=function(b){var c=this._node.attr("data-templateid"),d=a("#editelementform").serializeArray(),e=h.call([{methodname:"mod_customcert_save_element",args:{templateid:c,elementid:b,values:d}}]);return e[0]},{init:function(a){new i(a)}}}); \ No newline at end of file diff --git a/amd/src/dialogue.js b/amd/src/dialogue.js new file mode 100644 index 0000000..88da5ac --- /dev/null +++ b/amd/src/dialogue.js @@ -0,0 +1,100 @@ +// 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 . + +/** + * Wrapper for the YUI M.core.notification class. Allows us to + * use the YUI version in AMD code until it is replaced. + * + * @module mod_customcert/dialogue + * @package mod_customcert + * @copyright 2016 Mark Nelson + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +define(['core/yui'], function(Y) { + + /** + * Constructor + * + * @param {String} title Title for the window. + * @param {String} content The content for the window. + * @param {function} afterShow Callback executed after the window is opened. + * @param {function} afterHide Callback executed after the window is closed. + * @param {Boolean} wide Specify we want an extra wide dialogue (the size is standard, but wider than the default). + */ + var dialogue = function(title, content, afterShow, afterHide, wide) { + this.yuiDialogue = null; + var parent = this; + + // Default for wide is false. + if (typeof wide == 'undefined') { + wide = false; + } + + Y.use('moodle-core-notification', 'timers', function () { + var width = '480px'; + if (wide) { + width = '800px'; + } + + parent.yuiDialogue = new M.core.dialogue({ + headerContent: title, + bodyContent: content, + draggable: true, + visible: false, + center: true, + modal: true, + width: width + }); + + parent.yuiDialogue.after('visibleChange', function(e) { + if (e.newVal) { + // Delay the callback call to the next tick, otherwise it can happen that it is + // executed before the dialogue constructor returns. + if ((typeof afterShow !== 'undefined')) { + Y.soon(function() { + afterShow(parent); + parent.yuiDialogue.centerDialogue(); + }); + } + } else { + if ((typeof afterHide !== 'undefined')) { + Y.soon(function() { + afterHide(parent); + }); + } + } + }); + + parent.yuiDialogue.show(); + }); + }; + + /** + * Close this window. + */ + dialogue.prototype.close = function() { + this.yuiDialogue.hide(); + this.yuiDialogue.destroy(); + }; + + /** + * Get content. + */ + dialogue.prototype.getContent = function() { + return this.yuiDialogue.bodyNode.getDOMNode(); + }; + + return /** @alias module:mod_customcert/dialogue */ dialogue; +}); diff --git a/amd/src/rearrange-area.js b/amd/src/rearrange-area.js new file mode 100644 index 0000000..034c2d7 --- /dev/null +++ b/amd/src/rearrange-area.js @@ -0,0 +1,148 @@ +// 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 . + +/** + * AMD module used when rearranging a custom certificate. + * + * @module mod_customcert/rearrange-area + * @package mod_customcert + * @copyright 2016 Mark Nelson + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +define(['jquery', 'core/yui', 'core/fragment', 'mod_customcert/dialogue', 'core/notification', + 'core/str', 'core/templates', 'core/ajax'], + function($, Y, fragment, Dialogue, notification, str, template, ajax) { + + /** + * RearrangeArea class. + * + * @param {String} selector The rearrange PDF selector + */ + var RearrangeArea = function(selector) { + this._node = $(selector); + this._setEvents(); + }; + + RearrangeArea.prototype.CUSTOMCERT_REF_POINT_TOPLEFT = 0; + RearrangeArea.prototype.CUSTOMCERT_REF_POINT_TOPCENTER = 1; + RearrangeArea.prototype.CUSTOMCERT_REF_POINT_TOPRIGHT = 2; + + RearrangeArea.prototype._setEvents = function() { + this._node.on('click', '.element', this._editElement.bind(this)); + }; + + RearrangeArea.prototype._editElement = function(event) { + var elementid = event.currentTarget.id.substr(8); + var contextid = this._node.attr('data-contextid'); + var params = { + 'elementid' : elementid + }; + + fragment.loadFragment('mod_customcert', 'editelement', contextid, params).done(function(html, js) { + str.get_string('editelement', 'mod_customcert').done(function(title) { + Y.use('moodle-core-formchangechecker', function () { + new Dialogue( + title, + '
', + this._editElementDialogueConfig.bind(this, elementid, html, js), + undefined, + true + ); + }.bind(this)); + }.bind(this)); + }.bind(this)).fail(notification.exception); + }; + + RearrangeArea.prototype._editElementDialogueConfig = function(elementid, html, js, popup) { + // Place the content in the dialogue. + template.replaceNode('#elementcontent', html, js); + + // Add events for when we save, close and cancel the page. + var body = $(popup.getContent()); + body.on('click', '#id_submitbutton', function(e) { + // Do not want to ask the user if they wish to stay on page after saving. + M.core_formchangechecker.reset_form_dirty_state(); + // Save the data. + this._saveElement(elementid).always(function() { + // Update the DOM to reflect the adjusted value. + this._getElementHTML(elementid).always(function(html) { + var elementNode = this._node.find('#element-' + elementid); + var refpoint = $('#id_refpoint').val(); + var refpointClass = ''; + if (refpoint == this.CUSTOMCERT_REF_POINT_TOPLEFT) { + refpointClass = 'refpoint-left'; + } else if (refpoint == this.CUSTOMCERT_REF_POINT_TOPCENTER) { + refpointClass = 'refpoint-center'; + } else if (refpoint == this.CUSTOMCERT_REF_POINT_TOPRIGHT) { + refpointClass = 'refpoint-right'; + } + elementNode.empty().append(html); + // Update the ref point. + elementNode.removeClass(); + elementNode.addClass('element ' + refpointClass); + popup.close(); + }.bind(this)); + }.bind(this)); + e.preventDefault(); + }.bind(this)); + + body.on('click', '#id_cancel', function(e) { + popup.close(); + e.preventDefault(); + }.bind(this)); + }; + + RearrangeArea.prototype._getElementHTML = function(elementid) { + // Get the variables we need. + var templateid = this._node.attr('data-templateid'); + + // Call the web service to get the updated element. + var promises = ajax.call([{ + methodname: 'mod_customcert_get_element_html', + args: { + templateid : templateid, + elementid : elementid + } + }]); + + // Return the promise. + return promises[0]; + }; + + RearrangeArea.prototype._saveElement = function(elementid) { + // Get the variables we need. + var templateid = this._node.attr('data-templateid'); + var inputs = $('#editelementform').serializeArray(); + + // Call the web service to save the element. + var promises = ajax.call([{ + methodname: 'mod_customcert_save_element', + args: { + templateid : templateid, + elementid : elementid, + values : inputs + } + }]); + + // Return the promise. + return promises[0]; + }; + + return { + init : function(selector) { + new RearrangeArea(selector); + } + }; +}); diff --git a/classes/edit_element_form.php b/classes/edit_element_form.php index a2df501..7a38033 100644 --- a/classes/edit_element_form.php +++ b/classes/edit_element_form.php @@ -44,14 +44,19 @@ class edit_element_form extends \moodleform { public function definition() { $mform =& $this->_form; + $mform->updateAttributes(array('id' => 'editelementform')); + $element = $this->_customdata['element']; - // Add the field for the name of the element, this is required for all elements. - $mform->addElement('text', 'name', get_string('elementname', 'customcert')); - $mform->setType('name', PARAM_TEXT); - $mform->setDefault('name', get_string('pluginname', 'customcertelement_' . $element->element)); - $mform->addRule('name', get_string('required'), 'required', null, 'client'); - $mform->addHelpButton('name', 'elementname', 'customcert'); + // Do not display the name if we are on the rearrange page. + if (!isset($this->_customdata['rearrange'])) { + // Add the field for the name of the element, this is required for all elements. + $mform->addElement('text', 'name', get_string('elementname', 'customcert')); + $mform->setType('name', PARAM_TEXT); + $mform->setDefault('name', get_string('pluginname', 'customcertelement_' . $element->element)); + $mform->addRule('name', get_string('required'), 'required', null, 'client'); + $mform->addHelpButton('name', 'elementname', 'customcert'); + } $this->element = \mod_customcert\element::instance($element); $this->element->render_form_elements($mform); diff --git a/classes/element.php b/classes/element.php index 5f85ab2..a0a5a18 100644 --- a/classes/element.php +++ b/classes/element.php @@ -101,6 +101,7 @@ abstract class element { * Can be overridden if more functionality is needed. * * @param \stdClass $data the form data + * @return bool true of success, false otherwise. */ public function save_form_elements($data) { global $DB; @@ -119,13 +120,13 @@ abstract class element { // Check if we are updating, or inserting a new element. if (!empty($this->element->id)) { // Must be updating a record in the database. $element->id = $this->element->id; - $DB->update_record('customcert_elements', $element); + return $DB->update_record('customcert_elements', $element); } else { // Must be adding a new one. $element->element = $data->element; $element->pageid = $data->pageid; $element->sequence = \mod_customcert\element_helper::get_element_sequence($element->pageid); $element->timecreated = time(); - $DB->insert_record('customcert_elements', $element); + return $DB->insert_record('customcert_elements', $element, false); } } diff --git a/classes/external.php b/classes/external.php new file mode 100644 index 0000000..5e993b3 --- /dev/null +++ b/classes/external.php @@ -0,0 +1,164 @@ +. + +/** + * This is the external API for this tool. + * + * @package mod_customcert + * @copyright 2016 Mark Nelson + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace mod_customcert; +defined('MOODLE_INTERNAL') || die(); + +require_once("$CFG->libdir/externallib.php"); + +/** +* This is the external API for this tool. +* +* @copyright 2016 Mark Nelson +* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later +*/ +class external extends \external_api { + + /** + * Returns the save_element() parameters. + * + * @return \external_function_parameters + */ + public static function save_element_parameters() { + return new \external_function_parameters( + array( + 'templateid' => new \external_value(PARAM_INT, 'The template id'), + 'elementid' => new \external_value(PARAM_INT, 'The element id'), + 'values' => new \external_multiple_structure( + new \external_single_structure( + array( + 'name' => new \external_value(PARAM_ALPHANUMEXT, 'The field to update'), + 'value' => new \external_value(PARAM_RAW, 'The value of the field'), + ) + ) + ) + ) + ); + } + + /** + * Handles saving element data. + * + * @param int $templateid The template id. + * @param int $elementid The element id. + * @param array $values The values to save + * @return array + */ + public static function save_element($templateid, $elementid, $values) { + global $DB; + + $template = $DB->get_record('customcert_templates', array('id' => $templateid), '*', MUST_EXIST); + $element = $DB->get_record('customcert_elements', array('id' => $elementid), '*', MUST_EXIST); + + // Set the template. + $template = new \mod_customcert\template($template); + + // Perform checks. + if ($cm = $template->get_cm()) { + require_login($cm->course, false, $cm); + } else { + require_login(); + } + // Make sure the user has the required capabilities. + $template->require_manage(); + + // Set the values we are going to save. + $data = new \stdClass(); + $data->id = $element->id; + $data->name = $element->name; + foreach ($values as $value) { + $field = $value['name']; + $data->$field = $value['value']; + } + + // Get an instance of the element class. + if ($e = \mod_customcert\element::instance($element)) { + return $e->save_form_elements($data); + } + + return false; + } + + /** + * Returns the save_element result value. + * + * @return \external_value + */ + public static function save_element_returns() { + return new \external_value(PARAM_BOOL, 'True if successful, false otherwise'); + } + + /** + * Returns get_element() parameters. + * + * @return \external_function_parameters + */ + public static function get_element_html_parameters() { + return new \external_function_parameters( + array( + 'templateid' => new \external_value(PARAM_INT, 'The template id'), + 'elementid' => new \external_value(PARAM_INT, 'The element id'), + ) + ); + } + + /** + * Handles return the element's HTML. + * + * @param int $templateid The template id + * @param int $elementid The element id. + * @return string + */ + public static function get_element_html($templateid, $elementid) { + global $DB; + + $template = $DB->get_record('customcert_templates', array('id' => $templateid), '*', MUST_EXIST); + $element = $DB->get_record('customcert_elements', array('id' => $elementid), '*', MUST_EXIST); + + // Set the template. + $template = new \mod_customcert\template($template); + + // Perform checks. + if ($cm = $template->get_cm()) { + require_login($cm->course, false, $cm); + } else { + require_login(); + } + + // Get an instance of the element class. + if ($e = \mod_customcert\element::instance($element)) { + return $e->render_html(); + } + + return ''; + } + + /** + * Returns the get_element result value. + * + * @return \external_value + */ + public static function get_element_html_returns() { + return new \external_value(PARAM_RAW, 'The HTML'); + } +} diff --git a/db/services.php b/db/services.php new file mode 100644 index 0000000..1a74560 --- /dev/null +++ b/db/services.php @@ -0,0 +1,42 @@ +. + +/** + * Web service for mod customcert. + * + * @package mod_customcert + * @copyright 2016 Mark Nelson + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +$functions = array( + 'mod_customcert_save_element' => array( + 'classname' => 'mod_customcert\external', + 'methodname' => 'save_element', + 'classpath' => '', + 'description' => 'Saves data for an element', + 'type' => 'write', + 'ajax' => true + ), + 'mod_customcert_get_element_html' => array( + 'classname' => 'mod_customcert\external', + 'methodname' => 'get_element_html', + 'classpath' => '', + 'description' => 'Returns the HTML to display for an element', + 'type' => 'read', + 'ajax' => true + ), +); diff --git a/element/image/classes/element.php b/element/image/classes/element.php index 39b817a..5fe6706 100644 --- a/element/image/classes/element.php +++ b/element/image/classes/element.php @@ -99,6 +99,7 @@ class element extends \mod_customcert\element { * Can be overridden if more functionality is needed. * * @param \stdClass $data the form data + * @return bool true of success, false otherwise. */ public function save_form_elements($data) { global $COURSE; @@ -106,7 +107,7 @@ class element extends \mod_customcert\element { // Handle file uploads. \mod_customcert\certificate::upload_imagefiles($data->customcertimage, \context_course::instance($COURSE->id)->id); - parent::save_form_elements($data); + return parent::save_form_elements($data); } /** diff --git a/lib.php b/lib.php index 5c633f7..33c01ee 100644 --- a/lib.php +++ b/lib.php @@ -303,6 +303,24 @@ function customcert_cron() { return true; } +/** + * Serve the edit element as a fragment. + * + * @param array $args List of named arguments for the fragment loader. + * @return string + */ +function mod_customcert_output_fragment_editelement($args) { + global $DB; + + // Get the element. + $element = $DB->get_record('customcert_elements', array('id' => $args['elementid']), '*', MUST_EXIST); + + $pageurl = new moodle_url('/mod/customcert/rearrange.php', array('pid' => $element->pageid)); + $form = new \mod_customcert\edit_element_form($pageurl, array('element' => $element, 'rearrange' => true)); + + return $form->render(); +} + /** * This function extends the settings navigation block for the site. * diff --git a/rearrange.php b/rearrange.php index a8bebb4..4f6def5 100644 --- a/rearrange.php +++ b/rearrange.php @@ -65,7 +65,12 @@ $html .= html_writer::end_tag('div'); // Create the div that represents the PDF. $style = 'height: ' . $page->height . 'mm; line-height: normal; width: ' . $page->width . 'mm;'; $marginstyle = 'height: ' . $page->height . 'mm; width:1px; float:left; position:relative;'; -$html .= html_writer::start_tag('div', array('id' => 'pdf', 'style' => $style)); +$html .= html_writer::start_tag('div', array( + 'data-templateid' => $template->get_id(), + 'data-contextid' => $template->get_contextid(), + 'id' => 'pdf', + 'style' => $style) +); if ($page->leftmargin) { $position = 'left:' . $page->leftmargin . 'mm;'; $html .= "
"; @@ -83,7 +88,7 @@ if ($elements) { break; case \mod_customcert\element_helper::CUSTOMCERT_REF_POINT_TOPLEFT: default: - $class = 'element refpoint-left'; + $class = 'element refpoint-left'; } $html .= html_writer::tag('div', $e->render_html(), array('class' => $class, 'id' => 'element-' . $element->id)); } @@ -98,4 +103,5 @@ $html .= html_writer::end_tag('div'); echo $OUTPUT->header(); echo $OUTPUT->heading(get_string('rearrangeelementsheading', 'customcert'), 4); echo $html; -echo $OUTPUT->footer(); \ No newline at end of file +$PAGE->requires->js_call_amd('mod_customcert/rearrange-area', 'init', array('#pdf')); +echo $OUTPUT->footer(); diff --git a/styles.css b/styles.css index 3c4aa5c..238cae6 100644 --- a/styles.css +++ b/styles.css @@ -65,3 +65,11 @@ #page-mod-customcert-rearrange div#rightmargin { border-right: 1px dotted black; } + +/* + This is a hack - the fieldset being returned by the edit_element_form class contains + the CSS class 'hidden' which when shown in a dialogue hides the fieldset. +*/ +.moodle-dialogue #editelementform fieldset.hidden { + display: block; +} diff --git a/version.php b/version.php index 8b6a4d8..1ef024a 100644 --- a/version.php +++ b/version.php @@ -24,10 +24,10 @@ defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.'); -$plugin->version = 2016032200; // The current module version (Date: YYYYMMDDXX). -$plugin->requires = 2015051100; // Requires this Moodle version (2.9). +$plugin->version = 2016052300; // The current module version (Date: YYYYMMDDXX). +$plugin->requires = 2016052300; // Requires this Moodle version (3.1). $plugin->cron = 0; // Period for cron to check this module (secs). $plugin->component = 'mod_customcert'; $plugin->maturity = MATURITY_BETA; -$plugin->release = "Beta release (Build: 2016021900)"; // User-friendly version number. +$plugin->release = "3.1 beta release (Build: 2016052300)"; // User-friendly version number.