Projet

Général

Profil

Paste
Télécharger (28,1 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / webform / js / webform.js @ 389fb945

1
/**
2
 * @file
3
 * JavaScript behaviors for the front-end display of webforms.
4
 */
5

    
6
(function ($) {
7

    
8
  "use strict";
9

    
10
  Drupal.behaviors.webform = Drupal.behaviors.webform || {};
11

    
12
  Drupal.behaviors.webform.attach = function (context) {
13
    // Calendar datepicker behavior.
14
    Drupal.webform.datepicker(context);
15

    
16
    // Conditional logic.
17
    if (Drupal.settings.webform && Drupal.settings.webform.conditionals) {
18
      Drupal.webform.conditional(context);
19
    }
20
  };
21

    
22
  Drupal.webform = Drupal.webform || {};
23

    
24
  Drupal.webform.datepicker = function (context) {
25
    $('div.webform-datepicker').each(function () {
26
      var $webformDatepicker = $(this);
27
      var $calendar = $webformDatepicker.find('input.webform-calendar');
28

    
29
      // Ensure the page we're on actually contains a datepicker.
30
      if ($calendar.length == 0) {
31
        return;
32
      }
33

    
34
      var startDate = $calendar[0].className.replace(/.*webform-calendar-start-(\d{4}-\d{2}-\d{2}).*/, '$1').split('-');
35
      var endDate = $calendar[0].className.replace(/.*webform-calendar-end-(\d{4}-\d{2}-\d{2}).*/, '$1').split('-');
36
      var firstDay = $calendar[0].className.replace(/.*webform-calendar-day-(\d).*/, '$1');
37
      // Convert date strings into actual Date objects.
38
      startDate = new Date(startDate[0], startDate[1] - 1, startDate[2]);
39
      endDate = new Date(endDate[0], endDate[1] - 1, endDate[2]);
40

    
41
      // Ensure that start comes before end for datepicker.
42
      if (startDate > endDate) {
43
        var laterDate = startDate;
44
        startDate = endDate;
45
        endDate = laterDate;
46
      }
47

    
48
      var startYear = startDate.getFullYear();
49
      var endYear = endDate.getFullYear();
50

    
51
      // Set up the jQuery datepicker element.
52
      $calendar.datepicker({
53
        dateFormat: 'yy-mm-dd',
54
        yearRange: startYear + ':' + endYear,
55
        firstDay: parseInt(firstDay),
56
        minDate: startDate,
57
        maxDate: endDate,
58
        onSelect: function (dateText, inst) {
59
          var date = dateText.split('-');
60
          $webformDatepicker.find('select.year, input.year').val(+date[0]).trigger('change');
61
          $webformDatepicker.find('select.month').val(+date[1]).trigger('change');
62
          $webformDatepicker.find('select.day').val(+date[2]).trigger('change');
63
        },
64
        beforeShow: function (input, inst) {
65
          // Get the select list values.
66
          var year = $webformDatepicker.find('select.year, input.year').val();
67
          var month = $webformDatepicker.find('select.month').val();
68
          var day = $webformDatepicker.find('select.day').val();
69

    
70
          // If empty, default to the current year/month/day in the popup.
71
          var today = new Date();
72
          year = year ? year : today.getFullYear();
73
          month = month ? month : today.getMonth() + 1;
74
          day = day ? day : today.getDate();
75

    
76
          // Make sure that the default year fits in the available options.
77
          year = (year < startYear || year > endYear) ? startYear : year;
78

    
79
          // jQuery UI Datepicker will read the input field and base its date
80
          // off of that, even though in our case the input field is a button.
81
          $(input).val(year + '-' + month + '-' + day);
82
        }
83
      });
84

    
85
      // Prevent the calendar button from submitting the form.
86
      $calendar.click(function (event) {
87
        // This event is triggered also when pressing enter when the focus is on
88
        // previous webform components, but we only want to do something when
89
        // we are on the calendar component. By checking the event client x/y
90
        // position we known if it was the user clicking. For keyboard navigators
91
        // simply the focus handles the date picker so we don't have to do
92
        // anything special for them.
93
        if (event.clientX !== 0 && event.clientY !== 0) {
94
          // Focus is only necessary for Safari. But it has no impact on other
95
          // browsers.
96
          $(this).focus();
97
          event.preventDefault();
98
        }
99
      });
100

    
101
      // Clear date on backspace or delete.
102
      $calendar.keyup(function (e) {
103
        if (e.keyCode == 8 || e.keyCode == 46) {
104
          $.datepicker._clearDate(this);
105
        }
106
      });
107
    });
108
  };
109

    
110
  Drupal.webform.conditional = function (context) {
111
    // Add the bindings to each webform on the page.
112
    $.each(Drupal.settings.webform.conditionals, function (formKey, settings) {
113
      var $form = $('.' + formKey + ':not(.webform-conditional-processed)');
114
      $form.each(function (index, currentForm) {
115
        var $currentForm = $(currentForm);
116
        $currentForm.addClass('webform-conditional-processed');
117
        $currentForm.bind('change', {'settings': settings}, Drupal.webform.conditionalCheck);
118

    
119
        // Trigger all the elements that cause conditionals on this form.
120
        Drupal.webform.doConditions($currentForm, settings);
121
      });
122
    });
123
  };
124

    
125
  /**
126
   * Event handler to respond to field changes in a form.
127
   *
128
   * This event is bound to the entire form, not individual fields.
129
   */
130
  Drupal.webform.conditionalCheck = function (e) {
131
    var $triggerElement = $(e.target).closest('.webform-component');
132
    if (!$triggerElement.length) {
133
      return;
134
    }
135
    var $form = $triggerElement.closest('form');
136
    var triggerElementKey = $triggerElement.attr('class').match(/webform-component--[^ ]+/)[0];
137
    var settings = e.data.settings;
138
    if (settings.sourceMap[triggerElementKey]) {
139
      Drupal.webform.doConditions($form, settings);
140
    }
141
  };
142

    
143
  /**
144
   * Processes all conditional.
145
   */
146
  Drupal.webform.doConditions = function ($form, settings) {
147

    
148
    var stackPointer;
149
    var resultStack;
150

    
151
    /**
152
     * Initializes an execution stack for a conditional group's rules.
153
     *
154
     * Also initializes sub-conditional rules.
155
     */
156
    function executionStackInitialize(andor) {
157
      stackPointer = -1;
158
      resultStack = [];
159
      executionStackPush(andor);
160
    }
161

    
162
    /**
163
     * Starts a new subconditional for the given and/or operator.
164
     */
165
    function executionStackPush(andor) {
166
      resultStack[++stackPointer] = {
167
        results: [],
168
        andor: andor,
169
      };
170
    }
171

    
172
    /**
173
     * Adds a rule's result to the current sub-conditional.
174
     */
175
    function executionStackAccumulate(result) {
176
      resultStack[stackPointer]['results'].push(result);
177
    }
178

    
179
    /**
180
     * Finishes a sub-conditional and adds the result to the parent stack frame.
181
     */
182
    function executionStackPop() {
183
      // Calculate the and/or result.
184
      var stackFrame = resultStack[stackPointer];
185
      // Pop stack and protect against stack underflow.
186
      stackPointer = Math.max(0, stackPointer - 1);
187
      var $conditionalResults = stackFrame['results'];
188
      var filteredResults = $.map($conditionalResults, function (val) {
189
        return val ? val : null;
190
      });
191
      return stackFrame['andor'] === 'or'
192
                ? filteredResults.length > 0
193
                : filteredResults.length === $conditionalResults.length;
194
    }
195

    
196
    // Track what has been set/hidden for each target component's elements.
197
    // Hidden elements must be disabled because if they are required and don't
198
    // have a value, they will prevent submission due to html5 validation.
199
    // Each execution of the conditionals adds a temporary class
200
    // webform-disabled-flag so that elements hidden or set can be disabled and
201
    // also be prevented from being re-enabled by another conditional (such as a
202
    // parent fieldset). After processing conditionals, this temporary class
203
    // must be removed in preparation for the next execution of the
204
    // conditionals.
205
    $.each(settings.ruleGroups, function (rgid_key, rule_group) {
206
      var ruleGroup = settings.ruleGroups[rgid_key];
207

    
208
      // Perform the comparison callback and build the results for this group.
209
      executionStackInitialize(ruleGroup['andor']);
210
      $.each(ruleGroup['rules'], function (m, rule) {
211
        switch (rule['source_type']) {
212
          case 'component':
213
            var elementKey = rule['source'];
214
            var element = $form.find('.' + elementKey)[0];
215
            var existingValue = settings.values[elementKey] ? settings.values[elementKey] : null;
216
            executionStackAccumulate(window['Drupal']['webform'][rule.callback](element, existingValue, rule['value']));
217
            break;
218

    
219
          case 'conditional_start':
220
            executionStackPush(rule['andor']);
221
            break;
222

    
223
          case 'conditional_end':
224
            executionStackAccumulate(executionStackPop());
225
            break;
226
        }
227
      });
228
      var conditionalResult = executionStackPop();
229

    
230
      $.each(ruleGroup['actions'], function (aid, action) {
231
        var $target = $form.find('.' + action['target']);
232
        var actionResult = action['invert'] ? !conditionalResult : conditionalResult;
233
        switch (action['action']) {
234
          case 'show':
235
            var changed = actionResult != Drupal.webform.isVisible($target);
236
            if (actionResult) {
237
              $target.find('.webform-conditional-disabled:not(.webform-disabled-flag)')
238
                .removeClass('webform-conditional-disabled')
239
                .webformProp('disabled', false);
240
              $target
241
                .removeClass('webform-conditional-hidden')
242
                .show();
243
              $form.find('.chosen-disabled').prev().trigger('chosen:updated.chosen');
244
            }
245
            else {
246
              $target
247
                .hide()
248
                .addClass('webform-conditional-hidden')
249
                .find(':input')
250
                  .addClass('webform-conditional-disabled webform-disabled-flag')
251
                  .webformProp('disabled', true);
252
            }
253
            if (changed && $target.is('tr')) {
254
              Drupal.webform.restripeTable($target.closest('table').first());
255
            }
256
            break;
257

    
258
          case 'require':
259
            var $requiredSpan = $target.find('.form-required, .form-optional').first();
260
            if (actionResult != $requiredSpan.hasClass('form-required')) {
261
              var $targetInputElements = $target.find("input:text,textarea,input[type='email'],select,input:radio,input:checkbox,input:file");
262
              // Rather than hide the required tag, remove it so that other
263
              // jQuery can respond via Drupal behaviors.
264
              Drupal.detachBehaviors($requiredSpan);
265
              $targetInputElements
266
                .webformProp('required', actionResult)
267
                .toggleClass('required', actionResult);
268
              if (actionResult) {
269
                $requiredSpan.replaceWith('<span class="form-required" title="' + Drupal.t('This field is required.') + '">*</span>');
270
              }
271
              else {
272
                $requiredSpan.replaceWith('<span class="form-optional"></span>');
273
              }
274
              Drupal.attachBehaviors($requiredSpan);
275
            }
276
            break;
277

    
278
          case 'set':
279
            var $texts = $target.find("input:text,textarea,input[type='email']");
280
            var $selects = $target.find('select,select option,input:radio,input:checkbox');
281
            var $markups = $target.filter('.webform-component-markup');
282
            if (actionResult) {
283
              var multiple = $.map(action['argument'].split(','), $.trim);
284
              $selects
285
                .webformVal(multiple)
286
                .webformProp('disabled', true)
287
                  .addClass('webform-disabled-flag');
288
              $texts
289
                .val([action['argument']])
290
                .webformProp('readonly', true)
291
                .addClass('webform-disabled-flag');
292
              // A special case is made for markup. It is sanitized with
293
              // filter_xss_admin on the server. otherwise text() should be used
294
              // to avoid an XSS vulnerability. text() however would preclude
295
              // the use of tags like <strong> or <a>.
296
              $markups.html(action['argument']);
297
            }
298
            else {
299
              $selects.not('.webform-disabled-flag')
300
                .webformProp('disabled', false);
301
              $texts.not('.webform-disabled-flag')
302
                .webformProp('readonly', false);
303
              // Markup not set? Then restore original markup as provided in
304
              // the attribute data-webform-markup.
305
              $markups.each(function () {
306
                var $this = $(this);
307
                var original = $this.data('webform-markup');
308
                if (original !== undefined) {
309
                  $this.html(original);
310
                }
311
              });
312
            }
313
            break;
314
        }
315
      }); // End look on each action for one conditional.
316
    }); // End loop on each conditional.
317

    
318
    $form.find('.webform-disabled-flag').removeClass('webform-disabled-flag');
319
  };
320

    
321
  /**
322
   * Event handler to prevent propagation of events.
323
   *
324
   * Typically click for disabling radio and checkboxes.
325
   */
326
  Drupal.webform.stopEvent = function () {
327
    return false;
328
  };
329

    
330
  Drupal.webform.conditionalOperatorStringEqual = function (element, existingValue, ruleValue) {
331
    var returnValue = false;
332
    var currentValue = Drupal.webform.stringValue(element, existingValue);
333
    $.each(currentValue, function (n, value) {
334
      if (value.toLowerCase() === ruleValue.toLowerCase()) {
335
        returnValue = true;
336
        return false; // break.
337
      }
338
    });
339
    return returnValue;
340
  };
341

    
342
  Drupal.webform.conditionalOperatorStringNotEqual = function (element, existingValue, ruleValue) {
343
    var found = false;
344
    var currentValue = Drupal.webform.stringValue(element, existingValue);
345
    $.each(currentValue, function (n, value) {
346
      if (value.toLowerCase() === ruleValue.toLowerCase()) {
347
        found = true;
348
      }
349
    });
350
    return !found;
351
  };
352

    
353
  Drupal.webform.conditionalOperatorStringContains = function (element, existingValue, ruleValue) {
354
    var returnValue = false;
355
    var currentValue = Drupal.webform.stringValue(element, existingValue);
356
    $.each(currentValue, function (n, value) {
357
      if (value.toLowerCase().indexOf(ruleValue.toLowerCase()) > -1) {
358
        returnValue = true;
359
        return false; // break.
360
      }
361
    });
362
    return returnValue;
363
  };
364

    
365
  Drupal.webform.conditionalOperatorStringDoesNotContain = function (element, existingValue, ruleValue) {
366
    var found = false;
367
    var currentValue = Drupal.webform.stringValue(element, existingValue);
368
    $.each(currentValue, function (n, value) {
369
      if (value.toLowerCase().indexOf(ruleValue.toLowerCase()) > -1) {
370
        found = true;
371
      }
372
    });
373
    return !found;
374
  };
375

    
376
  Drupal.webform.conditionalOperatorStringBeginsWith = function (element, existingValue, ruleValue) {
377
    var returnValue = false;
378
    var currentValue = Drupal.webform.stringValue(element, existingValue);
379
    $.each(currentValue, function (n, value) {
380
      if (value.toLowerCase().indexOf(ruleValue.toLowerCase()) === 0) {
381
        returnValue = true;
382
        return false; // break.
383
      }
384
    });
385
    return returnValue;
386
  };
387

    
388
  Drupal.webform.conditionalOperatorStringEndsWith = function (element, existingValue, ruleValue) {
389
    var returnValue = false;
390
    var currentValue = Drupal.webform.stringValue(element, existingValue);
391
    $.each(currentValue, function (n, value) {
392
      if (value.toLowerCase().lastIndexOf(ruleValue.toLowerCase()) === value.length - ruleValue.length) {
393
        returnValue = true;
394
        return false; // break.
395
      }
396
    });
397
    return returnValue;
398
  };
399

    
400
  Drupal.webform.conditionalOperatorStringEmpty = function (element, existingValue, ruleValue) {
401
    var currentValue = Drupal.webform.stringValue(element, existingValue);
402
    var returnValue = true;
403
    $.each(currentValue, function (n, value) {
404
      if (value !== '') {
405
        returnValue = false;
406
        return false; // break.
407
      }
408
    });
409
    return returnValue;
410
  };
411

    
412
  Drupal.webform.conditionalOperatorStringNotEmpty = function (element, existingValue, ruleValue) {
413
    return !Drupal.webform.conditionalOperatorStringEmpty(element, existingValue, ruleValue);
414
  };
415

    
416
  Drupal.webform.conditionalOperatorSelectGreaterThan = function (element, existingValue, ruleValue) {
417
    var currentValue = Drupal.webform.stringValue(element, existingValue);
418
    return Drupal.webform.compare_select(currentValue[0], ruleValue, element) > 0;
419
  };
420

    
421
  Drupal.webform.conditionalOperatorSelectGreaterThanEqual = function (element, existingValue, ruleValue) {
422
    var currentValue = Drupal.webform.stringValue(element, existingValue);
423
    var comparison = Drupal.webform.compare_select(currentValue[0], ruleValue, element);
424
    return comparison > 0 || comparison === 0;
425
  };
426

    
427
  Drupal.webform.conditionalOperatorSelectLessThan = function (element, existingValue, ruleValue) {
428
    var currentValue = Drupal.webform.stringValue(element, existingValue);
429
    return Drupal.webform.compare_select(currentValue[0], ruleValue, element) < 0;
430
  };
431

    
432
  Drupal.webform.conditionalOperatorSelectLessThanEqual = function (element, existingValue, ruleValue) {
433
    var currentValue = Drupal.webform.stringValue(element, existingValue);
434
    var comparison = Drupal.webform.compare_select(currentValue[0], ruleValue, element);
435
    return comparison < 0 || comparison === 0;
436
  };
437

    
438
  Drupal.webform.conditionalOperatorNumericEqual = function (element, existingValue, ruleValue) {
439
    // See float comparison: http://php.net/manual/en/language.types.float.php
440
    var currentValue = Drupal.webform.stringValue(element, existingValue);
441
    var epsilon = 0.000001;
442
    // An empty string does not match any number.
443
    return currentValue[0] === '' ? false : (Math.abs(parseFloat(currentValue[0]) - parseFloat(ruleValue)) < epsilon);
444
  };
445

    
446
  Drupal.webform.conditionalOperatorNumericNotEqual = function (element, existingValue, ruleValue) {
447
    // See float comparison: http://php.net/manual/en/language.types.float.php
448
    var currentValue = Drupal.webform.stringValue(element, existingValue);
449
    var epsilon = 0.000001;
450
    // An empty string does not match any number.
451
    return currentValue[0] === '' ? true : (Math.abs(parseFloat(currentValue[0]) - parseFloat(ruleValue)) >= epsilon);
452
  };
453

    
454
  Drupal.webform.conditionalOperatorNumericGreaterThan = function (element, existingValue, ruleValue) {
455
    var currentValue = Drupal.webform.stringValue(element, existingValue);
456
    return parseFloat(currentValue[0]) > parseFloat(ruleValue);
457
  };
458

    
459
  Drupal.webform.conditionalOperatorNumericGreaterThanEqual = function (element, existingValue, ruleValue) {
460
    return Drupal.webform.conditionalOperatorNumericGreaterThan(element, existingValue, ruleValue) ||
461
           Drupal.webform.conditionalOperatorNumericEqual(element, existingValue, ruleValue);
462
  };
463

    
464
  Drupal.webform.conditionalOperatorNumericLessThan = function (element, existingValue, ruleValue) {
465
    var currentValue = Drupal.webform.stringValue(element, existingValue);
466
    return parseFloat(currentValue[0]) < parseFloat(ruleValue);
467
  };
468

    
469
  Drupal.webform.conditionalOperatorNumericLessThanEqual = function (element, existingValue, ruleValue) {
470
    return Drupal.webform.conditionalOperatorNumericLessThan(element, existingValue, ruleValue) ||
471
           Drupal.webform.conditionalOperatorNumericEqual(element, existingValue, ruleValue);
472
  };
473

    
474
  Drupal.webform.conditionalOperatorDateEqual = function (element, existingValue, ruleValue) {
475
    var currentValue = Drupal.webform.dateValue(element, existingValue);
476
    return currentValue === ruleValue;
477
  };
478

    
479
  Drupal.webform.conditionalOperatorDateNotEqual = function (element, existingValue, ruleValue) {
480
    return !Drupal.webform.conditionalOperatorDateEqual(element, existingValue, ruleValue);
481
  };
482

    
483
  Drupal.webform.conditionalOperatorDateBefore = function (element, existingValue, ruleValue) {
484
    var currentValue = Drupal.webform.dateValue(element, existingValue);
485
    return (currentValue !== false) && currentValue < ruleValue;
486
  };
487

    
488
  Drupal.webform.conditionalOperatorDateBeforeEqual = function (element, existingValue, ruleValue) {
489
    var currentValue = Drupal.webform.dateValue(element, existingValue);
490
    return (currentValue !== false) && (currentValue < ruleValue || currentValue === ruleValue);
491
  };
492

    
493
  Drupal.webform.conditionalOperatorDateAfter = function (element, existingValue, ruleValue) {
494
    var currentValue = Drupal.webform.dateValue(element, existingValue);
495
    return (currentValue !== false) && currentValue > ruleValue;
496
  };
497

    
498
  Drupal.webform.conditionalOperatorDateAfterEqual = function (element, existingValue, ruleValue) {
499
    var currentValue = Drupal.webform.dateValue(element, existingValue);
500
    return (currentValue !== false) && (currentValue > ruleValue || currentValue === ruleValue);
501
  };
502

    
503
  Drupal.webform.conditionalOperatorTimeEqual = function (element, existingValue, ruleValue) {
504
    var currentValue = Drupal.webform.timeValue(element, existingValue);
505
    return currentValue === ruleValue;
506
  };
507

    
508
  Drupal.webform.conditionalOperatorTimeNotEqual = function (element, existingValue, ruleValue) {
509
    return !Drupal.webform.conditionalOperatorTimeEqual(element, existingValue, ruleValue);
510
  };
511

    
512
  Drupal.webform.conditionalOperatorTimeBefore = function (element, existingValue, ruleValue) {
513
    // Date and time operators intentionally exclusive for "before".
514
    var currentValue = Drupal.webform.timeValue(element, existingValue);
515
    return (currentValue !== false) && (currentValue < ruleValue);
516
  };
517

    
518
  Drupal.webform.conditionalOperatorTimeBeforeEqual = function (element, existingValue, ruleValue) {
519
    // Date and time operators intentionally exclusive for "before".
520
    var currentValue = Drupal.webform.timeValue(element, existingValue);
521
    return (currentValue !== false) && (currentValue < ruleValue || currentValue === ruleValue);
522
  };
523

    
524
  Drupal.webform.conditionalOperatorTimeAfter = function (element, existingValue, ruleValue) {
525
    // Date and time operators intentionally inclusive for "after".
526
    var currentValue = Drupal.webform.timeValue(element, existingValue);
527
    return (currentValue !== false) && (currentValue > ruleValue);
528
  };
529

    
530
  Drupal.webform.conditionalOperatorTimeAfterEqual = function (element, existingValue, ruleValue) {
531
    // Date and time operators intentionally inclusive for "after".
532
    var currentValue = Drupal.webform.timeValue(element, existingValue);
533
    return (currentValue !== false) && (currentValue > ruleValue || currentValue === ruleValue);
534
  };
535

    
536
  /**
537
   * Utility function to compare values of a select component.
538
   *
539
   * @param string a
540
   *   First select option key to compare
541
   * @param string b
542
   *   Second select option key to compare
543
   * @param array options
544
   *   Associative array where the a and b are within the keys
545
   *
546
   * @return integer based upon position of $a and $b in $options
547
   *   -N if $a above (<) $b
548
   *   0 if $a = $b
549
   *   +N if $a is below (>) $b
550
   */
551
  Drupal.webform.compare_select = function (a, b, element) {
552
    var optionList = [];
553
    $('option,input:radio,input:checkbox', element).each(function () {
554
      optionList.push($(this).val());
555
    });
556
    var a_position = optionList.indexOf(a);
557
    var b_position = optionList.indexOf(b);
558
    return (a_position < 0 || b_position < 0) ? null : a_position - b_position;
559
  };
560

    
561
  /**
562
   * Utility to return current visibility.
563
   *
564
   * Uses actual visibility, except for hidden components which use the applied
565
   * disabled class.
566
   */
567
  Drupal.webform.isVisible = function ($element) {
568
    return $element.hasClass('webform-component-hidden')
569
              ? !$element.find('input').first().hasClass('webform-conditional-disabled')
570
              : $element.closest('.webform-conditional-hidden').length == 0;
571
  };
572

    
573
  /**
574
   * Function to get a string value from a select/radios/text/etc. field.
575
   */
576
  Drupal.webform.stringValue = function (element, existingValue) {
577
    var value = [];
578
    if (element) {
579
      var $element = $(element);
580
      if (Drupal.webform.isVisible($element)) {
581
        // Checkboxes and radios.
582
        $element.find('input[type=checkbox]:checked,input[type=radio]:checked').each(function () {
583
          value.push(this.value);
584
        });
585
        // Select lists.
586
        if (!value.length) {
587
          var selectValue = $element.find('select').val();
588
          if (selectValue) {
589
            if ($.isArray(selectValue)) {
590
              value = selectValue;
591
            }
592
            else {
593
              value.push(selectValue);
594
            }
595
          }
596
        }
597
        // Simple text fields. This check is done last so that the select list
598
        // in select-or-other fields comes before the "other" text field.
599
        if (!value.length) {
600
          $element.find('input:not([type=checkbox],[type=radio]),textarea').each(function () {
601
            value.push(this.value);
602
          });
603
        }
604
      }
605
    }
606
    else {
607
      switch ($.type(existingValue)) {
608
        case 'array':
609
          value = existingValue;
610
          break;
611

    
612
        case 'string':
613
          value.push(existingValue);
614
          break;
615
      }
616
    }
617
    return value;
618
  };
619

    
620
  /**
621
   * Utility function to calculate a second-based timestamp from a time field.
622
   */
623
  Drupal.webform.dateValue = function (element, existingValue) {
624
    var value = false;
625
    if (element) {
626
      var $element = $(element);
627
      if (Drupal.webform.isVisible($element)) {
628
        var day = $element.find('[name*=day]').val();
629
        var month = $element.find('[name*=month]').val();
630
        var year = $element.find('[name*=year]').val();
631
        // Months are 0 indexed in JavaScript.
632
        if (month) {
633
          month--;
634
        }
635
        if (year !== '' && month !== '' && day !== '') {
636
          value = Date.UTC(year, month, day) / 1000;
637
        }
638
      }
639
    }
640
    else {
641
      if ($.type(existingValue) === 'array' && existingValue.length) {
642
        existingValue = existingValue[0];
643
      }
644
      if ($.type(existingValue) === 'string') {
645
        existingValue = existingValue.split('-');
646
      }
647
      if (existingValue.length === 3) {
648
        value = Date.UTC(existingValue[0], existingValue[1], existingValue[2]) / 1000;
649
      }
650
    }
651
    return value;
652
  };
653

    
654
  /**
655
   * Utility function to calculate a millisecond timestamp from a time field.
656
   */
657
  Drupal.webform.timeValue = function (element, existingValue) {
658
    var value = false;
659
    if (element) {
660
      var $element = $(element);
661
      if (Drupal.webform.isVisible($element)) {
662
        var hour = $element.find('[name*=hour]').val();
663
        var minute = $element.find('[name*=minute]').val();
664
        var ampm = $element.find('[name*=ampm]:checked').val();
665

    
666
        // Convert to integers if set.
667
        hour = (hour === '') ? hour : parseInt(hour);
668
        minute = (minute === '') ? minute : parseInt(minute);
669

    
670
        if (hour !== '') {
671
          hour = (hour < 12 && ampm == 'pm') ? hour + 12 : hour;
672
          hour = (hour === 12 && ampm == 'am') ? 0 : hour;
673
        }
674
        if (hour !== '' && minute !== '') {
675
          value = Date.UTC(1970, 0, 1, hour, minute) / 1000;
676
        }
677
      }
678
    }
679
    else {
680
      if ($.type(existingValue) === 'array' && existingValue.length) {
681
        existingValue = existingValue[0];
682
      }
683
      if ($.type(existingValue) === 'string') {
684
        existingValue = existingValue.split(':');
685
      }
686
      if (existingValue.length >= 2) {
687
        value = Date.UTC(1970, 0, 1, existingValue[0], existingValue[1]) / 1000;
688
      }
689
    }
690
    return value;
691
  };
692

    
693
  /**
694
   * Make a prop shim for jQuery < 1.9.
695
   */
696
  $.fn.webformProp = $.fn.webformProp || function (name, value) {
697
    if (value) {
698
      return $.fn.prop ? this.prop(name, true) : this.attr(name, true);
699
    }
700
    else {
701
      return $.fn.prop ? this.prop(name, false) : this.removeAttr(name);
702
    }
703
  };
704

    
705
  /**
706
   * Make a multi-valued val() function.
707
   *
708
   * This is for setting checkboxes, radios, and select elements.
709
   */
710
  $.fn.webformVal = function (values) {
711
    this.each(function () {
712
      var $this = $(this);
713
      var value = $this.val();
714
      var on = $.inArray($this.val(), values) != -1;
715
      if (this.nodeName == 'OPTION') {
716
        $this.webformProp('selected', on ? value : false);
717
      }
718
      else {
719
        $this.val(on ? [value] : false);
720
      }
721
    });
722
    return this;
723
  };
724

    
725
  /**
726
   * Given a table's DOM element, restripe the odd/even classes.
727
   */
728
  Drupal.webform.restripeTable = function (table) {
729
    // :even and :odd are reversed because jQuery counts from 0 and
730
    // we count from 1, so we're out of sync.
731
    // Match immediate children of the parent element to allow nesting.
732
    $('> tbody > tr, > tr', table)
733
      .filter(':visible:odd').filter('.odd')
734
        .removeClass('odd').addClass('even')
735
      .end().end()
736
      .filter(':visible:even').filter('.even')
737
        .removeClass('even').addClass('odd');
738
  };
739

    
740
})(jQuery);