/*!
* FooTable Editable Plugin - Awesome Responsive FooTables That Are Editable
* Version : 0.1
* Author: Jake Drew - http://www.jakemdrew.com
*
* Requires jQuery - http://jquery.com/
* Requires FooTable http://themergency.com/footable
*
* FooTable Editable Copyright 2013 Jake Drew
*
* Released under the MIT license
* You are free to use FooTable Editable in commercial projects as long as this copyright header is left intact.
*
* Date: 2 Jul 2013
*/
(function ($, w, undefined) {
if (w.footable == undefined || w.footable == null)
throw new Error('Please check and make sure footable.js is included in the page and is loaded prior to this script.');
var defaults = {
serverTableName: undefined,
dataHandlerURL: "demo",
autoLoad: false,
createDetail:
function (element, data) {
var groups = { '_none': { 'name': null, 'data': [] } };
for (var i = 0; i < data.length; i++) {
var groupid = data[i].group;
if (groupid != null) {
if (!(groupid in groups))
groups[groupid] = { 'name': data[i].groupName, 'data': [] };
groups[groupid].data.push(data[i]);
} else {
groups._none.data.push(data[i]);
}
}
var table = $(element).closest('table');
for (var group in groups) {
if (groups[group].data.length == 0) continue;
if (group != '_none') element.append('
' + groups[group].name + '
');
for (var j = 0; j < groups[group].data.length; j++) {
var separator = (groups[group].data[j].name) ? ':' : '';
//check for html input tags or img tags
var tagTest = '
');
}
});
}
}
function addButtonsToDetailRow(detailRow, buttons) {
var row = $(detailRow).prev();
var detailDiv = $(detailRow).find('.footable-row-detail-inner');
var addButtonColVisible = $(row).find('input[type="button"][value="Add"].footableButton').is(":visible");
var deleteButtonColVisible = $(row).find('input[type="button"][value="Delete"].footableButton').is(":visible");
var newRecordRow = $(row).hasClass('fooNewRecord');
if (row.hasClass('fooNewRecord')) {
if (!addButtonColVisible && buttons.addCols.length >= 0) {
if ($(detailDiv).find('input[type="button"][value="Add"]').length <= 0) {
$(detailDiv).append('');
$(detailDiv).find('input[type="button"][value="Delete"].footableButton').remove();
}
}
}
else {
if (!newRecordRow && !deleteButtonColVisible && buttons.deleteCols.length >= 0) {
if ($(detailDiv).find('input[type="button"][value="Delete"]').length <= 0) {
$(detailDiv).append('');
$(detailDiv).find('input[type="button"][value="Add"].footableButton').remove();
}
}
}
}
function processCommand(target, command) {
var tId = $(target).closest('table').attr('id');
var curRow = getCurrentRow(target);
//Do not process update transactions on a new record
if ($(curRow).hasClass('fooNewRecord') && $(target).attr('type') != 'button') return;
var updateRecord = {};
updateRecord.command = command;
updateRecord.table = $.data(w.footable, tId + '_serverTableName');
if (command == "Load") {
//send the updateRecord to the server via AJAX
transportData(target,updateRecord);
return;
}
/*All fields are sent for command=add, only id fields for command=delete.*/
$(curRow).find('td').each(function () {
var fieldName = $.data(w.footable, tId + '_colNames')[$(this).index()];
var fieldIsVisible = $(this).is(":visible");
var fieldIsControl = $.data(w.footable, tId + '_colControlType')[$(this).index()];
var fieldIsIdCol = $.data(w.footable, tId + '_idColIndexes').indexOf($(this).index()) >= 0;
if (command == 'Add' || fieldIsIdCol) {
//input vals are always populated in corresponding td.
if (fieldIsControl === undefined) updateRecord[fieldName] = $(this).text().trim();
else {
var ctlVal = $(this).find('input').val().trim();
if (ctlVal != "true") ctlVal = "false";
updateRecord[fieldName] = ctlVal;
}
}
});
if (command == 'Update') {
var updatedFieldName = $.data(w.footable, tId + '_colNames')[$(target).index()];
var updatedFieldValue;
var updatedFieldOldValue = $.data(target, 'oldValue');
if ($(target).is("input[type='text']")) {
updatedFieldName = $(target).closest('div').text().trim().slice(0, -1);
updatedFieldValue = $(target).val();
}
else if($.data(w.footable, tId + '_colControlType')[$(target).index()] !== undefined) {
var ctl = $(target).find('input')
var ctlVal = ctl.val();
if (ctlVal != "true") ctlVal = "false";
updatedFieldValue = ctlVal;
updatedFieldOldValue = !ctlVal;
}
else {
updatedFieldValue = $(target).text();
}
updateRecord.updatedFieldName = updatedFieldName;
updateRecord.updatedFieldValue = updatedFieldValue;
updateRecord.updatedFieldOldValue = updatedFieldOldValue;
}
//send the updateRecord to the server via AJAX
transportData(target,updateRecord);
}
function transportData(target, updateRecord) {
var tId = $(target).closest('table').attr('id');
var dataHandlerURL = $.data(w.footable, tId + '_dataHandlerURL')
//Do nothing mode...
if (dataHandlerURL == '') return;
//Do not make ajax call for demo mode...
if (dataHandlerURL == 'demo' || updateRecord === undefined) {
alert("Demo Mode:\r\nThe following JSON data would be sent to the server: \r\n" + JSON.stringify(updateRecord));
//mimic server response
var response = {};
response.response = "Success";
response.message = "Your message here";
response.data = undefined;
alert("Demo Mode:\r\nThe server responded: \r\n" + JSON.stringify(response));
processServerResponse(target, JSON.stringify(response), updateRecord);
return;
}
//Send the updateRecord to the server via AJAX for valid command.
$.ajax({
type: "POST",
url: $.data(w.footable, tId + '_dataHandlerURL'),
contentType: "application/json; charset=uft-8",
data: JSON.stringify(updateRecord)
})
.done(function (data) { processServerResponse(target, data, updateRecord); })
.fail(function (msg) { alert("error: " + JSON.stringify(msg.responseText)); });
//.always(function (msg) { alert("complete" + JSON.stringify(msg)); });
}
function tryJSONParse(data){
try {
p = JSON.parse(data);
data = p;
} catch (e) {
// data was not valid json
}
finally{
return data;
}
}
function processServerResponse(target, data, updateRecord) {
//the data response variable can be json or a valid javascript object...
data = tryJSONParse(data);
data.responseData = tryJSONParse(data.responseData);
var table = $(target).closest('table');
var curRow = getCurrentRow(target);
var nextRow = $(curRow).next();
//handle processing for fooButtons
if ($(target).hasClass('footableButton') && data.response != "Error") {
if (updateRecord.command == "Add") {
//convert new record to normal record.
$(curRow).removeClass('fooNewRecord');
$(curRow).find('input[type="button"][value="Add"].footableButton').remove();
addButtonsToRow(curRow, getButtonIndexes(table));
if ($(nextRow).hasClass('footable-row-detail')) addButtonsToDetailRow(nextRow, getButtonIndexes(this));
//Hide the curRow detail row, if showing
if ($(curRow).hasClass('footable-detail-show')) {
$(curRow).removeClass('footable-detail-show');
$(nextRow).hide();
}
}
if (updateRecord.command == "Delete" && data.response == "Success") {
deleteRow(curRow);
}
}
//Handle processing AJAX server responses.
if (data.response == "Success") {
//Do nothing!
}
else if (data.response == "Load") {
deleteAllRows(table);
addRows(table, data.responseData);
}
else if (data.response == "Append") {
addRows(table, data.responseData);
}
else if (data.response == "Update") {
updateRow(curRow, data.responseData);
}
else if (data.response == "Delete") {
deleteRow(curRow);
}
else if (data.response == "DeleteAll") {
deleteAllRows(table);
}
else if (data.response == "Error") {
alert("The update was not successful\r\n" + data.message);
if (updateRecord.command == 'Update') {
//if a cell update fails, revert back to the previous value.
var updateIndex = $.data(w.footable, $(ft.table).attr('id') + '_colNames').indexOf(updateRecord.updatedFieldName);
$(target).closest('tr').find('td').eq(updateIndex)
.text(updateRecord.updatedFieldOldValue);
}
}
else {
alert('Invalid server response! Response recieved: ' + data.response);
}
checkNewEmptyRecord(table);
}
function getCurrentRow(target) {
var curRow = $(target).closest('tr');
if ($(curRow).hasClass('footable-row-detail')) curRow = $(curRow).prev();
return curRow;
}
function updateRow(row, rowData) {
var table = $(row).closest('table');
var rowTd = $(row).find('td');
$.each(rowData, function (name, value) {
var colIndex = $.data(w.footable, $(table).attr('id') + '_colNames').indexOf(name);
if (colIndex != -1) $(rowTd).eq(colIndex).text(value);
});
}
function deleteRow(row) {
if ($(row).next().hasClass('footable-row-detail')) {
$(row).next().remove();
}
if ($(row).prev().hasClass('footable-detail-show')) {
$(row).prev().remove();
}
$(row).remove();
}
function deleteAllRows(table) {
$(table).find('tbody > tr').not('.fooNewRecord').remove();
}
function checkNewEmptyRecord(table) {
//Don't add multiple empty records to a table.
if ($(table).find('.fooNewRecord').length > 0) return;
if ($.data(w.footable, $(table).attr('id') + '_fooNewRecord').length <= 0) return;
//make a copy of the default new record and hide fields that should be hidden.
var newRec = $.data(w.footable, $(table).attr('id') + '_fooNewRecord').clone(true, true);
var headRowTd = $(table).find('th');
$(newRec).find('td').each(function (index) {
if (!$(headRowTd[index]).is(":visible")) {
$(this).hide();
}
});
$(table).find('tbody').append(newRec);
}
function addRows(table, tableRows) {
//exit if there are no rows to process.
if (tableRows === undefined) return;
//Assume tableRows is a JSON string and try to parse, if it is not already and object
var tableTh = $(table).find('th');
var rows = "";
var fooButtonIndexes = getButtonIndexes(table);
var ft = $(table).data('ft');
$(tableRows).each(function () {
var tr = '
';
var i = 0;
//Build valid tr's for row in tableRows
$.each(this, function (name, value) {
//use any custom return parsers to parse the value for each new row's cell
var retParser = tableTh.eq(i).attr('data-return-type');
if (retParser != undefined) {
var parser = ft.options.parsers[retParser];
value = parser(value);
}
var fieldIsControl = $.data(w.footable, $(table).attr('id') + '_colControlType')[i];
if (fieldIsControl != undefined) {
if (value == "true" || value ==true) value = '';
else value = '';
}
//capture and add fooTable data-class values.
var classes = "";
var dataClass = $(tableTh).eq(i).attr('data-class');
if (dataClass !== undefined) classes = ' class="' + dataClass + '" ';
//handle hidden fields
var style = "";
if (!$(tableTh).eq(i).is(":visible")) style = ' style="display:none;" ';
tr += '
' + value + '
';
i++
});
//add 1 empty td for each button column
$(fooButtonIndexes.buttonColCt).each(function () {
tr += '
';
});
tr += '
';
rows += tr;
});
$(table).find('tbody').prepend(rows);
addFooRowButtons(table);
makeColsFooEditable(table);
$(table).data('ft').bindToggleSelectors();
$(table).data('ft').resize(); //makes new rows display correct when fields are hidden.
}
$.fn.ftEditable = function (target) {
var e = {};
e.processCommand = processCommand;
e.transportData = transportData;
e.processServerResponse = processServerResponse;
e.updateRow = updateRow;
e.deleteRow = deleteRow;
e.deleteAllRows = deleteAllRows;
e.checkNewEmptyRecord = checkNewEmptyRecord;
e.addRows = addRows;
return e;
}
function Editable() {
//Expose plugin features to w.footable
var p = this;
p.name = 'Footable Editable';
p.init = function (ft) {
//save a reference to ft.
$(ft.table).data('ft', ft);
//capture any default over-rides by user
var tId = $(ft.table).attr('id');
$.data(w.footable, tId + '_dataHandlerURL', ft.options.dataHandlerURL);
$.data(w.footable, tId + '_serverTableName', ft.options.serverTableName);
$.data(w.footable, tId + '_autoLoad', ft.options.autoLoad);
$(ft.table).bind({
'footable_initialized': function (e) {
//Get array of all the column names, indexes with class='id' and footableButtons
var idColIndexes = new Array();
var colNames = new Array();
var fooEditableCols = new Array();
//var expandCols = new Array;
var colControlType = new Array();
$(ft.table).find('th').each(function (index) {
var fieldName = $(this).text().trim();
colNames.push(fieldName);
if ($(this).hasClass('fooEditable')) {
fooEditableCols.push(fieldName);
}
if ($(this).hasClass('fooId')) {
idColIndexes.push(index);
}
colControlType.push($(this).attr('data-ft-control'));
});
//set global table specific variables
$.data(w.footable, tId + '_colNames', colNames);
$.data(w.footable, tId + '_idColIndexes', idColIndexes);
$.data(w.footable, tId + '_fooEditableCols', fooEditableCols);
$.data(w.footable, tId + '_fooNewRecord', $(ft.table).find('.fooNewRecord').clone(true, true));
$.data(w.footable, tId + '_colControlType', colControlType);
//Populate the value property of checkboxes in the footable to true or false
$(ft.table).on('change', 'input[type="checkbox"]', function (e) {
var val = $(this).is(':checked');
$(this).val(val);
if(val) this.setAttribute("checked", "checked");
else this.removeAttribute("checked");
$(this).parent().trigger('td-cell-changed');
});
//track each footable cell's old value to determine when a cell value changes.
$(ft.table).on('focus', 'td', function () {
$.data(this, 'oldValue', $(this).text());
});
//fire custom event each time a footable cell value changes
$(ft.table).on('blur', 'td', function (e) {
if ($(this).text() != $.data(this, 'oldValue')) {
$(this).trigger('td-cell-changed');
}
});
//if a footable cell value changes, create an update object to send to the server.
$(ft.table).bind('td-cell-changed', function (e) {
var buttons = getButtonIndexes(this);
//Do not process update on data-ft-buttons columns
if (buttons.buttonCols.indexOf($(e.target).index()) < 0) {
processCommand(e.target, 'Update');
}
});
//must use on() function since input tags are dynamically created.
$(ft.table).on('focus', 'input', function (e) {
$.data(this, 'oldValue', $(this).val());
});
//must use on() function since input tags are dynamically created.
$(ft.table).on('change', 'input', function (e) {
if ($(e.target).closest('tr').hasClass('footable-row-detail')) {
//Always pass changes made to an input detail textbox to it's corr parent td
var FieldName = $(e.target).closest('div').text().trim().slice(0, -1);
var tdFieldIndex = $.data(w.footable, $(ft.table).attr('id') + '_colNames').indexOf(FieldName);
$(e.target).closest('tr').prev().find('td').eq(tdFieldIndex)
.text($.data(e.target, 'oldValue'))
.text($(e.target).val());
processCommand(e.target, 'Update');
}
});
$(ft.table).on('click', 'input[type="button"][value="Add"].footableButton', function (e) {
processCommand(e.target, 'Add');
});
$(ft.table).on('click', 'input[type="button"][value="Delete"].footableButton', function (e) {
processCommand(e.target, 'Delete');
});
} //footable_initialized
}); //ft.table bind
makeColsFooEditable(ft.table);
addFooRowButtons(ft.table);
//AutoLoad sends a load command each time a Footable is created.
if($.data(w.footable, $(ft.table).attr('id') + '_autoLoad')) {
processCommand(ft.table, 'Load');
}
//Create event that fires after footable-row-detail-inner div has been populated.
$(ft.table).on('click', 'tr', function () {
if (!$(this).hasClass('footable-row-detail')) {
$(this).trigger('fooDetail-Populated');
}
});
//Add any footableButton's to a row's footable-row-detail-inner div
$(ft.table).bind('fooDetail-Populated', function (e) {
addButtonsToDetailRow($(e.target).next(), getButtonIndexes(this));
});
} //p.init
} // Editable()
w.footable.plugins.register(new Editable(), defaults);
})(jQuery, window);