mirror of https://github.com/artf/grapesjs.git
11 changed files with 614 additions and 3 deletions
@ -0,0 +1,382 @@ |
|||
define(['backbone'], |
|||
function(Backbone) { |
|||
|
|||
return Backbone.View.extend({ |
|||
|
|||
initialize: function(opt) { |
|||
_.bindAll(this,'startSort','onMove','endMove','rollback', 'itemLeft'); |
|||
var o = opt || {}; |
|||
this.config = o.config || {}; |
|||
this.pfx = this.config.stylePrefix || ''; |
|||
this.itemClass = '.' + this.pfx + this.config.itemClass; |
|||
this.itemsClass = '.' + this.pfx + this.config.itemsClass; |
|||
this.setElement('.'+this.pfx+this.config.containerId); |
|||
|
|||
this.elT = 0; |
|||
this.elL = 0; |
|||
|
|||
this.el = document.querySelector(o.container); |
|||
this.$el = $(this.el); |
|||
this.containerSel = 'div'; |
|||
this.itemSel = 'div'; |
|||
}, |
|||
|
|||
/** |
|||
* Returns true if the element matches with selector |
|||
* @param {Element} el |
|||
* @param {String} selector |
|||
* @return {Boolean} |
|||
*/ |
|||
matches: function(el, selector){ |
|||
var els = (el.parentNode || document.body).querySelectorAll(selector); |
|||
var i = 0; |
|||
while (els[i] && els[i] !== el) |
|||
++i; |
|||
return !!els[i]; |
|||
}, |
|||
|
|||
/** |
|||
* Closest parent |
|||
* @param {Element} el |
|||
* @param {String} selector |
|||
* @return {Element|null} |
|||
*/ |
|||
closest: function(el, selector){ |
|||
var elem = el.parentNode; |
|||
while (elem && elem.nodeType === 1) { |
|||
if (this.matches(elem, selector)) |
|||
return elem; |
|||
elem = elem.parentNode; |
|||
} |
|||
return null; |
|||
}, |
|||
|
|||
/** |
|||
* Get the offset of the element |
|||
* @param {HTMLElement} el |
|||
* @return {Object} |
|||
*/ |
|||
offset: function(el){ |
|||
var rect = el.getBoundingClientRect(); |
|||
return { |
|||
top: rect.top + document.body.scrollTop, |
|||
left: rect.left + document.body.scrollLeft |
|||
}; |
|||
}, |
|||
|
|||
/** |
|||
* Create placeholder |
|||
* @return {HTMLElement} |
|||
*/ |
|||
createPlaceholder: function(){ |
|||
var pfx = this.pfx; |
|||
var el = document.createElement('div'); |
|||
var ins = document.createElement('div'); |
|||
el.id = pfx + 'placeholder'; |
|||
el.style.display = 'none'; |
|||
el.style['pointer-events'] = 'none'; |
|||
ins.id = pfx + "plh-int"; |
|||
el.appendChild(ins); |
|||
return el; |
|||
}, |
|||
|
|||
/** |
|||
* Picking component to move |
|||
* @param {Object} Element view |
|||
* */ |
|||
startSort: function(el){ |
|||
this.moved = 0; |
|||
this.eV = el.el || el; |
|||
|
|||
// Create placeholder if not exists
|
|||
if(!this.plh){ |
|||
this.plh = this.createPlaceholder(); |
|||
this.el.appendChild(this.plh); |
|||
} |
|||
//freeze el
|
|||
|
|||
this.$el.on('mousemove',this.onMove); |
|||
$(document).on('mouseup',this.endMove); |
|||
$(document).on('keypress',this.rollback); |
|||
}, |
|||
|
|||
/** |
|||
* During move |
|||
* @param {Event} e |
|||
* */ |
|||
onMove: function(e){ |
|||
this.moved = 1; |
|||
|
|||
// Turn placeholder visibile
|
|||
var plh = this.plh; |
|||
if(plh.style.display === 'none'){ |
|||
plh.style.display = 'block'; |
|||
} |
|||
|
|||
// Cache all necessary positions
|
|||
var rect = this.el.getBoundingClientRect(); |
|||
var body = document.body; |
|||
var eO = { top: rect.top + body.scrollTop, left: rect.left + body.scrollLeft }; |
|||
this.elT = eO.top; |
|||
this.elL = eO.left; |
|||
this.rY = (e.pageY - this.elT) + this.el.scrollTop; |
|||
this.rX = (e.pageX - this.elL) + this.el.scrollLeft; |
|||
|
|||
this.inspect(e); |
|||
this.updatePosition(this.rX, this.rY); |
|||
var actualPos = this.posIndex+':'+this.posMethod; |
|||
|
|||
//If there is a significant changes with the pointer
|
|||
if(!this.lastPos || (this.lastPos != actualPos)){ |
|||
this.updatePlaceholderPos(this.posIndex, this.posMethod); |
|||
this.lastPos = this.posIndex+':'+this.posMethod; |
|||
} |
|||
//Working alternative for find taget element
|
|||
//var $targetEl = this.$selParent.children('.'+this.pfx+this.config.itemClass).eq(this.aIndex);
|
|||
}, |
|||
|
|||
/** |
|||
* Get children dimensions |
|||
* @param {HTMLELement} el Element root |
|||
* @retun {Array} |
|||
* */ |
|||
getChildrenDim: function(elem){ |
|||
var dim = []; |
|||
var ch = elem.children;//TODO filter match
|
|||
for (var i = 0, len = ch.length; i < len; i++) { |
|||
var el = ch[i]; |
|||
var elO = this.offset(el); |
|||
dim.push([elO.top - this.elT, elO.left - this.elL, el.offsetHeight, el.offsetWidth, true, el]); |
|||
} |
|||
return dim; |
|||
}, |
|||
|
|||
/** |
|||
* Search where to put placeholder |
|||
* @param int X position of the mouse |
|||
* @param int Y position of the mouse |
|||
* @retun void |
|||
* */ |
|||
updatePosition: function( posX, posY ){ |
|||
this.posMethod = "before"; |
|||
this.posIndex = 0; |
|||
var leftLimit = 0, xLimit = 0, dimRight = 0, yLimit = 0, xCenter = 0, yCenter = 0, dimDown = 0, dim = 0; |
|||
for(var i = 0; i < this.cDim.length; i++){ //Dim => t,l,h,w
|
|||
dim = this.cDim[i]; |
|||
dimDown = dim[0] + dim[2]; |
|||
yCenter = dim[0] + (dim[2] / 2); //Horizontal center
|
|||
xCenter = dim[1] + (dim[3] / 2); //Vertical center
|
|||
dimRight = dim[1] + dim[3]; |
|||
if( (xLimit && dim[1] > xLimit) || (yLimit && yCenter > yLimit) || |
|||
(leftLimit && dimRight < leftLimit)) //No need with this one if over the limit
|
|||
continue; |
|||
if(!dim[4]){ //If it's not inFlow (like float element)
|
|||
if( posY < dimDown) |
|||
yLimit = dimDown; |
|||
if( posX < xCenter){ //If mouse lefter than center
|
|||
xLimit = xCenter; |
|||
this.posMethod = "before"; |
|||
}else{ |
|||
leftLimit = xCenter; |
|||
this.posMethod = "after"; |
|||
} |
|||
this.posIndex = i; |
|||
}else{ |
|||
this.posIndex = this.aIndex = i; |
|||
if( posY < yCenter ){ //If mouse upper than center
|
|||
this.posMethod = "before"; //Should place helper before
|
|||
if(posY < dim[0]) |
|||
this.aIndex = i - 1; |
|||
break; //No need to continue under inFlow element
|
|||
}else |
|||
this.posMethod = "after"; |
|||
} |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* Updates the position of the placeholder |
|||
* @param int Index of the nearest child |
|||
* @param str Before or after position |
|||
* @return void |
|||
* */ |
|||
updatePlaceholderPos: function(index, method){ |
|||
var marg = 0, t = 0, l = 0, w = 0, h = 0, |
|||
un = 'px', |
|||
margI = 5, |
|||
plh = this.$plh[0]; |
|||
if( this.cDim[index] ){ |
|||
var elDim = this.cDim[index]; |
|||
//If it's like with 'float' style
|
|||
if(!elDim[4]){ |
|||
w = 'auto'; |
|||
h = elDim[2] - (marg * 2) + un; |
|||
t = elDim[0] + marg; |
|||
l = (method == 'before') ? (elDim[1] - marg) : (elDim[1] + elDim[3] - marg); |
|||
}else{ |
|||
//w = '100%';
|
|||
w = elDim[3] + un; |
|||
//h = elDim[3] + un;
|
|||
t = (method == 'before') ? (elDim[0] - marg) : (elDim[0] + elDim[2] - marg); |
|||
l = elDim[1]; |
|||
} |
|||
}else{ |
|||
if(this.$targetEl){ |
|||
var trg = this.$targetEl[0], |
|||
$elO = this.$targetEl.offset(); |
|||
t = $elO.top - this.elT + margI + 17; |
|||
l = $elO.left - this.elL + margI * 7; |
|||
w = (parseInt(trg.offsetWidth) - margI * 14) + un; |
|||
} |
|||
} |
|||
plh.style.top = t + un; |
|||
plh.style.left = l + un; |
|||
if(w) |
|||
plh.style.width = w; |
|||
if(h) |
|||
plh.style.height = h; |
|||
}, |
|||
|
|||
/** |
|||
* Leave item |
|||
* @param event |
|||
* |
|||
* @return void |
|||
* */ |
|||
endMove: function(e){ |
|||
this.$el.off('mousemove',this.onMove); |
|||
$(document).off('mouseup', this.endMove); |
|||
$(document).off('keypress', this.rollback); |
|||
this.eV.unfreeze(); |
|||
this.$plh.hide(); |
|||
if(this.moved) |
|||
this.move(this.$targetEl, this.$sel, this.posIndex, this.posMethod); |
|||
this.itemLeft(); |
|||
}, |
|||
|
|||
/** |
|||
* Move component to new position |
|||
* @param {Object} Component to move |
|||
* @param {Object} Target component |
|||
* @param {Integer} Indicates the position inside the collection |
|||
* @param {String} Before of after component |
|||
* |
|||
* @return void |
|||
* */ |
|||
move: function(target, el, posIndex, method){ |
|||
var trg = target|| this.$targetEl; |
|||
trg = trg || this.$backupEl; |
|||
if(!trg) |
|||
return; |
|||
var index = posIndex || 0; |
|||
var model = el.data("model"); |
|||
var collection = model.collection; |
|||
var targetModel = trg.data('model'); |
|||
var targetCollection = targetModel.collection; |
|||
|
|||
if(!this.cDim.length) |
|||
targetCollection = targetModel.get('components'); |
|||
|
|||
if(targetCollection && targetModel.get('droppable')){ |
|||
index = method == 'after' ? index + 1 : index; |
|||
var modelTemp = targetCollection.add({style:{}}, { at: index}); |
|||
var modelRemoved = collection.remove(model, { silent:false }); |
|||
targetCollection.add(modelRemoved, { at: index, silent:false }); |
|||
targetCollection.remove(modelTemp); |
|||
}else |
|||
console.warn("Invalid target position"); |
|||
}, |
|||
|
|||
/** |
|||
* Track inside which element pointer entered |
|||
* @param event |
|||
* |
|||
* @return void |
|||
* */ |
|||
inspect: function(e){ |
|||
var item = $(e.target).closest(this.itemClass); |
|||
if(!this.$targetEl || (item.length && item[0] != this.$targetEl[0]) ){ |
|||
this.status = 1; |
|||
if(item.length){ |
|||
this.$targetEl = this.$backupEl = item; |
|||
this.$targetElP = this.$targetEl.parent(); |
|||
this.$targetsEl = this.$targetEl.find(this.itemsClass + ':first'); |
|||
this.$targetEl.on('mouseleave', this.itemLeft); |
|||
this.targetM = this.$targetEl.data('model'); |
|||
this.dimT = this.getTargetDim(this.$targetEl[0]); |
|||
this.cDim = this.getChildrenDim(); |
|||
} |
|||
}else if( this.nearToBorders(this.$targetEl[0]) || this.$targetEl[0] == this.$sel[0] ){ |
|||
if(this.status == 1){ |
|||
this.status = 2; |
|||
this.lastPos = null; |
|||
this.cDim = this.getChildrenDim(this.$targetElP); |
|||
} |
|||
}else if( !this.nearToBorders(this.$targetEl[0]) ){ |
|||
if(this.status == 2){ |
|||
this.status = 1; |
|||
this.lastPos = null; |
|||
} |
|||
this.cDim = []; |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* Triggered when pointer leaves item |
|||
* @param event |
|||
* |
|||
* @return void |
|||
* */ |
|||
itemLeft: function(e){ |
|||
if(this.$targetEl){ |
|||
this.$targetEl.off('mouseleave',this.itemLeft); |
|||
this.$targetEl = null; |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* Returns dimension of the target |
|||
* @param Event |
|||
* |
|||
* @return Array |
|||
* */ |
|||
getTargetDim: function(e){ |
|||
var $el = $(e), |
|||
$elO = $el.offset(); |
|||
return [ $elO.top - this.elT, $elO.left - this.elL, $el.outerHeight(), $el.outerWidth() ]; |
|||
}, |
|||
|
|||
/** |
|||
* Check if pointer is near to the borders of the target |
|||
* @param event |
|||
* @return Bool |
|||
* */ |
|||
nearToBorders: function(e){ |
|||
var m = 10; //Limit in pixels for be near
|
|||
if(!this.dimT) |
|||
return; |
|||
var dimT = this.dimT; |
|||
if( ((dimT[0] + m) > this.rY) || (this.rY > (dimT[0] + dimT[2] - m)) || |
|||
((dimT[1] + m) > this.rX) || (this.rX > (dimT[1] + dimT[3] - m)) ) |
|||
return 1; |
|||
else |
|||
return 0; |
|||
}, |
|||
|
|||
/** |
|||
* Rollback to previous situation |
|||
* @param Event |
|||
* @param Bool Indicates if rollback in anycase |
|||
* @return void |
|||
* */ |
|||
rollback: function(e, force){ |
|||
var key = e.which || e.keyCode; |
|||
if(key == 27 || force){ |
|||
this.moved = false; |
|||
this.endMove(); |
|||
} |
|||
return; |
|||
}, |
|||
|
|||
}); |
|||
}); |
|||
@ -0,0 +1,13 @@ |
|||
define(function(require) { |
|||
|
|||
var Utils = function(){ |
|||
|
|||
var Sorter = require('./Sorter'); |
|||
|
|||
return { |
|||
Sorter: Sorter, |
|||
}; |
|||
}; |
|||
|
|||
return Utils; |
|||
}); |
|||
@ -0,0 +1,163 @@ |
|||
var path = 'Utils/'; |
|||
define([path + 'Sorter',], |
|||
function(Sorter) { |
|||
|
|||
return { |
|||
run : function(){ |
|||
|
|||
describe('Sorter', function() { |
|||
var obj; |
|||
var parent; |
|||
|
|||
beforeEach(function () { |
|||
parent = document.createElement('div'); |
|||
parent.setAttribute('class', 'parent1'); |
|||
document.body.appendChild(parent); |
|||
obj = new Sorter({container: '.parent1'}); |
|||
}); |
|||
|
|||
afterEach(function () { |
|||
delete obj; |
|||
}); |
|||
|
|||
it('matches class', function() { |
|||
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); |
|||
}); |
|||
|
|||
it('matches id', function() { |
|||
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); |
|||
}); |
|||
|
|||
it('matches tag', function() { |
|||
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); |
|||
}); |
|||
|
|||
it('Creates placeholder', function() { |
|||
obj.createPlaceholder().id.should.equal('placeholder'); |
|||
}); |
|||
|
|||
describe('Closest method', function() { |
|||
var parent2; |
|||
var parent3; |
|||
|
|||
beforeEach(function () { |
|||
parent2 = document.createElement('span'); |
|||
parent2.setAttribute('class', 'parent2'); |
|||
parent3 = document.createElement('div'); |
|||
parent3.setAttribute('class', 'parent3'); |
|||
parent.appendChild(parent2); |
|||
parent2.appendChild(parent3); |
|||
}); |
|||
|
|||
it('Closest by class', function() { |
|||
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); |
|||
}); |
|||
|
|||
it('Closest by tag', function() { |
|||
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); |
|||
}); |
|||
|
|||
}); |
|||
|
|||
describe('With elements', function() { |
|||
|
|||
var parent2; |
|||
var parent3; |
|||
var sib1; |
|||
var sib2; |
|||
var sib3; |
|||
var sib4; |
|||
var el; |
|||
|
|||
beforeEach(function () { |
|||
parent2 = document.createElement('span'); |
|||
parent2.setAttribute('class', 'parent2'); |
|||
parent3 = document.createElement('div'); |
|||
parent3.setAttribute('class', 'parent3'); |
|||
parent.appendChild(parent2); |
|||
parent2.appendChild(parent3); |
|||
el = document.createElement('div'); |
|||
el.setAttribute('class', 'el'); |
|||
parent3.appendChild(el); |
|||
|
|||
sib1 = document.createElement('div'); |
|||
sib2 = document.createElement('div'); |
|||
sib3 = document.createElement('div'); |
|||
sib4 = document.createElement('div'); |
|||
sib1.style.width = '100px'; |
|||
sib1.style.height = '50px'; |
|||
sib2.style.width = '100px'; |
|||
sib2.style.height = '50px'; |
|||
sib3.style.width = '100px'; |
|||
sib3.style.height = '50px'; |
|||
sib3.style.float = 'left'; |
|||
sib4.style.width = '70px'; |
|||
sib4.style.height = '50px'; |
|||
sib4.style.float = 'left'; |
|||
el.appendChild(sib1); |
|||
el.appendChild(sib2); |
|||
el.appendChild(sib3); |
|||
el.appendChild(sib4); |
|||
}); |
|||
|
|||
it('startSort inits correctly inits', function() { |
|||
obj.startSort(el); |
|||
obj.plh.style.display.should.equal('none'); |
|||
}); |
|||
|
|||
it.skip('onMove', function() { |
|||
obj.startSort(el); |
|||
obj.onMove({ |
|||
pageX: 0, |
|||
pageY: 0, |
|||
}); |
|||
obj.plh.style.display.should.equal('block'); |
|||
}); |
|||
|
|||
it('getChildrenDim from element', function() { |
|||
el.style.position = 'absolute'; |
|||
el.style.top = '0'; |
|||
var ch = obj.getChildrenDim(el); |
|||
ch = ch.map(function(v){ |
|||
return v.slice(0, 5); |
|||
}); |
|||
var result = [ |
|||
[0, 0, 50, 100, true], |
|||
[50, 0, 50, 100, true], |
|||
[100, 0, 50, 100, true], |
|||
[100, 100, 50, 70, true], |
|||
] |
|||
ch.should.deep.equal(result); |
|||
}); |
|||
|
|||
}); |
|||
|
|||
}); |
|||
|
|||
} |
|||
}; |
|||
|
|||
}); |
|||
@ -0,0 +1,17 @@ |
|||
var modulePath = './../../../test/specs/utils'; |
|||
|
|||
define([ |
|||
'Utils', |
|||
modulePath + '/Sorter' |
|||
], |
|||
function( |
|||
Utils, |
|||
Sorter |
|||
) { |
|||
|
|||
describe('Utils', function() { |
|||
|
|||
Sorter.run(); |
|||
|
|||
}); |
|||
}); |
|||
Loading…
Reference in new issue