jrief/django-admin-sortable2

Sorting does not work for StackedInline if widget of the field has <table>

igor-zmitrovich opened this issue · 2 comments

https://github.com/jrief/django-admin-sortable2/blob/2.1.10/client/admin-sortable2.ts#L214

This line selects the <thead> of the field's widget <table> and attaches the event listeners to it in if (tBody) {...}

image

This causes the UI to not handle the drag events correctly:
ezgif-5-393cf1496a

As a workaround, I've copy-pasted the compiled version of adminsortable2.js to myproject/static/adminsortable2/js/adminsortable2.js and modified the InlineSortable's constructor as follows:

...
  var InlineSortable = class {
    constructor(inlineFieldSet) {
      this.reversed = inlineFieldSet.classList.contains("reversed");

      // Old code - selects any child (even tables inside fields' widgets)
      // const tBody = inlineFieldSet.querySelector("table tbody");

      // Fixed code - selects only direct child table
      let tBody = null;
      const table = inlineFieldSet.querySelector("table");
      if (table && table.parentElement === inlineFieldSet) {
        tBody = table.querySelector("tbody");
      }

      if (tBody) {
...

I am using:

Django==4.2.9
django-admin-sortable2==2.1.10
django-json-widget==1.1.1

Could you please dump a HTML code snippet containing at least one complete stacked inline form.

Then I will check what can be done by adopting the selectors.

Here is the snippet:

<div class="js-inline-admin-formset inline-group" id="example-3-group" data-inline-type="stacked" data-inline-formset="{&quot;name&quot;: &quot;#example-3&quot;, &quot;options&quot;: {&quot;prefix&quot;: &quot;example-3&quot;, &quot;addText&quot;: &quot;Add another Appointment Screen&quot;, &quot;deleteText&quot;: &quot;Remove&quot;}}">
  <fieldset class="module collapse show sortable">
    <h2>Section (<a id="fieldsetcollapser10" class="collapse-toggle" href="#">Hide</a>)</h2>
    <input type="hidden" name="example-3-TOTAL_FORMS" value="3" id="id_example-3-TOTAL_FORMS" autocomplete="off"><input type="hidden" name="example-3-INITIAL_FORMS" value="3" id="id_example-3-INITIAL_FORMS"><input type="hidden" name="example-3-MIN_NUM_FORMS" value="0" id="id_example-3-MIN_NUM_FORMS" autocomplete="off"><input type="hidden" name="example-3-MAX_NUM_FORMS" value="1000" id="id_example-3-MAX_NUM_FORMS" autocomplete="off">
    <div class="inline-related has_original dynamic-example-3 sortable-chosen" id="example-3-0" draggable="true" style="">
      <h3><b>Section:</b> <span class="inline_label">some object label
        </span>
        <span class="delete"><input type="checkbox" name="example-3-0-DELETE" id="id_example-3-0-DELETE"> <label class="vCheckboxLabel inline" for="id_example-3-0-DELETE">Delete</label></span>
      </h3>
      <fieldset class="module aligned ">
        <div class="form-row field-some_choice">
          <div>
            <div class="flex-container">
              <label class="required" for="id_example-3-0-some_choice">Some choice:</label>
              <select name="example-3-0-some_choice" id="id_example-3-0-some_choice">
                <option value="whatever" selected="">Whatever</option>
              </select>
            </div>
          </div>
        </div>
        <div class="form-row field-translations">
          <div>
            <div class="flex-container">
              <label for="id_example-3-0-translations">Translations:</label>
              <div style="height:auto;width:90%;display:inline-block;" id="id_example-3-0-translations">
                <div class="jsoneditor jsoneditor-mode-form">
                  <div class="jsoneditor-menu">
                    <button type="button" class="jsoneditor-expand-all" title="Expand all fields"></button><button type="button" title="Collapse all fields" class="jsoneditor-collapse-all"></button><button type="button" class="jsoneditor-undo jsoneditor-separator" title="Undo last action (Ctrl+Z)" disabled=""></button><button type="button" class="jsoneditor-redo" title="Redo (Ctrl+Shift+Z)" disabled=""></button>
                    <div class="jsoneditor-modes" style="position: relative;"><button type="button" class="jsoneditor-modes jsoneditor-separator" title="Switch Editor Mode">Form ▾</button></div>
                    <div class="jsoneditor-search">
                      <div class="jsoneditor-results"></div>
                      <div class="jsoneditor-frame" title="Search fields and values"><button type="button" class="jsoneditor-refresh"></button><input type="text"><button type="button" title="Next result (Enter)" class="jsoneditor-next"></button><button type="button" title="Previous result (Shift + Enter)" class="jsoneditor-previous"></button></div>
                    </div>
                    <button type="button" class="jsoneditor-repair" title="Fill empty values with exemplary data"></button>
                  </div>
                  <div class="jsoneditor-outer has-main-menu-bar">
                    <div class="jsoneditor-tree">
                      <div class="jsoneditor-tree-inner">
                        <table class="jsoneditor-tree">
                          <colgroup>
                            <col width="24px">
                            <col>
                          </colgroup>
                          <tbody>
                            <tr class=" jsoneditor-expandable jsoneditor-expanded">
                              <td>
                                <table style="border-collapse: collapse; margin-left: 0px;" class="jsoneditor-values">
                                  <tbody>
                                    <tr>
                                      <td class="jsoneditor-tree"><button type="button" class="jsoneditor-button jsoneditor-expanded" title="Click to expand/collapse this field (Ctrl+E).
                                        Ctrl+Click to expand/collapse including all childs."></button></td>
                                      <td class="jsoneditor-tree">
                                        <div contenteditable="false" class="jsoneditor-readonly">Translations</div>
                                      </td>
                                      <td class="jsoneditor-tree"></td>
                                      <td class="jsoneditor-tree">
                                        <div class="jsoneditor-value jsoneditor-object" title="object containing 4 items">{4}</div>
                                      </td>
                                    </tr>
                                  </tbody>
                                </table>
                              </td>
                            </tr>
                            <tr>
                              <td>
                                <table style="border-collapse: collapse; margin-left: 24px;" class="jsoneditor-values">
                                  <tbody>
                                    <tr>
                                      <td class="jsoneditor-tree"><button type="button" class="jsoneditor-button jsoneditor-invisible" title=""></button></td>
                                      <td class="jsoneditor-tree">
                                        <div contenteditable="false" spellcheck="false" class="jsoneditor-field">title</div>
                                      </td>
                                      <td class="jsoneditor-separator">:</td>
                                      <td class="jsoneditor-tree">
                                        <div contenteditable="true" spellcheck="false" class="jsoneditor-value jsoneditor-string" title="">Translation</div>
                                      </td>
                                    </tr>
                                  </tbody>
                                </table>
                              </td>
                            </tr>
                            <tr>
                              <td>
                                <table style="border-collapse: collapse; margin-left: 24px;" class="jsoneditor-values">
                                  <tbody>
                                    <tr>
                                      <td class="jsoneditor-tree"><button type="button" class="jsoneditor-button jsoneditor-invisible" title=""></button></td>
                                      <td class="jsoneditor-tree">
                                        <div contenteditable="false" spellcheck="false" class="jsoneditor-field">header</div>
                                      </td>
                                      <td class="jsoneditor-separator">:</td>
                                      <td class="jsoneditor-tree">
                                        <div contenteditable="true" spellcheck="false" class="jsoneditor-value jsoneditor-string" title="">Translation</div>
                                      </td>
                                    </tr>
                                  </tbody>
                                </table>
                              </td>
                            </tr>
                            <tr>
                              <td>
                                <table style="border-collapse: collapse; margin-left: 24px;" class="jsoneditor-values">
                                  <tbody>
                                    <tr>
                                      <td class="jsoneditor-tree"><button type="button" class="jsoneditor-button jsoneditor-invisible" title=""></button></td>
                                      <td class="jsoneditor-tree">
                                        <div contenteditable="false" spellcheck="false" class="jsoneditor-field">cta_button</div>
                                      </td>
                                      <td class="jsoneditor-separator">:</td>
                                      <td class="jsoneditor-tree">
                                        <div contenteditable="true" spellcheck="false" class="jsoneditor-value jsoneditor-string" title="">Translation</div>
                                      </td>
                                    </tr>
                                  </tbody>
                                </table>
                              </td>
                            </tr>
                            <tr>
                              <td>
                                <table style="border-collapse: collapse; margin-left: 24px;" class="jsoneditor-values">
                                  <tbody>
                                    <tr>
                                      <td class="jsoneditor-tree"><button type="button" class="jsoneditor-button jsoneditor-invisible" title=""></button></td>
                                      <td class="jsoneditor-tree">
                                        <div contenteditable="false" spellcheck="false" class="jsoneditor-field">description</div>
                                      </td>
                                      <td class="jsoneditor-separator">:</td>
                                      <td class="jsoneditor-tree">
                                        <div contenteditable="true" spellcheck="false" class="jsoneditor-value jsoneditor-string" title="">Translation</div>
                                      </td>
                                    </tr>
                                  </tbody>
                                </table>
                              </td>
                            </tr>
                            <tr class="jsoneditor-append"></tr>
                          </tbody>
                        </table>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
              <textarea id="id_example-3-0-translations_textarea" name="example-3-0-translations" required="" style="display: none">{"title": "Translation", "header": "Translation", "cta_button": "Translation", "descrition": "Translation"}</textarea>
              <script id="widget-options-id_example-3-0-translations" type="application/json">"{\"modes\": [\"form\", \"code\"], \"mode\": \"form\", \"search\": true, \"name\": \"Translations\", \"enableTransform\": false, \"enableSort\": false, \"maxVisibleChilds\": 25, \"navigationBar\": false}"</script>
              <script id="widget-value-id_example-3-0-translations" type="application/json">"{\"title\": \"Translation\", \"header\": \"Translation\", \"cta_button\": \"Translation\", \"description\": \"Translation\"}"</script>
              <script>
                (function() {
                    let container = document.getElementById("id_example-3-0-translations");
                    let textarea = document.getElementById("id_example-3-0-translations_textarea");
                
                    /* To prevent possible XSS attacks, parse text value of <script> tag to JSON.
                       We need to parse value twice as JSONEditorWidget already dumps options and value as strings. */
                    let options = JSON.parse(JSON.parse(document.getElementById('widget-options-id_example-3-0-translations').textContent));
                    let data = JSON.parse(JSON.parse(document.getElementById('widget-value-id_example-3-0-translations').textContent));
                
                    options.onChange = function () {
                        let json = editor.get();
                        textarea.value=JSON.stringify(json);
                    }
                
                    let editor = new JSONEditor(container, options);
                    container.jsoneditor = editor;
                    editor.set(data);
                })();
              </script>
            </div>
            <div class="help" id="id_example-3-0-translations_helptext">
              <div>To edit translations used on newly added screen click "SAVE" and come back to this page again.</div>
            </div>
          </div>
        </div>
        <div class="form-row field-checkbox">
          <div>
            <div class="flex-container checkbox-row">
              <input type="checkbox" name="example-3-0-checkbox" id="id_example-3-0-checkbox" checked=""><label class="vCheckboxLabel" for="id_example-3-0-checkbox">Checkbox</label>
            </div>
            <div class="help" id="id_example-3-0-checkbox_helptext">
              <div>Label</div>
            </div>
          </div>
        </div>
        <div class="form-row hidden field-ordering">
          <div>
            <div class="flex-container">
              <label for="id_example-3-0-ordering">Ordering:</label>
              <input type="hidden" name="example-3-0-ordering" value="1" class="_reorder_" id="id_example-3-0-ordering">
            </div>
          </div>
        </div>
      </fieldset>
      <input type="hidden" name="example-3-0-id" value="235" id="id_example-3-0-id">
      <input type="hidden" name="example-3-0-example" value="231" id="id_example-3-0-example">
    </div>
    ...
    other forms 
    ...
    <div class="add-row"><a href="#">Add another Example</a></div>
  </fieldset>
</div>