From 657314adb14a84b81daa080d0541a8b9c8ac5a9c Mon Sep 17 00:00:00 2001 From: abd-msyukyu-odoo Date: Wed, 6 Sep 2023 17:08:34 +0200 Subject: [PATCH] [IMP] web: draggable_hook_builder option to ignore selectively The way the `ignore` property is implemented means that nested draggable elements under a single element matching the `ignore` selector will all be disabled. In Knowledge, the sidebar allowing to move articles around has nested draggable elements that the user should be able to move around, under other draggable elements that should stay in place (depending on access rights). Therefore, this commit introduces another way to disable dragging with the `preventDrag` property which is a function that receives the dragging candidate and returns a boolean to indicate whether the element can effectively be dragged or not in its current situation. task-3456741 closes odoo/odoo#135981 X-original-commit: eb302dbf48500b2eee61a0efb865868866fb454b Related: odoo/enterprise#47640 Signed-off-by: Thibault Delavallee (tde) Signed-off-by: Damien Abeloos (abd) --- .../src/core/utils/draggable_hook_builder.js | 15 ++++- .../static/src/core/utils/nested_sortable.js | 6 +- .../tests/core/utils/draggable_tests.js | 65 +++++++++++++++++++ 3 files changed, 83 insertions(+), 3 deletions(-) diff --git a/addons/web/static/src/core/utils/draggable_hook_builder.js b/addons/web/static/src/core/utils/draggable_hook_builder.js index df0bd02724b..11310493ca4 100644 --- a/addons/web/static/src/core/utils/draggable_hook_builder.js +++ b/addons/web/static/src/core/utils/draggable_hook_builder.js @@ -34,6 +34,7 @@ import { hasTouch, isBrowserFirefox, isIOS } from "../browser/feature_detection" * @property {boolean} [followCursor=true] * @property {string | null} [cursor=null] * @property {() => boolean} [enable=() => false] + * @property {(HTMLElement) => boolean} [preventDrag=(el) => false] * @property {Position} [pointer={ x: 0, y: 0 }] * @property {EdgeScrollingOptions} [edgeScrolling] * @property {number} [delay] @@ -77,6 +78,7 @@ const DRAGGED_CLASS = "o_dragged"; const DEFAULT_ACCEPTED_PARAMS = { enable: [Boolean, Function], + preventDrag: [Function], ref: [Object], elements: [String], handle: [String, Function], @@ -89,6 +91,7 @@ const DEFAULT_ACCEPTED_PARAMS = { const DEFAULT_DEFAULT_PARAMS = { elements: `.${DRAGGABLE_CLASS}`, enable: true, + preventDrag: () => false, edgeScrolling: { speed: 10, threshold: 30, @@ -658,11 +661,13 @@ export function makeDraggableHook(hookParams) { // outside of the window. dragEnd(null); + const fullSelectorEl = ev.target.closest(ctx.fullSelector); if ( ev.button !== LEFT_CLICK || !ctx.enable() || - !ev.target.closest(ctx.fullSelector) || - (ctx.ignoreSelector && ev.target.closest(ctx.ignoreSelector)) + !fullSelectorEl || + (ctx.ignoreSelector && ev.target.closest(ctx.ignoreSelector)) || + ctx.preventDrag(fullSelectorEl) ) { return; } @@ -886,6 +891,7 @@ export function makeDraggableHook(hookParams) { /** @type {DraggableHookContext} */ const ctx = { enable: () => false, + preventDrag: () => false, ref: params.ref, ignoreSelector: null, fullSelector: null, @@ -911,6 +917,11 @@ export function makeDraggableHook(hookParams) { // Enable getter ctx.enable = actualParams.enable; + // Dragging constraint + if (actualParams.preventDrag) { + ctx.preventDrag = actualParams.preventDrag; + } + // Selectors ctx.elementSelector = actualParams.elements; if (!ctx.elementSelector) { diff --git a/addons/web/static/src/core/utils/nested_sortable.js b/addons/web/static/src/core/utils/nested_sortable.js index 553edbcde0b..e58f99e70f6 100644 --- a/addons/web/static/src/core/utils/nested_sortable.js +++ b/addons/web/static/src/core/utils/nested_sortable.js @@ -22,7 +22,11 @@ import { makeDraggableHook } from "@web/core/utils/draggable_hook_builder"; * @property {string | () => string} [handle] additional selector for when the dragging * sequence must be initiated when dragging on a certain part of the element. * @property {string | () => string} [ignore] selector targeting elements that must - * initiate a drag. + * initiate a drag (recursive in depth: all draggable elements under an + * ignored element will be locked in place) + * @property {(HTMLElement) => boolean} [preventDrag] function receiving a + * the current target for dragging (element) and returning a boolean, whether + * the element can be effectively dragged or not. * @property {boolean | () => boolean} [connectGroups] whether elements can be dragged * across different parent groups. Note that it requires a `groups` param to work. * @property {string | () => string} [cursor] cursor style during the dragging sequence. diff --git a/addons/web/static/tests/core/utils/draggable_tests.js b/addons/web/static/tests/core/utils/draggable_tests.js index 6b82c994852..beab3836274 100644 --- a/addons/web/static/tests/core/utils/draggable_tests.js +++ b/addons/web/static/tests/core/utils/draggable_tests.js @@ -215,6 +215,71 @@ QUnit.module("Draggable", ({ beforeEach }) => { assert.verifySteps([]); }); + QUnit.test("Ignore specific elements in a nested draggable", async (assert) => { + assert.expect(7); + + class List extends Component { + static components = { List }; + static template = xml` +
+
    +
  • + +
      +
    • + +
    • +
    +
  • +
+
`; + setup() { + useDraggable({ + ref: useRef("root"), + elements: ".item", + preventDrag: (el) => el.classList.contains('ignored'), + onDragStart() { + assert.step("drag"); + }, + }); + } + } + + await mount(List, target); + + assert.verifySteps([]); + + // Drag ignored under non-ignored -> block + await dragAndDrop( + ".not-ignored.parent .ignored.child", + ".not-ignored.parent .not-ignored.child" + ); + assert.verifySteps([]); + + // Drag not-ignored-under not-ignored -> succeed + await dragAndDrop( + ".not-ignored.parent .not-ignored.child", + ".not-ignored.parent .ignored.child" + ); + assert.verifySteps(["drag"]); + + // Drag ignored under ignored -> block + await dragAndDrop( + ".ignored.parent .ignored.child", + ".ignored.parent .not-ignored.child" + ); + assert.verifySteps([]); + + // Drag not-ignored under ignored -> succeed + await dragAndDrop( + ".ignored.parent .not-ignored.child", + ".ignored.parent .ignored.child" + ); + assert.verifySteps(["drag"]); + }); + QUnit.test("Dragging element with touch event", async (assert) => { assert.expect(10);