├── .travis.yml ├── Gruntfile.js ├── README.md ├── bower.json ├── build └── table-edits.min.js ├── index.html ├── package.json ├── src └── table-edits.js └── test ├── jasmine.html └── table-edits.spec.js /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.12" 4 | before_install: 5 | - npm install -g grunt-cli 6 | install: 7 | - mkdir travis-phantomjs 8 | - wget https://s3.amazonaws.com/travis-phantomjs/phantomjs-2.0.0-ubuntu-12.04.tar.bz2 -O $PWD/travis-phantomjs/phantomjs-2.0.0-ubuntu-12.04.tar.bz2 9 | - tar -xvf $PWD/travis-phantomjs/phantomjs-2.0.0-ubuntu-12.04.tar.bz2 -C $PWD/travis-phantomjs 10 | - export PATH=$PWD/travis-phantomjs:$PATH 11 | - npm install 12 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function(grunt) { 3 | grunt.initConfig({ 4 | pkg: grunt.file.readJSON('package.json'), 5 | uglify: { 6 | build: { 7 | files: { 8 | 'build/<%= pkg.name %>.min.js': 'src/table-edits.js' 9 | } 10 | } 11 | }, 12 | jasmine: { 13 | test: { 14 | src: 'src/table-edits.js', 15 | options: { 16 | vendor: 'node_modules/jquery/dist/jquery.js', 17 | specs: ['test/table-edits.spec.js'] 18 | } 19 | } 20 | } 21 | }); 22 | 23 | grunt.loadNpmTasks('grunt-contrib-uglify'); 24 | grunt.loadNpmTasks('grunt-contrib-jasmine'); 25 | 26 | grunt.registerTask('default', ['uglify']); 27 | }; 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## $.Table Edits 2 | 3 | [![Build Status](https://travis-ci.org/nathancahill/table-edits.svg?branch=master)](https://travis-ci.org/nathancahill/table-edits) 4 | [![File Size](https://badge-size.herokuapp.com/nathancahill/table-edits/master/build/table-edits.min.js.svg?compression=gzip&label=size)](https://raw.githubusercontent.com/nathancahill/table-edits/master/build/table-edits.min.js) 5 | 6 | Table Edits is a lightweight jQuery plugin for making table rows editable. Built as minimally as possible so it's easy to extend. [Demo](http://nathancahill.github.io/table-edits/) 7 | 8 | __Table Edits__ only does a couple things: 9 | 10 | - Replaces cell values with input fields on edit 11 | - Handles row editing state 12 | - Fires callbacks for edit, save and cancel 13 | 14 | And __optionally__, a couple more: 15 | 16 | - Binds a button or double click to start editing 17 | - Binds enter and escape keys to save and cancel 18 | - Maintains column widths when switching to edit 19 | - Create select fields instead of input fields 20 | 21 | #### Options 22 | 23 | ``` 24 | $("table tr").editable({ 25 | keyboard: true, 26 | dblclick: true, 27 | button: true, 28 | buttonSelector: ".edit", 29 | dropdowns: {}, 30 | maintainWidth: true, 31 | edit: function(values) {}, 32 | save: function(values) {}, 33 | cancel: function(values) {} 34 | }); 35 | ``` 36 | 37 | #### Markup 38 | 39 | The only additional markup __Table Edits__ requires is a `data-field` attribute on each editable cell with it's column name: 40 | 41 | ``` 42 | 43 | 44 | 45 | 46 | 47 |
Dave Gamache26Male 48 | " 6 | ], 7 | "license": "MIT", 8 | "homepage": "https://github.com/nathancahill/table-edits", 9 | "ignore": [ 10 | "**/.*", 11 | "node_modules", 12 | "bower_components", 13 | "test", 14 | "tests" 15 | ], 16 | "dependencies": { 17 | "jquery": "~2.1.4" 18 | }, 19 | "devDependencies": { 20 | "font-awesome": "~4.3.0", 21 | "skeleton": "~2.0.4", 22 | "pikaday-skeleton": "~2.0.4", 23 | "momentjs": "~2.10.3" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /build/table-edits.min.js: -------------------------------------------------------------------------------- 1 | !function(a,b,c,d){function e(b,c){this.element=b,this.options=a.extend({},g,c),this._defaults=g,this._name=f,this.init()}var f="editable",g={keyboard:!0,dblclick:!0,button:!0,buttonSelector:".edit",maintainWidth:!0,dropdowns:{},edit:function(){},save:function(){},cancel:function(){}};e.prototype={init:function(){this.editing=!1,this.options.dblclick&&a(this.element).css("cursor","pointer").bind("dblclick",this.toggle.bind(this)),this.options.button&&a(this.options.buttonSelector,this.element).bind("click",this.toggle.bind(this))},toggle:function(a){a.preventDefault(),this.editing=!this.editing,this.editing?this.edit():this.save()},edit:function(){var b=this,c={};a("td[data-field]",this.element).each(function(){var d,e=a(this).data("field"),f=a(this).text(),g=a(this).width();if(c[e]=f,a(this).empty(),b.options.maintainWidth&&a(this).width(g),e in b.options.dropdowns){d=a("");for(var h=0;h").text(b.options.dropdowns[e][h]).appendTo(d);d.val(f).data("old-value",f).dblclick(b._captureEvent)}else d=a('').val(f).data("old-value",f).dblclick(b._captureEvent);d.appendTo(this),b.options.keyboard&&d.keydown(b._captureKey.bind(b))}),this.options.edit.bind(this.element)(c)},save:function(){var b={};a("td[data-field]",this.element).each(function(){var c=a(":input",this).val();b[a(this).data("field")]=c,a(this).empty().text(c)}),this.options.save.bind(this.element)(b)},cancel:function(){var b={};a("td[data-field]",this.element).each(function(){var c=a(":input",this).data("old-value");b[a(this).data("field")]=c,a(this).empty().text(c)}),this.options.cancel.bind(this.element)(b)},_captureEvent:function(a){a.stopPropagation()},_captureKey:function(a){13===a.which?(this.editing=!1,this.save()):27===a.which&&(this.editing=!1,this.cancel())}},a.fn[f]=function(b){return this.each(function(){a.data(this,"plugin_"+f)||a.data(this,"plugin_"+f,new e(this,b))})}}(jQuery,window,document); -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Table Edits jQuery Plugin 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 66 | 67 | 68 | Fork me on GitHub 69 | 70 |
71 |
72 |
73 |

