webform.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740
  1. /**
  2. * @file
  3. * JavaScript behaviors for the front-end display of webforms.
  4. */
  5. (function ($) {
  6. "use strict";
  7. Drupal.behaviors.webform = Drupal.behaviors.webform || {};
  8. Drupal.behaviors.webform.attach = function (context) {
  9. // Calendar datepicker behavior.
  10. Drupal.webform.datepicker(context);
  11. // Conditional logic.
  12. if (Drupal.settings.webform && Drupal.settings.webform.conditionals) {
  13. Drupal.webform.conditional(context);
  14. }
  15. };
  16. Drupal.webform = Drupal.webform || {};
  17. Drupal.webform.datepicker = function (context) {
  18. $('div.webform-datepicker').each(function () {
  19. var $webformDatepicker = $(this);
  20. var $calendar = $webformDatepicker.find('input.webform-calendar');
  21. // Ensure the page we're on actually contains a datepicker.
  22. if ($calendar.length == 0) {
  23. return;
  24. }
  25. var startDate = $calendar[0].className.replace(/.*webform-calendar-start-(\d{4}-\d{2}-\d{2}).*/, '$1').split('-');
  26. var endDate = $calendar[0].className.replace(/.*webform-calendar-end-(\d{4}-\d{2}-\d{2}).*/, '$1').split('-');
  27. var firstDay = $calendar[0].className.replace(/.*webform-calendar-day-(\d).*/, '$1');
  28. // Convert date strings into actual Date objects.
  29. startDate = new Date(startDate[0], startDate[1] - 1, startDate[2]);
  30. endDate = new Date(endDate[0], endDate[1] - 1, endDate[2]);
  31. // Ensure that start comes before end for datepicker.
  32. if (startDate > endDate) {
  33. var laterDate = startDate;
  34. startDate = endDate;
  35. endDate = laterDate;
  36. }
  37. var startYear = startDate.getFullYear();
  38. var endYear = endDate.getFullYear();
  39. // Set up the jQuery datepicker element.
  40. $calendar.datepicker({
  41. dateFormat: 'yy-mm-dd',
  42. yearRange: startYear + ':' + endYear,
  43. firstDay: parseInt(firstDay),
  44. minDate: startDate,
  45. maxDate: endDate,
  46. onSelect: function (dateText, inst) {
  47. var date = dateText.split('-');
  48. $webformDatepicker.find('select.year, input.year').val(+date[0]).trigger('change');
  49. $webformDatepicker.find('select.month').val(+date[1]).trigger('change');
  50. $webformDatepicker.find('select.day').val(+date[2]).trigger('change');
  51. },
  52. beforeShow: function (input, inst) {
  53. // Get the select list values.
  54. var year = $webformDatepicker.find('select.year, input.year').val();
  55. var month = $webformDatepicker.find('select.month').val();
  56. var day = $webformDatepicker.find('select.day').val();
  57. // If empty, default to the current year/month/day in the popup.
  58. var today = new Date();
  59. year = year ? year : today.getFullYear();
  60. month = month ? month : today.getMonth() + 1;
  61. day = day ? day : today.getDate();
  62. // Make sure that the default year fits in the available options.
  63. year = (year < startYear || year > endYear) ? startYear : year;
  64. // jQuery UI Datepicker will read the input field and base its date
  65. // off of that, even though in our case the input field is a button.
  66. $(input).val(year + '-' + month + '-' + day);
  67. }
  68. });
  69. // Prevent the calendar button from submitting the form.
  70. $calendar.click(function (event) {
  71. // This event is triggered also when pressing enter when the focus is on
  72. // previous webform components, but we only want to do something when
  73. // we are on the calendar component. By checking the event client x/y
  74. // position we known if it was the user clicking. For keyboard navigators
  75. // simply the focus handles the date picker so we don't have to do
  76. // anything special for them.
  77. if (event.clientX !== 0 && event.clientY !== 0) {
  78. // Focus is only necessary for Safari. But it has no impact on other
  79. // browsers.
  80. $(this).focus();
  81. event.preventDefault();
  82. }
  83. });
  84. // Clear date on backspace or delete.
  85. $calendar.keyup(function (e) {
  86. if (e.keyCode == 8 || e.keyCode == 46) {
  87. $.datepicker._clearDate(this);
  88. }
  89. });
  90. });
  91. };
  92. Drupal.webform.conditional = function (context) {
  93. // Add the bindings to each webform on the page.
  94. $.each(Drupal.settings.webform.conditionals, function (formKey, settings) {
  95. var $form = $('.' + formKey + ':not(.webform-conditional-processed)');
  96. $form.each(function (index, currentForm) {
  97. var $currentForm = $(currentForm);
  98. $currentForm.addClass('webform-conditional-processed');
  99. $currentForm.bind('change', {'settings': settings}, Drupal.webform.conditionalCheck);
  100. // Trigger all the elements that cause conditionals on this form.
  101. Drupal.webform.doConditions($currentForm, settings);
  102. });
  103. });
  104. };
  105. /**
  106. * Event handler to respond to field changes in a form.
  107. *
  108. * This event is bound to the entire form, not individual fields.
  109. */
  110. Drupal.webform.conditionalCheck = function (e) {
  111. var $triggerElement = $(e.target).closest('.webform-component');
  112. if (!$triggerElement.length) {
  113. return;
  114. }
  115. var $form = $triggerElement.closest('form');
  116. var triggerElementKey = $triggerElement.attr('class').match(/webform-component--[^ ]+/)[0];
  117. var settings = e.data.settings;
  118. if (settings.sourceMap[triggerElementKey]) {
  119. Drupal.webform.doConditions($form, settings);
  120. }
  121. };
  122. /**
  123. * Processes all conditional.
  124. */
  125. Drupal.webform.doConditions = function ($form, settings) {
  126. var stackPointer;
  127. var resultStack;
  128. /**
  129. * Initializes an execution stack for a conditional group's rules.
  130. *
  131. * Also initializes sub-conditional rules.
  132. */
  133. function executionStackInitialize(andor) {
  134. stackPointer = -1;
  135. resultStack = [];
  136. executionStackPush(andor);
  137. }
  138. /**
  139. * Starts a new subconditional for the given and/or operator.
  140. */
  141. function executionStackPush(andor) {
  142. resultStack[++stackPointer] = {
  143. results: [],
  144. andor: andor,
  145. };
  146. }
  147. /**
  148. * Adds a rule's result to the current sub-conditional.
  149. */
  150. function executionStackAccumulate(result) {
  151. resultStack[stackPointer]['results'].push(result);
  152. }
  153. /**
  154. * Finishes a sub-conditional and adds the result to the parent stack frame.
  155. */
  156. function executionStackPop() {
  157. // Calculate the and/or result.
  158. var stackFrame = resultStack[stackPointer];
  159. // Pop stack and protect against stack underflow.
  160. stackPointer = Math.max(0, stackPointer - 1);
  161. var $conditionalResults = stackFrame['results'];
  162. var filteredResults = $.map($conditionalResults, function (val) {
  163. return val ? val : null;
  164. });
  165. return stackFrame['andor'] === 'or'
  166. ? filteredResults.length > 0
  167. : filteredResults.length === $conditionalResults.length;
  168. }
  169. // Track what has been set/hidden for each target component's elements.
  170. // Hidden elements must be disabled because if they are required and don't
  171. // have a value, they will prevent submission due to html5 validation.
  172. // Each execution of the conditionals adds a temporary class
  173. // webform-disabled-flag so that elements hidden or set can be disabled and
  174. // also be prevented from being re-enabled by another conditional (such as a
  175. // parent fieldset). After processing conditionals, this temporary class
  176. // must be removed in preparation for the next execution of the
  177. // conditionals.
  178. $.each(settings.ruleGroups, function (rgid_key, rule_group) {
  179. var ruleGroup = settings.ruleGroups[rgid_key];
  180. // Perform the comparison callback and build the results for this group.
  181. executionStackInitialize(ruleGroup['andor']);
  182. $.each(ruleGroup['rules'], function (m, rule) {
  183. switch (rule['source_type']) {
  184. case 'component':
  185. var elementKey = rule['source'];
  186. var element = $form.find('.' + elementKey)[0];
  187. var existingValue = settings.values[elementKey] ? settings.values[elementKey] : null;
  188. executionStackAccumulate(window['Drupal']['webform'][rule.callback](element, existingValue, rule['value']));
  189. break;
  190. case 'conditional_start':
  191. executionStackPush(rule['andor']);
  192. break;
  193. case 'conditional_end':
  194. executionStackAccumulate(executionStackPop());
  195. break;
  196. }
  197. });
  198. var conditionalResult = executionStackPop();
  199. $.each(ruleGroup['actions'], function (aid, action) {
  200. var $target = $form.find('.' + action['target']);
  201. var actionResult = action['invert'] ? !conditionalResult : conditionalResult;
  202. switch (action['action']) {
  203. case 'show':
  204. var changed = actionResult != Drupal.webform.isVisible($target);
  205. if (actionResult) {
  206. $target.find('.webform-conditional-disabled:not(.webform-disabled-flag)')
  207. .removeClass('webform-conditional-disabled')
  208. .webformProp('disabled', false);
  209. $target
  210. .removeClass('webform-conditional-hidden')
  211. .show();
  212. $form.find('.chosen-disabled').prev().trigger('chosen:updated.chosen');
  213. }
  214. else {
  215. $target
  216. .hide()
  217. .addClass('webform-conditional-hidden')
  218. .find(':input')
  219. .addClass('webform-conditional-disabled webform-disabled-flag')
  220. .webformProp('disabled', true);
  221. }
  222. if (changed && $target.is('tr')) {
  223. Drupal.webform.restripeTable($target.closest('table').first());
  224. }
  225. break;
  226. case 'require':
  227. var $requiredSpan = $target.find('.form-required, .form-optional').first();
  228. if (actionResult != $requiredSpan.hasClass('form-required')) {
  229. var $targetInputElements = $target.find("input:text,textarea,input[type='email'],select,input:radio,input:checkbox,input:file");
  230. // Rather than hide the required tag, remove it so that other
  231. // jQuery can respond via Drupal behaviors.
  232. Drupal.detachBehaviors($requiredSpan);
  233. $targetInputElements
  234. .webformProp('required', actionResult)
  235. .toggleClass('required', actionResult);
  236. if (actionResult) {
  237. $requiredSpan.replaceWith('<span class="form-required" title="' + Drupal.t('This field is required.') + '">*</span>');
  238. }
  239. else {
  240. $requiredSpan.replaceWith('<span class="form-optional"></span>');
  241. }
  242. Drupal.attachBehaviors($requiredSpan);
  243. }
  244. break;
  245. case 'set':
  246. var $texts = $target.find("input:text,textarea,input[type='email']");
  247. var $selects = $target.find('select,select option,input:radio,input:checkbox');
  248. var $markups = $target.filter('.webform-component-markup');
  249. if (actionResult) {
  250. var multiple = $.map(action['argument'].split(','), $.trim);
  251. $selects
  252. .webformVal(multiple)
  253. .webformProp('disabled', true)
  254. .addClass('webform-disabled-flag');
  255. $texts
  256. .val([action['argument']])
  257. .webformProp('readonly', true)
  258. .addClass('webform-disabled-flag');
  259. // A special case is made for markup. It is sanitized with
  260. // filter_xss_admin on the server. otherwise text() should be used
  261. // to avoid an XSS vulnerability. text() however would preclude
  262. // the use of tags like <strong> or <a>.
  263. $markups.html(action['argument']);
  264. }
  265. else {
  266. $selects.not('.webform-disabled-flag')
  267. .webformProp('disabled', false);
  268. $texts.not('.webform-disabled-flag')
  269. .webformProp('readonly', false);
  270. // Markup not set? Then restore original markup as provided in
  271. // the attribute data-webform-markup.
  272. $markups.each(function () {
  273. var $this = $(this);
  274. var original = $this.data('webform-markup');
  275. if (original !== undefined) {
  276. $this.html(original);
  277. }
  278. });
  279. }
  280. break;
  281. }
  282. }); // End look on each action for one conditional.
  283. }); // End loop on each conditional.
  284. $form.find('.webform-disabled-flag').removeClass('webform-disabled-flag');
  285. };
  286. /**
  287. * Event handler to prevent propagation of events.
  288. *
  289. * Typically click for disabling radio and checkboxes.
  290. */
  291. Drupal.webform.stopEvent = function () {
  292. return false;
  293. };
  294. Drupal.webform.conditionalOperatorStringEqual = function (element, existingValue, ruleValue) {
  295. var returnValue = false;
  296. var currentValue = Drupal.webform.stringValue(element, existingValue);
  297. $.each(currentValue, function (n, value) {
  298. if (value.toLowerCase() === ruleValue.toLowerCase()) {
  299. returnValue = true;
  300. return false; // break.
  301. }
  302. });
  303. return returnValue;
  304. };
  305. Drupal.webform.conditionalOperatorStringNotEqual = function (element, existingValue, ruleValue) {
  306. var found = false;
  307. var currentValue = Drupal.webform.stringValue(element, existingValue);
  308. $.each(currentValue, function (n, value) {
  309. if (value.toLowerCase() === ruleValue.toLowerCase()) {
  310. found = true;
  311. }
  312. });
  313. return !found;
  314. };
  315. Drupal.webform.conditionalOperatorStringContains = function (element, existingValue, ruleValue) {
  316. var returnValue = false;
  317. var currentValue = Drupal.webform.stringValue(element, existingValue);
  318. $.each(currentValue, function (n, value) {
  319. if (value.toLowerCase().indexOf(ruleValue.toLowerCase()) > -1) {
  320. returnValue = true;
  321. return false; // break.
  322. }
  323. });
  324. return returnValue;
  325. };
  326. Drupal.webform.conditionalOperatorStringDoesNotContain = function (element, existingValue, ruleValue) {
  327. var found = false;
  328. var currentValue = Drupal.webform.stringValue(element, existingValue);
  329. $.each(currentValue, function (n, value) {
  330. if (value.toLowerCase().indexOf(ruleValue.toLowerCase()) > -1) {
  331. found = true;
  332. }
  333. });
  334. return !found;
  335. };
  336. Drupal.webform.conditionalOperatorStringBeginsWith = function (element, existingValue, ruleValue) {
  337. var returnValue = false;
  338. var currentValue = Drupal.webform.stringValue(element, existingValue);
  339. $.each(currentValue, function (n, value) {
  340. if (value.toLowerCase().indexOf(ruleValue.toLowerCase()) === 0) {
  341. returnValue = true;
  342. return false; // break.
  343. }
  344. });
  345. return returnValue;
  346. };
  347. Drupal.webform.conditionalOperatorStringEndsWith = function (element, existingValue, ruleValue) {
  348. var returnValue = false;
  349. var currentValue = Drupal.webform.stringValue(element, existingValue);
  350. $.each(currentValue, function (n, value) {
  351. if (value.toLowerCase().lastIndexOf(ruleValue.toLowerCase()) === value.length - ruleValue.length) {
  352. returnValue = true;
  353. return false; // break.
  354. }
  355. });
  356. return returnValue;
  357. };
  358. Drupal.webform.conditionalOperatorStringEmpty = function (element, existingValue, ruleValue) {
  359. var currentValue = Drupal.webform.stringValue(element, existingValue);
  360. var returnValue = true;
  361. $.each(currentValue, function (n, value) {
  362. if (value !== '') {
  363. returnValue = false;
  364. return false; // break.
  365. }
  366. });
  367. return returnValue;
  368. };
  369. Drupal.webform.conditionalOperatorStringNotEmpty = function (element, existingValue, ruleValue) {
  370. return !Drupal.webform.conditionalOperatorStringEmpty(element, existingValue, ruleValue);
  371. };
  372. Drupal.webform.conditionalOperatorSelectGreaterThan = function (element, existingValue, ruleValue) {
  373. var currentValue = Drupal.webform.stringValue(element, existingValue);
  374. return Drupal.webform.compare_select(currentValue[0], ruleValue, element) > 0;
  375. };
  376. Drupal.webform.conditionalOperatorSelectGreaterThanEqual = function (element, existingValue, ruleValue) {
  377. var currentValue = Drupal.webform.stringValue(element, existingValue);
  378. var comparison = Drupal.webform.compare_select(currentValue[0], ruleValue, element);
  379. return comparison > 0 || comparison === 0;
  380. };
  381. Drupal.webform.conditionalOperatorSelectLessThan = function (element, existingValue, ruleValue) {
  382. var currentValue = Drupal.webform.stringValue(element, existingValue);
  383. return Drupal.webform.compare_select(currentValue[0], ruleValue, element) < 0;
  384. };
  385. Drupal.webform.conditionalOperatorSelectLessThanEqual = function (element, existingValue, ruleValue) {
  386. var currentValue = Drupal.webform.stringValue(element, existingValue);
  387. var comparison = Drupal.webform.compare_select(currentValue[0], ruleValue, element);
  388. return comparison < 0 || comparison === 0;
  389. };
  390. Drupal.webform.conditionalOperatorNumericEqual = function (element, existingValue, ruleValue) {
  391. // See float comparison: http://php.net/manual/en/language.types.float.php
  392. var currentValue = Drupal.webform.stringValue(element, existingValue);
  393. var epsilon = 0.000001;
  394. // An empty string does not match any number.
  395. return currentValue[0] === '' ? false : (Math.abs(parseFloat(currentValue[0]) - parseFloat(ruleValue)) < epsilon);
  396. };
  397. Drupal.webform.conditionalOperatorNumericNotEqual = function (element, existingValue, ruleValue) {
  398. // See float comparison: http://php.net/manual/en/language.types.float.php
  399. var currentValue = Drupal.webform.stringValue(element, existingValue);
  400. var epsilon = 0.000001;
  401. // An empty string does not match any number.
  402. return currentValue[0] === '' ? true : (Math.abs(parseFloat(currentValue[0]) - parseFloat(ruleValue)) >= epsilon);
  403. };
  404. Drupal.webform.conditionalOperatorNumericGreaterThan = function (element, existingValue, ruleValue) {
  405. var currentValue = Drupal.webform.stringValue(element, existingValue);
  406. return parseFloat(currentValue[0]) > parseFloat(ruleValue);
  407. };
  408. Drupal.webform.conditionalOperatorNumericGreaterThanEqual = function (element, existingValue, ruleValue) {
  409. return Drupal.webform.conditionalOperatorNumericGreaterThan(element, existingValue, ruleValue) ||
  410. Drupal.webform.conditionalOperatorNumericEqual(element, existingValue, ruleValue);
  411. };
  412. Drupal.webform.conditionalOperatorNumericLessThan = function (element, existingValue, ruleValue) {
  413. var currentValue = Drupal.webform.stringValue(element, existingValue);
  414. return parseFloat(currentValue[0]) < parseFloat(ruleValue);
  415. };
  416. Drupal.webform.conditionalOperatorNumericLessThanEqual = function (element, existingValue, ruleValue) {
  417. return Drupal.webform.conditionalOperatorNumericLessThan(element, existingValue, ruleValue) ||
  418. Drupal.webform.conditionalOperatorNumericEqual(element, existingValue, ruleValue);
  419. };
  420. Drupal.webform.conditionalOperatorDateEqual = function (element, existingValue, ruleValue) {
  421. var currentValue = Drupal.webform.dateValue(element, existingValue);
  422. return currentValue === ruleValue;
  423. };
  424. Drupal.webform.conditionalOperatorDateNotEqual = function (element, existingValue, ruleValue) {
  425. return !Drupal.webform.conditionalOperatorDateEqual(element, existingValue, ruleValue);
  426. };
  427. Drupal.webform.conditionalOperatorDateBefore = function (element, existingValue, ruleValue) {
  428. var currentValue = Drupal.webform.dateValue(element, existingValue);
  429. return (currentValue !== false) && currentValue < ruleValue;
  430. };
  431. Drupal.webform.conditionalOperatorDateBeforeEqual = function (element, existingValue, ruleValue) {
  432. var currentValue = Drupal.webform.dateValue(element, existingValue);
  433. return (currentValue !== false) && (currentValue < ruleValue || currentValue === ruleValue);
  434. };
  435. Drupal.webform.conditionalOperatorDateAfter = function (element, existingValue, ruleValue) {
  436. var currentValue = Drupal.webform.dateValue(element, existingValue);
  437. return (currentValue !== false) && currentValue > ruleValue;
  438. };
  439. Drupal.webform.conditionalOperatorDateAfterEqual = function (element, existingValue, ruleValue) {
  440. var currentValue = Drupal.webform.dateValue(element, existingValue);
  441. return (currentValue !== false) && (currentValue > ruleValue || currentValue === ruleValue);
  442. };
  443. Drupal.webform.conditionalOperatorTimeEqual = function (element, existingValue, ruleValue) {
  444. var currentValue = Drupal.webform.timeValue(element, existingValue);
  445. return currentValue === ruleValue;
  446. };
  447. Drupal.webform.conditionalOperatorTimeNotEqual = function (element, existingValue, ruleValue) {
  448. return !Drupal.webform.conditionalOperatorTimeEqual(element, existingValue, ruleValue);
  449. };
  450. Drupal.webform.conditionalOperatorTimeBefore = function (element, existingValue, ruleValue) {
  451. // Date and time operators intentionally exclusive for "before".
  452. var currentValue = Drupal.webform.timeValue(element, existingValue);
  453. return (currentValue !== false) && (currentValue < ruleValue);
  454. };
  455. Drupal.webform.conditionalOperatorTimeBeforeEqual = function (element, existingValue, ruleValue) {
  456. // Date and time operators intentionally exclusive for "before".
  457. var currentValue = Drupal.webform.timeValue(element, existingValue);
  458. return (currentValue !== false) && (currentValue < ruleValue || currentValue === ruleValue);
  459. };
  460. Drupal.webform.conditionalOperatorTimeAfter = function (element, existingValue, ruleValue) {
  461. // Date and time operators intentionally inclusive for "after".
  462. var currentValue = Drupal.webform.timeValue(element, existingValue);
  463. return (currentValue !== false) && (currentValue > ruleValue);
  464. };
  465. Drupal.webform.conditionalOperatorTimeAfterEqual = function (element, existingValue, ruleValue) {
  466. // Date and time operators intentionally inclusive for "after".
  467. var currentValue = Drupal.webform.timeValue(element, existingValue);
  468. return (currentValue !== false) && (currentValue > ruleValue || currentValue === ruleValue);
  469. };
  470. /**
  471. * Utility function to compare values of a select component.
  472. *
  473. * @param string a
  474. * First select option key to compare
  475. * @param string b
  476. * Second select option key to compare
  477. * @param array options
  478. * Associative array where the a and b are within the keys
  479. *
  480. * @return integer based upon position of $a and $b in $options
  481. * -N if $a above (<) $b
  482. * 0 if $a = $b
  483. * +N if $a is below (>) $b
  484. */
  485. Drupal.webform.compare_select = function (a, b, element) {
  486. var optionList = [];
  487. $('option,input:radio,input:checkbox', element).each(function () {
  488. optionList.push($(this).val());
  489. });
  490. var a_position = optionList.indexOf(a);
  491. var b_position = optionList.indexOf(b);
  492. return (a_position < 0 || b_position < 0) ? null : a_position - b_position;
  493. };
  494. /**
  495. * Utility to return current visibility.
  496. *
  497. * Uses actual visibility, except for hidden components which use the applied
  498. * disabled class.
  499. */
  500. Drupal.webform.isVisible = function ($element) {
  501. return $element.hasClass('webform-component-hidden')
  502. ? !$element.find('input').first().hasClass('webform-conditional-disabled')
  503. : $element.closest('.webform-conditional-hidden').length == 0;
  504. };
  505. /**
  506. * Function to get a string value from a select/radios/text/etc. field.
  507. */
  508. Drupal.webform.stringValue = function (element, existingValue) {
  509. var value = [];
  510. if (element) {
  511. var $element = $(element);
  512. if (Drupal.webform.isVisible($element)) {
  513. // Checkboxes and radios.
  514. $element.find('input[type=checkbox]:checked,input[type=radio]:checked').each(function () {
  515. value.push(this.value);
  516. });
  517. // Select lists.
  518. if (!value.length) {
  519. var selectValue = $element.find('select').val();
  520. if (selectValue) {
  521. if ($.isArray(selectValue)) {
  522. value = selectValue;
  523. }
  524. else {
  525. value.push(selectValue);
  526. }
  527. }
  528. }
  529. // Simple text fields. This check is done last so that the select list
  530. // in select-or-other fields comes before the "other" text field.
  531. if (!value.length) {
  532. $element.find('input:not([type=checkbox],[type=radio]),textarea').each(function () {
  533. value.push(this.value);
  534. });
  535. }
  536. }
  537. }
  538. else {
  539. switch ($.type(existingValue)) {
  540. case 'array':
  541. value = existingValue;
  542. break;
  543. case 'string':
  544. value.push(existingValue);
  545. break;
  546. }
  547. }
  548. return value;
  549. };
  550. /**
  551. * Utility function to calculate a second-based timestamp from a time field.
  552. */
  553. Drupal.webform.dateValue = function (element, existingValue) {
  554. var value = false;
  555. if (element) {
  556. var $element = $(element);
  557. if (Drupal.webform.isVisible($element)) {
  558. var day = $element.find('[name*=day]').val();
  559. var month = $element.find('[name*=month]').val();
  560. var year = $element.find('[name*=year]').val();
  561. // Months are 0 indexed in JavaScript.
  562. if (month) {
  563. month--;
  564. }
  565. if (year !== '' && month !== '' && day !== '') {
  566. value = Date.UTC(year, month, day) / 1000;
  567. }
  568. }
  569. }
  570. else {
  571. if ($.type(existingValue) === 'array' && existingValue.length) {
  572. existingValue = existingValue[0];
  573. }
  574. if ($.type(existingValue) === 'string') {
  575. existingValue = existingValue.split('-');
  576. }
  577. if (existingValue.length === 3) {
  578. value = Date.UTC(existingValue[0], existingValue[1], existingValue[2]) / 1000;
  579. }
  580. }
  581. return value;
  582. };
  583. /**
  584. * Utility function to calculate a millisecond timestamp from a time field.
  585. */
  586. Drupal.webform.timeValue = function (element, existingValue) {
  587. var value = false;
  588. if (element) {
  589. var $element = $(element);
  590. if (Drupal.webform.isVisible($element)) {
  591. var hour = $element.find('[name*=hour]').val();
  592. var minute = $element.find('[name*=minute]').val();
  593. var ampm = $element.find('[name*=ampm]:checked').val();
  594. // Convert to integers if set.
  595. hour = (hour === '') ? hour : parseInt(hour);
  596. minute = (minute === '') ? minute : parseInt(minute);
  597. if (hour !== '') {
  598. hour = (hour < 12 && ampm == 'pm') ? hour + 12 : hour;
  599. hour = (hour === 12 && ampm == 'am') ? 0 : hour;
  600. }
  601. if (hour !== '' && minute !== '') {
  602. value = Date.UTC(1970, 0, 1, hour, minute) / 1000;
  603. }
  604. }
  605. }
  606. else {
  607. if ($.type(existingValue) === 'array' && existingValue.length) {
  608. existingValue = existingValue[0];
  609. }
  610. if ($.type(existingValue) === 'string') {
  611. existingValue = existingValue.split(':');
  612. }
  613. if (existingValue.length >= 2) {
  614. value = Date.UTC(1970, 0, 1, existingValue[0], existingValue[1]) / 1000;
  615. }
  616. }
  617. return value;
  618. };
  619. /**
  620. * Make a prop shim for jQuery < 1.9.
  621. */
  622. $.fn.webformProp = $.fn.webformProp || function (name, value) {
  623. if (value) {
  624. return $.fn.prop ? this.prop(name, true) : this.attr(name, true);
  625. }
  626. else {
  627. return $.fn.prop ? this.prop(name, false) : this.removeAttr(name);
  628. }
  629. };
  630. /**
  631. * Make a multi-valued val() function.
  632. *
  633. * This is for setting checkboxes, radios, and select elements.
  634. */
  635. $.fn.webformVal = function (values) {
  636. this.each(function () {
  637. var $this = $(this);
  638. var value = $this.val();
  639. var on = $.inArray($this.val(), values) != -1;
  640. if (this.nodeName == 'OPTION') {
  641. $this.webformProp('selected', on ? value : false);
  642. }
  643. else {
  644. $this.val(on ? [value] : false);
  645. }
  646. });
  647. return this;
  648. };
  649. /**
  650. * Given a table's DOM element, restripe the odd/even classes.
  651. */
  652. Drupal.webform.restripeTable = function (table) {
  653. // :even and :odd are reversed because jQuery counts from 0 and
  654. // we count from 1, so we're out of sync.
  655. // Match immediate children of the parent element to allow nesting.
  656. $('> tbody > tr, > tr', table)
  657. .filter(':visible:odd').filter('.odd')
  658. .removeClass('odd').addClass('even')
  659. .end().end()
  660. .filter(':visible:even').filter('.even')
  661. .removeClass('even').addClass('odd');
  662. };
  663. })(jQuery);