From 5aa230e09318fbd6a894f7a064fda23a665fb55e Mon Sep 17 00:00:00 2001
From: Mingsong <amds@2986445.no-reply.drupal.org>
Date: Mon, 2 May 2022 00:37:54 +0000
Subject: [PATCH] Issue #3205538: Add (optional) confirmation dialog after
 drag&drop

---
 config/schema/hm_display_profile.schema.yml   |   3 +
 js/Plugin/jstree/hm.jstree.js                 | 163 ++++++++++++------
 src/Entity/HmDisplayProfile.php               |   8 +
 src/Form/HmDisplayProfileForm.php             |  10 ++
 src/Form/HmMenuForm.php                       |   3 +-
 src/Form/HmOverviewTerms.php                  |   3 +-
 .../HmDisplayPlugin/HmDisplayJstree.php       |   3 +-
 7 files changed, 142 insertions(+), 51 deletions(-)

diff --git a/config/schema/hm_display_profile.schema.yml b/config/schema/hm_display_profile.schema.yml
index 6f91e0f..6be45fe 100644
--- a/config/schema/hm_display_profile.schema.yml
+++ b/config/schema/hm_display_profile.schema.yml
@@ -16,3 +16,6 @@ hierarchy_manager.hm_display_profile.*:
     config:
       type: text
       label: 'Configuration'
+    confirm:
+      type: boolean
+      label: 'Confirm'
diff --git a/js/Plugin/jstree/hm.jstree.js b/js/Plugin/jstree/hm.jstree.js
index 15c2bb7..ed9b21b 100644
--- a/js/Plugin/jstree/hm.jstree.js
+++ b/js/Plugin/jstree/hm.jstree.js
@@ -17,6 +17,7 @@
           const optionsJson = treeContainer.attr("options");
           const dataURL = treeContainer.attr('data-source') + '&parent=0';
           const updateURL = treeContainer.attr('url-update');
