├── LICENSE.txt ├── README.md ├── examples.html └── lib └── EditTable.js /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Jeremy Dorn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | EditTable 2 | ========= 3 | 4 | A small jQuery plugin to make your HTML tables editable. 5 | 6 | When you click on a table cell, it is replaced with an editor that lets you change the value. 7 | 8 | * Requires jQuery, but doesn't have any other dependencies 9 | * Works on any standard HTML table and doesn't require additional markup 10 | * Works great with Twitter Bootstrap, jQueryUI, and other CSS frameworks 11 | * Keyboard navigation to move to different table cells (tab, shift+tab, up, down) 12 | * Comes with 2 built-in editors - Text and Select - and it's easy to add your own 13 | * Get table contents as a JSON array at any time 14 | 15 | Basic Usage 16 | -------------- 17 | 18 | EditTable requires a recent version of jQuery. 19 | 20 | ```html 21 | 22 | 23 | ``` 24 | 25 | Making an existing HTML table editable is easy: 26 | 27 | ```html 28 | 29 | ... 30 |
31 | 32 | 38 | ``` 39 | 40 | Checkout out the [examples](http://htmlpreview.github.io/?http://github.com/jdorn/EditTable/blob/master/examples.html) for more advanced options. 41 | -------------------------------------------------------------------------------- /examples.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | EditTable Examples 5 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 28 | 29 | 30 |
31 |
32 |

Zero-Configuration Example

33 |

EditTable can work with zero configuration on an existing HTML table.

34 |

Click cells to edit them, use keyboard navigation (TAB, Shift+Tab, Up/Down Arrows), and try out the buttons below.

35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 |
NameGenderDescription
John SmithMaleFamous for having the most popular male fake name.
Jane DoeFemaleFriend of John Smith.
John DoeMaleHusband of Jane Doe and extremely jealous of John Smith.
58 |
59 | 60 | 61 | 62 | 63 |
64 | 65 | 68 |
69 | 70 |
71 |

Full Featured Example

72 |

This example demonstrates many of the advanced features of EditTable. Things to note:

73 |
    74 |
  • Different editors (Gender column uses select box)
  • 75 |
  • Custom formatting (Email turned into mailto link)
  • 76 |
  • Email column is disabled and you can't edit it
  • 77 |
  • Changing the Name column re-generates the Email column
  • 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 |
NameGenderDescriptionEmail
John SmithMaleFamous for having the most popular male fake name.
Jane DoeFemaleFriend of John Smith.
John DoeMaleHusband of Jane Doe and extremely jealous of John Smith.
106 |
107 | 108 | 109 | 110 | 111 |
112 | 113 | 153 |
154 | 155 |
156 |

jQueryUI Integration Example

157 |

Works great with jQueryUI. This example demonstrates:

158 |
    159 |
  • Custom classes for table cells and headers
  • 160 |
  • Drag and drop rows to rearrange (uses jQueryUI's sortable component)
  • 161 |
162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 |
NameGenderDescription
John SmithMaleFamous for having the most popular male fake name.
Jane DoeFemaleFriend of John Smith.
John DoeMaleHusband of Jane Doe and extremely jealous of John Smith.
185 | 186 |
187 | 188 | 189 | 190 | 191 |
192 | 193 | 219 |
220 | 221 |
222 |

Twitter Bootstrap Integration Example

223 |

Doesn't modify table markup, so it should work flawlessly with CSS frameworks like Bootstrap.

224 |

This example also shows creating an EditTable from an array of JSON objects instead of from existing HTML.

225 | 226 |
227 | 228 |
229 | 230 | 231 | 232 | 233 |
234 | 235 | 258 |
259 |
260 | 261 | 295 | 296 | 297 | -------------------------------------------------------------------------------- /lib/EditTable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * EditTable is a jQuery plugin to make your HTML tables editable. 3 | * It has no dependencies (other than jQuery) and is easy to customize. 4 | * 5 | * @author Jeremy Dorn 6 | * @copyright 2013 Jeremy Dorn 7 | * @license http://opensource.org/licenses/MIT 8 | * @link http://github.com/jdorn/EditTable 9 | * @version 0.3.0 10 | */ 11 | (function($) { 12 | /** 13 | * Container for helper functions, default options, and editors 14 | */ 15 | $.EditTable = { 16 | default_options: { 17 | columns: {}, 18 | defaultEditor: "text", 19 | defaultOptions: {}, 20 | tdClass: '', 21 | thClass: '', 22 | includeTBody: true, 23 | includeTHead: true, 24 | set: null, 25 | build: null 26 | }, 27 | editors: {} 28 | }; 29 | 30 | var methods = { 31 | /** 32 | * Constructor 33 | * @param options An options hash. 34 | * @constructor 35 | */ 36 | init: function(options) { 37 | var settings = $.extend(true, {}, $.EditTable.default_options, options); 38 | 39 | return this.each(function() { 40 | var data = { 41 | settings: settings, 42 | initialized: true, 43 | editors: {} 44 | }; 45 | var $this = $(this); 46 | 47 | if(settings.set) $this.on('set.editTable',settings.set); 48 | if(settings.build) $this.on('build.editTable',settings.build); 49 | 50 | $this.data('editTable',data); 51 | $this.editTable('refresh'); 52 | 53 | // Clicking on a cell starts the editor 54 | $this.on('click.editTable','td',function() { 55 | var cell = $(this).data('editTable'); 56 | if(!cell) return; 57 | 58 | $this.editTable('edit', cell.row, cell.col); 59 | }); 60 | }); 61 | }, 62 | 63 | /** 64 | * Set or get one or more options 65 | * 66 | * Usage 1 - Get single option 67 | * var value = el.editTable('option', 'key'); 68 | * 69 | * Usage 2 - Get multiple values 70 | * var values = el.editTable('option', ['key1','key2']); 71 | * console.log(values.key1, values.key2); 72 | * 73 | * Usage 3 - Set single option 74 | * el.editTable('option', 'key', 'value'); 75 | * 76 | * Usage 4 - Set multiple options 77 | * el.editTable('option', { key1: 'value1', key2: 'value2' }); 78 | */ 79 | option: function(key,value) { 80 | // option({key: value}) 81 | // set multiple options 82 | if($.type(key) === "object") { 83 | return this.each(function() { 84 | var $this = $(this); 85 | var data = $this.data('editTable'); 86 | if(!data || !data.initialized) $.error('EditTable must be initialized before setting options'); 87 | data.settings = $.extend(data.settings, key); 88 | 89 | if(key.set) $this.on('set.editTable',key.set); 90 | if(key.build) $this.on('build.editTable',key.build); 91 | 92 | $this.editTable('refresh'); 93 | }); 94 | } 95 | // option(['key1','key2']) 96 | // get multiple options 97 | else if($.type(key) === "array") { 98 | var ret = {}; 99 | var $this = this.eq(0); 100 | var data = $this.data('editTable'); 101 | if(!data || !data.initialized) $.error('EditTable must be initialized before getting options'); 102 | $.each(key,function(i,k) { 103 | if(typeof data.settings[k] === 'undefined') $.error('Unknown option '+k); 104 | else ret[k] = data.settings[k]; 105 | }); 106 | return ret; 107 | } 108 | // option('key','value') 109 | // set single option 110 | else if(typeof value !== 'undefined') { 111 | return this.editTable('option',{key: value}); 112 | } 113 | // option('key') 114 | // get single option 115 | else { 116 | return this.editTable('option',[key])[key]; 117 | } 118 | }, 119 | 120 | /** 121 | * Refreshes edit table to match the dom. 122 | * Should be called after manipulating the containing table 123 | * DOM element or it's children. 124 | */ 125 | refresh: function() { 126 | return this.each(function() { 127 | var $this = $(this); 128 | 129 | var data = $this.data('editTable'); 130 | if(!data || !data.initialized) $.error("EditTable must be initialized before calling refresh"); 131 | 132 | data.rows = []; 133 | data.cols = []; 134 | data.json = []; 135 | 136 | // Determine columns from first row 137 | $("td,th",$("tr",$this).eq(0)).each(function() { 138 | var colname = $.trim($(this).text()); 139 | 140 | data.settings.columns[colname] = data.settings.columns[colname] || {}; 141 | 142 | // If this column has a class applied 143 | if(data.settings.columns[colname].thClass) { 144 | $(this).addClass(data.settings.columns[colname].thClass); 145 | } 146 | if(data.settings.thClass) { 147 | $(this).addClass(data.settings.thClass); 148 | } 149 | 150 | data.cols.push(colname); 151 | 152 | // Determine the editor to use for this column 153 | var editor, editor_options; 154 | if(!data.settings.columns[colname].editor) { 155 | editor = data.settings.defaultEditor; 156 | editor_options = data.settings.defualtOptions; 157 | } 158 | else { 159 | editor = data.settings.columns[colname].editor; 160 | editor_options = data.settings.columns[colname].options || {}; 161 | } 162 | 163 | if(!$.EditTable.editors[editor]) $.error("Unknown editor "+editor); 164 | data.editors[colname] = new $.EditTable.editors[editor]($this, editor_options); 165 | }); 166 | // Determine row data from remaining rows 167 | $("tr",$this).slice(1).each(function() { 168 | var row = []; 169 | var json = {}; 170 | var $row = $(this); 171 | 172 | $("td",$row).each(function() { 173 | var cell = { 174 | row: data.rows.length, 175 | col: row.length, 176 | colname: data.cols[row.length], 177 | value: $.trim($(this).text()), 178 | el: $(this) 179 | }; 180 | 181 | // If this column has a class applied 182 | if(data.settings.columns[cell.colname].tdClass) { 183 | $(this).addClass(data.settings.columns[cell.colname].tdClass); 184 | } 185 | if(data.settings.tdClass) { 186 | $(this).addClass(data.settings.tdClass); 187 | } 188 | 189 | $(this).data('editTable',cell); 190 | row.push(cell); 191 | json[cell.colname] = cell.value; 192 | }); 193 | 194 | data.rows.push(row); 195 | data.json.push(json); 196 | }); 197 | 198 | $.each(data.rows,function() { 199 | $.each(this,function() { 200 | $this.editTable('set', this.row, this.col, this.value); 201 | }); 202 | }); 203 | }); 204 | }, 205 | 206 | /** 207 | * Get or set the table's rows. 208 | * 209 | * Usage 1 - Get the rows as an array of JSON objects 210 | * var rows = el.editTable('rows'); 211 | * 212 | * Usage 2 - Set the rows from an array of JSON objects 213 | * var rows = [ 214 | * {col1: "value1", col2: "value2"}, 215 | * {col1: "value3", col2: "value4"} 216 | * ]; 217 | * el.editTable('rows', rows); 218 | */ 219 | rows: function(rows) { 220 | // Setting data 221 | if($.type(rows) === 'array' && rows.length) { 222 | return this.each(function() { 223 | var $this = $(this); 224 | 225 | var data = $this.data('editTable'); 226 | if(!data || !data.initialized) $.error("EditTable must be initialized before calling json"); 227 | 228 | $this.empty(); 229 | 230 | var header_row = $("") 231 | if(data.settings.includeTHead) { 232 | header_row.appendTo($("").appendTo($this)); 233 | } 234 | else { 235 | header_row.appendTo($this); 236 | } 237 | 238 | // Determine the headers 239 | var headers = []; 240 | $.each(rows[0],function(header) { 241 | headers.push(header); 242 | $("").text(header).appendTo(header_row); 243 | }); 244 | 245 | // Add each row 246 | var tbody = $this; 247 | if(data.settings.includeTBody) { 248 | tbody = $("").appendTo($this); 249 | } 250 | $.each(rows,function(i,row) { 251 | var $row = $("").appendTo(tbody); 252 | $.each(headers,function() { 253 | $("").text(row[this] || '').appendTo($row); 254 | }); 255 | }); 256 | 257 | $this.editTable('refresh'); 258 | $this.trigger('build.editTable'); 259 | }); 260 | } 261 | // Getting data 262 | else { 263 | // Make sure we're only operating on 1 table 264 | var $this = $(this).eq(0); 265 | 266 | var data = $this.data('editTable'); 267 | if(!data || !data.initialized) $.error("EditTable must be initialized before calling json"); 268 | 269 | // Do a deep copy so modifying this won't mess up the actual data 270 | var ret = []; 271 | $.each(data.json,function() { 272 | ret.push($.extend({},this)); 273 | }); 274 | return ret; 275 | } 276 | }, 277 | 278 | /** 279 | * Start editing a cell. 280 | * @param row The row number of the cell starting at 0 281 | * @param col The column number or column name of the cell 282 | */ 283 | edit: function(row, col) { 284 | var orig = $(this); 285 | 286 | // Make sure we're only operating on 1 table 287 | var $this = $(this).eq(0); 288 | 289 | var data = $this.data('editTable'); 290 | if(!data || !data.initialized) $.error("EditTable must be initialized before calling edit"); 291 | 292 | if(typeof col !== 'number') { 293 | col = $.inArray(col, data.cols); 294 | } 295 | 296 | if(!data.cols[col]) $.error("Unknown column "+col); 297 | if(!data.rows[row]) $.error("Invalid row "+row); 298 | if(!data.rows[row][col]) $.error("Invalid row and column"); 299 | 300 | var cell = data.rows[row][col]; 301 | 302 | // Determine the editor to use for this column 303 | data.current_cell = cell; 304 | 305 | // Stop all open editors 306 | $this.editTable('stop'); 307 | 308 | // Don't start editing a disabled column 309 | if(data.settings.columns[cell.colname] && data.settings.columns[cell.colname].disabled) return orig; 310 | 311 | // Start editing the cell 312 | var called = false; 313 | data.editors[cell.colname].start(cell.el, cell.value, function(val) { 314 | if(called) return; 315 | called = true; 316 | 317 | // Save the value 318 | $this.editTable('set', cell.row, cell.col, val); 319 | }); 320 | 321 | return orig; 322 | }, 323 | 324 | /** 325 | * Stop editing cells in the table and close all editors 326 | */ 327 | stop: function() { 328 | return this.each(function() { 329 | var $this = $(this); 330 | var data = $this.data('editTable'); 331 | if(!data || !data.initialized) $.error("EditTable must be initialized before calling stop"); 332 | 333 | $.each(data.editors,function(i,el) { 334 | el.stop(); 335 | }); 336 | }); 337 | }, 338 | 339 | /** 340 | * Set the value of a cell 341 | * @param row The row number of the cell starting at 0 342 | * @param col The column number or column name of the cell 343 | * @param value The value to set 344 | */ 345 | set: function(row,col,value) { 346 | return this.each(function() { 347 | var $this = $(this); 348 | var data = $this.data('editTable'); 349 | if(!data || !data.initialized) $.error("EditTable must be initialized before calling set"); 350 | 351 | if(typeof col !== 'number') { 352 | col = $.inArray(col, data.cols); 353 | } 354 | 355 | if(!data.cols[col]) $.error("Unknown column "+col); 356 | if(!data.rows[row]) $.error("Invalid row "+row); 357 | if(!data.rows[row][col]) $.error("Invalid row and column"); 358 | 359 | var cell = data.rows[row][col]; 360 | cell.value = value; 361 | data.json[row][cell.colname] = value; 362 | 363 | cell.el.text(value); 364 | 365 | $this.trigger('set.editTable', { 366 | el: cell.el, 367 | col: cell.col, 368 | row: cell.row, 369 | colname: cell.colname, 370 | value: cell.value 371 | }); 372 | }); 373 | }, 374 | 375 | /** 376 | * Get the value of a cell 377 | * @param row The row number of the cell starting at 0 378 | * @param col The column number or column name of the cell 379 | * @return The value 380 | */ 381 | get: function(row,col) { 382 | // Make sure we're only operating on 1 table 383 | var $this = $(this).eq(0); 384 | 385 | var data = $this.data('editTable'); 386 | if(!data || !data.initialized) $.error("EditTable must be initialized before calling edit"); 387 | 388 | if(typeof col !== 'number') { 389 | col = $.inArray(col, data.cols); 390 | } 391 | 392 | if(!data.cols[col]) $.error("Unknown column "+col); 393 | if(!data.rows[row]) $.error("Invalid row "+row); 394 | if(!data.rows[row][col]) $.error("Invalid row and column"); 395 | 396 | return data.rows[row][col]; 397 | }, 398 | 399 | /** 400 | * Change the cell that's currently being edited 401 | * @param direction One of 'left', 'right', 'up', or 'down' 402 | * @param amount The number of cells to move (defaults to 1) 403 | */ 404 | move: function(direction, amount) { 405 | amount = amount || 1; 406 | 407 | return this.each(function() { 408 | var $this = $(this); 409 | var data = $this.data('editTable'); 410 | if(!data || !data.initialized) $.error("EditTable must be initialized before calling move"); 411 | 412 | var row = 0, col = 0; 413 | if(data.current_cell) { 414 | row = data.current_cell.row; 415 | col = data.current_cell.col; 416 | 417 | if(direction === 'left') col-=amount; 418 | else if(direction === 'right') col+=amount; 419 | else if(direction === 'up') row-=amount; 420 | else if(direction === 'down') row+=amount; 421 | else $.error("Unknown direction "+direction); 422 | } 423 | 424 | if(col < 0) { 425 | row --; 426 | col = data.cols.length + col; 427 | // TODO: this will break if going back more than data.cols.length 428 | } 429 | else if(col >= data.cols.length) { 430 | col = col - data.cols.length; 431 | row ++; 432 | // TODO; this will break if going forward more than data.cols.length 433 | } 434 | 435 | // Can't move further up 436 | if(row < 0) return; 437 | // Can't move further down 438 | // TODO: automatically create new row 439 | else if(row >= data.rows.length) return; 440 | 441 | // Skip over disabled cells 442 | if(data.settings.columns[data.cols[col]] && data.settings.columns[data.cols[col]].disabled) { 443 | 444 | $this.editTable('move', direction, amount+1); 445 | } 446 | else { 447 | $this.editTable('edit',row,col); 448 | } 449 | }); 450 | }, 451 | 452 | /** 453 | * Add blank row(s) to the table 454 | * @param num The number of rows to add (defaults to 1) 455 | */ 456 | add: function(num) { 457 | return this.each(function() { 458 | var $this = $(this); 459 | var data = $this.data('editTable'); 460 | if(!data || !data.initialized) $.error("EditTable must be initialized before calling add"); 461 | 462 | num = num || 1; 463 | 464 | // Where we add the row(s) to 465 | var container = $("tbody",$this); 466 | if(container.length === 0) container = $this; 467 | 468 | for(var i=0; i").appendTo(container); 470 | for(var j=0; j").appendTo(row); 472 | } 473 | } 474 | 475 | $this.editTable('refresh'); 476 | }); 477 | }, 478 | 479 | /** 480 | * Remove row from table 481 | * @param row The row to remove (starting at 0). Defaults to last row in table. 482 | */ 483 | remove: function(row) { 484 | return this.each(function() { 485 | var $this = $(this); 486 | var data = $this.data('editTable'); 487 | if(!data || !data.initialized) $.error("EditTable must be initialized before calling remove"); 488 | 489 | var i = row; 490 | if(typeof i === 'undefined') { 491 | i = data.rows.length - 1; 492 | } 493 | if(i < 0) i = 0; 494 | 495 | $("tr",$this).eq(i+1).remove(); 496 | 497 | $this.editTable('refresh'); 498 | }); 499 | }, 500 | 501 | /** 502 | * Remove all event listeners and DOM elements for the editor. 503 | */ 504 | destroy: function() { 505 | return this.each(function() { 506 | var $this = $(this); 507 | 508 | // If it's already been destroyed 509 | if(!$this.data('editTable')) return; 510 | 511 | // Stop any currently running editors 512 | $.each($this.data('editTable').editors,function() { 513 | this.stop(); 514 | }); 515 | 516 | // Remove data and event listeners 517 | $("td",$this).each(function() { 518 | $(this).removeData('editTable'); 519 | }); 520 | $this.off('.editTable'); 521 | $this.removeData('editTable'); 522 | }); 523 | } 524 | }; 525 | 526 | $.fn.editTable = function( method ) { 527 | // Method calling logic 528 | if ( methods[method] ) { 529 | return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 )); 530 | } else if ( typeof method === 'object' || ! method ) { 531 | return methods.init.apply( this, arguments ); 532 | } else { 533 | $.error( 'Method ' + method + ' does not exist on jQuery.editTable' ); 534 | } 535 | }; 536 | 537 | // Helper methods for editors 538 | $.EditTable.helpers = { 539 | /** 540 | * Horizontally center an editor over a table cell and match the cell's height 541 | * @param editor_el The editor element 542 | * @param td The table cell element 543 | */ 544 | centerX: function(editor_el, td) { 545 | var pos = td.position(); 546 | 547 | // Fill the parent height, keep editor's width, horizontally center 548 | var height = td.outerHeight(); 549 | var width = editor_el.outerWidth(); 550 | var left = pos.left + (td.outerWidth() - width)/2; 551 | 552 | editor_el.css({ 553 | position: 'absolute', 554 | top: pos.top, 555 | left: left, 556 | height: height 557 | }); 558 | }, 559 | /** 560 | * Vertically center an editor over a table cell and match the cell's width 561 | * @param editor_el The editor element 562 | * @param td The table cell element 563 | */ 564 | centerY: function(editor_el,td) { 565 | var pos = td.position(); 566 | 567 | // Fill the parent width, keep editor's height, vertically center 568 | var width = td.outerWidth(); 569 | var height = editor_el.outerHeight(); 570 | var top = pos.top + (td.outerHeight() - height)/2; 571 | 572 | editor_el.css({ 573 | position: 'absolute', 574 | top: top, 575 | left: pos.left, 576 | width: width 577 | }); 578 | }, 579 | /** 580 | * Vertically and horizontally center an editor over a table cell 581 | * @param editor_el The editor element 582 | * @param td The table cell element 583 | */ 584 | center: function(editor_el,td) { 585 | var pos = td.position(); 586 | 587 | // Keep editor's width and height, vertically and horizontally center 588 | var width = editor_el.outerWidth(); 589 | var height = editor_el.outerHeight(); 590 | var top = pos.top + (td.outerHeight() - height)/2; 591 | var left = pos.left + (td.outerWidth() - width)/2; 592 | 593 | editor_el.css({ 594 | position: 'absolute', 595 | top: top, 596 | left: left 597 | }); 598 | }, 599 | /** 600 | * Make an editor fully obscure a table cell 601 | * @param editor_el The editor element 602 | * @param td The table cell element 603 | */ 604 | fill: function(editor_el,td) { 605 | var pos = td.position(); 606 | 607 | // Fill the parent 608 | editor_el.css({ 609 | position: 'absolute', 610 | top: pos.top, 611 | left: pos.left, 612 | width: td.outerWidth(), 613 | height: td.outerHeight() 614 | }); 615 | }, 616 | /** 617 | * Add keyboard navigation events to hook up tab/arrow keys. 618 | * @param editor_el The editor element 619 | * @param editTable The editTable element 620 | * @param preserve_arrows If true, up/down arrow navigation will require the shift key. 621 | */ 622 | addKeyboardNavigation: function(editor_el, editTable, preserve_arrows) { 623 | editor_el.on('keydown',function(e) { 624 | // TAB 625 | if(e.which == 9) { 626 | e.preventDefault(); 627 | 628 | // Shift+TAB 629 | if(e.shiftKey) { 630 | editTable.editTable('move','left'); 631 | } 632 | // TAB 633 | else { 634 | editTable.editTable('move','right'); 635 | } 636 | } 637 | // UP ARROW 638 | else if(e.which == 38) { 639 | if(!preserve_arrows || e.shiftKey) { 640 | e.preventDefault(); 641 | editTable.editTable('move','up'); 642 | } 643 | } 644 | // DOWN ARROW 645 | else if(e.which == 40) { 646 | if(!preserve_arrows || e.shiftKey) { 647 | e.preventDefault(); 648 | editTable.editTable('move','down'); 649 | } 650 | } 651 | }); 652 | } 653 | }; 654 | 655 | // Pre-defined Editors 656 | // You can add your own as needed 657 | // Each editor class needs a 'start' and 'stop' function. 658 | 659 | /** 660 | * Text area editor. This is the default editor. 661 | */ 662 | $.EditTable.editors.text = function(editTable, options) { 663 | this.editTable = editTable; 664 | 665 | var self = this; 666 | this.start = function(el, value, callback) { 667 | if(self.el) self.stop(); 668 | 669 | self.callback = callback; 670 | 671 | self.el = $("") 672 | .appendTo(document.body) 673 | .val(value); 674 | 675 | // Hook up tabs/arrow keys events to editor element 676 | $.EditTable.helpers.addKeyboardNavigation(self.el, self.editTable); 677 | 678 | // Position editor element over table cell 679 | $.EditTable.helpers.fill(self.el, el); 680 | 681 | self.el.focus().select(); 682 | 683 | // Hook up blur/enter events 684 | self.el.on('blur',function() { 685 | self.stop(); 686 | }); 687 | self.el.on('keydown',function(e) { 688 | if(e.which == 13) { 689 | // If the shift key was also pressed, pass through to the input 690 | // Otherwise, save the value and stop editing 691 | if(!e.shiftKey) { 692 | e.preventDefault(); 693 | self.stop(); 694 | } 695 | } 696 | }); 697 | }; 698 | 699 | this.stop = function() { 700 | if(!self.el) return; 701 | var temp = self.el; 702 | self.el = null; 703 | 704 | if(self.callback) self.callback(temp.val()); 705 | temp.remove(); 706 | }; 707 | }; 708 | 709 | /** 710 | * Select box editor. 711 | */ 712 | $.EditTable.editors.select = function(editTable, options) { 713 | this.editTable = editTable; 714 | 715 | // If options is an array, convert to object 716 | if($.type(options.values) === 'array') { 717 | var temp = {}; 718 | $.each(options.values,function(k,v) { 719 | temp[v] = v; 720 | }); 721 | options.values = temp; 722 | } 723 | 724 | if($.type(options.values) !== 'object') $.error("Invalid select options"); 725 | 726 | this.options = options.values; 727 | 728 | var self = this; 729 | this.start = function(el, value, callback) { 730 | if(self.el) self.stop(); 731 | 732 | self.callback = callback; 733 | self.el = $("").appendTo(document.body); 734 | 735 | $.each(self.options,function(k,v) { 736 | self.el.append($("").attr('value',k).text(v)); 737 | }); 738 | 739 | self.el.val(value); 740 | 741 | // Hook up tabs/arrow keys events to editor element 742 | $.EditTable.helpers.addKeyboardNavigation(self.el, self.editTable, true); 743 | 744 | // Position editor element over table cell 745 | $.EditTable.helpers.centerY(self.el, el); 746 | 747 | self.el.focus(); 748 | 749 | // Hook up blur/enter events 750 | self.el.on('blur',function() { 751 | self.stop(); 752 | }); 753 | self.el.on('keydown',function(e) { 754 | if(e.which == 13) { 755 | e.preventDefault(); 756 | self.stop(); 757 | } 758 | }); 759 | }; 760 | 761 | this.stop = function() { 762 | if(!self.el) return; 763 | var temp = self.el; 764 | self.el = null; 765 | 766 | if(self.callback) self.callback(temp.val()); 767 | temp.remove(); 768 | }; 769 | }; 770 | 771 | })(jQuery); 772 | --------------------------------------------------------------------------------