mirror of
https://github.com/moodle/moodle.git
synced 2025-05-05 07:48:21 +02:00
This is a squash of many small fixes. The list of changes are: Initial set of behat selectors overrides required for new theme. set core behat selectors if not overridden by theme Remove pause Fix for checkboxes not saving properly Custom override for click in the user menu Fix for behat guessing the field type. Behat fix for guessing the correct field type. Fix for missing closeElementBefore mforms mess. Fix for textareas not displaying their contents. Fix availability form element Fix action menu selectors for activities in a course page Fix textareas not retaining their values Fix selector for blocks I had to change the single select template to use an input instead of a button because of a behat bug we have not tracked down yet. Fix for frozen attribute in form elements Use input instead of button while behat is still broken Fix for navigation selector override Behat selector fixes and multi-select fixes for templated select Fix for configure blocks selector + booleans for autocomplete Fixes for course_summary behat tests Allow optgroups in single_select Behat fixes for block_navigation Fixes for url form element and activity chooser selectors blocks_social - fix specific selector for behat Fix export_for_template for selectgroups element. Dont set empty id on the form Fix direct call to behat_navigation in backup context Checkbox empty value fix Fix for assign grading panel popouts file picker overrides to find a file manager in the page Fix chat tests to use element name Make selectors in mod_data behat less specific MDL-55839: recent activity behat test relies on time() SQUASH Honour mform element "class" attribute on the container SQUASH Fixes for behat in mod_forum SQUASH search form template showing value as text next to the button SQUASH undo double selectgroup fix SQUASH - The slightly different structure to action menus breaks course drag and drop SQUASH Wrap loading icon in a span so the selector still works. SQUASH Fix fragile javascript in report_loglive SQUASH Fix tabtree, and selectgroups for mod_feedback Also - do-not enhance the action menu - we are dropping support for this. SQUASH Fix filemanager selector and pix_icon template for mod_forum tests. SQUASH Fixes to breadcrumb and add formid to single select (fixes mod_glossary tests) SQUASH lesson behat fixes. 95% of lesson styles were rubbish so they were disgarded. SQUASH Fix lti behat tests - they were cheating and using a context step from quiz SQUASH Behat fixes for quiz with new theme SQUASH behat fixes for mod_wiki SQUASH Workshop behat fixes SQUASH Frozen form elements cannot use a readonly form element because it screws with the form JS "disabledIf" stuff. SQUASH Fixes for grades behat tests SQUASH behat fixes for groups SQUASH blacklist action menu tests SQUASH Fix for tag form element missing "manage standard tags" link SQUASH Incomplete fixes for course behat tests SQUASH behat fixes for badges SQUASH Tags fixes for behat SQUASH filepicker fixes for behat SQUASH Abort guessing field type when we hit 'html' SQUASH Fix for admin/tool/behat tests SQUASH fix for admin tool behat tests SQUASH fix for availabilty conditions hiding "aria-hidden" elements SQUASH give the blocks more room. We get behat files because nav tree nodes are not in view SQUASH Blocks fixed Fix behaviour of unchecked checkboxes SQUASH Remove incorrect step in user test SQUASH behat fixes for course tests Part of MDL-55071
593 lines
23 KiB
JavaScript
593 lines
23 KiB
JavaScript
/**
|
|
* 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 || {};
|
|
|
|
if (typeof M.form.dependencyManager === 'undefined') {
|
|
var dependencyManager = function() {
|
|
dependencyManager.superclass.constructor.apply(this, arguments);
|
|
};
|
|
Y.extend(dependencyManager, Y.Base, {
|
|
_locks: null,
|
|
_hides: null,
|
|
_dirty: null,
|
|
_nameCollections: null,
|
|
_fileinputs: null,
|
|
|
|
initializer: function() {
|
|
// Setup initial values for complex properties.
|
|
this._locks = {};
|
|
this._hides = {};
|
|
this._dirty = {};
|
|
|
|
// Setup event handlers.
|
|
Y.Object.each(this.get('dependencies'), function(value, i) {
|
|
var elements = this.elementsByName(i);
|
|
elements.each(function(node) {
|
|
var nodeName = node.get('nodeName').toUpperCase();
|
|
if (nodeName == 'INPUT') {
|
|
if (node.getAttribute('type').match(/^(button|submit|radio|checkbox)$/)) {
|
|
node.on('click', this.updateEventDependencies, this);
|
|
} else {
|
|
node.on('blur', this.updateEventDependencies, this);
|
|
}
|
|
node.on('change', this.updateEventDependencies, this);
|
|
} else if (nodeName == 'SELECT') {
|
|
node.on('change', this.updateEventDependencies, this);
|
|
} else {
|
|
node.on('click', this.updateEventDependencies, this);
|
|
node.on('blur', this.updateEventDependencies, this);
|
|
node.on('change', this.updateEventDependencies, this);
|
|
}
|
|
}, this);
|
|
}, this);
|
|
|
|
// Handle the reset button.
|
|
this.get('form').get('elements').each(function(input) {
|
|
if (input.getAttribute('type') == 'reset') {
|
|
input.on('click', function() {
|
|
this.get('form').reset();
|
|
this.updateAllDependencies();
|
|
}, this);
|
|
}
|
|
}, this);
|
|
|
|
this.updateAllDependencies();
|
|
},
|
|
|
|
/**
|
|
* Initializes the mapping from element name to YUI NodeList
|
|
*/
|
|
initElementsByName: function() {
|
|
var names = {};
|
|
|
|
// Collect element names.
|
|
Y.Object.each(this.get('dependencies'), function(conditions, i) {
|
|
names[i] = new Y.NodeList();
|
|
for (var condition in conditions) {
|
|
for (var value in conditions[condition]) {
|
|
for (var ei in conditions[condition][value]) {
|
|
names[conditions[condition][value][ei]] = new Y.NodeList();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Locate elements for each name.
|
|
this.get('form').get('elements').each(function(node) {
|
|
var name = node.getAttribute('name');
|
|
if (({}).hasOwnProperty.call(names, name)) {
|
|
names[name].push(node);
|
|
}
|
|
});
|
|
this._nameCollections = names;
|
|
},
|
|
|
|
/**
|
|
* Gets all elements in the form by their name and returns
|
|
* a YUI NodeList
|
|
*
|
|
* @param {String} name The form element name.
|
|
* @return {Y.NodeList}
|
|
*/
|
|
elementsByName: function(name) {
|
|
if (!this._nameCollections) {
|
|
this.initElementsByName();
|
|
}
|
|
if (!({}).hasOwnProperty.call(this._nameCollections, name)) {
|
|
return new Y.NodeList();
|
|
}
|
|
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.
|
|
*
|
|
* @param {EventFacade | null} e The event, if any.
|
|
* @param {String} dependon The form element name to check dependencies against.
|
|
* @return {Boolean}
|
|
*/
|
|
checkDependencies: function(e, dependon) {
|
|
var dependencies = this.get('dependencies'),
|
|
tohide = {},
|
|
tolock = {},
|
|
condition, value, lock, hide,
|
|
checkfunction, result, elements;
|
|
if (!({}).hasOwnProperty.call(dependencies, dependon)) {
|
|
return true;
|
|
}
|
|
elements = this.elementsByName(dependon);
|
|
for (condition in dependencies[dependon]) {
|
|
for (value in dependencies[dependon][condition]) {
|
|
checkfunction = '_dependency' + condition[0].toUpperCase() + condition.slice(1);
|
|
if (Y.Lang.isFunction(this[checkfunction])) {
|
|
result = this[checkfunction].apply(this, [elements, value, e]);
|
|
} else {
|
|
result = this._dependencyDefault(elements, value, e);
|
|
}
|
|
lock = result.lock || false;
|
|
hide = result.hide || false;
|
|
for (var ei in dependencies[dependon][condition][value]) {
|
|
var eltolock = dependencies[dependon][condition][value][ei];
|
|
if (({}).hasOwnProperty.call(tohide, eltolock)) {
|
|
tohide[eltolock] = tohide[eltolock] || hide;
|
|
} else {
|
|
tohide[eltolock] = hide;
|
|
}
|
|
|
|
if (({}).hasOwnProperty.call(tolock, eltolock)) {
|
|
tolock[eltolock] = tolock[eltolock] || lock;
|
|
} else {
|
|
tolock[eltolock] = lock;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (var el in tolock) {
|
|
var needsupdate = false;
|
|
if (!({}).hasOwnProperty.call(this._locks, el)) {
|
|
this._locks[el] = {};
|
|
}
|
|
if (({}).hasOwnProperty.call(tolock, el) && tolock[el]) {
|
|
if (!({}).hasOwnProperty.call(this._locks[el], dependon) || this._locks[el][dependon]) {
|
|
this._locks[el][dependon] = true;
|
|
needsupdate = true;
|
|
}
|
|
} else if (({}).hasOwnProperty.call(this._locks[el], dependon) && this._locks[el][dependon]) {
|
|
delete this._locks[el][dependon];
|
|
needsupdate = true;
|
|
}
|
|
|
|
if (!({}).hasOwnProperty.call(this._hides, el)) {
|
|
this._hides[el] = {};
|
|
}
|
|
if (({}).hasOwnProperty.call(tohide, el) && tohide[el]) {
|
|
if (!({}).hasOwnProperty.call(this._hides[el], dependon) || this._hides[el][dependon]) {
|
|
this._hides[el][dependon] = true;
|
|
needsupdate = true;
|
|
}
|
|
} else if (({}).hasOwnProperty.call(this._hides[el], dependon) && this._hides[el][dependon]) {
|
|
delete this._hides[el][dependon];
|
|
needsupdate = true;
|
|
}
|
|
|
|
if (needsupdate) {
|
|
this._dirty[el] = true;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
},
|
|
/**
|
|
* Update all dependencies in form
|
|
*/
|
|
updateAllDependencies: function() {
|
|
Y.Object.each(this.get('dependencies'), function(value, name) {
|
|
this.checkDependencies(null, name);
|
|
}, this);
|
|
|
|
this.updateForm();
|
|
},
|
|
/**
|
|
* Update dependencies associated with event
|
|
*
|
|
* @param {Event} e The event.
|
|
*/
|
|
updateEventDependencies: function(e) {
|
|
var el = e.target.getAttribute('name');
|
|
this.checkDependencies(e, el);
|
|
this.updateForm();
|
|
},
|
|
/**
|
|
* Flush pending changes to the form
|
|
*/
|
|
updateForm: function() {
|
|
var el;
|
|
for (el in this._dirty) {
|
|
if (({}).hasOwnProperty.call(this._locks, el)) {
|
|
this._disableElement(el, !Y.Object.isEmpty(this._locks[el]));
|
|
}
|
|
if (({}).hasOwnProperty.call(this._hides, el)) {
|
|
this._hideElement(el, !Y.Object.isEmpty(this._hides[el]));
|
|
}
|
|
}
|
|
|
|
this._dirty = {};
|
|
},
|
|
/**
|
|
* Disables or enables all form elements with the given name
|
|
*
|
|
* @param {String} name The form element name.
|
|
* @param {Boolean} disabled True to disable, false to enable.
|
|
*/
|
|
_disableElement: function(name, disabled) {
|
|
var els = this.elementsByName(name);
|
|
var filepicker = this.isFilePicker(name);
|
|
els.each(function(node) {
|
|
if (disabled) {
|
|
node.setAttribute('disabled', 'disabled');
|
|
} else {
|
|
node.removeAttribute('disabled');
|
|
}
|
|
|
|
// Extra code to disable filepicker or filemanager form elements
|
|
if (filepicker) {
|
|
var fitem = node.ancestor('.fitem');
|
|
if (fitem) {
|
|
if (disabled) {
|
|
fitem.addClass('disabled');
|
|
} else {
|
|
fitem.removeClass('disabled');
|
|
}
|
|
}
|
|
}
|
|
});
|
|
},
|
|
/**
|
|
* Hides or shows all form elements with the given name.
|
|
*
|
|
* @param {String} name The form element name.
|
|
* @param {Boolean} hidden True to hide, false to show.
|
|
*/
|
|
_hideElement: function(name, hidden) {
|
|
var els = this.elementsByName(name);
|
|
els.each(function(node) {
|
|
var e = node.ancestor('.fitem');
|
|
if (e) {
|
|
e.setStyles({
|
|
display: (hidden) ? 'none' : ''
|
|
});
|
|
}
|
|
});
|
|
},
|
|
/**
|
|
* Is the form element inside a filepicker or filemanager?
|
|
*
|
|
* @param {String} el The form element name.
|
|
* @return {Boolean}
|
|
*/
|
|
isFilePicker: function(el) {
|
|
if (!this._fileinputs) {
|
|
var fileinputs = {};
|
|
var els = this.get('form').all('.fitem [data-fieldtype="filepicker"] input,.fitem [data-fieldtype="filemanager"] input');
|
|
els.each(function(node) {
|
|
fileinputs[node.getAttribute('name')] = true;
|
|
});
|
|
this._fileinputs = fileinputs;
|
|
}
|
|
|
|
if (({}).hasOwnProperty.call(this._fileinputs, el)) {
|
|
return this._fileinputs[el] || false;
|
|
}
|
|
|
|
return false;
|
|
},
|
|
_dependencyNotchecked: function(elements, value) {
|
|
var lock = false;
|
|
elements.each(function() {
|
|
if (this.getAttribute('type').toLowerCase() == 'hidden' &&
|
|
!this.siblings('input[type=checkbox][name="' + this.get('name') + '"]').isEmpty()) {
|
|
// This is the hidden input that is part of an advcheckbox.
|
|
return;
|
|
}
|
|
if (this.getAttribute('type').toLowerCase() == 'radio' && this.get('value') != value) {
|
|
return;
|
|
}
|
|
lock = lock || !Y.Node.getDOMNode(this).checked;
|
|
});
|
|
return {
|
|
lock: lock,
|
|
hide: false
|
|
};
|
|
},
|
|
_dependencyChecked: function(elements, value) {
|
|
var lock = false;
|
|
elements.each(function() {
|
|
if (this.getAttribute('type').toLowerCase() == 'hidden' &&
|
|
!this.siblings('input[type=checkbox][name="' + this.get('name') + '"]').isEmpty()) {
|
|
// This is the hidden input that is part of an advcheckbox.
|
|
return;
|
|
}
|
|
if (this.getAttribute('type').toLowerCase() == 'radio' && this.get('value') != value) {
|
|
return;
|
|
}
|
|
lock = lock || Y.Node.getDOMNode(this).checked;
|
|
});
|
|
return {
|
|
lock: lock,
|
|
hide: false
|
|
};
|
|
},
|
|
_dependencyNoitemselected: function(elements, value) {
|
|
var lock = false;
|
|
elements.each(function() {
|
|
lock = lock || this.get('selectedIndex') == -1;
|
|
});
|
|
return {
|
|
lock: lock,
|
|
hide: false
|
|
};
|
|
},
|
|
_dependencyEq: function(elements, value) {
|
|
var lock = false;
|
|
var hiddenVal = false;
|
|
var options, v, selected, values;
|
|
elements.each(function() {
|
|
if (this.getAttribute('type').toLowerCase() == 'radio' && !Y.Node.getDOMNode(this).checked) {
|
|
return;
|
|
} else if (this.getAttribute('type').toLowerCase() == 'hidden' &&
|
|
!this.siblings('input[type=checkbox][name="' + this.get('name') + '"]').isEmpty()) {
|
|
// This is the hidden input that is part of an advcheckbox.
|
|
hiddenVal = (this.get('value') == value);
|
|
return;
|
|
} else if (this.getAttribute('type').toLowerCase() == 'checkbox' && !Y.Node.getDOMNode(this).checked) {
|
|
lock = lock || hiddenVal;
|
|
return;
|
|
}
|
|
if (this.getAttribute('class').toLowerCase() == 'filepickerhidden') {
|
|
// Check for filepicker status.
|
|
var elementname = this.getAttribute('name');
|
|
if (elementname && M.form_filepicker.instances[elementname].fileadded) {
|
|
lock = false;
|
|
} else {
|
|
lock = true;
|
|
}
|
|
} else if (this.get('nodeName').toUpperCase() === 'SELECT' && this.get('multiple') === true) {
|
|
// Multiple selects can have one or more value assigned. A pipe (|) is used as a value separator
|
|
// when multiple values have to be selected at the same time.
|
|
values = value.split('|');
|
|
selected = [];
|
|
options = this.get('options');
|
|
options.each(function() {
|
|
if (this.get('selected')) {
|
|
selected[selected.length] = this.get('value');
|
|
}
|
|
});
|
|
if (selected.length > 0 && selected.length === values.length) {
|
|
for (var i in selected) {
|
|
v = selected[i];
|
|
if (values.indexOf(v) > -1) {
|
|
lock = true;
|
|
} else {
|
|
lock = false;
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
lock = false;
|
|
}
|
|
} else {
|
|
lock = lock || this.get('value') == value;
|
|
}
|
|
});
|
|
return {
|
|
lock: lock,
|
|
hide: false
|
|
};
|
|
},
|
|
/**
|
|
* Lock the given field if the field value is in the given set of values.
|
|
*
|
|
* @param {Array} elements
|
|
* @param {String} values Single value or pipe (|) separated values when multiple
|
|
* @returns {{lock: boolean, hide: boolean}}
|
|
* @private
|
|
*/
|
|
_dependencyIn: function(elements, values) {
|
|
// A pipe (|) is used as a value separator
|
|
// when multiple values have to be passed on at the same time.
|
|
values = values.split('|');
|
|
var lock = false;
|
|
var hiddenVal = false;
|
|
var options, v, selected, value;
|
|
elements.each(function() {
|
|
if (this.getAttribute('type').toLowerCase() == 'radio' && !Y.Node.getDOMNode(this).checked) {
|
|
return;
|
|
} else if (this.getAttribute('type').toLowerCase() == 'hidden' &&
|
|
!this.siblings('input[type=checkbox][name="' + this.get('name') + '"]').isEmpty()) {
|
|
// This is the hidden input that is part of an advcheckbox.
|
|
hiddenVal = (values.indexOf(this.get('value')) > -1);
|
|
return;
|
|
} else if (this.getAttribute('type').toLowerCase() == 'checkbox' && !Y.Node.getDOMNode(this).checked) {
|
|
lock = lock || hiddenVal;
|
|
return;
|
|
}
|
|
if (this.getAttribute('class').toLowerCase() == 'filepickerhidden') {
|
|
// Check for filepicker status.
|
|
var elementname = this.getAttribute('name');
|
|
if (elementname && M.form_filepicker.instances[elementname].fileadded) {
|
|
lock = false;
|
|
} else {
|
|
lock = true;
|
|
}
|
|
} else if (this.get('nodeName').toUpperCase() === 'SELECT' && this.get('multiple') === true) {
|
|
// Multiple selects can have one or more value assigned.
|
|
selected = [];
|
|
options = this.get('options');
|
|
options.each(function() {
|
|
if (this.get('selected')) {
|
|
selected[selected.length] = this.get('value');
|
|
}
|
|
});
|
|
if (selected.length > 0 && selected.length === values.length) {
|
|
for (var i in selected) {
|
|
v = selected[i];
|
|
if (values.indexOf(v) > -1) {
|
|
lock = true;
|
|
} else {
|
|
lock = false;
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
lock = false;
|
|
}
|
|
} else {
|
|
value = this.get('value');
|
|
lock = lock || (values.indexOf(value) > -1);
|
|
}
|
|
});
|
|
return {
|
|
lock: lock,
|
|
hide: false
|
|
};
|
|
},
|
|
_dependencyHide: function(elements, value) {
|
|
return {
|
|
lock: false,
|
|
hide: true
|
|
};
|
|
},
|
|
_dependencyDefault: function(elements, value, ev) {
|
|
var lock = false,
|
|
hiddenVal = false,
|
|
values
|
|
;
|
|
elements.each(function() {
|
|
var selected;
|
|
if (this.getAttribute('type').toLowerCase() == 'radio' && !Y.Node.getDOMNode(this).checked) {
|
|
return;
|
|
} else if (this.getAttribute('type').toLowerCase() == 'hidden' &&
|
|
!this.siblings('input[type=checkbox][name="' + this.get('name') + '"]').isEmpty()) {
|
|
// This is the hidden input that is part of an advcheckbox.
|
|
hiddenVal = (this.get('value') != value);
|
|
return;
|
|
} else if (this.getAttribute('type').toLowerCase() == 'checkbox' && !Y.Node.getDOMNode(this).checked) {
|
|
lock = lock || hiddenVal;
|
|
return;
|
|
}
|
|
// Check for filepicker status.
|
|
if (this.getAttribute('class').toLowerCase() == 'filepickerhidden') {
|
|
var elementname = this.getAttribute('name');
|
|
if (elementname && M.form_filepicker.instances[elementname].fileadded) {
|
|
lock = true;
|
|
} else {
|
|
lock = false;
|
|
}
|
|
} else if (this.get('nodeName').toUpperCase() === 'SELECT' && this.get('multiple') === true) {
|
|
// Multiple selects can have one or more value assigned. A pipe (|) is used as a value separator
|
|
// when multiple values have to be selected at the same time.
|
|
values = value.split('|');
|
|
this.get('options').each(function() {
|
|
if (this.get('selected')) {
|
|
selected[selected.length] = this.get('value');
|
|
}
|
|
});
|
|
if (selected.length > 0 && selected.length === values.length) {
|
|
for (var i in selected) {
|
|
if (values.indexOf(selected[i]) > -1) {
|
|
lock = false;
|
|
} else {
|
|
lock = true;
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
lock = true;
|
|
}
|
|
} else {
|
|
lock = lock || this.get('value') != value;
|
|
}
|
|
});
|
|
return {
|
|
lock: lock,
|
|
hide: false
|
|
};
|
|
}
|
|
}, {
|
|
NAME: 'mform-dependency-manager',
|
|
ATTRS: {
|
|
form: {
|
|
setter: function(value) {
|
|
return Y.one('#' + value);
|
|
},
|
|
value: null
|
|
},
|
|
|
|
dependencies: {
|
|
value: {}
|
|
}
|
|
}
|
|
});
|
|
|
|
M.form.dependencyManager = dependencyManager;
|
|
}
|
|
|
|
/**
|
|
* Stores a list of the dependencyManager for each form on the page.
|
|
*/
|
|
M.form.dependencyManagers = {};
|
|
|
|
/**
|
|
* Initialises a manager for a forms dependencies.
|
|
* This should happen once per form.
|
|
*
|
|
* @param {YUI} Y YUI3 instance
|
|
* @param {String} formid ID of the form
|
|
* @param {Array} dependencies array
|
|
* @return {M.form.dependencyManager}
|
|
*/
|
|
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));
|
|
}
|
|
};
|
|
|
|
M.form.dependencyManagers[formid] = new M.form.dependencyManager({form: formid, dependencies: dependencies});
|
|
return M.form.dependencyManagers[formid];
|
|
};
|
|
|
|
/**
|
|
* Update the state of a form. You need to call this after, for example, changing
|
|
* the state of some of the form input elements in your own code, in order that
|
|
* things like the disableIf state of elements can be updated.
|
|
*
|
|
* @param {String} formid ID of the form
|
|
*/
|
|
M.form.updateFormState = function(formid) {
|
|
if (formid in M.form.dependencyManagers) {
|
|
M.form.dependencyManagers[formid].updateAllDependencies();
|
|
}
|
|
};
|