diff --git a/src/dom_components/model/Component.js b/src/dom_components/model/Component.js index c8f2654e1..a3f580c6d 100644 --- a/src/dom_components/model/Component.js +++ b/src/dom_components/model/Component.js @@ -58,7 +58,7 @@ export const keyUpdateInside = `${keyUpdate}-inside`; * 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` - * @property {Boolean|String} [droppable=true] Indicates if it's possible to drop other components inside. You can use + * @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`. 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` * @property {Boolean|Array} [stylable=true] True if it's possible to style the component. diff --git a/src/utils/Sorter.js b/src/utils/Sorter.js index 16d691626..f786a0e79 100644 --- a/src/utils/Sorter.js +++ b/src/utils/Sorter.js @@ -558,7 +558,8 @@ export default Backbone.View.extend({ const $parent = parent && $(parent); if (style.overflow && style.overflow !== 'visible') return; - if ($el.css('float') !== 'none') return; + const propFloat = $el.css('float'); + if (propFloat && propFloat !== 'none') return; if ( $parent && $parent.css('display') == 'flex' && @@ -622,13 +623,22 @@ export default Backbone.View.extend({ // Check if the target could accept the source let droppable = trgModel.get('droppable'); - droppable = droppable instanceof Backbone.Collection ? 1 : droppable; - droppable = droppable instanceof Array ? droppable.join(', ') : droppable; - result.dropInfo = droppable; - droppable = isString(droppable) ? this.matches(src, droppable) : droppable; - droppable = - draggable && this.isTextableActive(srcModel, trgModel) ? 1 : droppable; - result.droppable = droppable; + if (typeof droppable === 'function') { + let res = droppable(srcModel, trgModel); + result.droppable = res; + result.dropInfo = res; + droppable = res; + } else { + droppable = droppable instanceof Backbone.Collection ? 1 : droppable; + droppable = droppable instanceof Array ? droppable.join(', ') : droppable; + result.dropInfo = droppable; + droppable = isString(droppable) + ? this.matches(src, droppable) + : droppable; + droppable = + draggable && this.isTextableActive(srcModel, trgModel) ? 1 : droppable; + result.droppable = droppable; + } if (!droppable || !draggable) { result.valid = false; diff --git a/test/specs/utils/Sorter.js b/test/specs/utils/Sorter.js index d6a0d1845..d084b8ba6 100644 --- a/test/specs/utils/Sorter.js +++ b/test/specs/utils/Sorter.js @@ -1,8 +1,13 @@ -// import Sorter from '../../../src/utils/Sorter.js' +import Backbone from 'backbone'; +import Sorter from '../../../src/utils/Sorter.js'; +import ComponentView from 'dom_components/view/ComponentView'; +import ComponentTextView from 'dom_components/view/ComponentTextView'; +import Component from 'dom_components/model/Component'; +const $ = Backbone.$; // TODO: Migrate this file to Jest -describe.skip('Sorter', () => { +describe('Sorter', () => { var fixture; var obj; var parent; @@ -21,32 +26,32 @@ describe.skip('Sorter', () => { obj = new Sorter({ container: '.parent1' }); document.body.appendChild(fixture); fixture.appendChild(parent); - html = - '
' + - '
ba' + - '

baa

' + - 'bab' + - 'bac' + - '
eldiv
' + - '
' + - '
' + - '
' + - '
aa' + - '

aaa

' + - 'aab' + - 'aac' + - '
' + - '
ab
' + - '
ac' + - '
aca
' + - '
acb
' + - '
' + - '' + - '
'; + html = ` +
+
ba +

baa

+ bab + bac +
eldiv
+
+
+
+
aa +

aaa

