/*
* File: jquery.dataTables.editable.js
* Version: 1.0.0
* Author: Jovan Popovic
*
* Copyright 2010-2011 Jovan Popovic, all rights reserved.
*
* This source file is free software, under either the GPL v2 license or a
* BSD style license, as supplied with this software.
*
* This source file is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE.
*
* Parameters:
* @sUpdateURL String URL of the server-side page used for updating cell. Default value is "UpdateData".
* @sAddURL String URL of the server-side page used for adding new row. Default value is "AddData".
* @sDeleteURL String URL of the server-side page used to delete row by id. Default value is "DeleteData".
* @fnShowError Function function(message, action){...} used to show error message. Action value can be "update", "add" or "delete".
* @sAddNewRowFormId String Id of the form for adding new row. Default id is "formAddNewRow".
* @sAddNewRowButtonId String Id of the button for adding new row. Default id is "btnAddNewRow".
* @sAddNewRowOkButtonId String Id of the OK button placed in add new row dialog. Default value is "btnAddNewRowOk".
* @sAddNewRowCancelButtonId String Id of the OK button placed in add new row dialog. Default value is "btnAddNewRowCancel".
* @sDeleteRowButtonId String Id of the button for adding new row. Default id is "btnDeleteRow".
* @sSelectedRowClass String Class that will be associated to the selected row. Default class is "row_selected".
* @sReadOnlyCellClass String Class of the cells that should not be editable. Default value is "read_only".
* @sAddDeleteToolbarSelector String Selector used to identify place where add and delete buttons should be placed. Default value is ".add_delete_toolbar".
* @fnStartProcessingMode Function function(){...} called when AJAX call is started. Use this function to add "Please wait..." message when some button is pressed.
* @fnEndProcessingMode Function function(){...} called when AJAX call is ended. Use this function to close "Please wait..." message.
* @aoColumns Array Array of the JEditable settings that will be applied on the columns
*/
(function ($) {
///Utility function used to determine id of the cell
//By default it is assumed that id is placed as an id attribute of <tr> that that surround the cell (<td> tag). E.g.:
//<tr id="17">
// <td>...</td><td>...</td><td>...</td><td>...</td>
//</tr>
function fnGetCellID(cell) {
return properties.fnGetRowID($(cell.parentNode));
}
///Utility function used to set id of the new row
//It is assumed that id is placed as an id attribute of <tr> that that surround the cell (<td> tag). E.g.:
//<tr id="17">
// <td>...</td><td>...</td><td>...</td><td>...</td>
//</tr>
function _fnSetRowIDInAttribute(row, id) {
row.attr("id", id);
}
//Utility function used to get id of the row
//It is assumed that id is placed as an id attribute of <tr> that that surround the cell (<td> tag). E.g.:
//<tr id="17">
// <td>...</td><td>...</td><td>...</td><td>...</td>
//</tr>
function _fnGetRowIDFromAttribute(row) {
return row.attr("id");
}
//Utility function used to set id of the new row
//It is assumed that id is placed as an id attribute of <tr> that that surround the cell (<td> tag). E.g.:
//<tr>
// <td>17</td><td>...</td><td>...</td><td>...</td>
//</tr>
function _fnSetRowIDInFirstCell(row, id) {
$("td:first", row).html(id);
}
//Utility function used to get id of the row
//It is assumed that id is placed as an id attribute of <tr> that that surround the cell (<td> tag). E.g.:
//<tr>
// <td>17</td><td>...</td><td>...</td><td>...</td>
//</tr>
function _fnGetRowIDFromFirstCell(row) {
return $("td:first", row).html();
}
//Reference to the DataTable object
var oTable;
//Refences to the buttons used for manipulating table data
var oAddNewRowButton, oDeleteRowButton, oConfirmRowAddingButton, oCancelRowAddingButton;
//Reference to the form used for adding new data
var oAddNewRowForm;
//Plugin options
var properties;
/// Utility function that shows an error message
///@param errorText - text that should be shown
///@param action - action that was executed when error occured e.g. "update", "delete", or "add"
function _fnShowError(errorText, action) {
alert(errorText);
}
//Utility function that put the table into the "Processing" state
function _fnStartProcessingMode() {
if (oTable.fnSettings().oFeatures.bProcessing) {
$(".dataTables_processing").css('visibility', 'visible');
//$(".dataTables_processing").show();
}
}
//Utility function that put the table in the normal state
function _fnEndProcessingMode() {
if (oTable.fnSettings().oFeatures.bProcessing) {
$(".dataTables_processing").css('visibility', 'hidden');
//$(".dataTables_processing").hide();
}
}
var sOldValue, sNewCellValue;
//Utility function used to apply editable plugin on table cells
function fnApplyEditable(aoNodes) {
var oDefaultEditableSettings = {
event: 'dblclick',
"callback": function (sValue, settings) {
properties.fnEndProcessingMode();
if (sNewCellValue == sValue) {
var aPos = oTable.fnGetPosition(this);
oTable.fnUpdate(sValue, aPos[0], aPos[2]);
} else {
var aPos = oTable.fnGetPosition(this);
oTable.fnUpdate(sOldValue, aPos[0], aPos[2]);
properties.fnShowError(sValue, "update");
}
},
"submitdata": function (value, settings) {
properties.fnStartProcessingMode();
sOldValue = value;
sNewCellValue = $("input,select", $(this)).val();
var id = fnGetCellID(this);
var rowId = oTable.fnGetPosition(this)[0];
var columnPosition = oTable.fnGetPosition(this)[1];
var columnId = oTable.fnGetPosition(this)[2];
var sColumnName = oTable.fnSettings().aoColumns[columnId].sName;
if (sColumnName == null || sColumnName == "")
sColumnName = oTable.fnSettings().aoColumns[columnId].sTitle;
return {
"id": id,
"rowId": rowId,
"columnPosition": columnPosition,
"columnId": columnId,
"columnName": sColumnName
};
},
"onerror": function () {
properties.fnEndProcessingMode();
properties.fnShowError("Cell cannot be updated(Server error)", "update");
},
"height": properties.height
};
var cells = null;
if (properties.aoColumns != null) {
for (var i = 0; i < properties.aoColumns.length; i++) {
if (properties.aoColumns[i] != null) {
cells = $("td:nth-child(" + (i + 1) + ")", aoNodes);
var oColumnSettings = oDefaultEditableSettings;
oColumnSettings = $.extend({}, properties.aoColumns[i], oDefaultEditableSettings);
cells.editable(properties.sUpdateURL, oColumnSettings);
}
}
} else {
cells = $('td:not(.' + properties.sReadOnlyCellClass + ')', aoNodes);
cells.editable(properties.sUpdateURL, oDefaultEditableSettings);
}
}
//Called when user confirm that he want to add new record
function fnOnRowAdding(event) {
if (oAddNewRowForm.valid()) {
properties.fnStartProcessingMode();
var params = oAddNewRowForm.serialize();
$.ajax({ 'url': properties.sAddURL,
'data': params,
'type': 'POST',
success: fnOnRowAdded,
error: function (response) {
properties.fnEndProcessingMode();
properties.fnShowError(response.responseText, "add");
}
});
}
event.stopPropagation();
event.preventDefault();
}
///Event handler called when a new row is added and response is returned from server
function fnOnRowAdded(data) {
properties.fnEndProcessingMode();
var iColumnCount = oTable.dataTableSettings[0].aoColumns.length;
var values = new Array();
$("input:text[rel],input:radio[rel][checked],select[rel],textarea[rel]", oAddNewRowForm).each(function () {
var rel = $(this).attr("rel");
if (rel >= iColumnCount)
properties.fnShowError("In the add form is placed input element with the name '" + $(this).attr("name") + "' with the 'rel' attribute that must be less than a column count - " + iColumnCount, "add");
else
values[rel] = this.value;
});
//Add values from the form into the table
var rtn = oTable.fnAddData(values);
var oTRAdded = oTable.fnGetNodes(rtn);
//Apply editable plugin on the cells of the table
fnApplyEditable(oTRAdded);
//add id returned by server page as an TR id attribute
properties.fnSetRowID($(oTRAdded), data);
//Close the dialog
oAddNewRowForm.dialog('close');
$(oAddNewRowForm)[0].reset();
$(".error", $(oAddNewRowForm)).html("");
}
//Called when user cancels ading new record in popup
function fnOnCancelRowAdd(event) {
//Close the dialog
oAddNewRowForm.dialog('close');
$(oAddNewRowForm)[0].reset();
$(".error", $(oAddNewRowForm)).html("");
event.stopPropagation();
event.preventDefault();
}
//Called before row is deleted
//Returning false will abort delete
function fnOnRowDeleting(tr, id) {
return true;
}
//Called when user deletes a row
function fnOnRowDelete(event) {
if ($('tr.' + properties.sSelectedRowClass + ' td', oTable).length == 0) {
oDeleteRowButton.attr("disabled", "true");
return;
}
var id = fnGetCellID($('tr.' + properties.sSelectedRowClass + ' td', oTable)[0]);
if (fnOnRowDeleting($('tr.' + properties.sSelectedRowClass, oTable), id)) {
properties.fnStartProcessingMode();
$.ajax({ 'url': properties.sDeleteURL,
'data': 'id=' + id,
"success": fnOnRowDeleted,
"error": function (response) {
properties.fnEndProcessingMode();
properties.fnShowError(response.responseText, "delete");
}
});
}
}
//Called when record is deleted on the server
function fnOnRowDeleted(response) {
properties.fnEndProcessingMode();
var oTRSelected = $('tr.' + properties.sSelectedRowClass, oTable)[0];
if (response == "ok" || response == "") {
oTable.fnDeleteRow(oTRSelected);
oDeleteRowButton.attr("disabled", "true");
}
else {
properties.fnShowError(response, "delete");
}
}
$.fn.makeEditable = function (options) {
oTable = this;
var defaults = {
fnGetRowID: _fnGetRowIDFromAttribute,
fnSetRowID: _fnSetRowIDInAttribute,
sUpdateURL: "UpdateData",
sAddURL: "AddData",
sDeleteURL: "DeleteData",
height: "14px",
sAddNewRowFormId: "formAddNewRow",
sAddNewRowButtonId: "btnAddNewRow",
sAddNewRowOkButtonId: "btnAddNewRowOk",
sAddNewRowCancelButtonId: "btnAddNewRowCancel",
sDeleteRowButtonId: "btnDeleteRow",
sSelectedRowClass: "row_selected",
sReadOnlyCellClass: "read_only",
sAddDeleteToolbarSelector: ".add_delete_toolbar",
fnShowError: _fnShowError,
fnStartProcessingMode: _fnStartProcessingMode,
fnEndProcessingMode: _fnEndProcessingMode,
aoColumns: null
};
properties = $.extend(defaults, options);
return this.each(function () {
if (oTable.fnSettings().oFeatures.bServerSide) {
oTable.fnSettings().aoDrawCallback.push({
"fn": function () {
//Apply jEditable plugin on the table cells
fnApplyEditable(oTable.fnGetNodes());
$(oTable.fnGetNodes()).each(function () {
var position = oTable.fnGetPosition(this);
var id = oTable.fnGetData(position)[0];
properties.fnSetRowID($(this), id);
}
);
},
"sName": "fnApplyEditable"
});
} else {
//Apply jEditable plugin on the table cells
fnApplyEditable(oTable.fnGetNodes());
}
//Setup form to open in dialog
oAddNewRowForm = $("#" + properties.sAddNewRowFormId);
if (oAddNewRowForm.length != 0) {
oAddNewRowForm.dialog({ autoOpen: false });
//Add button click handler on the "Add new row" button
oAddNewRowButton = $("#" + properties.sAddNewRowButtonId);
if (oAddNewRowButton.length != 0) {
oAddNewRowButton.click(function () { oAddNewRowForm.dialog('open'); });
} else {
if ($(properties.sAddDeleteToolbarSelector).length == 0)
throw "Cannot find button for adding new row althogh form for adding new record is specified";
else
oAddNewRowButton = null;
}
oConfirmRowAddingButton = $("#" + properties.sAddNewRowOkButtonId, oAddNewRowForm);
if (oConfirmRowAddingButton.length == 0) {
oAddNewRowForm.append("<button id='" + properties.sAddNewRowOkButtonId + "'>Ok</button>");
oConfirmRowAddingButton = $("#" + properties.sAddNewRowOkButtonId, oAddNewRowForm);
}
//Add button click handler on the "Ok" button in the add new row dialog
oConfirmRowAddingButton.click(fnOnRowAdding);
oCancelRowAddingButton = $("#" + properties.sAddNewRowCancelButtonId);
if (oCancelRowAddingButton.length == 0) {
oCancelRowAddingButton = oAddNewRowForm.append("<button id='" + properties.sAddNewRowCancelButtonId + "'>Cancel</button>");
oCancelRowAddingButton = $("#" + properties.sAddNewRowCancelButtonId);
}
oCancelRowAddingButton.click(fnOnCancelRowAdd);
} else {
oAddNewRowForm = null;
}
//Set the click handler on the "Delete selected row" button
oDeleteRowButton = $('#' + properties.sDeleteRowButtonId);
if (oDeleteRowButton.length != 0)
oDeleteRowButton.click(fnOnRowDelete);
else {
oDeleteRowButton = null;
}
//If an add and delete buttons does not exists but Add-delete toolbar is specificed
//Autogenerate these buttons
oAddDeleteToolbar = $(properties.sAddDeleteToolbarSelector);
if (oAddDeleteToolbar.length != 0) {
if (oAddNewRowButton == null && properties.sAddNewRowButtonId != ""
&& oAddNewRowForm != null) {
oAddDeleteToolbar.append("<button id='" + properties.sAddNewRowButtonId + "' class='add_row'>Add</button>");
oAddNewRowButton = $("#" + properties.sAddNewRowButtonId);
oAddNewRowButton.click(function () { oAddNewRowForm.dialog('open'); });
}
if (oDeleteRowButton == null && properties.sDeleteRowButtonId != "") {
oAddDeleteToolbar.append("<button id='" + properties.sDeleteRowButtonId + "' class='delete_row'>Delete</button>");
oDeleteRowButton = $("#" + properties.sDeleteRowButtonId);
oDeleteRowButton.click(fnOnRowDelete);
}
}
//If delete button exists disable it until some row is selected
if (oDeleteRowButton != null)
oDeleteRowButton.attr("disabled", "true");
//Set selected class on row that is clicked
$("tbody", oTable).click(function (event) {
if ($(event.target.parentNode).hasClass(properties.sSelectedRowClass)) {
$(event.target.parentNode).removeClass(properties.sSelectedRowClass);
if (oDeleteRowButton != null)
oDeleteRowButton.attr("disabled", "true");
} else {
$(oTable.fnSettings().aoData).each(function () {
$(this.nTr).removeClass(properties.sSelectedRowClass);
});
$(event.target.parentNode).addClass(properties.sSelectedRowClass);
if (oDeleteRowButton != null)
oDeleteRowButton.removeAttr("disabled");
}
});
});
};
})(jQuery);