From 63592652b06e7cca919dd40b3f7be2cc99123b7c Mon Sep 17 00:00:00 2001 From: Bachir Soussi Chiadmi Date: Wed, 26 Jun 2019 15:40:49 +0200 Subject: [PATCH] re-implemented tracklist to term_reference_tree --- .../schema/term_reference_tree.schema.yml | 3 + .../src/Element/CheckboxTree.php | 38 +++- .../Field/FieldWidget/TermReferenceTree.php | 29 +++ .../term_reference_tree.js | 215 ++---------------- .../term_reference_tree.module | 84 ++++++- 5 files changed, 165 insertions(+), 204 deletions(-) diff --git a/web/modules/dev/term_reference_tree/config/schema/term_reference_tree.schema.yml b/web/modules/dev/term_reference_tree/config/schema/term_reference_tree.schema.yml index d0834a44..70145972 100644 --- a/web/modules/dev/term_reference_tree/config/schema/term_reference_tree.schema.yml +++ b/web/modules/dev/term_reference_tree/config/schema/term_reference_tree.schema.yml @@ -14,6 +14,9 @@ field.widget.settings.term_reference_tree: cascading_selection: type: integer label: 'Cascading selection' + track_list: + type: boolean + label: 'Track list' max_depth: type: integer label: 'Maximum Depth' diff --git a/web/modules/dev/term_reference_tree/src/Element/CheckboxTree.php b/web/modules/dev/term_reference_tree/src/Element/CheckboxTree.php index d79be89c..eedabd6a 100644 --- a/web/modules/dev/term_reference_tree/src/Element/CheckboxTree.php +++ b/web/modules/dev/term_reference_tree/src/Element/CheckboxTree.php @@ -78,7 +78,43 @@ class CheckboxTree extends FormElement { $tree = new \stdClass(); $tree->children = $terms; unset($element['#needs_validation']); - $element[] = _term_reference_tree_build_level($element, $tree, $form_state, $value, $element['#max_choices'], [], 1); + $element['checkbox_tree'] = _term_reference_tree_build_level($element, $tree, $form_state, $value, $element['#max_choices'], [], 1); + + // track list + if ($element['#track_list']) { + if($form_state->getTriggeringElement()){ + // we are on ajax + // $value is not updated on ajax callback + // we need an other way to get wich options are checked + // and give an accurate track list + + $parent = $element['#parents'][0]; // get the element's parent field + $input = $form_state->getUserInput(); // get all inputs from form state + $checkbox_tree_input = $input[$parent]['checkbox_tree']; // get the chekbox_tree input + $selected = _term_reference_tree_get_flatten_selected_values($checkbox_tree_input); // get selected flattenized + + $track_list_input = $input[$parent]['track_list']; // get the current track_list input with correct order + $old_list = array_keys($track_list_input); // flattenize + + // remove all chebox_tree input unselected from the list + $list = []; + foreach ($old_list as $id) { + if(in_array($id, $selected)){ + $list[] = $id; + } + } + // append newly selected + $diff = array_diff($selected, $list); + foreach ($diff as $id) { + $list[] = $id; + } + }else{ + // if not ajax just get the default value + $list = $value; + } + // build the track_list form element + $element['track_list'] = _term_reference_tree_build_track_list_order($list, $element['#options']); + } return $element; } diff --git a/web/modules/dev/term_reference_tree/src/Plugin/Field/FieldWidget/TermReferenceTree.php b/web/modules/dev/term_reference_tree/src/Plugin/Field/FieldWidget/TermReferenceTree.php index c89c115f..0d5f47b7 100644 --- a/web/modules/dev/term_reference_tree/src/Plugin/Field/FieldWidget/TermReferenceTree.php +++ b/web/modules/dev/term_reference_tree/src/Plugin/Field/FieldWidget/TermReferenceTree.php @@ -2,6 +2,7 @@ namespace Drupal\term_reference_tree\Plugin\Field\FieldWidget; +use Drupal\Component\Utility\NestedArray; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\WidgetBase; use Drupal\Core\Form\FormStateInterface; @@ -37,6 +38,7 @@ class TermReferenceTree extends WidgetBase { 'leaves_only' => FALSE, 'select_parents' => FALSE, 'cascading_selection' => self::CASCADING_SELECTION_NONE, + 'track_list' => FALSE, 'max_depth' => 0, ] + parent::defaultSettings(); } @@ -94,6 +96,15 @@ class TermReferenceTree extends WidgetBase { $form['cascading_selection']['#description'] .= ' ' . $this->t("This option is only valid if an unlimited number of values can be selected.") . ''; } + $form['track_list'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Track list'), + '#description' => $this->t('Track what the user has chosen in a list below the tree. + Useful when the tree is large, with many levels.'), + '#default_value' => $this->getSetting('track_list'), + '#return_value' => 1, + ]; + $form['max_depth'] = [ '#type' => 'number', '#title' => $this->t('Maximum Depth'), @@ -134,6 +145,10 @@ class TermReferenceTree extends WidgetBase { $summary[] = sprintf('%s (%s)', $this->t('Cascading selection'), $this->t('Only deselect')); } + if ($this->getSetting('track_list')) { + $summary[] = $this->t('Track list'); + } + if ($this->getSetting('max_depth')) { $summary[] = $this->formatPlural($this->getSetting('max_depth'), 'Maximum Depth: @count level', 'Maximum Depth: @count levels'); } @@ -156,6 +171,7 @@ class TermReferenceTree extends WidgetBase { $element['#leaves_only'] = $this->getSetting('leaves_only'); $element['#select_parents'] = $this->getSetting('select_parents'); $element['#cascading_selection'] = $this->getSetting('cascading_selection'); + $element['#track_list'] = $this->getSetting('track_list'); $element['#value_key'] = 'target_id'; $element['#max_depth'] = $this->getSetting('max_depth'); $element['#start_minimized'] = $this->getSetting('start_minimized'); @@ -189,6 +205,19 @@ class TermReferenceTree extends WidgetBase { array_push($value, [$element['#value_key'] => $child['#value']]); } } + // if track_list enabled, reorder the value array regarding the tablegrag input + if (!empty($value) && $element['#track_list']) { + $input = &$form_state->getUserInput(); + $nested_input = NestedArray::getValue($form_state->getValues(), $element['#parents']); + if (is_array($nested_input['track_list'])) { + $track_list = array_keys($nested_input['track_list']); + $sorted_value = []; + foreach ($track_list as $id) { + $sorted_value[] = $value[array_search($id, array_column($value, 'target_id'))]; + } + $value = $sorted_value; + } + } } else { // If it's a tree of radio buttons, they all have the same value, diff --git a/web/modules/dev/term_reference_tree/term_reference_tree.js b/web/modules/dev/term_reference_tree/term_reference_tree.js index 864a084b..d9f6c268 100644 --- a/web/modules/dev/term_reference_tree/term_reference_tree.js +++ b/web/modules/dev/term_reference_tree/term_reference_tree.js @@ -33,84 +33,6 @@ Drupal.behaviors.termReferenceTree = { $(this).find('.form-checkbox').parent().addClass('select-parents'); } - //On page load, check if the user wants a track list. If so, add the - //currently selected items to it. - if($(this).hasClass('term-reference-tree-track-list-shown')) { - var track_list_container = $(this).find('.term-reference-tree-track-list'); - - //Var to track whether using checkboxes or radio buttons. - var input_type = - ( $(this).has('input[type=checkbox]').length > 0 ) ? 'checkbox' : 'radio'; - - //Find all the checked controls. - var checked_controls = $(this).find('input[type=' + input_type + ']:checked'); - - //Get their labels. - var labels = checked_controls.next(); - var label_element; - - //For each label of the checked boxes, add item to the track list. - labels.each(function(index) { - label_element = $(labels[index]); - addItemToTrackList( - track_list_container, //Where to add new item. - label_element.html(), //Text of new item. - $(label_element).attr('for'), //Id of control new item is for. - input_type //checkbox or radio - ); - }); //End labels.each - - //Show "nothing selected" message, if needed. - showNothingSelectedMessage(track_list_container); - - //Event - when an element on the track list is clicked on: - // 1. Delete it. - // 2. Uncheck the associated checkbox. - //The event is bound to the track list container, not each element. - $(track_list_container).click(function(event){ - //Remove the "nothing selected" message if showing - add it later if needed. - //removeNothingSelectedMessage(track_list_container); - var event_target = $(event.target); - var control_id = event_target.data('control_id'); - - if(control_id) { - event_target.remove(); - - var checkbox = $('#' + control_id); - checkbox.removeAttr('checked'); - checkMaxChoices(tree, checkbox); - - //Show "nothing selected" message, if needed. - showNothingSelectedMessage(track_list_container); - } - }); - - //Change track list when controls are clicked. - $(this).find('.form-' + input_type).change(function(event){ - //Remove the "nothing selected" message if showing - add it later if needed. - removeNothingSelectedMessage(track_list_container); - var event_target = $(event.target); - var control_id = event_target.attr('id'); - if ( event_target.attr('checked') ) { - //Control checked - add item to the track list. - label_element = event_target.next(); - addItemToTrackList( - track_list_container, //Where to add new item. - label_element.html(), //Text of new item. - $(label_element).attr('for'), //Id of control new item is for. - input_type //checkbox or radio - ); - } - else { - //Checkbox unchecked. Remove from the track list. - $('#' + control_id + '_list').remove(); - } - - //Show "nothing selected" message, if needed. - showNothingSelectedMessage(track_list_container); - }); //End process checkbox changes. - } //End Want a track list. - //On page load, check if the user wants a cascading selection. if($(this).hasClass('term-reference-tree-cascading-selection')) { var mode_select = $(this).hasClass('term-reference-tree-cascading-selection-mode-select'); @@ -140,122 +62,22 @@ Drupal.behaviors.termReferenceTree = { } //End Want a cascading checking. }); + + // track_list + // just unselect items in tree on track_list button remove click + // let builtin ajax form do the heavy work + $('.term-reference-tree-track-list input[type="submit"][value="remove"]', context).click(function(e){ + e.preventDefault(); + var key = $(this).attr('term-reference-tree-key'); + $('input[type="checkbox"][value="'+key+'"]', $(this).parents('.term-reference-tree')) + .prop('checked', false) + .trigger('change'); + return false; + }); + } }; -/** - * Add a new item to the track list. - * If more than one item can be selected, the new item is positioned to - * match the order of the terms in the checkbox tree. - * - * @param track_list_container Container where the new item will be added. - * - * @param item_text Text of the item to add. - * - * @param control_id Id of the checkbox/radio control the item matches. - * - * @param control_type Control type - 'checkbox' or 'radio'. - */ -function addItemToTrackList(track_list_container, item_text, control_id, control_type) { - var new_item = $('
  • ' + item_text + '
  • '); - new_item.data('control_id', control_id); - - //Add an id for easy finding of the item. - new_item.attr('id', control_id + '_list'); - - //Process radio controls - only one item can be selected. - if ( control_type == 'radio') { - //Find the existing element on the track list, if there is one. - var current_items = track_list_container.find('li'); - - //If there are no items on the track list, add the new item. - if ( current_items.length == 0 ) { - track_list_container.append(new_item); - } - else { - //There is an item on the list. - var current_item = $(current_items.get(0)); - - //Is the item we want to add different from what is there? - if ( current_item.data('control_id') != control_id ) { - //Remove exiting element from track list, and add the new one. - current_item.remove(); - track_list_container.append(new_item); - } - } - return; - } - - //Using checkboxes, so there can be more than one selected item. - //Find the right place to put the new item, to match the order of the - //checkboxes. - var list_items = track_list_container.find('li'); - var item_comparing_to; - - //Flag to tell whether the item was inserted. - var inserted_flag = false; - list_items.each(function(index){ - item_comparing_to = $(list_items[index]); - - //If item is already on the track list, do nothing. - if ( control_id == item_comparing_to.data('control_id') ) { - inserted_flag = true; - return false; //Returning false stops the loop. - } - else if ( control_id < item_comparing_to.data('control_id') ) { - //Add it here. - item_comparing_to.before(new_item); - inserted_flag = true; - return false; //Returning false stops the loop. - } - }); - - //If not inserted yet, add new item at the end of the track list. - if ( ! inserted_flag ) { - track_list_container.append(new_item); - } -} - -/** - * Show the 'nothing selected' message if it applies. - * - * @param track_list_container Where the message is to be shown. - */ -function showNothingSelectedMessage(track_list_container) { - //Is the message there already? - var message_showing = - (track_list_container.find('.term_ref_tree_nothing_message').length != 0); - - //Number of real items showing. - var num_real_items_showing = - message_showing - ? track_list_container.find('li').length - 1 - : track_list_container.find('li').length; - if ( num_real_items_showing == 0 ) { - //No items showing, so show the message. - if ( ! message_showing ) { - track_list_container.append( - '
  • ' + termReferenceTreeNothingSelectedText + '
  • ' - ); - } - } - else { // !(num_real_items_showing == 0) - //There are real items. - if ( message_showing ) { - track_list_container.find('.term_ref_tree_nothing_message').remove(); - } - } -} - -/** - * Remove the 'nothing selected' message. Makes processing easier. - * - * @param track_list_container Where the message is shown. - */ -function removeNothingSelectedMessage(track_list_container) { - track_list_container.find('.term_ref_tree_nothing_message').remove(); -} - // This helper function checks if the maximum number of choices is already selected. // If so, it disables all the other options. If not, it enables them. function checkMaxChoices(item, checkbox) { @@ -274,21 +96,10 @@ function checkMaxChoices(item, checkbox) { if(checkbox) { if(item.hasClass('term-reference-tree-select-parents')) { - var track_list_container = item.find('.term-reference-tree-track-list'); if(checkbox.prop('checked')) { checkbox.parents('ul.term-reference-tree-level li').children('div.form-item').find('input[type=checkbox]').each(function() { $(this).prop('checked', true); - - if(track_list_container) { - var label_element = $(this).next(); - addItemToTrackList( - track_list_container, //Where to add new item. - label_element.html(), //Text of new item. - $(label_element).attr('for'), //Id of control new item is for. - (item.has('input[type=checkbox]').length > 0) ? 'checkbox' : 'radio' - ); - } }); } } diff --git a/web/modules/dev/term_reference_tree/term_reference_tree.module b/web/modules/dev/term_reference_tree/term_reference_tree.module index 2f50e357..148e1a3d 100644 --- a/web/modules/dev/term_reference_tree/term_reference_tree.module +++ b/web/modules/dev/term_reference_tree/term_reference_tree.module @@ -1,6 +1,9 @@ $parent_tids, '#default_value' => isset($value[$term->tid]) ? $term->tid : NULL, '#attributes' => isset($element['#attributes']) ? $element['#attributes'] : NULL, - '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL, + '#ajax' => array( + 'callback' => '_term_reference_tree_item_changed_ajax_callback', + 'event' => 'change', + 'message' => '', + ) ); if ($e['#type'] == 'radio') { @@ -496,3 +503,78 @@ function _term_reference_tree_output_list_level(&$element, &$tree) { } return $output; } + +/** + * Helper function to output a dragtable as track_list. + */ +function _term_reference_tree_build_track_list_order($value, $options){ + // define the tabledrag container + $table = array( + '#type' => 'table', + '#prefix' => '', + '#header' => array('Label', 'weight', 'remove'), + '#tabledrag' => array( + array( + 'action' => 'order', + 'relationship' => 'sibling', + 'group' => 'tracklist-order-weight', + ), + ), + ); + // define the table rows + $index = 0; + foreach ($value as $key) { + $table[$key] = [ + '#attributes' => array( + 'class' => ['draggable'] + ), + '#weight' => $index, + 'label' => array( + '#plain_text' => $options[$key], + ), + 'weight' => array( + '#type' => 'weight', + '#title' => t('Weight for @title', array('@title' => $options[$key])), + '#title_display' => 'invisible', + '#default_value' => $index, + // Classify the weight element for #tabledrag. + '#attributes' => array('class' => array('tracklist-order-weight')), + ), + 'remove' => array( + '#type' => 'button', + '#value' => 'remove', + '#attributes' => array( + 'term-reference-tree-key' => $key, + ) + ) + ]; + $index ++; + } + return $table; +} + +function _term_reference_tree_item_changed_ajax_callback(array &$form, FormState $form_state){ + $trigger = $form_state->getTriggeringElement(); // get the trigger element (a term_ref_tree checkbox) + $parent = $trigger['#parents'][0]; // get the element's parent field name + $track_list_form = $form[$parent]['widget']['track_list']; // extract the track_list form part + // ajax response + $response = new AjaxResponse(); + $response->addCommand(new ReplaceCommand('.term-reference-tree-track-list', $track_list_form)); + return $response; +} + +function _term_reference_tree_get_flatten_selected_values($tree){ + $selected = []; + foreach ($tree as $key => $value) { + if ( array_key_exists($key, $value) ) { + if( $value[$key] == $key ){ + $selected[] = $key; + } + } + if( array_key_exists($key.'-children', $value) ){ + $selected += _term_reference_tree_get_flatten_selected_values($value[$key.'-children']); + } + } + return $selected; +}