+ aab + aac +
+
ab
+
ac +
aca
+
acb
+
+ +
`; }); afterEach(() => { @@ -60,36 +65,36 @@ describe.skip('Sorter', () => { var el = document.createElement('div'); el.setAttribute('class', 'test test2'); parent.appendChild(el); - obj.matches(el, '.test').should.equal(true); - obj.matches(el, '.test2').should.equal(true); - obj.matches(el, '.test3').should.equal(false); + expect(obj.matches(el, '.test')).toEqual(true); + expect(obj.matches(el, '.test2')).toEqual(true); + expect(obj.matches(el, '.test3')).toEqual(false); }); test('matches id', () => { var el = document.createElement('div'); el.setAttribute('id', 'test2'); parent.appendChild(el); - obj.matches(el, '#test2').should.equal(true); - obj.matches(el, '.test2').should.equal(false); - obj.matches(el, '#test').should.equal(false); + expect(obj.matches(el, '#test2')).toEqual(true); + expect(obj.matches(el, '.test2')).toEqual(false); + expect(obj.matches(el, '#test')).toEqual(false); }); test('matches tag', () => { var el = document.createElement('span'); parent.appendChild(el); - obj.matches(el, 'span').should.equal(true); - obj.matches(el, 'div').should.equal(false); - obj.matches(el, '*').should.equal(true); + expect(obj.matches(el, 'span')).toEqual(true); + expect(obj.matches(el, 'div')).toEqual(false); + expect(obj.matches(el, '*')).toEqual(true); }); test('Creates placeholder', () => { - obj.createPlaceholder().className.should.equal('placeholder'); + expect(obj.createPlaceholder().className).toEqual('placeholder'); }); test('isInFlow to overflow hidden', () => { parent.innerHTML = html; var el = parent.querySelector('#el1'); - obj.isInFlow(el).should.equal(false); + expect(obj.isInFlow(el)).toEqual(false); }); test('isInFlow inner to overflow', () => { @@ -99,13 +104,13 @@ describe.skip('Sorter', () => { console.log('phantom issue'); return; } - obj.isInFlow(el).should.equal(true); + expect(obj.isInFlow(el)).toEqual(true); }); test('isInFlow for span', () => { parent.innerHTML = html; var el = parent.querySelector('#elspan'); - obj.isInFlow(el).should.equal(false); + expect(obj.isInFlow(el)).toEqual(false); }); test('isInFlow for div #a', () => { @@ -115,7 +120,7 @@ describe.skip('Sorter', () => { console.log('phantom issue'); return; } - obj.isInFlow(el).should.equal(true); + expect(obj.isInFlow(el)).toEqual(true); }); test('isInFlow for div #aa', () => { @@ -125,7 +130,7 @@ describe.skip('Sorter', () => { console.log('phantom issue'); return; } - obj.isInFlow(el).should.equal(true); + expect(obj.isInFlow(el)).toEqual(true); }); test('isInFlow for p #aaa', () => { @@ -135,13 +140,13 @@ describe.skip('Sorter', () => { console.log('phantom issue'); return; } - obj.isInFlow(el).should.equal(true); + expect(obj.isInFlow(el)).toEqual(true); }); test('isInFlow for span #aab', () => { parent.innerHTML = html; var el = parent.querySelector('#aab'); - obj.isInFlow(el).should.equal(false); + expect(obj.isInFlow(el)).toEqual(false); }); test('isInFlow for span #aac with display block', () => { @@ -150,19 +155,19 @@ describe.skip('Sorter', () => { if (!el) // in phantom doesnt work return; - obj.isInFlow(el).should.equal(true); + expect(obj.isInFlow(el)).toEqual(true); }); test('isInFlow for div #ab with float left', () => { parent.innerHTML = html; var el = parent.querySelector('#ab'); - obj.isInFlow(el).should.equal(false); + expect(obj.isInFlow(el)).toEqual(false); }); test('isInFlow for div #ac in absolute', () => { parent.innerHTML = html; var el = parent.querySelector('#ac'); - obj.isInFlow(el).should.equal(false); + expect(obj.isInFlow(el)).toEqual(false); }); test('isInFlow for div #acb inside absolute', () => { @@ -172,13 +177,13 @@ describe.skip('Sorter', () => { console.log('phantom issue'); return; } - obj.isInFlow(el).should.equal(true); + expect(obj.isInFlow(el)).toEqual(true); }); test('isInFlow for div #ad overflow hidden', () => { parent.innerHTML = html; var el = parent.querySelector('#ad'); - obj.isInFlow(el).should.equal(false); + expect(obj.isInFlow(el)).toEqual(false); }); describe('Closest method', () => { @@ -197,22 +202,22 @@ describe.skip('Sorter', () => { test('Closest by class', () => { var el = document.createElement('div'); parent3.appendChild(el); - obj.closest(el, '.parent2').should.deep.equal(parent2); - obj.closest(el, '.parent3').should.deep.equal(parent3); - obj.closest(el, '.parent1').should.deep.equal(parent); + expect(obj.closest(el, '.parent2')).toEqual(parent2); + expect(obj.closest(el, '.parent3')).toEqual(parent3); + expect(obj.closest(el, '.parent1')).toEqual(parent); }); test('Closest by tag', () => { var el = document.createElement('div'); el.setAttribute('class', 'el'); parent3.appendChild(el); - obj.closest(el, 'span').should.deep.equal(parent2); - obj.closest(el, 'div').should.deep.equal(parent3); - obj.closest(el, '*').should.deep.equal(parent3); + expect(obj.closest(el, 'span')).toEqual(parent2); + expect(obj.closest(el, 'div')).toEqual(parent3); + expect(obj.closest(el, '*')).toEqual(parent3); }); }); - describe('With elements', () => { + describe.skip('With elements', () => { var vertDims; var parent2; var parent3; @@ -262,8 +267,8 @@ describe.skip('Sorter', () => { test('startSort inits correctly inits', () => { obj.startSort(el); - obj.moved.should.equal(0); - obj.plh.style.display.should.equal('none'); + expect(obj.moved).toEqual(0); + expect(obj.plh.style.display).toEqual('none'); }); test('onMove', () => { @@ -274,7 +279,7 @@ describe.skip('Sorter', () => { pageY: 0, target: target }); - obj.moved.should.equal(1); + expect(obj.moved).toEqual(1); }); test('getDim from element', () => { @@ -282,7 +287,7 @@ describe.skip('Sorter', () => { var top = subPos.top; var left = subPos.left; var result = [top, left, 50, 100]; - obj.getDim(sib1).should.deep.equal(result); + expect(obj.getDim(sib1)).toEqual(result); }); test('getChildrenDim from element', () => { @@ -301,15 +306,15 @@ describe.skip('Sorter', () => { [top + 100, left + 0, 50, 100, true], [top + 100, left + 100, 50, 70, true] ]; - ch.should.deep.equal(result); + expect(ch).toEqual(result); }); test('nearBorders', () => { obj.borderOffset = 10; var dim = [0, 0, 100, 200]; - obj.nearBorders(dim, 20, 15).should.equal(false); - obj.nearBorders(dim, 3, 4).should.equal(true); - obj.nearBorders(dim, 500, 500).should.equal(true); + expect(obj.nearBorders(dim, 20, 15)).toEqual(false); + expect(obj.nearBorders(dim, 3, 4)).toEqual(true); + expect(obj.nearBorders(dim, 500, 500)).toEqual(true); }); test('dimsFromTarget', () => { @@ -342,14 +347,15 @@ describe.skip('Sorter', () => { dims = dims.map(function(v) { return v.slice(0, 5); }); - dims.should.deep.equal(resultParent); + + expect(dims).toEqual(resultParent); // Inside target var dims = obj.dimsFromTarget(sib3, leftSib3 + 15, topSib3 + 15); dims = dims.map(function(v) { return v.slice(0, 5); }); - dims.should.deep.equal(resultChildren); + expect(dims).toEqual(resultChildren); // Exactly on border var bOffset = obj.borderOffset; @@ -361,7 +367,7 @@ describe.skip('Sorter', () => { dims = dims.map(function(v) { return v.slice(0, 5); }); - dims.should.deep.equal(resultChildren); + expect(dims).toEqual(resultChildren); // Slightly near border var dims = obj.dimsFromTarget( @@ -372,7 +378,7 @@ describe.skip('Sorter', () => { dims = dims.map(function(v) { return v.slice(0, 5); }); - dims.should.deep.equal(resultParent); + expect(dims).toEqual(resultParent); }); describe('findPosition', () => { @@ -380,23 +386,23 @@ describe.skip('Sorter', () => { test('Vertical dimensions', () => { var result = { index: 0, method: 'before' }; - obj.findPosition(vertDims, -10, -10).should.deep.equal(result); - obj.findPosition(vertDims, 0, 0).should.deep.equal(result); - obj.findPosition(vertDims, 10, 10).should.deep.equal(result); + expect(obj.findPosition(vertDims, -10, -10)).toEqual(result); + expect(obj.findPosition(vertDims, 0, 0)).toEqual(result); + expect(obj.findPosition(vertDims, 10, 10)).toEqual(result); var result = { index: 1, method: 'before' }; - obj.findPosition(vertDims, 10, 30).should.deep.equal(result); - obj.findPosition(vertDims, 10, 70).should.deep.equal(result); + expect(obj.findPosition(vertDims, 10, 30)).toEqual(result); + expect(obj.findPosition(vertDims, 10, 70)).toEqual(result); var result = { index: 2, method: 'before' }; - obj.findPosition(vertDims, 10, 76).should.deep.equal(result); + expect(obj.findPosition(vertDims, 10, 76)).toEqual(result); var result = { index: 3, method: 'before' }; - obj.findPosition(vertDims, 100, 140).should.deep.equal(result); - obj.findPosition(vertDims, 100, 160).should.deep.equal(result); + expect(obj.findPosition(vertDims, 100, 140)).toEqual(result); + expect(obj.findPosition(vertDims, 100, 160)).toEqual(result); var result = { index: 3, method: 'after' }; - obj.findPosition(vertDims, 1000, 1000).should.deep.equal(result); + expect(obj.findPosition(vertDims, 1000, 1000)).toEqual(result); }); }); @@ -414,19 +420,73 @@ describe.skip('Sorter', () => { var pos = { index: 2, method: 'before' }; obj.movePlaceholder(plh, vertDims, pos); var style = plh.style; - style.top.should.equal('100px'); - style.left.should.equal('30px'); - style.width.should.equal('100px'); + expect(style.top).toEqual('100px'); + expect(style.left).toEqual('30px'); + expect(style.width).toEqual('100px'); }); test('Vertical dimensions with after position', () => { var pos = { index: 1, method: 'after' }; obj.movePlaceholder(plh, vertDims, pos); var style = plh.style; - style.top.should.equal('100px'); - style.left.should.equal('20px'); - style.width.should.equal('70px'); + expect(style.top).toEqual('100px'); + expect(style.left).toEqual('20px'); + expect(style.width).toEqual('70px'); }); }); }); + + describe('Valid Target with components', () => { + var parentModel; + var parentView; + var sorter; + + beforeEach(() => { + parentModel = new Component({ + droppable: (srcModel, trgModel) => { + if (srcModel.getEl().className === 'canDrop') { + return true; + } + return false; + } + }); + parentView = new ComponentTextView({ + model: parentModel + }); + + sorter = new Sorter({ container: parentView.el }); + }); + + afterEach(() => { + parentView.remove(); + }); + + 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 + }); + + expect(obj.validTarget(parentView.el, srcView.el).valid).toEqual(true); + }); + + 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); + }); + }); });