diff --git a/src/dom_components/model/Component.js b/src/dom_components/model/Component.js index fbd649d4d..aec02a40f 100644 --- a/src/dom_components/model/Component.js +++ b/src/dom_components/model/Component.js @@ -61,10 +61,10 @@ export const keyUpdateInside = `${keyUpdate}-inside`; * @property {Object} [attributes={}] Key-value object of the component's attributes, eg. `{ title: 'Hello' }` Default: `{}` * @property {String} [name=''] Name of the component. Will be used, for example, in Layers and badges * @property {Boolean} [removable=true] When `true` the component is removable from the canvas, default: `true` - * @property {Boolean|String} [draggable=true] Indicates if it's possible to drag the component inside others. + * @property {Boolean|String|Function} [draggable=true] Indicates if it's possible to drag the component inside others. * You can also specify a query string to indentify elements, * eg. `'.some-class[title=Hello], [data-gjs-type=column]'` means you can drag the component only inside elements - * containing `some-class` class and `Hello` title, and `column` components. Default: `true` + * containing `some-class` class and `Hello` title, and `column` components. In the case of a function, target and destination components are passed as arguments, return a Boolean to indicate if the drag is possible. Default: `true` * @property {Boolean|String|Function} [droppable=true] Indicates if it's possible to drop other components inside. You can use * a query string as with `draggable`. In the case of a function, target and destination components are passed as arguments, return a Boolean to indicate if the drop is possible. Default: `true` * @property {Boolean} [badgable=true] Set to false if you don't want to see the badge (with the name) over the component. Default: `true` diff --git a/src/utils/Sorter.js b/src/utils/Sorter.js index b4b6c6573..3aec19472 100644 --- a/src/utils/Sorter.js +++ b/src/utils/Sorter.js @@ -619,16 +619,24 @@ export default Backbone.View.extend({ return result; } - // check if the source is draggable in target + // Check if the source is draggable in target let draggable = srcModel.get('draggable'); - draggable = draggable instanceof Array ? draggable.join(', ') : draggable; - result.dragInfo = draggable; - draggable = isString(draggable) ? this.matches(trg, draggable) : draggable; - result.draggable = draggable; + if (isFunction(draggable)) { + const res = draggable(srcModel, trgModel); + result.dragInfo = res; + result.draggable = res; + draggable = res; + } else { + draggable = draggable instanceof Array ? draggable.join(', ') : draggable; + result.dragInfo = draggable; + draggable = isString(draggable) + ? this.matches(trg, draggable) + : draggable; + result.draggable = draggable; + } // Check if the target could accept the source let droppable = trgModel.get('droppable'); - if (isFunction(droppable)) { const res = droppable(srcModel, trgModel); result.droppable = res; diff --git a/test/specs/utils/Sorter.js b/test/specs/utils/Sorter.js index 3b6459b6b..d4f32ecee 100644 --- a/test/specs/utils/Sorter.js +++ b/test/specs/utils/Sorter.js @@ -436,56 +436,100 @@ describe('Sorter', () => { }); describe('Valid Target with components', () => { - var parentModel; - var parentView; - var sorter; + describe('Droppable', () => { + var parentModel; + var parentView; - beforeEach(() => { - parentModel = new Component({ - droppable: (srcModel, trgModel) => { - if (srcModel.getEl().className === 'canDrop') { - return true; + beforeEach(() => { + parentModel = new Component({ + droppable: (srcModel, trgModel) => { + return srcModel.getEl().className === 'canDrop'; } - return false; - } + }); + parentView = new ComponentTextView({ + model: parentModel + }); }); - parentView = new ComponentTextView({ - model: parentModel + + afterEach(() => { + parentView.remove(); }); - sorter = new Sorter({ container: parentView.el }); - }); + test('Droppable function', () => { + var srcModel = new Component({ + tagName: 'div', + draggable: true, + content: 'Content text', + attributes: { class: 'canDrop' } + }); + var srcView = new ComponentTextView({ + model: srcModel + }); + + expect(obj.validTarget(parentView.el, srcView.el).valid).toEqual(true); + }); - afterEach(() => { - parentView.remove(); + test('Not droppable function', () => { + var srcModel = new Component({ + tagName: 'div', + draggable: true, + content: 'Content text', + attributes: { class: 'cannotDrop' } + }); + var srcView = new ComponentTextView({ + model: srcModel + }); + + expect(obj.validTarget(parentView.el, srcView.el).valid).toEqual(false); + }); }); - test('Droppable function', () => { - var srcModel = new Component({ - tagName: 'div', - draggable: true, // Can't move it - content: 'Content text', // Text inside component - attributes: { class: 'canDrop' } - }); - var srcView = new ComponentTextView({ - model: srcModel - }); + describe('Draggable', () => { + var srcModel; + var srcView; - expect(obj.validTarget(parentView.el, srcView.el).valid).toEqual(true); - }); + beforeEach(() => { + srcModel = new Component({ + draggable: (srcModel, trgModel) => { + return trgModel.getEl().className === 'canDrag'; + } + }); + srcView = new ComponentTextView({ + model: srcModel + }); + }); - test('Not droppable function', () => { - var srcModel = new Component({ - tagName: 'div', - draggable: true, - content: 'Content text', - attributes: { class: 'cannotDrop' } + afterEach(() => { + srcView.remove(); }); - var srcView = new ComponentTextView({ - model: srcModel + + test('Draggable function', () => { + var parentModel = new Component({ + tagName: 'div', + droppable: true, + content: 'Content text', + attributes: { class: 'canDrag' } + }); + var parentView = new ComponentTextView({ + model: parentModel + }); + + expect(obj.validTarget(parentView.el, srcView.el).valid).toEqual(true); }); - expect(obj.validTarget(parentView.el, srcView.el).valid).toEqual(false); + test('Not draggable function', () => { + var parentModel = new Component({ + tagName: 'div', + droppable: true, + content: 'Content text', + attributes: { class: 'cannotDrag' } + }); + var parentView = new ComponentTextView({ + model: parentModel + }); + + expect(obj.validTarget(parentView.el, srcView.el).valid).toEqual(false); + }); }); }); });