hierarchical_select.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702
  1. (function($) {
  2. Drupal.behaviors.HierarchicalSelect = {
  3. attach: function (context) {
  4. $('.hierarchical-select-wrapper:not(.hierarchical-select-wrapper-processed)', context)
  5. .addClass('hierarchical-select-wrapper-processed').each(function() {
  6. var hsid = $(this).attr('id').replace(/^hierarchical-select-(.+)-wrapper$/, "$1");
  7. Drupal.HierarchicalSelect.initialize(hsid);
  8. });
  9. }
  10. };
  11. Drupal.HierarchicalSelect = {};
  12. Drupal.HierarchicalSelect.state = [];
  13. Drupal.HierarchicalSelect.context = function() {
  14. return $("form .hierarchical-select-wrapper");
  15. };
  16. Drupal.HierarchicalSelect.initialize = function(hsid) {
  17. // Prevent JS errors when Hierarchical Select is loaded dynamically.
  18. if (undefined == Drupal.settings.HierarchicalSelect || undefined == Drupal.settings.HierarchicalSelect.settings["hs-" + hsid]) {
  19. return false;
  20. }
  21. // If you set Drupal.settings.HierarchicalSelect.pretendNoJS to *anything*,
  22. // and as such, Hierarchical Select won't initialize its Javascript! It
  23. // will seem as if your browser had Javascript disabled.
  24. if (undefined != Drupal.settings.HierarchicalSelect.pretendNoJS) {
  25. return false;
  26. }
  27. var form = $('#hierarchical-select-'+ hsid +'-wrapper').parents('form');
  28. // Pressing the 'enter' key on a form that contains an HS widget, depending
  29. // on which browser, usually causes the first submit button to be pressed
  30. // (likely an HS button). This results in unpredictable behaviour. There is
  31. // no way to determine the 'real' submit button, so disable the enter key.
  32. form.find('input').keypress(function(event) {
  33. if (event.keyCode == 13) {
  34. event.preventDefault();
  35. return false;
  36. }
  37. });
  38. // Turn off Firefox' autocomplete feature. This causes Hierarchical Select
  39. // form items to be disabled after a hard refresh.
  40. // See http://drupal.org/node/453048 and
  41. // http://www.ryancramer.com/journal/entries/radio_buttons_firefox/
  42. if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1) {
  43. form.attr('autocomplete', 'off');
  44. }
  45. // Enable *all* submit buttons in this form, as well as all input-related
  46. // elements of the current hierarchical select, in case we reloaded while
  47. // they were disabled.
  48. form.add('#hierarchical-select-' + hsid +'-wrapper .hierarchical-select .selects select')
  49. .add('#hierarchical-select-' + hsid +'-wrapper .hierarchical-select input')
  50. .attr('disabled', false);
  51. if (this.cache != null) {
  52. this.cache.initialize();
  53. }
  54. Drupal.settings.HierarchicalSelect.settings["hs-" + hsid]['updatesEnabled'] = true;
  55. if (undefined == Drupal.HierarchicalSelect.state["hs-" + hsid]) {
  56. Drupal.HierarchicalSelect.state["hs-" + hsid] = {};
  57. }
  58. this.transform(hsid);
  59. if (Drupal.settings.HierarchicalSelect.settings["hs-" + hsid].resizable) {
  60. this.resizable(hsid);
  61. }
  62. Drupal.HierarchicalSelect.attachBindings(hsid);
  63. if (this.cache != null && this.cache.status()) {
  64. this.cache.load(hsid);
  65. }
  66. Drupal.HierarchicalSelect.log(hsid);
  67. };
  68. Drupal.HierarchicalSelect.log = function(hsid, messages) {
  69. // Only perform logging if logging is enabled.
  70. if (Drupal.settings.HierarchicalSelect.initialLog == undefined || Drupal.settings.HierarchicalSelect.initialLog["hs-" + hsid] == undefined) {
  71. return;
  72. }
  73. else {
  74. Drupal.HierarchicalSelect.state["hs-" + hsid].log = [];
  75. }
  76. // Store the log messages. The first call to this function may not contain a
  77. // message: the initial log included in the initial HTML rendering should be
  78. // used instead..
  79. if (Drupal.HierarchicalSelect.state["hs-" + hsid].log.length == 0) {
  80. Drupal.HierarchicalSelect.state["hs-" + hsid].log.push(Drupal.settings.HierarchicalSelect.initialLog["hs-" + hsid]);
  81. }
  82. else {
  83. Drupal.HierarchicalSelect.state["hs-" + hsid].log.push(messages);
  84. }
  85. // Print the log messages.
  86. console.log("HIERARCHICAL SELECT " + hsid);
  87. var logIndex = Drupal.HierarchicalSelect.state["hs-" + hsid].log.length - 1;
  88. for (var i = 0; i < Drupal.HierarchicalSelect.state["hs-" + hsid].log[logIndex].length; i++) {
  89. console.log(Drupal.HierarchicalSelect.state["hs-" + hsid].log[logIndex][i]);
  90. }
  91. console.log(' ');
  92. };
  93. Drupal.HierarchicalSelect.transform = function(hsid) {
  94. var removeString = $('#hierarchical-select-'+ hsid +'-wrapper .dropbox .dropbox-remove:first', Drupal.HierarchicalSelect.context).text();
  95. $('#hierarchical-select-'+ hsid +'-wrapper', Drupal.HierarchicalSelect.context)
  96. // Remove the .nojs div.
  97. .find('.nojs').hide().end()
  98. // Find all .dropbox-remove cells in the dropbox table.
  99. .find('.dropbox .dropbox-remove')
  100. // Hide the children of these table cells. We're not removing them because
  101. // we want to continue to use the "Remove" checkboxes.
  102. .find('*').css('display', 'none').end() // We can't use .hide() because of collapse.js: http://drupal.org/node/351458#comment-1258303.
  103. // Put a "Remove" link there instead.
  104. .append('<a href="">'+ removeString +'</a>');
  105. };
  106. Drupal.HierarchicalSelect.resizable = function(hsid) {
  107. var $selectsWrapper = $('#hierarchical-select-' + hsid + '-wrapper .hierarchical-select .selects', Drupal.HierarchicalSelect.context);
  108. // No select wrapper present: the user is creating a new item.
  109. if ($selectsWrapper.length == 0) {
  110. return;
  111. }
  112. // Append the drag handle ("grippie").
  113. $selectsWrapper.append($('<div class="grippie"></div>'));
  114. // jQuery object that contains all selects in the hierarchical select, to
  115. // speed up DOM manipulation during dragging.
  116. var $selects = $selectsWrapper.find('select');
  117. var defaultPadding = parseInt($selects.slice(0, 1).css('padding-top').replace(/^(\d+)px$/, "$1")) + parseInt($selects.slice(0, 1).css('padding-bottom').replace(/^(\d+)px$/, "$1"));
  118. var defaultHeight = Drupal.HierarchicalSelect.state["hs-" + hsid].defaultHeight = $selects.slice(0, 1).height() + defaultPadding;
  119. var defaultSize = Drupal.HierarchicalSelect.state["hs-" + hsid].defaultSize = $selects.slice(0, 1).attr('size');
  120. defaultSize = (defaultSize == 0) ? 1 : defaultSize;
  121. var margin = Drupal.HierarchicalSelect.state["hs-" + hsid].margin = parseInt($selects.slice(0, 1).css('margin-bottom').replace(/^(\d+)px$/, "$1"));
  122. // Bind the drag event.
  123. $('.grippie', $selectsWrapper)
  124. .mousedown(startDrag)
  125. .dblclick(function() {
  126. if (Drupal.HierarchicalSelect.state["hs-" + hsid].resizedHeight == undefined) {
  127. Drupal.HierarchicalSelect.state["hs-" + hsid].resizedHeight = defaultHeight;
  128. }
  129. var resizedHeight = Drupal.HierarchicalSelect.state["hs-" + hsid].resizedHeight = (Drupal.HierarchicalSelect.state["hs-" + hsid].resizedHeight > defaultHeight + 2) ? defaultHeight : 4.6 / defaultSize * defaultHeight;
  130. Drupal.HierarchicalSelect.resize($selects, defaultHeight, resizedHeight, defaultSize, margin);
  131. });
  132. function startDrag(e) {
  133. staticOffset = $selects.slice(0, 1).height() - e.pageY;
  134. $selects.css('opacity', 0.25);
  135. $(document).mousemove(performDrag).mouseup(endDrag);
  136. return false;
  137. }
  138. function performDrag(e) {
  139. var resizedHeight = staticOffset + e.pageY;
  140. Drupal.HierarchicalSelect.resize($selects, defaultHeight, resizedHeight, defaultSize, margin);
  141. return false;
  142. }
  143. function endDrag(e) {
  144. var height = $selects.slice(0, 1).height();
  145. $(document).unbind("mousemove", performDrag).unbind("mouseup", endDrag);
  146. $selects.css('opacity', 1);
  147. if (height != Drupal.HierarchicalSelect.state["hs-" + hsid].resizedHeight) {
  148. Drupal.HierarchicalSelect.state["hs-" + hsid].resizedHeight = (height > defaultHeight) ? height : defaultHeight;
  149. }
  150. }
  151. };
  152. Drupal.HierarchicalSelect.resize = function($selects, defaultHeight, resizedHeight, defaultSize, margin) {
  153. if (resizedHeight == undefined) {
  154. resizedHeight = defaultHeight;
  155. }
  156. $selects
  157. .attr('size', (resizedHeight > defaultHeight) ? 2 : defaultSize)
  158. .height(Math.max(defaultHeight + margin, resizedHeight)); // Without the margin component, the height() method would allow the select to be sized to low: defaultHeight - margin.
  159. };
  160. Drupal.HierarchicalSelect.disableForm = function(hsid) {
  161. // Disable *all* submit buttons in this form, as well as all input-related
  162. // elements of the current hierarchical select.
  163. $('form:has(#hierarchical-select-' + hsid +'-wrapper) :submit')
  164. .add('#hierarchical-select-' + hsid +'-wrapper .hierarchical-select .selects select')
  165. .add('#hierarchical-select-' + hsid +'-wrapper .hierarchical-select :input')
  166. .attr('disabled', true);
  167. // Add the 'waiting' class. Default style: make everything transparent.
  168. $('#hierarchical-select-' + hsid +'-wrapper').addClass('waiting');
  169. // Indicate that the user has to wait.
  170. $('body').css('cursor', 'wait');
  171. };
  172. Drupal.HierarchicalSelect.enableForm = function(hsid) {
  173. // This method undoes everything the disableForm() method did.
  174. $e = $('form:has(#hierarchical-select-' + hsid +'-wrapper) :submit')
  175. .add('#hierarchical-select-' + hsid +'-wrapper .hierarchical-select :input:not(:submit)');
  176. // Don't enable the selects again if they've been disabled because the
  177. // dropbox limit was exceeded.
  178. dropboxLimitExceeded = $('#hierarchical-select-' + hsid +'-wrapper .hierarchical-select-dropbox-limit-warning').length > 0;
  179. if (!dropboxLimitExceeded) {
  180. $e = $e.add($('#hierarchical-select-' + hsid +'-wrapper .hierarchical-select .selects select'));
  181. }
  182. $e.removeAttr("disabled");
  183. // Don't enable the 'Add' button again if it's been disabled because the
  184. // dropbox limit was exceeded.
  185. if (dropboxLimitExceeded) {
  186. $('#hierarchical-select-' + hsid +'-wrapper .hierarchical-select :submit')
  187. .attr('disabled', true);
  188. }
  189. $('#hierarchical-select-' + hsid +'-wrapper').removeClass('waiting');
  190. $('body').css('cursor', 'auto');
  191. };
  192. Drupal.HierarchicalSelect.throwError = function(hsid, message) {
  193. // Show the error to the user.
  194. alert(message);
  195. // Log the error.
  196. Drupal.HierarchicalSelect.log(hsid, [ message ]);
  197. // Re-enable the form to allow the user to retry, but reset the selection to
  198. // the level label if possible, otherwise the "<none>" option if possible.
  199. var $select = $('#hierarchical-select-' + hsid +'-wrapper .hierarchical-select .selects select:first');
  200. var levelLabelOption = $('option[value^=label_]', $select).val();
  201. if (levelLabelOption !== undefined) {
  202. $select.val(levelLabelOption);
  203. }
  204. else {
  205. var noneOption = $('option[value=none]', $select).val();
  206. if (noneOption !== undefined) {
  207. $select.val(noneOption);
  208. }
  209. }
  210. Drupal.HierarchicalSelect.enableForm(hsid);
  211. };
  212. Drupal.HierarchicalSelect.prepareGETSubmit = function(hsid) {
  213. // Remove the name attributes of all form elements that end up in GET,
  214. // except for the "flat select" form element.
  215. $('#hierarchical-select-'+ hsid +'-wrapper', Drupal.HierarchicalSelect.context)
  216. .find('input, select')
  217. .not('.flat-select')
  218. .removeAttr('name');
  219. // Update the name attribute of the "flat select" form element
  220. var $flatSelect = $('#hierarchical-select-'+ hsid +'-wrapper .flat-select', Drupal.HierarchicalSelect.context);
  221. var newName = $flatSelect.attr('name').replace(/^([a-zA-Z0-9_\-]*)(?:\[flat_select\]){1}(\[\])?$/, "$1$2");
  222. $flatSelect.attr('name', newName);
  223. Drupal.HierarchicalSelect.triggerEvents(hsid, 'prepared-GET-submit', {});
  224. };
  225. Drupal.HierarchicalSelect.attachBindings = function(hsid) {
  226. var updateOpString = $('#hierarchical-select-'+ hsid +'-wrapper .update-button').val();
  227. var addOpString = $('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .add-to-dropbox', Drupal.HierarchicalSelect.context).val();
  228. var createNewItemOpString = $('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .create-new-item-create', Drupal.HierarchicalSelect.context).val();
  229. var cancelNewItemOpString = $('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .create-new-item-cancel', Drupal.HierarchicalSelect.context).val();
  230. var data = {};
  231. data.hsid = hsid;
  232. $('#hierarchical-select-'+ hsid +'-wrapper', this.context)
  233. // "disable-updates" event
  234. .unbind('disable-updates').bind('disable-updates', data, function(e) {
  235. Drupal.settings.HierarchicalSelect.settings["hs-" + e.data.hsid]['updatesEnabled'] = false;
  236. })
  237. // "enforce-update" event
  238. .unbind('enforce-update').bind('enforce-update', data, function(e, extraPost) {
  239. Drupal.HierarchicalSelect.update(e.data.hsid, 'enforced-update', { opString: updateOpString, extraPost: extraPost });
  240. })
  241. // "prepare-GET-submit" event
  242. .unbind('prepare-GET-submit').bind('prepare-GET-submit', data, function(e) {
  243. Drupal.HierarchicalSelect.prepareGETSubmit(e.data.hsid);
  244. })
  245. // "update-hierarchical-select" event
  246. .find('.hierarchical-select .selects select').unbind().change(function(_hsid) {
  247. return function() {
  248. if (Drupal.settings.HierarchicalSelect.settings["hs-" + _hsid]['updatesEnabled']) {
  249. Drupal.HierarchicalSelect.update(_hsid, 'update-hierarchical-select', { opString: updateOpString, select_id : $(this).attr('id') });
  250. }
  251. };
  252. }(hsid)).end()
  253. // "create-new-item" event
  254. .find('.hierarchical-select .create-new-item .create-new-item-create').unbind().click(function(_hsid) {
  255. return function() {
  256. Drupal.HierarchicalSelect.update(_hsid, 'create-new-item', { opString : createNewItemOpString });
  257. return false; // Prevent the browser from POSTing the page.
  258. };
  259. }(hsid)).end()
  260. // "cancel-new-item" event"
  261. .find('.hierarchical-select .create-new-item .create-new-item-cancel').unbind().click(function(_hsid) {
  262. return function() {
  263. Drupal.HierarchicalSelect.update(_hsid, 'cancel-new-item', { opString : cancelNewItemOpString });
  264. return false; // Prevent the browser from POSTing the page (in case of the "Cancel" button).
  265. };
  266. }(hsid)).end()
  267. // "add-to-dropbox" event
  268. .find('.hierarchical-select .add-to-dropbox').unbind().click(function(_hsid) {
  269. return function() {
  270. Drupal.HierarchicalSelect.update(_hsid, 'add-to-dropbox', { opString : addOpString });
  271. return false; // Prevent the browser from POSTing the page.
  272. };
  273. }(hsid)).end()
  274. // "remove-from-dropbox" event
  275. // (anchors in the .dropbox-remove cells in the .dropbox table)
  276. .find('.dropbox .dropbox-remove a').unbind().click(function(_hsid) {
  277. return function() {
  278. var isDisabled = $('#hierarchical-select-'+ hsid +'-wrapper', Drupal.HierarchicalSelect.context).attr('disabled');
  279. // If the hierarchical select is disabled, then ignore this click.
  280. if (isDisabled) {
  281. return false;
  282. }
  283. // Check the (hidden, because JS is enabled) checkbox that marks this
  284. // dropbox entry for removal.
  285. $(this).parent().find('input[type=checkbox]').attr('checked', true);
  286. Drupal.HierarchicalSelect.update(_hsid, 'remove-from-dropbox', { opString: updateOpString });
  287. return false; // Prevent the browser from POSTing the page.
  288. };
  289. }(hsid));
  290. };
  291. Drupal.HierarchicalSelect.preUpdateAnimations = function(hsid, updateType, lastUnchanged, callback) {
  292. switch (updateType) {
  293. case 'update-hierarchical-select':
  294. // Drop out the selects of the levels deeper than the select of the
  295. // level that just changed.
  296. var animationDelay = Drupal.settings.HierarchicalSelect.settings["hs-" + hsid]['animationDelay'];
  297. var $animatedSelects = $('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .selects select', Drupal.HierarchicalSelect.context).slice(lastUnchanged);
  298. if ($animatedSelects.size() > 0) {
  299. $animatedSelects.hide();
  300. for (var i = 0; i < $animatedSelects.size(); i++) {
  301. if (i < $animatedSelects.size() - 1) {
  302. $animatedSelects.slice(i, i + 1).hide("drop", { direction: "left" }, animationDelay);
  303. }
  304. else {
  305. $animatedSelects.slice(i, i + 1).hide("drop", { direction: "left" }, animationDelay, callback);
  306. }
  307. }
  308. }
  309. else if (callback) {
  310. callback();
  311. }
  312. break;
  313. default:
  314. if (callback) {
  315. callback();
  316. }
  317. break;
  318. }
  319. };
  320. Drupal.HierarchicalSelect.postUpdateAnimations = function(hsid, updateType, lastUnchanged, callback) {
  321. if (Drupal.settings.HierarchicalSelect.settings["hs-" + hsid].resizable) {
  322. // Restore the resize.
  323. Drupal.HierarchicalSelect.resize(
  324. $('#hierarchical-select-' + hsid + '-wrapper .hierarchical-select .selects select', Drupal.HierarchicalSelect.context),
  325. Drupal.HierarchicalSelect.state["hs-" + hsid].defaultHeight,
  326. Drupal.HierarchicalSelect.state["hs-" + hsid].resizedHeight,
  327. Drupal.HierarchicalSelect.state["hs-" + hsid].defaultSize,
  328. Drupal.HierarchicalSelect.state["hs-" + hsid].margin
  329. );
  330. }
  331. switch (updateType) {
  332. case 'update-hierarchical-select':
  333. var $createNewItemInput = $('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .create-new-item-input', Drupal.HierarchicalSelect.context);
  334. // Hide the loaded selects after the one that was just changed, then
  335. // drop them in.
  336. var animationDelay = Drupal.settings.HierarchicalSelect.settings["hs-" + hsid]['animationDelay'];
  337. var $animatedSelects = $('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .selects select', Drupal.HierarchicalSelect.context).slice(lastUnchanged);
  338. if ($animatedSelects.size() > 0) {
  339. $animatedSelects.hide();
  340. for (var i = 0; i < $animatedSelects.size(); i++) {
  341. if (i < $animatedSelects.size() - 1) {
  342. $animatedSelects.slice(i, i + 1).show("drop", { direction: "left" }, animationDelay);
  343. }
  344. else {
  345. $animatedSelects.slice(i, i + 1).show("drop", { direction: "left" }, animationDelay, callback);
  346. }
  347. }
  348. }
  349. else if (callback) {
  350. callback();
  351. }
  352. if ($createNewItemInput.size() == 0) {
  353. // Give focus to the level below the one that has changed, if it
  354. // exists.
  355. setTimeout(
  356. function() {
  357. $('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .selects select', Drupal.HierarchicalSelect.context)
  358. .slice(lastUnchanged, lastUnchanged + 1)
  359. .focus();
  360. },
  361. animationDelay + 100
  362. );
  363. }
  364. else {
  365. // Give focus to the input field of the "create new item/level"
  366. // section, if it exists, and also select the existing text.
  367. $createNewItemInput.focus();
  368. $createNewItemInput[0].select();
  369. }
  370. break;
  371. case 'create-new-item':
  372. // Make sure that other Hierarchical Selects that represent the same
  373. // hierarchy are also updated, to make sure that they have the newly
  374. // created item!
  375. var cacheId = Drupal.settings.HierarchicalSelect.settings["hs-" + hsid].cacheId;
  376. for (var otherHsid in Drupal.settings.HierarchicalSelect.settings) {
  377. if (Drupal.settings.HierarchicalSelect.settings[otherHsid].cacheId == cacheId) {
  378. $('#hierarchical-select-'+ otherHsid +'-wrapper')
  379. .trigger('enforce-update');
  380. }
  381. }
  382. // TRICKY: NO BREAK HERE!
  383. case 'cancel-new-item':
  384. // After an item/level has been created/cancelled, reset focus to the
  385. // beginning of the hierarchical select.
  386. $('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .selects select', Drupal.HierarchicalSelect.context)
  387. .slice(0, 1)
  388. .focus();
  389. if (callback) {
  390. callback();
  391. }
  392. break;
  393. default:
  394. if (callback) {
  395. callback();
  396. }
  397. break;
  398. }
  399. };
  400. Drupal.HierarchicalSelect.triggerEvents = function(hsid, updateType, settings) {
  401. $('#hierarchical-select-'+ hsid +'-wrapper', Drupal.HierarchicalSelect.context)
  402. .trigger(updateType, [ hsid, settings ]);
  403. };
  404. Drupal.HierarchicalSelect.update = function(hsid, updateType, settings) {
  405. var post = $('form:has(#hierarchical-select-' + hsid +'-wrapper)', Drupal.HierarchicalSelect.context).formToArray();
  406. var hs_current_language = Drupal.settings.HierarchicalSelect.hs_current_language;
  407. // Pass the hierarchical_select id via POST.
  408. post.push({ name : 'hsid', value : hsid });
  409. // Send the current language so we can use the same language during the AJAX callback.
  410. post.push({ name : 'hs_current_language', value : hs_current_language});
  411. // Emulate the AJAX data sent normally so that we get the same theme.
  412. post.push({ name : 'ajax_page_state[theme]', value : Drupal.settings.ajaxPageState.theme });
  413. post.push({ name : 'ajax_page_state[theme_token]', value : Drupal.settings.ajaxPageState.theme_token });
  414. // If a cache system is installed, let the server know if it's running
  415. // properly. If it is running properly, the server will send back additional
  416. // information to maintain a lazily-loaded cache.
  417. if (Drupal.HierarchicalSelect.cache != null) {
  418. post.push({ name : 'client_supports_caching', value : Drupal.HierarchicalSelect.cache.status() });
  419. }
  420. // updateType is one of:
  421. // - 'none' (default)
  422. // - 'update-hierarchical-select'
  423. // - 'enforced-update'
  424. // - 'create-new-item'
  425. // - 'cancel-new-item'
  426. // - 'add-to-dropbox'
  427. // - 'remove-from-dropbox'
  428. switch (updateType) {
  429. case 'update-hierarchical-select':
  430. var value = $('#'+ settings.select_id).val();
  431. var lastUnchanged = parseInt(settings.select_id.replace(/^.*-hierarchical-select-selects-(\d+)/, "$1")) + 1;
  432. var optionClass = $('#'+ settings.select_id).find('option[value="'+ value +'"]').attr('class');
  433. // Don't do anything (also no callback to the server!) when the selected
  434. // item is:
  435. // - the '<none>' option and the renderFlatSelect setting is disabled, or
  436. // - a level label, or
  437. // - an option of class 'has-no-children', and
  438. // (the renderFlatSelect setting is disabled or the dropbox is enabled)
  439. // and
  440. // (the createNewLevels setting is disabled).
  441. if ((value == 'none' && Drupal.settings.HierarchicalSelect.settings["hs-" + hsid]['renderFlatSelect'] == false)
  442. || value.match(/^label_\d+$/)
  443. || (optionClass == 'has-no-children'
  444. &&
  445. (
  446. (Drupal.settings.HierarchicalSelect.settings["hs-" + hsid]['renderFlatSelect'] == false
  447. || $('#hierarchical-select-'+ hsid +'-wrapper .dropbox').length > 0
  448. )
  449. &&
  450. Drupal.settings.HierarchicalSelect.settings["hs-" + hsid]['createNewLevels'] == false
  451. )
  452. )
  453. )
  454. {
  455. Drupal.HierarchicalSelect.preUpdateAnimations(hsid, updateType, lastUnchanged, function() {
  456. // Remove the sublevels.
  457. $('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .selects select', Drupal.HierarchicalSelect.context)
  458. .slice(lastUnchanged)
  459. .remove();
  460. // The selection of this hierarchical select has changed!
  461. Drupal.HierarchicalSelect.triggerEvents(hsid, 'change-hierarchical-select', settings);
  462. });
  463. return;
  464. }
  465. post.push({ name : 'op', value : settings.opString });
  466. break;
  467. case 'enforced-update':
  468. post.push({ name : 'op', value : settings.opString });
  469. post = post.concat(settings.extraPost);
  470. break;
  471. case 'create-new-item':
  472. case 'cancel-new-item':
  473. case 'add-to-dropbox':
  474. case 'remove-from-dropbox':
  475. post.push({ name : 'op', value : settings.opString });
  476. break;
  477. default:
  478. break;
  479. }
  480. // Construct the URL the request should be made to.
  481. var url = Drupal.settings.HierarchicalSelect.settings["hs-" + hsid].ajax_url;
  482. // Construct the object that contains the options for a callback to the
  483. // server. If a client-side cache is found however, it's possible that this
  484. // won't be used.
  485. var ajaxOptions = $.extend({}, Drupal.ajax.prototype, {
  486. url: url,
  487. type: 'POST',
  488. dataType: 'json',
  489. data: post,
  490. effect: 'fade',
  491. wrapper: '#hierarchical-select-' + hsid + '-wrapper',
  492. beforeSend: function() {
  493. Drupal.HierarchicalSelect.triggerEvents(hsid, 'before-' + updateType, settings);
  494. Drupal.HierarchicalSelect.disableForm(hsid);
  495. },
  496. error: function (XMLHttpRequest, textStatus, errorThrown) {
  497. // When invalid HTML is received in Safari, jQuery calls this function.
  498. Drupal.HierarchicalSelect.throwError(hsid, Drupal.t('Received an invalid response from the server.'));
  499. },
  500. success: function(response, status) {
  501. // An invalid response may be returned by the server, in case of a PHP
  502. // error. Detect this and let the user know.
  503. if (response === null || response.length == 0) {
  504. Drupal.HierarchicalSelect.throwError(hsid, Drupal.t('Received an invalid response from the server.'));
  505. return;
  506. }
  507. // Execute all AJAX commands in the response. But pass an additional
  508. // hsid parameter, which is then only used by the commands written
  509. // for Hierarchical Select.
  510. // This is another hack because of the non-Drupal ajax implementation
  511. // of this module, one of the response that can come from a drupal
  512. // ajax command is insert, which expects a Drupal.ajax object as the first
  513. // arguments and assumes that certain functions/settings are available.
  514. // Because we are calling a Drupal.ajax.command but providing the regular
  515. // jQuery ajax object itself, we are allowing Drupal.ajax.prototype.commands
  516. // to misserably fail.
  517. //
  518. // This hack attempts to fix one issue with an insert command,
  519. // @see https://www.drupal.org/node/2393695, allowing it to work properly
  520. // Other hacks might be necessary for other ajax commands if they are added
  521. // by external modules.
  522. this.effect = 'none';
  523. this.getEffect = Drupal.ajax.prototype.getEffect;
  524. for (var i in response) {
  525. if (response[i]['command'] && Drupal.ajax.prototype.commands[response[i]['command']]) {
  526. Drupal.ajax.prototype.commands[response[i]['command']](this, response[i], status, hsid);
  527. }
  528. }
  529. // Attach behaviors. This is just after the HTML has been updated, so
  530. // it's as soon as we can.
  531. Drupal.attachBehaviors($('#hierarchical-select-' + hsid + '-wrapper').parents('div.form-type-hierarchical-select')[0]);
  532. // Transform the hierarchical select and/or dropbox to the JS variant,
  533. // make it resizable again and re-enable the disabled form items.
  534. Drupal.HierarchicalSelect.enableForm(hsid);
  535. Drupal.HierarchicalSelect.postUpdateAnimations(hsid, updateType, lastUnchanged, function() {
  536. // Update the client-side cache when:
  537. // - information for in the cache is provided in the response, and
  538. // - the cache system is available, and
  539. // - the cache system is running.
  540. if (response.cache != null && Drupal.HierarchicalSelect.cache != null && Drupal.HierarchicalSelect.cache.status()) {
  541. Drupal.HierarchicalSelect.cache.sync(hsid, response.cache);
  542. }
  543. if (response.log != undefined) {
  544. Drupal.HierarchicalSelect.log(hsid, response.log);
  545. }
  546. Drupal.HierarchicalSelect.triggerEvents(hsid, updateType, settings);
  547. if (updateType == 'update-hierarchical-select') {
  548. // The selection of this hierarchical select has changed!
  549. Drupal.HierarchicalSelect.triggerEvents(hsid, 'change-hierarchical-select', settings);
  550. }
  551. });
  552. }
  553. });
  554. // Use the client-side cache to update the hierarchical select when:
  555. // - the hierarchical select is being updated (i.e. no add/remove), and
  556. // - the renderFlatSelect setting is disabled, and
  557. // - the createNewItems setting is disabled, and
  558. // - the cache system is available, and
  559. // - the cache system is running.
  560. // Otherwise, perform a normal dynamic form submit.
  561. if (updateType == 'update-hierarchical-select'
  562. && Drupal.settings.HierarchicalSelect.settings["hs-" + hsid]['renderFlatSelect'] == false
  563. && Drupal.settings.HierarchicalSelect.settings["hs-" + hsid]['createNewItems'] == false
  564. && Drupal.HierarchicalSelect.cache != null
  565. && Drupal.HierarchicalSelect.cache.status())
  566. {
  567. Drupal.HierarchicalSelect.cache.updateHierarchicalSelect(hsid, value, settings, lastUnchanged, ajaxOptions);
  568. }
  569. else {
  570. Drupal.HierarchicalSelect.preUpdateAnimations(hsid, updateType, lastUnchanged, function() {
  571. // Adding current theme to prevent conflicts, @see ajax.js
  572. // @TODO, try converting to use Drupal.ajax instead.
  573. // Prevent duplicate HTML ids in the returned markup.
  574. // @see drupal_html_id()
  575. var ids = [];
  576. $('[id]').each(function () {
  577. ids.push(this.id);
  578. });
  579. ajaxOptions.data.push({ name : 'ajax_html_ids[]', value : ids });
  580. ajaxOptions.data.push({ name : 'ajax_page_state[theme]', value : Drupal.settings.ajaxPageState.theme });
  581. ajaxOptions.data.push({ name : 'ajax_page_state[theme_token]', value : Drupal.settings.ajaxPageState.theme_token });
  582. for (var key in Drupal.settings.ajaxPageState.css) {
  583. ajaxOptions.data.push({ name : 'ajax_page_state[css][' + key + ']', value : 1});
  584. }
  585. for (var key in Drupal.settings.ajaxPageState.js) {
  586. ajaxOptions.data.push({ name : 'ajax_page_state[js][' + key + ']', value : 1});
  587. }
  588. // Make it work with jquery update
  589. if (Drupal.settings.ajaxPageState.jquery_version) {
  590. ajaxOptions.data.push({ name : 'ajax_page_state[jquery_version]', value : Drupal.settings.ajaxPageState.jquery_version });
  591. }
  592. $.ajax(ajaxOptions);
  593. });
  594. }
  595. };
  596. Drupal.ajax.prototype.commands.hierarchicalSelectUpdate = function(ajax, response, status, hsid) {
  597. // Replace the old HTML with the (relevant part of) retrieved HTML.
  598. $('#hierarchical-select-'+ hsid +'-wrapper', Drupal.HierarchicalSelect.context)
  599. .parent('.form-item')
  600. .replaceWith($(response.output));
  601. };
  602. Drupal.ajax.prototype.commands.hierarchicalSelectSettingsUpdate = function(ajax, response, status, hsid) {
  603. Drupal.settings.HierarchicalSelect.settings["hs-" + response.hsid] = response.settings;
  604. };
  605. })(jQuery);