form.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. /**
  2. * Copyright © Magento, Inc. All rights reserved.
  3. * See COPYING.txt for license details.
  4. */
  5. /* global varienGlobalEvents, varienWindowOnloadCache, RegionUpdater, FormElementDependenceController */
  6. /* eslint-disable strict */
  7. define([
  8. 'jquery',
  9. 'prototype',
  10. 'mage/adminhtml/events'
  11. ], function (jQuery) {
  12. var varienElementMethods;
  13. /*
  14. * @TODO Need to be removed after refactoring all dependent of the form the components
  15. */
  16. (function ($) {
  17. $(document).ready(function () {
  18. $(document).on('beforeSubmit', function (e) { //eslint-disable-line max-nested-callbacks
  19. if (typeof varienGlobalEvents !== 'undefined') {
  20. varienGlobalEvents.fireEvent('formSubmit', $(e.target).attr('id'));
  21. }
  22. });
  23. });
  24. })(jQuery);
  25. /**
  26. * Additional elements methods
  27. */
  28. varienElementMethods = {
  29. /**
  30. * @param {HTMLElement} element
  31. */
  32. setHasChanges: function (element) {
  33. var elm;
  34. if ($(element) && $(element).hasClassName('no-changes')) {
  35. return;
  36. }
  37. elm = element;
  38. while (elm && elm.tagName != 'BODY') { //eslint-disable-line eqeqeq
  39. if (elm.statusBar) {
  40. Element.addClassName($(elm.statusBar), 'changed');
  41. }
  42. elm = elm.parentNode;
  43. }
  44. },
  45. /**
  46. * @param {HTMLElement} element
  47. * @param {*} flag
  48. * @param {Object} form
  49. */
  50. setHasError: function (element, flag, form) {
  51. var elm = element;
  52. while (elm && elm.tagName != 'BODY') { //eslint-disable-line eqeqeq
  53. if (elm.statusBar) {
  54. /* eslint-disable max-depth */
  55. if (form.errorSections.keys().indexOf(elm.statusBar.id) < 0) {
  56. form.errorSections.set(elm.statusBar.id, flag);
  57. }
  58. if (flag) {
  59. Element.addClassName($(elm.statusBar), 'error');
  60. if (form.canShowError && $(elm.statusBar).show) {
  61. form.canShowError = false;
  62. $(elm.statusBar).show();
  63. }
  64. form.errorSections.set(elm.statusBar.id, flag);
  65. } else if (!form.errorSections.get(elm.statusBar.id)) {
  66. Element.removeClassName($(elm.statusBar), 'error');
  67. }
  68. /* eslint-enable max-depth */
  69. }
  70. elm = elm.parentNode;
  71. }
  72. this.canShowElement = false;
  73. }
  74. };
  75. Element.addMethods(varienElementMethods);
  76. // Global bind changes
  77. window.varienWindowOnloadCache = {};
  78. /**
  79. * @param {*} useCache
  80. */
  81. function varienWindowOnload(useCache) {
  82. var dataElements = $$('input', 'select', 'textarea'),
  83. i;
  84. for (i = 0; i < dataElements.length; i++) {
  85. if (dataElements[i] && dataElements[i].id) {
  86. /* eslint-disable max-depth */
  87. if (!useCache || !varienWindowOnloadCache[dataElements[i].id]) {
  88. Event.observe(dataElements[i], 'change', dataElements[i].setHasChanges.bind(dataElements[i]));
  89. if (useCache) {
  90. varienWindowOnloadCache[dataElements[i].id] = true;
  91. }
  92. }
  93. /* eslint-disable max-depth */
  94. }
  95. }
  96. }
  97. Event.observe(window, 'load', varienWindowOnload);
  98. window.RegionUpdater = Class.create();
  99. RegionUpdater.prototype = {
  100. /**
  101. * @param {HTMLElement} countryEl
  102. * @param {HTMLElement} regionTextEl
  103. * @param {HTMLElement}regionSelectEl
  104. * @param {Object} regions
  105. * @param {*} disableAction
  106. * @param {*} clearRegionValueOnDisable
  107. */
  108. initialize: function (
  109. countryEl, regionTextEl, regionSelectEl, regions, disableAction, clearRegionValueOnDisable
  110. ) {
  111. this.isRegionRequired = true;
  112. this.countryEl = $(countryEl);
  113. this.regionTextEl = $(regionTextEl);
  114. this.regionSelectEl = $(regionSelectEl);
  115. this.config = regions.config;
  116. delete regions.config;
  117. this.regions = regions;
  118. this.disableAction = typeof disableAction === 'undefined' ? 'hide' : disableAction;
  119. this.clearRegionValueOnDisable = typeof clearRegionValueOnDisable === 'undefined' ?
  120. false : clearRegionValueOnDisable;
  121. if (this.regionSelectEl.options.length <= 1) {
  122. this.update();
  123. } else {
  124. this.lastCountryId = this.countryEl.value;
  125. }
  126. this.countryEl.changeUpdater = this.update.bind(this);
  127. Event.observe(this.countryEl, 'change', this.update.bind(this));
  128. },
  129. /**
  130. * @private
  131. */
  132. _checkRegionRequired: function () {
  133. var label, wildCard, elements, that, regionRequired;
  134. if (!this.isRegionRequired) {
  135. return;
  136. }
  137. elements = [this.regionTextEl, this.regionSelectEl];
  138. that = this;
  139. if (typeof this.config == 'undefined') {
  140. return;
  141. }
  142. regionRequired = this.config['regions_required'].indexOf(this.countryEl.value) >= 0;
  143. elements.each(function (currentElement) {
  144. var form, validationInstance, field, topElement;
  145. if (!currentElement) {
  146. return;
  147. }
  148. form = currentElement.form;
  149. validationInstance = form ? jQuery(form).data('validation') : null;
  150. field = currentElement.up('.field') || new Element('div');
  151. if (validationInstance) {
  152. validationInstance.clearError(currentElement);
  153. }
  154. label = $$('label[for="' + currentElement.id + '"]')[0];
  155. if (label) {
  156. wildCard = label.down('em') || label.down('span.required');
  157. topElement = label.up('tr') || label.up('li');
  158. if (!that.config['show_all_regions'] && topElement) {
  159. if (regionRequired) {
  160. topElement.show();
  161. } else {
  162. topElement.hide();
  163. }
  164. }
  165. }
  166. if (label && wildCard) {
  167. if (!regionRequired) {
  168. wildCard.hide();
  169. } else {
  170. wildCard.show();
  171. }
  172. }
  173. //compute the need for the required fields
  174. if (!regionRequired || !currentElement.visible()) {
  175. if (field.hasClassName('required')) {
  176. field.removeClassName('required');
  177. }
  178. if (currentElement.hasClassName('required-entry')) {
  179. currentElement.removeClassName('required-entry');
  180. }
  181. if (currentElement.tagName.toLowerCase() == 'select' && //eslint-disable-line eqeqeq
  182. currentElement.hasClassName('validate-select')
  183. ) {
  184. currentElement.removeClassName('validate-select');
  185. }
  186. } else {
  187. if (!field.hasClassName('required')) {
  188. field.addClassName('required');
  189. }
  190. if (!currentElement.hasClassName('required-entry')) {
  191. currentElement.addClassName('required-entry');
  192. }
  193. if (currentElement.tagName.toLowerCase() == 'select' && //eslint-disable-line eqeqeq
  194. !currentElement.hasClassName('validate-select')
  195. ) {
  196. currentElement.addClassName('validate-select');
  197. }
  198. }
  199. });
  200. },
  201. /**
  202. * Disable region validation.
  203. */
  204. disableRegionValidation: function () {
  205. this.isRegionRequired = false;
  206. },
  207. /**
  208. * Update.
  209. */
  210. update: function () {
  211. var option, region, def, regionId;
  212. if (this.regions[this.countryEl.value]) {
  213. if (this.lastCountryId != this.countryEl.value) { //eslint-disable-line eqeqeq
  214. def = this.regionSelectEl.getAttribute('defaultValue');
  215. if (this.regionTextEl) {
  216. if (!def) {
  217. def = this.regionTextEl.value.toLowerCase();
  218. }
  219. this.regionTextEl.value = '';
  220. }
  221. this.regionSelectEl.options.length = 1;
  222. for (regionId in this.regions[this.countryEl.value]) { //eslint-disable-line guard-for-in
  223. region = this.regions[this.countryEl.value][regionId];
  224. option = document.createElement('OPTION');
  225. option.value = regionId;
  226. option.text = region.name.stripTags();
  227. option.title = region.name;
  228. if (this.regionSelectEl.options.add) {
  229. this.regionSelectEl.options.add(option);
  230. } else {
  231. this.regionSelectEl.appendChild(option);
  232. }
  233. if (regionId == def || region.name.toLowerCase() == def || region.code.toLowerCase() == def) { //eslint-disable-line
  234. this.regionSelectEl.value = regionId;
  235. }
  236. }
  237. }
  238. if (this.disableAction == 'hide') { //eslint-disable-line eqeqeq
  239. if (this.regionTextEl) {
  240. this.regionTextEl.style.display = 'none';
  241. this.regionTextEl.style.disabled = true;
  242. }
  243. this.regionSelectEl.style.display = '';
  244. this.regionSelectEl.disabled = false;
  245. } else if (this.disableAction == 'disable') { //eslint-disable-line eqeqeq
  246. if (this.regionTextEl) {
  247. this.regionTextEl.disabled = true;
  248. }
  249. this.regionSelectEl.disabled = false;
  250. }
  251. this.setMarkDisplay(this.regionSelectEl, true);
  252. this.lastCountryId = this.countryEl.value;
  253. } else {
  254. if (this.disableAction == 'hide') { //eslint-disable-line eqeqeq
  255. if (this.regionTextEl) {
  256. this.regionTextEl.style.display = '';
  257. this.regionTextEl.style.disabled = false;
  258. }
  259. this.regionSelectEl.style.display = 'none';
  260. this.regionSelectEl.disabled = true;
  261. } else if (this.disableAction == 'disable') { //eslint-disable-line eqeqeq
  262. if (this.regionTextEl) {
  263. this.regionTextEl.disabled = false;
  264. }
  265. this.regionSelectEl.disabled = true;
  266. if (this.clearRegionValueOnDisable) {
  267. this.regionSelectEl.value = '';
  268. }
  269. } else if (this.disableAction == 'nullify') { //eslint-disable-line eqeqeq
  270. this.regionSelectEl.options.length = 1;
  271. this.regionSelectEl.value = '';
  272. this.regionSelectEl.selectedIndex = 0;
  273. this.lastCountryId = '';
  274. }
  275. this.setMarkDisplay(this.regionSelectEl, false);
  276. }
  277. varienGlobalEvents.fireEvent('address_country_changed', this.countryEl);
  278. this._checkRegionRequired();
  279. },
  280. /**
  281. * @param {HTMLElement} elem
  282. * @param {*} display
  283. */
  284. setMarkDisplay: function (elem, display) {
  285. var marks;
  286. if (elem.parentNode.parentNode) {
  287. marks = Element.select(elem.parentNode.parentNode, '.required');
  288. if (marks[0]) {
  289. display ? marks[0].show() : marks[0].hide();
  290. }
  291. }
  292. }
  293. };
  294. window.regionUpdater = RegionUpdater;
  295. /**
  296. * Fix errorrs in IE
  297. */
  298. Event.pointerX = function (event) {
  299. try {
  300. return event.pageX || (event.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft)); //eslint-disable-line
  301. }
  302. catch (e) {}
  303. };
  304. /**
  305. * @param {jQuery.Event} event
  306. * @return {*}
  307. */
  308. Event.pointerY = function (event) {
  309. try {
  310. return event.pageY || (event.clientY + (document.documentElement.scrollTop || document.body.scrollTop)); //eslint-disable-line
  311. }
  312. catch (e) {}
  313. };
  314. /**
  315. * Observer that watches for dependent form elements
  316. * If an element depends on 1 or more of other elements,
  317. * it should show up only when all of them gain specified values
  318. */
  319. window.FormElementDependenceController = Class.create();
  320. FormElementDependenceController.prototype = {
  321. /**
  322. * Structure of elements: {
  323. * 'id_of_dependent_element' : {
  324. * 'id_of_master_element_1' : 'reference_value',
  325. * 'id_of_master_element_2' : 'reference_value'
  326. * 'id_of_master_element_3' : ['reference_value1', 'reference_value2']
  327. * ...
  328. * }
  329. * }
  330. * @param {Object} elementsMap
  331. * @param {Object} config
  332. */
  333. initialize: function (elementsMap, config) {
  334. var idTo, idFrom, values, fromId, radioFrom;
  335. if (config) {
  336. this._config = jQuery.extend(this._config, config);
  337. }
  338. for (idTo in elementsMap) { //eslint-disable-line guard-for-in
  339. for (idFrom in elementsMap[idTo]) { //eslint-disable-line guard-for-in
  340. if ($(idFrom)) {
  341. Event.observe(
  342. $(idFrom),
  343. 'change',
  344. this.trackChange.bindAsEventListener(this, idTo, elementsMap[idTo])
  345. );
  346. } else {
  347. // Check if radio button
  348. values = elementsMap[idTo][idFrom].values;
  349. fromId = $(idFrom + values[0]);
  350. radioFrom = fromId ? $$('[name="' + fromId.name + '"]') : false;
  351. if (radioFrom) {
  352. radioFrom.invoke(
  353. 'on',
  354. 'change',
  355. this.trackChange.bindAsEventListener(this, idTo, elementsMap[idTo])
  356. );
  357. }
  358. }
  359. this.trackChange(null, idTo, elementsMap[idTo]);
  360. }
  361. }
  362. },
  363. /**
  364. * Misc. config options
  365. * Keys are underscored intentionally
  366. */
  367. _config: {
  368. 'levels_up': 1 // how many levels up to travel when toggling element
  369. },
  370. /**
  371. * Define whether target element should be toggled and show/hide its row
  372. *
  373. * @param {Object} e - event
  374. * @param {String} idTo - id of target element
  375. * @param {Object} valuesFrom - ids of master elements and reference values
  376. * @return
  377. */
  378. trackChange: function (e, idTo, valuesFrom) {
  379. // define whether the target should show up
  380. var shouldShowUp = true,
  381. idFrom, from, values, isInArray, isNegative, headElement, isInheritCheckboxChecked, target, inputs,
  382. isAnInputOrSelect, currentConfig, rowElement, fromId, radioFrom;
  383. for (idFrom in valuesFrom) { //eslint-disable-line guard-for-in
  384. from = $(idFrom);
  385. if (from) {
  386. values = valuesFrom[idFrom].values;
  387. isInArray = values.indexOf(from.value) != -1; //eslint-disable-line
  388. isNegative = valuesFrom[idFrom].negative;
  389. if (!from || isInArray && isNegative || !isInArray && !isNegative) {
  390. shouldShowUp = false;
  391. }
  392. // Check if radio button
  393. } else {
  394. values = valuesFrom[idFrom].values;
  395. fromId = $(idFrom + values[0]);
  396. if (fromId) {
  397. radioFrom = $$('[name="' + fromId.name + '"]:checked');
  398. isInArray = radioFrom.length > 0 && values.indexOf(radioFrom[0].value) !== -1;
  399. isNegative = valuesFrom[idFrom].negative;
  400. if (!radioFrom || isInArray && isNegative || !isInArray && !isNegative) {
  401. shouldShowUp = false;
  402. }
  403. }
  404. }
  405. }
  406. // toggle target row
  407. headElement = $(idTo + '-head');
  408. isInheritCheckboxChecked = $(idTo + '_inherit') && $(idTo + '_inherit').checked;
  409. target = $(idTo);
  410. // Target won't always exist (for example, if field type is "label")
  411. if (target) {
  412. inputs = target.up(this._config['levels_up']).select('input', 'select', 'td');
  413. isAnInputOrSelect = ['input', 'select'].indexOf(target.tagName.toLowerCase()) != -1; //eslint-disable-line
  414. if (target.type === 'fieldset') {
  415. inputs = target.select('input', 'select', 'td');
  416. }
  417. } else {
  418. inputs = false;
  419. isAnInputOrSelect = false;
  420. }
  421. if (shouldShowUp) {
  422. currentConfig = this._config;
  423. if (inputs) {
  424. inputs.each(function (item) {
  425. // don't touch hidden inputs (and Use Default inputs too), bc they may have custom logic
  426. if ((!item.type || item.type != 'hidden') && !($(item.id + '_inherit') && $(item.id + '_inherit').checked) && //eslint-disable-line
  427. !(currentConfig['can_edit_price'] != undefined && !currentConfig['can_edit_price']) //eslint-disable-line
  428. ) {
  429. item.disabled = false;
  430. jQuery(item).removeClass('ignore-validate');
  431. }
  432. });
  433. }
  434. if (headElement) {
  435. headElement.show();
  436. if (headElement.hasClassName('open') && target) {
  437. target.show();
  438. } else if (target) {
  439. target.hide();
  440. }
  441. } else {
  442. if (target) {
  443. target.show();
  444. }
  445. if (isAnInputOrSelect && !isInheritCheckboxChecked) {
  446. if (target) {
  447. if (target.getAttribute('readonly')) {
  448. target.disabled = true;
  449. } else {
  450. target.disabled = false;
  451. }
  452. }
  453. jQuery('#' + idTo).removeClass('ignore-validate');
  454. }
  455. }
  456. } else {
  457. if (inputs) {
  458. inputs.each(function (item) {
  459. // don't touch hidden inputs (and Use Default inputs too), bc they may have custom logic
  460. if ((!item.type || item.type != 'hidden') && //eslint-disable-line eqeqeq
  461. !($(item.id + '_inherit') && $(item.id + '_inherit').checked)
  462. ) {
  463. item.disabled = true;
  464. jQuery(item).addClass('ignore-validate');
  465. }
  466. });
  467. }
  468. if (headElement) {
  469. headElement.hide();
  470. }
  471. if (target) {
  472. target.hide();
  473. }
  474. if (isAnInputOrSelect && !isInheritCheckboxChecked) {
  475. if (target) {
  476. target.disabled = true;
  477. }
  478. jQuery('#' + idTo).addClass('ignore-validate');
  479. }
  480. }
  481. rowElement = $('row_' + idTo);
  482. if (rowElement == undefined && target) { //eslint-disable-line eqeqeq
  483. rowElement = target.up(this._config['levels_up']);
  484. if (target.type === 'fieldset') {
  485. rowElement = target;
  486. }
  487. }
  488. if (rowElement) {
  489. if (shouldShowUp) {
  490. rowElement.show();
  491. } else {
  492. rowElement.hide();
  493. }
  494. }
  495. }
  496. };
  497. window.varienWindowOnload = varienWindowOnload;
  498. window.varienElementMethods = varienElementMethods;
  499. });