danielebarchiesi@0: (function ($) { danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * The base States namespace. danielebarchiesi@0: * danielebarchiesi@0: * Having the local states variable allows us to use the States namespace danielebarchiesi@0: * without having to always declare "Drupal.states". danielebarchiesi@0: */ danielebarchiesi@0: var states = Drupal.states = { danielebarchiesi@0: // An array of functions that should be postponed. danielebarchiesi@0: postponed: [] danielebarchiesi@0: }; danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * Attaches the states. danielebarchiesi@0: */ danielebarchiesi@0: Drupal.behaviors.states = { danielebarchiesi@0: attach: function (context, settings) { danielebarchiesi@0: var $context = $(context); danielebarchiesi@0: for (var selector in settings.states) { danielebarchiesi@0: for (var state in settings.states[selector]) { danielebarchiesi@0: new states.Dependent({ danielebarchiesi@0: element: $context.find(selector), danielebarchiesi@0: state: states.State.sanitize(state), danielebarchiesi@0: constraints: settings.states[selector][state] danielebarchiesi@0: }); danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: // Execute all postponed functions now. danielebarchiesi@0: while (states.postponed.length) { danielebarchiesi@0: (states.postponed.shift())(); danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: }; danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * Object representing an element that depends on other elements. danielebarchiesi@0: * danielebarchiesi@0: * @param args danielebarchiesi@0: * Object with the following keys (all of which are required): danielebarchiesi@0: * - element: A jQuery object of the dependent element danielebarchiesi@0: * - state: A State object describing the state that is dependent danielebarchiesi@0: * - constraints: An object with dependency specifications. Lists all elements danielebarchiesi@0: * that this element depends on. It can be nested and can contain arbitrary danielebarchiesi@0: * AND and OR clauses. danielebarchiesi@0: */ danielebarchiesi@0: states.Dependent = function (args) { danielebarchiesi@0: $.extend(this, { values: {}, oldValue: null }, args); danielebarchiesi@0: danielebarchiesi@0: this.dependees = this.getDependees(); danielebarchiesi@0: for (var selector in this.dependees) { danielebarchiesi@0: this.initializeDependee(selector, this.dependees[selector]); danielebarchiesi@0: } danielebarchiesi@0: }; danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * Comparison functions for comparing the value of an element with the danielebarchiesi@0: * specification from the dependency settings. If the object type can't be danielebarchiesi@0: * found in this list, the === operator is used by default. danielebarchiesi@0: */ danielebarchiesi@0: states.Dependent.comparisons = { danielebarchiesi@0: 'RegExp': function (reference, value) { danielebarchiesi@0: return reference.test(value); danielebarchiesi@0: }, danielebarchiesi@0: 'Function': function (reference, value) { danielebarchiesi@0: // The "reference" variable is a comparison function. danielebarchiesi@0: return reference(value); danielebarchiesi@0: }, danielebarchiesi@0: 'Number': function (reference, value) { danielebarchiesi@0: // If "reference" is a number and "value" is a string, then cast reference danielebarchiesi@0: // as a string before applying the strict comparison in compare(). Otherwise danielebarchiesi@0: // numeric keys in the form's #states array fail to match string values danielebarchiesi@0: // returned from jQuery's val(). danielebarchiesi@0: return (typeof value === 'string') ? compare(reference.toString(), value) : compare(reference, value); danielebarchiesi@0: } danielebarchiesi@0: }; danielebarchiesi@0: danielebarchiesi@0: states.Dependent.prototype = { danielebarchiesi@0: /** danielebarchiesi@0: * Initializes one of the elements this dependent depends on. danielebarchiesi@0: * danielebarchiesi@0: * @param selector danielebarchiesi@0: * The CSS selector describing the dependee. danielebarchiesi@0: * @param dependeeStates danielebarchiesi@0: * The list of states that have to be monitored for tracking the danielebarchiesi@0: * dependee's compliance status. danielebarchiesi@0: */ danielebarchiesi@0: initializeDependee: function (selector, dependeeStates) { danielebarchiesi@0: var state; danielebarchiesi@0: danielebarchiesi@0: // Cache for the states of this dependee. danielebarchiesi@0: this.values[selector] = {}; danielebarchiesi@0: danielebarchiesi@0: for (var i in dependeeStates) { danielebarchiesi@0: if (dependeeStates.hasOwnProperty(i)) { danielebarchiesi@0: state = dependeeStates[i]; danielebarchiesi@0: // Make sure we're not initializing this selector/state combination twice. danielebarchiesi@0: if ($.inArray(state, dependeeStates) === -1) { danielebarchiesi@0: continue; danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: state = states.State.sanitize(state); danielebarchiesi@0: danielebarchiesi@0: // Initialize the value of this state. danielebarchiesi@0: this.values[selector][state.name] = null; danielebarchiesi@0: danielebarchiesi@0: // Monitor state changes of the specified state for this dependee. danielebarchiesi@0: $(selector).bind('state:' + state, $.proxy(function (e) { danielebarchiesi@0: this.update(selector, state, e.value); danielebarchiesi@0: }, this)); danielebarchiesi@0: danielebarchiesi@0: // Make sure the event we just bound ourselves to is actually fired. danielebarchiesi@0: new states.Trigger({ selector: selector, state: state }); danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: }, danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * Compares a value with a reference value. danielebarchiesi@0: * danielebarchiesi@0: * @param reference danielebarchiesi@0: * The value used for reference. danielebarchiesi@0: * @param selector danielebarchiesi@0: * CSS selector describing the dependee. danielebarchiesi@0: * @param state danielebarchiesi@0: * A State object describing the dependee's updated state. danielebarchiesi@0: * danielebarchiesi@0: * @return danielebarchiesi@0: * true or false. danielebarchiesi@0: */ danielebarchiesi@0: compare: function (reference, selector, state) { danielebarchiesi@0: var value = this.values[selector][state.name]; danielebarchiesi@0: if (reference.constructor.name in states.Dependent.comparisons) { danielebarchiesi@0: // Use a custom compare function for certain reference value types. danielebarchiesi@0: return states.Dependent.comparisons[reference.constructor.name](reference, value); danielebarchiesi@0: } danielebarchiesi@0: else { danielebarchiesi@0: // Do a plain comparison otherwise. danielebarchiesi@0: return compare(reference, value); danielebarchiesi@0: } danielebarchiesi@0: }, danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * Update the value of a dependee's state. danielebarchiesi@0: * danielebarchiesi@0: * @param selector danielebarchiesi@0: * CSS selector describing the dependee. danielebarchiesi@0: * @param state danielebarchiesi@0: * A State object describing the dependee's updated state. danielebarchiesi@0: * @param value danielebarchiesi@0: * The new value for the dependee's updated state. danielebarchiesi@0: */ danielebarchiesi@0: update: function (selector, state, value) { danielebarchiesi@0: // Only act when the 'new' value is actually new. danielebarchiesi@0: if (value !== this.values[selector][state.name]) { danielebarchiesi@0: this.values[selector][state.name] = value; danielebarchiesi@0: this.reevaluate(); danielebarchiesi@0: } danielebarchiesi@0: }, danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * Triggers change events in case a state changed. danielebarchiesi@0: */ danielebarchiesi@0: reevaluate: function () { danielebarchiesi@0: // Check whether any constraint for this dependent state is satisifed. danielebarchiesi@0: var value = this.verifyConstraints(this.constraints); danielebarchiesi@0: danielebarchiesi@0: // Only invoke a state change event when the value actually changed. danielebarchiesi@0: if (value !== this.oldValue) { danielebarchiesi@0: // Store the new value so that we can compare later whether the value danielebarchiesi@0: // actually changed. danielebarchiesi@0: this.oldValue = value; danielebarchiesi@0: danielebarchiesi@0: // Normalize the value to match the normalized state name. danielebarchiesi@0: value = invert(value, this.state.invert); danielebarchiesi@0: danielebarchiesi@0: // By adding "trigger: true", we ensure that state changes don't go into danielebarchiesi@0: // infinite loops. danielebarchiesi@0: this.element.trigger({ type: 'state:' + this.state, value: value, trigger: true }); danielebarchiesi@0: } danielebarchiesi@0: }, danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * Evaluates child constraints to determine if a constraint is satisfied. danielebarchiesi@0: * danielebarchiesi@0: * @param constraints danielebarchiesi@0: * A constraint object or an array of constraints. danielebarchiesi@0: * @param selector danielebarchiesi@0: * The selector for these constraints. If undefined, there isn't yet a danielebarchiesi@0: * selector that these constraints apply to. In that case, the keys of the danielebarchiesi@0: * object are interpreted as the selector if encountered. danielebarchiesi@0: * danielebarchiesi@0: * @return danielebarchiesi@0: * true or false, depending on whether these constraints are satisfied. danielebarchiesi@0: */ danielebarchiesi@0: verifyConstraints: function(constraints, selector) { danielebarchiesi@0: var result; danielebarchiesi@0: if ($.isArray(constraints)) { danielebarchiesi@0: // This constraint is an array (OR or XOR). danielebarchiesi@0: var hasXor = $.inArray('xor', constraints) === -1; danielebarchiesi@0: for (var i = 0, len = constraints.length; i < len; i++) { danielebarchiesi@0: if (constraints[i] != 'xor') { danielebarchiesi@0: var constraint = this.checkConstraints(constraints[i], selector, i); danielebarchiesi@0: // Return if this is OR and we have a satisfied constraint or if this danielebarchiesi@0: // is XOR and we have a second satisfied constraint. danielebarchiesi@0: if (constraint && (hasXor || result)) { danielebarchiesi@0: return hasXor; danielebarchiesi@0: } danielebarchiesi@0: result = result || constraint; danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: // Make sure we don't try to iterate over things other than objects. This danielebarchiesi@0: // shouldn't normally occur, but in case the condition definition is bogus, danielebarchiesi@0: // we don't want to end up with an infinite loop. danielebarchiesi@0: else if ($.isPlainObject(constraints)) { danielebarchiesi@0: // This constraint is an object (AND). danielebarchiesi@0: for (var n in constraints) { danielebarchiesi@0: if (constraints.hasOwnProperty(n)) { danielebarchiesi@0: result = ternary(result, this.checkConstraints(constraints[n], selector, n)); danielebarchiesi@0: // False and anything else will evaluate to false, so return when any danielebarchiesi@0: // false condition is found. danielebarchiesi@0: if (result === false) { return false; } danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: return result; danielebarchiesi@0: }, danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * Checks whether the value matches the requirements for this constraint. danielebarchiesi@0: * danielebarchiesi@0: * @param value danielebarchiesi@0: * Either the value of a state or an array/object of constraints. In the danielebarchiesi@0: * latter case, resolving the constraint continues. danielebarchiesi@0: * @param selector danielebarchiesi@0: * The selector for this constraint. If undefined, there isn't yet a danielebarchiesi@0: * selector that this constraint applies to. In that case, the state key is danielebarchiesi@0: * propagates to a selector and resolving continues. danielebarchiesi@0: * @param state danielebarchiesi@0: * The state to check for this constraint. If undefined, resolving danielebarchiesi@0: * continues. danielebarchiesi@0: * If both selector and state aren't undefined and valid non-numeric danielebarchiesi@0: * strings, a lookup for the actual value of that selector's state is danielebarchiesi@0: * performed. This parameter is not a State object but a pristine state danielebarchiesi@0: * string. danielebarchiesi@0: * danielebarchiesi@0: * @return danielebarchiesi@0: * true or false, depending on whether this constraint is satisfied. danielebarchiesi@0: */ danielebarchiesi@0: checkConstraints: function(value, selector, state) { danielebarchiesi@0: // Normalize the last parameter. If it's non-numeric, we treat it either as danielebarchiesi@0: // a selector (in case there isn't one yet) or as a trigger/state. danielebarchiesi@0: if (typeof state !== 'string' || (/[0-9]/).test(state[0])) { danielebarchiesi@0: state = null; danielebarchiesi@0: } danielebarchiesi@0: else if (typeof selector === 'undefined') { danielebarchiesi@0: // Propagate the state to the selector when there isn't one yet. danielebarchiesi@0: selector = state; danielebarchiesi@0: state = null; danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: if (state !== null) { danielebarchiesi@0: // constraints is the actual constraints of an element to check for. danielebarchiesi@0: state = states.State.sanitize(state); danielebarchiesi@0: return invert(this.compare(value, selector, state), state.invert); danielebarchiesi@0: } danielebarchiesi@0: else { danielebarchiesi@0: // Resolve this constraint as an AND/OR operator. danielebarchiesi@0: return this.verifyConstraints(value, selector); danielebarchiesi@0: } danielebarchiesi@0: }, danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * Gathers information about all required triggers. danielebarchiesi@0: */ danielebarchiesi@0: getDependees: function() { danielebarchiesi@0: var cache = {}; danielebarchiesi@0: // Swivel the lookup function so that we can record all available selector- danielebarchiesi@0: // state combinations for initialization. danielebarchiesi@0: var _compare = this.compare; danielebarchiesi@0: this.compare = function(reference, selector, state) { danielebarchiesi@0: (cache[selector] || (cache[selector] = [])).push(state.name); danielebarchiesi@0: // Return nothing (=== undefined) so that the constraint loops are not danielebarchiesi@0: // broken. danielebarchiesi@0: }; danielebarchiesi@0: danielebarchiesi@0: // This call doesn't actually verify anything but uses the resolving danielebarchiesi@0: // mechanism to go through the constraints array, trying to look up each danielebarchiesi@0: // value. Since we swivelled the compare function, this comparison returns danielebarchiesi@0: // undefined and lookup continues until the very end. Instead of lookup up danielebarchiesi@0: // the value, we record that combination of selector and state so that we danielebarchiesi@0: // can initialize all triggers. danielebarchiesi@0: this.verifyConstraints(this.constraints); danielebarchiesi@0: // Restore the original function. danielebarchiesi@0: this.compare = _compare; danielebarchiesi@0: danielebarchiesi@0: return cache; danielebarchiesi@0: } danielebarchiesi@0: }; danielebarchiesi@0: danielebarchiesi@0: states.Trigger = function (args) { danielebarchiesi@0: $.extend(this, args); danielebarchiesi@0: danielebarchiesi@0: if (this.state in states.Trigger.states) { danielebarchiesi@0: this.element = $(this.selector); danielebarchiesi@0: danielebarchiesi@0: // Only call the trigger initializer when it wasn't yet attached to this danielebarchiesi@0: // element. Otherwise we'd end up with duplicate events. danielebarchiesi@0: if (!this.element.data('trigger:' + this.state)) { danielebarchiesi@0: this.initialize(); danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: }; danielebarchiesi@0: danielebarchiesi@0: states.Trigger.prototype = { danielebarchiesi@0: initialize: function () { danielebarchiesi@0: var trigger = states.Trigger.states[this.state]; danielebarchiesi@0: danielebarchiesi@0: if (typeof trigger == 'function') { danielebarchiesi@0: // We have a custom trigger initialization function. danielebarchiesi@0: trigger.call(window, this.element); danielebarchiesi@0: } danielebarchiesi@0: else { danielebarchiesi@0: for (var event in trigger) { danielebarchiesi@0: if (trigger.hasOwnProperty(event)) { danielebarchiesi@0: this.defaultTrigger(event, trigger[event]); danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: // Mark this trigger as initialized for this element. danielebarchiesi@0: this.element.data('trigger:' + this.state, true); danielebarchiesi@0: }, danielebarchiesi@0: danielebarchiesi@0: defaultTrigger: function (event, valueFn) { danielebarchiesi@0: var oldValue = valueFn.call(this.element); danielebarchiesi@0: danielebarchiesi@0: // Attach the event callback. danielebarchiesi@0: this.element.bind(event, $.proxy(function (e) { danielebarchiesi@0: var value = valueFn.call(this.element, e); danielebarchiesi@0: // Only trigger the event if the value has actually changed. danielebarchiesi@0: if (oldValue !== value) { danielebarchiesi@0: this.element.trigger({ type: 'state:' + this.state, value: value, oldValue: oldValue }); danielebarchiesi@0: oldValue = value; danielebarchiesi@0: } danielebarchiesi@0: }, this)); danielebarchiesi@0: danielebarchiesi@0: states.postponed.push($.proxy(function () { danielebarchiesi@0: // Trigger the event once for initialization purposes. danielebarchiesi@0: this.element.trigger({ type: 'state:' + this.state, value: oldValue, oldValue: null }); danielebarchiesi@0: }, this)); danielebarchiesi@0: } danielebarchiesi@0: }; danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * This list of states contains functions that are used to monitor the state danielebarchiesi@0: * of an element. Whenever an element depends on the state of another element, danielebarchiesi@0: * one of these trigger functions is added to the dependee so that the danielebarchiesi@0: * dependent element can be updated. danielebarchiesi@0: */ danielebarchiesi@0: states.Trigger.states = { danielebarchiesi@0: // 'empty' describes the state to be monitored danielebarchiesi@0: empty: { danielebarchiesi@0: // 'keyup' is the (native DOM) event that we watch for. danielebarchiesi@0: 'keyup': function () { danielebarchiesi@0: // The function associated to that trigger returns the new value for the danielebarchiesi@0: // state. danielebarchiesi@0: return this.val() == ''; danielebarchiesi@0: } danielebarchiesi@0: }, danielebarchiesi@0: danielebarchiesi@0: checked: { danielebarchiesi@0: 'change': function () { danielebarchiesi@0: return this.attr('checked'); danielebarchiesi@0: } danielebarchiesi@0: }, danielebarchiesi@0: danielebarchiesi@0: // For radio buttons, only return the value if the radio button is selected. danielebarchiesi@0: value: { danielebarchiesi@0: 'keyup': function () { danielebarchiesi@0: // Radio buttons share the same :input[name="key"] selector. danielebarchiesi@0: if (this.length > 1) { danielebarchiesi@0: // Initial checked value of radios is undefined, so we return false. danielebarchiesi@0: return this.filter(':checked').val() || false; danielebarchiesi@0: } danielebarchiesi@0: return this.val(); danielebarchiesi@0: }, danielebarchiesi@0: 'change': function () { danielebarchiesi@0: // Radio buttons share the same :input[name="key"] selector. danielebarchiesi@0: if (this.length > 1) { danielebarchiesi@0: // Initial checked value of radios is undefined, so we return false. danielebarchiesi@0: return this.filter(':checked').val() || false; danielebarchiesi@0: } danielebarchiesi@0: return this.val(); danielebarchiesi@0: } danielebarchiesi@0: }, danielebarchiesi@0: danielebarchiesi@0: collapsed: { danielebarchiesi@0: 'collapsed': function(e) { danielebarchiesi@0: return (typeof e !== 'undefined' && 'value' in e) ? e.value : this.is('.collapsed'); danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: }; danielebarchiesi@0: danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * A state object is used for describing the state and performing aliasing. danielebarchiesi@0: */ danielebarchiesi@0: states.State = function(state) { danielebarchiesi@0: // We may need the original unresolved name later. danielebarchiesi@0: this.pristine = this.name = state; danielebarchiesi@0: danielebarchiesi@0: // Normalize the state name. danielebarchiesi@0: while (true) { danielebarchiesi@0: // Iteratively remove exclamation marks and invert the value. danielebarchiesi@0: while (this.name.charAt(0) == '!') { danielebarchiesi@0: this.name = this.name.substring(1); danielebarchiesi@0: this.invert = !this.invert; danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: // Replace the state with its normalized name. danielebarchiesi@0: if (this.name in states.State.aliases) { danielebarchiesi@0: this.name = states.State.aliases[this.name]; danielebarchiesi@0: } danielebarchiesi@0: else { danielebarchiesi@0: break; danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: }; danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * Creates a new State object by sanitizing the passed value. danielebarchiesi@0: */ danielebarchiesi@0: states.State.sanitize = function (state) { danielebarchiesi@0: if (state instanceof states.State) { danielebarchiesi@0: return state; danielebarchiesi@0: } danielebarchiesi@0: else { danielebarchiesi@0: return new states.State(state); danielebarchiesi@0: } danielebarchiesi@0: }; danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * This list of aliases is used to normalize states and associates negated names danielebarchiesi@0: * with their respective inverse state. danielebarchiesi@0: */ danielebarchiesi@0: states.State.aliases = { danielebarchiesi@0: 'enabled': '!disabled', danielebarchiesi@0: 'invisible': '!visible', danielebarchiesi@0: 'invalid': '!valid', danielebarchiesi@0: 'untouched': '!touched', danielebarchiesi@0: 'optional': '!required', danielebarchiesi@0: 'filled': '!empty', danielebarchiesi@0: 'unchecked': '!checked', danielebarchiesi@0: 'irrelevant': '!relevant', danielebarchiesi@0: 'expanded': '!collapsed', danielebarchiesi@0: 'readwrite': '!readonly' danielebarchiesi@0: }; danielebarchiesi@0: danielebarchiesi@0: states.State.prototype = { danielebarchiesi@0: invert: false, danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * Ensures that just using the state object returns the name. danielebarchiesi@0: */ danielebarchiesi@0: toString: function() { danielebarchiesi@0: return this.name; danielebarchiesi@0: } danielebarchiesi@0: }; danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * Global state change handlers. These are bound to "document" to cover all danielebarchiesi@0: * elements whose state changes. Events sent to elements within the page danielebarchiesi@0: * bubble up to these handlers. We use this system so that themes and modules danielebarchiesi@0: * can override these state change handlers for particular parts of a page. danielebarchiesi@0: */ danielebarchiesi@0: $(document).bind('state:disabled', function(e) { danielebarchiesi@0: // Only act when this change was triggered by a dependency and not by the danielebarchiesi@0: // element monitoring itself. danielebarchiesi@0: if (e.trigger) { danielebarchiesi@0: $(e.target) danielebarchiesi@0: .attr('disabled', e.value) danielebarchiesi@0: .closest('.form-item, .form-submit, .form-wrapper').toggleClass('form-disabled', e.value) danielebarchiesi@0: .find('select, input, textarea').attr('disabled', e.value); danielebarchiesi@0: danielebarchiesi@0: // Note: WebKit nightlies don't reflect that change correctly. danielebarchiesi@0: // See https://bugs.webkit.org/show_bug.cgi?id=23789 danielebarchiesi@0: } danielebarchiesi@0: }); danielebarchiesi@0: danielebarchiesi@0: $(document).bind('state:required', function(e) { danielebarchiesi@0: if (e.trigger) { danielebarchiesi@0: if (e.value) { danielebarchiesi@0: $(e.target).closest('.form-item, .form-wrapper').find('label').append('*'); danielebarchiesi@0: } danielebarchiesi@0: else { danielebarchiesi@0: $(e.target).closest('.form-item, .form-wrapper').find('label .form-required').remove(); danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: }); danielebarchiesi@0: danielebarchiesi@0: $(document).bind('state:visible', function(e) { danielebarchiesi@0: if (e.trigger) { danielebarchiesi@0: $(e.target).closest('.form-item, .form-submit, .form-wrapper').toggle(e.value); danielebarchiesi@0: } danielebarchiesi@0: }); danielebarchiesi@0: danielebarchiesi@0: $(document).bind('state:checked', function(e) { danielebarchiesi@0: if (e.trigger) { danielebarchiesi@0: $(e.target).attr('checked', e.value); danielebarchiesi@0: } danielebarchiesi@0: }); danielebarchiesi@0: danielebarchiesi@0: $(document).bind('state:collapsed', function(e) { danielebarchiesi@0: if (e.trigger) { danielebarchiesi@0: if ($(e.target).is('.collapsed') !== e.value) { danielebarchiesi@0: $('> legend a', e.target).click(); danielebarchiesi@0: } danielebarchiesi@0: } danielebarchiesi@0: }); danielebarchiesi@0: danielebarchiesi@0: /** danielebarchiesi@0: * These are helper functions implementing addition "operators" and don't danielebarchiesi@0: * implement any logic that is particular to states. danielebarchiesi@0: */ danielebarchiesi@0: danielebarchiesi@0: // Bitwise AND with a third undefined state. danielebarchiesi@0: function ternary (a, b) { danielebarchiesi@0: return typeof a === 'undefined' ? b : (typeof b === 'undefined' ? a : a && b); danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: // Inverts a (if it's not undefined) when invert is true. danielebarchiesi@0: function invert (a, invert) { danielebarchiesi@0: return (invert && typeof a !== 'undefined') ? !a : a; danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: // Compares two values while ignoring undefined values. danielebarchiesi@0: function compare (a, b) { danielebarchiesi@0: return (a === b) ? (typeof a === 'undefined' ? a : true) : (typeof a === 'undefined' || typeof b === 'undefined'); danielebarchiesi@0: } danielebarchiesi@0: danielebarchiesi@0: })(jQuery);