$.Table Edits

74 |

75 | Table Edits is a lightweight jQuery plugin for making table rows editable. 76 | Built as minimally as possible so it's easy to extend. 77 |

78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 110 | 111 | 112 |
NameBirthdayAgeSexEdit
Dave GamacheMay 19, 201526Male 95 | 96 | 97 | 98 |
Dwayne JohnsonMay 19, 201542Male 106 | 107 | 108 | 109 |
113 |
114 |
115 |
116 |
117 |

118 | Table Edits only does a couple things: 119 |

    120 |
  • Replaces cell values with input fields on edit
  • 121 |
  • Handles row editing state
  • 122 |
  • Fires callbacks for edit, save and cancel
  • 123 |
124 | And optionally, a couple more: 125 |

126 |
    127 |
  • Binds a button or double click to start editing
  • 128 |
  • Binds enter and escape keys to save and cancel
  • 129 |
  • Maintains column widths when switching to edit
  • 130 |
  • Create select fields instead of input fields
  • 131 |
132 |

133 |
134 |
135 |
$("table tr").editable({
136 |     keyboard: true,
137 |     dblclick: true,
138 |     button: true,
139 |     buttonSelector: ".edit",
140 |     dropdowns: {},
141 |     maintainWidth: true,
142 |     edit: function(values) {},
143 |     save: function(values) {},
144 |     cancel: function(values) {}
145 | });
146 |
147 |
148 |
149 |
150 |

151 | The only additional markup Table Edits requires 152 | is a data-field attribute on each editable cell with it's column name. 153 |

154 |
155 |
156 |
157 |
158 |
Saving Table Data
159 |

160 | Table Edits makes it easy to save edits. Callbacks are passed a values 161 | object with column names and values of the edited row. 162 |

