assets/src/tables.js
/*global $, define, rangy, _, JST*/
//= require templates/tables-ribbon
//= require templates/tables-selection
//= require templates/tables-insert
//= require templates/tables-new
define(['websync'], function(WS) {
'use strict';
/**
* WebSync Tables plugin.
*
* Plugins should use a jQuery namespace for ease of use.
* Bind Example: $(document).bind("click.Tables", clickHandler);
* Unbind Example: $("*").unbind(".Tables");
*
* @exports tables
* @module tables
*/
var exports = {
/**
* Keeps track of the size of the cell to poll for changes.
*/
lastSize: '',
/**
* An observer to update position and headers if the cell size changes.
*/
observer: function() {
var boundingData = JSON.stringify(exports.selectedElem.getBoundingClientRect());
if (boundingData !== exports.lastSize) {
setTimeout(function() {
exports.cursorUpdate();
}, 1);
setTimeout(function() {
exports.headerUpdate();
}, 2);
exports.lastSize = boundingData;
}
},
/**
* Is the selection active?
*/
selectionActive: false,
/**
* Disable the axis position. Only used for the Spreadsheet format.
*/
disableAxisPositioning: false,
/**
* Handler for keypresses
*
* @param e {Event} the keypress event
*/
keypressHandler: function(e) {
if (!exports.selectedElem) {
return;
}
if (exports.selected) {
var editting = false;
if (exports.selectedElem.contentEditable) {
editting = exports.selectedElem.contentEditable === 'true';
}
if (e.keyCode === 13 && !e.shiftKey) {
exports.cursorMove(0, 1);
} else if (e.keyCode === 9) {
// Tab key
exports.selectedEditable(false);
exports.cursorMove(1 - 2 * e.shiftKey, 0);
e.preventDefault();
} else if (e.keyCode === 27) {
// Escape
exports.selectedEditable(false);
} else if (e.keyCode === 37 && !editting) {
// Left arrow
exports.cursorMove(-1, 0);
e.preventDefault();
} else if (e.keyCode === 39 && !editting) {
// Right arrow
exports.cursorMove(1, 0);
e.preventDefault();
} else if (e.keyCode === 38 && !editting) {
// Up arrow
exports.cursorMove(0, -1);
e.preventDefault();
} else if (e.keyCode === 40 && !editting) {
// Down a1rrow
exports.cursorMove(0, 1);
e.preventDefault();
} else if (e.keyCode === 46 && !editting) { // Delete key
exports.emptySelection();
e.preventDefault();
} else {
if ((!exports.selectedElem.contentEditable || exports.selectedElem.contentEditable === 'inherit') && _.indexOf([16, 17, 18, 91, 92], e.keyCode) === -1 && !(e.keyCode === 67 && e.ctrlKey)) {
exports.selectedEditable(true);
$(exports.selectedElem).focus();
//WebSync.setCaretPosition(exports.selectedElem,0);
}
setTimeout(exports.cursorUpdate, 1);
}
} else {
if (!exports.selectedElem.contentEditable || exports.selectedElem.contentEditable === 'false') {
exports.selectedEditable(true);
$(exports.selectedElem).focus();
//WebSync.setCaretPosition(exports.selectedElem,0);
}
}
},
/**
* Checks the table for =JS() equations in cells. If found it will run the
* JavaScript and return the result.
*/
checkForJS: function() {
var elems = $(".content table td:contains('=')");
_.each(elems, function(cell) {
var data = $(cell).data().content;
if (!data) {
var text = $(cell).text();
if (text[0] === '=') {
$(cell).data('content', text);
exports.updateJS(cell);
}
} else if (data[0] === '=') {
exports.updateJS(cell);
}
});
},
/**
* Disables the plugin. This has to be set for possible plugin unloading.
*/
disable: function() {
$('.Table').remove();
WS.updateRibbon();
$('*').unbind('.Tables');
$('*').off('.Tables');
$('*').undelegate('.Tables');
},
/**
* Render the titles for the table header.
*/
redrawTitles: function() {
$('.Table.axis').remove();
if (exports.selectedElem) {
exports.cursorSelect(exports.selectedElem, true);
}
exports.headerUpdate();
},
/**
* Selects a cell to be the target of the cursor.
*
* @param td {Element} the table cell.
* @param noanim {Boolean} disables the animations.
*/
cursorSelect: function(td, noanim) {
$("#ribbon_buttons a:contains('Table')").parent().fadeIn(200);
// Cleanup last elem.
if (exports.selectedElem) {
exports.selectedEditable(false);
}
document.getSelection().removeAllRanges();
exports.selected = true;
exports.selectedElem = td;
$(exports.selectedElem).focus();
exports.selectedEditable(false);
//exports.observer.observe(exports.selectedElem,{characterData:true});
//exports.selectedElem.addEventListener('DOMSubtreeModified', exports.observer);
$(exports.selectedElem).on('keypress.Tables', _.throttle(exports.observer, 100)).on('resize.Tables', _.throttle(exports.observer, 100));
if ($('.Table.axis').length === 0) {
var size = exports.tableSize();
var nodes = '<table class="Table axis" id="x"><thead><tr>';
var i, elem, bounding;
for (i = 0; i < size[0]; i++) {
elem = exports.posToElem(i, 0);
bounding = elem.getBoundingClientRect();
nodes += '<th style="width: ' + (bounding.width - 1).toFixed(0) + 'px">' + exports.columnLabel(i) + '</th>';
}
var tableCount = $('.content_container table').index(exports.primaryTable()) + 1;
var name = 'Table ' + tableCount;
nodes += '</tr></thead></table><table class="Table axis" id="y"><thead><tr><th>' + name + '</th></tr>';
for (i = 0; i < size[1]; i++) {
elem = exports.posToElem(0, i);
nodes += '<tr><th style="height: ' + ($(elem).height() + 1) + 'px">' + (i + 1) + '</th></tr>';
}
nodes += '</thead></table>';
$('.content').append($(nodes));
if (noanim) {
$('.Table.axis').show();
} else {
$('.Table.axis').fadeIn(200);
}
}
exports.cursorUpdate();
exports.enterLeaveBinds();
},
/**
* Update the header
*/
headerUpdate: function() {
var size = exports.tableSize();
var xNodes = $('.axis#x th');
var i, elem, bounding;
for (i = 0; i < size[0]; i++) {
elem = exports.posToElem(i, 0);
bounding = elem.getBoundingClientRect();
xNodes[i].style.width = (bounding.width) + 'px';
}
var yNodes = $('.axis#y th');
for (i = 0; i < size[1]; i++) {
elem = exports.posToElem(0, i);
bounding = elem.getBoundingClientRect();
yNodes[i + 1].style.height = (bounding.height) + 'px';
}
},
/**
* Select a cell using coordinates.
*
* @param dColumn {Number} the cell column
* @param dRow {Number} the cell row
*/
cursorMove: function(dColumn, dRow) {
exports.selectedEditable(false);
var pos = exports.selectedPos();
var column = pos[0];
var row = pos[1];
// TODO: Redo this into a more readable form using jQuery.
if (exports.selectedElem.parentElement.parentElement.children.length > row + dRow && exports.selectedElem.parentElement.parentElement.children[0].children.length > column + dColumn && row + dRow >= 0 && column + dColumn >= 0) {
var newTd = exports.selectedElem.parentElement.parentElement.children[row + dRow].children[column + dColumn];
exports.cursorSelect(newTd);
}
},
/**
* Update the position of the table header axis.
*/
axisPosition: function() {
if (!exports.selectedElem) {
return;
}
var table = $(exports.primaryTable());
var offset = table.offset();
var box = table[0].getBoundingClientRect();
$('.Table.axis#x').offset({
left: offset.left,
top: offset.top - 16
}).width(box.width - 2);
$('.Table.axis#y').offset({
left: offset.left - $('.Table.axis#y').width(),
top: offset.top - 16
});
},
/**
* Update the position of the cursor.
*/
cursorUpdate: function() {
var pos = $(exports.selectedElem).offset();
pos.top += 2;
pos.left += 2;
exports.updateSelectedArea();
var table = $(exports.primaryTable());
var elemBox = exports.selectedElem.getBoundingClientRect();
$('#table_cursor').offset({
left: pos.left,
top: pos.top
}).height(elemBox.height - 4).
width(elemBox.width - 6).
get(0).scrollIntoViewIfNeeded();
if (table.css('position') === 'absolute' || pos) {
if (!exports.disableAxisPositioning) {
exports.axisPosition();
}
var box = table[0].getBoundingClientRect();
$('.Table.axis#x').width(box.width - 2);
}
},
/**
* Gets values in the format of "Name.A1:B6"
*/
getCellData: function(range) {
var bits = range.split('.');
var table; // = exports.primaryTable();
if (bits.length >= 2) {
var name = bits[0];
var search = $(".content_container table[name='" + name + "']");
if (search.length >= 1) {
table = search.get(0);
} else if (name.match(/^Table \d+$/)) {
var index = parseInt(name.split(' ')[1], 10) - 1;
table = $('.content_container table').get(index);
}
}
var parts = _.last(bits).split(':');
var first = exports.coordsFromLabel(parts[0]),
second;
if (parts.length === 2) {
second = exports.coordsFromLabel(parts[1]);
}
var topLeft, bottomRight;
if (second) {
// Get top left cell.
topLeft = [
first[0] < second[0] ? first[0] : second[0],
first[1] < second[1] ? first[1] : second[1]
];
bottomRight = [
first[0] > second[0] ? first[0] : second[0],
first[1] > second[1] ? first[1] : second[1]
];
} else {
topLeft = first;
bottomRight = first;
}
var size = [
bottomRight[0] - topLeft[0] + 1,
bottomRight[1] - topLeft[1] + 1
];
var data = [];
var elemInTable = $(table).find('td, th')[0] || window._tmpElem;
var x, y, elem, val;
for (x = 0; x < size[0]; x++) {
for (y = 0; y < size[1]; y++) {
if (!data[x]) {
data[x] = [];
}
elem = exports.posToElem(topLeft[0] + x, topLeft[1] + y,
elemInTable);
if (window._tmpElem && elem === window._tmpElem) {
throw "Error: Cell can't select it's own content.";
}
val = $(elem).text();
if (parseFloat(val).toString() === val) {
val = parseFloat(val);
}
data[x][y] = val;
}
}
if (data.length === 1 && data[0].length === 1) {
return data[0][0];
}
return data;
},
/**
* Eval the content of a cell.
* TODO: Move into a webworker to sandbox
*
* @param js {String} the cell contents to execute
* @param elem {Element} the cell running the JS
*/
evalJS: function(js, elem) {
// Hack. :(
window._tmpElem = elem;
var c = exports.getCellData;
var out;
try {
out = eval(js);
} catch (e) {
out = '!' + e;
}
delete window._tmpElem;
return out;
},
/**
* Run the nodes javascript and update it's text with the result.
*
* @param node {Element} the element to run
*/
updateJS: function(node) {
var data = $(node).data().content;
if (data[0] === '=') {
$(node).text(exports.evalJS(data.slice(1), node));
}
},
/**
* Execute all equations in a document.
*/
updateAllJS: function() {
var nodes = $('.content table td:data(content)');
_.each(nodes, function(node) {
exports.updateJS(node);
});
},
/**
* Make the currently selected element editable or not.
*
* @param edit {Boolean} whether the selected cell should be editable
*/
selectedEditable: function(edit) {
var style, contentEditable;
if (edit) {
contentEditable = true;
style = 'dashed';
var data = $(exports.selectedElem).data();
if (data.content) {
$(exports.selectedElem).text(data.content);
}
} else {
contentEditable = 'inherit';
style = 'solid';
var text = $(exports.selectedElem).text();
if (text[0] === '=') {
$(exports.selectedElem).data('content', text);
}
exports.updateAllJS();
}
exports.selectedElem.contentEditable = contentEditable;
$('#table_cursor').css({
borderStyle: style,
outlineStyle: style
});
$('#ribbon_buttons a:contains("Table")').click();
},
/**
* Clear the table selection.
*/
clearSelect: function() {
if (exports.selected) {
exports.selected = false;
exports.selectedEditable(false);
$('#table_cursor').offset({
left: -10000
});
//exports.selectedElem.removeEventListener('DOMSubtreeModified', exports.observer);
$(exports.selectedElem).off('.Tables');
delete exports.selectedElem;
$("#ribbon_buttons a:contains('Table')").parent().fadeOut(200);
$('#ribbon_buttons a:contains("Text")').click();
exports.selectedElem = null;
exports.updateSelectedArea();
$('.Table.axis').fadeOut(200).promise().done(function() {
$('.Table.axis#x').remove();
$('.Table.axis#y').remove();
});
exports.enterLeaveBinds();
}
},
/**
* The currently selected table.
*/
table: null,
/**
* Runs enterTable or leaveTable depending.
*/
enterLeaveBinds: function() {
if (exports.selected) {
var primary = exports.primaryTable();
if (primary !== exports.table) {
if (exports.table) {
exports.leaveTable(exports.table);
}
exports.table = primary;
exports.enterTable(exports.table);
}
} else {
if (exports.table) {
exports.leaveTable(exports.table);
exports.table = null;
}
}
},
/**
* Handler for table enter that registers key and resize binds, and
* updates the header and cursor.
*
* @param table {Element} the table being entered
*/
enterTable: function(table) {
$(document).on('keydown.TablesTemp', exports.keypressHandler);
$('.content_container').delegate('resize.Tables', 'table', function() {
exports.cursorUpdate();
exports.headerUpdate();
});
},
/**
* Handler for table leave that unregisters the bindings.
*
* @param table {Element} the table being left
*/
leaveTable: function(table) {
$(table).unbind('.TablesTemp');
$(table).undelegate('.TablesTemp');
$(table).off('.TablesTemp');
$(document).unbind('.TablesTemp');
$(document).undelegate('.TablesTemp');
$(document).off('.TablesTemp');
},
/**
* Update the area selection.
*/
updateSelectedArea: function() {
$('.Table.axis th').removeClass('active');
if (!exports.selected || exports.primaryTable() !== exports.primaryTable(exports.selectionEnd)) {
$('#table_selection').hide();
if (exports.selected) {
$($('.Table.axis#x').children().children().children()[exports.selectedPos()[0]]).addClass('active');
$($('.Table.axis#y').children().children()[exports.selectedPos()[1]]).children().addClass('active');
}
} else {
var top, bottom, left, right;
var end = exports.selectionEnd || exports.selectedElem;
if (exports.selectionEnd && exports.selectionEnd !== exports.selectedElem) {
var startBox = exports.selectedElem.getBoundingClientRect();
var endBox = end.getBoundingClientRect();
top = startBox.top < endBox.top ? startBox.top : endBox.top;
bottom = startBox.bottom > endBox.bottom ? startBox.bottom : endBox.bottom;
left = startBox.left < endBox.left ? startBox.left : endBox.left;
right = startBox.right > endBox.right ? startBox.right : endBox.right;
$('#table_selection').show().offset({
left: left,
top: top
}).height(bottom - top - 2).width(right - left - 3);
} else {
$('#table_selection').hide();
}
// Set hidden selection area contents to mini-table.
var selectionHtml = '<table><tbody>';
var tposStart = exports.selectedPos();
var tposEnd = exports.selectedPos(end);
top = tposStart[1] < tposEnd[1] ? tposStart[1] : tposEnd[1];
bottom = tposStart[1] > tposEnd[1] ? tposStart[1] : tposEnd[1];
left = tposStart[0] < tposEnd[0] ? tposStart[0] : tposEnd[0];
right = tposStart[0] > tposEnd[0] ? tposStart[0] : tposEnd[0];
$('.Table.axis#x').children().children().children().slice(left, right + 1).addClass('active');
$('.Table.axis#y').children().children().slice(top + 1, bottom + 2).children().addClass('active');
var x, y;
for (y = top; y <= bottom; y++) {
selectionHtml += '<tr>';
for (x = left; x <= right; x++) {
selectionHtml += '<td>' + exports.posToElem(x, y).innerHTML + '</td>';
}
selectionHtml += '</tr>';
}
selectionHtml += '</tbody></table>';
$('#table_clip').html(selectionHtml);
if (exports.selectedElem.contentEditable !== 'true') {
// Hidden selection area for copying.
var range = rangy.createRange();
range.selectNodeContents($('#table_clip').get(0));
var sel = rangy.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
}
},
/**
* Set everything in the current selection to be ''.
*/
emptySelection: function() {
var end = exports.selectionEnd || exports.selectedElem;
var tposStart = exports.selectedPos();
var tposEnd = exports.selectedPos(end);
var top = tposStart[1] < tposEnd[1] ? tposStart[1] : tposEnd[1];
var bottom = tposStart[1] > tposEnd[1] ? tposStart[1] : tposEnd[1];
var left = tposStart[0] < tposEnd[0] ? tposStart[0] : tposEnd[0];
var right = tposStart[0] > tposEnd[0] ? tposStart[0] : tposEnd[0];
var x, y, node;
for (y = top; y <= bottom; y++) {
for (x = left; x <= right; x++) {
node = exports.selectedElem.parentElement.parentElement.children[y].children[x];
node.innerHTML = '';
$(node).data('content', null);
}
}
},
/**
* Move cursor to the end of the content editable element (usually a cell or td).
*
* @param contentEditableElement {Element} the element to move cursor to the end of.
*/
setEndOfContenteditable: function(contentEditableElement) {
var range, selection;
if (document.createRange) //Firefox, Chrome, Opera, Safari, IE 9+
{
range = document.createRange(); //Create a range (a range is a like the selection but invisible)
range.selectNodeContents(contentEditableElement); //Select the entire contents of the element with the range
range.collapse(false); //collapse the range to the end point. false means collapse to end rather than the start
selection = window.getSelection(); //get the selection object (allows you to change selection)
selection.removeAllRanges(); //remove any selections already made
selection.addRange(range); //make the range you have just created the visible selection
} else if (document.selection) { //IE 8 and lower
range = document.body.createTextRange(); //Create a range (a range is a like the selection but invisible)
range.moveToElementText(contentEditableElement); //Select the entire contents of the element with the range
range.collapse(false); //collapse the range to the end point. false means collapse to end rather than the start
range.select(); //Select the range (make it the visible selection
}
},
/**
* Return the primary table associated with the element.
*
* @param elem {Element} the child element
* @return {Element}
*/
primaryTable: function(elem) {
return $(elem || exports.selectedElem).parents('table')[0];
},
/**
* Returns the element corresponding to the column, row coordinates in the
* table with the element.
*
* @param x {Number} the column
* @param y {Number} the row
* @param elem {Element} an element in the table of interest.
* @return {Element}
*/
posToElem: function(x, y, elem) {
if (!elem) {
elem = exports.selectedElem;
}
return $(exports.primaryTable(elem)).find(':not(table) tr').eq(y).
find('> td')[x];
},
/**
* Return the position of the target element
*
* @param targetElement {Element} the element of interest
* @return {Number[]} An array of column, row.
*/
selectedPos: function(targetElem) {
var child = (targetElem || exports.selectedElem);
var column = 0;
while ((child = child.previousElementSibling) !== null) {
if (child.nodeName === 'TD' || child.nodeName === 'TH') {
column++;
}
}
child = (targetElem || exports.selectedElem).parentElement;
var row = 0;
while ((child = child.previousElementSibling) !== null) {
if (child.nodeName === 'TR') {
row++;
}
}
return [column, row];
},
/**
* Returns the table size in [column, row] counts.
*
* @return {Number[]} [column, row]
*/
tableSize: function() {
var rows = $(exports.primaryTable()).find(':not(table) tr');
return [
rows.first().find('> td').length,
rows.length
];
},
/**
* Returns the column label from the column number. Ex: '0' -> 'A'
* http://stackoverflow.com/questions/8603480/how-to-create-a-function-that-converts-a-number-to-a-bijective-hexavigesimal
*
* @param a {Number} the column number
* @return {String} the formated number
*/
columnLabel: function(a) {
var alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
// First figure out how many digits there are.
a += 1; // This line is funky
var c = 0;
var x = 1;
while (a >= x) {
c++;
a -= x;
x *= 26;
}
// Now you can do normal base conversion.
var s = '';
for (var i = 0; i < c; i++) {
s = alpha.charAt(a % 26) + s;
a = Math.floor(a / 26);
}
return s;
},
/**
* Convert from a "A1" formated label to a coordinate.
*
* @param label {String} the label
* @return {Array<Number>} an array with column, row.
*/
coordsFromLabel: function(label) {
var alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
// Row (number), Column (Base 26)
var r = '',
c = '';
_.each(label, function(sym) {
if (alpha.indexOf(sym) !== -1) {
c += sym;
} else {
r += sym;
}
});
var column = 0;
_.each(c, function(sym, i) {
column += (alpha.indexOf(sym) + 1) * Math.pow(26, c.length - i - 1);
});
return [column - 1, parseInt(r) - 1];
}
};
$('.ribbon').append(JST['templates/tables-ribbon']({}));
$('.content').append(JST['templates/tables-selection']({}));
$('#Insert').append(JST['templates/tables-insert']({}));
$('#table').bind('click.Tables', function() {
var newTable = $(JST['templates/tables-new']({}));
WS.insertAtCursor(newTable);
});
$('.Table [title="Insert Row Above"]').bind('click.Tables', function() {
if (exports.selected) {
var html = '<tr>';
var size = exports.tableSize();
var i;
for (i = 0; i < size[0]; i++) {
html += '<td></td>';
}
html += '</tr>';
$(html).insertBefore(exports.selectedElem.parentElement);
exports.redrawTitles();
exports.cursorMove(0, -1);
}
});
$('.Table [title="Insert Row Below"]').bind('click.Tables', function() {
if (exports.selected) {
var html = '<tr>';
var size = exports.tableSize();
var i;
for (i = 0; i < size[0]; i++) {
html += '<td></td>';
}
html += '</tr>';
$(html).insertAfter(exports.selectedElem.parentElement);
exports.redrawTitles();
exports.cursorMove(0, 1);
}
});
$('.Table [title="Delete Row"]').bind('click.Tables', function() {
if (exports.selected) {
var size = exports.tableSize();
if (size[1] > 1) {
var pos = exports.selectedPos();
var nY = pos[1] + 1;
if (nY >= size[1]) {
nY -= 2;
}
var nSelected = exports.posToElem(pos[0], nY);
$(exports.selectedElem).closest('tr').remove();
exports.clearSelect();
exports.cursorSelect(nSelected);
exports.redrawTitles();
}
}
});
$('.Table [title="Insert Column Left"]').bind('click.Tables', function(e) {
if (exports.selected) {
var size = exports.tableSize();
var pos = exports.selectedPos();
for (var i = 0; i < size[1]; i++) {
$('<td></td>').insertBefore(exports.selectedElem.parentElement.parentElement.children[i].children[pos[0]]);
}
exports.redrawTitles();
exports.cursorMove(-1, 0);
}
});
$('.Table [title="Insert Column Right"]').bind('click.Tables', function(e) {
if (exports.selected) {
var size = exports.tableSize();
var pos = exports.selectedPos();
for (var i = 0; i < size[1]; i++) {
$('<td></td>').insertAfter(exports.selectedElem.parentElement.parentElement.children[i].children[pos[0]]);
}
exports.redrawTitles();
exports.cursorMove(1, 0);
}
});
$('.Table [title="Delete Column"]').bind('click.Tables', function(e) {
if (exports.selected) {
var size = exports.tableSize();
var parentElem = $(exports.selectedElem).parent().parent();
if (size[0] > 1) {
var pos = exports.selectedPos();
var nX = pos[0] + 1;
if (nX >= size[0]) {
nX -= 2;
}
var nSelected = exports.posToElem(nX, pos[1]);
for (var i = 0; i < size[1]; i++) {
$(parentElem).children('tr').eq(i).children('td').eq(pos[0]).remove();
}
exports.clearSelect();
exports.cursorSelect(nSelected);
exports.redrawTitles();
}
}
});
$('.Table [title="Delete Table"]').bind('click.Tables', function(e) {
if (exports.selected) {
$(exports.selectedElem).parents('table').first().remove();
exports.clearSelect();
}
});
$('.content').delegate('table', 'click.Tables', function(e) {
if (!exports.selectedElem || exports.selectedElem.contentEditable !== 'true') {
$('a:contains("Table")').click();
}
e.stopPropagation();
});
$('.content').delegate('td', 'mouseenter.Tables', function(e) {
if (exports.selectionActive && exports.primaryTable() === exports.primaryTable(this)) {
exports.selectionEnd = this;
exports.updateSelectedArea();
}
});
$('.content').delegate('td', 'mouseleave.Tables', function(e) {});
$('.content').delegate('.Table.axis#x th', 'mousemove.Tables', function(e) {
var position = $(this).offset();
$('.Table.axis th.resize').removeClass('resize');
if (Math.abs(e.pageX - position.left) < 5 || Math.abs(e.pageX - (position.left + $(this).width())) < 5) {
$(this).addClass('resize');
}
});
$('.content').delegate('.Table.axis#y th', 'mousemove.Tables', function(e) {
var position = $(this).offset();
$('.Table.axis th.resize').removeClass('resize');
if (Math.abs(e.pageY - position.top) < 5 || Math.abs(e.pageY - (position.top + $(this).height())) < 5) {
$(this).addClass('resize');
}
});
$('.content').delegate('td', 'mousedown.Tables', function(e) {
if (this !== exports.selectedElem) {
exports.cursorSelect(this);
exports.selectedElem.contentEditable = true;
exports.setEndOfContenteditable(exports.selectedElem);
exports.selectedElem.contentEditable = 'inherit';
}
exports.selectionActive = true;
exports.selectionEnd = null;
exports.updateSelectedArea();
});
$('.content').bind('mousemove.Tables', function(e) {
if (exports.selectionActive && exports.selectedElem && exports.selectedElem.contentEditable !== 'true') {
e.preventDefault();
}
});
$('.content').delegate('td', 'mouseup.Tables', function(e) {
exports.selectionActive = false;
});
$('.content').bind('click.Tables', function(e) {
exports.clearSelect();
});
$(document).bind('clear_select.Tables', function(e) {
exports.clearSelect();
});
$('.content').delegate('td', 'contextmenu.Tables', function(e) {
if (this !== exports.selectedElem) {
exports.cursorSelect(this);
}
if (exports.selectedElem.contentEditable !== 'true') {
//e.preventDefault();
//$(this).contextmenu();
}
});
$('.content').delegate('td', 'dblclick.Tables', function(e) {
if (exports.selectedElem.contentEditable !== 'true') {
exports.selectedEditable(true);
}
});
$('.content').delegate('.Table.axis#x th.resize', 'mousedown.Tables', function(e) {
exports.drag = true;
if (Math.abs(e.pageX - $(this).offset().left) < 5) {
exports.active = $(this).prev()[0];
} else {
exports.active = this;
}
exports.origX = e.pageX;
exports.origWidth = $(exports.active).width();
});
$('.content').delegate('.Table.axis#y th.resize', 'mousedown.Tables', function(e) {
exports.drag = true;
if (Math.abs(e.pageY - $(this).offset().top) < 5) {
exports.active = $(this).prev()[0];
} else {
exports.active = this;
}
exports.origY = e.pageY;
exports.origHeight = $(exports.active).height();
});
$(document).bind('mousemove.Tables', function(e) {
if (exports.drag) {
var pos;
if (exports.origX) {
$(exports.active).width(e.pageX - exports.origX + exports.origWidth);
pos = exports.selectedPos(exports.active);
$(exports.posToElem(pos[0], 0)).width(e.pageX - exports.origX + exports.origWidth);
}
if (exports.origY) {
$(exports.active).height(e.pageY - exports.origY + exports.origHeight);
pos = exports.selectedPos(exports.active);
$(exports.posToElem(0, pos[1]).parentElement).height(e.pageY - exports.origY + exports.origHeight);
}
exports.cursorUpdate();
}
});
$(document).bind('mouseup.Tables', function(e) {
if (exports.drag) {
e.preventDefault();
exports.origX = null;
exports.origY = null;
exports.origWidth = null;
exports.origHeight = null;
exports.drag = false;
}
});
$('.content').delegate(".Table.axis#x th:not('resize')", 'click.Tables', function(e) {
var pos = exports.selectedPos(this);
var size = exports.tableSize();
exports.cursorSelect(exports.posToElem(pos[0], 0));
if (!e.shiftKey) {
exports.selectionEnd = exports.posToElem(pos[0], size[1] - 1);
}
exports.updateSelectedArea();
});
$('.content').delegate(".Table.axis#y th:not('resize')", 'click.Tables', function(e) {
var pos = exports.selectedPos(this);
var size = exports.tableSize();
exports.cursorSelect(exports.posToElem(0, pos[1] - 1));
if (!e.shiftKey) {
exports.selectionEnd = exports.posToElem(size[0] - 1, pos[1] - 1);
}
exports.updateSelectedArea();
});
$(document).bind('paste.Tables', function(e) {
if (exports.selected) {
var nodes = $(e.originalEvent.clipboardData.getData('text/html'));
nodes.find('script').remove();
if (nodes.filter('table').length > 0) {
var rows = nodes.filter('table').find('tr');
var pos = exports.selectedPos();
_.each(rows, function(row, i) {
_.each($(row).children(), function(element, j) {
// TODO: Finish up pasting.
var html = $(element).html();
var elem = exports.posToElem(pos[0] + j, pos[1] + i);
$(elem).html(html);
});
});
e.preventDefault();
}
}
});
WS.registerDOMException('TABLE', function(obj) {
var out = {
data: [],
heights: [],
widths: []
};
for (var attr, i = 0, attrs = obj.attributes, l = attrs.length; i < l; i++) {
attr = attrs.item(i);
if (!out.attrs) {
out.attrs = {};
}
out.attrs[attr.nodeName] = attr.nodeValue;
}
var rows = $(obj).find(':not(table) tr');
_.each(rows, function(row, i) {
var height = $(row)[0].style.height;
if (height) {
out.heights[i] = height;
}
var columns = $(row).children('td');
var rowOut = [];
_.each(columns, function(cell, j) {
var width = $(row)[0].style.width;
if (width) {
out.widths[j] = width;
}
// Potential JS code.
var data;
var content = $(cell).data().content;
if (content) {
data = [document.createTextNode(content)];
} else {
data = cell.childNodes;
}
var cellOut = {};
var contentJson = WS.DOMToJSON(data);
if (!_.isEmpty(contentJson)) {
cellOut.content = contentJson;
}
for (var attr, i = 0, attrs = cell.attributes, l = attrs.length; i < l; i++) {
attr = attrs.item(i);
// Ignore attribute if it's set by the table.
if (attr.nodeName !== 'data-content' &&
attr.nodeName !== 'contenteditable') {
if (!cellOut.attrs) {
cellOut.attrs = {};
}
cellOut.attrs[attr.nodeName] = attr.nodeValue;
}
}
rowOut.push(cellOut);
});
out.data.push(rowOut);
});
return out;
}, function(json) {
var html = '<table';
_.each(json.attrs, function(v, k) {
html += ' ' + k + '=\"' + v + '\"';
});
html += '><tbody>';
_.each(json.data, function(row, i) {
html += '<tr>';
_.each(row, function(cell, j) {
html += '<td>';
if (cell.content) {
if (cell.content[0] === '=') {
html += cell.content;
} else {
html += WS.JSONToDOM(cell.content);
}
}
html += '</td>';
});
html += '</tr>';
});
html += '</tbody></table>';
_.defer(exports.checkForJS);
return html;
});
WS.updateRibbon();
$("#ribbon_buttons a:contains('Table')").parent().hide();
$(document).on('zoom', function() {
if (exports.selected) {
exports.cursorUpdate();
exports.headerUpdate();
}
});
// Return exports so other modules can hook into this one.
return exports;
});