diff --git a/lib/form/form.js b/lib/form/form.js new file mode 100644 index 00000000000..6136ea3980e --- /dev/null +++ b/lib/form/form.js @@ -0,0 +1,288 @@ +/** + * This file contains JS functionality required by mforms and is included automatically + * when required. + */ + +// Namespace for the form bits and bobs +M.form = M.form || {}; + +/** + * Initialises the show advanced functionality and events. + * This should only ever happen ONCE per page. + * + * @param {YUI} Y + * @param {object} config + */ +M.form.initShowAdvanced = function(Y, config) { + if (M.form.showAdvanced) { + return M.form.showAdvanced; + } + var showAdvanced = function(config) { + showAdvanced.superclass.constructor.apply(this, arguments); + } + showAdvanced.prototype = { + _advButtons : [], + _advAreas : [], + _stateInput : null, + initializer : function() { + this._advAreas = Y.all('form .advanced'); + this._advButtons = Y.all('.showadvancedbtn'); + if (this._advButtons.size() > 0) { + this._advButtons.item(0).get('form').get('elements').each(function(n){ + if (this._stateInput == null && n.getAttribute('name') == 'mform_showadvanced_last') { + this._stateInput = n; + } + }, this); + this._advButtons.on('click', this.switchState, this); + } + }, + /** + * Toggles between showing advanced items and hiding them. + * Should be fired by an event. + */ + switchState : function(e) { + e.preventDefault(); + if (this._stateInput.get('value')=='1') { + this._stateInput.set('value', '0'); + this._advButtons.setAttribute('value', M.str.form.showadvanced); + this._advAreas.addClass('hide'); + } else { + this._stateInput.set('value', '1'); + this._advButtons.setAttribute('value', M.str.form.hideadvanced); + this._advAreas.removeClass('hide'); + } + } + } + // Extend it with the YUI widget fw. + Y.extend(showAdvanced, Y.Base, showAdvanced.prototype, { + NAME : 'mform-showAdvanced' + }) + M.form.showAdvanced = new showAdvanced(config); + return M.form.showAdvanced; +} + +/** + * Initialises a manager for a forms dependencies. + * This should happen once per form. + */ +M.form.initFormDependencies = function(Y, formid, dependencies) { + + // If the dependencies isn't an array or object we don't want to + // know about it + if (!Y.Lang.isArray(dependencies) && !Y.Lang.isObject(dependencies)) { + return false; + } + + /** + * Fixes an issue with YUI's processing method of form.elements property + * in Internet Explorer. + * http://yuilibrary.com/projects/yui3/ticket/2528030 + */ + Y.Node.ATTRS.elements = { + getter: function() { + return Y.all(new Y.Array(this._node.elements, 0, true)); + } + }; + + // Define the dependency manager if it hasn't already been defined. + M.form.dependencyManager = M.form.dependencyManager || (function(){ + var dependencyManager = function(config) { + dependencyManager.superclass.constructor.apply(this, arguments); + } + dependencyManager.prototype = { + _form : null, + _depElements : [], + _nameCollections : [], + initializer : function(config) { + var i = 0, nodeName; + this._form = Y.one('#'+formid); + for (i in dependencies) { + this._depElements[i] = this.elementsByName(i); + if (this._depElements[i].size() == 0) { + continue; + } + this._depElements[i].each(function(node){ + nodeName = node.get('nodeName').toUpperCase(); + if (nodeName == 'INPUT') { + if (node.getAttribute('type').match(/^(button|submit|radio|checkbox)$/)) { + node.on('click', this.checkDependencies, this); + } else { + node.on('blur', this.checkDependencies, this); + } + node.on('change', this.checkDependencies, this); + } else if (nodeName == 'SELECT') { + node.on('change', this.checkDependencies, this); + } else { + node.on('click', this.checkDependencies, this); + node.on('blur', this.checkDependencies, this); + node.on('change', this.checkDependencies, this); + } + }, this); + } + this._form.all('reset').each(function(reset){ + this._form.reset(); + reset.on('click', this.checkDependencies, this); + }, this); + + return this.checkDependencies(null); + }, + /** + * Gets all elements in the form by thier name and returns + * a YUI NodeList + * @return Y.NodeList + */ + elementsByName : function(name) { + if (!this._nameCollections[name]) { + var elements = []; + this._form.get('elements').each(function(){ + if (this.getAttribute('name') == name) { + elements.push(this); + } + }) + this._nameCollections[name] = new Y.NodeList(elements); + } + return this._nameCollections[name]; + }, + /** + * Checks the dependencies the form has an makes any changes to the + * form that are required. + * + * Changes are made by functions title _dependency_{dependencytype} + * and more can easily be introduced by defining further functions. + */ + checkDependencies : function(e) { + var tolock = [], + tohide = [], + dependon, condition, value, + lock, hide, checkfunction, result; + for (dependon in dependencies) { + if (this._depElements[dependon].size() == 0) { + continue; + } + for (condition in dependencies[dependon]) { + for (value in dependencies[dependon][condition]) { + lock = false; + hide = false; + checkfunction = '_dependency_'+condition; + if (Y.Lang.isFunction(this[checkfunction])) { + result = this[checkfunction].apply(this, [this._depElements[dependon], value]); + } else { + result = this._dependency_default(this._depElements[dependon], value); + } + lock = result.lock || false; + hide = result.hide || false; + for (var ei in dependencies[dependon][condition][value]) { + var eltolock = dependencies[dependon][condition][value][ei]; + if (hide) { + tohide[eltolock] = true; + } + if (tolock[eltolock] != null) { + tolock[eltolock] = lock || tolock[eltolock]; + } else { + tolock[eltolock] = lock; + } + } + } + } + } + for (var el in tolock) { + this._disableElement(el, tolock[el]); + if (tohide.propertyIsEnumerable(el)) { + this._hideElement(el, tohide[el]); + } + } + return true; + }, + /** + * Disabled all form elements with the given name + */ + _disableElement : function(name, disabled) { + var els = this.elementsByName(name); + els.each(function(){ + if (disabled) { + this.setAttribute('disabled', 'disabled'); + } else { + this.removeAttribute('disabled'); + } + }) + }, + /** + * Hides all elements with the given name. + */ + _hideElement : function(name, hidden) { + var els = this.elementsByName(name); + els.each(function(){ + var e = els.ancestor('.fitem'); + if (e) { + e.setStyles({ + display : (hidden)?'none':'' + }) + } + }); + }, + _dependency_notchecked : function(elements, value) { + var lock = false; + elements.each(function(){ + lock = lock || !this.get('checked'); + }); + return { + lock : lock, + hide : false + } + }, + _dependency_checked : function(elements, value) { + var lock = false; + elements.each(function(){ + lock = lock || this.get('checked'); + }); + return { + lock : lock, + hide : false + } + }, + _dependency_noitemselected : function(elements, value) { + var lock = false; + elements.each(function(){ + lock = lock || this.get('selectedIndex') == -1; + }); + return { + lock : lock, + hide : false + } + }, + _dependency_eq : function(elements, value) { + var lock = false; + elements.each(function(){ + lock = lock || this.get('value') == value; + }); + return { + lock : lock, + hide : false + } + }, + _dependency_hide : function(elements, value) { + return { + lock : false, + hide : true + } + }, + _dependency_default : function(elements, value) { + var lock = false; + elements.each(function(){ + lock = lock || this.get('value') != value; + }); + return { + lock : lock, + hide : false + } + } + } + Y.extend(dependencyManager, Y.Base, dependencyManager.prototype, { + NAME : 'mform-dependency-manager' + }); + + return dependencyManager; + })(); + + return new M.form.dependencyManager(); +} \ No newline at end of file diff --git a/lib/form/yui/dateselector/dateselector.js b/lib/form/yui/dateselector/dateselector.js index 478a6e51d06..77e63f82d77 100644 --- a/lib/form/yui/dateselector/dateselector.js +++ b/lib/form/yui/dateselector/dateselector.js @@ -100,7 +100,7 @@ YUI.add('moodle-form-dateselector', function(Y) { } else if (node.get('name').match(/\[day]/)) { this.dayselect = node; } else { - node.on('focus', this.cancel_any_timeout, this); + node.on('focus', M.form.dateselector.cancel_any_timeout, M.form.dateselector); node.on('blur', this.blur_event, this); return; } diff --git a/lib/formslib.php b/lib/formslib.php index 49cc4de187d..13774938f2f 100644 --- a/lib/formslib.php +++ b/lib/formslib.php @@ -1075,7 +1075,7 @@ EOS; global $PAGE; if (is_string($element)) { $element = $this->_form->getElement($element); -} + } if (is_object($element)) { $element->_generateId(); $elementid = $element->getAttribute('id'); @@ -1091,6 +1091,23 @@ EOS; } } } + + /** + * Returns a JS module definition for the mforms JS + * @return array + */ + public static function get_js_module() { + global $CFG; + return array( + 'name' => 'mform', + 'fullpath' => '/lib/form/form.js', + 'requires' => array('base', 'node'), + 'strings' => array( + array('showadvanced', 'form'), + array('hideadvanced', 'form') + ) + ); + } } /** @@ -1803,22 +1820,14 @@ function validate_' . $this->_formName . '(frm) { } } - /** - * @return string - */ - function getLockOptionEndScript(){ - - $iname = $this->getAttribute('id').'items'; - $js = ''."\n"; - return $js; + return array($this->getAttribute('id'), $result); } /** @@ -2238,16 +2243,20 @@ class MoodleQuickForm_Renderer extends HTML_QuickForm_Renderer_Tableless{ } /** + * @global moodle_page $PAGE * @param object $form Passed by reference */ function finishForm(&$form){ + global $PAGE; if ($form->isFrozen()){ $this->_hiddenHtml = ''; } parent::finishForm($form); - if ((!$form->isFrozen()) && ('' != ($script = $form->getLockOptionEndScript()))) { - // add a lockoptions script - $this->_html = $this->_html . "\n" . $script; + if (!$form->isFrozen()) { + $args = $form->getLockOptionObject(); + if (count($args[1]) > 0) { + $PAGE->requires->js_init_call('M.form.initFormDependencies', $args, false, moodleform::get_js_module()); + } } } /** @@ -2256,17 +2265,10 @@ class MoodleQuickForm_Renderer extends HTML_QuickForm_Renderer_Tableless{ * @param object $header An HTML_QuickForm_header element being visited * @access public * @return void + * @global moodle_page $PAGE */ function renderHeader(&$header) { global $PAGE; - static $advformcount; - - // This ensures that if 2(+) advanced buttons are used - // that all show/hide buttons appear in the correct place - // Because of now using $PAGE->requires->js_function_call - if ($advformcount==null) { - $advformcount = 1; - } $name = $header->getName(); @@ -2282,30 +2284,17 @@ class MoodleQuickForm_Renderer extends HTML_QuickForm_Renderer_Tableless{ if (isset($this->_advancedElements[$name])){ $header_html =str_replace('{advancedimg}', $this->_advancedHTML, $header_html); + $elementName='mform_showadvanced'; + if ($this->_showAdvanced==0){ + $buttonlabel = get_string('showadvanced', 'form'); + } else { + $buttonlabel = get_string('hideadvanced', 'form'); + } + $button = ''; + $PAGE->requires->js_init_call('M.form.initShowAdvanced', array(), false, moodleform::get_js_module()); + $header_html = str_replace('{button}', $button, $header_html); } else { $header_html =str_replace('{advancedimg}', '', $header_html); - } - $elementName='mform_showadvanced'; - if ($this->_showAdvanced==0){ - $buttonlabel = get_string('showadvanced', 'form'); - } else { - $buttonlabel = get_string('hideadvanced', 'form'); - } - - if (isset($this->_advancedElements[$name])){ - $PAGE->requires->yui2_lib('event'); - // this is tricky - the first submit button on form is "clicked" if user presses enter - // we do not want to "submit" using advanced button if javascript active - $button_nojs = ''; - - $buttonlabel = addslashes_js($buttonlabel); - $PAGE->requires->string_for_js('showadvanced', 'form'); - $PAGE->requires->string_for_js('hideadvanced', 'form'); - $PAGE->requires->js_function_call('showAdvancedInit', Array($elementName.(string)$advformcount, $elementName, $buttonlabel)); - - $advformcount++; - $header_html = str_replace('{button}', $button_nojs, $header_html); - } else { $header_html = str_replace('{button}', '', $header_html); } diff --git a/lib/javascript-static.js b/lib/javascript-static.js index 74ed2aa8b7f..c086771f08a 100644 --- a/lib/javascript-static.js +++ b/lib/javascript-static.js @@ -675,223 +675,6 @@ function checknone() { } } -function lockoptions(formid, master, subitems) { - // Subitems is an array of names of sub items. - // Optionally, each item in subitems may have a - // companion hidden item in the form with the - // same name but prefixed by "h". - var form = document.forms[formid]; - - if (eval("form."+master+".checked")) { - for (i=0; i