+          const confirm = treeContainer.attr("confirm");
           let reload = true;
           let rollback = false;
           let themes = {
@@ -103,61 +104,80 @@
               const drupalMessages = new Drupal.Message();
 
               if (!rollback) {
-                // Update the data on server side.
-                $.post(updateURL, {
-                  keys: [movedNode.id],
-                  target: data.position,
-                  parent: parent,
-                  old_parent: old_parent,
-                  old_position: data.old_position
-                })
-                  .done(function(response) {
-                    if (response.result !== "success") {
-                      alert("Server error:" + response.result);
-                      rollback = true;
-                      thisTree.move_node(movedNode, data.old_parent, data.old_position);
-                    }
-                    else {
-                      if (parent === 0) {
-                        var parentText = Drupal.t('root');
-                      }
-                      else {
-                        var parentText = parent_node.text;
-                      }
-                      if (parent_node.data && !parent_node.data.draggable) {
-                        // The parent node is not draggable.
-                        // We have to update all duplicated nodes
-                        // by refreshing the whole tree.
-                        thisTree.refresh();
+                let parentText = Drupal.t('root');
+                if (parent !== 0) {
+                  parentText = $("<div/>").html(thisTree.get_node(parent).text);
+                  parentText.find("span").remove();
+                  parentText = parentText.text();
+                }
+                // Function to move the tree item.
+                function moveTreeItem() {
+                  // Update the data on server side.
+                  $.post(updateURL, {
+                    keys: [movedNode.id],
+                    target: data.position,
+                    parent: parent,
+                    old_parent: old_parent,
+                    old_position: data.old_position
+                  })
+                    .done(function(response) {
+                      if (response.result !== "success") {
+                        alert("Server error:" + response.result);
+                        rollback = true;
+                        thisTree.move_node(movedNode, data.old_parent, data.old_position);
                       }
                       else {
-                        // Update the nodes changed in the server side.
-                        if (response.updated_nodes) {
-                          let update_nodes = response.updated_nodes;
-                          for (const id in update_nodes) {
-                            let node = thisTree.get_node(id);
-                            if (node) {
-                              node.data.weight = update_nodes[id];
+                        if (parent_node.data && !parent_node.data.draggable) {
+                          // The parent node is not draggable.
+                          // We have to update all duplicated nodes
+                          // by refreshing the whole tree.
+                          thisTree.refresh();
+                        }
+                        else {
+                          // Update the nodes changed in the server side.
+                          if (response.updated_nodes) {
+                            let update_nodes = response.updated_nodes;
+                            for (const id in update_nodes) {
+                              let node = thisTree.get_node(id);
+                              if (node) {
+                                node.data.weight = update_nodes[id];
+                              }
                             }
+                            //Refresh the tree without reloading data from server.
+                            thisTree.sort(parent_node, true);
+                            thisTree.redraw(true);
                           }
-                          //Refresh the tree without reloading data from server.
-                          thisTree.sort(parent_node, true);
-                          thisTree.redraw(true);
                         }
+  
+                        let message = Drupal.t('@node is moved to position @position under @parent', {'@node': data.node.text, '@parent': parentText, '@position': data.position + 1});
+                        // Inform user the movement.
+                        drupalMessages.clear();
+                        drupalMessages.add(message);
                       }
-
-                      let message = Drupal.t('@node is moved to position @position under @parent', {'@node': data.node.text, '@parent': parentText, '@position': data.position + 1});
-                      // Inform user the movement.
+                    })
+                    .fail(function() {
                       drupalMessages.clear();
-                      drupalMessages.add(message);
-                    }
-                  })
-                  .fail(function() {
-                    drupalMessages.clear();
-                    drupalMessages.add(Drupal.t("Can't connect to the server."), {type: 'error'});
-                    rollback = true;
-                    thisTree.move_node(movedNode, data.old_parent, data.old_position);
-                  });
+                      drupalMessages.add(Drupal.t("Can't connect to the server."), {type: 'error'});
+                      rollback = true;
+                      thisTree.move_node(movedNode, data.old_parent, data.old_position);
+                    });
+                }
+
+                // Check if confirmation dialog is enabled.
+                if (typeof confirm !== 'undefined' && confirm !== false) {
+                  // Confirmation dialog enabled.
+                  let modalTitle = Drupal.t('Confirm move?');
+                  let modalMessage = Drupal.t('Move <em class="placeholder">@node</em> to position @position under <em class="placeholder">@parent</em>?', { '@node': data.node.text, '@parent': parentText, '@position': data.position + 1 });
+                  modalConfirmation(modalTitle, modalMessage, moveTreeItem, function () {
+                    // Callback when confirmation is denied.
+                     rollback = true;
+                     thisTree.move_node(movedNode, data.old_parent, data.old_position);
+                   });
+                } else {
+                  // Confirmation dialog disabled.
+                  moveTreeItem()
+                }
+
               }
               else {
                 rollback = false;
@@ -184,4 +204,51 @@
         });
     }
   };
+
+  /**
+   * Generic modal helper function.
+   *
+   * @param {string} title - The title for the confirm dialog.
+   * @param {string} message - The main message for the confirm dialog.
+   * @param {function} accept - Callback fired when the user answers positive.
+   * @param {function} deny - Callback fired when the user answers negative.
+   * @returns {Object} - A jQuery dialog object.
+   */
+  function modalConfirmation(title, message, accept, deny) {
+    let proceed = false;
+    let modalConfirmationForm = $('<div></div>').appendTo('body')
+      .html(message)
+      .dialog({
+        modal: true,
+        title: title,
+        autoOpen: false,
+        width: 400,
+        resizable: false,
+        sticky: true,
+        closeOnEscape: true,
+        dialogClass: "hm-confirm",
+        buttons: [
+          {
+            class: 'button button--primary',
+            text: Drupal.t('Yes'),
+            click: function () {
+              proceed = true;
+              $(this).dialog('close');
+            }
+          },
+          {
+            class: 'button',
+            text: Drupal.t('No'),
+            click: function () {
+              $(this).dialog('close');
+            }
+          }
+        ],
+        close: function () {
+          proceed ? accept() : deny();
+        }
+      });
+    return modalConfirmationForm.dialog('open');
+  }
+
 })(jQuery, Drupal);
diff --git a/src/Entity/HmDisplayProfile.php b/src/Entity/HmDisplayProfile.php
index 56d40b3..32f1f0d 100644
--- a/src/Entity/HmDisplayProfile.php
+++ b/src/Entity/HmDisplayProfile.php
@@ -34,6 +34,7 @@ use Drupal\Core\Config\Entity\ConfigEntityBase;
  *     "label",
  *     "plugin",
  *     "config",
+ *     "confirm",
  *   },
  *   links = {
  *     "canonical" = "/admin/structure/hm_display_profile/{hm_display_profile}",
@@ -73,4 +74,11 @@ class HmDisplayProfile extends ConfigEntityBase implements HmDisplayProfileInter
    * @var string
    */
   protected $config;
+
+  /**
+   * The confirmation option.
+   *
+   * @var bool
+   */
+  protected $confirm = FALSE;
 }
