Browse Source

Start Utils with sorter

pull/36/head
Artur Arseniev 10 years ago
parent
commit
8875b53d86
  1. 3
      src/config/require-config.js
  2. 3
      src/style_manager/templates/layer.html
  3. 10
      src/style_manager/view/LayerView.js
  4. 382
      src/utils/Sorter.js
  5. 13
      src/utils/main.js
  6. 11
      styles/css/main.css
  7. 11
      styles/scss/main.scss
  8. 3
      test/runner/main.js
  9. 1
      test/specs/style_manager/view/LayerView.js
  10. 163
      test/specs/utils/Sorter.js
  11. 17
      test/specs/utils/main.js

3
src/config/require-config.js

@ -45,6 +45,7 @@ require.config({
{ name: 'CssComposer', location: 'css_composer', }, { name: 'CssComposer', location: 'css_composer', },
{ name: 'Commands', location: 'commands', }, { name: 'Commands', location: 'commands', },
{ name: 'Canvas', location: 'canvas', }, { name: 'Canvas', location: 'canvas', },
{ name: 'Panels', location: 'panels', } { name: 'Panels', location: 'panels', },
{ name: 'Utils', location: 'utils', }
] ]
}); });

3
src/style_manager/templates/layer.html

@ -1,3 +1,6 @@
<div id="<%= pfx %>move">
<i class="fa fa-arrows"></i>
</div>
<div id="<%= pfx %>label"><%= label %></div> <div id="<%= pfx %>label"><%= label %></div>
<div id="<%= pfx %>preview-box"> <div id="<%= pfx %>preview-box">
<div id="<%= pfx %>preview"></div> <div id="<%= pfx %>preview"></div>

10
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:value', this.valueChanged);
this.listenTo(this.model, 'change:props', this.showProps); this.listenTo(this.model, 'change:props', this.showProps);
this.events['click #' + this.pfx + 'close-layer'] = 'remove'; this.events['click #' + this.pfx + 'close-layer'] = 'remove';
//this.events['mousedown > #' + this.pfx + 'move'] = 'initSorter';
if( !this.model.get('preview') ){ if( !this.model.get('preview') ){
this.$el.addClass(this.pfx + 'no-preview'); this.$el.addClass(this.pfx + 'no-preview');
@ -28,6 +29,15 @@ define(['backbone', 'text!./../templates/layer.html'],
this.delegateEvents(); this.delegateEvents();
}, },
/**
* Delegate sorting
* @param {Event} e
* */
initSorter: function(e){
if(this.sorter)
this.sorter.startMove(this);
},
/** /**
* Returns properties * Returns properties
* @return {Collection|null} * @return {Collection|null}

382
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;
},
});
});

13
src/utils/main.js

@ -0,0 +1,13 @@
define(function(require) {
var Utils = function(){
var Sorter = require('./Sorter');
return {
Sorter: Sorter,
};
};
return Utils;
});

11
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 { .wte-sm-sector .wte-sm-btn-upload #wte-sm-label, .wte-clm-tags .wte-sm-btn-upload #wte-sm-label {
padding: 2px 0; } 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 **********/ /********* END Style Manager **********/
/********* Class manager **********/ /********* Class manager **********/
.wte-clm-tags { .wte-clm-tags {

11
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}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 **********/ /********* END Style Manager **********/
/********* Class manager **********/ /********* Class manager **********/

3
test/runner/main.js

@ -17,7 +17,8 @@ require(['../src/config/require-config.js', 'config/config.js'], function() {
'specs/code_manager/main.js', 'specs/code_manager/main.js',
'specs/panels/main.js', 'specs/panels/main.js',
'specs/commands/main.js', 'specs/commands/main.js',
'specs/style_manager/main.js' 'specs/style_manager/main.js',
'specs/utils/main.js'
], function(chai) ], function(chai)
{ {
var should = chai.should(), var should = chai.should(),

1
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); (view.model.get('props') === null).should.equal(true);
}); });
}); });
} }
}; };

163
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);
});
});
});
}
};
});

17
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();
});
});
Loading…
Cancel
Save