re-implemented tracklist to term_reference_tree

This commit is contained in:
Bachir Soussi Chiadmi 2019-06-26 15:40:49 +02:00
parent b02f874d6b
commit 63592652b0
5 changed files with 165 additions and 204 deletions

View File

@ -14,6 +14,9 @@ field.widget.settings.term_reference_tree:
cascading_selection: cascading_selection:
type: integer type: integer
label: 'Cascading selection' label: 'Cascading selection'
track_list:
type: boolean
label: 'Track list'
max_depth: max_depth:
type: integer type: integer
label: 'Maximum Depth' label: 'Maximum Depth'

View File

@ -78,7 +78,43 @@ class CheckboxTree extends FormElement {
$tree = new \stdClass(); $tree = new \stdClass();
$tree->children = $terms; $tree->children = $terms;
unset($element['#needs_validation']); 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; return $element;
} }

View File

@ -2,6 +2,7 @@
namespace Drupal\term_reference_tree\Plugin\Field\FieldWidget; namespace Drupal\term_reference_tree\Plugin\Field\FieldWidget;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase; use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Form\FormStateInterface;
@ -37,6 +38,7 @@ class TermReferenceTree extends WidgetBase {
'leaves_only' => FALSE, 'leaves_only' => FALSE,
'select_parents' => FALSE, 'select_parents' => FALSE,
'cascading_selection' => self::CASCADING_SELECTION_NONE, 'cascading_selection' => self::CASCADING_SELECTION_NONE,
'track_list' => FALSE,
'max_depth' => 0, 'max_depth' => 0,
] + parent::defaultSettings(); ] + parent::defaultSettings();
} }
@ -94,6 +96,15 @@ class TermReferenceTree extends WidgetBase {
$form['cascading_selection']['#description'] .= ' <em>' . $this->t("This option is only valid if an unlimited number of values can be selected.") . '</em>'; $form['cascading_selection']['#description'] .= ' <em>' . $this->t("This option is only valid if an unlimited number of values can be selected.") . '</em>';
} }
$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'] = [ $form['max_depth'] = [
'#type' => 'number', '#type' => 'number',
'#title' => $this->t('Maximum Depth'), '#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')); $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')) { if ($this->getSetting('max_depth')) {
$summary[] = $this->formatPlural($this->getSetting('max_depth'), 'Maximum Depth: @count level', 'Maximum Depth: @count levels'); $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['#leaves_only'] = $this->getSetting('leaves_only');
$element['#select_parents'] = $this->getSetting('select_parents'); $element['#select_parents'] = $this->getSetting('select_parents');
$element['#cascading_selection'] = $this->getSetting('cascading_selection'); $element['#cascading_selection'] = $this->getSetting('cascading_selection');
$element['#track_list'] = $this->getSetting('track_list');
$element['#value_key'] = 'target_id'; $element['#value_key'] = 'target_id';
$element['#max_depth'] = $this->getSetting('max_depth'); $element['#max_depth'] = $this->getSetting('max_depth');
$element['#start_minimized'] = $this->getSetting('start_minimized'); $element['#start_minimized'] = $this->getSetting('start_minimized');
@ -189,6 +205,19 @@ class TermReferenceTree extends WidgetBase {
array_push($value, [$element['#value_key'] => $child['#value']]); 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 { else {
// If it's a tree of radio buttons, they all have the same value, // If it's a tree of radio buttons, they all have the same value,

View File

@ -33,84 +33,6 @@ Drupal.behaviors.termReferenceTree = {
$(this).find('.form-checkbox').parent().addClass('select-parents'); $(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. //On page load, check if the user wants a cascading selection.
if($(this).hasClass('term-reference-tree-cascading-selection')) { if($(this).hasClass('term-reference-tree-cascading-selection')) {
var mode_select = $(this).hasClass('term-reference-tree-cascading-selection-mode-select'); var mode_select = $(this).hasClass('term-reference-tree-cascading-selection-mode-select');
@ -140,121 +62,21 @@ Drupal.behaviors.termReferenceTree = {
} //End Want a cascading checking. } //End Want a cascading checking.
}); });
}
};
/** // track_list
* Add a new item to the track list. // just unselect items in tree on track_list button remove click
* If more than one item can be selected, the new item is positioned to // let builtin ajax form do the heavy work
* match the order of the terms in the checkbox tree. $('.term-reference-tree-track-list input[type="submit"][value="remove"]', context).click(function(e){
* e.preventDefault();
* @param track_list_container Container where the new item will be added. var key = $(this).attr('term-reference-tree-key');
* $('input[type="checkbox"][value="'+key+'"]', $(this).parents('.term-reference-tree'))
* @param item_text Text of the item to add. .prop('checked', false)
* .trigger('change');
* @param control_id Id of the checkbox/radio control the item matches. return false;
*
* @param control_type Control type - 'checkbox' or 'radio'.
*/
function addItemToTrackList(track_list_container, item_text, control_id, control_type) {
var new_item = $('<li class="track-item">' + item_text + '</li>');
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(
'<li class="term_ref_tree_nothing_message">' + termReferenceTreeNothingSelectedText + '</li>'
);
}
}
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. // 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. // If so, it disables all the other options. If not, it enables them.
@ -274,21 +96,10 @@ function checkMaxChoices(item, checkbox) {
if(checkbox) { if(checkbox) {
if(item.hasClass('term-reference-tree-select-parents')) { if(item.hasClass('term-reference-tree-select-parents')) {
var track_list_container = item.find('.term-reference-tree-track-list');
if(checkbox.prop('checked')) { if(checkbox.prop('checked')) {
checkbox.parents('ul.term-reference-tree-level li').children('div.form-item').find('input[type=checkbox]').each(function() { checkbox.parents('ul.term-reference-tree-level li').children('div.form-item').find('input[type=checkbox]').each(function() {
$(this).prop('checked', true); $(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'
);
}
}); });
} }
} }

View File

@ -1,6 +1,9 @@
<?php <?php
use \Drupal\Core\Render\Element; use \Drupal\Core\Render\Element;
use Drupal\Core\Form\FormState;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\ReplaceCommand;
/** /**
* Implements hook_theme(). * Implements hook_theme().
@ -357,7 +360,11 @@ function _term_reference_tree_build_item($element, $term, $form_state, $value, $
'#parent_values' => $parent_tids, '#parent_values' => $parent_tids,
'#default_value' => isset($value[$term->tid]) ? $term->tid : NULL, '#default_value' => isset($value[$term->tid]) ? $term->tid : NULL,
'#attributes' => isset($element['#attributes']) ? $element['#attributes'] : 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') { if ($e['#type'] == 'radio') {
@ -496,3 +503,78 @@ function _term_reference_tree_output_list_level(&$element, &$tree) {
} }
return $output; 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' => '<aside class="term-reference-tree-track-list">',
'#suffix' => '</aside>',
'#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;
}