1(function ($) { 2 3/** 4 * The base States namespace. 5 * 6 * Having the local states variable allows us to use the States namespace 7 * without having to always declare "Drupal.states". 8 */ 9var states = Drupal.states = { 10 // An array of functions that should be postponed. 11 postponed: [] 12}; 13 14/** 15 * Attaches the states. 16 */ 17Drupal.behaviors.states = { 18 attach: function (context, settings) { 19 var $context = $(context); 20 for (var selector in settings.states) { 21 for (var state in settings.states[selector]) { 22 new states.Dependent({ 23 element: $context.find(selector), 24 state: states.State.sanitize(state), 25 constraints: settings.states[selector][state] 26 }); 27 } 28 } 29 30 // Execute all postponed functions now. 31 while (states.postponed.length) { 32 (states.postponed.shift())(); 33 } 34 } 35}; 36 37/** 38 * Object representing an element that depends on other elements. 39 * 40 * @param args 41 * Object with the following keys (all of which are required): 42 * - element: A jQuery object of the dependent element 43 * - state: A State object describing the state that is dependent 44 * - constraints: An object with dependency specifications. Lists all elements 45 * that this element depends on. It can be nested and can contain arbitrary 46 * AND and OR clauses. 47 */ 48states.Dependent = function (args) { 49 $.extend(this, { values: {}, oldValue: null }, args); 50 51 this.dependees = this.getDependees(); 52 for (var selector in this.dependees) { 53 this.initializeDependee(selector, this.dependees[selector]); 54 } 55}; 56 57/** 58 * Comparison functions for comparing the value of an element with the 59 * specification from the dependency settings. If the object type can't be 60 * found in this list, the === operator is used by default. 61 */ 62states.Dependent.comparisons = { 63 'RegExp': function (reference, value) { 64 return reference.test(value); 65 }, 66 'Function': function (reference, value) { 67 // The "reference" variable is a comparison function. 68 return reference(value); 69 }, 70 'Number': function (reference, value) { 71 // If "reference" is a number and "value" is a string, then cast reference 72 // as a string before applying the strict comparison in compare(). Otherwise 73 // numeric keys in the form's #states array fail to match string values 74 // returned from jQuery's val(). 75 return (typeof value === 'string') ? compare(reference.toString(), value) : compare(reference, value); 76 } 77}; 78 79states.Dependent.prototype = { 80 /** 81 * Initializes one of the elements this dependent depends on. 82 * 83 * @param selector 84 * The CSS selector describing the dependee. 85 * @param dependeeStates 86 * The list of states that have to be monitored for tracking the 87 * dependee's compliance status. 88 */ 89 initializeDependee: function (selector, dependeeStates) { 90 var state; 91 92 // Cache for the states of this dependee. 93 this.values[selector] = {}; 94 95 for (var i in dependeeStates) { 96 if (dependeeStates.hasOwnProperty(i)) { 97 state = dependeeStates[i]; 98 // Make sure we're not initializing this selector/state combination twice. 99 if ($.inArray(state, dependeeStates) === -1) { 100 continue; 101 } 102 103 state = states.State.sanitize(state); 104 105 // Initialize the value of this state. 106 this.values[selector][state.name] = null; 107 108 // Monitor state changes of the specified state for this dependee. 109 $(selector).bind('state:' + state, $.proxy(function (e) { 110 this.update(selector, state, e.value); 111 }, this)); 112 113 // Make sure the event we just bound ourselves to is actually fired. 114 new states.Trigger({ selector: selector, state: state }); 115 } 116 } 117 }, 118 119 /** 120 * Compares a value with a reference value. 121 * 122 * @param reference 123 * The value used for reference. 124 * @param selector 125 * CSS selector describing the dependee. 126 * @param state 127 * A State object describing the dependee's updated state. 128 * 129 * @return 130 * true or false. 131 */ 132 compare: function (reference, selector, state) { 133 var value = this.values[selector][state.name]; 134 if (reference.constructor.name in states.Dependent.comparisons) { 135 // Use a custom compare function for certain reference value types. 136 return states.Dependent.comparisons[reference.constructor.name](reference, value); 137 } 138 else { 139 // Do a plain comparison otherwise. 140 return compare(reference, value); 141 } 142 }, 143 144 /** 145 * Update the value of a dependee's state. 146 * 147 * @param selector 148 * CSS selector describing the dependee. 149 * @param state 150 * A State object describing the dependee's updated state. 151 * @param value 152 * The new value for the dependee's updated state. 153 */ 154 update: function (selector, state, value) { 155 // Only act when the 'new' value is actually new. 156 if (value !== this.values[selector][state.name]) { 157 this.values[selector][state.name] = value; 158 this.reevaluate(); 159 } 160 }, 161 162 /** 163 * Triggers change events in case a state changed. 164 */ 165 reevaluate: function () { 166 // Check whether any constraint for this dependent state is satisifed. 167 var value = this.verifyConstraints(this.constraints); 168 169 // Only invoke a state change event when the value actually changed. 170 if (value !== this.oldValue) { 171 // Store the new value so that we can compare later whether the value 172 // actually changed. 173 this.oldValue = value; 174 175 // Normalize the value to match the normalized state name. 176 value = invert(value, this.state.invert); 177 178 // By adding "trigger: true", we ensure that state changes don't go into 179 // infinite loops. 180 this.element.trigger({ type: 'state:' + this.state, value: value, trigger: true }); 181 } 182 }, 183 184 /** 185 * Evaluates child constraints to determine if a constraint is satisfied. 186 * 187 * @param constraints 188 * A constraint object or an array of constraints. 189 * @param selector 190 * The selector for these constraints. If undefined, there isn't yet a 191 * selector that these constraints apply to. In that case, the keys of the 192 * object are interpreted as the selector if encountered. 193 * 194 * @return 195 * true or false, depending on whether these constraints are satisfied. 196 */ 197 verifyConstraints: function(constraints, selector) { 198 var result; 199 if ($.isArray(constraints)) { 200 // This constraint is an array (OR or XOR). 201 var hasXor = $.inArray('xor', constraints) === -1; 202 for (var i = 0, len = constraints.length; i < len; i++) { 203 if (constraints[i] != 'xor') { 204 var constraint = this.checkConstraints(constraints[i], selector, i); 205 // Return if this is OR and we have a satisfied constraint or if this 206 // is XOR and we have a second satisfied constraint. 207 if (constraint && (hasXor || result)) { 208 return hasXor; 209 } 210 result = result || constraint; 211 } 212 } 213 } 214 // Make sure we don't try to iterate over things other than objects. This 215 // shouldn't normally occur, but in case the condition definition is bogus, 216 // we don't want to end up with an infinite loop. 217 else if ($.isPlainObject(constraints)) { 218 // This constraint is an object (AND). 219 for (var n in constraints) { 220 if (constraints.hasOwnProperty(n)) { 221 result = ternary(result, this.checkConstraints(constraints[n], selector, n)); 222 // False and anything else will evaluate to false, so return when any 223 // false condition is found. 224 if (result === false) { return false; } 225 } 226 } 227 } 228 return result; 229 }, 230 231 /** 232 * Checks whether the value matches the requirements for this constraint. 233 * 234 * @param value 235 * Either the value of a state or an array/object of constraints. In the 236 * latter case, resolving the constraint continues. 237 * @param selector 238 * The selector for this constraint. If undefined, there isn't yet a 239 * selector that this constraint applies to. In that case, the state key is 240 * propagates to a selector and resolving continues. 241 * @param state 242 * The state to check for this constraint. If undefined, resolving 243 * continues. 244 * If both selector and state aren't undefined and valid non-numeric 245 * strings, a lookup for the actual value of that selector's state is 246 * performed. This parameter is not a State object but a pristine state 247 * string. 248 * 249 * @return 250 * true or false, depending on whether this constraint is satisfied. 251 */ 252 checkConstraints: function(value, selector, state) { 253 // Normalize the last parameter. If it's non-numeric, we treat it either as 254 // a selector (in case there isn't one yet) or as a trigger/state. 255 if (typeof state !== 'string' || (/[0-9]/).test(state[0])) { 256 state = null; 257 } 258 else if (typeof selector === 'undefined') { 259 // Propagate the state to the selector when there isn't one yet. 260 selector = state; 261 state = null; 262 } 263 264 if (state !== null) { 265 // constraints is the actual constraints of an element to check for. 266 state = states.State.sanitize(state); 267 return invert(this.compare(value, selector, state), state.invert); 268 } 269 else { 270 // Resolve this constraint as an AND/OR operator. 271 return this.verifyConstraints(value, selector); 272 } 273 }, 274 275 /** 276 * Gathers information about all required triggers. 277 */ 278 getDependees: function() { 279 var cache = {}; 280 // Swivel the lookup function so that we can record all available selector- 281 // state combinations for initialization. 282 var _compare = this.compare; 283 this.compare = function(reference, selector, state) { 284 (cache[selector] || (cache[selector] = [])).push(state.name); 285 // Return nothing (=== undefined) so that the constraint loops are not 286 // broken. 287 }; 288 289 // This call doesn't actually verify anything but uses the resolving 290 // mechanism to go through the constraints array, trying to look up each 291 // value. Since we swivelled the compare function, this comparison returns 292 // undefined and lookup continues until the very end. Instead of lookup up 293 // the value, we record that combination of selector and state so that we 294 // can initialize all triggers. 295 this.verifyConstraints(this.constraints); 296 // Restore the original function. 297 this.compare = _compare; 298 299 return cache; 300 } 301}; 302 303states.Trigger = function (args) { 304 $.extend(this, args); 305 306 if (this.state in states.Trigger.states) { 307 this.element = $(this.selector); 308 309 // Only call the trigger initializer when it wasn't yet attached to this 310 // element. Otherwise we'd end up with duplicate events. 311 if (!this.element.data('trigger:' + this.state)) { 312 this.initialize(); 313 } 314 } 315}; 316 317states.Trigger.prototype = { 318 initialize: function () { 319 var trigger = states.Trigger.states[this.state]; 320 321 if (typeof trigger == 'function') { 322 // We have a custom trigger initialization function. 323 trigger.call(window, this.element); 324 } 325 else { 326 for (var event in trigger) { 327 if (trigger.hasOwnProperty(event)) {
328 this.defaultTrigger(event, trigger[event]); 329 } 330 } 331 } 332 333 // Mark this trigger as initialized for this element. 334 this.element.data('trigger:' + this.state, true); 335 }, 336 337 defaultTrigger: function (event, valueFn) { 338 var oldValue = valueFn.call(this.element); 339 340 // Attach the event callback. 341 this.element.bind(event, $.proxy(function (e) { 342 var value = valueFn.call(this.element, e); 343 // Only trigger the event if the value has actually changed. 344 if (oldValue !== value) { 345 this.element.trigger({ type: 'state:' + this.state, value: value, oldValue: oldValue }); 346 oldValue = value; 347 } 348 }, this)); 349 350 states.postponed.push($.proxy(function () { 351 // Trigger the event once for initialization purposes. 352 this.element.trigger({ type: 'state:' + this.state, value: oldValue, oldValue: null }); 353 }, this)); 354 } 355}; 356 357/** 358 * This list of states contains functions that are used to monitor the state 359 * of an element. Whenever an element depends on the state of another element, 360 * one of these trigger functions is added to the dependee so that the 361 * dependent element can be updated. 362 */ 363states.Trigger.states = { 364 // 'empty' describes the state to be monitored 365 empty: { 366 // 'keyup' is the (native DOM) event that we watch for. 367 'keyup': function () { 368 // The function associated to that trigger returns the new value for the 369 // state. 370 return this.val() == ''; 371 } 372 }, 373 374 checked: { 375 'change': function () { 376 return this.is(':checked'); 377 } 378 }, 379 380 // For radio buttons, only return the value if the radio button is selected. 381 value: { 382 'keyup': function () { 383 // Radio buttons share the same :input[name="key"] selector. 384 if (this.length > 1) { 385 // Initial checked value of radios is undefined, so we return false. 386 return this.filter(':checked').val() || false; 387 } 388 return this.val(); 389 }, 390 'change': function () { 391 // Radio buttons share the same :input[name="key"] selector. 392 if (this.length > 1) { 393 // Initial checked value of radios is undefined, so we return false. 394 return this.filter(':checked').val() || false; 395 } 396 return this.val(); 397 } 398 }, 399 400 collapsed: { 401 'collapsed': function(e) { 402 return (typeof e !== 'undefined' && 'value' in e) ? e.value : this.is('.collapsed'); 403 } 404 } 405}; 406 407 408/** 409 * A state object is used for describing the state and performing aliasing. 410 */ 411states.State = function(state) { 412 // We may need the original unresolved name later. 413 this.pristine = this.name = state; 414 415 // Normalize the state name. 416 while (true) { 417 // Iteratively remove exclamation marks and invert the value. 418 while (this.name.charAt(0) == '!') { 419 this.name = this.name.substring(1); 420 this.invert = !this.invert; 421 } 422 423 // Replace the state with its normalized name. 424 if (this.name in states.State.aliases) { 425 this.name = states.State.aliases[this.name]; 426 } 427 else { 428 break; 429 } 430 } 431}; 432 433/** 434 * Creates a new State object by sanitizing the passed value. 435 */ 436states.State.sanitize = function (state) { 437 if (state instanceof states.State) { 438 return state; 439 } 440 else { 441 return new states.State(state); 442 } 443}; 444 445/** 446 * This list of aliases is used to normalize states and associates negated names 447 * with their respective inverse state. 448 */ 449states.State.aliases = { 450 'enabled': '!disabled', 451 'invisible': '!visible', 452 'invalid': '!valid', 453 'untouched': '!touched', 454 'optional': '!required', 455 'filled': '!empty', 456 'unchecked': '!checked', 457 'irrelevant': '!relevant', 458 'expanded': '!collapsed', 459 'readwrite': '!readonly' 460}; 461 462states.State.prototype = { 463 invert: false, 464 465 /** 466 * Ensures that just using the state object returns the name. 467 */ 468 toString: function() { 469 return this.name; 470 } 471}; 472 473/** 474 * Global state change handlers. These are bound to "document" to cover all 475 * elements whose state changes. Events sent to elements within the page 476 * bubble up to these handlers. We use this system so that themes and modules 477 * can override these state change handlers for particular parts of a page. 478 */ 479$(document).bind('state:disabled', function(e) { 480 // Only act when this change was triggered by a dependency and not by the 481 // element monitoring itself. 482 if (e.trigger) { 483 $(e.target) 484 .attr('disabled', e.value) 485 .closest('.form-item, .form-submit, .form-wrapper').toggleClass('form-disabled', e.value) 486 .find('select, input, textarea').attr('disabled', e.value); 487 488 // Note: WebKit nightlies don't reflect that change correctly. 489 // See https://bugs.webkit.org/show_bug.cgi?id=23789 490 } 491}); 492 493$(document).bind('state:required', function(e) { 494 if (e.trigger) { 495 if (e.value) { 496 var $label = $(e.target).closest('.form-item, .form-wrapper').find('label'); 497 // Avoids duplicate required markers on initialization. 498 if (!$label.find('.form-required').length) { 499 $label.append('<span class="form-required">*</span>'); 500 } 501 } 502 else { 503 $(e.target).closest('.form-item, .form-wrapper').find('label .form-required').remove(); 504 } 505 } 506}); 507 508$(document).bind('state:visible', function(e) { 509 if (e.trigger) { 510 $(e.target).closest('.form-item, .form-submit, .form-wrapper').toggle(e.value); 511 } 512}); 513 514$(document).bind('state:checked', function(e) { 515 if (e.trigger) { 516 $(e.target).attr('checked', e.value); 517 } 518}); 519 520$(document).bind('state:collapsed', function(e) { 521 if (e.trigger) { 522 if ($(e.target).is('.collapsed') !== e.value) { 523 $('> legend a', e.target).click(); 524 } 525 } 526}); 527 528/** 529 * These are helper functions implementing addition "operators" and don't 530 * implement any logic that is particular to states. 531 */ 532 533// Bitwise AND with a third undefined state. 534function ternary (a, b) { 535 return typeof a === 'undefined' ? b : (typeof b === 'undefined' ? a : a && b); 536} 537 538// Inverts a (if it's not undefined) when invert is true. 539function invert (a, invert) { 540 return (invert && typeof a !== 'undefined') ? !a : a; 541} 542 543// Compares two values while ignoring undefined values. 544function compare (a, b) { 545 return (a === b) ? (typeof a === 'undefined' ? a : true) : (typeof a === 'undefined' || typeof b === 'undefined'); 546} 547 548})(jQuery); 549; 550(function ($) { 551 552/** 553 * Prevents consecutive form submissions of identical form values. 554 * 555 * Repetitive form submissions that would submit the identical form values are 556 * prevented, unless the form values are different from the previously 557 * submitted values. 558 * 559 * This is a simplified re-implementation of a user-agent behavior that should 560 * be natively supported by major web browsers, but at this time, only Firefox 561 * has a built-in protection. 562 * 563 * A form value-based approach ensures that the constraint is triggered for 564 * consecutive, identical form submissions only. Compared to that, a form 565 * button-based approach would (1) rely on [visible] buttons to exist where 566 * technically not required and (2) require more complex state management if 567 * there are multiple buttons in a form. 568 * 569 * This implementation is based on form-level submit events only and relies on 570 * jQuery's serialize() method to determine submitted form values. As such, the 571 * following limitations exist: 572 * 573 * - Event handlers on form buttons that preventDefault() do not receive a 574 * double-submit protection. That is deemed to be fine, since such button 575 * events typically trigger reversible client-side or server-side operations 576 * that are local to the context of a form only. 577 * - Changed values in advanced form controls, such as file inputs, are not part 578 * of the form values being compared between consecutive form submits (due to 579 * limitations of jQuery.serialize()). That is deemed to be acceptable, 580 * because if the user forgot to attach a file, then the size of HTTP payload 581 * will most likely be small enough to be fully passed to the server endpoint 582 * within (milli)seconds. If a user mistakenly attached a wrong file and is
583 * technically versed enough to cancel the form submission (and HTTP payload) 584 * in order to attach a different file, then that edge-case is not supported 585 * here. 586 * 587 * Lastly, all forms submitted via HTTP GET are idempotent by definition of HTTP 588 * standards, so excluded in this implementation. 589 */ 590Drupal.behaviors.formSingleSubmit = { 591 attach: function () { 592 function onFormSubmit (e) { 593 var $form = $(e.currentTarget); 594 var formValues = $form.serialize(); 595 var previousValues = $form.attr('data-drupal-form-submit-last'); 596 if (previousValues === formValues) { 597 e.preventDefault(); 598 } 599 else { 600 $form.attr('data-drupal-form-submit-last', formValues); 601 } 602 } 603 604 $('body').once('form-single-submit') 605 .delegate('form:not([method~="GET"])', 'submit.singleSubmit', onFormSubmit); 606 } 607}; 608 609})(jQuery); 610; 611(function ($) { 612 613/** 614 * Retrieves the summary for the first element. 615 */ 616$.fn.drupalGetSummary = function () { 617 var callback = this.data('summaryCallback'); 618 return (this[0] && callback) ? $.trim(callback(this[0])) : ''; 619}; 620 621/** 622 * Sets the summary for all matched elements. 623 * 624 * @param callback 625 * Either a function that will be called each time the summary is 626 * retrieved or a string (which is returned each time). 627 */ 628$.fn.drupalSetSummary = function (callback) { 629 var self = this; 630 631 // To facilitate things, the callback should always be a function. If it's 632 // not, we wrap it into an anonymous function which just returns the value. 633 if (typeof callback != 'function') { 634 var val = callback; 635 callback = function () { return val; }; 636 } 637 638 return this 639 .data('summaryCallback', callback) 640 // To prevent duplicate events, the handlers are first removed and then 641 // (re-)added. 642 .unbind('formUpdated.summary') 643 .bind('formUpdated.summary', function () { 644 self.trigger('summaryUpdated'); 645 }) 646 // The actual summaryUpdated handler doesn't fire when the callback is 647 // changed, so we have to do this manually. 648 .trigger('summaryUpdated'); 649}; 650 651/** 652 * Sends a 'formUpdated' event each time a form element is modified. 653 */ 654Drupal.behaviors.formUpdated = { 655 attach: function (context) { 656 // These events are namespaced so that we can remove them later. 657 var events = 'change.formUpdated click.formUpdated blur.formUpdated keyup.formUpdated'; 658 $(context) 659 // Since context could be an input element itself, it's added back to 660 // the jQuery object and filtered again. 661 .find(':input').andSelf().filter(':input') 662 // To prevent duplicate events, the handlers are first removed and then 663 // (re-)added. 664 .unbind(events).bind(events, function () { 665 $(this).trigger('formUpdated'); 666 }); 667 } 668}; 669 670/** 671 * Prepopulate form fields with information from the visitor cookie. 672 */ 673Drupal.behaviors.fillUserInfoFromCookie = { 674 attach: function (context, settings) { 675 $('form.user-info-from-cookie').once('user-info-from-cookie', function () { 676 var formContext = this; 677 $.each(['name', 'mail', 'homepage'], function () { 678 var $element = $('[name=' + this + ']', formContext); 679 var cookie = $.cookie('Drupal.visitor.' + this); 680 if ($element.length && cookie) { 681 $element.val(cookie); 682 } 683 }); 684 }); 685 } 686}; 687 688})(jQuery); 689;
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.