;(function () {
'use strict';
/**
* @namespace
*/
db.libs.tableEditor = (function($, Mousetrap){
var name = 'tableEditor';
/**
* Return table data as JSON
* @public
* @memberof! db.libs.tableEditor
* @param {jQuery|string} id The table to be referenced
* @returns {json} Table data formated as json
*/
function getJSON(id){
var $table = $(id);
var data = {};
if( $table.find('thead').length ){
data.head = {
rows: []
};
$table.find('thead tr').each(function(i, r){
var row = {
cells: [],
classes: $(r).attr('class')
};
$(r).find('td').each(function(i, cell){
row.cells.push({
value: $(cell).html(),
classes: $(cell).attr('class')
});
});
data.head.rows.push(row);
});
}
if( $table.find('tbody').length ){
data.body = {
rows: []
};
$table.find('tbody tr').each(function(i, r){
var row = {
cells: [],
classes: $(r).attr('class')
};
$(r).find('td').each(function(i, cell){
row.cells.push({
value: $(cell).html(),
classes: $(cell).attr('class')
});
});
data.body.rows.push(row);
});
}
return JSON.stringify(data);
}
/**
* Toggles classnames for column
* @public
* @memberof db.libs.tableEditor
* @param {jQuery|string} id The table to be referenced
* @param {number} [i] Index for the column to alter, if null, last focused is used, if that's not possible, the last column in the table is used
* @param {string} addClassnames Classes to toggle
* @param {string} removeClassnames Classes to remove
* @return {jQuery} the table
*/
function toggleColumnClass(id, i, toggleClassnames, removeClassnames){
var $table = $(id);
if( !$.isNumeric(i) ){
if($table.data('lastFocus')){
i = $($table.data('lastFocus')).index();
} else {
i = $table.find('tbody tr td').length;
}
}
$table.find('tr').each(function(r, row){
var $cell = $(row).find('td:nth-child(' + (i + 1) + ')');
$cell.removeClass(removeClassnames);
$cell.toggleClass(toggleClassnames);
});
return $table;
}
/**
* Toggle classnames for row
* @public
* @memberof db.libs.tableEditor
* @param {jQuery|string} id The table to be referenced
* @param {number} [i] Index for the row to alter, if null, last focused is used, if that's not possible, the last row in the table is used
* @param {string} addClassnames Classes to toggle
* @param {string} removeClassnames Classes to remove
* @return {jQuery} the table
*/
function toggleRowClass(id, i, addClassnames, removeClassnames){
var $table = $(id);
var $row;
if( !$.isNumeric(i) ){
if($table.data('lastFocus')){
$row = $($table.data('lastFocus')).parent();
} else {
$row = $table.find('tbody tr').last();
}
} else {
$row = $table.find('tbody tr:nth-child(' + i + ')');
}
$row.removeClass(removeClassnames);
$row.toggleClass(addClassnames);
return $table;
}
/**
* Toggles the table header/thead
* @public
* @memberof db.libs.tableEditor
* @param {jQuery|string} id The table to be referenced
* @return {jQuery} the table
*/
function toggleHeader(id){
var $table = $(id);
if($table.find('thead').length){
deleteHeader(id);
} else {
insertHeader(id);
}
return $table;
}
/**
* Inserts a header/thead at the top of the table
* @public
* @memberof db.libs.tableEditor
* @param {jQuery|string} id The table to be referenced
* @return {jQuery} the table
*/
function insertHeader(id){
var $table = $(id);
var $thead = $('<thead><tr></tr></thead>');
for(var x = 0; x < $table.find('tbody tr:first-child td').length; x++){
var $cell = $('<td contenteditable></td>');
$cell.on('focus', bindEvents);
$cell.on('blur', unbindEvents);
$thead.find('tr').append($cell);
}
$table.prepend($thead);
return $table;
}
/**
* Removed the header/thead from the table
* @public
* @memberof db.libs.tableEditor
* @param {jQuery|string} id The table to be referenced
* @return {jQuery} the table
*/
function deleteHeader(id){
var $table = $(id).find('thead').remove();
return $table;
}
/**
* Inserts a new column
* @public
* @memberof db.libs.tableEditor
* @param {jQuery|string} id The table to be referenced
* @param {number} [i] Where to insert the column, if not set, after last focused is used, if that's not possible, inserted at the end
* @return {jQuery} the table
*/
function insertColumn(id, i){
var $table = $(id);
if(i === undefined){
if($table.data('lastFocus')){
i = $($table.data('lastFocus')).index() + 1;
$table.data('lastFocus', null);
} else {
i = $table.find('tbody tr td').length;
}
}
$table.find('tr').each(function(r, row){
var $index = $(row).find('td:nth-child(' + (i + 1) + ')');
var $cell = $('<td contenteditable></td>');
if($index.length === 0){
$(row).append($cell);
} else {
$index.before($cell);
}
$cell.on('focus', bindEvents);
$cell.on('blur', unbindEvents);
});
return $table;
}
/**
* Deletes a column
* @public
* @memberof db.libs.tableEditor
* @param {jQuery|string} id The table to be referenced
* @param {number} [i] Index for the column to delete. If not set, the last column is selected
* @return {jQuery} the table
*/
function deleteColumn(id, i){
var $table = $(id);
if(i === undefined){
if($table.data('lastFocus')){
i = $($table.data('lastFocus')).index();
$table.data('lastFocus', null);
} else {
i = $table.find('tbody tr:first-child td').length - 1;
}
}
$table.find('tr').each(function(r, row){
$( $(row).find('td')[i] ).remove();
});
return $table;
}
/**
* Insert a new row
* @public
* @memberof db.libs.tableEditor
* @param {jQuery|string} id The table to be referenced
* @param {number} [i] Where to insert the row, if not set, after last focused is used, if that's not possible, inserted at the end
* @return {jQuery} the table
*/
function insertRow(id, i){
var $table = $(id);
var $row = $('<tr></tr>');
var cells = $table.find('tbody tr:first-child td').length;
if(cells === 0){
cells = $table.find('thead tr:first-child td').length;
}
for(var x = 0; x < cells; x++){
var $cell = $('<td contenteditable></td>');
$cell.on('focus', bindEvents);
$cell.on('blur', unbindEvents);
$row.append($cell);
}
//FIXME: Should also support inserting row after last index.
if(i !== undefined){
var $index = $table.find('tbody tr:nth-child(' + (i+1) + ')');
$index.before($row);
} else {
$table.find('tbody').append($row);
}
return $table;
}
/**
* Delete a row
* @public
* @memberof db.libs.tableEditor
* @param {jQuery|string} id The table to be referenced
* @param {number} [i] Index for the row to delete. If not set, last focus is used, if that is not possible the last row is selected
* @return {jQuery} the table
*/
function deleteRow(id, i){
var $table = $(id);
var $row;
if(i === undefined){
if($table.data('lastFocus')){
$row = $($table.data('lastFocus')).parent();
$table.data('lastFocus', null);
} else {
$row = $table.find('tbody tr').last();
}
} else {
$row = $table.find('tbody tr:nth-child(' + i + ')');
}
if($row.parent().prop('tagName') == 'TBODY'){
$row.remove();
}
return $table;
}
/**
* Set focus to the next cell, if there is no "next cell", one is created
* @private
* @memberof db.libs.tableEditor
* @param {event} [event] event passed from keypress
*/
function nextCell(event){
if(event) event.preventDefault();
var $el = $(':focus');
//If the cell is the last cell in a row, insert a new cell at the end
if( ($el.index() + 1) == $el.parent().find('td').length ){
insertColumn( $(':focus').closest('table') );
}
$el.next().focus();
}
/**
* Set focus to the first cell in the next row. If there is no "next row", one is created
* @private
* @memberof db.libs.tableEditor
* @param {event} [event] event passed from keypress
*/
function nextRow(event){
if(event) event.preventDefault();
var $el = $(':focus');
var $row = $el.parent();
var $table = $(':focus').closest('table');
// If this is the last row in the table, insert a new at the end
if( $el.parent().parent().prop('tagName') == 'TBODY' && ($row.index() + 1) == $table.find('tbody tr').length){
insertRow( $table );
}
if( $el.parent().parent().prop('tagName') != 'TBODY' ){
$table.find('tbody tr td:first-child').focus();
} else {
$row.next().find('td:first-child').focus();
}
}
/**
* Binds events
* @private
* @memberof db.libs.tableEditor
* @param {event} event Event passed from focus or blur
*/
function bindEvents(event){
$(event.currentTarget).closest('table').data('lastFocus', $(event.currentTarget));
setTimeout(function(){
Mousetrap.bind('tab', nextCell);
Mousetrap.bind('enter', nextRow);
},0);
}
/**
* Unbind events
* @private
* @memberof db.libs.tableEditor
* @param {event} event Event passed from focus or blur
*/
function unbindEvents(event){
var $table = $(event.currentTarget).closest('table');
setTimeout(function(){
//If focus not within the table
if( $table.find(':focus').length === 0 ){
Mousetrap.unbind('tab', insertColumn);
Mousetrap.unbind('enter', insertRow);
}
}, 0);
}
/**
* Init Table editor
* @public
* @memberof db.libs.tableEditor
* @return {jQuery} all table editors
*/
function init(){
$('table[data-editor]').each(function(i, table){
if( !db.utils.isInitialized(table, name) ){
$(table).find('td').each(function(i, cell){
$(cell).on('focus', bindEvents);
$(cell).on('blur', unbindEvents);
});
db.utils.initialized(table, name);
}
});
return $('table[data-editor]');
}
return {
init: init,
reflow: init,
toggleHeader: toggleHeader,
insertColumn: insertColumn,
deleteColumn: deleteColumn,
insertRow: insertRow,
deleteRow: deleteRow,
toggleColumnClass: toggleColumnClass,
toggleRowClass: toggleRowClass,
getJSON: getJSON
};
})(jQuery, Mousetrap);
})();