annotate misc/states.js @ 13:134d4b2e75f6

updated quicktabs and google analytics modules
author danieleb <danielebarchiesi@me.com>
date Tue, 29 Oct 2013 13:48:59 +0000
parents ff03f76ab3fe
children
rev   line source
danielebarchiesi@0 1 (function ($) {
danielebarchiesi@0 2
danielebarchiesi@0 3 /**
danielebarchiesi@0 4 * The base States namespace.
danielebarchiesi@0 5 *
danielebarchiesi@0 6 * Having the local states variable allows us to use the States namespace
danielebarchiesi@0 7 * without having to always declare "Drupal.states".
danielebarchiesi@0 8 */
danielebarchiesi@0 9 var states = Drupal.states = {
danielebarchiesi@0 10 // An array of functions that should be postponed.
danielebarchiesi@0 11 postponed: []
danielebarchiesi@0 12 };
danielebarchiesi@0 13
danielebarchiesi@0 14 /**
danielebarchiesi@0 15 * Attaches the states.
danielebarchiesi@0 16 */
danielebarchiesi@0 17 Drupal.behaviors.states = {
danielebarchiesi@0 18 attach: function (context, settings) {
danielebarchiesi@0 19 var $context = $(context);
danielebarchiesi@0 20 for (var selector in settings.states) {
danielebarchiesi@0 21 for (var state in settings.states[selector]) {
danielebarchiesi@0 22 new states.Dependent({
danielebarchiesi@0 23 element: $context.find(selector),
danielebarchiesi@0 24 state: states.State.sanitize(state),
danielebarchiesi@0 25 constraints: settings.states[selector][state]
danielebarchiesi@0 26 });
danielebarchiesi@0 27 }
danielebarchiesi@0 28 }
danielebarchiesi@0 29
danielebarchiesi@0 30 // Execute all postponed functions now.
danielebarchiesi@0 31 while (states.postponed.length) {
danielebarchiesi@0 32 (states.postponed.shift())();
danielebarchiesi@0 33 }
danielebarchiesi@0 34 }
danielebarchiesi@0 35 };
danielebarchiesi@0 36
danielebarchiesi@0 37 /**
danielebarchiesi@0 38 * Object representing an element that depends on other elements.
danielebarchiesi@0 39 *
danielebarchiesi@0 40 * @param args
danielebarchiesi@0 41 * Object with the following keys (all of which are required):
danielebarchiesi@0 42 * - element: A jQuery object of the dependent element
danielebarchiesi@0 43 * - state: A State object describing the state that is dependent
danielebarchiesi@0 44 * - constraints: An object with dependency specifications. Lists all elements
danielebarchiesi@0 45 * that this element depends on. It can be nested and can contain arbitrary
danielebarchiesi@0 46 * AND and OR clauses.
danielebarchiesi@0 47 */
danielebarchiesi@0 48 states.Dependent = function (args) {
danielebarchiesi@0 49 $.extend(this, { values: {}, oldValue: null }, args);
danielebarchiesi@0 50
danielebarchiesi@0 51 this.dependees = this.getDependees();
danielebarchiesi@0 52 for (var selector in this.dependees) {
danielebarchiesi@0 53 this.initializeDependee(selector, this.dependees[selector]);
danielebarchiesi@0 54 }
danielebarchiesi@0 55 };
danielebarchiesi@0 56
danielebarchiesi@0 57 /**
danielebarchiesi@0 58 * Comparison functions for comparing the value of an element with the
danielebarchiesi@0 59 * specification from the dependency settings. If the object type can't be
danielebarchiesi@0 60 * found in this list, the === operator is used by default.
danielebarchiesi@0 61 */
danielebarchiesi@0 62 states.Dependent.comparisons = {
danielebarchiesi@0 63 'RegExp': function (reference, value) {
danielebarchiesi@0 64 return reference.test(value);
danielebarchiesi@0 65 },
danielebarchiesi@0 66 'Function': function (reference, value) {
danielebarchiesi@0 67 // The "reference" variable is a comparison function.
danielebarchiesi@0 68 return reference(value);
danielebarchiesi@0 69 },
danielebarchiesi@0 70 'Number': function (reference, value) {
danielebarchiesi@0 71 // If "reference" is a number and "value" is a string, then cast reference
danielebarchiesi@0 72 // as a string before applying the strict comparison in compare(). Otherwise
danielebarchiesi@0 73 // numeric keys in the form's #states array fail to match string values
danielebarchiesi@0 74 // returned from jQuery's val().
danielebarchiesi@0 75 return (typeof value === 'string') ? compare(reference.toString(), value) : compare(reference, value);
danielebarchiesi@0 76 }
danielebarchiesi@0 77 };
danielebarchiesi@0 78
danielebarchiesi@0 79 states.Dependent.prototype = {
danielebarchiesi@0 80 /**
danielebarchiesi@0 81 * Initializes one of the elements this dependent depends on.
danielebarchiesi@0 82 *
danielebarchiesi@0 83 * @param selector
danielebarchiesi@0 84 * The CSS selector describing the dependee.
danielebarchiesi@0 85 * @param dependeeStates
danielebarchiesi@0 86 * The list of states that have to be monitored for tracking the
danielebarchiesi@0 87 * dependee's compliance status.
danielebarchiesi@0 88 */
danielebarchiesi@0 89 initializeDependee: function (selector, dependeeStates) {
danielebarchiesi@0 90 var state;
danielebarchiesi@0 91
danielebarchiesi@0 92 // Cache for the states of this dependee.
danielebarchiesi@0 93 this.values[selector] = {};
danielebarchiesi@0 94
danielebarchiesi@0 95 for (var i in dependeeStates) {
danielebarchiesi@0 96 if (dependeeStates.hasOwnProperty(i)) {
danielebarchiesi@0 97 state = dependeeStates[i];
danielebarchiesi@0 98 // Make sure we're not initializing this selector/state combination twice.
danielebarchiesi@0 99 if ($.inArray(state, dependeeStates) === -1) {
danielebarchiesi@0 100 continue;
danielebarchiesi@0 101 }
danielebarchiesi@0 102
danielebarchiesi@0 103 state = states.State.sanitize(state);
danielebarchiesi@0 104
danielebarchiesi@0 105 // Initialize the value of this state.
danielebarchiesi@0 106 this.values[selector][state.name] = null;
danielebarchiesi@0 107
danielebarchiesi@0 108 // Monitor state changes of the specified state for this dependee.
danielebarchiesi@0 109 $(selector).bind('state:' + state, $.proxy(function (e) {
danielebarchiesi@0 110 this.update(selector, state, e.value);
danielebarchiesi@0 111 }, this));
danielebarchiesi@0 112
danielebarchiesi@0 113 // Make sure the event we just bound ourselves to is actually fired.
danielebarchiesi@0 114 new states.Trigger({ selector: selector, state: state });
danielebarchiesi@0 115 }
danielebarchiesi@0 116 }
danielebarchiesi@0 117 },
danielebarchiesi@0 118
danielebarchiesi@0 119 /**
danielebarchiesi@0 120 * Compares a value with a reference value.
danielebarchiesi@0 121 *
danielebarchiesi@0 122 * @param reference
danielebarchiesi@0 123 * The value used for reference.
danielebarchiesi@0 124 * @param selector
danielebarchiesi@0 125 * CSS selector describing the dependee.
danielebarchiesi@0 126 * @param state
danielebarchiesi@0 127 * A State object describing the dependee's updated state.
danielebarchiesi@0 128 *
danielebarchiesi@0 129 * @return
danielebarchiesi@0 130 * true or false.
danielebarchiesi@0 131 */
danielebarchiesi@0 132 compare: function (reference, selector, state) {
danielebarchiesi@0 133 var value = this.values[selector][state.name];
danielebarchiesi@0 134 if (reference.constructor.name in states.Dependent.comparisons) {
danielebarchiesi@0 135 // Use a custom compare function for certain reference value types.
danielebarchiesi@0 136 return states.Dependent.comparisons[reference.constructor.name](reference, value);
danielebarchiesi@0 137 }
danielebarchiesi@0 138 else {
danielebarchiesi@0 139 // Do a plain comparison otherwise.
danielebarchiesi@0 140 return compare(reference, value);
danielebarchiesi@0 141 }
danielebarchiesi@0 142 },
danielebarchiesi@0 143
danielebarchiesi@0 144 /**
danielebarchiesi@0 145 * Update the value of a dependee's state.
danielebarchiesi@0 146 *
danielebarchiesi@0 147 * @param selector
danielebarchiesi@0 148 * CSS selector describing the dependee.
danielebarchiesi@0 149 * @param state
danielebarchiesi@0 150 * A State object describing the dependee's updated state.
danielebarchiesi@0 151 * @param value
danielebarchiesi@0 152 * The new value for the dependee's updated state.
danielebarchiesi@0 153 */
danielebarchiesi@0 154 update: function (selector, state, value) {
danielebarchiesi@0 155 // Only act when the 'new' value is actually new.
danielebarchiesi@0 156 if (value !== this.values[selector][state.name]) {
danielebarchiesi@0 157 this.values[selector][state.name] = value;
danielebarchiesi@0 158 this.reevaluate();
danielebarchiesi@0 159 }
danielebarchiesi@0 160 },
danielebarchiesi@0 161
danielebarchiesi@0 162 /**
danielebarchiesi@0 163 * Triggers change events in case a state changed.
danielebarchiesi@0 164 */
danielebarchiesi@0 165 reevaluate: function () {
danielebarchiesi@0 166 // Check whether any constraint for this dependent state is satisifed.
danielebarchiesi@0 167 var value = this.verifyConstraints(this.constraints);
danielebarchiesi@0 168
danielebarchiesi@0 169 // Only invoke a state change event when the value actually changed.
danielebarchiesi@0 170 if (value !== this.oldValue) {
danielebarchiesi@0 171 // Store the new value so that we can compare later whether the value
danielebarchiesi@0 172 // actually changed.
danielebarchiesi@0 173 this.oldValue = value;
danielebarchiesi@0 174
danielebarchiesi@0 175 // Normalize the value to match the normalized state name.
danielebarchiesi@0 176 value = invert(value, this.state.invert);
danielebarchiesi@0 177
danielebarchiesi@0 178 // By adding "trigger: true", we ensure that state changes don't go into
danielebarchiesi@0 179 // infinite loops.
danielebarchiesi@0 180 this.element.trigger({ type: 'state:' + this.state, value: value, trigger: true });
danielebarchiesi@0 181 }
danielebarchiesi@0 182 },
danielebarchiesi@0 183
danielebarchiesi@0 184 /**
danielebarchiesi@0 185 * Evaluates child constraints to determine if a constraint is satisfied.
danielebarchiesi@0 186 *
danielebarchiesi@0 187 * @param constraints
danielebarchiesi@0 188 * A constraint object or an array of constraints.
danielebarchiesi@0 189 * @param selector
danielebarchiesi@0 190 * The selector for these constraints. If undefined, there isn't yet a
danielebarchiesi@0 191 * selector that these constraints apply to. In that case, the keys of the
danielebarchiesi@0 192 * object are interpreted as the selector if encountered.
danielebarchiesi@0 193 *
danielebarchiesi@0 194 * @return
danielebarchiesi@0 195 * true or false, depending on whether these constraints are satisfied.
danielebarchiesi@0 196 */
danielebarchiesi@0 197 verifyConstraints: function(constraints, selector) {
danielebarchiesi@0 198 var result;
danielebarchiesi@0 199 if ($.isArray(constraints)) {
danielebarchiesi@0 200 // This constraint is an array (OR or XOR).
danielebarchiesi@0 201 var hasXor = $.inArray('xor', constraints) === -1;
danielebarchiesi@0 202 for (var i = 0, len = constraints.length; i < len; i++) {
danielebarchiesi@0 203 if (constraints[i] != 'xor') {
danielebarchiesi@0 204 var constraint = this.checkConstraints(constraints[i], selector, i);
danielebarchiesi@0 205 // Return if this is OR and we have a satisfied constraint or if this
danielebarchiesi@0 206 // is XOR and we have a second satisfied constraint.
danielebarchiesi@0 207 if (constraint && (hasXor || result)) {
danielebarchiesi@0 208 return hasXor;
danielebarchiesi@0 209 }
danielebarchiesi@0 210 result = result || constraint;
danielebarchiesi@0 211 }
danielebarchiesi@0 212 }
danielebarchiesi@0 213 }
danielebarchiesi@0 214 // Make sure we don't try to iterate over things other than objects. This
danielebarchiesi@0 215 // shouldn't normally occur, but in case the condition definition is bogus,
danielebarchiesi@0 216 // we don't want to end up with an infinite loop.
danielebarchiesi@0 217 else if ($.isPlainObject(constraints)) {
danielebarchiesi@0 218 // This constraint is an object (AND).
danielebarchiesi@0 219 for (var n in constraints) {
danielebarchiesi@0 220 if (constraints.hasOwnProperty(n)) {
danielebarchiesi@0 221 result = ternary(result, this.checkConstraints(constraints[n], selector, n));
danielebarchiesi@0 222 // False and anything else will evaluate to false, so return when any
danielebarchiesi@0 223 // false condition is found.
danielebarchiesi@0 224 if (result === false) { return false; }
danielebarchiesi@0 225 }
danielebarchiesi@0 226 }
danielebarchiesi@0 227 }
danielebarchiesi@0 228 return result;
danielebarchiesi@0 229 },
danielebarchiesi@0 230
danielebarchiesi@0 231 /**
danielebarchiesi@0 232 * Checks whether the value matches the requirements for this constraint.
danielebarchiesi@0 233 *
danielebarchiesi@0 234 * @param value
danielebarchiesi@0 235 * Either the value of a state or an array/object of constraints. In the
danielebarchiesi@0 236 * latter case, resolving the constraint continues.
danielebarchiesi@0 237 * @param selector
danielebarchiesi@0 238 * The selector for this constraint. If undefined, there isn't yet a
danielebarchiesi@0 239 * selector that this constraint applies to. In that case, the state key is
danielebarchiesi@0 240 * propagates to a selector and resolving continues.
danielebarchiesi@0 241 * @param state
danielebarchiesi@0 242 * The state to check for this constraint. If undefined, resolving
danielebarchiesi@0 243 * continues.
danielebarchiesi@0 244 * If both selector and state aren't undefined and valid non-numeric
danielebarchiesi@0 245 * strings, a lookup for the actual value of that selector's state is
danielebarchiesi@0 246 * performed. This parameter is not a State object but a pristine state
danielebarchiesi@0 247 * string.
danielebarchiesi@0 248 *
danielebarchiesi@0 249 * @return
danielebarchiesi@0 250 * true or false, depending on whether this constraint is satisfied.
danielebarchiesi@0 251 */
danielebarchiesi@0 252 checkConstraints: function(value, selector, state) {
danielebarchiesi@0 253 // Normalize the last parameter. If it's non-numeric, we treat it either as
danielebarchiesi@0 254 // a selector (in case there isn't one yet) or as a trigger/state.
danielebarchiesi@0 255 if (typeof state !== 'string' || (/[0-9]/).test(state[0])) {
danielebarchiesi@0 256 state = null;
danielebarchiesi@0 257 }
danielebarchiesi@0 258 else if (typeof selector === 'undefined') {
danielebarchiesi@0 259 // Propagate the state to the selector when there isn't one yet.
danielebarchiesi@0 260 selector = state;
danielebarchiesi@0 261 state = null;
danielebarchiesi@0 262 }
danielebarchiesi@0 263
danielebarchiesi@0 264 if (state !== null) {
danielebarchiesi@0 265 // constraints is the actual constraints of an element to check for.
danielebarchiesi@0 266 state = states.State.sanitize(state);
danielebarchiesi@0 267 return invert(this.compare(value, selector, state), state.invert);
danielebarchiesi@0 268 }
danielebarchiesi@0 269 else {
danielebarchiesi@0 270 // Resolve this constraint as an AND/OR operator.
danielebarchiesi@0 271 return this.verifyConstraints(value, selector);
danielebarchiesi@0 272 }
danielebarchiesi@0 273 },
danielebarchiesi@0 274
danielebarchiesi@0 275 /**
danielebarchiesi@0 276 * Gathers information about all required triggers.
danielebarchiesi@0 277 */
danielebarchiesi@0 278 getDependees: function() {
danielebarchiesi@0 279 var cache = {};
danielebarchiesi@0 280 // Swivel the lookup function so that we can record all available selector-
danielebarchiesi@0 281 // state combinations for initialization.
danielebarchiesi@0 282 var _compare = this.compare;
danielebarchiesi@0 283 this.compare = function(reference, selector, state) {
danielebarchiesi@0 284 (cache[selector] || (cache[selector] = [])).push(state.name);
danielebarchiesi@0 285 // Return nothing (=== undefined) so that the constraint loops are not
danielebarchiesi@0 286 // broken.
danielebarchiesi@0 287 };
danielebarchiesi@0 288
danielebarchiesi@0 289 // This call doesn't actually verify anything but uses the resolving
danielebarchiesi@0 290 // mechanism to go through the constraints array, trying to look up each
danielebarchiesi@0 291 // value. Since we swivelled the compare function, this comparison returns
danielebarchiesi@0 292 // undefined and lookup continues until the very end. Instead of lookup up
danielebarchiesi@0 293 // the value, we record that combination of selector and state so that we
danielebarchiesi@0 294 // can initialize all triggers.
danielebarchiesi@0 295 this.verifyConstraints(this.constraints);
danielebarchiesi@0 296 // Restore the original function.
danielebarchiesi@0 297 this.compare = _compare;
danielebarchiesi@0 298
danielebarchiesi@0 299 return cache;
danielebarchiesi@0 300 }
danielebarchiesi@0 301 };
danielebarchiesi@0 302
danielebarchiesi@0 303 states.Trigger = function (args) {
danielebarchiesi@0 304 $.extend(this, args);
danielebarchiesi@0 305
danielebarchiesi@0 306 if (this.state in states.Trigger.states) {
danielebarchiesi@0 307 this.element = $(this.selector);
danielebarchiesi@0 308
danielebarchiesi@0 309 // Only call the trigger initializer when it wasn't yet attached to this
danielebarchiesi@0 310 // element. Otherwise we'd end up with duplicate events.
danielebarchiesi@0 311 if (!this.element.data('trigger:' + this.state)) {
danielebarchiesi@0 312 this.initialize();
danielebarchiesi@0 313 }
danielebarchiesi@0 314 }
danielebarchiesi@0 315 };
danielebarchiesi@0 316
danielebarchiesi@0 317 states.Trigger.prototype = {
danielebarchiesi@0 318 initialize: function () {
danielebarchiesi@0 319 var trigger = states.Trigger.states[this.state];
danielebarchiesi@0 320
danielebarchiesi@0 321 if (typeof trigger == 'function') {
danielebarchiesi@0 322 // We have a custom trigger initialization function.
danielebarchiesi@0 323 trigger.call(window, this.element);
danielebarchiesi@0 324 }
danielebarchiesi@0 325 else {
danielebarchiesi@0 326 for (var event in trigger) {
danielebarchiesi@0 327 if (trigger.hasOwnProperty(event)) {
danielebarchiesi@0 328 this.defaultTrigger(event, trigger[event]);
danielebarchiesi@0 329 }
danielebarchiesi@0 330 }
danielebarchiesi@0 331 }
danielebarchiesi@0 332
danielebarchiesi@0 333 // Mark this trigger as initialized for this element.
danielebarchiesi@0 334 this.element.data('trigger:' + this.state, true);
danielebarchiesi@0 335 },
danielebarchiesi@0 336
danielebarchiesi@0 337 defaultTrigger: function (event, valueFn) {
danielebarchiesi@0 338 var oldValue = valueFn.call(this.element);
danielebarchiesi@0 339
danielebarchiesi@0 340 // Attach the event callback.
danielebarchiesi@0 341 this.element.bind(event, $.proxy(function (e) {
danielebarchiesi@0 342 var value = valueFn.call(this.element, e);
danielebarchiesi@0 343 // Only trigger the event if the value has actually changed.
danielebarchiesi@0 344 if (oldValue !== value) {
danielebarchiesi@0 345 this.element.trigger({ type: 'state:' + this.state, value: value, oldValue: oldValue });
danielebarchiesi@0 346 oldValue = value;
danielebarchiesi@0 347 }
danielebarchiesi@0 348 }, this));
danielebarchiesi@0 349
danielebarchiesi@0 350 states.postponed.push($.proxy(function () {
danielebarchiesi@0 351 // Trigger the event once for initialization purposes.
danielebarchiesi@0 352 this.element.trigger({ type: 'state:' + this.state, value: oldValue, oldValue: null });
danielebarchiesi@0 353 }, this));
danielebarchiesi@0 354 }
danielebarchiesi@0 355 };
danielebarchiesi@0 356
danielebarchiesi@0 357 /**
danielebarchiesi@0 358 * This list of states contains functions that are used to monitor the state
danielebarchiesi@0 359 * of an element. Whenever an element depends on the state of another element,
danielebarchiesi@0 360 * one of these trigger functions is added to the dependee so that the
danielebarchiesi@0 361 * dependent element can be updated.
danielebarchiesi@0 362 */
danielebarchiesi@0 363 states.Trigger.states = {
danielebarchiesi@0 364 // 'empty' describes the state to be monitored
danielebarchiesi@0 365 empty: {
danielebarchiesi@0 366 // 'keyup' is the (native DOM) event that we watch for.
danielebarchiesi@0 367 'keyup': function () {
danielebarchiesi@0 368 // The function associated to that trigger returns the new value for the
danielebarchiesi@0 369 // state.
danielebarchiesi@0 370 return this.val() == '';
danielebarchiesi@0 371 }
danielebarchiesi@0 372 },
danielebarchiesi@0 373
danielebarchiesi@0 374 checked: {
danielebarchiesi@0 375 'change': function () {
danielebarchiesi@0 376 return this.attr('checked');
danielebarchiesi@0 377 }
danielebarchiesi@0 378 },
danielebarchiesi@0 379
danielebarchiesi@0 380 // For radio buttons, only return the value if the radio button is selected.
danielebarchiesi@0 381 value: {
danielebarchiesi@0 382 'keyup': function () {
danielebarchiesi@0 383 // Radio buttons share the same :input[name="key"] selector.
danielebarchiesi@0 384 if (this.length > 1) {
danielebarchiesi@0 385 // Initial checked value of radios is undefined, so we return false.
danielebarchiesi@0 386 return this.filter(':checked').val() || false;
danielebarchiesi@0 387 }
danielebarchiesi@0 388 return this.val();
danielebarchiesi@0 389 },
danielebarchiesi@0 390 'change': function () {
danielebarchiesi@0 391 // Radio buttons share the same :input[name="key"] selector.
danielebarchiesi@0 392 if (this.length > 1) {
danielebarchiesi@0 393 // Initial checked value of radios is undefined, so we return false.
danielebarchiesi@0 394 return this.filter(':checked').val() || false;
danielebarchiesi@0 395 }
danielebarchiesi@0 396 return this.val();
danielebarchiesi@0 397 }
danielebarchiesi@0 398 },
danielebarchiesi@0 399
danielebarchiesi@0 400 collapsed: {
danielebarchiesi@0 401 'collapsed': function(e) {
danielebarchiesi@0 402 return (typeof e !== 'undefined' && 'value' in e) ? e.value : this.is('.collapsed');
danielebarchiesi@0 403 }
danielebarchiesi@0 404 }
danielebarchiesi@0 405 };
danielebarchiesi@0 406
danielebarchiesi@0 407
danielebarchiesi@0 408 /**
danielebarchiesi@0 409 * A state object is used for describing the state and performing aliasing.
danielebarchiesi@0 410 */
danielebarchiesi@0 411 states.State = function(state) {
danielebarchiesi@0 412 // We may need the original unresolved name later.
danielebarchiesi@0 413 this.pristine = this.name = state;
danielebarchiesi@0 414
danielebarchiesi@0 415 // Normalize the state name.
danielebarchiesi@0 416 while (true) {
danielebarchiesi@0 417 // Iteratively remove exclamation marks and invert the value.
danielebarchiesi@0 418 while (this.name.charAt(0) == '!') {
danielebarchiesi@0 419 this.name = this.name.substring(1);
danielebarchiesi@0 420 this.invert = !this.invert;
danielebarchiesi@0 421 }
danielebarchiesi@0 422
danielebarchiesi@0 423 // Replace the state with its normalized name.
danielebarchiesi@0 424 if (this.name in states.State.aliases) {
danielebarchiesi@0 425 this.name = states.State.aliases[this.name];
danielebarchiesi@0 426 }
danielebarchiesi@0 427 else {
danielebarchiesi@0 428 break;
danielebarchiesi@0 429 }
danielebarchiesi@0 430 }
danielebarchiesi@0 431 };
danielebarchiesi@0 432
danielebarchiesi@0 433 /**
danielebarchiesi@0 434 * Creates a new State object by sanitizing the passed value.
danielebarchiesi@0 435 */
danielebarchiesi@0 436 states.State.sanitize = function (state) {
danielebarchiesi@0 437 if (state instanceof states.State) {
danielebarchiesi@0 438 return state;
danielebarchiesi@0 439 }
danielebarchiesi@0 440 else {
danielebarchiesi@0 441 return new states.State(state);
danielebarchiesi@0 442 }
danielebarchiesi@0 443 };
danielebarchiesi@0 444
danielebarchiesi@0 445 /**
danielebarchiesi@0 446 * This list of aliases is used to normalize states and associates negated names
danielebarchiesi@0 447 * with their respective inverse state.
danielebarchiesi@0 448 */
danielebarchiesi@0 449 states.State.aliases = {
danielebarchiesi@0 450 'enabled': '!disabled',
danielebarchiesi@0 451 'invisible': '!visible',
danielebarchiesi@0 452 'invalid': '!valid',
danielebarchiesi@0 453 'untouched': '!touched',
danielebarchiesi@0 454 'optional': '!required',
danielebarchiesi@0 455 'filled': '!empty',
danielebarchiesi@0 456 'unchecked': '!checked',
danielebarchiesi@0 457 'irrelevant': '!relevant',
danielebarchiesi@0 458 'expanded': '!collapsed',
danielebarchiesi@0 459 'readwrite': '!readonly'
danielebarchiesi@0 460 };
danielebarchiesi@0 461
danielebarchiesi@0 462 states.State.prototype = {
danielebarchiesi@0 463 invert: false,
danielebarchiesi@0 464
danielebarchiesi@0 465 /**
danielebarchiesi@0 466 * Ensures that just using the state object returns the name.
danielebarchiesi@0 467 */
danielebarchiesi@0 468 toString: function() {
danielebarchiesi@0 469 return this.name;
danielebarchiesi@0 470 }
danielebarchiesi@0 471 };
danielebarchiesi@0 472
danielebarchiesi@0 473 /**
danielebarchiesi@0 474 * Global state change handlers. These are bound to "document" to cover all
danielebarchiesi@0 475 * elements whose state changes. Events sent to elements within the page
danielebarchiesi@0 476 * bubble up to these handlers. We use this system so that themes and modules
danielebarchiesi@0 477 * can override these state change handlers for particular parts of a page.
danielebarchiesi@0 478 */
danielebarchiesi@0 479 $(document).bind('state:disabled', function(e) {
danielebarchiesi@0 480 // Only act when this change was triggered by a dependency and not by the
danielebarchiesi@0 481 // element monitoring itself.
danielebarchiesi@0 482 if (e.trigger) {
danielebarchiesi@0 483 $(e.target)
danielebarchiesi@0 484 .attr('disabled', e.value)
danielebarchiesi@0 485 .closest('.form-item, .form-submit, .form-wrapper').toggleClass('form-disabled', e.value)
danielebarchiesi@0 486 .find('select, input, textarea').attr('disabled', e.value);
danielebarchiesi@0 487
danielebarchiesi@0 488 // Note: WebKit nightlies don't reflect that change correctly.
danielebarchiesi@0 489 // See https://bugs.webkit.org/show_bug.cgi?id=23789
danielebarchiesi@0 490 }
danielebarchiesi@0 491 });
danielebarchiesi@0 492
danielebarchiesi@0 493 $(document).bind('state:required', function(e) {
danielebarchiesi@0 494 if (e.trigger) {
danielebarchiesi@0 495 if (e.value) {
danielebarchiesi@0 496 $(e.target).closest('.form-item, .form-wrapper').find('label').append('<span class="form-required">*</span>');
danielebarchiesi@0 497 }
danielebarchiesi@0 498 else {
danielebarchiesi@0 499 $(e.target).closest('.form-item, .form-wrapper').find('label .form-required').remove();
danielebarchiesi@0 500 }
danielebarchiesi@0 501 }
danielebarchiesi@0 502 });
danielebarchiesi@0 503
danielebarchiesi@0 504 $(document).bind('state:visible', function(e) {
danielebarchiesi@0 505 if (e.trigger) {
danielebarchiesi@0 506 $(e.target).closest('.form-item, .form-submit, .form-wrapper').toggle(e.value);
danielebarchiesi@0 507 }
danielebarchiesi@0 508 });
danielebarchiesi@0 509
danielebarchiesi@0 510 $(document).bind('state:checked', function(e) {
danielebarchiesi@0 511 if (e.trigger) {
danielebarchiesi@0 512 $(e.target).attr('checked', e.value);
danielebarchiesi@0 513 }
danielebarchiesi@0 514 });
danielebarchiesi@0 515
danielebarchiesi@0 516 $(document).bind('state:collapsed', function(e) {
danielebarchiesi@0 517 if (e.trigger) {
danielebarchiesi@0 518 if ($(e.target).is('.collapsed') !== e.value) {
danielebarchiesi@0 519 $('> legend a', e.target).click();
danielebarchiesi@0 520 }
danielebarchiesi@0 521 }
danielebarchiesi@0 522 });
danielebarchiesi@0 523
danielebarchiesi@0 524 /**
danielebarchiesi@0 525 * These are helper functions implementing addition "operators" and don't
danielebarchiesi@0 526 * implement any logic that is particular to states.
danielebarchiesi@0 527 */
danielebarchiesi@0 528
danielebarchiesi@0 529 // Bitwise AND with a third undefined state.
danielebarchiesi@0 530 function ternary (a, b) {
danielebarchiesi@0 531 return typeof a === 'undefined' ? b : (typeof b === 'undefined' ? a : a && b);
danielebarchiesi@0 532 }
danielebarchiesi@0 533
danielebarchiesi@0 534 // Inverts a (if it's not undefined) when invert is true.
danielebarchiesi@0 535 function invert (a, invert) {
danielebarchiesi@0 536 return (invert && typeof a !== 'undefined') ? !a : a;
danielebarchiesi@0 537 }
danielebarchiesi@0 538
danielebarchiesi@0 539 // Compares two values while ignoring undefined values.
danielebarchiesi@0 540 function compare (a, b) {
danielebarchiesi@0 541 return (a === b) ? (typeof a === 'undefined' ? a : true) : (typeof a === 'undefined' || typeof b === 'undefined');
danielebarchiesi@0 542 }
danielebarchiesi@0 543
danielebarchiesi@0 544 })(jQuery);