mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-14 00:59:06 +00:00
Resolve "Edit a row in a popup"
This commit is contained in:
parent
0d528b8d22
commit
1202e2b9c2
19 changed files with 592 additions and 116 deletions
changelog.mdfieldTypes.js
web-frontend/modules
core/assets/scss/components
database
components
field
row
RowEditFieldBoolean.vueRowEditFieldNumber.vueRowEditFieldText.vueRowEditModal.vueRowEditModalField.vue
view/grid
mixins
store/view
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
* Added row modal editing feature to the grid view.
|
||||||
|
* Made it possible to resize the field width per view.
|
||||||
* Added validation and formatting for the number field.
|
* Added validation and formatting for the number field.
|
||||||
* Cancel the editing state of a fields when the escape key is pressed.
|
* Cancel the editing state of a fields when the escape key is pressed.
|
||||||
* The next field is now selected when the tab character is pressed when a field is
|
* The next field is now selected when the tab character is pressed when a field is
|
||||||
|
|
|
@ -15,6 +15,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.control-label-icon {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
.control-context {
|
.control-context {
|
||||||
color: $color-primary-900;
|
color: $color-primary-900;
|
||||||
margin-left: 6px;
|
margin-left: 6px;
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
<template>
|
||||||
|
<Context ref="context">
|
||||||
|
<ul class="context-menu">
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
ref="updateFieldContextLink"
|
||||||
|
class="grid-view-description-options"
|
||||||
|
@click="
|
||||||
|
$refs.updateFieldContext.toggle(
|
||||||
|
$refs.updateFieldContextLink,
|
||||||
|
'bottom',
|
||||||
|
'left',
|
||||||
|
0
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<i class="context-menu-icon fas fa-fw fa-pen"></i>
|
||||||
|
Edit field
|
||||||
|
</a>
|
||||||
|
<UpdateFieldContext
|
||||||
|
ref="updateFieldContext"
|
||||||
|
:field="field"
|
||||||
|
@update="$refs.context.hide()"
|
||||||
|
></UpdateFieldContext>
|
||||||
|
</li>
|
||||||
|
<li v-if="!field.primary">
|
||||||
|
<a @click="deleteField(field)">
|
||||||
|
<i class="context-menu-icon fas fa-fw fa-trash"></i>
|
||||||
|
Delete field
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</Context>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { notifyIf } from '@baserow/modules/core/utils/error'
|
||||||
|
import context from '@baserow/modules/core/mixins/context'
|
||||||
|
import UpdateFieldContext from '@baserow/modules/database/components/field/UpdateFieldContext'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'FieldContext',
|
||||||
|
components: { UpdateFieldContext },
|
||||||
|
mixins: [context],
|
||||||
|
props: {
|
||||||
|
field: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setLoading(field, value) {
|
||||||
|
this.$store.dispatch('field/setItemLoading', { field, value })
|
||||||
|
},
|
||||||
|
async deleteField(field) {
|
||||||
|
this.$refs.context.hide()
|
||||||
|
this.setLoading(field, true)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.$store.dispatch('field/delete', field)
|
||||||
|
} catch (error) {
|
||||||
|
notifyIf(error, 'field')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setLoading(field, false)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,26 @@
|
||||||
|
<template>
|
||||||
|
<div class="control-elements">
|
||||||
|
<div
|
||||||
|
class="field-boolean-checkbox"
|
||||||
|
:class="{ active: value }"
|
||||||
|
@click="toggle(value)"
|
||||||
|
>
|
||||||
|
<i class="fas fa-check check"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import rowEditField from '@baserow/modules/database/mixins/rowEditField'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [rowEditField],
|
||||||
|
methods: {
|
||||||
|
toggle(value) {
|
||||||
|
const oldValue = !!value
|
||||||
|
const newValue = !value
|
||||||
|
this.$emit('update', newValue, oldValue)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,27 @@
|
||||||
|
<template>
|
||||||
|
<div class="control-elements">
|
||||||
|
<input
|
||||||
|
ref="input"
|
||||||
|
v-model="copy"
|
||||||
|
type="text"
|
||||||
|
class="input input-large field-number"
|
||||||
|
:class="{ 'input-error': !isValid() }"
|
||||||
|
@keyup.enter="$refs.input.blur()"
|
||||||
|
@focus="select()"
|
||||||
|
@blur="unselect()"
|
||||||
|
/>
|
||||||
|
<div v-show="!isValid()" class="error">
|
||||||
|
{{ getError() }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import rowEditField from '@baserow/modules/database/mixins/rowEditField'
|
||||||
|
import rowEditFieldInput from '@baserow/modules/database/mixins/rowEditFieldInput'
|
||||||
|
import numberField from '@baserow/modules/database/mixins/numberField'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [rowEditField, rowEditFieldInput, numberField],
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,22 @@
|
||||||
|
<template>
|
||||||
|
<div class="control-elements">
|
||||||
|
<input
|
||||||
|
ref="input"
|
||||||
|
v-model="copy"
|
||||||
|
type="text"
|
||||||
|
class="input input-large"
|
||||||
|
@keyup.enter="$refs.input.blur()"
|
||||||
|
@focus="select()"
|
||||||
|
@blur="unselect()"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import rowEditField from '@baserow/modules/database/mixins/rowEditField'
|
||||||
|
import rowEditFieldInput from '@baserow/modules/database/mixins/rowEditFieldInput'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [rowEditField, rowEditFieldInput],
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,92 @@
|
||||||
|
<template>
|
||||||
|
<Modal>
|
||||||
|
<h2 v-if="primary !== undefined" class="box-title">
|
||||||
|
{{ getHeading(primary, row) }}
|
||||||
|
</h2>
|
||||||
|
<form>
|
||||||
|
<RowEditModalField
|
||||||
|
v-for="field in getFields(fields, primary)"
|
||||||
|
:ref="'field-' + field.id"
|
||||||
|
:key="'row-edit-field-' + field.id"
|
||||||
|
:field="field"
|
||||||
|
:row="row"
|
||||||
|
@update="update"
|
||||||
|
></RowEditModalField>
|
||||||
|
<div class="actions">
|
||||||
|
<a
|
||||||
|
ref="createFieldContextLink"
|
||||||
|
@click="$refs.createFieldContext.toggle($refs.createFieldContextLink)"
|
||||||
|
>
|
||||||
|
<i class="fas fa-plus"></i>
|
||||||
|
add field
|
||||||
|
</a>
|
||||||
|
<CreateFieldContext
|
||||||
|
ref="createFieldContext"
|
||||||
|
:table="table"
|
||||||
|
></CreateFieldContext>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import modal from '@baserow/modules/core/mixins/modal'
|
||||||
|
import RowEditModalField from '@baserow/modules/database/components/row/RowEditModalField'
|
||||||
|
import CreateFieldContext from '@baserow/modules/database/components/field/CreateFieldContext'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'RowEditModal',
|
||||||
|
components: {
|
||||||
|
RowEditModalField,
|
||||||
|
CreateFieldContext,
|
||||||
|
},
|
||||||
|
mixins: [modal],
|
||||||
|
props: {
|
||||||
|
table: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
primary: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
row: {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
show(row, ...args) {
|
||||||
|
this.row = row
|
||||||
|
this.getRootModal().show(...args)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Because the modal can't update values by himself, an event will be called to
|
||||||
|
* notify the parent component to actually update the value.
|
||||||
|
*/
|
||||||
|
update(context) {
|
||||||
|
context.table = this.table
|
||||||
|
this.$emit('update', context)
|
||||||
|
},
|
||||||
|
getFields(fields, primary) {
|
||||||
|
return primary !== undefined ? [primary].concat(fields) : fields
|
||||||
|
},
|
||||||
|
getHeading(primary, row) {
|
||||||
|
const name = `field_${primary.id}`
|
||||||
|
if (Object.prototype.hasOwnProperty.call(row, name)) {
|
||||||
|
return this.$registry
|
||||||
|
.get('field', primary.type)
|
||||||
|
.toHumanReadableString(primary, row[name])
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,58 @@
|
||||||
|
<template>
|
||||||
|
<div class="control">
|
||||||
|
<label class="control-label">
|
||||||
|
<i
|
||||||
|
class="fas control-label-icon"
|
||||||
|
:class="'fa-' + field._.type.iconClass"
|
||||||
|
></i>
|
||||||
|
{{ field.name }}
|
||||||
|
<a
|
||||||
|
ref="contextLink"
|
||||||
|
class="control-context"
|
||||||
|
@click="$refs.context.toggle($refs.contextLink, 'bottom', 'left', 0)"
|
||||||
|
>
|
||||||
|
<i class="fas fa-caret-down"></i>
|
||||||
|
</a>
|
||||||
|
</label>
|
||||||
|
<FieldContext ref="context" :field="field"></FieldContext>
|
||||||
|
<component
|
||||||
|
:is="getFieldComponent(field.type)"
|
||||||
|
ref="field"
|
||||||
|
:field="field"
|
||||||
|
:value="row['field_' + field.id]"
|
||||||
|
@update="update"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import FieldContext from '@baserow/modules/database/components/field/FieldContext'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'RowEditModalField',
|
||||||
|
components: { FieldContext },
|
||||||
|
props: {
|
||||||
|
field: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getFieldComponent(type) {
|
||||||
|
return this.$registry.get('field', type).getRowEditFieldComponent()
|
||||||
|
},
|
||||||
|
update(value, oldValue) {
|
||||||
|
this.$emit('update', {
|
||||||
|
row: this.row,
|
||||||
|
field: this.field,
|
||||||
|
value,
|
||||||
|
oldValue,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -45,7 +45,12 @@
|
||||||
v-for="row in rows"
|
v-for="row in rows"
|
||||||
:key="'left-row-' + view.id + '-' + row.id"
|
:key="'left-row-' + view.id + '-' + row.id"
|
||||||
class="grid-view-row"
|
class="grid-view-row"
|
||||||
:class="{ 'grid-view-row-loading': row._.loading }"
|
:class="{
|
||||||
|
'grid-view-row-loading': row._.loading,
|
||||||
|
'grid-view-row-hover': row._.hover,
|
||||||
|
}"
|
||||||
|
@mouseover="setRowHover(row, true)"
|
||||||
|
@mouseleave="setRowHover(row, false)"
|
||||||
@contextmenu.prevent="showRowContext($event, row)"
|
@contextmenu.prevent="showRowContext($event, row)"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
@ -54,7 +59,10 @@
|
||||||
>
|
>
|
||||||
<div class="grid-view-row-info">
|
<div class="grid-view-row-info">
|
||||||
<div class="grid-view-row-count">{{ row.id }}</div>
|
<div class="grid-view-row-count">{{ row.id }}</div>
|
||||||
<a href="#" class="grid-view-row-more">
|
<a
|
||||||
|
class="grid-view-row-more"
|
||||||
|
@click="$refs.rowEditModal.show(row)"
|
||||||
|
>
|
||||||
<i class="fas fa-expand"></i>
|
<i class="fas fa-expand"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -68,6 +76,7 @@
|
||||||
:style="{ width: widths.fields[primary.id] + 'px' }"
|
:style="{ width: widths.fields[primary.id] + 'px' }"
|
||||||
@selected="selectedField(primary, $event.component)"
|
@selected="selectedField(primary, $event.component)"
|
||||||
@selectNext="selectNextField(row, primary, fields, primary)"
|
@selectNext="selectNextField(row, primary, fields, primary)"
|
||||||
|
@update="updateValue"
|
||||||
></GridViewField>
|
></GridViewField>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -175,7 +184,12 @@
|
||||||
v-for="row in rows"
|
v-for="row in rows"
|
||||||
:key="'right-row-' + view.id + '-' + row.id"
|
:key="'right-row-' + view.id + '-' + row.id"
|
||||||
class="grid-view-row"
|
class="grid-view-row"
|
||||||
:class="{ 'grid-view-row-loading': row._.loading }"
|
:class="{
|
||||||
|
'grid-view-row-loading': row._.loading,
|
||||||
|
'grid-view-row-hover': row._.hover,
|
||||||
|
}"
|
||||||
|
@mouseover="setRowHover(row, true)"
|
||||||
|
@mouseleave="setRowHover(row, false)"
|
||||||
@contextmenu.prevent="showRowContext($event, row)"
|
@contextmenu.prevent="showRowContext($event, row)"
|
||||||
>
|
>
|
||||||
<GridViewField
|
<GridViewField
|
||||||
|
@ -193,6 +207,7 @@
|
||||||
selectNextField(row, field, fields, primary, true)
|
selectNextField(row, field, fields, primary, true)
|
||||||
"
|
"
|
||||||
@selectNext="selectNextField(row, field, fields, primary)"
|
@selectNext="selectNextField(row, field, fields, primary)"
|
||||||
|
@update="updateValue"
|
||||||
></GridViewField>
|
></GridViewField>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -225,6 +240,13 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</Context>
|
</Context>
|
||||||
|
<RowEditModal
|
||||||
|
ref="rowEditModal"
|
||||||
|
:table="table"
|
||||||
|
:primary="primary"
|
||||||
|
:fields="fields"
|
||||||
|
@update="updateValue"
|
||||||
|
></RowEditModal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -235,6 +257,7 @@ import CreateFieldContext from '@baserow/modules/database/components/field/Creat
|
||||||
import GridViewFieldType from '@baserow/modules/database/components/view/grid/GridViewFieldType'
|
import GridViewFieldType from '@baserow/modules/database/components/view/grid/GridViewFieldType'
|
||||||
import GridViewField from '@baserow/modules/database/components/view/grid/GridViewField'
|
import GridViewField from '@baserow/modules/database/components/view/grid/GridViewField'
|
||||||
import GridViewFieldWidthHandle from '@baserow/modules/database/components/view/grid/GridViewFieldWidthHandle'
|
import GridViewFieldWidthHandle from '@baserow/modules/database/components/view/grid/GridViewFieldWidthHandle'
|
||||||
|
import RowEditModal from '@baserow/modules/database/components/row/RowEditModal'
|
||||||
import { notifyIf } from '@baserow/modules/core/utils/error'
|
import { notifyIf } from '@baserow/modules/core/utils/error'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
@ -245,6 +268,7 @@ export default {
|
||||||
GridViewFieldType,
|
GridViewFieldType,
|
||||||
GridViewField,
|
GridViewField,
|
||||||
GridViewFieldWidthHandle,
|
GridViewFieldWidthHandle,
|
||||||
|
RowEditModal,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
primary: {
|
primary: {
|
||||||
|
@ -273,6 +297,7 @@ export default {
|
||||||
addHover: false,
|
addHover: false,
|
||||||
loading: true,
|
loading: true,
|
||||||
selectedRow: null,
|
selectedRow: null,
|
||||||
|
lastHoveredRow: null,
|
||||||
widths: {
|
widths: {
|
||||||
fields: {},
|
fields: {},
|
||||||
},
|
},
|
||||||
|
@ -308,6 +333,19 @@ export default {
|
||||||
this.calculateWidths(this.primary, this.fields, this.fieldOptions)
|
this.calculateWidths(this.primary, this.fields, this.fieldOptions)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
async updateValue({ field, row, value, oldValue }) {
|
||||||
|
try {
|
||||||
|
await this.$store.dispatch('view/grid/updateValue', {
|
||||||
|
table: this.table,
|
||||||
|
row,
|
||||||
|
field,
|
||||||
|
value,
|
||||||
|
oldValue,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
notifyIf(error, 'field')
|
||||||
|
}
|
||||||
|
},
|
||||||
scroll(pixelY, pixelX) {
|
scroll(pixelY, pixelX) {
|
||||||
const $rightBody = this.$refs.rightBody
|
const $rightBody = this.$refs.rightBody
|
||||||
const $right = this.$refs.right
|
const $right = this.$refs.right
|
||||||
|
@ -412,7 +450,8 @@ export default {
|
||||||
try {
|
try {
|
||||||
await this.$store.dispatch('view/grid/create', {
|
await this.$store.dispatch('view/grid/create', {
|
||||||
table: this.table,
|
table: this.table,
|
||||||
fields: this.fields,
|
// We need a list of all fields including the primary one here.
|
||||||
|
fields: [this.primary].concat(...this.fields),
|
||||||
values: {},
|
values: {},
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -523,6 +562,21 @@ export default {
|
||||||
current[0].unselect()
|
current[0].unselect()
|
||||||
next[0].select()
|
next[0].select()
|
||||||
},
|
},
|
||||||
|
setRowHover(row, value) {
|
||||||
|
// Sometimes the mouseleave is not triggered, but because you can hover only one
|
||||||
|
// row at a time we can remember which was hovered last and set the hover state to
|
||||||
|
// false if it differs.
|
||||||
|
if (this.lastHoveredRow !== null && this.lastHoveredRow.id !== row.id) {
|
||||||
|
this.$store.dispatch('view/grid/setRowHover', {
|
||||||
|
row: this.lastHoveredRow,
|
||||||
|
value: false,
|
||||||
|
})
|
||||||
|
this.lastHoveredRow = true
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$store.dispatch('view/grid/setRowHover', { row, value })
|
||||||
|
this.lastHoveredRow = row
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<div class="grid-view-column" @click="select()">
|
<div class="grid-view-column" @click="select()">
|
||||||
<component
|
<component
|
||||||
:is="getFieldComponent(field.type)"
|
:is="getFieldComponent(field.type)"
|
||||||
ref="column"
|
ref="field"
|
||||||
:field="field"
|
:field="field"
|
||||||
:value="row['field_' + field.id]"
|
:value="row['field_' + field.id]"
|
||||||
:selected="selected"
|
:selected="selected"
|
||||||
|
@ -13,15 +13,10 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { isElement } from '@baserow/modules/core/utils/dom'
|
import { isElement } from '@baserow/modules/core/utils/dom'
|
||||||
import { notifyIf } from '@baserow/modules/core/utils/error'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'GridViewField',
|
name: 'GridViewField',
|
||||||
props: {
|
props: {
|
||||||
table: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
|
@ -62,25 +57,12 @@ export default {
|
||||||
* which will actually update the value via the store.
|
* which will actually update the value via the store.
|
||||||
*/
|
*/
|
||||||
update(value, oldValue) {
|
update(value, oldValue) {
|
||||||
this.$store
|
this.$emit('update', {
|
||||||
.dispatch('view/grid/updateValue', {
|
row: this.row,
|
||||||
table: this.table,
|
field: this.field,
|
||||||
row: this.row,
|
value,
|
||||||
field: this.field,
|
oldValue,
|
||||||
value,
|
})
|
||||||
oldValue,
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
notifyIf(error, 'column')
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
this.$forceUpdate()
|
|
||||||
})
|
|
||||||
|
|
||||||
// This is needed because in some cases we do have a value yet, so a watcher of
|
|
||||||
// the value is not guaranteed. This will make sure the component shows the
|
|
||||||
// latest value.
|
|
||||||
this.$forceUpdate()
|
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Method that is called when a user clicks on the grid field. It wil
|
* Method that is called when a user clicks on the grid field. It wil
|
||||||
|
@ -97,7 +79,7 @@ export default {
|
||||||
this.clickTimestamp !== null &&
|
this.clickTimestamp !== null &&
|
||||||
timestamp - this.clickTimestamp < 200
|
timestamp - this.clickTimestamp < 200
|
||||||
) {
|
) {
|
||||||
this.$refs.column.doubleClick()
|
this.$refs.field.doubleClick()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If the field is not yet selected we can change the state to selected.
|
// If the field is not yet selected we can change the state to selected.
|
||||||
|
@ -105,7 +87,7 @@ export default {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
// Call the select method on the next tick because we want to wait for all
|
// Call the select method on the next tick because we want to wait for all
|
||||||
// changes to have rendered.
|
// changes to have rendered.
|
||||||
this.$refs.column.select()
|
this.$refs.field.select()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Register a body click event listener so that we can detect if a user has
|
// Register a body click event listener so that we can detect if a user has
|
||||||
|
@ -146,7 +128,7 @@ export default {
|
||||||
this.clickTimestamp = timestamp
|
this.clickTimestamp = timestamp
|
||||||
},
|
},
|
||||||
unselect() {
|
unselect() {
|
||||||
this.$refs.column.beforeUnSelect()
|
this.$refs.field.beforeUnSelect()
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.selected = false
|
this.selected = false
|
||||||
})
|
})
|
||||||
|
|
|
@ -18,6 +18,19 @@ import gridField from '@baserow/modules/database/mixins/gridField'
|
||||||
export default {
|
export default {
|
||||||
mixins: [gridField],
|
mixins: [gridField],
|
||||||
methods: {
|
methods: {
|
||||||
|
select() {
|
||||||
|
// While the field is selected we want to toggle the value by pressing the enter
|
||||||
|
// key.
|
||||||
|
this.$el.keydownEvent = (event) => {
|
||||||
|
if (event.keyCode === 13) {
|
||||||
|
this.toggle(this.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.body.addEventListener('keydown', this.$el.keydownEvent)
|
||||||
|
},
|
||||||
|
beforeUnSelect() {
|
||||||
|
document.body.removeEventListener('keydown', this.$el.keydownEvent)
|
||||||
|
},
|
||||||
toggle(value) {
|
toggle(value) {
|
||||||
if (!this.selected) {
|
if (!this.selected) {
|
||||||
return
|
return
|
||||||
|
|
|
@ -25,42 +25,17 @@
|
||||||
<script>
|
<script>
|
||||||
import gridField from '@baserow/modules/database/mixins/gridField'
|
import gridField from '@baserow/modules/database/mixins/gridField'
|
||||||
import gridFieldInput from '@baserow/modules/database/mixins/gridFieldInput'
|
import gridFieldInput from '@baserow/modules/database/mixins/gridFieldInput'
|
||||||
|
import numberField from '@baserow/modules/database/mixins/numberField'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [gridField, gridFieldInput],
|
mixins: [gridField, gridFieldInput, numberField],
|
||||||
methods: {
|
methods: {
|
||||||
getError() {
|
|
||||||
if (this.copy === null || this.copy === '') {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
if (isNaN(parseFloat(this.copy)) || !isFinite(this.copy)) {
|
|
||||||
return 'Invalid number'
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
},
|
|
||||||
isValid() {
|
|
||||||
return this.getError() === null
|
|
||||||
},
|
|
||||||
afterEdit() {
|
afterEdit() {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.$refs.input.focus()
|
this.$refs.input.focus()
|
||||||
this.$refs.input.selectionStart = this.$refs.input.selectionEnd = 100000
|
this.$refs.input.selectionStart = this.$refs.input.selectionEnd = 100000
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
beforeSave(value) {
|
|
||||||
if (value === '' || isNaN(value) || value === undefined) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
const decimalPlaces =
|
|
||||||
this.field.number_type === 'DECIMAL'
|
|
||||||
? this.field.number_decimal_places
|
|
||||||
: 0
|
|
||||||
let number = parseFloat(value)
|
|
||||||
if (!this.field.number_negative && number < 0) {
|
|
||||||
number = 0
|
|
||||||
}
|
|
||||||
return number.toFixed(decimalPlaces)
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -15,72 +15,23 @@
|
||||||
>
|
>
|
||||||
<i class="fas fa-caret-down"></i>
|
<i class="fas fa-caret-down"></i>
|
||||||
</a>
|
</a>
|
||||||
<Context ref="context">
|
<FieldContext ref="context" :field="field"></FieldContext>
|
||||||
<ul class="context-menu">
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
ref="updateFieldContextLink"
|
|
||||||
class="grid-view-description-options"
|
|
||||||
@click="
|
|
||||||
$refs.updateFieldContext.toggle(
|
|
||||||
$refs.updateFieldContextLink,
|
|
||||||
'bottom',
|
|
||||||
'right',
|
|
||||||
0
|
|
||||||
)
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<i class="context-menu-icon fas fa-fw fa-pen"></i>
|
|
||||||
Edit field
|
|
||||||
</a>
|
|
||||||
<UpdateFieldContext
|
|
||||||
ref="updateFieldContext"
|
|
||||||
:field="field"
|
|
||||||
@update="$refs.context.hide()"
|
|
||||||
></UpdateFieldContext>
|
|
||||||
</li>
|
|
||||||
<li v-if="!field.primary">
|
|
||||||
<a @click="deleteField(field)">
|
|
||||||
<i class="context-menu-icon fas fa-fw fa-trash"></i>
|
|
||||||
Delete field
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</Context>
|
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { notifyIf } from '@baserow/modules/core/utils/error'
|
import FieldContext from '@baserow/modules/database/components/field/FieldContext'
|
||||||
import UpdateFieldContext from '@baserow/modules/database/components/field/UpdateFieldContext'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'GridViewFieldType',
|
name: 'GridViewFieldType',
|
||||||
components: { UpdateFieldContext },
|
components: { FieldContext },
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
|
||||||
setLoading(field, value) {
|
|
||||||
this.$store.dispatch('field/setItemLoading', { field, value })
|
|
||||||
},
|
|
||||||
async deleteField(field) {
|
|
||||||
this.$refs.context.hide()
|
|
||||||
this.setLoading(field, true)
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.$store.dispatch('field/delete', field)
|
|
||||||
} catch (error) {
|
|
||||||
notifyIf(error, 'field')
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setLoading(field, false)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -7,6 +7,10 @@ import GridViewFieldText from '@baserow/modules/database/components/view/grid/Gr
|
||||||
import GridViewFieldNumber from '@baserow/modules/database/components/view/grid/GridViewFieldNumber'
|
import GridViewFieldNumber from '@baserow/modules/database/components/view/grid/GridViewFieldNumber'
|
||||||
import GridViewFieldBoolean from '@baserow/modules/database/components/view/grid/GridViewFieldBoolean'
|
import GridViewFieldBoolean from '@baserow/modules/database/components/view/grid/GridViewFieldBoolean'
|
||||||
|
|
||||||
|
import RowEditFieldText from '@baserow/modules/database/components/row/RowEditFieldText'
|
||||||
|
import RowEditFieldNumber from '@baserow/modules/database/components/row/RowEditFieldNumber'
|
||||||
|
import RowEditFieldBoolean from '@baserow/modules/database/components/row/RowEditFieldBoolean'
|
||||||
|
|
||||||
export class FieldType extends Registerable {
|
export class FieldType extends Registerable {
|
||||||
/**
|
/**
|
||||||
* The font awesome 5 icon name that is used as convenience for the user to
|
* The font awesome 5 icon name that is used as convenience for the user to
|
||||||
|
@ -37,7 +41,9 @@ export class FieldType extends Registerable {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @TODO make this depending on the view type.
|
* This grid view field component should represent the related row value of this
|
||||||
|
* type. It will only be used in the grid view and it also responsible for editing
|
||||||
|
* the value.
|
||||||
*/
|
*/
|
||||||
getGridViewFieldComponent() {
|
getGridViewFieldComponent() {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
@ -45,6 +51,17 @@ export class FieldType extends Registerable {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The row edit field should represent a the related row value of this type. It
|
||||||
|
* will be used in the row edit modal, but can also be used in other forms. It is
|
||||||
|
* responsible for editing the value.
|
||||||
|
*/
|
||||||
|
getRowEditFieldComponent() {
|
||||||
|
throw new Error(
|
||||||
|
'Not implement error. This method should return a component.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Because we want to show a new row immediately after creating we need to have an
|
* Because we want to show a new row immediately after creating we need to have an
|
||||||
* empty value to show right away.
|
* empty value to show right away.
|
||||||
|
@ -90,6 +107,13 @@ export class FieldType extends Registerable {
|
||||||
name: this.name,
|
name: this.name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should return a for humans readable representation of the value.
|
||||||
|
*/
|
||||||
|
toHumanReadableString(field, value) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TextFieldType extends FieldType {
|
export class TextFieldType extends FieldType {
|
||||||
|
@ -113,6 +137,10 @@ export class TextFieldType extends FieldType {
|
||||||
return GridViewFieldText
|
return GridViewFieldText
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getRowEditFieldComponent() {
|
||||||
|
return RowEditFieldText
|
||||||
|
}
|
||||||
|
|
||||||
getEmptyValue(field) {
|
getEmptyValue(field) {
|
||||||
return field.text_default
|
return field.text_default
|
||||||
}
|
}
|
||||||
|
@ -138,6 +166,10 @@ export class NumberFieldType extends FieldType {
|
||||||
getGridViewFieldComponent() {
|
getGridViewFieldComponent() {
|
||||||
return GridViewFieldNumber
|
return GridViewFieldNumber
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getRowEditFieldComponent() {
|
||||||
|
return RowEditFieldNumber
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BooleanFieldType extends FieldType {
|
export class BooleanFieldType extends FieldType {
|
||||||
|
@ -157,6 +189,10 @@ export class BooleanFieldType extends FieldType {
|
||||||
return GridViewFieldBoolean
|
return GridViewFieldBoolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getRowEditFieldComponent() {
|
||||||
|
return RowEditFieldBoolean
|
||||||
|
}
|
||||||
|
|
||||||
getEmptyValue(field) {
|
getEmptyValue(field) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,11 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
* Indicates whether of the user is editing the value.
|
* Indicates whether the user is editing the value.
|
||||||
*/
|
*/
|
||||||
editing: false,
|
editing: false,
|
||||||
/**
|
/**
|
||||||
* A temporary copy of the value when editing.
|
* A temporary copy of the value when editing.
|
||||||
*/
|
*/
|
||||||
copy: null,
|
copy: null,
|
||||||
}
|
}
|
||||||
|
|
43
web-frontend/modules/database/mixins/numberField.js
Normal file
43
web-frontend/modules/database/mixins/numberField.js
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
/**
|
||||||
|
* This mixin contains some method overrides for validating and formatting the
|
||||||
|
* number field. This mixin is used in both the GridViewFieldNumber and
|
||||||
|
* RowEditFieldNumber components.
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* Generates a human readable error for the user if something is wrong.
|
||||||
|
*/
|
||||||
|
getError() {
|
||||||
|
if (this.copy === null || this.copy === '') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (isNaN(parseFloat(this.copy)) || !isFinite(this.copy)) {
|
||||||
|
return 'Invalid number'
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
isValid() {
|
||||||
|
return this.getError() === null
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Formats the value based on the field's settings. The number will be rounded
|
||||||
|
* if to much decimal places are provided and if negative numbers aren't allowed
|
||||||
|
* they will be set to 0.
|
||||||
|
*/
|
||||||
|
beforeSave(value) {
|
||||||
|
if (value === '' || isNaN(value) || value === undefined) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const decimalPlaces =
|
||||||
|
this.field.number_type === 'DECIMAL'
|
||||||
|
? this.field.number_decimal_places
|
||||||
|
: 0
|
||||||
|
let number = parseFloat(value)
|
||||||
|
if (!this.field.number_negative && number < 0) {
|
||||||
|
number = 0
|
||||||
|
}
|
||||||
|
return number.toFixed(decimalPlaces)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
25
web-frontend/modules/database/mixins/rowEditField.js
Normal file
25
web-frontend/modules/database/mixins/rowEditField.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/**
|
||||||
|
* A mixin that can be used by a row edit modal component. It introduces the props that
|
||||||
|
* will be passed by the RowEditModalField component.
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
/**
|
||||||
|
* Contains the field type object. Because each field type can have different
|
||||||
|
* settings you need this in order to render the correct component or implement
|
||||||
|
* correct validation.
|
||||||
|
*/
|
||||||
|
field: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* The value of the grid field, this could for example for a number field 10,
|
||||||
|
* text field 'Random string' etc.
|
||||||
|
*/
|
||||||
|
value: {
|
||||||
|
type: [String, Number, Object, Boolean],
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
84
web-frontend/modules/database/mixins/rowEditFieldInput.js
Normal file
84
web-frontend/modules/database/mixins/rowEditFieldInput.js
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
/**
|
||||||
|
* This mixin can be used with a row edit field if the field only needs an input. For
|
||||||
|
* example for the text and number fields. It depends on the rowEditField mixin.
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Indicates whether the user is editing the value.
|
||||||
|
*/
|
||||||
|
editing: false,
|
||||||
|
/**
|
||||||
|
* A temporary copy of the value when editing.
|
||||||
|
*/
|
||||||
|
copy: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value(value) {
|
||||||
|
if (!this.editing) {
|
||||||
|
this.copy = value
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.copy = this.value
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* Event that is called when the user starts editing the value. In this case we
|
||||||
|
* will only enable the editing state.
|
||||||
|
*/
|
||||||
|
select() {
|
||||||
|
this.editing = true
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Event that is called when the user finishes editing. If the value is not
|
||||||
|
* valid we aren't going to do anything because it can't be changed anyway and
|
||||||
|
* we want to give the user a change to fix the value.
|
||||||
|
*/
|
||||||
|
unselect() {
|
||||||
|
if (!this.isValid() || !this.editing) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.editing = false
|
||||||
|
this.save()
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Saves the value if it has changed. Should only be called by the unselect
|
||||||
|
* method and not directly.
|
||||||
|
*/
|
||||||
|
save() {
|
||||||
|
const newValue = this.beforeSave(this.copy)
|
||||||
|
|
||||||
|
// If the value hasn't changed we don't want to do anything.
|
||||||
|
if (newValue === this.value) {
|
||||||
|
this.copy = this.value
|
||||||
|
} else {
|
||||||
|
this.$emit('update', newValue, this.value)
|
||||||
|
this.afterSave()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* This method is called before saving the value. Optionally the value can be
|
||||||
|
* changed or formatted here if necessary.
|
||||||
|
*/
|
||||||
|
beforeSave(value) {
|
||||||
|
return value
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Method that is called after saving the value. This can be overridden in the
|
||||||
|
* component.
|
||||||
|
*/
|
||||||
|
afterSave() {},
|
||||||
|
/**
|
||||||
|
* Should return a boolean if the copy that is going to be saved is valid. If it
|
||||||
|
* returns false unselecting is not possible.
|
||||||
|
*/
|
||||||
|
isValid() {
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
import Vue from 'vue'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
@ -5,7 +6,10 @@ import GridService from '@baserow/modules/database/services/view/grid'
|
||||||
import RowService from '@baserow/modules/database/services/row'
|
import RowService from '@baserow/modules/database/services/row'
|
||||||
|
|
||||||
export function populateRow(row) {
|
export function populateRow(row) {
|
||||||
row._ = { loading: false }
|
row._ = {
|
||||||
|
loading: false,
|
||||||
|
hover: false,
|
||||||
|
}
|
||||||
return row
|
return row
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,7 +122,10 @@ export const mutations = {
|
||||||
ADD_FIELD(state, { field, value }) {
|
ADD_FIELD(state, { field, value }) {
|
||||||
const name = `field_${field.id}`
|
const name = `field_${field.id}`
|
||||||
state.rows.forEach((row) => {
|
state.rows.forEach((row) => {
|
||||||
row[name] = value
|
// We have to use the Vue.set function here to make it reactive immediately.
|
||||||
|
// If we don't do this the value in the field components of the grid and modal
|
||||||
|
// don't have the correct value and will act strange.
|
||||||
|
Vue.set(row, name, value)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
SET_ROW_LOADING(state, { row, value }) {
|
SET_ROW_LOADING(state, { row, value }) {
|
||||||
|
@ -136,6 +143,9 @@ export const mutations = {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
SET_ROW_HOVER(state, { row, value }) {
|
||||||
|
row._.hover = value
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Contains the timeout needed for the delayed delayed scroll top action.
|
// Contains the timeout needed for the delayed delayed scroll top action.
|
||||||
|
@ -549,6 +559,9 @@ export const actions = {
|
||||||
values,
|
values,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
setRowHover({ commit }, { row, value }) {
|
||||||
|
commit('SET_ROW_HOVER', { row, value })
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getters = {
|
export const getters = {
|
||||||
|
|
Loading…
Add table
Reference in a new issue