X-Git-Url: http://www.aleph1.co.uk/gitweb/?p=yaffs-website;a=blobdiff_plain;f=web%2Fcore%2Fmisc%2Fstates.js;fp=web%2Fcore%2Fmisc%2Fstates.js;h=61bbf46c612eb9470d9d7fdb89429d8d6c84ffd0;hp=24374b625f7a192010b73fdacf4b9d7403d45592;hb=9917807b03b64faf00f6a1f29dcb6eafc454efa5;hpb=aea91e65e895364e460983b890e295aa5d5540a5 diff --git a/web/core/misc/states.js b/web/core/misc/states.js index 24374b625..61bbf46c6 100644 --- a/web/core/misc/states.js +++ b/web/core/misc/states.js @@ -1,41 +1,20 @@ /** - * @file - * Drupal's states library. - */ +* DO NOT EDIT THIS FILE. +* See the following change record for more information, +* https://www.drupal.org/node/2815083 +* @preserve +**/ (function ($, Drupal) { - - 'use strict'; - - /** - * The base States namespace. - * - * Having the local states variable allows us to use the States namespace - * without having to always declare "Drupal.states". - * - * @namespace Drupal.states - */ var states = Drupal.states = { - - /** - * An array of functions that should be postponed. - */ postponed: [] }; - /** - * Attaches the states. - * - * @type {Drupal~behavior} - * - * @prop {Drupal~behaviorAttach} attach - * Attaches states behaviors. - */ Drupal.behaviors.states = { - attach: function (context, settings) { + attach: function attach(context, settings) { var $states = $(context).find('[data-drupal-states]'); - var config; - var state; + var config = void 0; + var state = void 0; var il = $states.length; for (var i = 0; i < il; i++) { config = JSON.parse($states[i].getAttribute('data-drupal-states')); @@ -50,31 +29,14 @@ } } - // Execute all postponed functions now. while (states.postponed.length) { - (states.postponed.shift())(); + states.postponed.shift()(); } } }; - /** - * Object representing an element that depends on other elements. - * - * @constructor Drupal.states.Dependent - * - * @param {object} args - * Object with the following keys (all of which are required) - * @param {jQuery} args.element - * A jQuery object of the dependent element - * @param {Drupal.states.State} args.state - * A State object describing the state that is dependent - * @param {object} args.constraints - * An object with dependency specifications. Lists all elements that this - * element depends on. It can be nested and can contain - * arbitrary AND and OR clauses. - */ states.Dependent = function (args) { - $.extend(this, {values: {}, oldValue: null}, args); + $.extend(this, { values: {}, oldValue: null }, args); this.dependees = this.getDependees(); for (var selector in this.dependees) { @@ -84,295 +46,137 @@ } }; - /** - * Comparison functions for comparing the value of an element with the - * specification from the dependency settings. If the object type can't be - * found in this list, the === operator is used by default. - * - * @name Drupal.states.Dependent.comparisons - * - * @prop {function} RegExp - * @prop {function} Function - * @prop {function} Number - */ states.Dependent.comparisons = { - RegExp: function (reference, value) { + RegExp: function RegExp(reference, value) { return reference.test(value); }, - Function: function (reference, value) { - // The "reference" variable is a comparison function. + Function: function Function(reference, value) { return reference(value); }, - Number: function (reference, value) { - // If "reference" is a number and "value" is a string, then cast - // reference as a string before applying the strict comparison in - // compare(). - // Otherwise numeric keys in the form's #states array fail to match - // string values returned from jQuery's val(). - return (typeof value === 'string') ? compare(reference.toString(), value) : compare(reference, value); + Number: function Number(reference, value) { + return typeof value === 'string' ? _compare2(reference.toString(), value) : _compare2(reference, value); } }; states.Dependent.prototype = { - - /** - * Initializes one of the elements this dependent depends on. - * - * @memberof Drupal.states.Dependent# - * - * @param {string} selector - * The CSS selector describing the dependee. - * @param {object} dependeeStates - * The list of states that have to be monitored for tracking the - * dependee's compliance status. - */ - initializeDependee: function (selector, dependeeStates) { - var state; + initializeDependee: function initializeDependee(selector, dependeeStates) { + var state = void 0; var self = this; function stateEventHandler(e) { self.update(e.data.selector, e.data.state, e.value); } - // Cache for the states of this dependee. this.values[selector] = {}; for (var i in dependeeStates) { if (dependeeStates.hasOwnProperty(i)) { state = dependeeStates[i]; - // Make sure we're not initializing this selector/state combination - // twice. + if ($.inArray(state, dependeeStates) === -1) { continue; } state = states.State.sanitize(state); - // Initialize the value of this state. this.values[selector][state.name] = null; - // Monitor state changes of the specified state for this dependee. - $(selector).on('state:' + state, {selector: selector, state: state}, stateEventHandler); + $(selector).on('state:' + state, { selector: selector, state: state }, stateEventHandler); - // Make sure the event we just bound ourselves to is actually fired. - new states.Trigger({selector: selector, state: state}); + new states.Trigger({ selector: selector, state: state }); } } }, - - /** - * Compares a value with a reference value. - * - * @memberof Drupal.states.Dependent# - * - * @param {object} reference - * The value used for reference. - * @param {string} selector - * CSS selector describing the dependee. - * @param {Drupal.states.State} state - * A State object describing the dependee's updated state. - * - * @return {bool} - * true or false. - */ - compare: function (reference, selector, state) { + compare: function compare(reference, selector, state) { var value = this.values[selector][state.name]; if (reference.constructor.name in states.Dependent.comparisons) { - // Use a custom compare function for certain reference value types. return states.Dependent.comparisons[reference.constructor.name](reference, value); } - else { - // Do a plain comparison otherwise. - return compare(reference, value); - } - }, - /** - * Update the value of a dependee's state. - * - * @memberof Drupal.states.Dependent# - * - * @param {string} selector - * CSS selector describing the dependee. - * @param {Drupal.states.state} state - * A State object describing the dependee's updated state. - * @param {string} value - * The new value for the dependee's updated state. - */ - update: function (selector, state, value) { - // Only act when the 'new' value is actually new. + return _compare2(reference, value); + }, + update: function update(selector, state, value) { if (value !== this.values[selector][state.name]) { this.values[selector][state.name] = value; this.reevaluate(); } }, - - /** - * Triggers change events in case a state changed. - * - * @memberof Drupal.states.Dependent# - */ - reevaluate: function () { - // Check whether any constraint for this dependent state is satisfied. + reevaluate: function reevaluate() { var value = this.verifyConstraints(this.constraints); - // Only invoke a state change event when the value actually changed. if (value !== this.oldValue) { - // Store the new value so that we can compare later whether the value - // actually changed. this.oldValue = value; - // Normalize the value to match the normalized state name. value = invert(value, this.state.invert); - // By adding "trigger: true", we ensure that state changes don't go into - // infinite loops. - this.element.trigger({type: 'state:' + this.state, value: value, trigger: true}); + this.element.trigger({ type: 'state:' + this.state, value: value, trigger: true }); } }, - - /** - * Evaluates child constraints to determine if a constraint is satisfied. - * - * @memberof Drupal.states.Dependent# - * - * @param {object|Array} constraints - * A constraint object or an array of constraints. - * @param {string} selector - * The selector for these constraints. If undefined, there isn't yet a - * selector that these constraints apply to. In that case, the keys of the - * object are interpreted as the selector if encountered. - * - * @return {bool} - * true or false, depending on whether these constraints are satisfied. - */ - verifyConstraints: function (constraints, selector) { - var result; + verifyConstraints: function verifyConstraints(constraints, selector) { + var result = void 0; if ($.isArray(constraints)) { - // This constraint is an array (OR or XOR). var hasXor = $.inArray('xor', constraints) === -1; var len = constraints.length; for (var i = 0; i < len; i++) { if (constraints[i] !== 'xor') { var constraint = this.checkConstraints(constraints[i], selector, i); - // Return if this is OR and we have a satisfied constraint or if - // this is XOR and we have a second satisfied constraint. + if (constraint && (hasXor || result)) { return hasXor; } result = result || constraint; } } - } - // Make sure we don't try to iterate over things other than objects. This - // shouldn't normally occur, but in case the condition definition is - // bogus, we don't want to end up with an infinite loop. - else if ($.isPlainObject(constraints)) { - // This constraint is an object (AND). - for (var n in constraints) { - if (constraints.hasOwnProperty(n)) { - result = ternary(result, this.checkConstraints(constraints[n], selector, n)); - // False and anything else will evaluate to false, so return when - // any false condition is found. - if (result === false) { return false; } + } else if ($.isPlainObject(constraints)) { + for (var n in constraints) { + if (constraints.hasOwnProperty(n)) { + result = ternary(result, this.checkConstraints(constraints[n], selector, n)); + + if (result === false) { + return false; + } + } } } - } return result; }, - - /** - * Checks whether the value matches the requirements for this constraint. - * - * @memberof Drupal.states.Dependent# - * - * @param {string|Array|object} value - * Either the value of a state or an array/object of constraints. In the - * latter case, resolving the constraint continues. - * @param {string} [selector] - * The selector for this constraint. If undefined, there isn't yet a - * selector that this constraint applies to. In that case, the state key - * is propagates to a selector and resolving continues. - * @param {Drupal.states.State} [state] - * The state to check for this constraint. If undefined, resolving - * continues. If both selector and state aren't undefined and valid - * non-numeric strings, a lookup for the actual value of that selector's - * state is performed. This parameter is not a State object but a pristine - * state string. - * - * @return {bool} - * true or false, depending on whether this constraint is satisfied. - */ - checkConstraints: function (value, selector, state) { - // Normalize the last parameter. If it's non-numeric, we treat it either - // as a selector (in case there isn't one yet) or as a trigger/state. - if (typeof state !== 'string' || (/[0-9]/).test(state[0])) { + checkConstraints: function checkConstraints(value, selector, state) { + if (typeof state !== 'string' || /[0-9]/.test(state[0])) { state = null; - } - else if (typeof selector === 'undefined') { - // Propagate the state to the selector when there isn't one yet. + } else if (typeof selector === 'undefined') { selector = state; state = null; } if (state !== null) { - // Constraints is the actual constraints of an element to check for. state = states.State.sanitize(state); return invert(this.compare(value, selector, state), state.invert); } - else { - // Resolve this constraint as an AND/OR operator. - return this.verifyConstraints(value, selector); - } - }, - /** - * Gathers information about all required triggers. - * - * @memberof Drupal.states.Dependent# - * - * @return {object} - * An object describing the required triggers. - */ - getDependees: function () { + return this.verifyConstraints(value, selector); + }, + getDependees: function getDependees() { var cache = {}; - // Swivel the lookup function so that we can record all available - // selector- state combinations for initialization. + var _compare = this.compare; this.compare = function (reference, selector, state) { (cache[selector] || (cache[selector] = [])).push(state.name); - // Return nothing (=== undefined) so that the constraint loops are not - // broken. }; - // This call doesn't actually verify anything but uses the resolving - // mechanism to go through the constraints array, trying to look up each - // value. Since we swivelled the compare function, this comparison returns - // undefined and lookup continues until the very end. Instead of lookup up - // the value, we record that combination of selector and state so that we - // can initialize all triggers. this.verifyConstraints(this.constraints); - // Restore the original function. + this.compare = _compare; return cache; } }; - /** - * @constructor Drupal.states.Trigger - * - * @param {object} args - * Trigger arguments. - */ states.Trigger = function (args) { $.extend(this, args); if (this.state in states.Trigger.states) { this.element = $(this.selector); - // Only call the trigger initializer when it wasn't yet attached to this - // element. Otherwise we'd end up with duplicate events. if (!this.element.data('trigger:' + this.state)) { this.initialize(); } @@ -380,18 +184,12 @@ }; states.Trigger.prototype = { - - /** - * @memberof Drupal.states.Trigger# - */ - initialize: function () { + initialize: function initialize() { var trigger = states.Trigger.states[this.state]; if (typeof trigger === 'function') { - // We have a custom trigger initialization function. trigger.call(window, this.element); - } - else { + } else { for (var event in trigger) { if (trigger.hasOwnProperty(event)) { this.defaultTrigger(event, trigger[event]); @@ -399,93 +197,54 @@ } } - // Mark this trigger as initialized for this element. this.element.data('trigger:' + this.state, true); }, - - /** - * @memberof Drupal.states.Trigger# - * - * @param {jQuery.Event} event - * The event triggered. - * @param {function} valueFn - * The function to call. - */ - defaultTrigger: function (event, valueFn) { + defaultTrigger: function defaultTrigger(event, valueFn) { var oldValue = valueFn.call(this.element); - // Attach the event callback. this.element.on(event, $.proxy(function (e) { var value = valueFn.call(this.element, e); - // Only trigger the event if the value has actually changed. + if (oldValue !== value) { - this.element.trigger({type: 'state:' + this.state, value: value, oldValue: oldValue}); + this.element.trigger({ type: 'state:' + this.state, value: value, oldValue: oldValue }); oldValue = value; } }, this)); states.postponed.push($.proxy(function () { - // Trigger the event once for initialization purposes. - this.element.trigger({type: 'state:' + this.state, value: oldValue, oldValue: null}); + this.element.trigger({ type: 'state:' + this.state, value: oldValue, oldValue: null }); }, this)); } }; - /** - * This list of states contains functions that are used to monitor the state - * of an element. Whenever an element depends on the state of another element, - * one of these trigger functions is added to the dependee so that the - * dependent element can be updated. - * - * @name Drupal.states.Trigger.states - * - * @prop empty - * @prop checked - * @prop value - * @prop collapsed - */ states.Trigger.states = { - // 'empty' describes the state to be monitored. empty: { - // 'keyup' is the (native DOM) event that we watch for. - keyup: function () { - // The function associated with that trigger returns the new value for - // the state. + keyup: function keyup() { return this.val() === ''; } }, checked: { - change: function () { - // prop() and attr() only takes the first element into account. To - // support selectors matching multiple checkboxes, iterate over all and - // return whether any is checked. + change: function change() { var checked = false; this.each(function () { - // Use prop() here as we want a boolean of the checkbox state. - // @see http://api.jquery.com/prop/ checked = $(this).prop('checked'); - // Break the each() loop if this is checked. + return !checked; }); return checked; } }, - // For radio buttons, only return the value if the radio button is selected. value: { - keyup: function () { - // Radio buttons share the same :input[name="key"] selector. + keyup: function keyup() { if (this.length > 1) { - // Initial checked value of radios is undefined, so we return false. return this.filter(':checked').val() || false; } return this.val(); }, - change: function () { - // Radio buttons share the same :input[name="key"] selector. + change: function change() { if (this.length > 1) { - // Initial checked value of radios is undefined, so we return false. return this.filter(':checked').val() || false; } return this.val(); @@ -493,72 +252,38 @@ }, collapsed: { - collapsed: function (e) { - return (typeof e !== 'undefined' && 'value' in e) ? e.value : !this.is('[open]'); + collapsed: function collapsed(e) { + return typeof e !== 'undefined' && 'value' in e ? e.value : !this.is('[open]'); } } }; - /** - * A state object is used for describing the state and performing aliasing. - * - * @constructor Drupal.states.State - * - * @param {string} state - * The name of the state. - */ states.State = function (state) { - - /** - * Original unresolved name. - */ this.pristine = this.name = state; - // Normalize the state name. var process = true; do { - // Iteratively remove exclamation marks and invert the value. while (this.name.charAt(0) === '!') { this.name = this.name.substring(1); this.invert = !this.invert; } - // Replace the state with its normalized name. if (this.name in states.State.aliases) { this.name = states.State.aliases[this.name]; - } - else { + } else { process = false; } } while (process); }; - /** - * Creates a new State object by sanitizing the passed value. - * - * @name Drupal.states.State.sanitize - * - * @param {string|Drupal.states.State} state - * A state object or the name of a state. - * - * @return {Drupal.states.state} - * A state object. - */ states.State.sanitize = function (state) { if (state instanceof states.State) { return state; } - else { - return new states.State(state); - } + + return new states.State(state); }; - /** - * This list of aliases is used to normalize states and associates negated - * names with their respective inverse state. - * - * @name Drupal.states.State.aliases - */ states.State.aliases = { enabled: '!disabled', invisible: '!visible', @@ -575,44 +300,17 @@ }; states.State.prototype = { - - /** - * @memberof Drupal.states.State# - */ invert: false, - /** - * Ensures that just using the state object returns the name. - * - * @memberof Drupal.states.State# - * - * @return {string} - * The name of the state. - */ - toString: function () { + toString: function toString() { return this.name; } }; - /** - * Global state change handlers. These are bound to "document" to cover all - * elements whose state changes. Events sent to elements within the page - * bubble up to these handlers. We use this system so that themes and modules - * can override these state change handlers for particular parts of a page. - */ - var $document = $(document); $document.on('state:disabled', function (e) { - // Only act when this change was triggered by a dependency and not by the - // element monitoring itself. if (e.trigger) { - $(e.target) - .prop('disabled', e.value) - .closest('.js-form-item, .js-form-submit, .js-form-wrapper').toggleClass('form-disabled', e.value) - .find('select, input, textarea').prop('disabled', e.value); - - // Note: WebKit nightlies don't reflect that change correctly. - // See https://bugs.webkit.org/show_bug.cgi?id=23789 + $(e.target).prop('disabled', e.value).closest('.js-form-item, .js-form-submit, .js-form-wrapper').toggleClass('form-disabled', e.value).find('select, input, textarea').prop('disabled', e.value); } }); @@ -620,13 +318,12 @@ if (e.trigger) { if (e.value) { var label = 'label' + (e.target.id ? '[for=' + e.target.id + ']' : ''); - var $label = $(e.target).attr({'required': 'required', 'aria-required': 'aria-required'}).closest('.js-form-item, .js-form-wrapper').find(label); - // Avoids duplicate required markers on initialization. + var $label = $(e.target).attr({ required: 'required', 'aria-required': 'aria-required' }).closest('.js-form-item, .js-form-wrapper').find(label); + if (!$label.hasClass('js-form-required').length) { $label.addClass('js-form-required form-required'); } - } - else { + } else { $(e.target).removeAttr('required aria-required').closest('.js-form-item, .js-form-wrapper').find('label.js-form-required').removeClass('js-form-required form-required'); } } @@ -652,73 +349,25 @@ } }); - /** - * These are helper functions implementing addition "operators" and don't - * implement any logic that is particular to states. - */ - - /** - * Bitwise AND with a third undefined state. - * - * @function Drupal.states~ternary - * - * @param {*} a - * Value a. - * @param {*} b - * Value b - * - * @return {bool} - * The result. - */ function ternary(a, b) { if (typeof a === 'undefined') { return b; - } - else if (typeof b === 'undefined') { + } else if (typeof b === 'undefined') { return a; } - else { - return a && b; - } + + return a && b; } - /** - * Inverts a (if it's not undefined) when invertState is true. - * - * @function Drupal.states~invert - * - * @param {*} a - * The value to maybe invert. - * @param {bool} invertState - * Whether to invert state or not. - * - * @return {bool} - * The result. - */ function invert(a, invertState) { - return (invertState && typeof a !== 'undefined') ? !a : a; + return invertState && typeof a !== 'undefined' ? !a : a; } - /** - * Compares two values while ignoring undefined values. - * - * @function Drupal.states~compare - * - * @param {*} a - * Value a. - * @param {*} b - * Value b. - * - * @return {bool} - * The comparison result. - */ - function compare(a, b) { + function _compare2(a, b) { if (a === b) { return typeof a === 'undefined' ? a : true; } - else { - return typeof a === 'undefined' || typeof b === 'undefined'; - } - } -})(jQuery, Drupal); + return typeof a === 'undefined' || typeof b === 'undefined'; + } +})(jQuery, Drupal); \ No newline at end of file