163 | Posting the new data to an API endpoint is simple. 164 |

165 |
166 |
167 |
$("table tr").editable({
168 |     save: function(values) {
169 |       var id = $(this).data('id');
170 |       $.post('/api/object/' + id, values);
171 |     }
172 | });
173 |
174 |
175 |
176 | 177 | 178 | 179 | 180 | 181 | 225 | 226 | 227 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "table-edits", 3 | "version": "0.0.3", 4 | "description": "A lightweight jQuery plugin for making table rows editable", 5 | "main": "src/table-edits.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/nathancahill/table-edits.git" 9 | }, 10 | "keywords": [ 11 | "jquery", "table", "editable" 12 | ], 13 | "author": "Nathan Cahill ", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/nathancahill/table-edits/issues" 17 | }, 18 | "homepage": "https://github.com/nathancahill/table-edits#readme", 19 | "devDependencies": { 20 | "bower": "^1.3.12", 21 | "grunt": "^0.4.5", 22 | "grunt-contrib-jasmine": "nathancahill/grunt-contrib-jasmine", 23 | "grunt-contrib-uglify": "^0.9.1", 24 | "jasmine": "^2.3.2", 25 | "jquery": "^2.1.4" 26 | }, 27 | "scripts": { 28 | "test": "./node_modules/.bin/grunt jasmine" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/table-edits.js: -------------------------------------------------------------------------------- 1 | 2 | ;(function ($, window, document, undefined) { 3 | var pluginName = "editable", 4 | defaults = { 5 | keyboard: true, 6 | dblclick: true, 7 | button: true, 8 | buttonSelector: ".edit", 9 | maintainWidth: true, 10 | dropdowns: {}, 11 | edit: function() {}, 12 | save: function() {}, 13 | cancel: function() {} 14 | }; 15 | 16 | function editable(element, options) { 17 | this.element = element; 18 | this.options = $.extend({}, defaults, options) ; 19 | 20 | this._defaults = defaults; 21 | this._name = pluginName; 22 | 23 | this.init(); 24 | } 25 | 26 | editable.prototype = { 27 | init: function() { 28 | this.editing = false; 29 | 30 | if (this.options.dblclick) { 31 | $(this.element) 32 | .css('cursor', 'pointer') 33 | .bind('dblclick', this.toggle.bind(this)); 34 | } 35 | 36 | if (this.options.button) { 37 | $(this.options.buttonSelector, this.element) 38 | .bind('click', this.toggle.bind(this)); 39 | } 40 | }, 41 | 42 | toggle: function(e) { 43 | e.preventDefault(); 44 | 45 | this.editing = !this.editing; 46 | 47 | if (this.editing) { 48 | this.edit(); 49 | } else { 50 | this.save(); 51 | } 52 | }, 53 | 54 | edit: function() { 55 | var instance = this, 56 | values = {}; 57 | 58 | $('td[data-field]', this.element).each(function() { 59 | var input, 60 | field = $(this).data('field'), 61 | value = $(this).text(), 62 | width = $(this).width(); 63 | 64 | values[field] = value; 65 | 66 | $(this).empty(); 67 | 68 | if (instance.options.maintainWidth) { 69 | $(this).width(width); 70 | } 71 | 72 | if (field in instance.options.dropdowns) { 73 | input = $(''); 74 | 75 | for (var i = 0; i < instance.options.dropdowns[field].length; i++) { 76 | $('') 77 | .text(instance.options.dropdowns[field][i]) 78 | .appendTo(input); 79 | }; 80 | 81 | input.val(value) 82 | .data('old-value', value) 83 | .dblclick(instance._captureEvent); 84 | } else { 85 | input = $('') 86 | .val(value) 87 | .data('old-value', value) 88 | .dblclick(instance._captureEvent); 89 | } 90 | 91 | input.appendTo(this); 92 | 93 | if (instance.options.keyboard) { 94 | input.keydown(instance._captureKey.bind(instance)); 95 | } 96 | }); 97 | 98 | this.options.edit.bind(this.element)(values); 99 | }, 100 | 101 | save: function() { 102 | var instance = this, 103 | values = {}; 104 | 105 | $('td[data-field]', this.element).each(function() { 106 | var value = $(':input', this).val(); 107 | 108 | values[$(this).data('field')] = value; 109 | 110 | $(this).empty() 111 | .text(value); 112 | }); 113 | 114 | this.options.save.bind(this.element)(values); 115 | }, 116 | 117 | cancel: function() { 118 | var instance = this, 119 | values = {}; 120 | 121 | $('td[data-field]', this.element).each(function() { 122 | var value = $(':input', this).data('old-value'); 123 | 124 | values[$(this).data('field')] = value; 125 | 126 | $(this).empty() 127 | .text(value); 128 | }); 129 | 130 | this.options.cancel.bind(this.element)(values); 131 | }, 132 | 133 | _captureEvent: function(e) { 134 | e.stopPropagation(); 135 | }, 136 | 137 | _captureKey: function(e) { 138 | if (e.which === 13) { 139 | this.editing = false; 140 | this.save(); 141 | } else if (e.which === 27) { 142 | this.editing = false; 143 | this.cancel(); 144 | } 145 | } 146 | }; 147 | 148 | $.fn[pluginName] = function(options) { 149 | return this.each(function () { 150 | if (!$.data(this, "plugin_" + pluginName)) { 151 | $.data(this, "plugin_" + pluginName, 152 | new editable(this, options)); 153 | } 154 | }); 155 | }; 156 | 157 | })(jQuery, window, document); 158 | -------------------------------------------------------------------------------- /test/jasmine.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test/table-edits.spec.js: -------------------------------------------------------------------------------- 1 | 2 | describe('Table-Edits', function() { 3 | beforeEach(function() { 4 | this.table = document.createElement('table'); 5 | this.row = document.createElement('tr'); 6 | this.cell = document.createElement('td'); 7 | this.cell.innerHTML = 'Hello World'; 8 | this.cell.setAttribute('data-field', 'message'); 9 | this.editCell = document.createElement('td'); 10 | this.edit = document.createElement('a'); 11 | this.edit.setAttribute('class', 'edit'); 12 | 13 | document.body.appendChild(this.table) 14 | this.table.appendChild(this.row); 15 | this.row.appendChild(this.cell); 16 | this.row.appendChild(this.editCell); 17 | this.editCell.appendChild(this.edit); 18 | }); 19 | 20 | afterEach(function() { 21 | document.body.removeChild(this.table); 22 | }); 23 | 24 | it('does not change values when initiated', function() { 25 | $('table tr').editable(); 26 | 27 | expect(this.cell.innerHTML).toBe('Hello World'); 28 | }); 29 | 30 | it('replaces cell values with input fields on edit', function() { 31 | $('table tr').editable(); 32 | 33 | this.edit.click(); 34 | 35 | expect(this.cell.firstChild.tagName.toLowerCase()).toBe('input'); 36 | }); 37 | 38 | it('sets input values to table cell values on edit', function() { 39 | $('table tr').editable(); 40 | 41 | this.edit.click(); 42 | 43 | expect(this.cell.firstChild.value).toBe('Hello World'); 44 | }); 45 | 46 | it('triggers an edit on double click', function() { 47 | $('table tr').editable(); 48 | 49 | $(this.row).dblclick(); 50 | 51 | expect(this.cell.firstChild.tagName.toLowerCase()).toBe('input'); 52 | }); 53 | 54 | it('saves values on when edit is clicked again', function() { 55 | $('table tr').editable(); 56 | 57 | this.edit.click(); 58 | this.cell.firstChild.value = 'Hi there!'; 59 | this.edit.click(); 60 | 61 | expect(this.cell.innerHTML).toBe('Hi there!'); 62 | }); 63 | 64 | it('disables button when button is false', function() { 65 | $('table tr').editable({ 66 | button: false 67 | }); 68 | 69 | this.edit.click(); 70 | 71 | expect(this.cell.innerHTML).toBe('Hello World'); 72 | }); 73 | 74 | it('allows button selector to change', function() { 75 | this.edit.setAttribute('class', 'change'); 76 | 77 | $('table tr').editable({ 78 | buttonSelector: '.change' 79 | }); 80 | 81 | this.edit.click(); 82 | 83 | expect(this.cell.firstChild.tagName.toLowerCase()).toBe('input'); 84 | }); 85 | 86 | it('maintians width on edit', function() { 87 | $('table tr').editable(); 88 | 89 | var width = this.editCell.clientWidth; 90 | 91 | this.edit.click(); 92 | 93 | expect(this.editCell.clientWidth).toBe(width); 94 | }); 95 | 96 | it('supports dropdowns', function() { 97 | $('table tr').editable({ 98 | dropdowns: { 99 | message: [ 100 | 'Hello World', 101 | 'Hi there!' 102 | ] 103 | } 104 | }); 105 | 106 | this.edit.click(); 107 | 108 | expect(this.cell.firstChild.tagName.toLowerCase()).toBe('select'); 109 | }); 110 | 111 | it('does not bind fields without data-field attribute', function() { 112 | this.edit.removeAttribute('data-field'); 113 | 114 | $('table tr').editable(); 115 | 116 | expect(this.cell.innerHTML).toBe('Hello World'); 117 | }); 118 | 119 | it('support the enter key for save', function() { 120 | $('table tr').editable(); 121 | 122 | this.edit.click(); 123 | this.cell.firstChild.value = 'Hi there!'; 124 | 125 | var e = $.Event('keydown'); 126 | e.which = 13; 127 | 128 | $(this.cell.firstChild).trigger(e); 129 | 130 | expect(this.cell.innerHTML).toBe('Hi there!'); 131 | }); 132 | 133 | it('support the escape key for cancel', function() { 134 | $('table tr').editable(); 135 | 136 | this.edit.click(); 137 | this.cell.firstChild.value = 'Hi there!'; 138 | 139 | var e = $.Event('keydown'); 140 | e.which = 27; 141 | 142 | $(this.cell.firstChild).trigger(e); 143 | 144 | expect(this.cell.innerHTML).toBe('Hello World'); 145 | }); 146 | 147 | it('disables keyboard when keyboard is false', function() { 148 | $('table tr').editable({ 149 | keyboard: false 150 | }); 151 | 152 | this.edit.click(); 153 | this.cell.firstChild.value = 'Hi there!'; 154 | 155 | var e = $.Event('keydown'); 156 | e.which = 13; 157 | 158 | $(this.cell.firstChild).trigger(e); 159 | 160 | expect(this.cell.firstChild.tagName.toLowerCase()).toBe('input'); 161 | }); 162 | 163 | it('calls the edit callback on edit', function() { 164 | var spy = { 165 | callback: function (values) {} 166 | } 167 | 168 | spyOn(spy, 'callback'); 169 | 170 | $('table tr').editable({ 171 | edit: spy.callback 172 | }); 173 | 174 | this.edit.click(); 175 | 176 | expect(spy.callback).toHaveBeenCalledWith({'message': 'Hello World'}); 177 | }); 178 | 179 | it('calls the save callback on save', function() { 180 | var spy = { 181 | callback: function (values) {} 182 | } 183 | 184 | spyOn(spy, 'callback'); 185 | 186 | $('table tr').editable({ 187 | save: spy.callback 188 | }); 189 | 190 | this.edit.click(); 191 | this.cell.firstChild.value = 'Hi there!'; 192 | this.edit.click(); 193 | 194 | expect(spy.callback).toHaveBeenCalledWith({'message': 'Hi there!'}); 195 | }); 196 | 197 | it('calls the cancel callback on cancel', function() { 198 | var spy = { 199 | callback: function (values) {} 200 | } 201 | 202 | spyOn(spy, 'callback'); 203 | 204 | $('table tr').editable({ 205 | cancel: spy.callback 206 | }); 207 | 208 | this.edit.click(); 209 | this.cell.firstChild.value = 'Hi there!'; 210 | 211 | var e = $.Event('keydown'); 212 | e.which = 27; 213 | 214 | $(this.cell.firstChild).trigger(e); 215 | 216 | expect(spy.callback).toHaveBeenCalledWith({'message': 'Hello World'}); 217 | }); 218 | }); 219 | --------------------------------------------------------------------------------