/*! DataTables Editor v2.0.9 * * ©2012-2022 SpryMedia Ltd, all rights reserved. * License: editor.datatables.net/license */ (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 ) { // CommonJS environments without a window global must pass a // root. This will give an error otherwise root = window; } if ( ! $ ) { $ = typeof window !== 'undefined' ? // jQuery's factory checks for a global window require('jquery') : require('jquery')( root ); } 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 formOptions = { buttons: true, drawType: false, focus: 0, message: true, nest: false, onBackground: 'blur', onBlur: 'close', onComplete: 'close', onEsc: 'close', onFieldError: 'focus', onReturn: 'submit', scope: 'row', submit: 'all', submitHtml: '▶', submitTrigger: null, title: true }; var defaults$1 = { /** * Parameter name to use to submit data to the server. * * @type string */ actionName: 'action', /** * Control how the Ajax call to update data on the server. * * This option matches the `dt-init ajax` option in that is can be provided * in one of three different ways: * * * string - As a string, the value given is used as the url to target * the Ajax request to, using the default Editor Ajax options. Note that * for backwards compatibility you can use the form "METHOD URL" - for * example: `"PUT api/users"`, although it is recommended you use the * object form described below. * * object - As an object, the `ajax` property has two forms: * * Used to extend and override the default Ajax options that Editor * uses. This can be very useful for adding extra data for example, or * changing the HTTP request type. * * With `create`, `edit` and `remove` properties, Editor will use the * option for the action that it is taking, which can be useful for * REST style interfaces. The value of each property can be a string, * object or function, using exactly the same options as the main `ajax` * option. All three options must be defined if this form is to be used. * * function - As a function this gives complete control over the method * used to update the server (if indeed a server is being used!). For * example, you could use a different data store such as localStorage, * Firebase or route the data through a web-socket. * * @example * // As a string - all actions are submitted to this URI as POST requests * $(document).ready(function() { * var editor = new $.fn.Editor( { * "ajax": 'php/index.php', * "table": "#example" * } ); * } ); * * @example * // As an object - using GET rather than POST * $(document).ready(function() { * var editor = new $.fn.Editor( { * "ajax": { * "type": 'GET', * "url": 'php/index.php * }, * "table": "#example" * } ); * } ); * * @example * // As an object - each action is submitted to a different URI as POST requests * $(document).ready(function() { * var editor = new $.fn.Editor( { * "ajax": { * "create": "/rest/user/create", * "edit": "/rest/user/_id_/edit", * "remove": "/rest/user/_id_/delete" * }, * "table": "#example" * } ); * } ); * * @example * // As an object - with different HTTP methods for each action * $(document).ready(function() { * var editor = new $.fn.Editor( { * "ajax": { * "create": { * type: 'POST', * url: '/rest/user/create' * }, * "edit": { * type: 'PUT', * url: '/rest/user/edit/_id_' * }, * "remove": { * type: 'DELETE', * url: '/rest/user/delete' * } * }, * "table": "#example" * } ); * } ); * * // As a function - Making a custom `$.ajax` call * $(document).ready(function() { * var editor = new $.fn.Editor( { * "table": "#example", * "ajax": function ( method, url, data, successCallback, errorCallback ) { * $.ajax( { * "type": method, * "url": url, * "data": data, * "dataType": "json", * "success": function (json) { * successCallback( json ); * }, * "error": function (xhr, error, thrown) { * errorCallback( xhr, error, thrown ); * } * } ); * } * } ); * } ); */ ajax: null, /** * The display controller for the form. The form itself is just a collection of * DOM elements which require a display container. This display controller allows * the visual appearance of the form to be significantly altered without major * alterations to the Editor code. There are two display controllers built into * Editor *lightbox* and *envelope*. The value of this property will * be used to access the display controller defined in {@link Editor.display} * for the given name. Additional display controllers can be added by adding objects * to that object, through extending the displayController model: * {@link Editor.models.displayController}. * * @type string * @default lightbox * * @example * $(document).ready(function() { * var editor = new $.fn.Editor( { * "ajax": "php/index.php", * "table": "#example", * "display": 'envelope' * } ); * } ); */ display: 'lightbox', /** * Events / callbacks - event handlers can be assigned as an individual function * during initialisation using the parameters in this name space. The names, and * the parameters passed to each callback match their event equivalent in the * {@link Editor} object. * * @namespace * @deprecated Since 1.3. Use the `on()` API method instead. Note that events * passed in do still operate as they did in 1.2- but are no longer * individually documented. */ events: {}, /** * Fields to initialise the form with - see {@link Editor.models.field} for * a full list of the options available to each field. Note that if fields are not * added to the form at initialisation time using this option, they can be added using * the {@link Editor#add} API method. * * @type array * @default [] * * @example * $(document).ready(function() { * var editor = new $.fn.Editor( { * "ajax": "php/index.php", * "table": "#example", * "fields": [ { * "label": "User name:", * "name": "username" * } * // More fields would typically be added here! * } ] * } ); * } ); */ fields: [], formOptions: { bubble: $.extend({}, formOptions, { buttons: '_basic', message: false, submit: 'changed', title: false }), inline: $.extend({}, formOptions, { buttons: false, submit: 'changed' }), main: $.extend({}, formOptions) }, /** * Internationalisation options for Editor. All client-side strings that the * end user can see in the interface presented by Editor can be modified here. * * You may also wish to refer to the * DataTables internationalisation options to provide a fully language * customised table interface. * * @namespace * * @example * // Set the 'create' button text. All other strings used are the * // default values. * var editor = new $.fn.Editor( { * "ajax": "data/source", * "table": "#example", * "i18n": { * "create": { * "button": "New user" * } * } * } ); * * @example * // Set the submit text for all three actions * var editor = new $.fn.Editor( { * "ajax": "data/source", * "table": "#example", * "i18n": { * "create": { * "submit": "Create new user" * }, * "edit": { * "submit": "Update user" * }, * "remove": { * "submit": "Remove user" * } * } * } ); */ i18n: { /** * Close button title text * * @type string * @default Close */ close: 'Close', /** * Strings used when working with the Editor 'create' action (creating new * records). * * @namespace */ create: { /** * Buttons button text * * @type string * @default New */ button: 'New', /** * Submit button text * * @type string * @default Create */ submit: 'Create', /** * Display container title (when showing the editor display) * * @type string * @default Create new entry */ title: 'Create new entry' }, datetime: { amPm: ['am', 'pm'], hours: 'Hour', minutes: 'Minute', months: [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ], next: 'Next', previous: 'Previous', seconds: 'Second', unknown: '-', weekdays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], }, /** * Strings used when working with the Editor 'edit' action (editing existing * records). * * @namespace */ edit: { /** * Buttons button text * * @type string * @default Edit */ button: 'Edit', /** * Submit button text * * @type string * @default Update */ submit: 'Update', /** * Display container title (when showing the editor display) * * @type string * @default Edit entry */ title: 'Edit entry' }, /** * Strings used for error conditions. * * @namespace */ error: { /** * Generic server error message * * @type string * @default * A system error has occurred (More information) */ system: 'A system error has occurred (More information).' }, /** * Strings used for multi-value editing * * @namespace */ multi: { /** * Shown below the multi title text, although only the first * instance of this text is shown in the form to reduce redundancy */ info: 'The selected items contain different values for this input. To edit and set all items for this input to the same value, click or tap here, otherwise they will retain their individual values.', /** * Disabled for multi-row editing */ noMulti: 'This input can be edited individually, but not part of a group.', /** * Shown below the field input when group editing a value to allow * the user to return to the original multiple values */ restore: 'Undo changes', /** * Shown in place of the field value when a field has multiple values */ title: 'Multiple values' }, /** * Strings used when working with the Editor 'delete' action (deleting * existing records). * * @namespace */ remove: { /** * Buttons button text * * @type string * @default Delete */ button: 'Delete', /** * Deletion confirmation message. * * As Editor has the ability to delete either a single or multiple rows * at a time, this option can be given as either a string (which will be * used regardless of how many records are selected) or as an object * where the property "_" will be used (with %d substituted for the number * of records to be deleted) as the delete message, unless there is a * key with the number of records to be deleted. This allows Editor * to consider the different pluralisation characteristics of different * languages. * * @type object|string * @default Are you sure you wish to delete %d rows? * * @example * // String - no plural consideration * var editor = new $.fn.Editor( { * "ajax": "data/source", * "table": "#example", * "i18n": { * "remove": { * "confirm": "Are you sure you wish to delete %d record(s)?" * } * } * } ); * * @example * // Basic 1 (singular) or _ (plural) * var editor = new $.fn.Editor( { * "ajax": "data/source", * "table": "#example", * "i18n": { * "remove": { * "confirm": { * "_": "Confirm deletion of %d records.", * "1": "Confirm deletion of record." * } * } * } ); * * @example * // Singular, dual and plural * var editor = new $.fn.Editor( { * "ajax": "data/source", * "table": "#example", * "i18n": { * "remove": { * "confirm": { * "_": "Confirm deletion of %d records.", * "1": "Confirm deletion of record.", * "2": "Confirm deletion of both record." * } * } * } ); * */ confirm: { 1: 'Are you sure you wish to delete 1 row?', _: 'Are you sure you wish to delete %d rows?' }, /** * Submit button text * * @type string * @default Delete */ submit: 'Delete', /** * Display container title (when showing the editor display) * * @type string * @default Delete */ title: 'Delete', } }, /** * JSON property from which to read / write the row's ID property (i.e. its * unique column index that identifies the row to the database). By default * Editor will use the `DT_RowId` property from the data source object * (DataTable's magic property to set the DOM id for the row). * * If you want to read a parameter from the data source object instead of * using `DT_RowId`, set this option to the property name to use. * * Like other data source options the `srcId` option can be given in dotted * object notation to read nested objects. * * @type null|string * @default DT_RowId * * @example * // Using a data source such as: * // { "id":12, "browser":"Chrome", ... } * $(document).ready(function() { * var editor = new $.fn.Editor( { * "ajax": "php/index.php", * "table": "#example", * "idSrc": "id" * } ); * } ); */ idSrc: 'DT_RowId', /** * jQuery selector that can be used to identify the table you wish to apply * this editor instance to. * * In previous versions of Editor (1.2 and earlier), this parameter was * called `table`. The name has been altered in 1.3+ to simplify the * initialisation. This is a backwards compatible change - if you pass in * a `table` option it will be used. * * @type string * @default Empty string * * @example * $(document).ready(function() { * var editor = new $.fn.Editor( { * "ajax": "php/index.php", * "table": "#example" * } ); * } ); */ table: null, }; var settings = { action: null, actionName: 'action', ajax: null, bubbleNodes: [], closeCb: null, closeIcb: null, dataSource: null, displayController: null, displayed: false, editCount: 0, editData: {}, editFields: {}, editOpts: {}, fields: {}, formOptions: { bubble: $.extend({}, formOptions), inline: $.extend({}, formOptions), main: $.extend({}, formOptions), }, globalError: '', id: -1, idSrc: null, includeFields: [], mode: null, modifier: null, opts: null, order: [], processing: false, setFocus: null, table: null, template: null, unique: 0 }; var DataTable$6 = $.fn.dataTable; var DtInternalApi = DataTable$6.ext.oApi; function objectKeys(o) { var out = []; for (var key in o) { if (o.hasOwnProperty(key)) { out.push(key); } } return out; } function el(tag, ctx) { if (ctx === undefined) { ctx = document; } return $('*[data-dte-e="' + tag + '"]', ctx); } function safeDomId(id, prefix) { if (prefix === void 0) { prefix = '#'; } return typeof id === 'string' ? prefix + id.replace(/\./g, '-') : prefix + id; } function safeQueryId(id, prefix) { if (prefix === void 0) { prefix = '#'; } return typeof id === 'string' ? prefix + id.replace(/(:|\.|\[|\]|,)/g, '\\$1') : prefix + id; } function dataGet(src) { return DtInternalApi._fnGetObjectDataFn(src); } function dataSet(src) { return DtInternalApi._fnSetObjectDataFn(src); } var extend = DtInternalApi._fnExtend; function pluck(a, prop) { var out = []; $.each(a, function (idx, elIn) { out.push(elIn[prop]); }); return out; } /** * Compare parameters for difference - diving into arrays and objects if * needed, allowing the object reference to be different, but the contents to * match. * * Please note that LOOSE type checking is used */ function deepCompare(o1, o2) { if (typeof o1 !== 'object' || typeof o2 !== 'object') { return o1 == o2; } var o1Props = objectKeys(o1); var o2Props = objectKeys(o2); if (o1Props.length !== o2Props.length) { return false; } for (var i = 0, ien = o1Props.length; i < ien; i++) { var propName = o1Props[i]; if (typeof o1[propName] === 'object') { if (!deepCompare(o1[propName], o2[propName])) { return false; } } else if (o1[propName] != o2[propName]) { return false; } } return true; } /* - - - - - - - - - - * DataTables editor interface */ var _dtIsSsp = function (dt, editor) { // If the draw type is `none`, then we still need to use the DT API to // update the display with the new data return dt.settings()[0].oFeatures.bServerSide && editor.s.editOpts.drawType !== 'none'; }; var _dtApi = function (table) { return table instanceof $.fn.dataTable.Api ? table : $(table).DataTable(); }; var _dtHighlight = function (node) { // Highlight a row using CSS transitions. The timeouts need to match the // transition duration from the CSS node = $(node); setTimeout(function () { node.addClass('highlight'); setTimeout(function () { node .addClass('noHighlight') .removeClass('highlight'); setTimeout(function () { node.removeClass('noHighlight'); }, 550); }, 500); }, 20); }; var _dtRowSelector = function (out, dt, identifier, fields, idFn) { dt.rows(identifier).indexes().each(function (idx) { var row = dt.row(idx); var data = row.data(); var idSrc = idFn(data); if (idSrc === undefined) { Editor.error('Unable to find row identifier', 14); } out[idSrc] = { data: data, fields: fields, idSrc: idSrc, node: row.node(), type: 'row' }; }); }; var _dtFieldsFromIdx = function (dt, fields, idx, ignoreUnknown) { var col = dt.settings()[0].aoColumns[idx]; var dataSrc = col.editField !== undefined ? col.editField : col.mData; var resolvedFields = {}; var run = function (field, dataSrcIn) { if (field.name() === dataSrcIn) { resolvedFields[field.name()] = field; } }; $.each(fields, function (name, fieldInst) { if (Array.isArray(dataSrc)) { for (var _i = 0, dataSrc_1 = dataSrc; _i < dataSrc_1.length; _i++) { var data = dataSrc_1[_i]; run(fieldInst, data); } } else { run(fieldInst, dataSrc); } }); if ($.isEmptyObject(resolvedFields) && !ignoreUnknown) { Editor.error('Unable to automatically determine field from source. Please specify the field name.', 11); } return resolvedFields; }; var _dtCellSelector = function (out, dt, identifier, allFields, idFn, forceFields) { if (forceFields === void 0) { forceFields = null; } var cells = dt.cells(identifier); cells.indexes().each(function (idx) { var cell = dt.cell(idx); var row = dt.row(idx.row); var data = row.data(); var idSrc = idFn(data); var fields = forceFields || _dtFieldsFromIdx(dt, allFields, idx.column, cells.count() > 1); var isNode = (typeof identifier === 'object' && identifier.nodeName) || identifier instanceof $; var prevDisplayFields; var prevAttach; var prevAttachFields; // Only add if a field was found to edit if (Object.keys(fields).length) { // The row selector will create a new `out` object for the identifier, and the // cell selector might be called multiple times for a row, so we need to save // our specific items if (out[idSrc]) { prevAttach = out[idSrc].attach; prevAttachFields = out[idSrc].attachFields; prevDisplayFields = out[idSrc].displayFields; } // Use the row selector to get the row information _dtRowSelector(out, dt, idx.row, allFields, idFn); out[idSrc].attachFields = prevAttachFields || []; out[idSrc].attachFields.push(Object.keys(fields)); out[idSrc].attach = prevAttach || []; out[idSrc].attach.push(isNode ? $(identifier).get(0) : cell.fixedNode ? // If its under a fixed column, get the floating node cell.fixedNode() : cell.node()); out[idSrc].displayFields = prevDisplayFields || {}; $.extend(out[idSrc].displayFields, fields); } }); }; var _dtColumnSelector = function (out, dt, identifier, fields, idFn) { dt.cells(null, identifier).indexes().each(function (idx) { _dtCellSelector(out, dt, idx, fields, idFn); }); }; var dataSource$1 = { commit: function (action, identifier, data, store) { // Updates complete - redraw var that = this; var dt = _dtApi(this.s.table); var ssp = dt.settings()[0].oFeatures.bServerSide; var ids = store.rowIds; // On edit, if there are any rows left in the `store.rowIds`, then they // were not returned by the server and should be removed (they might not // meet filtering requirements any more for example) if (!_dtIsSsp(dt, this) && action === 'edit' && store.rowIds.length) { var row = void 0; var compare = function (id) { return function (rowIdx, rowData, rowNode) { return id == dataSource$1.id.call(that, rowData); }; }; for (var i = 0, ien = ids.length; i < ien; i++) { // Find the row to edit - attempt to do an id look up first for speed try { row = dt.row(safeQueryId(ids[i])); } catch (e) { row = dt; } // If not found, then we need to do it the slow way if (!row.any()) { row = dt.row(compare(ids[i])); } if (row.any() && !ssp) { row.remove(); } } } var drawType = this.s.editOpts.drawType; if (drawType !== 'none') { var dtAny = dt; // SSP highlighting has to go after the draw, but this can't be // merged with client-side processing highlight as we want that // to work even when there isn't a draw happening. if (ssp && ids && ids.length) { dt.one('draw', function () { for (var i = 0, ien = ids.length; i < ien; i++) { var row = dt.row(safeQueryId(ids[i])); if (row.any()) { _dtHighlight(row.node()); } } }); } dt.draw(drawType); // Responsive needs to take account of new data column widths if (dtAny.responsive) { dtAny.responsive.recalc(); } // Rebuild searchpanes if (typeof dtAny.searchPanes === 'function' && !ssp) { dtAny.searchPanes.rebuildPane(undefined, true); } // Rebuild searchbuilder if (dtAny.searchBuilder !== undefined && typeof dtAny.searchBuilder.rebuild === 'function' && !ssp) { dtAny.searchBuilder.rebuild(dtAny.searchBuilder.getDetails()); } } }, create: function (fields, data) { var dt = _dtApi(this.s.table); if (!_dtIsSsp(dt, this)) { var row = dt.row.add(data); _dtHighlight(row.node()); } }, edit: function (identifier, fields, data, store) { var that = this; var dt = _dtApi(this.s.table); // No point in doing anything when server-side processing - the commit // will redraw the table if (!_dtIsSsp(dt, this) || this.s.editOpts.drawType === 'none') { // The identifier can select one or more rows, but the data will // refer to just a single row. We need to determine which row from // the set is the one to operator on. var rowId_1 = dataSource$1.id.call(this, data); var row = void 0; // Find the row to edit - attempt to do an id look up first for speed try { row = dt.row(safeQueryId(rowId_1)); } catch (e) { row = dt; } // If not found, then we need to do it the slow way if (!row.any()) { row = dt.row(function (rowIdx, rowData, rowNode) { return rowId_1 == dataSource$1.id.call(that, rowData); }); } if (row.any()) { // Merge data to allow for a sub-set to be returned var toSave = extend({}, row.data(), true); toSave = extend(toSave, data, true); row.data(toSave); // Remove the item from the list of indexes now that is has been // updated var idx = $.inArray(rowId_1, store.rowIds); store.rowIds.splice(idx, 1); } else { // If not found, then its a new row (change in pkey possibly) row = dt.row.add(data); } _dtHighlight(row.node()); } }, fakeRow: function (insertPoint) { var dt = _dtApi(this.s.table); var tr = $('