diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..97008e5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +yarn.lock \ No newline at end of file diff --git a/README.md b/README.md index 79e2e93..aaecbd5 100644 --- a/README.md +++ b/README.md @@ -2,127 +2,238 @@ [![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=7LKYWG9LXNQ9C&lc=ES&item_name=Tito%20Hinostroza&item_number=2153&no_note=0&cn=Dar%20instrucciones%20especiales%20al%20vendedor%3a&no_shipping=2¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted) +#### Note from maintainer, I have not changed the above donation link. Please donate to the original author. + +# Release Status + +This is the first "stable" version. Previous versions had an API that was in flux. We're now in the feature release phase of development. + # Bootstable -Javascript library to make HMTL tables editable. -![Bootstable](http://blog.pucp.edu.pe/blog/tito/wp-content/uploads/sites/610/2018/01/Sin-título-13.png "Bootstable") +Tiny class to make bootstrap tables editable. -"Bootstable" is a javascript library (plug-in), that lets convert a HTML static table to a editable table. -A table is made editable, including several buttons to perform the edition actions. +![Bootstable](https://raw.githubusercontent.com/SeraphNet/bootstable-bootstrap5/1.5/bootstable.png "Bootstable") + +"Bootstable" is a javascript class, that converts HTML static tables into an editable table. No database connection is included. The library was designed to work offline, when editing. -Edition options includes: +In order to save to a database, use the onEditSave hooks to call an API to save the data. + +Editing options includes: -* Edit fields. -* Remove rows. -* Add rows. +- Edit Row +- Remove Row +- Add Row ## Dependencies: -* Jquery -* Bootstrap +- Bootstrap +- Bootstrap Icons Bootstrap is necessary to format correctly the controls used, and to draw icons. -It's possible to work without Bootstrap too. In this case style is missing. + +This will work without Bootsrap, however the structures are heavily reliant on CSS classes provided by Bootstrap 5+ and display issues will result. To adjust, modify your CSS/SASS to handle the classes presented. + +Bootstrap Icons is used for glyphs in the buttons. ## Requirements 1. For a table to be editable, it needs to have the following structure: -``` +```html - - - ... - - - - - ... - - + + + + + + ... + + + + + + + + ... + +
``` -2. Bootstable needs the ID of the table to edit, and can only work on a single table. - - $('.mytable').SetEditable(); //BAD! No class reference allowed. - $('table').SetEditable(); //BAD! No several tables allowed. +You can also hide columns and set a "role" for that column. In this case you can maintain an "ID" column for interacting with a database. + +```html + + + + + + + + + + + + + + + +
+``` -If several tables need to be editable in a same Web page, it's needed to set each table: +2. Bootstable needs the ID of the table to edit, and can only work on a single table. - $('#mytable1').SetEditable(); //GOOD! - $('#mytable2').SetEditable(); //GOOD! + const bstable = new bootstable("mytable", options) -LIMITATION: When using several editable tables, events won't work properly. +3. To edit multiple tables on a single page, instantiate the class for each table. ## Examples Sets all the columns of #mytable editable: - $('#mytable').SetEditable(); +```javascript +const bstable = new bootstable("mytable"); +``` Sets the columns 0 and 1 of #mytable editable: - $('#mytable').SetEditable({ - columnsEd: "0,1" //editable columns - }); - -Includes a "New row" button (Obsolete): - - $('#mytable').SetEditable({ - columnsEd: "0,1", - $addButton: $('#but_add') - }); - -Includes a "New row" button (Prefered): +```javascript +const bstable = new bootstable("mytable", { + columnsEd: [0, 1], //editable columns +}); +``` - $('#mytable').SetEditable(); +Includes a "New row" button, this will add as a new button to the table headers: - $('#but_add').click(function() { - rowAddNew('mytable'); - }); +```javascript +const bstable = new bootstable("mytable", { + columnsEd: [0, 1], + $addButton: "buttonId", +}); +``` Set a "New row" button to add a row and set initial values: - $('#mytable').SetEditable(); - - $('#but_add').click(function() { - rowAddNew('mytable', [1,2,3]); - }); +```javascript +const bstable = new bootstable("mytable", { + $addButton: "buttonId", + defaultValues: [1, 2, 3], +}); +``` Set a "New row" button to add a row, set initial values and turn to edit mode: - $('#mytable').SetEditable(); - - $('#but_add').click(function() { - rowAddNewAndEdit('mytable', [1,2,3]); - }); +```javascript +const bstable = new bootstable("mytable", { + $addButton: "buttonId", + defaultValues: [1, 2, 3], + addButtonEdit: true, // Forces bootstable to edit the new row immediately. +}); +``` Parameters: - columnsEd: null, //Index to editable columns. If null, all columns will be editables. Ex.: "1,2,3,4,5" - $addButton: null, //Jquery object of "Add" button. OBSOLETE. - bootstrap: true, //Indicates if library is going to worl with Bootstrap library. - onEdit: function() {}, //Called after edition - onBeforeDelete: function() {}, //Called before deletion - onDelete: function() {}, //Called after deletion - onAdd: function() {} //Called when added a new row +```javascript +const options = { + // Properties + columnsEd: Array(), // Default: null -- Index to editable columns. If null, all columns will be editable. Ex.: [ 0,1,2,3,4,5 ] + $addButton: string, // Default: null -- ID of "Add" button. + defaultValues: Array(), // Default: null -- Set default values, must match the number of editable columns + addButtonEdit: boolean, // Default: false -- Should bootstable edit the rows after adding? + buttons: Object(), // Overide default buttons + exportCsvButton: boolean, Default: false -- add an export to CSV button + exportJsonButton: boolean, Default: false -- add an export to JSON button + + // Callbacks + onEdit: (rowElement) => {}, // Called after clicking edit button + onBeforeDelete: (rowElement) => {}, // Called just before deletion must return a boolean, true means row will be deleted. + onDelete: (rowElement) => {}, // Called after deletion button, but after onBeforeDelete. If onBeforeDelete returns false, bypass. + onAdd: (rowElement) => {} // Called when new row is added to table +}; +``` # Utilities There are two functions, included in the library, to facilitate the export of the table: -* function TableToCSV(tabId, separator) -* function TableToJson(tabId) +```javascript +// Get a CSV string and/or trigger a browser download +const csvString = bstable.TableToCSV( + tableId, + separator, + downloadBool, + filenameStr +); + +// Get a JSON string and/or trigger a browser download +const jsonString = bstable.TableToJson(tableId, downloadBool, filenameStr); +``` -These functions return a string in the appropriate format (CSV or JSON) from any HTML table. +These functions return a string of the data, but can be set to create a file download by setting downloadBool to true and supplying a filename for download. + +# Default Buttons + +In order to self-stylize buttons, pass a replacement object for the button(s) you wish to modify: + +```javascript +const buttons = { + bEdit: { + className: "btn btn-sm btn-primary", + icon: "fa fa-pencil", + display: "block", + onclick: (but) => { + var target = but.target; + if (target.tagName == "I") { + target = but.target.parentNode; + } + this.butRowEdit(target); + }, + }, + bElim: { + className: "btn btn-sm btn-danger", + icon: "fa fa-trash", + display: "block", + onclick: (but) => { + var target = but.target; + if (target.tagName == "I") { + target = but.target.parentNode; + } + this.butRowDelete(target); + }, + }, + bAcep: { + className: "btn btn-sm btn-success", + icon: "fa fa-check", + display: "none", + onclick: (but) => { + var target = but.target; + if (target.tagName == "I") { + target = but.target.parentNode; + } + this.butRowAcep(target); + }, + }, + bCanc: { + className: "btn btn-sm btn-warning", + icon: "fa fa-remove", + display: "none", + onclick: (but) => { + var target = but.target; + if (target.tagName == "I") { + target = but.target.parentNode; + } + this.butRowCancel(target); + }, + }, +}; +``` -# References +# References (Obsolete, needs updating) Some page explaining the use of bootstable: -* https://medium.com/@diyifang/bootstable-js-editable-table-with-bootstrap-6694f016f1b8 -* https://codepen.io/diyifang/pen/mXdQmB -* http://ivanovdmitry.com/blog/post/create-editable-tables-with-jquery-and-bootstrap-bootstable +- https://medium.com/@diyifang/bootstable-js-editable-table-with-bootstrap-6694f016f1b8 +- https://codepen.io/diyifang/pen/mXdQmB +- http://ivanovdmitry.com/blog/post/create-editable-tables-with-jquery-and-bootstrap-bootstable diff --git a/bootstable.js b/bootstable.js index 35165c7..05c59d9 100644 --- a/bootstable.js +++ b/bootstable.js @@ -1,262 +1,504 @@ /* Bootstable @description Javascript library to make HMTL tables editable, using Bootstrap - @version 1.1 - @autor Tito Hinostroza + @version 1.5.32 + @author Tito Hinostroza + @contributors Tyler Hardison */ "use strict"; -//Global variables -var params = null; //Parameters -var colsEdi =null; -var newColHtml = '
'+ -''+ -''+ -''+ -''+ - '
'; - //Case NOT Bootstrap - var newColHtml2 = '
'+ - ''+ - ''+ - ''+ - ''+ - '
'; -var colEdicHtml = ''+newColHtml+''; -$.fn.SetEditable = function (options) { - var defaults = { - columnsEd: null, //Index to editable columns. If null all td editables. Ex.: "1,2,3,4,5" - $addButton: null, //Jquery object of "Add" button - bootstrap: true, //Indicates bootstrap is present. - onEdit: function() {}, //Called after edition - onBeforeDelete: function() {}, //Called before deletion - onDelete: function() {}, //Called after deletion - onAdd: function() {} //Called when added a new row - }; - params = $.extend(defaults, options); - var $tabedi = this; //Read reference to the current table. - $tabedi.find('thead tr').append(''); //Add empty column - if (!params.bootstrap) { - colEdicHtml = ''+newColHtml2+''; + +class bootstable { + constructor(/** @type string */ element, /** @type object */ options) { + var defaults = { + columnsEd: [], //Index to editable columns. If null all td editables. Ex.: "1,2,3,4,5" + $addButton: null, //Selector for Add Button + exportCsvButton: false, // Add an export to CSV button + exportJsonButton: false, // Add an export to JSON button + defaultValues: [], // set default values on add + addButtonEdit: true, // set fields to editable when add + buttons: null, + customInputs: [], // Add in custom form fields + onEditSave: () => {}, //Called after edition + onBeforeDelete: () => true, //Called before deletion + onDelete: () => {}, //Called after deletion + onAdd: () => {}, //Called when added a new row + }; + this.table = document.getElementById(element); + this.headers = this._getTableHeaders(this.table); + this.params = this._extend(defaults, options); + this.colEdicHtml = document.createElement("td"); + this.colEdicHtml.setAttribute("name", "buttons"); + this.colEdicHtml.appendChild( + this._buildDefaultButtons(this.params.buttons) + ); + this.SetEditable(element); } - //Add column for buttons to all rows. - $tabedi.find('tbody tr').append(colEdicHtml); - //Process "addButton" parameter - if (params.$addButton != null) { - //There is parameter - params.$addButton.click(function() { - rowAddNew($tabedi.attr("id")); + + SetEditable(/** @type string */ element) { + const addButtonTh = document.createElement("th"); + addButtonTh.style.whiteSpace = "nowrap"; + addButtonTh.style.width = "6.3vw"; + addButtonTh.setAttribute("name", "buttons"); + if (this.params.exportCsvButton) { + const csvButton = document.createElement("button"); + csvButton.className = "btn btn-secondary btn-sm float-end"; + csvButton.id = "btnCsv"; + csvButton.addEventListener("click", () => { + this.TableToCSV(element, ",", true, `${element}.export.csv`); }); - } - //Process "columnsEd" parameter - if (params.columnsEd != null) { + csvButton.setAttribute("text", "Export to CSV"); + const glyph = document.createElement("i"); + glyph.className = "bi bi-file-spreadsheet"; + csvButton.appendChild(glyph); + addButtonTh.appendChild(csvButton); + } + if (this.params.exportCsvButton) { + const csvButton = document.createElement("button"); + csvButton.className = "btn btn-secondary btn-sm float-end"; + csvButton.id = "btnJson"; + csvButton.addEventListener("click", () => { + this.TableToJSON(element, true, `${element}.export.csv`); + }); + csvButton.setAttribute("text", "Export to JSON"); + const glyph = document.createElement("i"); + glyph.className = "bi bi-file-code"; + csvButton.appendChild(glyph); + addButtonTh.appendChild(csvButton); + } + + if (this.params.$addButton) { + const addButton = document.createElement("button"); + addButton.className = "btn btn-success btn-sm float-end"; + addButton.id = this.params.$addButton; + addButton.addEventListener("click", () => { + if (this.params.addButtonEdit) + this.rowAddNewAndEdit(element, this.params.defaultValues); + else this.rowAddNew(element); + }); + addButton.setAttribute("text", "Add New Row"); + const glyph = document.createElement("i"); + glyph.className = "bi bi-plus"; + addButton.appendChild(glyph); + + addButtonTh.appendChild(addButton); + } + + document + .querySelectorAll(`#${element} thead tr`)[0] + .appendChild(addButtonTh); + + //Add column for buttons to all rows. + const rows = document.querySelectorAll(`#${element} tbody tr`); + + for (var i = 0; i < rows.length; i++) { + var td = document.createElement("td"); + td.setAttribute("name", "buttons"); + td.appendChild(this._buildDefaultButtons(this.params.buttons)); + rows[i].appendChild(td); + } + + //Process "columnsEd" parameter + if (this.params.columnsEd != null) { //Extract felds - colsEdi = params.columnsEd.split(','); + this.colsEdi = this.params.columnsEd; + } } -}; -function IterarCamposEdit($cols, action) { -//Iterate through editable fields in a row - var n = 0; - $cols.each(function() { + + /** + * + * @param array $cols + * @param {action} action - process table cell + */ + + /** + * Callback to handle interated cells. + * @callback action + * @param {object} boots - reference to self + * @param {object} tableCell + * @param {number} index + */ + IterarCamposEdit(/** @type array */ $cols, /** @type function */ action) { + //Iterate through editable fields in a row + var n = -1; + for (const col of $cols) { n++; - if ($(this).attr('name')=='buttons') return; //Exclude buttons column - if (!IsEditable(n-1)) return; //It's not editable - action($(this)); - }); - - function IsEditable(idx) { - //Indicates if the passed column is set to be editable - if (colsEdi==null) { //no se definió - return true; //todas son editable - } else { //hay filtro de campos - for (var i = 0; i < colsEdi.length; i++) { - if (idx == colsEdi[i]) return true; - } - return false; //no se encontró - } + if (col.style.display == "none") continue; // Ignore Hidden Columns + if (col.getAttribute("name") == "buttons") continue; //Exclude buttons column + if (!this.IsEditable(n)) continue; //It's not editable + action(this, col, n); + } } -} -function ModoEdicion($row) { - if ($row.attr('id')=='editing') { + + IsEditable(/** @type number */ idx) { + //Indicates if the passed column is set to be editable + if (this.colsEdi.length == 0) return true; + return this.colsEdi.includes(idx); + } + + ModoEdicion(/** @type array */ $row) { + if ($row.id == "editing") { return true; - } else { + } else { return false; + } } -} -//Set buttons state -function SetButtonsNormal(but) { - $(but).parent().find('#bAcep').hide(); - $(but).parent().find('#bCanc').hide(); - $(but).parent().find('#bEdit').show(); - $(but).parent().find('#bElim').show(); - var $row = $(but).parents('tr'); //accede a la fila - $row.attr('id', ''); //quita marca -} -function SetButtonsEdit(but) { - $(but).parent().find('#bAcep').show(); - $(but).parent().find('#bCanc').show(); - $(but).parent().find('#bEdit').hide(); - $(but).parent().find('#bElim').hide(); - var $row = $(but).parents('tr'); //accede a la fila - $row.attr('id', 'editing'); //indica que está en edición -} -//Events functions -function butRowAcep(but) { -//Acepta los cambios de la edición - var $row = $(but).parents('tr'); //accede a la fila - var $cols = $row.find('td'); //lee campos - if (!ModoEdicion($row)) return; //Ya está en edición - //Está en edición. Hay que finalizar la edición - IterarCamposEdit($cols, function($td) { //itera por la columnas - var cont = $td.find('input').val(); //lee contenido del input - $td.html(cont); //fija contenido y elimina controles - }); - SetButtonsNormal(but); - params.onEdit($row); -} -function butRowCancel(but) { -//Rechaza los cambios de la edición - var $row = $(but).parents('tr'); //accede a la fila - var $cols = $row.find('td'); //lee campos - if (!ModoEdicion($row)) return; //Ya está en edición - //Está en edición. Hay que finalizar la edición - IterarCamposEdit($cols, function($td) { //itera por la columnas - var cont = $td.find('div').html(); //lee contenido del div - $td.html(cont); //fija contenido y elimina controles - }); - SetButtonsNormal(but); -} -function butRowEdit(but) { - //Start the edition mode for a row. - var $row = $(but).parents('tr'); //accede a la fila - var $cols = $row.find('td'); //lee campos - if (ModoEdicion($row)) return; //Ya está en edición - //Pone en modo de edición - var focused=false; //flag - IterarCamposEdit($cols, function($td) { //itera por la columnas - var cont = $td.html(); //lee contenido + //Set buttons state + SetButtonsNormal(/** @type HTMLElement */ but) { + const children = but.parentNode.childNodes; + for (var child of children) { + switch (child.id) { + case "bAcep": + child.style.display = "none"; + break; + case "bCanc": + child.style.display = "none"; + break; + case "bEdit": + child.style.display = "block"; + break; + case "bElim": + child.style.display = "block"; + break; + } + } + but.parentNode.parentNode.parentNode.setAttribute("id", ""); + } + + SetButtonsEdit(/** @type HTMLElement */ but) { + const children = but.parentNode.childNodes; + for (var child of children) { + switch (child.id) { + case "bAcep": + child.style.display = "block"; + break; + case "bCanc": + child.style.display = "block"; + break; + case "bEdit": + child.style.display = "none"; + break; + case "bElim": + child.style.display = "none"; + break; + } + } + but.parentNode.parentNode.parentNode.setAttribute("id", "editing"); + } + //Events functions + butRowAcep(/** @type HTMLElement */ but) { + //Acepta los cambios de la edición + var $row = but.parentNode.parentNode.parentNode; //accede a la fila + var $cols = $row.querySelectorAll("td"); //lee campos + if (!this.ModoEdicion($row)) return; //Ya está en edición + //Está en edición. Hay que finalizar la edición + this.IterarCamposEdit($cols, function (boots, $td, index) { + //itera por la columnas + var cont = $td.querySelector("input").value; //lee contenido del input + $td.innerHTML = cont; //fija contenido y elimina controles + }); + this.SetButtonsNormal(but); + this.params.onEditSave($row); + } + butRowCancel(/** @type HTMLElement */ but) { + //Rechaza los cambios de la edición + var $row = but.parentNode.parentNode.parentNode; //accede a la fila + var $cols = $row.querySelectorAll("td"); //lee campos + if (!this.ModoEdicion($row)) return; //Ya está en edición + //Está en edición. Hay que finalizar la edición + this.IterarCamposEdit($cols, function (boots, $td, index) { + //itera por la columnas + var cont = $td.querySelector("div").innerHTML; //lee contenido del div + $td.innerHTML = cont; //fija contenido y elimina controles + }); + this.SetButtonsNormal(but); + } + butRowEdit(/** @type HTMLElement */ but) { + //Start the edition mode for a row. + var $row = but.parentNode.parentNode.parentNode; //accede a la fila + var $cols = $row.querySelectorAll("td"); //lee campos + if (this.ModoEdicion($row)) return; //Ya está en edición + //Pone en modo de edición + var focused = false; //flag + + this.IterarCamposEdit($cols, function (boots, $td, index) { + //itera por la columnas + var cont = $td.innerHTML; //lee contenido + $td.innerHTML = ""; //Save previous content in a hide
- var div = '
' + cont + '
'; - var input= ''; - $td.html(div + input); //Set new content + var div = document.createElement("div"); + div.style.display = "none"; + var divText = document.createTextNode(cont); + div.appendChild(divText); + + $td.appendChild(div); + var input; + if (boots.customInputs && boots.customInputs.length > 0) { + input = boots._customInput( + boots, + boots.customInputs[index], + cont, + index + ); + } else { + input = document.createElement("input"); + input.className = "form-control form-control-sm"; + input.value = cont; + input.setAttribute("name", boots.headers[index]); + } + $td.appendChild(input); //Set focus to first column if (!focused) { - $td.find('input').focus(); + $td.querySelectorAll("input")[0].focus(); focused = true; } - }); - SetButtonsEdit(but); -} -function butRowDelete(but) { //Elimina la fila actual - var $row = $(but).parents('tr'); //accede a la fila - params.onBeforeDelete($row); - $row.remove(); - params.onDelete(); -} -//Functions that can be called directly -function rowAddNew(tabId, initValues=[]) { - /* Add a new row to a editable table. + }); + this.SetButtonsEdit(but); + } + butRowDelete(/** @type HTMLElement */ but) { + //Elimina la fila actual + var $row = but.parentNode.parentNode.parentNode; //accede a la fila + if (this.params.onBeforeDelete($row)) { + $row.remove(); + this.params.onDelete($row); + } + } + //Functions that can be called directly + rowAddNew(/** @type string */ tabId, /** @type array */ initValues = []) { + /* Add a new row to a editable table. Parameters: tabId -> Id for the editable table. initValues -> Optional. Array containing the initial value for the new row. */ - var $tab_en_edic = $("#"+tabId); //Table to edit - var $rows = $tab_en_edic.find('tbody tr'); - //if ($rows.length==0) { - //No hay filas de datos. Hay que crearlas completas - var $row = $tab_en_edic.find('thead tr'); //encabezado - var $cols = $row.find('th'); //lee campos - //construye html - var htmlDat = ''; - var i = 0; - $cols.each(function() { - if ($(this).attr('name')=='buttons') { - //Es columna de botones - htmlDat = htmlDat + colEdicHtml; //agrega botones - } else { - if (i'+initValues[i]+''; - } else { - htmlDat = htmlDat + ''; - } + var $tab_en_edic = document.getElementById(tabId); //Table to edit + + var $row = $tab_en_edic.querySelectorAll("thead tr")[0]; //encabezado + var $cols = $row.querySelectorAll("th"); //lee campos + //construye html + var i = 0; + var tr = document.createElement("tr"); + + for (const col of $cols) { + if (col.style.display == "none") { + const td = document.createElement("td"); + td.style.display = "none"; + td.setAttribute("role", col.getAttribute("role")); + tr.appendChild(td); + } else if (col.getAttribute("name") == "buttons") { + //Es columna de botones + tr.appendChild(this.colEdicHtml); + } else { + const td = document.createElement("td"); + if (i < initValues.length) { + const value = document.createTextNode(initValues[i]); + td.appendChild(value); + tr.appendChild(td); + } else { + tr.appendChild(td); + } + } + i++; + } + $tab_en_edic.querySelectorAll("tbody")[0].appendChild(tr); + this.params.onAdd(tr); + } + rowAddNewAndEdit( + /** @type string */ tabId, + /** @type array */ initValues = [] + ) { + /* Add a new row an set edition mode */ + this.rowAddNew(tabId, initValues); + var $lastRow = document + .getElementById(tabId) + .querySelectorAll("tbody")[0].lastChild; + this.butRowEdit($lastRow.querySelector("#bEdit")); //Pass a button reference + } + TableToCSV( + /** @type string */ tabId, + /** @type string */ separator, + /** @type boolean */ download = false, + /** @type string */ filename = null + ) { + //Convert table to CSV + var csv = []; + + var $tab_en_edic = document.getElementById(tabId); //Table source + const headers = this._getTableHeaders($tab_en_edic); + + csv.push(headers); + + for (const row of $tab_en_edic.querySelectorAll("tbody tr")) { + var $cols = row.querySelectorAll("td"); //lee campos + csv.push([]); + for (const col of $cols) { + if (col.getAttribute("name") == "buttons") { + //Es columna de botones + continue; + } else { + csv[csv.length - 1].push(col.innerHTML); + } + } + } + var csvString = ""; + for (const csvRow of csv) { + csvString += csvRow.join(separator) + "\n"; + } + if (download && filename) { + this._download(filename, csvString, "application/csv"); + } + return csvString; + } + + TableToJson( + /** @type string */ tabId, + /** @type boolean */ download = false, + /** @type string */ filename = null + ) { + var obj = []; + const table = document.getElementById(tabId); + const headersHtml = table.querySelectorAll("thead tr th"); + var headers = []; + + for (const head of headersHtml) { + headers.push(head.innerHTML); + } + + const rows = table.querySelectorAll("tbody tr"); + + for (const row of rows) { + var count = 0; + obj.push({}); + for (const field of row.querySelectorAll("td")) { + if (field.getAttribute("name") == "buttons") continue; + obj[obj.length - 1][headers[count]] = field.innerHTML; + count++; + } + } + + if (download) { + this._download(filename, JSON.stringify(obj), "application/json"); + } + return JSON.stringify(obj); + } + + _extend(/** @type object */ a, /** @type object */ b) { + for (var key in b) if (b.hasOwnProperty(key)) a[key] = b[key]; + return a; + } + + _buildDefaultButtons(/** @type object */ buttonOverride = {}) { + const buttons = { + bEdit: { + className: "btn btn-sm btn-primary", + icon: "bi bi-pencil", + display: "block", + onclick: (but) => { + var target = but.target; + if (target.tagName == "I") { + target = but.target.parentNode; } - i++; - }); - $tab_en_edic.find('tbody').append(''+htmlDat+''); - /*} else { - //Hay otras filas, podemos clonar la última fila, para copiar los botones - var $lastRow = $tab_en_edic.find('tr:last'); - $lastRow.clone().appendTo($lastRow.parent()); - $lastRow = $tab_en_edic.find('tr:last'); - var $cols = $lastRow.find('td'); //lee campos - $cols.each(function() { - if ($(this).attr('name')=='buttons') { - //Es columna de botones - } else { - $(this).html(''); //limpia contenido + this.butRowEdit(target); + }, + }, + bElim: { + className: "btn btn-sm btn-danger", + icon: "bi bi-trash", + display: "block", + onclick: (but) => { + var target = but.target; + if (target.tagName == "I") { + target = but.target.parentNode; } - }); - }*/ - params.onAdd(); -} -function rowAddNewAndEdit(tabId, initValues=[]) { -/* Add a new row an set edition mode */ - rowAddNew(tabId, initValues); - var $lastRow = $('#'+tabId + ' tr:last'); - butRowEdit($lastRow.find('#bEdit')); //Pass a button reference -} -function TableToCSV(tabId, separator) { //Convert table to CSV - var datFil = ''; - var tmp = ''; - var $tab_en_edic = $("#" + tabId); //Table source - $tab_en_edic.find('tbody tr').each(function() { - //Termina la edición si es que existe - if (ModoEdicion($(this))) { - $(this).find('#bAcep').click(); //acepta edición - } - var $cols = $(this).find('td'); //lee campos - datFil = ''; - $cols.each(function() { - if ($(this).attr('name')=='buttons') { - //Es columna de botones - } else { - datFil = datFil + $(this).html() + separator; + this.butRowDelete(target); + }, + }, + bAcep: { + className: "btn btn-sm btn-success", + icon: "bi bi-check", + display: "none", + onclick: (but) => { + var target = but.target; + if (target.tagName == "I") { + target = but.target.parentNode; } - }); - if (datFil!='') { - datFil = datFil.substr(0, datFil.length-separator.length); - } - tmp = tmp + datFil + '\n'; - }); - return tmp; + this.butRowAcep(target); + }, + }, + bCanc: { + className: "btn btn-sm btn-warning", + icon: "bi bi-x-circle", + display: "none", + onclick: (but) => { + var target = but.target; + if (target.tagName == "I") { + target = but.target.parentNode; + } + this.butRowCancel(target); + }, + }, + }; + + this.localbuttons = this._extend(buttons, buttonOverride); + + const div = document.createElement("div"); + div.className = "d-grid gap-2 d-md-flex justify-content-md-end"; + + for (const button in this.localbuttons) { + const thisButton = document.createElement("button"); + thisButton.id = button; + thisButton.className = this.localbuttons[button].className; + thisButton.style.display = this.localbuttons[button].display; + thisButton.addEventListener("click", this.localbuttons[button].onclick); + const icon = document.createElement("i"); + icon.className = this.localbuttons[button].icon; + thisButton.appendChild(icon); + div.appendChild(thisButton); + } + return div; + } + _download( + /** @type string */ filename, + /** @type string */ text, + /** @type string */ mimetype = "text/plain" + ) { + var element = document.createElement("a"); + element.setAttribute( + "href", + `data:${mimetype};charset=utf-8,${encodeURIComponent(text)}` + ); + element.setAttribute("download", filename); + + element.style.display = "none"; + document.body.appendChild(element); + + element.click(); + + document.body.removeChild(element); + } + _getTableHeaders(/** @type HTMLElement */ table) { + const headersHtml = table.querySelectorAll("thead tr th"); + var headers = []; + + for (const head of headersHtml) { + if (head.getAttribute("name") == "buttons") continue; + headers.push(head.innerHTML); + } + return headers; + } + + _customInput( + /** @type object */ boots, + /** @type object */ inputObj, + /** @type string */ value = null, + /** @type number */ index + ) { + const input = document.createElement(inputObj.element); + if (input.element === "input") input.setAttribute("type", inputObj.type); + input.className = inputObj.className; + input.setAttribute("name", inputObj.name); + if (value) input.value = value; + else if (boots.defaultValues.length > 0) + input.value = boots.defaultValues[index]; + return input; + } } -function TableToJson(tabId) { //Convert table to JSON - var json = '{'; - var otArr = []; - var tbl2 = $('#'+tabId+' tr').each(function(i) { - var x = $(this).children(); - var itArr = []; - x.each(function() { - itArr.push('"' + $(this).text() + '"'); - }); - otArr.push('"' + i + '": [' + itArr.join(',') + ']'); - }) - json += otArr.join(",") + '}' - return json; -} \ No newline at end of file diff --git a/bootstable.min.js b/bootstable.min.js index eb33b02..6aa714f 100644 --- a/bootstable.min.js +++ b/bootstable.min.js @@ -1 +1 @@ -"use strict";var params=null,colsEdi=null,newColHtml='
',newColHtml2='
',colEdicHtml=''+newColHtml+"";function IterarCamposEdit(t,n){var o=0;t.each(function(){o++,"buttons"!=$(this).attr("name")&&function(t){if(null==colsEdi)return!0;for(var n=0;n'+n+"
",a='';t.html(o+a),i||(t.find("input").focus(),i=!0)}),SetButtonsEdit(t)}}function butRowDelete(t){var n=$(t).parents("tr");params.onBeforeDelete(n),n.remove(),params.onDelete()}function rowAddNew(t,n=[]){var o=$("#"+t),i=(o.find("tbody tr"),o.find("thead tr").find("th")),a="",s=0;i.each(function(){"buttons"==$(this).attr("name")?a+=colEdicHtml:s"+n[s]+"":a+="",s++}),o.find("tbody").append(""+a+""),params.onAdd()}function rowAddNewAndEdit(t,n=[]){rowAddNew(t,n),butRowEdit($("#"+t+" tr:last").find("#bEdit"))}function TableToCSV(t,n){var o="",i="";return $("#"+t).find("tbody tr").each(function(){ModoEdicion($(this))&&$(this).find("#bAcep").click();var t=$(this).find("td");o="",t.each(function(){"buttons"==$(this).attr("name")||(o=o+$(this).html()+n)}),""!=o&&(o=o.substr(0,o.length-n.length)),i=i+o+"\n"}),i}function TableToJson(t){var n="{",o=[];$("#"+t+" tr").each(function(t){var n=$(this).children(),i=[];n.each(function(){i.push('"'+$(this).text()+'"')}),o.push('"'+t+'": ['+i.join(",")+"]")});return n+=o.join(",")+"}"}$.fn.SetEditable=function(t){params=$.extend({columnsEd:null,$addButton:null,bootstrap:!0,onEdit:function(){},onBeforeDelete:function(){},onDelete:function(){},onAdd:function(){}},t);var n=this;n.find("thead tr").append(''),params.bootstrap||(colEdicHtml=''+newColHtml2+""),n.find("tbody tr").append(colEdicHtml),null!=params.$addButton&¶ms.$addButton.click(function(){rowAddNew(n.attr("id"))}),null!=params.columnsEd&&(colsEdi=params.columnsEd.split(","))}; \ No newline at end of file +"use strict";class bootstable{constructor(t,e){this.table=document.getElementById(t),this.headers=this._getTableHeaders(this.table),this.params=this._extend({columnsEd:[],$addButton:null,exportCsvButton:!1,exportJsonButton:!1,defaultValues:[],addButtonEdit:!0,buttons:null,customInputs:[],onEditSave:()=>{},onBeforeDelete:()=>!0,onDelete:()=>{},onAdd:()=>{}},e),this.colEdicHtml=document.createElement("td"),this.colEdicHtml.setAttribute("name","buttons"),this.colEdicHtml.appendChild(this._buildDefaultButtons(this.params.buttons)),this.SetEditable(t)}SetEditable(t){const e=document.createElement("th");if(e.style.whiteSpace="nowrap",e.style.width="6.3vw",e.setAttribute("name","buttons"),this.params.exportCsvButton){const s=document.createElement("button");s.className="btn btn-secondary btn-sm float-end",s.id="btnCsv",s.addEventListener("click",()=>{this.TableToCSV(t,",",!0,t+".export.csv")}),s.setAttribute("text","Export to CSV");const d=document.createElement("i");d.className="bi bi-file-spreadsheet",s.appendChild(d),e.appendChild(s)}if(this.params.exportCsvButton){const l=document.createElement("button");l.className="btn btn-secondary btn-sm float-end",l.id="btnJson",l.addEventListener("click",()=>{this.TableToJSON(t,!0,t+".export.csv")}),l.setAttribute("text","Export to JSON");const r=document.createElement("i");r.className="bi bi-file-code",l.appendChild(r),e.appendChild(l)}if(this.params.$addButton){const i=document.createElement("button");i.className="btn btn-success btn-sm float-end",i.id=this.params.$addButton,i.addEventListener("click",()=>{this.params.addButtonEdit?this.rowAddNewAndEdit(t,this.params.defaultValues):this.rowAddNew(t)}),i.setAttribute("text","Add New Row");const c=document.createElement("i");c.className="bi bi-plus",i.appendChild(c),e.appendChild(i)}document.querySelectorAll(`#${t} thead tr`)[0].appendChild(e);const n=document.querySelectorAll(`#${t} tbody tr`);for(var o=0;o{var e=t.target;"I"==e.tagName&&(e=t.target.parentNode),this.butRowEdit(e)}},bElim:{className:"btn btn-sm btn-danger",icon:"bi bi-trash",display:"block",onclick:t=>{var e=t.target;"I"==e.tagName&&(e=t.target.parentNode),this.butRowDelete(e)}},bAcep:{className:"btn btn-sm btn-success",icon:"bi bi-check",display:"none",onclick:t=>{var e=t.target;"I"==e.tagName&&(e=t.target.parentNode),this.butRowAcep(e)}},bCanc:{className:"btn btn-sm btn-warning",icon:"bi bi-x-circle",display:"none",onclick:t=>{var e=t.target;"I"==e.tagName&&(e=t.target.parentNode),this.butRowCancel(e)}}};this.localbuttons=this._extend(e,t);const n=document.createElement("div");n.className="d-grid gap-2 d-md-flex justify-content-md-end";for(const o in this.localbuttons){const a=document.createElement("button");a.id=o,a.className=this.localbuttons[o].className,a.style.display=this.localbuttons[o].display,a.addEventListener("click",this.localbuttons[o].onclick);const s=document.createElement("i");s.className=this.localbuttons[o].icon,a.appendChild(s),n.appendChild(a)}return n}_download(t,e,n="text/plain"){var o=document.createElement("a");o.setAttribute("href",`data:${n};charset=utf-8,`+encodeURIComponent(e)),o.setAttribute("download",t),o.style.display="none",document.body.appendChild(o),o.click(),document.body.removeChild(o)}_getTableHeaders(t){var e=[];for(const n of t.querySelectorAll("thead tr th"))"buttons"!=n.getAttribute("name")&&e.push(n.innerHTML);return e}_customInput(t,e,n=null,o){const a=document.createElement(e.element);return"input"===a.element&&a.setAttribute("type",e.type),a.className=e.className,a.setAttribute("name",e.name),n?a.value=n:0 - - - - - - - - - - - + + + + + + + - + - -
Testing editable table
+ +
+
+
+
+
+
+
Bootstable - Tiny Utility for Producing Editable Tables
+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CharacterOriginYear
Iron ManSelf Invented Suit.1963
ThorGod of ThunderPre-history
Captain AmericaResult of Super Soldier Experiments in WW21941
HulkExposure to Gamma Radiation1962
Ant-ManCreated using Pym Particles and Suit Tech1962
+

+
+
+
+
+ -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameGentalemail
JamesMalejhsdfgh@hotmail.com
SamsMalehhfdfg@hotmail.com
MaryFemalevccch@gmail.com
AshlyFemaleasss@gmail.com
EdenMaleedd@hotmail.com
- -
- -
- - - - - - + + + + \ No newline at end of file