diff --git a/controllers.py b/controllers.py index 11a5c5e..0c3567b 100644 --- a/controllers.py +++ b/controllers.py @@ -257,6 +257,7 @@ def FormStyleSimpleTable(table, vars, errors, readonly, deletable): return FormStyleDefault(table, vars, errors, readonly, deletable, classes) + @action('companies', method=['POST', 'GET']) @action('companies///', method=['POST', 'GET']) @action.uses(session, db, auth, 'libs/simple_table.html') @@ -395,3 +396,122 @@ def employees(action=None, tablename=None, record_id=None): include_action_button_text=True) return dict(grid=grid) +#///////////////////////////////////////////////////////////////////////// +#contractor +#Datatables edit facility - Based on Example 3 from https://github.com/KasperOlesen/DataTable-AltEditor +#///////////////////////////////////////////////////////////////////////// +#This returns the +@unauthenticated +@action('contractor', method=['GET']) +@action.uses(session, db, auth, 'contractor.html') +def contractor(): + """ + display a page with a datatables.net with AltEditor on it + The datatable will get data from contractor_data, see contractor.html + :return: + """ + return dict() + +@unauthenticated +@action('contractor_data', method=['GET']) +@action.uses(session, db, auth) +def contractor_data(): + """ + :return: + return data in JSON format: e.g.: + [{"id":1, "name":"Tigerrrr Nixon", "position":"System Architect", "office":"Edinburgh", "extension":"5421", "startDate":"2011/04/25", "salary":"Tiger Nixon"}, + {"id":2, "name":"Garrett Winters", "position":"Accountant", "office":"Tokyo", "extension":"8422", "startDate":"2011/07/25", "salary":"Garrett Winters"}] + + """ + queries = [(db.contractor.id > 0)] + query = reduce(lambda a, b: (a & b), queries) + + data = [dict(id=z.id, + name=z.name, + position=z.position, + office=z.office, + extension=z.extension, + startDate=z.startDate, + salary=z.salary, + ) for z in db(query).select()] + + return json.dumps(data) + +#EDIT +@action('contractor/', method=['PUT', 'POST', 'GET']) +@action.uses(session, db, auth, 'contractor.html') +def contractor(contractor_id): + """ + This handles Ajax Put and Post requests. See lines 107 and 127 of contractor.html + Create/PUT: + url: 'contractor/0', + type: 'PUT', + + Edit/POST: + url: 'contractor/'+rowdata.id, + type: 'POST', + :return: + This function must return one compelte row of the new or edited row in JSON format for the table to update itself via Ajax. + e.g.: {"id":10, "name":"Name modified by server", "position":"Modified position", "office":"", "extension":"", "startDate":"", "salary":""} + """ + if request.method == 'GET': + if int(contractor_id) > 0: + z = db(db.contractor.id == int(contractor_id)).select().first() + data = dict(id=z.id, + name=z.name, + position=z.position, + office=z.office, + extension=z.extension, + startDate=z.startDate, + salary=z.salary, + ) + return json.dumps(data) + + if request.method == 'PUT': + #if contractor_id == 0: + #Create new + #Do other specific error checking here. + newid = db.contractor.insert(name=request.forms.get('name'), position=request.forms.get('position'), office=request.forms.get('office'), + extension=request.forms.get('extension'), startDate=request.forms.get('startdate'), salary=request.forms.get('salary')) + z = db(db.contractor.id == int(newid)).select().first() + data = dict(id=z.id, + name=z.name, + position=z.position, + office=z.office, + extension=z.extension, + startDate=z.startDate, + salary=z.salary) + return json.dumps(data) + #return """[{"id": 3, "name": "Ass", "position": "ret", "office": "london", "extension": "1", "startDate": null, "salary": "100000"}]""" + + if request.method == 'POST': + if int(contractor_id) > 0: + z = db(db.contractor.id == int(contractor_id)).select().first() + z.update_record(name=request.forms.get('name'), position=request.forms.get('position'), office=request.forms.get('office'), + extension=request.forms.get('extension'), startDate=request.forms.get('startdate'), salary=request.forms.get('salary')) + z = db(db.contractor.id == int(contractor_id)).select().first() + data = [dict(id=z.id, + name=z.name, + position=z.position, + office=z.office, + extension=z.extension, + startDate=z.startDate, + salary=z.salary)] + return json.dumps(data) + + +#///////////////////////////////////////////////////////////////////////// +#DELETE +@action('contractor/delete/', method=['DELETE','GET', 'POST']) +@action.uses(session, db, auth, 'contractor.html') +def contractor_delete(contractor_id): + """ + This handles delete requests + url: 'contractor/delete/' + rowdata.id, + type: 'DELETE', + :return: + Dont need to return anything + """ + result = db(db.contractor.id == contractor_id).delete() + return dict() +#///////////////////////////////////////////////////////////////////////// diff --git a/models.py b/models.py index f005807..1df5690 100644 --- a/models.py +++ b/models.py @@ -59,4 +59,19 @@ Field('hired', 'date', requires=IS_NULL_OR(IS_DATE())), Field('active', 'boolean', default=False)) +db.define_table('contractor', + Field('id', 'id', readable=False), + Field('name'), + Field('position'), + Field('office'), + Field('extension'), + Field('startDate'), + Field('salary'), + format='%(contractor)s') +if not db(db.contractor).count(): + db.contractor.insert(name='Martha', position='Software', office='london', extension='1', startDate="2011/07/25", salary=100000) + db.contractor.insert(name='Gavin Cortez', position='Developer', office='london', extension='1', startDate="2011/07/25", salary=100000) + db.contractor.insert(name='Suki Burks', position='Assistant', office='london', extension='1', startDate="2011/07/25", salary=100000) + db.contractor.insert(name='Fiona Green', position='Support', office='london', extension='1', startDate="2011/07/25", salary=100000) + db.contractor.insert(name='Peanut', position='great', office='london', extension='1', startDate="2011/07/25", salary=100000) db.commit() diff --git a/static/js/dataTables.altEditor.free.js b/static/js/dataTables.altEditor.free.js new file mode 100644 index 0000000..e5962b5 --- /dev/null +++ b/static/js/dataTables.altEditor.free.js @@ -0,0 +1,1027 @@ +/** + * @summary altEditor + * @description Lightweight editor for DataTables + * @version 2.0 + * @file dataTables.editor.free.js + * @author kingkode (www.kingkode.com) + * Modified by: Kasper Olesen (https://github.com/KasperOlesen), Luca Vercelli (https://github.com/luca-vercelli), Zack Hable (www.cobaltdevteam.com) + * @contact www.kingkode.com/contact + * @contact zack@cobaltdevteam.com + * @copyright Copyright 2016 Kingkode + * + * This source file is free software, available under the following license: MIT + * license + * + * 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. See the license files for details. + * + * + */ +(function (factory) { + if (typeof define === 'function' && define.amd) { + // AMD + define(['jquery', 'datatables.net'], function ($) { + return factory($, window, document); + }); + } + else if (typeof exports === 'object') { + // CommonJS + module.exports = function (root, $) { + if (!root) { + root = window; + } + + if (!$ || !$.fn.dataTable) { + $ = require('datatables.net')(root, $).$; + } + + return factory($, root, root.document); + }; + } + else { + // Browser + factory(jQuery, window, document); + } +}) +(function ($, window, document, undefined) { + 'use strict'; + var DataTable = $.fn.dataTable; + + var _instance = 0; + + /** + * altEditor provides modal editing of records for Datatables + * + * @class altEditor + * @constructor + * @param {object} + * oTD DataTables settings object + * @param {object} + * oConfig Configuration object for altEditor + */ + var altEditor = function (dt, opts) { + if (!DataTable.versionCheck || !DataTable.versionCheck('1.10.8')) { + throw ("Warning: altEditor requires DataTables 1.10.8 or greater"); + } + + // User and defaults configuration object + this.c = $.extend(true, {}, DataTable.defaults.altEditor, + altEditor.defaults, opts); + + /** + * @namespace Settings object which contains customisable information + * for altEditor instance + */ + this.s = { + /** @type {DataTable.Api} DataTables' API instance */ + dt: new DataTable.Api(dt), + + /** @type {String} Unique namespace for events attached to the document */ + namespace: '.altEditor' + (_instance++) + }; + + /** + * @namespace Common and useful DOM elements for the class instance + */ + this.dom = { + /** @type {jQuery} altEditor handle */ + modal: $('
'), + }; + + /* Constructor logic */ + this._constructor(); + } + + $.extend( + altEditor.prototype, + { + /*************************************************************** + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Constructor * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + */ + + /** + * Initialise the RowReorder instance + * + * @private + */ + _constructor: function () { + var that = this; + var dt = this.s.dt; + + if (dt.settings()[0].oInit.onAddRow) + that.onAddRow = dt.settings()[0].oInit.onAddRow; + if (dt.settings()[0].oInit.onDeleteRow) + that.onDeleteRow = dt.settings()[0].oInit.onDeleteRow; + if (dt.settings()[0].oInit.onEditRow) + that.onEditRow = dt.settings()[0].oInit.onEditRow; + + this._setup(); + + dt.on('destroy.altEditor', function () { + dt.off('.altEditor'); + $(dt.table().body()).off(that.s.namespace); + $(document.body).off(that.s.namespace); + }); + }, + + /*************************************************************** + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Private methods * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + */ + + /** + * Setup dom and bind button actions + * + * @private + */ + _setup: function () { + var that = this; + var dt = this.s.dt; + this.random_id = ("" + Math.random()).replace(".", ""); + var modal_id = 'altEditor-modal-' + this.random_id; + this.modal_selector = '#' + modal_id; + this.language = DataTable.settings.values().next().value.oLanguage.altEditor || {}; + this.language.modalClose = this.language.modalClose || 'Close'; + this.language.edit = this.language.edit || {}; + this.language.edit = { title: this.language.edit.title || 'Edit record', + button: this.language.edit.button || 'Edit' + }; + this.language.delete = this.language.delete || {}; + this.language.delete = { title: this.language.delete.title || 'Delete record', + button: this.language.delete.button || 'Delete' }; + this.language.add = this.language.add || {}; + this.language.add = { title: this.language.add.title || 'Add record', + button: this.language.add.button || 'Add' + }; + this.language.success = this.language.success || 'Success!'; + this.language.error = this.language.error || {}; + this.language.error = { message: this.language.error.message || 'There was an unknown error!', + label: this.language.error.label || 'Error!', + responseCode: this.language.error.responseCode || 'Response code: ', + required: this.language.error.required || 'Field is required', + unique: this.language.error.unique || 'Duplicated field' + }; + var modal = ''; + // add modal + $('body').append(modal); + + // add Edit Button + if (dt.button('edit:name')) { + dt.button('edit:name').action(function (e, dt, node, config) { + that._openEditModal(); + + $('#altEditor-edit-form-' + that.random_id) + .off('submit') + .on('submit', function (e) { + e.preventDefault(); + e.stopPropagation(); + that._editRowData(); + }); + }); + } + + // add Delete Button + if (dt.button('delete:name')) { + dt.button('delete:name').action(function (e, dt, node, config) { + that._openDeleteModal(); + + $('#altEditor-delete-form-' + that.random_id) + .off('submit') + .on('submit', function (e) { + e.preventDefault(); + e.stopPropagation(); + that._deleteRow(); + }); + }); + } + + // add Add Button + if (dt.button('add:name')) { + dt.button('add:name').action(function (e, dt, node, config) { + that._openAddModal(); + + $('#altEditor-add-form-' + that.random_id) + .off('submit') + .on('submit', function (e) { + e.preventDefault(); + e.stopPropagation(); + that._addRowData(); + }); + }); + } + + // bind 'unique' error messages + $(this.modal_selector).bind('input', '[data-unique]', function(elm) { + if ($(elm.target).attr('data-unique') == null || $(elm.target).attr('data-unique') === 'false') { + return; + } + var target = $(elm.target); + var colData = dt.column("th:contains('" + target.attr("name") + "')").data(); + // go through each item in this column + var selectedCellData = null; + if (dt.row({selected: true}).index() != null) + selectedCellData = dt.cell(dt.row({selected: true}).index(), dt.column("th:contains('" + target.attr("name") + "')").index()).data(); + elm.target.setCustomValidity(''); + for (var j in colData) { + // if the element is in the column and its not the selected one then its not unique + if (target.val() == colData[j] && colData[j] != selectedCellData) { + elm.target.setCustomValidity(that.language.error.unique); + } + } + }); + + // add Refresh button + if (this.s.dt.button('refresh:name')) { + this.s.dt.button('refresh:name').action(function (e, dt, node, config) { + if (dt.ajax && dt.ajax.url()) { + dt.ajax.reload(); + } + }); + } + }, + + /** + * Emit an event on the DataTable for listeners + * + * @param {string} + * name Event name + * @param {array} + * args Event arguments + * @private + */ + _emitEvent: function (name, args) { + this.s.dt.iterator('table', function (ctx, i) { + $(ctx.nTable).triggerHandler(name + '.dt', args); + }); + }, + + /** + * Open Edit Modal for selected row + * + * @private + */ + _openEditModal: function () { + + var dt = this.s.dt; + var adata = dt.rows({ + selected: true + }); + + var columnDefs = this.completeColumnDefs(); + var data = this.createDialog(columnDefs, this.language.edit.title, this.language.edit.button, + this.language.modalClose, 'editRowBtn', 'altEditor-edit-form'); + + var selector = this.modal_selector; + + for (var j in columnDefs) { + if (columnDefs[j].name != null) { + var arrIndex = columnDefs[j].name.toString().split("."); + var selectedValue = adata.data()[0]; + for (var index = 0; index < arrIndex.length; index++) { + selectedValue = selectedValue[arrIndex[index]]; + } + var jquerySelector = "#" + columnDefs[j].name.toString().replace(/\./g, "\\."); + $(selector).find(jquerySelector).val(selectedValue); // this._quoteattr or not? see #121 + $(selector).find(jquerySelector).trigger("change"); // required by select2 + //added checkbox + if (columnDefs[j].type.indexOf("checkbox") >= 0) { + if (this._quoteattr(selectedValue) === "true") { + $(selector).find(jquerySelector).prop("checked", this._quoteattr(selectedValue)); // required by checkbox + } + } + //added date + if (columnDefs[j].type.indexOf("date") >= 0) { + if (columnDefs[j].dateFormat !== "") { + var mDate = moment(this._quoteattr(selectedValue)); + if (mDate && mDate.isValid()) { + $(selector).find(jquerySelector).val(mDate.format(columnDefs[j].dateFormat)); + } + } + } + } + } + + $(selector + ' input[0]').focus(); + $(selector).trigger("alteditor:some_dialog_opened").trigger("alteditor:edit_dialog_opened"); + }, + + /** + * Callback for "Edit" button + */ + _editRowData: function () { + var that = this; + var dt = this.s.dt; + + // Complete new row data + var rowDataArray = {}; + + var adata = dt.rows({ + selected: true + }); + + // Getting the inputs from the edit-modal + $('form[name="altEditor-edit-form-' + this.random_id + '"] *').filter(':input[type!="file"]').each(function (i) { + rowDataArray[$(this).attr('id')] = $(this).val(); + }); + + //Getting the textArea from the modal + $('form[name="altEditor-edit-form-' + this.random_id + '"] *').filter('textarea').each(function (i) { + rowDataArray[$(this).attr('id')] = $(this).val(); + }); + + //Getting Files from the modal + var numFilesQueued = 0; + $('form[name="altEditor-edit-form-' + this.random_id + '"] *').filter(':input[type="file"]').each(function (i) { + if ($(this).prop('files')[0]) { + ++numFilesQueued; + that.getBase64($(this).prop('files')[0], function(filecontent) { + rowDataArray[$(this).attr('id')] = filecontent; + --numFilesQueued; + }); + } + }); + + // Getting the checkbox from the modal + $('form[name="altEditor-edit-form-' + this.random_id + '"] *').filter(':input[type="checkbox"]').each(function (i) { + rowDataArray[$(this).attr('id')] = this.checked; + }); + + console.log(rowDataArray); //DEBUG + + var checkFilesQueued = function() { + if (numFilesQueued == 0) { + that.onEditRow(that, + rowDataArray, + function(data,b,c,d,e){ that._editRowCallback(rowDataArray,b,c,d,e); }, + function(data){ that._errorCallback(data); + }); + } else { + console.log("Waiting for file base64-decoding..."); + setTimeout(checkFilesQueued, 1000); + } + }; + + checkFilesQueued(); + }, + + /** + * Open Delete Modal for selected row + * + * @private + */ + _openDeleteModal: function () { + + var that = this; + var dt = this.s.dt; + var adata = dt.rows({ + selected: true + }); + var columnDefs = this.completeColumnDefs(); + var formName = 'altEditor-delete-form-' + this.random_id; + + // TODO + // we should use createDialog() + // var data = this.createDialog(columnDefs, this.language.delete.title, this.language.delete.button, + // this.language.modalClose, 'deleteRowBtn', 'altEditor-delete-form'); + + // Building delete-modal + var data = ""; + + for (var j in columnDefs) { + if (columnDefs[j].type.indexOf("hidden") >= 0) { + data += ""; + } + else if (columnDefs[j].type.indexOf("file") < 0) { + var arrIndex = columnDefs[j].name.toString().split(".") + var fvalue = adata.data()[0]; + for (var index = 0; index < arrIndex.length; index++) { + fvalue = fvalue[arrIndex[index]]; + } + + //added dateFormat + if (columnDefs[j].type.indexOf("date") >= 0) { + if (columnDefs[j].dateFormat !== "") { + var mDate = moment(adata.data()[0][columnDefs[j].name]); + if (mDate && mDate.isValid()) { + fvalue = mDate.format(columnDefs[j].dateFormat); + } + } + } + + data += "
" + + fvalue + + "
"; + } + } + + var selector = this.modal_selector; + $(selector).on('show.bs.modal', function () { + var btns = '' + + ''; + $(selector).find('.modal-title').html(that.language.delete.title); + $(selector).find('.modal-body').html(data); + $(selector).find('.modal-footer').html(btns); + var modalContent = $(selector).find('.modal-content'); + if (modalContent.parent().is('form')) { + modalContent.parent().attr('name', formName); + modalContent.parent().attr('id', formName); + } else { + modalContent.wrap("
"); + } + }); + + $(selector).modal('show'); + $(selector + ' input[0]').focus(); + $(selector).trigger("alteditor:some_dialog_opened").trigger("alteditor:delete_dialog_opened"); + }, + + /** + * Callback for "Delete" button + */ + _deleteRow: function () { + var that = this; + var dt = this.s.dt; + + var jsonDataArray = {}; + + var adata = dt.rows({ + selected: true + }); + + // Getting the IDs and Values of the tablerow + for (var i = 0; i < dt.context[0].aoColumns.length; i++) { + // .data is the attribute name, if any; .idx is the column index, so it should always exists + var name = dt.context[0].aoColumns[i].data ? dt.context[0].aoColumns[i].data : + dt.context[0].aoColumns[i].mData ? dt.context[0].aoColumns[i].mData : + dt.context[0].aoColumns[i].idx; + jsonDataArray[name] = adata.data()[0][name]; + } + that.onDeleteRow(that, + jsonDataArray, + function(data){ that._deleteRowCallback(data); }, + function(data){ that._errorCallback(data); + }); + }, + + /** + * Open Add Modal for selected row + * + * @private + */ + _openAddModal: function () { + var dt = this.s.dt; + var columnDefs = this.completeColumnDefs(); + var data = this.createDialog(columnDefs, this.language.add.title, this.language.add.button, + this.language.modalClose, 'addRowBtn', 'altEditor-add-form'); + + var selector = this.modal_selector; + $(selector + ' input[0]').focus(); + $(selector).trigger("alteditor:some_dialog_opened").trigger("alteditor:add_dialog_opened"); + }, + + /** + * Complete DataTable.context[0].aoColumns with default values + */ + completeColumnDefs: function () { + var columnDefs = []; + var dt = this.s.dt; + for (var i in dt.context[0].aoColumns) { + var obj = dt.context[0].aoColumns[i]; + columnDefs[i] = { + title: obj.sTitle, + name: (obj.data ? obj.data : obj.mData), + type: (obj.type ? obj.type : 'text'), + rows: (obj.rows ? obj.rows : '5'), + cols: (obj.cols ? obj.cols : '30'), + options: (obj.options ? obj.options : []), + readonly: (obj.readonly ? obj.readonly : false), + disabled: (obj.disabled ? obj.disabled : false), + required: (obj.required ? obj.required : false), + msg: (obj.errorMsg ? obj.errorMsg : ''), // FIXME no more used + hoverMsg: (obj.hoverMsg ? obj.hoverMsg : ''), + pattern: (obj.pattern ? obj.pattern : '.*'), + special: (obj.special ? obj.special : ''), + unique: (obj.unique ? obj.unique : false), + uniqueMsg: (obj.uniqueMsg ? obj.uniqueMsg : ''), // FIXME no more used + maxLength: (obj.maxLength ? obj.maxLength : false), + multiple: (obj.multiple ? obj.multiple : false), + select2: (obj.select2 ? obj.select2 : false), + datepicker: (obj.datepicker ? obj.datepicker : false), + datetimepicker: (obj.datetimepicker ? obj.datetimepicker : false), + editorOnChange: (obj.editorOnChange ? obj.editorOnChange : null), + style: (obj.style ? obj.style : ''), + dateFormat: (obj.dateFormat ? obj.dateFormat : ''), + optionsSortByLabel: (obj.optionsSortByLabel ? obj.optionsSortByLabel : false) + } + } + return columnDefs; + }, + + /** + * Create both Edit and Add dialogs + * @param columnDefs as returned by completeColumnDefs() + */ + createDialog: function(columnDefs, title, buttonCaption, closeCaption, buttonClass, formName) { + formName = [formName, this.random_id].join('-'); + var data = ""; + for (var j in columnDefs) { + + //handle hidden fields + if (columnDefs[j].type.indexOf("hidden") >= 0) { + data += ""; + } + else { + // handle fields that are visible to the user + data += "
"; + data += "
"; + data += "
"; + data += "
"; + + // Adding readonly-fields + if (columnDefs[j].type.indexOf("readonly") >= 0) { + // type=readonly is deprecated, kept for backward compatibility + data += ""; + } + // Adding select-fields + else if (columnDefs[j].type.indexOf("select") >= 0) { + var options = ""; + var optionsArray = columnDefs[j].options; + if (optionsArray.length > 0) { + // array-style select or select2 + for (var i = 0; i < optionsArray.length; i++) { + options += ""; + } + } else { + // object-style select or select2 + for (var x in optionsArray) { + options += ""; + } + } + data += ""; + } + //Adding Text Area + else if (columnDefs[j].type.indexOf("textarea") >= 0) + { + data += ""; + } + // Adding text-inputs and errorlabels, but also new HTML5 typees (email, color, ...) + else { + data += ""; + } + data += ""; + data += "
"; + } + } + // data += ""; + + var selector = this.modal_selector; + $(selector).on('show.bs.modal', function () { + var btns = '' + + ''; + $(selector).find('.modal-title').html(title); + $(selector).find('.modal-body').html(data); + $(selector).find('.modal-footer').html(btns); + var modalContent = $(selector).find('.modal-content'); + if (modalContent.parent().is('form')) { + modalContent.parent().attr('name', formName); + modalContent.parent().attr('id', formName); + } else { + modalContent.wrap("
"); + } + }); + + $(selector).modal('show'); + $(selector + ' input[0]').focus(); + + var that = this; + + // enable select 2 items, datepicker, datetimepickerm + for (var j in columnDefs) { + if (columnDefs[j].select2) { + // Require select2 plugin + $(selector).find("select#" + columnDefs[j].name).select2(columnDefs[j].select2); + } else if (columnDefs[j].datepicker) { + // Require jquery-ui + $(selector).find("#" + columnDefs[j].name).datepicker(columnDefs[j].datepicker); + } else if (columnDefs[j].datetimepicker) { + // Require datetimepicker plugin + $(selector).find("#" + columnDefs[j].name).datetimepicker(columnDefs[j].datetimepicker); + } + // custom onchange triggers + if (columnDefs[j].editorOnChange) { + $(selector).find("#" + $.escapeSelector(columnDefs[j].name)).attr('alt-editor-id', this._quoteattr(j)); + $(selector).find("#" + columnDefs[j].name).on('change', function(elm) { + var f = columnDefs[$(this).attr('alt-editor-id')].editorOnChange; + f(elm, that); + }); + } + //added select sort + if (columnDefs[j].type.indexOf("select") >= 0) { + if (columnDefs[j].optionsSortByLabel) { + var jquerySelector = "#" + columnDefs[j].name.toString().replace(/\./g, "\\."); + var opts_list = $(selector).find(jquerySelector).find('option'); + opts_list.sort(function (a, b) { return $(a).text() > $(b).text() ? 1 : -1; }); + $(selector).find(jquerySelector).html('').append(opts_list); + $(selector).find(jquerySelector).val($(jquerySelector + " option:first").val()); + } + } + } + }, + + /** + * Callback for "Add" button + */ + _addRowData: function () { + var that = this; + var dt = this.s.dt; + + var rowDataArray = {}; + + // Getting the inputs from the modal + $('form[name="altEditor-add-form-' + this.random_id + '"] *').filter(':input[type!="file"]').each(function (i) { + rowDataArray[$(this).attr('id')] = $(this).val(); + }); + + //Getting the textArea from the modal + $('form[name="altEditor-add-form-' + this.random_id + '"] *').filter('textarea').each(function (i) { + rowDataArray[$(this).attr('id')] = $(this).val(); + }); + + //Getting Files from the modal + var numFilesQueued = 0; + $('form[name="altEditor-add-form-' + this.random_id + '"] *').filter(':input[type="file"]').each(function (i) { + if ($(this).prop('files')[0]) { + ++numFilesQueued; + that.getBase64($(this).prop('files')[0], function(filecontent) { + rowDataArray[$(this).attr('id')] = filecontent; + --numFilesQueued; + }); + } + }); + + // Getting the checkbox from the modal + $('form[name="altEditor-add-form-' + this.random_id + '"] *').filter(':input[type="checkbox"]').each(function (i) { + rowDataArray[$(this).attr('id')] = this.checked; + }); + + console.log(rowDataArray); //DEBUG + + var checkFilesQueued = function() { + if (numFilesQueued == 0) { + that.onAddRow(that, + rowDataArray, + function(data){ that._addRowCallback(data); }, + function(data){ that._errorCallback(data); + }); + } else { + console.log("Waiting for file base64-decoding..."); + setTimeout(checkFilesQueued, 1000); + } + }; + + checkFilesQueued(); + + }, + + /** + * Called after a row has been deleted on server + */ + _deleteRowCallback: function (response, status, more) { + var selector = this.modal_selector; + $(selector + ' .modal-body .alert').remove(); + + var message = ''; + $(selector + ' .modal-body').append(message); + + this.s.dt.row({ + selected : true + }).remove(); + this.s.dt.draw('page'); + + // Disabling submit button + $("div"+selector).find("button#addRowBtn").prop('disabled', true); + $("div"+selector).find("button#editRowBtn").prop('disabled', true); + $("div"+selector).find("button#deleteRowBtn").prop('disabled', true); + }, + + /** + * Called after a row has been inserted on server + */ + _addRowCallback: function (response, status, more) { + + //TODO should honor dt.ajax().dataSrc + + var data = (typeof response === "string") ? JSON.parse(response) : response; + var selector = this.modal_selector; + $(selector + ' .modal-body .alert').remove(); + + var message = ''; + $(selector + ' .modal-body').append(message); + + this.s.dt.row.add(data).draw(false); + + // Disabling submit button + $("div" + selector).find("button#addRowBtn").prop('disabled', true); + $("div" + selector).find("button#editRowBtn").prop('disabled', true); + $("div" + selector).find("button#deleteRowBtn").prop('disabled', true); + }, + + /** + * Called after a row has been updated on server + */ + _editRowCallback: function (response, status, more) { + + //TODO should honor dt.ajax().dataSrc + + var data = (typeof response === "string") ? JSON.parse(response) : response; + var selector = this.modal_selector; + $(selector + ' .modal-body .alert').remove(); + + var message = ''; + $(selector + ' .modal-body').append(message); + + this.s.dt.row({ + selected : true + }).data(data); + this.s.dt.draw('page'); + + // Disabling submit button + $("div" + selector).find("button#addRowBtn").prop('disabled', true); + $("div" + selector).find("button#editRowBtn").prop('disabled', true); + $("div" + selector).find("button#deleteRowBtn").prop('disabled', true); + }, + + /** + * Called after AJAX server returned an error + */ + _errorCallback: function (response, status, more) { + var error = response; + var selector = this.modal_selector; + $(selector + ' .modal-body .alert').remove(); + var errstr = this.language.error.message; + if (error.responseJSON && error.responseJSON.errors) { + errstr = ""; + for (var key in error.responseJSON.errors) { + errstr += error.responseJSON.errors[key][0]; + } + } + var message = ''; + + $(selector + ' .modal-body').append(message); + }, + + /** + * Default callback for insertion: mock webservice, always success. + */ + onAddRow: function(dt, rowdata, success, error) { + console.log("Missing AJAX configuration for INSERT"); + success(rowdata); + }, + + /** + * Default callback for editing: mock webservice, always success. + */ + onEditRow: function(dt, rowdata, success, error) { + console.log("Missing AJAX configuration for UPDATE"); + success(rowdata); + }, + + /** + * Default callback for deletion: mock webservice, always success. + */ + onDeleteRow: function(dt, rowdata, success, error) { + console.log("Missing AJAX configuration for DELETE"); + success(rowdata); + }, + + /** + * Dinamically reload options in SELECT menu + */ + reloadOptions: function($select, options) { + var oldValue = $select.val(); + $select.empty(); // remove old options + if (options.length > 0) { + // array-style select or select2 + $.each(options, function(key, value) { + $select.append($("") + .attr("value", value).text(value)); + }); + } else { + // object-style select or select2 + $.each(options, function(key, value) { + $select.append($("") + .attr("value", value).text(key)); + }); + } + $select.val(oldValue); // if still present, of course + $select.trigger('change'); + }, + + /** + * Convert file to Base 64 form + * @see https://stackoverflow.com/questions/36280818 + */ + getBase64: function(file, onSuccess, onError) { + var reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = function () { + console.log(reader.result); + if (onSuccess) onSuccess(reader.result); + }; + reader.onerror = function (error) { + console.log('Error: ', error); + if (onError) onError(error); + }; + }, + + /** + * Sanitizes input for use in HTML + * @param s + * @param preserveCR + * @returns {string} + * @private + */ + _quoteattr: function (s, preserveCR) { + if (s == null) + return ""; + preserveCR = preserveCR ? ' ' : '\n'; + if (Array.isArray(s)) { + // for MULTIPLE SELECT + var newArray = []; + var x; + for (x in s) newArray.push(s[x]); + return newArray; + } + return ('' + s) /* Forces the conversion to string. */ + .replace(/&/g, '&') /* This MUST be the 1st replacement. */ + .replace(/'/g, ''') /* The 4 other predefined entities, required. */ + .replace(/"/g, '"') + .replace(//g, '>') + .replace(/\r\n/g, preserveCR) /* Must be before the next replacement. */ + .replace(/[\r\n]/g, preserveCR); + }, + }); + + /** + * altEditor version + * + * @static + * @type String + */ + altEditor.version = '2.0'; + + /** + * altEditor defaults + * + * @namespace + */ + altEditor.defaults = { + /** + * @type {Boolean} Ask user what they want to do, even for a single + * option + */ + alwaysAsk: false, + + /** @type {string|null} What will trigger a focus */ + focus: null, // focus, click, hover + + /** @type {column-selector} Columns to provide auto fill for */ + columns: '', // all + + /** @type {boolean|null} Update the cells after a drag */ + update: null, // false is editor given, true otherwise + + /** @type {DataTable.Editor} Editor instance for automatic submission */ + editor: null + }; + + /** + * Classes used by altEditor that are configurable + * + * @namespace + */ + altEditor.classes = { + /** @type {String} Class used by the selection button */ + btn: 'btn' + }; + + // Attach a listener to the document which listens for DataTables + // initialisation + // events so we can automatically initialise + $(document).on('preInit.dt.altEditor', function (e, settings, json) { + if (e.namespace !== 'dt') { + return; + } + + var init = settings.oInit.altEditor; + var defaults = DataTable.defaults.altEditor; + + if (init || defaults) { + var opts = $.extend({}, init, defaults); + + if (init !== false) { + + var editor = new altEditor(settings, opts); + // e is a jQuery event object + // e.target is the underlying jQuery object, e.g. $('#mytable') + // so that you can retrieve the altEditor object later + e.target.altEditor = editor; + } + } + }); + + // Alias for access + DataTable.altEditor = altEditor; + return altEditor; +}); diff --git a/static/js/dataTables.altEditor.free.min.js b/static/js/dataTables.altEditor.free.min.js new file mode 100644 index 0000000..e69de29 diff --git a/templates/contractor.html b/templates/contractor.html new file mode 100644 index 0000000..83550b8 --- /dev/null +++ b/templates/contractor.html @@ -0,0 +1,145 @@ + + + + + + + DataTable-AltEditor - Example #3 + + + + + + + + + + +


+

DataTable-AltEditor - Example #3

+

with object-based datatable rows loaded via Ajax

+Rows are retrieved from some AJAX webservice, in the form: +{id:1, name:"Tiger Nixon", position:"System Architect", office:"Edinburgh", extension:"5421", startDate:"2011/04/25", salary:"Tiger Nixon"}
+INSERT and UPDATE will fail because there is no real server set up here. +
+ + + +
+
+ + + + + + + + + + + + + diff --git a/templates/layout.html b/templates/layout.html index 073df85..c0d5126 100644 --- a/templates/layout.html +++ b/templates/layout.html @@ -30,6 +30,7 @@