├── README.md ├── example.png ├── example ├── advanced.html ├── advanced.js ├── basic.html └── basic.js └── js └── dataTables.cellEdit.js /README.md: -------------------------------------------------------------------------------- 1 | # CellEdit 2 | ##### A plugin for [DataTables.net](https://datatables.net) 3 | ## Overview 4 | This plugin allows cells within a [DataTable](https://datatables.net/) to be editable. When a cell is click on, an input field will appear. When focus is lost on the input and the underlying DataTable object will be updated and the table will be redrawn. The new value is passed to a callback function, along with it's row, allowing for easy server-side data updates. 5 | 6 | ![Example image](example.png "Example") 7 | 8 | ## Usage 9 | ### MakeCellsEditable(settings); 10 | ##### Settings { JSON Object } 11 | Property | Type | Default | Example | Details 12 | :------ | :------ | :------ | :-----| :------ 13 | **onUpdate** | function | | ```function(cell, row, oldValue){ } ``` | The call back function to be executed. The updated **[cell](https://datatables.net/reference/api/cell())**, **[row](https://datatables.net/reference/api/row())**, and previous value in that cell are passed as arguments. 14 | **onValidate** _(optional)_ | function | none | ```function(cell, row, newValue){ } ``` | The call back function to be executed before updating the cell value. The relevant **[cell](https://datatables.net/reference/api/cell())**, **[row](https://datatables.net/reference/api/row())**, and new value in the editor are passed as arguments. The function should return `true` if the value is valid, or `false` if it does not pass validation logic. 15 | **inputCss** _(optional)_| string | none |```'my-css-class'```| A CSS class that will be applied to the input field 16 | **wrapperHtml** _(optional)_| string | none |```
{content}
```| HTML used to wrap the inline editor. Use `{content}` as the placeholder for the inline editor. 17 | **columns** _(optional)_| array | All columns |```[0,1,3,4]```| An array of column indexes defining the columns that you want to be editable. 18 | **allowNulls** _(optional)_| object | false | ```{ "columns": [4], "errorClass":"my-error"}``` | Determines which columns should allow null values to be entered and what CSS to apply if user input fails validation. If **errorClass** is null a default error class will be applied. 19 | **confirmationButton** _(optional)_| bool | object | false | ```{"confirmCss":"button"}``` | Will cause two links to appear after the input; _"Confirm"_ and _"Cancel"_. User input will not be accepted until _"Confirm"_ is clicked by the user. You can optionally pass in an object with **confirmCss** and **cancelCss** properties instead of boolean. These properties specify the CSS classes that should be applied to the _Confirm_ and _Cancel_ anchor tags. If you would like _Enter_ and _Escape_ keys to Confirm/Cancel also, add another property **listenToKeys** and set it to true. 20 | **inputTypes** _(optional)_ | object array | text | "inputTypes": [{"column":0, "type":"text", "options":null }] | Allows you to change the type of input that appears (IE dropdown or text). As different types of inputs are added I will update the advanced initialization example below with examples. 21 | 22 | ### Basic Initialization 23 | ```javascript 24 | var table = $('#myTable').DataTable(); 25 | 26 | function myCallbackFunction (updatedCell, updatedRow, oldValue) { 27 | console.log("The new value for the cell is: " + updatedCell.data()); 28 | console.log("The values for each cell in that row are: " + updatedRow.data()); 29 | } 30 | 31 | table.MakeCellsEditable({ 32 | "onUpdate": myCallbackFunction 33 | }); 34 | ``` 35 | ### Advanced Initialization 36 | ```javascript 37 | var table = $('#myAdvancedTable').DataTable(); 38 | 39 | function myCallbackFunction(updatedCell, updatedRow, oldValue) { 40 | console.log("The new value for the cell is: " + updatedCell.data()); 41 | console.log("The values for each cell in that row are: " + updatedRow.data()); 42 | } 43 | 44 | table.MakeCellsEditable({ 45 | "onUpdate": myCallbackFunction, 46 | "inputCss":'my-input-class', 47 | "columns": [0,1,2], 48 | "allowNulls": { 49 | "columns": [1], 50 | "errorClass": 'error' 51 | }, 52 | "confirmationButton": { 53 | "confirmCss": 'my-confirm-class', 54 | "cancelCss": 'my-cancel-class' 55 | }, 56 | "inputTypes": [ 57 | { 58 | "column":0, 59 | "type":"text", 60 | "options":null 61 | }, 62 | { 63 | "column":1, 64 | "type": "list", 65 | "options":[ 66 | { "value": "1", "display": "Beaty" }, 67 | { "value": "2", "display": "Doe" }, 68 | { "value": "3", "display": "Dirt" } 69 | ] 70 | } 71 | ,{ 72 | "column": 2, 73 | "type": "datepicker", // requires jQuery UI: http://http://jqueryui.com/download/ 74 | "options": { 75 | "icon": "http://jqueryui.com/resources/demos/datepicker/images/calendar.gif" // Optional 76 | } 77 | } 78 | ] 79 | }); 80 | ``` 81 | ##### Destroy 82 | If you need to **[destroy](https://datatables.net/reference/api/destroy())** a table and then reinitialize it, you'll need to destroy the MakeCellsEditable configuration as well. You can do this by passing "destroy" to the method. An example of this can be found in the advanced example. 83 | ```javascript 84 | table.MakeCellsEditable("destroy"); 85 | ``` 86 | -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ejbeaty/CellEdit/2bfc35f601dab6a7c572f64007dade2ddc8c1059/example.png -------------------------------------------------------------------------------- /example/advanced.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 |
First NameLast NameDOB
ElliottBeaty01/14/2011
JoeDirt01/14/2004
JohnDoe01/14/2003
JaneDoe01/11/2001
BillyBob02/14/1947
BobbyAxlerod01/27/2001
ElliottBeaty01/13/2007
JoeDirt01/14/2001
JohnDoe01/14/2001
JaneDoe01/14/2001
BillyBob01/14/2001
BobbyAxlerod01/14/2001
120 |
121 | Destroy Table 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /example/advanced.js: -------------------------------------------------------------------------------- 1 | var table; 2 | $(document).ready(function () { 3 | table = $('#myAdvancedTable').DataTable(); 4 | table.MakeCellsEditable({ 5 | "onUpdate": myCallbackFunction, 6 | "inputCss":'my-input-class', 7 | "columns": [0,1,2,3], 8 | "allowNulls": { 9 | "columns": [3], 10 | "errorClass": 'error' 11 | }, 12 | "confirmationButton": { // could also be true 13 | "confirmCss": 'my-confirm-class', 14 | "cancelCss": 'my-cancel-class' 15 | }, 16 | "inputTypes": [ 17 | { 18 | "column": 0, 19 | "type": "text", 20 | "options": null 21 | }, 22 | { 23 | "column":1, 24 | "type": "list", 25 | "options":[ 26 | { "value": "1", "display": "Beaty" }, 27 | { "value": "2", "display": "Doe" }, 28 | { "value": "3", "display": "Dirt" } 29 | ] 30 | }, 31 | { 32 | "column": 2, 33 | "type": "datepicker", // requires jQuery UI: http://http://jqueryui.com/download/ 34 | "options": { 35 | "icon": "http://jqueryui.com/resources/demos/datepicker/images/calendar.gif" // Optional 36 | } 37 | } 38 | // Nothing specified for column 3 so it will default to text 39 | 40 | ] 41 | }); 42 | 43 | }); 44 | 45 | function myCallbackFunction (updatedCell, updatedRow, oldValue) { 46 | console.log("The new value for the cell is: " + updatedCell.data()); 47 | console.log("The old value for that cell was: " + oldValue); 48 | console.log("The values for each cell in that row are: " + updatedRow.data()); 49 | } 50 | 51 | function destroyTable() { 52 | if ($.fn.DataTable.isDataTable('#myAdvancedTable')) { 53 | table.destroy(); 54 | table.MakeCellsEditable("destroy"); 55 | } 56 | } -------------------------------------------------------------------------------- /example/basic.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 |
First NameLast NameEmail
ElliottBeatyelliott@example.com
JoeDirtJoe@example.com
JohnDoeJohn@example.com
JaneDoeJane@example.com
BillyBobBilly@example.com
BobbyAxlerodBobby@axecapital.com
48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /example/basic.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function () { 2 | var table = $('#myTable').DataTable(); 3 | 4 | table.MakeCellsEditable({ 5 | "onUpdate": myCallbackFunction 6 | }); 7 | }); 8 | 9 | function myCallbackFunction(updatedCell, updatedRow, oldValue) { 10 | console.log("The new value for the cell is: " + updatedCell.data()); 11 | console.log("The old value for that cell was: " + oldValue); 12 | console.log("The values for each cell in that row are: " + updatedRow.data()); 13 | } -------------------------------------------------------------------------------- /js/dataTables.cellEdit.js: -------------------------------------------------------------------------------- 1 | /*! CellEdit 1.0.19 2 | * ©2016 Elliott Beaty - datatables.net/license 3 | */ 4 | 5 | /** 6 | * @summary CellEdit 7 | * @description Make a cell editable when clicked upon 8 | * @version 1.0.19 9 | * @file dataTables.editCell.js 10 | * @author Elliott Beaty 11 | * @contact elliott@elliottbeaty.com 12 | * @copyright Copyright 2016 Elliott Beaty 13 | * 14 | * This source file is free software, available under the following license: 15 | * MIT license - http://datatables.net/license/mit 16 | * 17 | * This source file is distributed in the hope that it will be useful, but 18 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 19 | * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. 20 | * 21 | * For details please refer to: http://www.datatables.net 22 | */ 23 | 24 | jQuery.fn.dataTable.Api.register('MakeCellsEditable()', function (settings) { 25 | var table = this.table(); 26 | 27 | jQuery.fn.extend({ 28 | // UPDATE 29 | updateEditableCell: function (callingElement) { 30 | // Need to redeclare table here for situations where we have more than one datatable on the page. See issue6 on github 31 | var table = $(callingElement).closest("table").DataTable().table(); 32 | var row = table.row($(callingElement).parents('tr')); 33 | var cell = table.cell($(callingElement).parents('td, th')); 34 | var columnIndex = cell.index().column; 35 | var inputField =getInputField(callingElement); 36 | 37 | // Update 38 | var newValue = inputField.val(); 39 | if (!newValue && ((settings.allowNulls) && settings.allowNulls != true)) { 40 | // If columns specified 41 | if (settings.allowNulls.columns) { 42 | // If current column allows nulls 43 | if (settings.allowNulls.columns.indexOf(columnIndex) > -1) { 44 | _update(newValue); 45 | } else { 46 | _addValidationCss(); 47 | } 48 | // No columns allow null 49 | } else if (!newValue) { 50 | _addValidationCss(); 51 | } 52 | //All columns allow null 53 | } else if (newValue && settings.onValidate) { 54 | if (settings.onValidate(cell, row, newValue)) { 55 | _update(newValue); 56 | } else { 57 | _addValidationCss(); 58 | } 59 | } 60 | else { 61 | _update(newValue); 62 | } 63 | function _addValidationCss() { 64 | // Show validation error 65 | if (settings.allowNulls.errorClass) { 66 | $(inputField).addClass(settings.allowNulls.errorClass); 67 | } else { 68 | $(inputField).css({ "border": "red solid 1px" }); 69 | } 70 | } 71 | function _update(newValue) { 72 | var oldValue = cell.data(); 73 | cell.data(newValue); 74 | //Return cell & row. 75 | settings.onUpdate(cell, row, oldValue); 76 | } 77 | // Get current page 78 | var currentPageIndex = table.page.info().page; 79 | 80 | //Redraw table 81 | table.page(currentPageIndex).draw(false); 82 | }, 83 | // CANCEL 84 | cancelEditableCell: function (callingElement) { 85 | var table = $(callingElement.closest("table")).DataTable().table(); 86 | var cell = table.cell($(callingElement).parents('td, th')); 87 | // Set cell to it's original value 88 | cell.data(cell.data()); 89 | 90 | // Redraw table 91 | table.draw(); 92 | } 93 | }); 94 | 95 | // Destroy 96 | if (settings === "destroy") { 97 | $(table.body()).off("click", "td"); 98 | table = null; 99 | } 100 | 101 | if (table != null) { 102 | // On cell click 103 | $(table.body()).on('click', 'td', function () { 104 | 105 | var currentColumnIndex = table.cell(this).index().column; 106 | 107 | // DETERMINE WHAT COLUMNS CAN BE EDITED 108 | if ((settings.columns && settings.columns.indexOf(currentColumnIndex) > -1) || (!settings.columns)) { 109 | var row = table.row($(this).parents('tr')); 110 | editableCellsRow = row; 111 | 112 | var cell = table.cell(this).node(); 113 | var oldValue = table.cell(this).data(); 114 | // Sanitize value 115 | oldValue = sanitizeCellValue(oldValue); 116 | 117 | // Show input 118 | if (!$(cell).find('input').length && !$(cell).find('select').length && !$(cell).find('textarea').length) { 119 | // Input CSS 120 | var input = getInputHtml(currentColumnIndex, settings, oldValue); 121 | $(cell).html(input.html); 122 | if (input.focus) { 123 | $('#ejbeatycelledit').focus(); 124 | } 125 | } 126 | } 127 | }); 128 | } 129 | 130 | }); 131 | 132 | function getInputHtml(currentColumnIndex, settings, oldValue) { 133 | var inputSetting, inputType, input, inputCss, confirmCss, cancelCss, startWrapperHtml = '', endWrapperHtml = '', listenToKeys = false; 134 | 135 | input = {"focus":true,"html":null}; 136 | 137 | if(settings.inputTypes){ 138 | $.each(settings.inputTypes, function (index, setting) { 139 | if (setting.column == currentColumnIndex) { 140 | inputSetting = setting; 141 | inputType = inputSetting.type.toLowerCase(); 142 | } 143 | }); 144 | } 145 | 146 | if (settings.inputCss) { inputCss = settings.inputCss; } 147 | if (settings.wrapperHtml) { 148 | var elements = settings.wrapperHtml.split('{content}'); 149 | if (elements.length === 2) { 150 | startWrapperHtml = elements[0]; 151 | endWrapperHtml = elements[1]; 152 | } 153 | } 154 | 155 | if (settings.confirmationButton) { 156 | if (settings.confirmationButton.listenToKeys) { listenToKeys = settings.confirmationButton.listenToKeys; } 157 | confirmCss = settings.confirmationButton.confirmCss; 158 | cancelCss = settings.confirmationButton.cancelCss; 159 | inputType = inputType + "-confirm"; 160 | } 161 | switch (inputType) { 162 | case "list": 163 | input.html = startWrapperHtml + "" + endWrapperHtml; 172 | input.focus = false; 173 | break; 174 | case "list-confirm": // List w/ confirm 175 | input.html = startWrapperHtml + " Confirm Cancel" + endWrapperHtml; 184 | input.focus = false; 185 | break; 186 | case "datepicker": //Both datepicker options work best when confirming the values 187 | case "datepicker-confirm": 188 | // Makesure jQuery UI is loaded on the page 189 | if (typeof jQuery.ui == 'undefined') { 190 | alert("jQuery UI is required for the DatePicker control but it is not loaded on the page!"); 191 | break; 192 | } 193 | jQuery(".datepick").datepicker("destroy"); 194 | input.html = startWrapperHtml + "  Confirm Cancel" + endWrapperHtml; 195 | setTimeout(function () { //Set timeout to allow the script to write the input.html before triggering the datepicker 196 | var icon = "http://jqueryui.com/resources/demos/datepicker/images/calendar.gif"; 197 | // Allow the user to provide icon 198 | if (typeof inputSetting.options !== 'undefined' && typeof inputSetting.options.icon !== 'undefined') { 199 | icon = inputSetting.options.icon; 200 | } 201 | var self = jQuery('.datepick').datepicker( 202 | { 203 | showOn: "button", 204 | buttonImage: icon, 205 | buttonImageOnly: true, 206 | buttonText: "Select date" 207 | }); 208 | },100); 209 | break; 210 | case "text-confirm": // text input w/ confirm 211 | input.html = startWrapperHtml + " Confirm Cancel" + endWrapperHtml; 212 | break; 213 | case "undefined-confirm": // text input w/ confirm 214 | input.html = startWrapperHtml + " Confirm Cancel" + endWrapperHtml; 215 | break; 216 | case "textarea": 217 | input.html = startWrapperHtml + "" + endWrapperHtml; 218 | break; 219 | case "textarea-confirm": 220 | input.html = startWrapperHtml + "Confirm Cancel" + endWrapperHtml; 221 | break; 222 | case "number-confirm" : 223 | input.html = startWrapperHtml + " Confirm Cancel" + endWrapperHtml; 224 | break; 225 | default: // text input 226 | input.html = startWrapperHtml + "" + endWrapperHtml; 227 | break; 228 | } 229 | return input; 230 | } 231 | 232 | function getInputField(callingElement) { 233 | // Update datatables cell value 234 | var inputField; 235 | switch ($(callingElement).prop('nodeName').toLowerCase()) { 236 | case 'a': // This means they're using confirmation buttons 237 | if ($(callingElement).siblings('input').length > 0) { 238 | inputField = $(callingElement).siblings('input'); 239 | } 240 | if ($(callingElement).siblings('select').length > 0) { 241 | inputField = $(callingElement).siblings('select'); 242 | } 243 | if ($(callingElement).siblings('textarea').length > 0) { 244 | inputField = $(callingElement).siblings('textarea'); 245 | } 246 | break; 247 | default: 248 | inputField = $(callingElement); 249 | } 250 | return inputField; 251 | } 252 | 253 | function sanitizeCellValue(cellValue) { 254 | if (typeof (cellValue) === 'undefined' || cellValue === null || cellValue.length < 1) { 255 | return ""; 256 | } 257 | 258 | // If not a number 259 | if (isNaN(cellValue)) { 260 | // escape single quote 261 | cellValue = cellValue.replace(/'/g, "'"); 262 | } 263 | return cellValue; 264 | } 265 | --------------------------------------------------------------------------------