Skip to content

Commit 7f672cc

Browse files
author
rajat
committedMar 13, 2018
key shortcuts functionality added
1 parent e784f57 commit 7f672cc

12 files changed

+361
-47
lines changed
 

‎.npmignore

+1
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ index.html
88
.travis.yml
99
bower.json
1010
spec/
11+
src/

‎README.md

+13
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,13 @@ You can add multiple select boxes , use your imagination.
259259
}
260260
```
261261

262+
## Keyboard shortcuts
263+
* <kbd>tab</kbd> - Move to right cell by pressing tab.
264+
* <kbd>shift + tab</kbd> - Move to left cell by shift + tab.
265+
* <kbd>top arrow</kbd> - Move to upper cell by pressing upper arrow.
266+
* <kbd>down arrow</kbd> - Move to upper cell by pressing down arrow.
267+
* <kbd>ctrl + z</kbd> - Press ctrl + z to undo the changes.
268+
* <kbd>shift + ctrl + z</kbd> - Press shift + ctrl + z to redo the changes.
262269

263270
## Test
264271
`npm test`
@@ -269,5 +276,11 @@ You can add multiple select boxes , use your imagination.
269276
* Run `gulp build`
270277
* Generate a PR
271278

279+
## WIP
280+
todos
281+
* Implement undo/redo functionality
282+
* Css of meta fields
283+
* Write tests
284+
272285
## License
273286
LICENSE (MIT)

‎dist/json-table-editor.js

+173-23
Original file line numberDiff line numberDiff line change
@@ -14,29 +14,41 @@ JSON table is a minimal, yet flexible HTML table editor, where you can attach fo
1414
var COLUMN_WIDTH = 16
1515
var BORDER_WIDTH = 1
1616
var DEFAULTOPTIONS = {
17-
formatOptions: [{
18-
type: 'button',
19-
name: 'bold',
20-
innerHTML: 'Bold'
21-
},
22-
{
23-
type: 'button',
24-
name: 'italic',
25-
innerHTML: 'Italic'
26-
},
27-
{
28-
type: 'radio',
29-
name: 'align',
30-
options: ['left', 'center', 'right']
31-
}
17+
formatOptions: [
18+
{
19+
type: 'button',
20+
name: 'bold',
21+
innerHTML: 'Bold'
22+
},
23+
{
24+
type: 'button',
25+
name: 'italic',
26+
innerHTML: 'Italic'
27+
},
28+
{
29+
type: 'radio',
30+
name: 'align',
31+
options: ['left', 'center', 'right']
32+
}
3233
],
3334
gridColumns: 10,
3435
gridRows: 10,
3536
selectorWrongMsg: 'Can\'t find html element with given selector.',
37+
metaFieldsId: 'jt-meta-fields',
3638
formatOptionsId: 'jt-format-options',
3739
colBtnId: 'jt-col-btn',
3840
rowBtnId: 'jt-row-btn',
39-
tableMainClass: 'js-main-table'
41+
tableMainClass: 'js-main-table',
42+
metaFields: [
43+
{
44+
type: 'string',
45+
name: 'name'
46+
},
47+
{
48+
type: 'string',
49+
name: 'description'
50+
}
51+
]
4052
}
4153

4254
function Grid (el, callback, rows, columns) {
@@ -157,23 +169,72 @@ Grid.prototype = {
157169
}
158170
}
159171

160-
function JSONTableView (container, formatOptions) {
161-
return this.init(container, formatOptions)
172+
function JSONTableKeyboardShortcuts (view, model) {
173+
return this.init(view, model)
174+
}
175+
176+
JSONTableKeyboardShortcuts.prototype = {
177+
init: function (view, model) {
178+
this.view = view
179+
this.model = model
180+
this.bindUpKey()
181+
this.bindDownKey()
182+
this.bindUndoKey()
183+
this.bindRedoKey()
184+
},
185+
186+
bindUpKey: function () {
187+
var self = this;
188+
this.view.container.addEventListener('keydown', function(event){
189+
if (event.keyCode === 38 && self.model.currentCell) {
190+
self.moveArrowUpDown(-1)
191+
}
192+
})
193+
},
194+
195+
bindDownKey: function () {
196+
var self = this;
197+
this.view.container.addEventListener('keydown', function(event){
198+
if (event.keyCode === 40 && self.model.currentCell) {
199+
self.moveArrowUpDown(1)
200+
}
201+
})
202+
},
203+
204+
moveArrowUpDown: function (direction, keyCode) {
205+
var currentCell = Object.assign({}, this.model.currentCell)
206+
currentCell.row = (Number(currentCell.row) + direction + this.model.meta.rows)%this.model.meta.rows
207+
this.view.focusCurrentCell(currentCell)
208+
},
209+
210+
bindUndoKey: function () {
211+
212+
},
213+
214+
bindRedoKey: function () {
215+
216+
}
217+
}
218+
219+
function JSONTableView (container, formatOptions, metaFields) {
220+
return this.init(container, formatOptions, metaFields)
162221
}
163222

164223
JSONTableView.prototype = {
165-
init: function (container, formatOptions) {
224+
init: function (container, formatOptions, metaFields) {
166225
this.table = document.createElement('table')
167226
this.table.setAttribute('class', DEFAULTOPTIONS.tableMainClass)
168227
this.cellTag = 'td'
169228
this.container = container
170229
this.formatOptions = formatOptions
230+
this.metaFields = metaFields
231+
this.metaFieldsId = DEFAULTOPTIONS.metaFieldsId
171232
this.formatOptionsId = DEFAULTOPTIONS.formatOptionsId
172233
this.colBtnId = DEFAULTOPTIONS.colBtnId
173234
this.rowBtnId = DEFAULTOPTIONS.rowBtnId
174235
},
175236

176-
insert: function () {
237+
insert: function (model) {
177238
var container = this.container
178239
if (container.firstChild) {
179240
container.replaceChild(this.table, container.childNodes[0])
@@ -182,6 +243,8 @@ JSONTableView.prototype = {
182243
}
183244
container.insertAdjacentHTML('afterbegin', this.formatOptionsContainer())
184245
this.updateFormatOptions()
246+
container.insertAdjacentHTML('afterbegin', this.metaFieldsContainer())
247+
this.updateMetaFields(model)
185248
container.insertAdjacentHTML('beforeend', this.utilButtons())
186249
},
187250

@@ -241,6 +304,28 @@ JSONTableView.prototype = {
241304
return html
242305
},
243306

307+
updateMetaFields: function (model) {
308+
var html = '';
309+
for (var i = 0; i < this.metaFields.length; i++) {
310+
var field = this.metaFields[i]
311+
if (field.type === "string"){
312+
html += field.name + ":" + '<input type="text" name="' + field.name + '" data-metakey="' + field.name + '" value="' + JSONTable.orEmpty(model.meta[field.name]) + '"><br>'
313+
} else if (field.type === "integer") {
314+
html += field.name + ":" + '<input type="number" name="' + field.name + '" data-metakey="' + field.name + '" value="' + JSONTable.orEmpty(model.meta[field.name]) + '"><br>'
315+
} else if (field.type === "select") {
316+
html += field.name + ":" +'<select' + ' data-metakey="' + field.name + '">'
317+
for (var j = 0; j < field.options.length; j++) {
318+
html += '<option value="' + field.options[j] + '"'
319+
html += (model.meta[field.name] === field.options[j] ? ' selected' : '')
320+
html += '>' + field.options[j] + '</option>'
321+
}
322+
html += '</select><br>'
323+
}
324+
}
325+
var metaFieldsContainer = JSONTable.qs('#' + this.metaFieldsId, this.container)
326+
metaFieldsContainer.innerHTML = html
327+
},
328+
244329
focusCurrentCell: function (currentCell) {
245330
var selector = "[data-row='" + String(currentCell.row) + "'][data-col='" + String(currentCell.col) + "']"
246331
var cell = JSONTable.qs(selector, this.container)
@@ -250,6 +335,12 @@ JSONTableView.prototype = {
250335
}
251336
},
252337

338+
metaFieldsContainer: function () {
339+
var html = '<div class="jt-meta-fields" id="' + this.metaFieldsId + '">'
340+
html += '</div>'
341+
return html
342+
},
343+
253344
formatOptionsContainer: function () {
254345
var html = '<div class="jt-format-options" id="' + this.formatOptionsId + '">'
255346
html += '</div>'
@@ -352,6 +443,10 @@ JSONTableModel.prototype = {
352443
this.data[row][column].content = event.target.innerHTML
353444
},
354445

446+
updateMetaContent: function (event) {
447+
this.meta[event.target.dataset.metakey] = event.target.value
448+
},
449+
355450
addARow: function () {
356451
this.meta.rows += 1
357452
this.updateDataAddRemoveExtraRowColumn()
@@ -444,6 +539,7 @@ JSONTableController.prototype = {
444539
self.handleCellBlur(e)
445540
})
446541
this.bindEventOnFormatingOptions()
542+
this.bindEventOnMetaFields()
447543
},
448544

449545
bindEventOnCell: function (type, handler) {
@@ -465,6 +561,18 @@ JSONTableController.prototype = {
465561
}
466562
},
467563

564+
bindEventOnMetaFields: function () {
565+
var self = this
566+
JSONTable.delegate(
567+
JSONTable.qs('#' + this.view.metaFieldsId, this.view.container),
568+
'input, select',
569+
'change',
570+
function (e) {
571+
self.handleMetaChange(e)
572+
}
573+
)
574+
},
575+
468576
handleCellFocus: function (event) {
469577
var self = this
470578
this.model.setCurrentCell(event.target.dataset)
@@ -486,6 +594,11 @@ JSONTableController.prototype = {
486594
}, 15)
487595
},
488596

597+
handleMetaChange: function (event) {
598+
this.model.updateMetaContent(event)
599+
this.view.container.dispatchEvent(this.model.data_changed_event)
600+
},
601+
489602
handleBtnClick: function (event) {
490603
this.blur_created_by_button_click = true
491604
event.preventDefault()
@@ -510,7 +623,7 @@ JSONTableController.prototype = {
510623
}
511624
}
512625

513-
function JSONTable (selector, tableData, options) {
626+
function JSONTable (selector, options, tableData) {
514627
return this.init(selector, tableData, options)
515628
}
516629

@@ -522,12 +635,13 @@ JSONTable.prototype = {
522635
this.gridRows = options.gridRows || DEFAULTOPTIONS.gridRows
523636
this.gridColumns = options.gridColumns || DEFAULTOPTIONS.gridColumns
524637
this.formatOptions = options.formatOptions || DEFAULTOPTIONS.formatOptions
638+
this.metaFields = options.metaFields || DEFAULTOPTIONS.metaFields
525639
this.container = JSONTable.qs(selector)
526640
if (!this.container) {
527641
throw DEFAULTOPTIONS.selectorWrongMsg
528642
}
529643
this.model = new JSONTableModel(tableData)
530-
this.view = new JSONTableView(this.container, this.formatOptions)
644+
this.view = new JSONTableView(this.container, this.formatOptions, this.metaFields)
531645
this.controller = new JSONTableController(this.view, this.model)
532646
this.setupTable()
533647
},
@@ -549,9 +663,10 @@ JSONTable.prototype = {
549663
},
550664

551665
initTable: function () {
552-
this.view.insert()
666+
this.view.insert(this.model)
553667
this.view.update(this.model)
554668
this.controller.bindEvents()
669+
this.keyboardHandler = new JSONTableKeyboardShortcuts(this.view, this.model)
555670
}
556671
}
557672

@@ -602,5 +717,40 @@ JSONTable.prototype = {
602717
}
603718
}
604719

720+
JSONTable.orEmpty = function (entity) {
721+
return entity || ""
722+
}
723+
724+
// polyfill for Object.assign
725+
if (typeof Object.assign != 'function') {
726+
// Must be writable: true, enumerable: false, configurable: true
727+
Object.defineProperty(Object, "assign", {
728+
value: function assign(target, varArgs) { // .length of function is 2
729+
'use strict';
730+
if (target == null) { // TypeError if undefined or null
731+
throw new TypeError('Cannot convert undefined or null to object');
732+
}
733+
734+
var to = Object(target);
735+
736+
for (var index = 1; index < arguments.length; index++) {
737+
var nextSource = arguments[index];
738+
739+
if (nextSource != null) { // Skip over if undefined or null
740+
for (var nextKey in nextSource) {
741+
// Avoid bugs when hasOwnProperty is shadowed
742+
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
743+
to[nextKey] = nextSource[nextKey];
744+
}
745+
}
746+
}
747+
}
748+
return to;
749+
},
750+
writable: true,
751+
configurable: true
752+
});
753+
}
754+
605755
window.JSONTableEditor = JSONTable
606756
})()

‎dist/json-table-editor.min.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎gulpfile.js

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ var concateJS = function () {
2323
"start.js",
2424
"constants.js",
2525
"grid.js",
26+
"keyboard_shortcuts.js",
2627
"table_view.js",
2728
"table_model.js",
2829
"table_controller.js",

‎src/js/constants.js

+28-16
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,39 @@
11
var COLUMN_WIDTH = 16
22
var BORDER_WIDTH = 1
33
var DEFAULTOPTIONS = {
4-
formatOptions: [{
5-
type: 'button',
6-
name: 'bold',
7-
innerHTML: 'Bold'
8-
},
9-
{
10-
type: 'button',
11-
name: 'italic',
12-
innerHTML: 'Italic'
13-
},
14-
{
15-
type: 'radio',
16-
name: 'align',
17-
options: ['left', 'center', 'right']
18-
}
4+
formatOptions: [
5+
{
6+
type: 'button',
7+
name: 'bold',
8+
innerHTML: 'Bold'
9+
},
10+
{
11+
type: 'button',
12+
name: 'italic',
13+
innerHTML: 'Italic'
14+
},
15+
{
16+
type: 'radio',
17+
name: 'align',
18+
options: ['left', 'center', 'right']
19+
}
1920
],
2021
gridColumns: 10,
2122
gridRows: 10,
2223
selectorWrongMsg: 'Can\'t find html element with given selector.',
24+
metaFieldsId: 'jt-meta-fields',
2325
formatOptionsId: 'jt-format-options',
2426
colBtnId: 'jt-col-btn',
2527
rowBtnId: 'jt-row-btn',
26-
tableMainClass: 'js-main-table'
28+
tableMainClass: 'js-main-table',
29+
metaFields: [
30+
{
31+
type: 'string',
32+
name: 'name'
33+
},
34+
{
35+
type: 'string',
36+
name: 'description'
37+
}
38+
]
2739
}

‎src/js/keyboard_shortcuts.js

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
function JSONTableKeyboardShortcuts (view, model) {
2+
return this.init(view, model)
3+
}
4+
5+
JSONTableKeyboardShortcuts.prototype = {
6+
init: function (view, model) {
7+
this.view = view
8+
this.model = model
9+
this.bindUpKey()
10+
this.bindDownKey()
11+
this.bindUndoKey()
12+
this.bindRedoKey()
13+
},
14+
15+
bindUpKey: function () {
16+
var self = this;
17+
this.view.container.addEventListener('keydown', function(event){
18+
if (event.keyCode === 38 && self.model.currentCell) {
19+
self.moveArrowUpDown(-1)
20+
}
21+
})
22+
},
23+
24+
bindDownKey: function () {
25+
var self = this;
26+
this.view.container.addEventListener('keydown', function(event){
27+
if (event.keyCode === 40 && self.model.currentCell) {
28+
self.moveArrowUpDown(1)
29+
}
30+
})
31+
},
32+
33+
moveArrowUpDown: function (direction, keyCode) {
34+
var currentCell = Object.assign({}, this.model.currentCell)
35+
currentCell.row = (Number(currentCell.row) + direction + this.model.meta.rows)%this.model.meta.rows
36+
this.view.focusCurrentCell(currentCell)
37+
},
38+
39+
bindUndoKey: function () {
40+
41+
},
42+
43+
bindRedoKey: function () {
44+
45+
}
46+
}

‎src/js/table_controller.js

+18
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ JSONTableController.prototype = {
1717
self.handleCellBlur(e)
1818
})
1919
this.bindEventOnFormatingOptions()
20+
this.bindEventOnMetaFields()
2021
},
2122

2223
bindEventOnCell: function (type, handler) {
@@ -38,6 +39,18 @@ JSONTableController.prototype = {
3839
}
3940
},
4041

42+
bindEventOnMetaFields: function () {
43+
var self = this
44+
JSONTable.delegate(
45+
JSONTable.qs('#' + this.view.metaFieldsId, this.view.container),
46+
'input, select',
47+
'change',
48+
function (e) {
49+
self.handleMetaChange(e)
50+
}
51+
)
52+
},
53+
4154
handleCellFocus: function (event) {
4255
var self = this
4356
this.model.setCurrentCell(event.target.dataset)
@@ -59,6 +72,11 @@ JSONTableController.prototype = {
5972
}, 15)
6073
},
6174

75+
handleMetaChange: function (event) {
76+
this.model.updateMetaContent(event)
77+
this.view.container.dispatchEvent(this.model.data_changed_event)
78+
},
79+
6280
handleBtnClick: function (event) {
6381
this.blur_created_by_button_click = true
6482
event.preventDefault()

‎src/js/table_handler.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
function JSONTable (selector, tableData, options) {
1+
function JSONTable (selector, options, tableData) {
22
return this.init(selector, tableData, options)
33
}
44

@@ -10,12 +10,13 @@ JSONTable.prototype = {
1010
this.gridRows = options.gridRows || DEFAULTOPTIONS.gridRows
1111
this.gridColumns = options.gridColumns || DEFAULTOPTIONS.gridColumns
1212
this.formatOptions = options.formatOptions || DEFAULTOPTIONS.formatOptions
13+
this.metaFields = options.metaFields || DEFAULTOPTIONS.metaFields
1314
this.container = JSONTable.qs(selector)
1415
if (!this.container) {
1516
throw DEFAULTOPTIONS.selectorWrongMsg
1617
}
1718
this.model = new JSONTableModel(tableData)
18-
this.view = new JSONTableView(this.container, this.formatOptions)
19+
this.view = new JSONTableView(this.container, this.formatOptions, this.metaFields)
1920
this.controller = new JSONTableController(this.view, this.model)
2021
this.setupTable()
2122
},
@@ -37,8 +38,9 @@ JSONTable.prototype = {
3738
},
3839

3940
initTable: function () {
40-
this.view.insert()
41+
this.view.insert(this.model)
4142
this.view.update(this.model)
4243
this.controller.bindEvents()
44+
this.keyboardHandler = new JSONTableKeyboardShortcuts(this.view, this.model)
4345
}
4446
}

‎src/js/table_model.js

+4
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ JSONTableModel.prototype = {
3939
this.data[row][column].content = event.target.innerHTML
4040
},
4141

42+
updateMetaContent: function (event) {
43+
this.meta[event.target.dataset.metakey] = event.target.value
44+
},
45+
4246
addARow: function () {
4347
this.meta.rows += 1
4448
this.updateDataAddRemoveExtraRowColumn()

‎src/js/table_view.js

+36-4
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
1-
function JSONTableView (container, formatOptions) {
2-
return this.init(container, formatOptions)
1+
function JSONTableView (container, formatOptions, metaFields) {
2+
return this.init(container, formatOptions, metaFields)
33
}
44

55
JSONTableView.prototype = {
6-
init: function (container, formatOptions) {
6+
init: function (container, formatOptions, metaFields) {
77
this.table = document.createElement('table')
88
this.table.setAttribute('class', DEFAULTOPTIONS.tableMainClass)
99
this.cellTag = 'td'
1010
this.container = container
1111
this.formatOptions = formatOptions
12+
this.metaFields = metaFields
13+
this.metaFieldsId = DEFAULTOPTIONS.metaFieldsId
1214
this.formatOptionsId = DEFAULTOPTIONS.formatOptionsId
1315
this.colBtnId = DEFAULTOPTIONS.colBtnId
1416
this.rowBtnId = DEFAULTOPTIONS.rowBtnId
1517
},
1618

17-
insert: function () {
19+
insert: function (model) {
1820
var container = this.container
1921
if (container.firstChild) {
2022
container.replaceChild(this.table, container.childNodes[0])
@@ -23,6 +25,8 @@ JSONTableView.prototype = {
2325
}
2426
container.insertAdjacentHTML('afterbegin', this.formatOptionsContainer())
2527
this.updateFormatOptions()
28+
container.insertAdjacentHTML('afterbegin', this.metaFieldsContainer())
29+
this.updateMetaFields(model)
2630
container.insertAdjacentHTML('beforeend', this.utilButtons())
2731
},
2832

@@ -82,6 +86,28 @@ JSONTableView.prototype = {
8286
return html
8387
},
8488

89+
updateMetaFields: function (model) {
90+
var html = '';
91+
for (var i = 0; i < this.metaFields.length; i++) {
92+
var field = this.metaFields[i]
93+
if (field.type === "string"){
94+
html += field.name + ":" + '<input type="text" name="' + field.name + '" data-metakey="' + field.name + '" value="' + JSONTable.orEmpty(model.meta[field.name]) + '"><br>'
95+
} else if (field.type === "integer") {
96+
html += field.name + ":" + '<input type="number" name="' + field.name + '" data-metakey="' + field.name + '" value="' + JSONTable.orEmpty(model.meta[field.name]) + '"><br>'
97+
} else if (field.type === "select") {
98+
html += field.name + ":" +'<select' + ' data-metakey="' + field.name + '">'
99+
for (var j = 0; j < field.options.length; j++) {
100+
html += '<option value="' + field.options[j] + '"'
101+
html += (model.meta[field.name] === field.options[j] ? ' selected' : '')
102+
html += '>' + field.options[j] + '</option>'
103+
}
104+
html += '</select><br>'
105+
}
106+
}
107+
var metaFieldsContainer = JSONTable.qs('#' + this.metaFieldsId, this.container)
108+
metaFieldsContainer.innerHTML = html
109+
},
110+
85111
focusCurrentCell: function (currentCell) {
86112
var selector = "[data-row='" + String(currentCell.row) + "'][data-col='" + String(currentCell.col) + "']"
87113
var cell = JSONTable.qs(selector, this.container)
@@ -91,6 +117,12 @@ JSONTableView.prototype = {
91117
}
92118
},
93119

120+
metaFieldsContainer: function () {
121+
var html = '<div class="jt-meta-fields" id="' + this.metaFieldsId + '">'
122+
html += '</div>'
123+
return html
124+
},
125+
94126
formatOptionsContainer: function () {
95127
var html = '<div class="jt-format-options" id="' + this.formatOptionsId + '">'
96128
html += '</div>'

‎src/js/utils.js

+35
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,38 @@
4444
range.select()
4545
}
4646
}
47+
48+
JSONTable.orEmpty = function (entity) {
49+
return entity || ""
50+
}
51+
52+
// polyfill for Object.assign
53+
if (typeof Object.assign != 'function') {
54+
// Must be writable: true, enumerable: false, configurable: true
55+
Object.defineProperty(Object, "assign", {
56+
value: function assign(target, varArgs) { // .length of function is 2
57+
'use strict';
58+
if (target == null) { // TypeError if undefined or null
59+
throw new TypeError('Cannot convert undefined or null to object');
60+
}
61+
62+
var to = Object(target);
63+
64+
for (var index = 1; index < arguments.length; index++) {
65+
var nextSource = arguments[index];
66+
67+
if (nextSource != null) { // Skip over if undefined or null
68+
for (var nextKey in nextSource) {
69+
// Avoid bugs when hasOwnProperty is shadowed
70+
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
71+
to[nextKey] = nextSource[nextKey];
72+
}
73+
}
74+
}
75+
}
76+
return to;
77+
},
78+
writable: true,
79+
configurable: true
80+
});
81+
}

0 commit comments

Comments
 (0)
Please sign in to comment.