Commit 91bda4cd authored by Damyon Wiese's avatar Damyon Wiese Committed by Dan Poltawski
Browse files

MDL-55417 forms: Render form elements with a template

This change allows form elements to be overridden with a mustache template.
The template can even listen for form validation errors and supply the JS to
change the look of the form element when there is/isn't a validation error.

Initial support is for all core form elements including:

text, select, selectyesno and checkboxes, groups, dateselector, datetimeselector,
autocomplete, modvisible, advcheckbox, button, duration, filemanager, filepicker, editor, static, grading,
warning, textarea, password, url, submit, questioncategory, recaptcha.

Part of MDL-55071
parent ef18a21f
......@@ -49,8 +49,12 @@ class edit_outcome_form extends moodleform {
$options = array();
$mform->addElement('selectwithlink', 'scaleid', get_string('scale'), $options, null,
array('link' => $CFG->wwwroot.'/grade/edit/scale/edit.php?courseid='.$COURSE->id, 'label' => get_string('scalescustomcreate')));
$mform->addElement('select', 'scaleid', get_string('scale'), $options);
$url = new moodle_url('/grade/edit/scale/edit.php', array('courseid' => $COURSE->id));
$label = get_string('scalescustomcreate');
$mform->addElement('static', 'scaleidlink', '', html_writer::link($url, $label));
$mform->addHelpButton('scaleid', 'typescale', 'grades');
$mform->addRule('scaleid', get_string('required'), 'required');
......
......@@ -56,8 +56,11 @@ class edit_outcomeitem_form extends moodleform {
$options[$outcome->id] = $outcome->get_name();
}
}
$mform->addElement('selectwithlink', 'outcomeid', get_string('outcome', 'grades'), $options, null,
array('link' => $CFG->wwwroot.'/grade/edit/outcome/course.php?id='.$COURSE->id, 'label' => get_string('outcomeassigntocourse', 'grades')));
$url = new moodle_url('/grade/edit/outcome/course.php', array('id' => $COURSE->id));
$label = get_string('outcomeassigntocourse', 'grades');
$mform->addElement('select', 'outcomeid', get_string('outcome', 'grades'), $options);
$mform->addElement('static', 'outcomeidlink', '', html_writer::link($url, $label));
$mform->addHelpButton('outcomeid', 'outcome', 'grades');
$mform->addRule('outcomeid', get_string('required'), 'required');
......
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
......@@ -27,7 +27,14 @@ define(['jquery', 'core/yui'],
function($, Y) {
return /** @alias module:core/event */ {
// Public variables and functions.
// These are AMD only events - no backwards compatibility for new things.
Events: {
FORM_FIELD_VALIDATION: "core_form-field-validation"
},
/**
* Trigger an event using both JQuery and YUI
*
......
......@@ -26,6 +26,7 @@
*/
require_once('HTML/QuickForm/advcheckbox.php');
require_once(__DIR__ . '/../outputcomponents.php');
/**
* HTML class for an advcheckbox type element
......@@ -38,7 +39,7 @@ require_once('HTML/QuickForm/advcheckbox.php');
* @copyright 2007 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_advcheckbox extends HTML_QuickForm_advcheckbox{
class MoodleQuickForm_advcheckbox extends HTML_QuickForm_advcheckbox implements templatable {
/** @var string html for help button, if empty then no help will icon will be dispalyed. */
var $_helpbutton='';
......@@ -130,4 +131,13 @@ class MoodleQuickForm_advcheckbox extends HTML_QuickForm_advcheckbox{
}
return $output;
}
public function export_for_template(renderer_base $output) {
$context = [];
$context['frozen'] = $this->_flagFrozen;
foreach ($this->getAttributes() as $name => $value) {
$context[$name] = $value;
}
return $context;
}
}
......@@ -204,4 +204,23 @@ class MoodleQuickForm_autocomplete extends MoodleQuickForm_select {
}
return parent::onQuickFormEvent($event, $arg, $caller);
}
public function export_for_template(renderer_base $output) {
global $PAGE;
$this->_generateId();
$id = $this->getAttribute('id');
$PAGE->requires->js_call_amd('core/form-autocomplete', 'enhance', $params = array('#' . $id, $this->tags, $this->ajax,
$this->placeholder, $this->casesensitive, $this->showsuggestions, $this->noselectionstring));
$context = parent::export_for_template($output);
$context['tags'] = $this->tags;
$context['ajax'] = $this->ajax;
$context['placeholder'] = $this->placeholder;
$context['casesensitive'] = $this->casesensitive;
$context['showsuggestions'] = $this->showsuggestions;
$context['noselectionstring'] = $this->noselectionstring;
return $context;
}
}
......@@ -26,6 +26,7 @@
*/
require_once("HTML/QuickForm/button.php");
require_once(__DIR__ . '/../outputcomponents.php');
/**
* HTML class for a button type element
......@@ -37,7 +38,7 @@ require_once("HTML/QuickForm/button.php");
* @copyright 2007 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_button extends HTML_QuickForm_button
class MoodleQuickForm_button extends HTML_QuickForm_button implements templatable
{
/** @var string html for help button, if empty then no help */
var $_helpbutton='';
......@@ -85,4 +86,13 @@ class MoodleQuickForm_button extends HTML_QuickForm_button
return 'default';
}
}
public function export_for_template(renderer_base $output) {
$context = [];
$context['frozen'] = $this->_flagFrozen;
foreach ($this->getAttributes() as $name => $value) {
$context[$name] = $value;
}
return $context;
}
}
......@@ -26,6 +26,7 @@
*/
require_once('HTML/QuickForm/checkbox.php');
require_once(__DIR__ . '/../outputcomponents.php');
/**
* HTML class for a checkbox type element
......@@ -39,7 +40,7 @@ require_once('HTML/QuickForm/checkbox.php');
* @copyright 2007 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_checkbox extends HTML_QuickForm_checkbox{
class MoodleQuickForm_checkbox extends HTML_QuickForm_checkbox implements templatable {
/** @var string html for help button, if empty then no help */
var $_helpbutton='';
......@@ -138,4 +139,13 @@ class MoodleQuickForm_checkbox extends HTML_QuickForm_checkbox{
}
return $output;
}
public function export_for_template(renderer_base $output) {
$context = [];
$context['frozen'] = $this->_flagFrozen;
foreach ($this->getAttributes() as $name => $value) {
$context[$name] = $value;
}
return $context;
}
}
......@@ -30,6 +30,7 @@ global $CFG;
require_once('HTML/QuickForm/element.php');
require_once($CFG->dirroot.'/lib/filelib.php');
require_once($CFG->dirroot.'/repository/lib.php');
require_once($CFG->libdir.'/outputcomponents.php');
/**
* Editor element
......@@ -43,7 +44,7 @@ require_once($CFG->dirroot.'/repository/lib.php');
* @todo MDL-29421 element Freezing
* @todo MDL-29426 ajax format conversion
*/
class MoodleQuickForm_editor extends HTML_QuickForm_element {
class MoodleQuickForm_editor extends HTML_QuickForm_element implements templatable {
/** @var string html for help button, if empty then no help will icon will be dispalyed. */
public $_helpbutton = '';
......@@ -293,7 +294,7 @@ class MoodleQuickForm_editor extends HTML_QuickForm_element {
* @return string
*/
function toHtml() {
global $CFG, $PAGE;
global $CFG, $PAGE, $OUTPUT;
require_once($CFG->dirroot.'/repository/lib.php');
if ($this->_flagFrozen) {
......@@ -394,24 +395,27 @@ class MoodleQuickForm_editor extends HTML_QuickForm_element {
$cols = empty($this->_attributes['cols']) ? 80 : $this->_attributes['cols'];
//Apply editor validation if required field
$editorrules = '';
if (!is_null($this->getAttribute('onblur')) && !is_null($this->getAttribute('onchange'))) {
$editorrules = ' onblur="'.htmlspecialchars($this->getAttribute('onblur')).'" onchange="'.htmlspecialchars($this->getAttribute('onchange')).'"';
$context = [];
$context['rows'] = $rows;
$context['cols'] = $cols;
$context['frozen'] = $this->_flagFrozen;
foreach ($this->getAttributes() as $name => $value) {
$context[$name] = $value;
}
$context['hasformats'] = count($formats) > 1;
$context['formats'] = [];
foreach ($formats as $value => $text) {
$context['formats'][] = ['value' => $value, 'text' => $text, 'selected' => ($value == $format)];
}
$str .= '<div><textarea id="'.$id.'" name="'.$elname.'[text]" rows="'.$rows.'" cols="'.$cols.'" spellcheck="true"'.$editorrules.'>';
$str .= s($text);
$str .= '</textarea></div>';
$context['id'] = $id;
$context['value'] = $text;
$context['format'] = $format;
$str .= '<div>';
if (count($formats)>1) {
$str .= html_writer::label(get_string('format'), 'menu'. $elname. 'format', false, array('class' => 'accesshide'));
$str .= html_writer::select($formats, $elname.'[format]', $format, false, array('id' => 'menu'. $elname. 'format'));
} else {
$keys = array_keys($formats);
$str .= html_writer::empty_tag('input',
array('name'=>$elname.'[format]', 'type'=> 'hidden', 'value' => array_pop($keys)));
$str .= $OUTPUT->render_from_template('core_form/editor_textarea', $context);
if (!is_null($this->getAttribute('onblur')) && !is_null($this->getAttribute('onchange'))) {
$context['changelistener'] = true;
}
$str .= '</div>';
// during moodle installation, user area doesn't exist
// so we need to disable filepicker here.
......@@ -446,6 +450,16 @@ class MoodleQuickForm_editor extends HTML_QuickForm_element {
return $str;
}
public function export_for_template(renderer_base $output) {
$context = [];
$context['frozen'] = $this->_flagFrozen;
foreach ($this->getAttributes() as $name => $value) {
$context[$name] = $value;
}
$context['html'] = $this->toHtml();
return $context;
}
/**
* What to display when element is frozen.
*
......
......@@ -30,6 +30,7 @@ global $CFG;
require_once('HTML/QuickForm/element.php');
require_once($CFG->dirroot.'/lib/filelib.php');
require_once($CFG->dirroot.'/repository/lib.php');
require_once($CFG->libdir.'/outputcomponents.php');
/**
* Filemanager form element
......@@ -40,7 +41,7 @@ require_once($CFG->dirroot.'/repository/lib.php');
* @copyright 2009 Dongsheng Cai <dongsheng@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_filemanager extends HTML_QuickForm_element {
class MoodleQuickForm_filemanager extends HTML_QuickForm_element implements templatable {
/** @var string html for help button, if empty then no help will icon will be dispalyed. */
public $_helpbutton = '';
......@@ -297,6 +298,16 @@ class MoodleQuickForm_filemanager extends HTML_QuickForm_element {
return $html;
}
public function export_for_template(renderer_base $output) {
$context = [];
$context['frozen'] = $this->_flagFrozen;
foreach ($this->getAttributes() as $name => $value) {
$context[$name] = $value;
}
$context['html'] = $this->toHtml();
return $context;
}
}
/**
......
......@@ -29,6 +29,7 @@ global $CFG;
require_once("HTML/QuickForm/button.php");
require_once($CFG->dirroot.'/repository/lib.php');
require_once($CFG->libdir.'/outputcomponents.php');
/**
* Filepicker form element
......@@ -40,7 +41,7 @@ require_once($CFG->dirroot.'/repository/lib.php');
* @copyright 2009 Dongsheng Cai <dongsheng@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_filepicker extends HTML_QuickForm_input {
class MoodleQuickForm_filepicker extends HTML_QuickForm_input implements templatable {
/** @var string html for help button, if empty then no help will icon will be dispalyed. */
public $_helpbutton = '';
......@@ -220,4 +221,14 @@ class MoodleQuickForm_filepicker extends HTML_QuickForm_input {
return $this->_prepareValue($draftitemid, true);
}
public function export_for_template(renderer_base $output) {
$context = [];
$context['frozen'] = $this->_flagFrozen;
foreach ($this->getAttributes() as $name => $value) {
$context[$name] = $value;
}
$context['html'] = $this->toHtml();
return $context;
}
}
......@@ -28,6 +28,7 @@
global $CFG;
require_once("HTML/QuickForm/element.php");
require_once($CFG->dirroot.'/grade/grading/form/lib.php');
require_once($CFG->libdir.'/outputcomponents.php');
if (class_exists('HTML_QuickForm')) {
HTML_QuickForm::registerRule('gradingvalidated', 'callback', '_validate', 'MoodleQuickForm_grading');
......@@ -47,7 +48,7 @@ if (class_exists('HTML_QuickForm')) {
* @copyright 2011 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_grading extends HTML_QuickForm_input{
class MoodleQuickForm_grading extends HTML_QuickForm_input implements templatable {
/** @var string html for help button, if empty then no help */
var $_helpbutton='';
......@@ -63,6 +64,7 @@ class MoodleQuickForm_grading extends HTML_QuickForm_input{
*/
public function __construct($elementName=null, $elementLabel=null, $attributes=null) {
parent::__construct($elementName, $elementLabel, $attributes);
$this->_type = 'grading';
$this->gradingattributes = $attributes;
}
......@@ -158,4 +160,8 @@ class MoodleQuickForm_grading extends HTML_QuickForm_input{
}
return true;
}
public function export_for_template(renderer_base $output) {
return $this->toHtml();
}
}
......@@ -26,6 +26,7 @@
*/
require_once("HTML/QuickForm/group.php");
require_once(__DIR__ . '/../outputcomponents.php');
/**
* HTML class for a form element group
......@@ -37,13 +38,15 @@ require_once("HTML/QuickForm/group.php");
* @copyright 2007 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_group extends HTML_QuickForm_group{
class MoodleQuickForm_group extends HTML_QuickForm_group implements templatable {
/** @var string html for help button, if empty then no help */
var $_helpbutton='';
/** @var MoodleQuickForm */
protected $_mform = null;
var $_renderedfromtemplate = false;
/**
* constructor
*
......@@ -147,4 +150,89 @@ class MoodleQuickForm_group extends HTML_QuickForm_group{
}
return call_user_func_array([$this->_mform, 'createElement'], func_get_args());
}
public function export_for_template(renderer_base $output) {
global $OUTPUT;
$this->_renderedfromtemplate = true;
include_once('HTML/QuickForm/Renderer/Default.php');
$context = [];
$context['frozen'] = $this->_flagFrozen;
foreach ($this->getAttributes() as $name => $value) {
$context[$name] = $value;
}
$elements = [];
foreach ($this->_elements as $key => $element) {
$element->_generateId();
$name = $this->getName();
$elementname = '';
if ($this->_appendName) {
$elementname = $element->getName();
if (isset($elementname)) {
$element->setName($name . '['. (strlen($elementname) ? $elementname : $key) .']');
} else {
$element->setName($name);
}
}
$out = $OUTPUT->mform_element($element, false, '', true);
if (empty($out)) {
$renderer = new HTML_QuickForm_Renderer_Default();
$renderer->setElementTemplate('{element}');
$element->accept($renderer);
$out = $renderer->toHtml();
}
$elements[] = $out;
// Restore the element's name.
if ($this->_appendName) {
$element->setName($elementname);
}
}
$context['elements'] = $elements;
return $context;
}
/**
* Accepts a renderer
*
* @param object An HTML_QuickForm_Renderer object
* @param bool Whether a group is required
* @param string An error message associated with a group
* @access public
* @return void
*/
function accept(&$renderer, $required = false, $error = null) {
$this->_createElementsIfNotExist();
$renderer->startGroup($this, $required, $error);
if (!$this->_renderedfromtemplate) {
// Backwards compatible path - only do this if we didn't render the sub-elements already.
$name = $this->getName();
foreach (array_keys($this->_elements) as $key) {
$element =& $this->_elements[$key];
if ($this->_appendName) {
$elementName = $element->getName();
if (isset($elementName)) {
$element->setName($name . '['. (strlen($elementName)? $elementName: $key) .']');
} else {
$element->setName($name);
}
}
$required = !$element->isFrozen() && in_array($element->getName(), $this->_required);
$element->accept($renderer, $required);
// restore the element's name
if ($this->_appendName) {
$element->setName($elementName);
}
}
}
$renderer->finishGroup($this);
}
}
......@@ -149,14 +149,14 @@ class MoodleQuickForm_modgrade extends MoodleQuickForm_group {
$langscale = get_string('modgradetypescale', 'grades');
$this->scaleformelement = $this->createFormElement('select', 'modgrade_scale', $langscale,
$scales, $attributes);
$this->scaleformelement->setHiddenLabel = false;
$this->scaleformelement->setHiddenLabel(true);
$scaleformelementid = $this->generate_modgrade_subelement_id('modgrade_scale');
$this->scaleformelement->updateAttributes(array('id' => $scaleformelementid));
// Maximum grade textbox.
$langmaxgrade = get_string('modgrademaxgrade', 'grades');
$this->maxgradeformelement = $this->createFormElement('text', 'modgrade_point', $langmaxgrade, array());
$this->maxgradeformelement->setHiddenLabel = false;
$this->maxgradeformelement->setHiddenLabel(true);
$maxgradeformelementid = $this->generate_modgrade_subelement_id('modgrade_point');
$this->maxgradeformelement->updateAttributes(array('id' => $maxgradeformelementid));
......@@ -169,7 +169,7 @@ class MoodleQuickForm_modgrade extends MoodleQuickForm_group {
$langtype = get_string('modgradetype', 'grades');
$this->gradetypeformelement = $this->createFormElement('select', 'modgrade_type', $langtype, $gradetype,
$attributes, true);
$this->gradetypeformelement->setHiddenLabel = false;
$this->gradetypeformelement->setHiddenLabel(true);
$gradetypeformelementid = $this->generate_modgrade_subelement_id('modgrade_type');
$this->gradetypeformelement->updateAttributes(array('id' => $gradetypeformelementid));
......@@ -188,7 +188,7 @@ class MoodleQuickForm_modgrade extends MoodleQuickForm_group {
'modgrade_rescalegrades',
$langrescalegrades,
$choices);
$rescalegradesselect->setHiddenLabel = false;
$rescalegradesselect->setHiddenLabel(true);
$rescalegradesselectid = $this->generate_modgrade_subelement_id('modgrade_rescalegrades');
$rescalegradesselect->updateAttributes(array('id' => $rescalegradesselectid));
}
......
......@@ -26,6 +26,7 @@
*/
require_once('HTML/QuickForm/password.php');
require_once(__DIR__ . '/../outputcomponents.php');
/**
* Password type form element
......@@ -37,7 +38,7 @@ require_once('HTML/QuickForm/password.php');
* @copyright 2006 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_password extends HTML_QuickForm_password{
class MoodleQuickForm_password extends HTML_QuickForm_password implements templatable {
/** @var string, html for help button, if empty then no help */
var $_helpbutton='';
......@@ -83,4 +84,13 @@ class MoodleQuickForm_password extends HTML_QuickForm_password{
function getHelpButton(){
return $this->_helpbutton;
}
public function export_for_template(renderer_base $output) {
$context = [];
$context['frozen'] = $this->_flagFrozen;
foreach ($this->getAttributes() as $name => $value) {
$context[$name] = $value;
}
return $context;
}
}
......@@ -26,6 +26,7 @@
*/
require_once('HTML/QuickForm/input.php');
require_once(__DIR__ . '/../outputcomponents.php');
/**
* recaptcha type form element
......@@ -37,7 +38,7 @@ require_once('HTML/QuickForm/input.php');
* @copyright 2008 Nicolas Connault <nicolasconnault@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_recaptcha extends HTML_QuickForm_input {
class MoodleQuickForm_recaptcha extends HTML_QuickForm_input implements templatable {
/** @var string html for help button, if empty then no help */
var $_helpbutton='';
......@@ -152,4 +153,14 @@ class MoodleQuickForm_recaptcha extends HTML_QuickForm_input {
}
return true;
}
public function export_for_template(renderer_base $output) {
$context = [];
$context['frozen'] = $this->_flagFrozen;
foreach ($this->getAttributes() as $name => $value) {
$context[$name] = $value;
}
$context['html'] = $this->toHtml();
return $context;
}
}
......@@ -26,6 +26,7 @@
*/
require_once('HTML/QuickForm/select.php');
require_once(__DIR__ . '/../outputcomponents.php');
/**
* select type form element
......@@ -37,7 +38,7 @@ require_once('HTML/QuickForm/select.php');
* @copyright 2006 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_select extends HTML_QuickForm_select{
class MoodleQuickForm_select extends HTML_QuickForm_select implements templatable {
/** @var string html for help button, if empty then no help */
var $_helpbutton='';
......@@ -189,4 +190,33 @@ class MoodleQuickForm_select extends HTML_QuickForm_select{
return $this->_prepareValue($cleaned[0], $assoc);
}
}
public function export_for_template(renderer_base $output) {
$context = [];
$context['frozen'] = $this->_flagFrozen;
$context['attributes'] = [];
foreach ($this->getAttributes() as $name => $value) {
$context[$name] = $value;
if (!in_array($name, ['id', 'name', 'multiple'])) {
$context['attributes'][] = ['name' => $name, 'value' => $value];
}
}
$options = [];
foreach ($this->_options as $option) {
if (is_array($this->_values) && in_array( (string) $option['attr']['value'], $this->_values)) {
$this->_updateAttrArray($option['attr'], ['selected' => 'selected']);
}
$o = [
'text' => $option['text'],
'value' => $option['attr']['value'],
'selected' => !empty($option['attr']['selected'])
];
$options[] = $o;
}
$context['options'] = $options;
$context['hideLabel'] = $this->_hiddenLabel;
return $context;
}
}
......@@ -26,6 +26,7 @@
*/
require_once('HTML/QuickForm/element.php');
require_once(__DIR__ . '/../outputcomponents.php');
/**
* select type form element
......@@ -37,7 +38,7 @@ require_once('HTML/QuickForm/element.php');
* @copyright 2007 Jamie Pratt <me@jamiep.org>