[IMP] web,web_editor,website_slide: sortable service for frontend usage
This commit removes the module sortable from jQueryUI and adds instead a
sortable service to use the sortable code already used by the backend.
The service can be called using `this.call("sortable", ...)` and the
sortable listeners are added when the `enable` function is called.
This function (`enable()`) returns an object that contains the `cleanup`
function (`cleanup()`).
```javascript
// start / setup
const sortable = this.call(
"sortable",
"create",
{
ref: { el: document.querySelector(".list") },
elements: ".items",
handle: ".drag-icon",
},
);
// on attach callback / on mounted
const sortableInstance = sortable.enable();
// on destroy / on willUnmount
sortableInstance.cleanup();
```
Also, it adds some new property to the useSortable:
* clone: the placeholder is a clone of the drag element
* placeholderClasses: classes added to the placeholder element
* applyChangeOnDrop: on drop the change is applied to the DOM
* followingElementClasses: classes added to the element following mouse
task-3439226
closes odoo/odoo#137527
Related: odoo/enterprise#48371
Signed-off-by: Adrien Dieudonné (adr) <adr@odoo.com>
This commit is contained in:
+2
-6
@@ -1,6 +1,6 @@
|
||||
/*! jQuery UI - v1.12.1 - 2023-09-14
|
||||
/*! jQuery UI - v1.12.1 - 2023-10-04
|
||||
* http://jqueryui.com
|
||||
* Includes: draggable.css, sortable.css, core.css, autocomplete.css, menu.css, theme.css
|
||||
* Includes: draggable.css, core.css, autocomplete.css, menu.css, theme.css
|
||||
* To view and modify this theme, visit http://jqueryui.com/themeroller/?scope=&folderName=base&cornerRadiusShadow=8px&offsetLeftShadow=0px&offsetTopShadow=0px&thicknessShadow=5px&opacityShadow=30&bgImgOpacityShadow=0&bgTextureShadow=flat&bgColorShadow=666666&opacityOverlay=30&bgImgOpacityOverlay=0&bgTextureOverlay=flat&bgColorOverlay=aaaaaa&iconColorError=cc0000&fcError=5f3f3f&borderColorError=f1a899&bgTextureError=flat&bgColorError=fddfdf&iconColorHighlight=777620&fcHighlight=777620&borderColorHighlight=dad55e&bgTextureHighlight=flat&bgColorHighlight=fffa90&iconColorActive=ffffff&fcActive=ffffff&borderColorActive=003eff&bgTextureActive=flat&bgColorActive=007fff&iconColorHover=555555&fcHover=2b2b2b&borderColorHover=cccccc&bgTextureHover=flat&bgColorHover=ededed&iconColorDefault=777777&fcDefault=454545&borderColorDefault=c5c5c5&bgTextureDefault=flat&bgColorDefault=f6f6f6&iconColorContent=444444&fcContent=333333&borderColorContent=dddddd&bgTextureContent=flat&bgColorContent=ffffff&iconColorHeader=444444&fcHeader=333333&borderColorHeader=dddddd&bgTextureHeader=flat&bgColorHeader=e9e9e9&cornerRadius=3px&fwDefault=normal&fsDefault=1em&ffDefault=Arial%2CHelvetica%2Csans-serif
|
||||
* Copyright jQuery Foundation and other contributors; Licensed MIT */
|
||||
|
||||
@@ -8,10 +8,6 @@
|
||||
-ms-touch-action: none;
|
||||
touch-action: none;
|
||||
}
|
||||
.ui-sortable-handle {
|
||||
-ms-touch-action: none;
|
||||
touch-action: none;
|
||||
}
|
||||
/* Layout helpers
|
||||
----------------------------------*/
|
||||
.ui-helper-hidden {
|
||||
|
||||
+2
-1538
File diff suppressed because it is too large
Load Diff
@@ -81,7 +81,7 @@ import { hasTouch, isBrowserFirefox, isIOS } from "../browser/feature_detection"
|
||||
*/
|
||||
|
||||
const DRAGGABLE_CLASS = "o_draggable";
|
||||
const DRAGGED_CLASS = "o_dragged";
|
||||
export const DRAGGED_CLASS = "o_dragged";
|
||||
|
||||
const DEFAULT_ACCEPTED_PARAMS = {
|
||||
enable: [Boolean, Function],
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { makeDraggableHook as nativeMakeDraggableHook } from "@web/core/utils/draggable_hook_builder";
|
||||
import {
|
||||
DRAGGED_CLASS,
|
||||
makeDraggableHook as nativeMakeDraggableHook,
|
||||
} from "@web/core/utils/draggable_hook_builder";
|
||||
import { pick } from "@web/core/utils/objects";
|
||||
|
||||
/** @typedef {import("@web/core/utils/draggable_hook_builder").DraggableHandlerParams} DraggableHandlerParams */
|
||||
@@ -28,6 +31,10 @@ import { pick } from "@web/core/utils/objects";
|
||||
* @property {boolean | (() => boolean)} [connectGroups] whether elements can be dragged
|
||||
* accross different parent groups. Note that it requires a `groups` param to work.
|
||||
* @property {string | (() => string)} [cursor] cursor style during the dragging sequence.
|
||||
* @property {Boolean} [clone] the placeholder is a clone of the drag element.
|
||||
* @property {Array} [placeholderClasses] array of classes added to the placeholder element.
|
||||
* @property {Boolean} [applyChangeOnDrop] on drop the change is applied to the DOM.
|
||||
* @property {Array} [followingElementClasses] array of classes added to the element that follow the pointer.
|
||||
*
|
||||
* HANDLERS (also optional)
|
||||
*
|
||||
@@ -69,11 +76,19 @@ const hookParams = {
|
||||
acceptedParams: {
|
||||
groups: [String, Function],
|
||||
connectGroups: [Boolean, Function],
|
||||
clone: [Boolean],
|
||||
placeholderClasses: [Object],
|
||||
applyChangeOnDrop: [Boolean],
|
||||
followingElementClasses: [Object],
|
||||
},
|
||||
defaultParams: {
|
||||
connectGroups: false,
|
||||
edgeScrolling: { speed: 20, threshold: 60 },
|
||||
groupSelector: null,
|
||||
clone: true,
|
||||
placeholderClasses: [],
|
||||
applyChangeOnDrop: false,
|
||||
followingElementClasses: [],
|
||||
},
|
||||
|
||||
// Build steps
|
||||
@@ -86,6 +101,11 @@ const hookParams = {
|
||||
|
||||
// Connection accross groups
|
||||
ctx.connectGroups = params.connectGroups;
|
||||
|
||||
ctx.placeholderClone = params.clone;
|
||||
ctx.placeholderClasses = params.placeholderClasses;
|
||||
ctx.applyChangeOnDrop = params.applyChangeOnDrop;
|
||||
ctx.followingElementClasses = params.followingElementClasses;
|
||||
},
|
||||
|
||||
// Runtime steps
|
||||
@@ -120,6 +140,65 @@ const hookParams = {
|
||||
callHandler("onElementLeave", { element });
|
||||
};
|
||||
|
||||
const onElementComplexPointerEnter = (ev) => {
|
||||
const element = ev.currentTarget;
|
||||
|
||||
const siblingArray = [...element.parentElement.children].filter(
|
||||
(el) =>
|
||||
el === current.placeHolder ||
|
||||
(el.matches(elementSelector) && !el.classList.contains(DRAGGED_CLASS))
|
||||
);
|
||||
const elementIndex = siblingArray.indexOf(element);
|
||||
const placeholderIndex = siblingArray.indexOf(current.placeHolder);
|
||||
const isDirectSibling = Math.abs(elementIndex - placeholderIndex) === 1;
|
||||
if (
|
||||
connectGroups ||
|
||||
!groupSelector ||
|
||||
current.group === element.closest(groupSelector)
|
||||
) {
|
||||
const pos = current.placeHolder.compareDocumentPosition(element);
|
||||
if (isDirectSibling) {
|
||||
if (pos === Node.DOCUMENT_POSITION_PRECEDING) {
|
||||
element.before(current.placeHolder);
|
||||
} else if (pos === Node.DOCUMENT_POSITION_FOLLOWING) {
|
||||
element.after(current.placeHolder);
|
||||
}
|
||||
} else {
|
||||
if (pos === Node.DOCUMENT_POSITION_FOLLOWING) {
|
||||
element.before(current.placeHolder);
|
||||
} else if (pos === Node.DOCUMENT_POSITION_PRECEDING) {
|
||||
element.after(current.placeHolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
callHandler("onElementEnter", { element });
|
||||
};
|
||||
|
||||
/**
|
||||
* Element "pointerleave" event handler.
|
||||
* @param {PointerEvent} ev
|
||||
*/
|
||||
const onElementComplexPointerLeave = (ev) => {
|
||||
const element = ev.currentTarget;
|
||||
|
||||
const siblingArray = [...element.parentElement.children].filter(
|
||||
(el) =>
|
||||
el === current.placeHolder ||
|
||||
(el.matches(elementSelector) && !el.classList.contains(DRAGGED_CLASS))
|
||||
);
|
||||
const elementIndex = siblingArray.indexOf(element);
|
||||
const isAtEdge = elementIndex === 0 || elementIndex === siblingArray.length - 1;
|
||||
if (isAtEdge && (!groupSelector || current.group === element.closest(groupSelector))) {
|
||||
const pos = current.placeHolder.compareDocumentPosition(element);
|
||||
if (pos === Node.DOCUMENT_POSITION_PRECEDING) {
|
||||
element.before(current.placeHolder);
|
||||
} else if (pos === Node.DOCUMENT_POSITION_FOLLOWING) {
|
||||
element.after(current.placeHolder);
|
||||
}
|
||||
}
|
||||
callHandler("onElementLeave", { element });
|
||||
};
|
||||
|
||||
/**
|
||||
* Group "pointerenter" event handler.
|
||||
* @param {PointerEvent} ev
|
||||
@@ -140,15 +219,17 @@ const hookParams = {
|
||||
};
|
||||
|
||||
const { connectGroups, current, elementSelector, groupSelector, ref } = ctx;
|
||||
const { width, height } = current.elementRect;
|
||||
if (ctx.placeholderClone) {
|
||||
const { width, height } = current.elementRect;
|
||||
|
||||
// Adjusts size for the placeholder element
|
||||
addStyle(current.placeHolder, {
|
||||
visibility: "hidden",
|
||||
display: "block",
|
||||
width: `${width}px`,
|
||||
height: `${height}px`,
|
||||
});
|
||||
// Adjusts size for the placeholder element
|
||||
addStyle(current.placeHolder, {
|
||||
visibility: "hidden",
|
||||
display: "block",
|
||||
width: `${width}px`,
|
||||
height: `${height}px`,
|
||||
});
|
||||
}
|
||||
|
||||
// Binds handlers on eligible groups, if the elements are not confined to
|
||||
// their parents and a 'groupSelector' has been provided.
|
||||
@@ -162,8 +243,13 @@ const hookParams = {
|
||||
// Binds handlers on eligible elements
|
||||
for (const siblingEl of ref.el.querySelectorAll(elementSelector)) {
|
||||
if (siblingEl !== current.element && siblingEl !== current.placeHolder) {
|
||||
addListener(siblingEl, "pointerenter", onElementPointerEnter);
|
||||
addListener(siblingEl, "pointerleave", onElementPointerLeave);
|
||||
if (ctx.placeholderClone) {
|
||||
addListener(siblingEl, "pointerenter", onElementPointerEnter);
|
||||
addListener(siblingEl, "pointerleave", onElementPointerLeave);
|
||||
} else {
|
||||
addListener(siblingEl, "pointerenter", onElementComplexPointerEnter);
|
||||
addListener(siblingEl, "pointerleave", onElementComplexPointerLeave);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,8 +266,17 @@ const hookParams = {
|
||||
const previous = current.placeHolder.previousElementSibling;
|
||||
const next = current.placeHolder.nextElementSibling;
|
||||
if (previous !== current.element && next !== current.element) {
|
||||
const element = current.element;
|
||||
if (ctx.applyChangeOnDrop) {
|
||||
// Apply to the DOM the result of sortable()
|
||||
if (previous) {
|
||||
previous.after(element);
|
||||
} else if (next) {
|
||||
next.before(element);
|
||||
}
|
||||
}
|
||||
return {
|
||||
element: current.element,
|
||||
element,
|
||||
group: current.group,
|
||||
previous,
|
||||
next,
|
||||
@@ -199,8 +294,15 @@ const hookParams = {
|
||||
}
|
||||
}
|
||||
|
||||
current.placeHolder = current.element.cloneNode(false);
|
||||
if (ctx.placeholderClone) {
|
||||
current.placeHolder = current.element.cloneNode(false);
|
||||
} else {
|
||||
current.placeHolder = document.createElement("div");
|
||||
}
|
||||
current.placeHolder.classList.add(...ctx.placeholderClasses);
|
||||
current.element.classList.add(...ctx.followingElementClasses);
|
||||
|
||||
addCleanup(() => current.element.classList.remove(...ctx.followingElementClasses));
|
||||
addCleanup(() => current.placeHolder.remove());
|
||||
|
||||
return pick(current, "element", "group");
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "../registry";
|
||||
import { useSortable } from "@web/core/utils/sortable";
|
||||
import { throttleForAnimation } from "@web/core/utils/timing";
|
||||
import { reactive } from "@odoo/owl";
|
||||
|
||||
/**
|
||||
* @typedef SortableServiceHookParams
|
||||
* @extends SortableParams
|
||||
* @property {{el: HTMLElement} | ReturnType<typeof import("@odoo/owl").useRef>} [ref] container of sortable
|
||||
* @property {string | Symbol} [sortableId] identifier when multiple sortable on the same container
|
||||
*/
|
||||
|
||||
const DEFAULT_SORTABLE_ID = Symbol.for("defaultSortable");
|
||||
export const sortableService = {
|
||||
start() {
|
||||
/**
|
||||
* Map to avoid to setup/enable twice or more time the same element
|
||||
* @type {Map<Element, Object>}
|
||||
*/
|
||||
const boundElements = new Map();
|
||||
return {
|
||||
/**
|
||||
* @param {SortableServiceHookParams} hookParams
|
||||
*/
|
||||
create: (hookParams) => {
|
||||
const element = hookParams.ref.el;
|
||||
const sortableId = hookParams.sortableId ?? DEFAULT_SORTABLE_ID;
|
||||
if (boundElements.has(element)) {
|
||||
const boundElement = boundElements.get(element);
|
||||
if (sortableId in boundElement) {
|
||||
return {
|
||||
enable() {
|
||||
return {
|
||||
cleanup: boundElement[sortableId],
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @type {Map<Function, function():Array>}
|
||||
*/
|
||||
const setupFunctions = new Map();
|
||||
/**
|
||||
* @type {Array<Function>}
|
||||
*/
|
||||
const cleanupFunctions = [];
|
||||
|
||||
const cleanup = () => {
|
||||
const boundElement = boundElements.get(element);
|
||||
if (sortableId in boundElement) {
|
||||
delete boundElement[sortableId];
|
||||
if (boundElement.length === 0) {
|
||||
boundElements.delete(element);
|
||||
}
|
||||
}
|
||||
cleanupFunctions.forEach((fn) => fn());
|
||||
};
|
||||
|
||||
// Setup hookParam
|
||||
const setupHooks = {
|
||||
wrapState: reactive,
|
||||
throttle: throttleForAnimation,
|
||||
addListener: (el, type, listener) => {
|
||||
el.addEventListener(type, listener);
|
||||
cleanupFunctions.push(() => el.removeEventListener(type, listener));
|
||||
},
|
||||
setup: (setupFn, dependenciesFn) => setupFunctions.set(setupFn, dependenciesFn),
|
||||
teardown: (fn) => cleanupFunctions.push(fn),
|
||||
};
|
||||
|
||||
useSortable({ setupHooks, ...hookParams });
|
||||
|
||||
const boundElement = boundElements.get(element);
|
||||
if (boundElement) {
|
||||
boundElement[sortableId] = cleanup;
|
||||
} else {
|
||||
boundElements.set(element, { [sortableId]: cleanup });
|
||||
}
|
||||
|
||||
return {
|
||||
enable() {
|
||||
setupFunctions.forEach((dependenciesFn, setupFn) =>
|
||||
setupFn(...dependenciesFn())
|
||||
);
|
||||
return {
|
||||
cleanup,
|
||||
};
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
registry.category("services").add("sortable", sortableService);
|
||||
@@ -498,4 +498,115 @@ QUnit.module("Draggable", ({ beforeEach }) => {
|
||||
|
||||
assert.verifySteps([]);
|
||||
});
|
||||
|
||||
QUnit.test("the classes parameters (placeholderElement, helpElement)", async (assert) => {
|
||||
assert.expect(7);
|
||||
|
||||
let dragElement;
|
||||
|
||||
class List extends Component {
|
||||
setup() {
|
||||
useSortable({
|
||||
ref: useRef("root"),
|
||||
elements: ".item",
|
||||
placeholderClasses: ["placeholder-t1", "placeholder-t2"],
|
||||
followingElementClasses: ["add-1", "add-2"],
|
||||
onDragStart({ element }) {
|
||||
dragElement = element;
|
||||
assert.hasClass(dragElement, "add-1");
|
||||
assert.hasClass(dragElement, "add-2");
|
||||
// the placeholder is added in onDragStart after the current element
|
||||
const children = [...dragElement.parentElement.children];
|
||||
const placeholder = children[children.indexOf(dragElement) + 1];
|
||||
assert.hasClass(placeholder, "placeholder-t1");
|
||||
assert.hasClass(placeholder, "placeholder-t2");
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
List.template = xml`
|
||||
<div t-ref="root" class="root">
|
||||
<ul class="list">
|
||||
<li t-foreach="[1, 2, 3]" t-as="i" t-key="i" t-esc="i" class="item" />
|
||||
</ul>
|
||||
</div>`;
|
||||
|
||||
await mount(List, target);
|
||||
// First item after 2nd item
|
||||
const { drop, moveTo } = await drag(".item:first-child");
|
||||
await moveTo(".item:nth-child(2)");
|
||||
await drop();
|
||||
assert.doesNotHaveClass(dragElement, "add-1");
|
||||
assert.doesNotHaveClass(dragElement, "add-2");
|
||||
assert.containsNone(target, ".item.placeholder-t1.placeholder-t2");
|
||||
});
|
||||
|
||||
QUnit.test("applyChangeOnDrop option", async (assert) => {
|
||||
assert.expect(2);
|
||||
|
||||
class List extends Component {
|
||||
setup() {
|
||||
useSortable({
|
||||
ref: useRef("root"),
|
||||
elements: ".item",
|
||||
placeholderClasses: ["placeholder"],
|
||||
applyChangeOnDrop: true,
|
||||
onDragStart({ element }) {
|
||||
const items = [...target.querySelectorAll(".item:not(.placeholder)")];
|
||||
assert.strictEqual(items.map((el) => el.innerText).toString(), "1,2,3");
|
||||
},
|
||||
onDragEnd({ element, group }) {
|
||||
const items = [...target.querySelectorAll(".item:not(.placeholder)")];
|
||||
assert.strictEqual(items.map((el) => el.innerText).toString(), "2,1,3");
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
List.template = xml`
|
||||
<div t-ref="root" class="root">
|
||||
<ul class="list">
|
||||
<li t-foreach="[1, 2, 3]" t-as="i" t-key="i" t-esc="i" class="item" />
|
||||
</ul>
|
||||
</div>`;
|
||||
|
||||
await mount(List, target);
|
||||
// First item after 2nd item
|
||||
const { drop, moveTo } = await drag(".item:first-child");
|
||||
await moveTo(".item:nth-child(2)");
|
||||
await drop();
|
||||
});
|
||||
|
||||
QUnit.test("clone option", async (assert) => {
|
||||
assert.expect(2);
|
||||
|
||||
class List extends Component {
|
||||
setup() {
|
||||
useSortable({
|
||||
ref: useRef("root"),
|
||||
elements: ".item",
|
||||
placeholderClasses: ["placeholder"],
|
||||
clone: false,
|
||||
onDragStart({ element }) {
|
||||
assert.containsOnce(target, ".placeholder:not(.item)");
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
List.template = xml`
|
||||
<div t-ref="root" class="root">
|
||||
<ul class="list">
|
||||
<li t-foreach="[1, 2, 3]" t-as="i" t-key="i" t-esc="i" class="item" />
|
||||
</ul>
|
||||
</div>`;
|
||||
|
||||
await mount(List, target);
|
||||
// First item after 2nd item
|
||||
const { drop, moveTo } = await drag(".item:first-child");
|
||||
await moveTo(".item:nth-child(2)");
|
||||
await drop();
|
||||
assert.containsNone(target, ".placeholder:not(.item)");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2008,6 +2008,14 @@ const ListUserValueWidget = UserValueWidget.extend({
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
destroy() {
|
||||
this.bindedSortable?.cleanup();
|
||||
this._super(...arguments);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -2170,16 +2178,18 @@ const ListUserValueWidget = UserValueWidget.extend({
|
||||
if (this.el.dataset.unsortable) {
|
||||
return;
|
||||
}
|
||||
$(this.listTable).sortable({
|
||||
axis: 'y',
|
||||
handle: '.o_we_drag_handle',
|
||||
items: 'tr',
|
||||
cursor: 'move',
|
||||
opacity: 0.6,
|
||||
stop: (event, ui) => {
|
||||
this._notifyCurrentState();
|
||||
this.bindedSortable = this.call(
|
||||
"sortable",
|
||||
"create",
|
||||
{
|
||||
ref: { el: this.listTable },
|
||||
elements: "tr",
|
||||
followingElementClasses: ["opacity-50"],
|
||||
handle: ".o_we_drag_handle",
|
||||
onDrop: () => this._notifyCurrentState(),
|
||||
applyChangeOnDrop: true,
|
||||
},
|
||||
});
|
||||
).enable();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
|
||||
@@ -77,7 +77,7 @@ wTourUtils.registerWebsitePreviewTour('snippet_social_media', {
|
||||
content: 'Drag the facebook link at the end of the list',
|
||||
trigger: 'we-list table we-button.o_we_drag_handle',
|
||||
position: 'bottom',
|
||||
run: "drag_and_drop we-list table tr:last-child",
|
||||
run: "drag_and_drop_native we-list table tr:last-child",
|
||||
},
|
||||
{
|
||||
content: 'Check drop completed',
|
||||
|
||||
@@ -111,6 +111,11 @@
|
||||
});
|
||||
},
|
||||
|
||||
destroy() {
|
||||
this._unbindSortable();
|
||||
return this._super(...arguments);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -141,12 +146,23 @@
|
||||
* @private
|
||||
*/
|
||||
_bindSortable: function () {
|
||||
this.$el.sortable({
|
||||
handle: '.o_wslides_js_quiz_sequence_handler',
|
||||
items: '.o_wslides_js_lesson_quiz_question',
|
||||
stop: this._reorderQuestions.bind(this),
|
||||
placeholder: 'o_wslides_js_quiz_sequence_highlight position-relative my-3'
|
||||
});
|
||||
this.bindedSortable = this.call(
|
||||
"sortable",
|
||||
"create",
|
||||
{
|
||||
ref: { el: this.el },
|
||||
handle: ".o_wslides_js_quiz_sequence_handler",
|
||||
elements: ".o_wslides_js_lesson_quiz_question",
|
||||
onDrop: this._reorderQuestions.bind(this),
|
||||
clone: false,
|
||||
placeholderClasses: ['o_wslides_js_quiz_sequence_highlight', 'position-relative', 'my-3'],
|
||||
applyChangeOnDrop: true
|
||||
},
|
||||
).enable();
|
||||
},
|
||||
|
||||
_unbindSortable: function () {
|
||||
this.bindedSortable?.cleanup();
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,11 +11,17 @@ publicWidget.registry.websiteSlidesCourseSlidesList = SlideCoursePage.extend({
|
||||
this._super.apply(this,arguments);
|
||||
|
||||
this.channelId = this.$el.data('channelId');
|
||||
this.bindedSortable = [];
|
||||
|
||||
this._updateHref();
|
||||
this._bindSortable();
|
||||
},
|
||||
|
||||
destroy() {
|
||||
this._unbindSortable();
|
||||
return this._super(...arguments);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------,
|
||||
@@ -28,20 +34,43 @@ publicWidget.registry.websiteSlidesCourseSlidesList = SlideCoursePage.extend({
|
||||
* @private
|
||||
*/
|
||||
_bindSortable: function () {
|
||||
this.$('ul.o_wslides_js_slides_list_container').sortable({
|
||||
handle: '.o_wslides_slides_list_drag',
|
||||
stop: this._reorderSlides.bind(this),
|
||||
items: '.o_wslides_slide_list_category',
|
||||
placeholder: 'o_wslides_slides_list_slide_hilight position-relative mb-1'
|
||||
});
|
||||
const sortableBaseParam = {
|
||||
clone: false,
|
||||
placeholderClasses: ['o_wslides_slides_list_slide_hilight', 'position-relative', 'mb-1'],
|
||||
onDrop: this._reorderSlides.bind(this),
|
||||
applyChangeOnDrop: true
|
||||
};
|
||||
|
||||
this.$('.o_wslides_js_slides_list_container ul').sortable({
|
||||
handle: '.o_wslides_slides_list_drag',
|
||||
connectWith: '.o_wslides_js_slides_list_container ul',
|
||||
stop: this._reorderSlides.bind(this),
|
||||
items: '.o_wslides_slides_list_slide:not(.o_wslides_js_slides_list_empty)',
|
||||
placeholder: 'o_wslides_slides_list_slide_hilight position-relative mb-1'
|
||||
});
|
||||
const container = this.el.querySelector('ul.o_wslides_js_slides_list_container');
|
||||
this.bindedSortable.push(this.call(
|
||||
"sortable",
|
||||
"create",
|
||||
{
|
||||
...sortableBaseParam,
|
||||
ref: { el: container },
|
||||
elements: ".o_wslides_slide_list_category",
|
||||
handle: ".o_wslides_slide_list_category_header .o_wslides_slides_list_drag",
|
||||
sortableId: "category",
|
||||
},
|
||||
).enable());
|
||||
|
||||
this.bindedSortable.push(this.call(
|
||||
"sortable",
|
||||
"create",
|
||||
{
|
||||
...sortableBaseParam,
|
||||
ref: { el: container },
|
||||
elements: ".o_wslides_slides_list_slide:not(.o_wslides_js_slides_list_empty):not(.o_not_editable)",
|
||||
handle: ".o_wslides_slides_list_drag",
|
||||
connectGroups: true,
|
||||
groups: ".o_wslides_js_slides_list_container ul",
|
||||
sortableId: "list",
|
||||
},
|
||||
).enable());
|
||||
},
|
||||
|
||||
_unbindSortable: function () {
|
||||
this.bindedSortable.forEach(sortable => sortable.cleanup());
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user