mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-07 14:25:37 +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
|
||||
|
||||
* 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.
|
||||
* 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
|
||||
|
|
|
@ -15,6 +15,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.control-label-icon {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.control-context {
|
||||
color: $color-primary-900;
|
||||
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"
|
||||
:key="'left-row-' + view.id + '-' + row.id"
|
||||
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)"
|
||||
>
|
||||
<div
|
||||
|
@ -54,7 +59,10 @@
|
|||
>
|
||||
<div class="grid-view-row-info">
|
||||
<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>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -68,6 +76,7 @@
|
|||
:style="{ width: widths.fields[primary.id] + 'px' }"
|
||||
@selected="selectedField(primary, $event.component)"
|
||||
@selectNext="selectNextField(row, primary, fields, primary)"
|
||||
@update="updateValue"
|
||||
></GridViewField>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -175,7 +184,12 @@
|
|||
v-for="row in rows"
|
||||
:key="'right-row-' + view.id + '-' + row.id"
|
||||
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)"
|
||||
>
|
||||
<GridViewField
|
||||
|
@ -193,6 +207,7 @@
|
|||
selectNextField(row, field, fields, primary, true)
|
||||
"
|
||||
@selectNext="selectNextField(row, field, fields, primary)"
|
||||
@update="updateValue"
|
||||
></GridViewField>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -225,6 +240,13 @@
|
|||
</li>
|
||||
</ul>
|
||||
</Context>
|
||||
<RowEditModal
|
||||
ref="rowEditModal"
|
||||
:table="table"
|
||||
:primary="primary"
|
||||
:fields="fields"
|
||||
@update="updateValue"
|
||||
></RowEditModal>
|
||||
</div>
|
||||
</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 GridViewField from '@baserow/modules/database/components/view/grid/GridViewField'
|
||||
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 _ from 'lodash'
|
||||
|
||||
|
@ -245,6 +268,7 @@ export default {
|
|||
GridViewFieldType,
|
||||
GridViewField,
|
||||
GridViewFieldWidthHandle,
|
||||
RowEditModal,
|
||||
},
|
||||
props: {
|
||||
primary: {
|
||||
|
@ -273,6 +297,7 @@ export default {
|
|||
addHover: false,
|
||||
loading: true,
|
||||
selectedRow: null,
|
||||
lastHoveredRow: null,
|
||||
widths: {
|
||||
fields: {},
|
||||
},
|
||||
|
@ -308,6 +333,19 @@ export default {
|
|||
this.calculateWidths(this.primary, this.fields, this.fieldOptions)
|
||||
},
|
||||
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) {
|
||||
const $rightBody = this.$refs.rightBody
|
||||
const $right = this.$refs.right
|
||||
|
@ -412,7 +450,8 @@ export default {
|
|||
try {
|
||||
await this.$store.dispatch('view/grid/create', {
|
||||
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: {},
|
||||
})
|
||||
} catch (error) {
|
||||
|
@ -523,6 +562,21 @@ export default {
|
|||
current[0].unselect()
|
||||
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>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<div class="grid-view-column" @click="select()">
|
||||
<component
|
||||
:is="getFieldComponent(field.type)"
|
||||
ref="column"
|
||||
ref="field"
|
||||
:field="field"
|
||||
:value="row['field_' + field.id]"
|
||||
:selected="selected"
|
||||
|
@ -13,15 +13,10 @@
|
|||
|
||||
<script>
|
||||
import { isElement } from '@baserow/modules/core/utils/dom'
|
||||
import { notifyIf } from '@baserow/modules/core/utils/error'
|
||||
|
||||
export default {
|
||||
name: 'GridViewField',
|
||||
props: {
|
||||
table: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
field: {
|
||||
type: Object,
|
||||
required: true,
|
||||
|
@ -62,25 +57,12 @@ export default {
|
|||
* which will actually update the value via the store.
|
||||
*/
|
||||
update(value, oldValue) {
|
||||
this.$store
|
||||
.dispatch('view/grid/updateValue', {
|
||||
table: this.table,
|
||||
row: this.row,
|
||||
field: this.field,
|
||||
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()
|
||||
this.$emit('update', {
|
||||
row: this.row,
|
||||
field: this.field,
|
||||
value,
|
||||
oldValue,
|
||||
})
|
||||
},
|
||||
/**
|
||||
* Method that is called when a user clicks on the grid field. It wil
|
||||
|
@ -97,7 +79,7 @@ export default {
|
|||
this.clickTimestamp !== null &&
|
||||
timestamp - this.clickTimestamp < 200
|
||||
) {
|
||||
this.$refs.column.doubleClick()
|
||||
this.$refs.field.doubleClick()
|
||||
}
|
||||
} else {
|
||||
// If the field is not yet selected we can change the state to selected.
|
||||
|
@ -105,7 +87,7 @@ export default {
|
|||
this.$nextTick(() => {
|
||||
// Call the select method on the next tick because we want to wait for all
|
||||
// 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
|
||||
|
@ -146,7 +128,7 @@ export default {
|
|||
this.clickTimestamp = timestamp
|
||||
},
|
||||
unselect() {
|
||||
this.$refs.column.beforeUnSelect()
|
||||
this.$refs.field.beforeUnSelect()
|
||||
this.$nextTick(() => {
|
||||
this.selected = false
|
||||
})
|
||||
|
|
|
@ -18,6 +18,19 @@ import gridField from '@baserow/modules/database/mixins/gridField'
|
|||
export default {
|
||||
mixins: [gridField],
|
||||
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) {
|
||||
if (!this.selected) {
|
||||
return
|
||||
|
|
|
@ -25,42 +25,17 @@
|
|||
<script>
|
||||
import gridField from '@baserow/modules/database/mixins/gridField'
|
||||
import gridFieldInput from '@baserow/modules/database/mixins/gridFieldInput'
|
||||
import numberField from '@baserow/modules/database/mixins/numberField'
|
||||
|
||||
export default {
|
||||
mixins: [gridField, gridFieldInput],
|
||||
mixins: [gridField, gridFieldInput, numberField],
|
||||
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() {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.input.focus()
|
||||
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>
|
||||
|
|
|
@ -15,72 +15,23 @@
|
|||
>
|
||||
<i class="fas fa-caret-down"></i>
|
||||
</a>
|
||||
<Context ref="context">
|
||||
<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>
|
||||
<FieldContext ref="context" :field="field"></FieldContext>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { notifyIf } from '@baserow/modules/core/utils/error'
|
||||
import UpdateFieldContext from '@baserow/modules/database/components/field/UpdateFieldContext'
|
||||
import FieldContext from '@baserow/modules/database/components/field/FieldContext'
|
||||
|
||||
export default {
|
||||
name: 'GridViewFieldType',
|
||||
components: { UpdateFieldContext },
|
||||
components: { FieldContext },
|
||||
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>
|
||||
|
|
|
@ -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 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 {
|
||||
/**
|
||||
* 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() {
|
||||
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
|
||||
* empty value to show right away.
|
||||
|
@ -90,6 +107,13 @@ export class FieldType extends Registerable {
|
|||
name: this.name,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Should return a for humans readable representation of the value.
|
||||
*/
|
||||
toHumanReadableString(field, value) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
export class TextFieldType extends FieldType {
|
||||
|
@ -113,6 +137,10 @@ export class TextFieldType extends FieldType {
|
|||
return GridViewFieldText
|
||||
}
|
||||
|
||||
getRowEditFieldComponent() {
|
||||
return RowEditFieldText
|
||||
}
|
||||
|
||||
getEmptyValue(field) {
|
||||
return field.text_default
|
||||
}
|
||||
|
@ -138,6 +166,10 @@ export class NumberFieldType extends FieldType {
|
|||
getGridViewFieldComponent() {
|
||||
return GridViewFieldNumber
|
||||
}
|
||||
|
||||
getRowEditFieldComponent() {
|
||||
return RowEditFieldNumber
|
||||
}
|
||||
}
|
||||
|
||||
export class BooleanFieldType extends FieldType {
|
||||
|
@ -157,6 +189,10 @@ export class BooleanFieldType extends FieldType {
|
|||
return GridViewFieldBoolean
|
||||
}
|
||||
|
||||
getRowEditFieldComponent() {
|
||||
return RowEditFieldBoolean
|
||||
}
|
||||
|
||||
getEmptyValue(field) {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -8,11 +8,11 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
/**
|
||||
* Indicates whether of the user is editing the value.
|
||||
* Indicates whether the user is editing the value.
|
||||
*/
|
||||
editing: false,
|
||||
/**
|
||||
* A temporary copy of the value when editing.
|
||||
* A temporary copy of the value when editing.
|
||||
*/
|
||||
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 _ from 'lodash'
|
||||
|
||||
|
@ -5,7 +6,10 @@ import GridService from '@baserow/modules/database/services/view/grid'
|
|||
import RowService from '@baserow/modules/database/services/row'
|
||||
|
||||
export function populateRow(row) {
|
||||
row._ = { loading: false }
|
||||
row._ = {
|
||||
loading: false,
|
||||
hover: false,
|
||||
}
|
||||
return row
|
||||
}
|
||||
|
||||
|
@ -118,7 +122,10 @@ export const mutations = {
|
|||
ADD_FIELD(state, { field, value }) {
|
||||
const name = `field_${field.id}`
|
||||
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 }) {
|
||||
|
@ -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.
|
||||
|
@ -549,6 +559,9 @@ export const actions = {
|
|||
values,
|
||||
})
|
||||
},
|
||||
setRowHover({ commit }, { row, value }) {
|
||||
commit('SET_ROW_HOVER', { row, value })
|
||||
},
|
||||
}
|
||||
|
||||
export const getters = {
|
||||
|
|
Loading…
Add table
Reference in a new issue