tabulator-tables
Version:
Interactive table generation JavaScript library
634 lines (492 loc) • 18.2 kB
JavaScript
var MoveRows = function(table){
this.table = table; //hold Tabulator object
this.placeholderElement = this.createPlaceholderElement();
this.hoverElement = false; //floating row header element
this.checkTimeout = false; //click check timeout holder
this.checkPeriod = 150; //period to wait on mousedown to consider this a move and not a click
this.moving = false; //currently moving row
this.toRow = false; //destination row
this.toRowAfter = false; //position of moving row relative to the desitnation row
this.hasHandle = false; //row has handle instead of fully movable row
this.startY = 0; //starting Y position within header element
this.startX = 0; //starting X position within header element
this.moveHover = this.moveHover.bind(this);
this.endMove = this.endMove.bind(this);
this.tableRowDropEvent = false;
this.touchMove = false;
this.connection = false;
this.connectionSelectorsTables = false;
this.connectionSelectorsElements = false;
this.connectionElements = [];
this.connections = [];
this.connectedTable = false;
this.connectedRow = false;
};
MoveRows.prototype.createPlaceholderElement = function(){
var el = document.createElement("div");
el.classList.add("tabulator-row");
el.classList.add("tabulator-row-placeholder");
return el;
};
MoveRows.prototype.initialize = function(handle){
this.connectionSelectorsTables = this.table.options.movableRowsConnectedTables;
this.connectionSelectorsElements = this.table.options.movableRowsConnectedElements;
this.connection = this.connectionSelectorsTables || this.connectionSelectorsElements;
};
MoveRows.prototype.setHandle = function(handle){
this.hasHandle = handle;
};
MoveRows.prototype.initializeGroupHeader = function(group){
var self = this,
config = {},
rowEl;
//inter table drag drop
config.mouseup = function(e){
self.tableRowDrop(e, row);
}.bind(self);
//same table drag drop
config.mousemove = function(e){
if(((e.pageY - Tabulator.prototype.helpers.elOffset(group.element).top) + self.table.rowManager.element.scrollTop) > (group.getHeight() / 2)){
if(self.toRow !== group || !self.toRowAfter){
var rowEl = group.getElement();
rowEl.parentNode.insertBefore(self.placeholderElement, rowEl.nextSibling);
self.moveRow(group, true);
}
}else{
if(self.toRow !== group || self.toRowAfter){
var rowEl = group.getElement();
if(rowEl.previousSibling){
rowEl.parentNode.insertBefore(self.placeholderElement, rowEl);
self.moveRow(group, false);
}
}
}
}.bind(self);
group.modules.moveRow = config;
};
MoveRows.prototype.initializeRow = function(row){
var self = this,
config = {},
rowEl;
//inter table drag drop
config.mouseup = function(e){
self.tableRowDrop(e, row);
}.bind(self);
//same table drag drop
config.mousemove = function(e){
var rowEl = row.getElement();
if(((e.pageY - Tabulator.prototype.helpers.elOffset(rowEl).top) + self.table.rowManager.element.scrollTop) > (row.getHeight() / 2)){
if(self.toRow !== row || !self.toRowAfter){
rowEl.parentNode.insertBefore(self.placeholderElement, rowEl.nextSibling);
self.moveRow(row, true);
}
}else{
if(self.toRow !== row || self.toRowAfter){
rowEl.parentNode.insertBefore(self.placeholderElement, rowEl);
self.moveRow(row, false);
}
}
}.bind(self);
if(!this.hasHandle){
rowEl = row.getElement();
rowEl.addEventListener("mousedown", function(e){
if(e.which === 1){
self.checkTimeout = setTimeout(function(){
self.startMove(e, row);
}, self.checkPeriod);
}
});
rowEl.addEventListener("mouseup", function(e){
if(e.which === 1){
if(self.checkTimeout){
clearTimeout(self.checkTimeout);
}
}
});
this.bindTouchEvents(row, row.getElement());
}
row.modules.moveRow = config;
};
MoveRows.prototype.initializeCell = function(cell){
var self = this,
cellEl = cell.getElement(true);
cellEl.addEventListener("mousedown", function(e){
if(e.which === 1){
self.checkTimeout = setTimeout(function(){
self.startMove(e, cell.row);
}, self.checkPeriod);
}
});
cellEl.addEventListener("mouseup", function(e){
if(e.which === 1){
if(self.checkTimeout){
clearTimeout(self.checkTimeout);
}
}
});
this.bindTouchEvents(cell.row, cellEl);
};
MoveRows.prototype.bindTouchEvents = function(row, element){
var self = this,
startYMove = false, //shifting center position of the cell
dir = false,
currentRow, nextRow, prevRow, nextRowHeight, prevRowHeight, nextRowHeightLast, prevRowHeightLast;
element.addEventListener("touchstart", function(e){
self.checkTimeout = setTimeout(function(){
self.touchMove = true;
currentRow = row;
nextRow = row.nextRow();
nextRowHeight = nextRow ? nextRow.getHeight()/2 : 0;
prevRow = row.prevRow();
prevRowHeight = prevRow ? prevRow.getHeight()/2 : 0;
nextRowHeightLast = 0;
prevRowHeightLast = 0;
startYMove = false;
self.startMove(e, row);
}, self.checkPeriod);
}, {passive: true});
this.moving, this.toRow, this.toRowAfter
element.addEventListener("touchmove", function(e){
var halfCol, diff, moveToRow;
if(self.moving){
e.preventDefault();
self.moveHover(e);
if(!startYMove){
startYMove = e.touches[0].pageY;
}
diff = e.touches[0].pageY - startYMove;
if(diff > 0){
if(nextRow && diff - nextRowHeightLast > nextRowHeight){
moveToRow = nextRow;
if(moveToRow !== row){
startYMove = e.touches[0].pageY
moveToRow.getElement().parentNode.insertBefore(self.placeholderElement, moveToRow.getElement().nextSibling);
self.moveRow(moveToRow, true);
}
}
}else{
if(prevRow && -diff - prevRowHeightLast > prevRowHeight){
moveToRow = prevRow;
if(moveToRow !== row){
startYMove = e.touches[0].pageY;
moveToRow.getElement().parentNode.insertBefore(self.placeholderElement, moveToRow.getElement());
self.moveRow(moveToRow, false);
}
}
}
if(moveToRow){
currentRow = moveToRow;
nextRow = moveToRow.nextRow();
nextRowHeightLast = nextRowHeight;
nextRowHeight = nextRow ? nextRow.getHeight() / 2 : 0;
prevRow = moveToRow.prevRow();
prevRowHeightLast = prevRowHeight;
prevRowHeight = prevRow ? prevRow.getHeight() / 2 : 0;
}
}
});
element.addEventListener("touchend", function(e){
if(self.checkTimeout){
clearTimeout(self.checkTimeout);
}
if(self.moving){
self.endMove(e);
self.touchMove = false;
}
});
};
MoveRows.prototype._bindMouseMove = function(){
var self = this;
self.table.rowManager.getDisplayRows().forEach(function(row){
if((row.type === "row" || row.type === "group") && row.modules.moveRow.mousemove){
row.getElement().addEventListener("mousemove", row.modules.moveRow.mousemove);
}
});
};
MoveRows.prototype._unbindMouseMove = function(){
var self = this;
self.table.rowManager.getDisplayRows().forEach(function(row){
if((row.type === "row" || row.type === "group") && row.modules.moveRow.mousemove){
row.getElement().removeEventListener("mousemove", row.modules.moveRow.mousemove);
}
});
};
MoveRows.prototype.startMove = function(e, row){
var element = row.getElement();
this.setStartPosition(e, row);
this.moving = row;
this.table.element.classList.add("tabulator-block-select");
//create placeholder
this.placeholderElement.style.width = row.getWidth() + "px";
this.placeholderElement.style.height = row.getHeight() + "px";
if(!this.connection){
element.parentNode.insertBefore(this.placeholderElement, element);
element.parentNode.removeChild(element);
}else{
this.table.element.classList.add("tabulator-movingrow-sending");
this.connectToTables(row);
}
//create hover element
this.hoverElement = element.cloneNode(true);
this.hoverElement.classList.add("tabulator-moving");
if(this.connection){
document.body.appendChild(this.hoverElement);
this.hoverElement.style.left = "0";
this.hoverElement.style.top = "0";
this.hoverElement.style.width = this.table.element.clientWidth + "px";
this.hoverElement.style.whiteSpace = "nowrap";
this.hoverElement.style.overflow = "hidden";
this.hoverElement.style.pointerEvents = "none";
}else{
this.table.rowManager.getTableElement().appendChild(this.hoverElement);
this.hoverElement.style.left = "0";
this.hoverElement.style.top = "0";
this._bindMouseMove();
}
document.body.addEventListener("mousemove", this.moveHover);
document.body.addEventListener("mouseup", this.endMove);
this.moveHover(e);
};
MoveRows.prototype.setStartPosition = function(e, row){
var pageX = this.touchMove ? e.touches[0].pageX : e.pageX,
pageY = this.touchMove ? e.touches[0].pageY : e.pageY,
element, position;
element = row.getElement();
if(this.connection){
position = element.getBoundingClientRect();
this.startX = position.left - pageX + window.pageXOffset;
this.startY = position.top - pageY + window.pageYOffset;
}else{
this.startY = (pageY - element.getBoundingClientRect().top);
}
};
MoveRows.prototype.endMove = function(e){
if(!e || e.which === 1 || this.touchMove){
this._unbindMouseMove();
if(!this.connection){
this.placeholderElement.parentNode.insertBefore(this.moving.getElement(), this.placeholderElement.nextSibling);
this.placeholderElement.parentNode.removeChild(this.placeholderElement);
}
this.hoverElement.parentNode.removeChild(this.hoverElement);
this.table.element.classList.remove("tabulator-block-select");
if(this.toRow){
this.table.rowManager.moveRow(this.moving, this.toRow, this.toRowAfter);
}
this.moving = false;
this.toRow = false;
this.toRowAfter = false;
document.body.removeEventListener("mousemove", this.moveHover);
document.body.removeEventListener("mouseup", this.endMove);
if(this.connection){
this.table.element.classList.remove("tabulator-movingrow-sending");
this.disconnectFromTables();
}
}
};
MoveRows.prototype.moveRow = function(row, after){
this.toRow = row;
this.toRowAfter = after;
};
MoveRows.prototype.moveHover = function(e){
if(this.connection){
this.moveHoverConnections.call(this, e);
}else{
this.moveHoverTable.call(this, e);
}
};
MoveRows.prototype.moveHoverTable = function(e){
var rowHolder = this.table.rowManager.getElement(),
scrollTop = rowHolder.scrollTop,
yPos = ((this.touchMove ? e.touches[0].pageY : e.pageY) - rowHolder.getBoundingClientRect().top) + scrollTop,
scrollPos;
this.hoverElement.style.top = (yPos - this.startY) + "px";
};
MoveRows.prototype.moveHoverConnections = function(e){
this.hoverElement.style.left = (this.startX + (this.touchMove ? e.touches[0].pageX : e.pageX)) + "px";
this.hoverElement.style.top = (this.startY + (this.touchMove ? e.touches[0].pageY : e.pageY)) + "px";
};
MoveRows.prototype.elementRowDrop = function(e, element, row){
if(this.table.options.movableRowsElementDrop){
this.table.options.movableRowsElementDrop(e, element, row ? row.getComponent() : false);
}
};
//establish connection with other tables
MoveRows.prototype.connectToTables = function(row){
var connectionTables;
if(this.connectionSelectorsTables){
connectionTables = this.table.modules.comms.getConnections(this.connectionSelectorsTables);
this.table.options.movableRowsSendingStart.call(this.table, connectionTables);
this.table.modules.comms.send(this.connectionSelectorsTables, "moveRow", "connect", {
row:row,
});
}
if(this.connectionSelectorsElements){
this.connectionElements = [];
if(!Array.isArray(this.connectionSelectorsElements)){
this.connectionSelectorsElements = [this.connectionSelectorsElements];
}
this.connectionSelectorsElements.forEach((query) => {
if(typeof query === "string"){
this.connectionElements = this.connectionElements.concat(Array.prototype.slice.call(document.querySelectorAll(query)));
}else{
this.connectionElements.push(query);
}
});
this.connectionElements.forEach((element) => {
var dropEvent = (e) => {
this.elementRowDrop(e, element, this.moving);
};
element.addEventListener("mouseup", dropEvent);
element.tabulatorElementDropEvent = dropEvent;
element.classList.add("tabulator-movingrow-receiving");
});
}
};
//disconnect from other tables
MoveRows.prototype.disconnectFromTables = function(){
var connectionTables;
if(this.connectionSelectorsTables){
connectionTables = this.table.modules.comms.getConnections(this.connectionSelectorsTables);
this.table.options.movableRowsSendingStop.call(this.table, connectionTables);
this.table.modules.comms.send(this.connectionSelectorsTables, "moveRow", "disconnect");
}
this.connectionElements.forEach((element) => {
element.classList.remove("tabulator-movingrow-receiving");
element.removeEventListener("mouseup", element.tabulatorElementDropEvent);
delete element.tabulatorElementDropEvent;
});
};
//accept incomming connection
MoveRows.prototype.connect = function(table, row){
var self = this;
if(!this.connectedTable){
this.connectedTable = table;
this.connectedRow = row;
this.table.element.classList.add("tabulator-movingrow-receiving");
self.table.rowManager.getDisplayRows().forEach(function(row){
if(row.type === "row" && row.modules.moveRow && row.modules.moveRow.mouseup){
row.getElement().addEventListener("mouseup", row.modules.moveRow.mouseup);
}
});
self.tableRowDropEvent = self.tableRowDrop.bind(self);
self.table.element.addEventListener("mouseup", self.tableRowDropEvent);
this.table.options.movableRowsReceivingStart.call(this.table, row, table);
return true;
}else{
console.warn("Move Row Error - Table cannot accept connection, already connected to table:", this.connectedTable);
return false;
}
};
//close incomming connection
MoveRows.prototype.disconnect = function(table){
var self = this;
if(table === this.connectedTable){
this.connectedTable = false;
this.connectedRow = false;
this.table.element.classList.remove("tabulator-movingrow-receiving");
self.table.rowManager.getDisplayRows().forEach(function(row){
if(row.type === "row" && row.modules.moveRow && row.modules.moveRow.mouseup){
row.getElement().removeEventListener("mouseup", row.modules.moveRow.mouseup);
}
});
self.table.element.removeEventListener("mouseup", self.tableRowDropEvent);
this.table.options.movableRowsReceivingStop.call(this.table, table);
}else{
console.warn("Move Row Error - trying to disconnect from non connected table")
}
};
MoveRows.prototype.dropComplete = function(table, row, success){
var sender = false;
if(success){
switch(typeof this.table.options.movableRowsSender){
case "string":
sender = this.senders[this.table.options.movableRowsSender];
break;
case "function":
sender = this.table.options.movableRowsSender;
break;
}
if(sender){
sender.call(this, this.moving.getComponent(), row ? row.getComponent() : undefined, table)
}else{
if(this.table.options.movableRowsSender){
console.warn("Mover Row Error - no matching sender found:", this.table.options.movableRowsSender);
}
}
this.table.options.movableRowsSent.call(this.table, this.moving.getComponent(), row ? row.getComponent() : undefined, table);
}else{
this.table.options.movableRowsSentFailed.call(this.table, this.moving.getComponent(), row ? row.getComponent() : undefined, table);
}
this.endMove();
};
MoveRows.prototype.tableRowDrop = function(e, row){
var receiver = false,
success = false;
e.stopImmediatePropagation();
switch(typeof this.table.options.movableRowsReceiver){
case "string":
receiver = this.receivers[this.table.options.movableRowsReceiver];
break;
case "function":
receiver = this.table.options.movableRowsReceiver;
break;
}
if(receiver){
success = receiver.call(this, this.connectedRow.getComponent(), row ? row.getComponent() : undefined, this.connectedTable)
}else{
console.warn("Mover Row Error - no matching receiver found:", this.table.options.movableRowsReceiver)
}
if(success){
this.table.options.movableRowsReceived.call(this.table, this.connectedRow.getComponent(), row ? row.getComponent() : undefined, this.connectedTable);
}else{
this.table.options.movableRowsReceivedFailed.call(this.table, this.connectedRow.getComponent(), row ? row.getComponent() : undefined, this.connectedTable);
}
this.table.modules.comms.send(this.connectedTable, "moveRow", "dropcomplete", {
row:row,
success:success,
});
};
MoveRows.prototype.receivers = {
insert:function(fromRow, toRow, fromTable){
this.table.addRow(fromRow.getData(), undefined, toRow);
return true;
},
add:function(fromRow, toRow, fromTable){
this.table.addRow(fromRow.getData());
return true;
},
update:function(fromRow, toRow, fromTable){
if(toRow){
toRow.update(fromRow.getData());
return true;
}
return false;
},
replace:function(fromRow, toRow, fromTable){
if(toRow){
this.table.addRow(fromRow.getData(), undefined, toRow);
toRow.delete();
return true;
}
return false;
},
};
MoveRows.prototype.senders = {
delete:function(fromRow, toRow, toTable){
fromRow.delete();
}
};
MoveRows.prototype.commsReceived = function(table, action, data){
switch(action){
case "connect":
return this.connect(table, data.row);
break;
case "disconnect":
return this.disconnect(table);
break;
case "dropcomplete":
return this.dropComplete(table, data.row, data.success);
break;
}
};
Tabulator.prototype.registerModule("moveRow", MoveRows);