term_reference_tree.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. (function($) {
  2. /**
  3. * Attaches the tree behavior to the term widget form.
  4. */
  5. Drupal.behaviors.termReferenceTree = {
  6. attach: function(context, settings) {
  7. // Bind the term expand/contract button to slide toggle the list underneath.
  8. $('.term-reference-tree-button', context).once('term-reference-tree-button').click(function() {
  9. $(this).toggleClass('term-reference-tree-collapsed');
  10. $(this).siblings('ul').slideToggle('fast');
  11. });
  12. // An expand all button (unimplemented)
  13. /*
  14. $('.expandbutton').click(function() {
  15. $(this).siblings('.term-reference-tree-button').trigger('click');
  16. });
  17. */
  18. $('.term-reference-tree', context).once('term-reference-tree').each(function() {
  19. // On page load, check whether the maximum number of choices is already selected.
  20. // If so, disable the other options.
  21. var tree = $(this);
  22. checkMaxChoices(tree, false);
  23. $(this).find('input[type=checkbox]').change(function() {
  24. checkMaxChoices(tree, $(this));
  25. });
  26. //On page load, check if the user wants a cascading selection.
  27. if($(this).hasClass('term-reference-tree-select-parents')) {
  28. $(this).find('.form-checkbox').parent().addClass('select-parents');
  29. }
  30. //On page load, check if the user wants a track list. If so, add the
  31. //currently selected items to it.
  32. if($(this).hasClass('term-reference-tree-track-list-shown')) {
  33. var track_list_container = $(this).find('.term-reference-tree-track-list');
  34. //Var to track whether using checkboxes or radio buttons.
  35. var input_type =
  36. ( $(this).has('input[type=checkbox]').length > 0 ) ? 'checkbox' : 'radio';
  37. //Find all the checked controls.
  38. var checked_controls = $(this).find('input[type=' + input_type + ']:checked');
  39. //Get their labels.
  40. var labels = checked_controls.next();
  41. var label_element;
  42. //For each label of the checked boxes, add item to the track list.
  43. labels.each(function(index) {
  44. label_element = $(labels[index]);
  45. addItemToTrackList(
  46. track_list_container, //Where to add new item.
  47. label_element.html(), //Text of new item.
  48. $(label_element).attr('for'), //Id of control new item is for.
  49. input_type //checkbox or radio
  50. );
  51. }); //End labels.each
  52. //Show "nothing selected" message, if needed.
  53. showNothingSelectedMessage(track_list_container);
  54. //Event - when an element on the track list is clicked on:
  55. // 1. Delete it.
  56. // 2. Uncheck the associated checkbox.
  57. //The event is bound to the track list container, not each element.
  58. $(track_list_container).click(function(event){
  59. //Remove the "nothing selected" message if showing - add it later if needed.
  60. //removeNothingSelectedMessage(track_list_container);
  61. var event_target = $(event.target);
  62. var control_id = event_target.data('control_id');
  63. if(control_id) {
  64. event_target.remove();
  65. var checkbox = $('#' + control_id);
  66. checkbox.removeAttr('checked');
  67. checkMaxChoices(tree, checkbox);
  68. //Show "nothing selected" message, if needed.
  69. showNothingSelectedMessage(track_list_container);
  70. }
  71. });
  72. //Change track list when controls are clicked.
  73. $(this).find('.form-' + input_type).change(function(event){
  74. //Remove the "nothing selected" message if showing - add it later if needed.
  75. removeNothingSelectedMessage(track_list_container);
  76. var event_target = $(event.target);
  77. var control_id = event_target.attr('id');
  78. if ( event_target.attr('checked') ) {
  79. //Control checked - add item to the track list.
  80. label_element = event_target.next();
  81. addItemToTrackList(
  82. track_list_container, //Where to add new item.
  83. label_element.html(), //Text of new item.
  84. $(label_element).attr('for'), //Id of control new item is for.
  85. input_type //checkbox or radio
  86. );
  87. }
  88. else {
  89. //Checkbox unchecked. Remove from the track list.
  90. $('#' + control_id + '_list').remove();
  91. }
  92. //Show "nothing selected" message, if needed.
  93. showNothingSelectedMessage(track_list_container);
  94. }); //End process checkbox changes.
  95. } //End Want a track list.
  96. //On page load, check if the user wants a cascading selection.
  97. if($(this).hasClass('term-reference-tree-cascading-selection')) {
  98. var mode_select = $(this).hasClass('term-reference-tree-cascading-selection-mode-select');
  99. var mode_deselect = $(this).hasClass('term-reference-tree-cascading-selection-mode-deselect');
  100. //Check children when checkboxes are clicked.
  101. $(this).find('.form-checkbox').change(function(event) {
  102. var event_target = $(event.target);
  103. var event_target_checked = event_target.is(':checked');
  104. var control_id = event_target.attr('id');
  105. var children = event_target.parent().next().children().find('> :not(ul) > input[id^="' + control_id + '-children"]');
  106. if (!mode_select && !mode_deselect) {
  107. if(event_target_checked) {
  108. $(children).filter(':not(:checked)').click().trigger('change');
  109. }
  110. else {
  111. $(children).filter(':checked').click().trigger('change');
  112. }
  113. } else if (mode_select && event_target_checked) {
  114. $(children).filter(':not(:checked)').click().trigger('change');
  115. } else if (mode_deselect && !event_target_checked) {
  116. $(children).filter(':checked').click().trigger('change');
  117. }
  118. });
  119. //End process checkbox changes.
  120. } //End Want a cascading checking.
  121. });
  122. }
  123. };
  124. /**
  125. * Add a new item to the track list.
  126. * If more than one item can be selected, the new item is positioned to
  127. * match the order of the terms in the checkbox tree.
  128. *
  129. * @param track_list_container Container where the new item will be added.
  130. *
  131. * @param item_text Text of the item to add.
  132. *
  133. * @param control_id Id of the checkbox/radio control the item matches.
  134. *
  135. * @param control_type Control type - 'checkbox' or 'radio'.
  136. */
  137. function addItemToTrackList(track_list_container, item_text, control_id, control_type) {
  138. var new_item = $('<li class="track-item">' + item_text + '</li>');
  139. new_item.data('control_id', control_id);
  140. //Add an id for easy finding of the item.
  141. new_item.attr('id', control_id + '_list');
  142. //Process radio controls - only one item can be selected.
  143. if ( control_type == 'radio') {
  144. //Find the existing element on the track list, if there is one.
  145. var current_items = track_list_container.find('li');
  146. //If there are no items on the track list, add the new item.
  147. if ( current_items.length == 0 ) {
  148. track_list_container.append(new_item);
  149. }
  150. else {
  151. //There is an item on the list.
  152. var current_item = $(current_items.get(0));
  153. //Is the item we want to add different from what is there?
  154. if ( current_item.data('control_id') != control_id ) {
  155. //Remove exiting element from track list, and add the new one.
  156. current_item.remove();
  157. track_list_container.append(new_item);
  158. }
  159. }
  160. return;
  161. }
  162. //Using checkboxes, so there can be more than one selected item.
  163. //Find the right place to put the new item, to match the order of the
  164. //checkboxes.
  165. var list_items = track_list_container.find('li');
  166. var item_comparing_to;
  167. //Flag to tell whether the item was inserted.
  168. var inserted_flag = false;
  169. list_items.each(function(index){
  170. item_comparing_to = $(list_items[index]);
  171. //If item is already on the track list, do nothing.
  172. if ( control_id == item_comparing_to.data('control_id') ) {
  173. inserted_flag = true;
  174. return false; //Returning false stops the loop.
  175. }
  176. else if ( control_id < item_comparing_to.data('control_id') ) {
  177. //Add it here.
  178. item_comparing_to.before(new_item);
  179. inserted_flag = true;
  180. return false; //Returning false stops the loop.
  181. }
  182. });
  183. //If not inserted yet, add new item at the end of the track list.
  184. if ( ! inserted_flag ) {
  185. track_list_container.append(new_item);
  186. }
  187. }
  188. /**
  189. * Show the 'nothing selected' message if it applies.
  190. *
  191. * @param track_list_container Where the message is to be shown.
  192. */
  193. function showNothingSelectedMessage(track_list_container) {
  194. //Is the message there already?
  195. var message_showing =
  196. (track_list_container.find('.term_ref_tree_nothing_message').length != 0);
  197. //Number of real items showing.
  198. var num_real_items_showing =
  199. message_showing
  200. ? track_list_container.find('li').length - 1
  201. : track_list_container.find('li').length;
  202. if ( num_real_items_showing == 0 ) {
  203. //No items showing, so show the message.
  204. if ( ! message_showing ) {
  205. track_list_container.append(
  206. '<li class="term_ref_tree_nothing_message">' + termReferenceTreeNothingSelectedText + '</li>'
  207. );
  208. }
  209. }
  210. else { // !(num_real_items_showing == 0)
  211. //There are real items.
  212. if ( message_showing ) {
  213. track_list_container.find('.term_ref_tree_nothing_message').remove();
  214. }
  215. }
  216. }
  217. /**
  218. * Remove the 'nothing selected' message. Makes processing easier.
  219. *
  220. * @param track_list_container Where the message is shown.
  221. */
  222. function removeNothingSelectedMessage(track_list_container) {
  223. track_list_container.find('.term_ref_tree_nothing_message').remove();
  224. }
  225. // This helper function checks if the maximum number of choices is already selected.
  226. // If so, it disables all the other options. If not, it enables them.
  227. function checkMaxChoices(item, checkbox) {
  228. var maxChoices = -1;
  229. try {
  230. maxChoices = parseInt(Drupal.settings.term_reference_tree.trees[item.attr('id')]['max_choices']);
  231. }
  232. catch (e){}
  233. var count = item.find(':checked').length;
  234. if(maxChoices > 0 && count >= maxChoices) {
  235. item.find('input[type=checkbox]:not(:checked)').attr('disabled', 'disabled').parent().addClass('disabled');
  236. } else {
  237. item.find('input[type=checkbox]').removeAttr('disabled').parent().removeClass('disabled');
  238. }
  239. if(checkbox) {
  240. if(item.hasClass('term-reference-tree-select-parents')) {
  241. var track_list_container = item.find('.term-reference-tree-track-list');
  242. if(checkbox.prop('checked')) {
  243. checkbox.parents('ul.term-reference-tree-level li').children('div.form-item').find('input[type=checkbox]').each(function() {
  244. $(this).prop('checked', true);
  245. if(track_list_container) {
  246. var label_element = $(this).next();
  247. addItemToTrackList(
  248. track_list_container, //Where to add new item.
  249. label_element.html(), //Text of new item.
  250. $(label_element).attr('for'), //Id of control new item is for.
  251. (item.has('input[type=checkbox]').length > 0) ? 'checkbox' : 'radio'
  252. );
  253. }
  254. });
  255. }
  256. }
  257. }
  258. }
  259. })(jQuery);