Skip to content

Commit 3944d17

Browse files
author
Andrea
committed
[Feature] Derived columns
1 parent 7aaccd2 commit 3944d17

File tree

4 files changed

+136
-19
lines changed

4 files changed

+136
-19
lines changed
File renamed without changes.

index.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010
body-field="results"
1111
id-col="id.value"
1212
endpoint="http://api.randomuser.me/?page=1&results=20&seed=abc"
13-
:header="{'name.first': 'name', 'name.last': 'surname', gender: 'gender', phone: 'phone', 'picture.thumbnail': 'avatar', nat: 'nationality'}"
13+
:header="{'name.first': 'name', 'name.last': 'surname', 'gender+phone': 'gender+', 'picture.thumbnail': 'avatar', nat: 'nationality'}"
14+
:actions="{'_delete': 'Delete'}"
15+
:delete="true"
16+
:add-row="true"
1417
>
1518
<src2img slot="picture.thumbnail"></src2img>
1619
</smart-table>

src/components/SmartTable.vue

Lines changed: 67 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
<input type="text" v-model="filters[column].model"/>
1515
</div>
1616
</th>
17+
<th v-if="delete">
1718
</thead>
1819
<thead class="regular-header" :class="{ transparent: scrolledPast }">
1920
<tr>
@@ -27,6 +28,8 @@
2728
<input type="text" v-model="filters[column].model"/>
2829
</div>
2930
</th>
31+
<th v-if="delete">
32+
</th>
3033
</tr>
3134
</thead>
3235
<tfoot>
@@ -41,6 +44,7 @@
4144
<td v-for="footerCell in footerRow" track-by="$index">
4245
{{footerCell}}
4346
</td>
47+
<td v-if="delete"></td>
4448
</tr>
4549
<tr v-if="actionsArePresent" class="action-row">
4650
<td class="smart-control-bar" colspan="{{span}}">
@@ -70,6 +74,9 @@
7074
</slot>
7175
</div>
7276
</td>
77+
<td v-if="delete">
78+
<button id="delete-{{key}}" @click="remove(key)">Delete</button>
79+
</td>
7380
</tr>
7481
<tr v-if="addRow" class="row-new">
7582
<td v-if="actionsArePresent"><!-- to match the toggle checkboxes --></td>
@@ -82,6 +89,7 @@
8289
<option v-for="(value, label) in inputList[col]" value="{{value}}" class="input-label">{{label}}</option>
8390
</select>
8491
</td>
92+
<td v-if="delete"></td>
8593
</tr>
8694
</tbody>
8795
</table>
@@ -121,6 +129,10 @@
121129
required: false,
122130
default: '_id'
123131
},
132+
'delete': {
133+
type: Boolean,
134+
default: false
135+
},
124136
footer: {
125137
required: false
126138
},
@@ -141,7 +153,7 @@
141153
actions: [Object, Array],
142154
endpoint: {
143155
type: String,
144-
default: 'http://api.randomuser.me/?results=2&seed=abc'
156+
default: 'http://localhost:8080'
145157
},
146158
labelCol: {
147159
type: String,
@@ -218,10 +230,11 @@
218230
return false
219231
},
220232
tableHeader () {
233+
// WARNING: must not (and cannot) depend on processedSmartBody!
234+
221235
if (this.body === undefined) {
222236
this.body = []
223237
}
224-
// must not depend on processedSmartBody
225238
// if object return object
226239
if (this.header !== undefined && !Array.isArray(this.header)) {
227240
return this.header
@@ -248,11 +261,8 @@
248261
},
249262
mainCol () {
250263
// choose an appropriate default label column
251-
var bodyKeys = Object.keys(this.processedSmartBody)
252-
const firstEntry = this.processedSmartBody[bodyKeys[0]]
253-
const firstEntryKeys = Object.keys(firstEntry)
254-
if (firstEntryKeys.indexOf(this.labelCol) === -1) {
255-
return firstEntryKeys[0]
264+
if (Object.keys(this.tableHeader).indexOf(this.labelCol) === -1) {
265+
return Object.keys(this.tableHeader)[0]
256266
} else {
257267
return this.labelCol
258268
}
@@ -285,7 +295,15 @@
285295
// filter unwanted columns
286296
let finalRow = {}
287297
Object.keys(this.tableHeader).forEach(col => {
288-
let realColValue = col.split('.').reduce((o,i)=>o[i], row)
298+
let realColValue = {}
299+
if (/\+/.test(col)) {
300+
// it's a composite column will return an object
301+
col.split('+').forEach(d=>{
302+
realColValue[d] = d.split('.').reduce((o,i)=>o[i], row)
303+
})
304+
} else {
305+
realColValue = col.split('.').reduce((o,i)=>o[i], row)
306+
}
289307
finalRow[col] = realColValue
290308
})
291309
let idValue = this.idCol.split('.').reduce((o,i)=>o[i], row)
@@ -310,7 +328,7 @@
310328
return smartBody
311329
},
312330
span () {
313-
return Object.keys(this.tableHeader).length + 1
331+
return Object.keys(this.tableHeader).length + 1 + (this.delete ? 1 : 0)
314332
}
315333
},
316334
beforeCompile () {
@@ -369,7 +387,7 @@
369387
saveNewRow () {
370388
if (this.canSaveNewRow) {
371389
this.$dispatch('before-request')
372-
this.$http.get(this.endpoint, {action: 'addRow', resource: this.newRow}).then((response) => {
390+
this.$http.post(this.endpoint, {action: 'addRow', resource: this.newRow}).then((response) => {
373391
this.$set('error', false)
374392
this.$set('body', response.data.body)
375393
this.$dispatch('successful-request')
@@ -442,7 +460,7 @@
442460
toggleAllRows () {
443461
if (this.toggleAll === false) {
444462
this.toggleAll = true
445-
this.selection = Object.keys(this.body)
463+
this.selection = Object.keys(this.processedSmartBody)
446464
} else {
447465
this.toggleAll = false
448466
this.selection = []
@@ -463,19 +481,50 @@
463481
},
464482
doCommand (command) {
465483
this.$dispatch('before-request')
466-
this.$http.get(this.endpoint, command).then((response) => {
467-
if (response.data.body !== undefined || response.data.body === {}) {
468-
this.$set('body', response.data.body)
484+
485+
// special case
486+
if (/^(_remove|_delete)$/i.test(command.action)) {
487+
this.$http.delete(this.endpoint, command).then(onSuccess, onFailure)
488+
} else {
489+
this.$http.get(this.endpoint, command).then(onSuccess, onFailure)
490+
}
491+
492+
function onSuccess(response) {
493+
if (response.data[this.bodyField] !== undefined || response.data[this.bodyField] === {}) {
494+
this.$set('body', response.data[this.bodyField])
469495
this.$set('footer', response.data.footer)
470496
}
471497
this.$dispatch('successful-request')
472498
this.$dispatch('after-request')
473499
this.$set('error', false)
474-
}, (response) => {
500+
}
501+
502+
function onFailure(response) {
475503
this.$set('error', { status: response.status, data: response.data.error })
476504
this.$dispatch('failed-request')
477505
this.$dispatch('after-request')
478-
})
506+
}
507+
},
508+
remove (id) {
509+
this.$dispatch('before-request')
510+
511+
this.$http.delete(this.endpoint + '/' + id).then(onSuccess, onFailure)
512+
513+
function onSuccess(response) {
514+
if (response.data[this.bodyField] !== undefined || response.data[this.bodyField] === {}) {
515+
this.$set('body', response.data[this.bodyField])
516+
this.$set('footer', response.data.footer)
517+
}
518+
this.$dispatch('successful-request')
519+
this.$dispatch('after-request')
520+
this.$set('error', false)
521+
}
522+
523+
function onFailure(response) {
524+
this.$set('error', { status: response.status, data: response.data.error })
525+
this.$dispatch('failed-request')
526+
this.$dispatch('after-request')
527+
}
479528
},
480529
isEditable (col) {
481530
if (this.editable === false) {
@@ -499,8 +548,8 @@
499548
this.modalEdit = {
500549
id: id,
501550
col: col,
502-
currentValue: this.body[id][col],
503-
previousValue: this.body[id][col],
551+
currentValue: this.processedSmartBody[id][col],
552+
previousValue: this.processedSmartBody[id][col],
504553
type: this.editType(col)
505554
}
506555
this.$broadcast('modalEdit', this.modalEdit)

test/unit/specs/SmartTable.spec.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ chai.use(require('chai-dom'))
1313
let testBody = [{_id: 0, name: 'Gennaro', age: 34}, {_id: 1, name: 'Marco', age: 22}]
1414
let testBody2 = [{_id: 1, name: 'Gennaro', age: 34}, {_id: 3, name: 'Marco', age: 22}]
1515
let testBody3 = [{_id: 1, bau: 'Gennaro', pau: 34}, {_id: 55, bau: 'Marco', pau: 22}]
16+
let testBodyId = [{id: 1, name: 'Gennaro', age: 34}, {id: 3, name: 'Marco', age: 22}]
1617
let testBodyNoId = [{name: 'Gennaro', age: 34}, {name: 'Marco', age: 22}]
1718

1819
describe('SmartTable.vue', () => {
@@ -122,6 +123,29 @@ describe('SmartTable.vue', () => {
122123
vm.$children[0].next()
123124
expect(vm.$children[0].$refs.son.received).to.eql(true)
124125
})
126+
it('should broadcast \'command\' with an action when command called and id-col is set', () => {
127+
const inject = require('!!vue?inject!../../../src/components/SmartTable.vue')
128+
const SmartTableWithMock = inject({
129+
'./Modal': {
130+
data () {
131+
this.$parent.$refs['son'] = this
132+
return {received: false}
133+
},
134+
events: {
135+
'command' () {
136+
this.received = true
137+
}
138+
}
139+
}
140+
})
141+
const vm = new Vue({
142+
template: '<div><smart-table :body="testBodyId" :actions="[\'act1\']" id-col="id"></smart-table></div>',
143+
components: {'smart-table': SmartTableWithMock},
144+
data: { testBodyId }
145+
}).$mount()
146+
vm.$children[0].next()
147+
expect(vm.$children[0].$refs.son.received).to.eql(true)
148+
})
125149
it('should broadcast \'command\' with an action key and label when command called (simple array case)', (done) => {
126150
const inject = require('!!vue?inject!../../../src/components/SmartTable.vue')
127151
const SmartTableWithMock = inject({
@@ -188,6 +212,27 @@ describe('SmartTable.vue', () => {
188212
vm.$children[0].selection = [1, 3]
189213
vm.$children[0].next()
190214
})
215+
it('should broadcast \'command\' with an array of id and labels when command called with id-col changed', (done) => {
216+
const inject = require('!!vue?inject!../../../src/components/SmartTable.vue')
217+
const SmartTableWithMock = inject({
218+
'./Modal': {
219+
events: {
220+
'command' (command) {
221+
expect(command.selection).to.eql([{key: 1, label: 'Gennaro'}, {key: 3, label: 'Marco'}])
222+
done()
223+
}
224+
}
225+
}
226+
})
227+
const vm = new Vue({
228+
template: '<div><smart-table :body="testBodyId" :actions="[\'act1\']" id-col="id" label-col="name"></smart-table></div>',
229+
components: {'smart-table': SmartTableWithMock},
230+
data: { testBodyId }
231+
}).$mount()
232+
vm.$children[0].action = 'act1'
233+
vm.$children[0].selection = [1, 3]
234+
vm.$children[0].next()
235+
})
191236
it('should extend an array of action names as an object', () => {
192237
const vm = new Vue({
193238
template: '<div><smart-table :body="testBody" :actions="[\'doge\',\'such test\']"></smart-table></div>',
@@ -352,6 +397,26 @@ describe('SmartTable.vue', () => {
352397
}).$mount()
353398
expect(vm.$children[0].mainCol).to.eql('bau')
354399
})
400+
it('should show a delete button if delete is set to true', () => {
401+
const vm = new Vue({
402+
template: '<div><smart-table :body="testBody2" :delete="true"></smart-table></div>',
403+
components: {SmartTable},
404+
data: {testBody2}
405+
}).$mount()
406+
expect(vm.$el.querySelectorAll('button#delete-3').length).to.eql(1)
407+
})
408+
it('should be able to form derived columns', () => {
409+
const vm = new Vue({
410+
template: '<div><smart-table :body="testBody" :header="subset"></smart-table></div>',
411+
components: {SmartTable},
412+
data: {
413+
subset: {'name+age': 'Nome+'},
414+
testBody: [{_id: 1, name: 'Gennaro', age: 34, hidden: 'pupu'}, {_id: 55, name: 'Marco', age: 22, hidden: 'caca'}]
415+
}
416+
}).$mount()
417+
expect(vm.$el.querySelectorAll('td').length).to.eql(2)
418+
expect(vm.$el.querySelector('#value-1-name\\+age')).to.exist
419+
})
355420
// todo: bring this to e2e
356421
/* it('double clicking a field should put it in edit mode', (done) => {
357422
const vm = new Vue({

0 commit comments

Comments
 (0)