diff --git a/src/Form/HmDisplayProfileForm.php b/src/Form/HmDisplayProfileForm.php
index 2979542..c85a30f 100644
--- a/src/Form/HmDisplayProfileForm.php
+++ b/src/Form/HmDisplayProfileForm.php
@@ -114,6 +114,13 @@ class HmDisplayProfileForm extends EntityForm {
       ];
     }
 
+    $form['confirm'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Confirm drag&drop'),
+      '#default_value' => $hm_display_profile->get("confirm"),
+      '#description' => $this->t('Displays a dialog when changing the hierarchy.'),
+    ];
+
     return $form;
   }
 
@@ -132,6 +139,9 @@ class HmDisplayProfileForm extends EntityForm {
       $hm_display_profile->set('config', $input['config']);
     }
 
+    if (isset($input['confirm'])) {
+      $hm_display_profile->set('confirm', $input['confirm']);
+    }
     
     $status = $hm_display_profile->save();
 
diff --git a/src/Form/HmMenuForm.php b/src/Form/HmMenuForm.php
index 6ff2eb9..428dd66 100644
--- a/src/Form/HmMenuForm.php
+++ b/src/Form/HmMenuForm.php
@@ -98,7 +98,8 @@ class HmMenuForm extends MenuForm {
         $source_url = Url::fromRoute('hierarchy_manager.menu.tree.json', ['mid' => $mid], ['query' => ['token' => $token, 'destination' => $destination]])->toString();
         $update_url = Url::fromRoute('hierarchy_manager.menu.tree.update', ['mid' => $mid], ['query' => ['token' => $token]])->toString();
         $config = $display_profile->get("config");
-        return $display_plugin_instance->getForm($source_url, $update_url, $form, $form_state, $config);
+        $confirm = $display_profile->get("confirm");
+        return $display_plugin_instance->getForm($source_url, $update_url, $form, $form_state, $config, $confirm);
       }
     }
 
diff --git a/src/Form/HmOverviewTerms.php b/src/Form/HmOverviewTerms.php
index 829551e..3c7bc87 100644
--- a/src/Form/HmOverviewTerms.php
+++ b/src/Form/HmOverviewTerms.php
@@ -64,7 +64,8 @@ class HmOverviewTerms extends OverviewTerms {
                 $source_url = Url::fromRoute('hierarchy_manager.taxonomy.tree.json', ['vid' => $vid], ['query' => ['token' => $token, 'destination' => $destination]])->toString();
                 $update_url = Url::fromRoute('hierarchy_manager.taxonomy.tree.update', ['vid' => $vid], ['query' => ['token' => $token]])->toString();
                 $config = $display_profile->get("config");
-                return $instance->getForm($source_url, $update_url, $form, $form_state, $config);
+                $confirm = $display_profile->get('confirm');
+                return $instance->getForm($source_url, $update_url, $form, $form_state, $config, $confirm);
               }
             }
           }
diff --git a/src/Plugin/HmDisplayPlugin/HmDisplayJstree.php b/src/Plugin/HmDisplayPlugin/HmDisplayJstree.php
index 80c6a97..8dba5c0 100644
--- a/src/Plugin/HmDisplayPlugin/HmDisplayJstree.php
+++ b/src/Plugin/HmDisplayPlugin/HmDisplayJstree.php
@@ -23,7 +23,7 @@ class HmDisplayJstree extends HmDisplayPluginBase implements HmDisplayPluginInte
   /*
    * Build the tree form.
    */
-  public function getForm(string $url_source, string $url_update, array &$form = [], FormStateInterface &$form_state = NULL, $options = NULL) {
+  public function getForm(string $url_source, string $url_update, array &$form = [], FormStateInterface &$form_state = NULL, $options = NULL, $confirm = FALSE) {
     if (!empty($url_source)) {
       if (!empty(($form_state))) {
         $parent_formObj = $form_state->getFormObject();
@@ -70,6 +70,7 @@ class HmDisplayJstree extends HmDisplayPluginBase implements HmDisplayPluginInte
           'id' => isset($parent_id) ? 'hm-jstree-' . $parent_id : 'hm-jstree',
           'parent-id' => isset($parent_id) ? $parent_id : '',
           'options' => $options,
+          'confirm' => $confirm,
           'data-source' => $url_source,
           'url-update' => $url_update,
         ],
-- 
GitLab