diff --git a/src/config/require-config.js b/src/config/require-config.js
index 3d2836551..9d4656c78 100644
--- a/src/config/require-config.js
+++ b/src/config/require-config.js
@@ -45,6 +45,7 @@ require.config({
{ name: 'CssComposer', location: 'css_composer', },
{ name: 'Commands', location: 'commands', },
{ name: 'Canvas', location: 'canvas', },
- { name: 'Panels', location: 'panels', }
+ { name: 'Panels', location: 'panels', },
+ { name: 'Utils', location: 'utils', }
]
});
\ No newline at end of file
diff --git a/src/style_manager/templates/layer.html b/src/style_manager/templates/layer.html
index 58a55fb47..e045fb7ac 100644
--- a/src/style_manager/templates/layer.html
+++ b/src/style_manager/templates/layer.html
@@ -1,3 +1,6 @@
+
diff --git a/src/style_manager/view/LayerView.js b/src/style_manager/view/LayerView.js
index 873183362..d745009d7 100644
--- a/src/style_manager/view/LayerView.js
+++ b/src/style_manager/view/LayerView.js
@@ -20,6 +20,7 @@ define(['backbone', 'text!./../templates/layer.html'],
this.listenTo(this.model, 'change:value', this.valueChanged);
this.listenTo(this.model, 'change:props', this.showProps);
this.events['click #' + this.pfx + 'close-layer'] = 'remove';
+ //this.events['mousedown > #' + this.pfx + 'move'] = 'initSorter';
if( !this.model.get('preview') ){
this.$el.addClass(this.pfx + 'no-preview');
@@ -28,6 +29,15 @@ define(['backbone', 'text!./../templates/layer.html'],
this.delegateEvents();
},
+ /**
+ * Delegate sorting
+ * @param {Event} e
+ * */
+ initSorter: function(e){
+ if(this.sorter)
+ this.sorter.startMove(this);
+ },
+
/**
* Returns properties
* @return {Collection|null}
diff --git a/src/utils/Sorter.js b/src/utils/Sorter.js
new file mode 100644
index 000000000..42af3065d
--- /dev/null
+++ b/src/utils/Sorter.js
@@ -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;
+ },
+
+ });
+});
\ No newline at end of file
diff --git a/src/utils/main.js b/src/utils/main.js
new file mode 100644
index 000000000..b23bc9054
--- /dev/null
+++ b/src/utils/main.js
@@ -0,0 +1,13 @@
+define(function(require) {
+
+ var Utils = function(){
+
+ var Sorter = require('./Sorter');
+
+ return {
+ Sorter: Sorter,
+ };
+ };
+
+ return Utils;
+});
\ No newline at end of file
diff --git a/styles/css/main.css b/styles/css/main.css
index e1faf3a04..1242ef154 100644
--- a/styles/css/main.css
+++ b/styles/css/main.css
@@ -3215,6 +3215,17 @@ ol.example li.placeholder:before {
.wte-sm-sector .wte-sm-btn-upload #wte-sm-label, .wte-clm-tags .wte-sm-btn-upload #wte-sm-label {
padding: 2px 0; }
+.wte-sm-layer > #wte-sm-move {
+ opacity: 0.3;
+ filter: alpha(opacity=30);
+ cursor: pointer;
+ font-size: 12px;
+ float: left;
+ margin: 0 5px 0 0; }
+ .wte-sm-layer > #wte-sm-move:hover {
+ opacity: 0.5;
+ filter: alpha(opacity=50); }
+
/********* END Style Manager **********/
/********* Class manager **********/
.wte-clm-tags {
diff --git a/styles/scss/main.scss b/styles/scss/main.scss
index a01c0aea9..7374cb650 100644
--- a/styles/scss/main.scss
+++ b/styles/scss/main.scss
@@ -740,6 +740,17 @@ $arrowColor: darken($fontColor,24%); /*b1b1b1*/
.#{$sm-prefix}btn-upload ##{$sm-prefix}label { padding: 2px 0;}
}
+.#{$sm-prefix}layer > ##{$sm-prefix}move {
+ @include opacity(0.3);
+ cursor: pointer;
+ font-size: 12px;
+ float: left;
+ margin: 0 5px 0 0;
+
+ &:hover{
+ @include opacity(0.5);
+ }
+}
/********* END Style Manager **********/
/********* Class manager **********/
diff --git a/test/runner/main.js b/test/runner/main.js
index 28bf7e8df..f0e519ceb 100644
--- a/test/runner/main.js
+++ b/test/runner/main.js
@@ -17,7 +17,8 @@ require(['../src/config/require-config.js', 'config/config.js'], function() {
'specs/code_manager/main.js',
'specs/panels/main.js',
'specs/commands/main.js',
- 'specs/style_manager/main.js'
+ 'specs/style_manager/main.js',
+ 'specs/utils/main.js'
], function(chai)
{
var should = chai.should(),
diff --git a/test/specs/style_manager/view/LayerView.js b/test/specs/style_manager/view/LayerView.js
index 0af7c4679..f7aa3dc21 100644
--- a/test/specs/style_manager/view/LayerView.js
+++ b/test/specs/style_manager/view/LayerView.js
@@ -80,7 +80,6 @@ define([path + 'LayerView', 'StyleManager/model/Layers'],
(view.model.get('props') === null).should.equal(true);
});
-
});
}
};
diff --git a/test/specs/utils/Sorter.js b/test/specs/utils/Sorter.js
new file mode 100644
index 000000000..0151fa906
--- /dev/null
+++ b/test/specs/utils/Sorter.js
@@ -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);
+ });
+
+ });
+
+ });
+
+ }
+ };
+
+});
\ No newline at end of file
diff --git a/test/specs/utils/main.js b/test/specs/utils/main.js
new file mode 100644
index 000000000..175dc100a
--- /dev/null
+++ b/test/specs/utils/main.js
@@ -0,0 +1,17 @@
+var modulePath = './../../../test/specs/utils';
+
+define([
+ 'Utils',
+ modulePath + '/Sorter'
+ ],
+ function(
+ Utils,
+ Sorter
+ ) {
+
+ describe('Utils', function() {
+
+ Sorter.run();
+
+ });
+});
\ No newline at end of file