mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-05-12 12:21:50 +00:00
Enable linking to multiple link to table entries in the form view
This commit is contained in:
parent
587efa2b3a
commit
94af7f96f7
19 changed files with 364 additions and 52 deletions
backend
src/baserow/contrib/database
api/views/form
migrations
views
tests/baserow/contrib/database/api/views/form
changelog/entries/unreleased/feature
web-frontend
locales
modules
core
database
|
@ -49,6 +49,7 @@ class FormViewFieldOptionsSerializer(serializers.ModelSerializer):
|
|||
"order",
|
||||
"conditions",
|
||||
"condition_groups",
|
||||
"field_component",
|
||||
)
|
||||
|
||||
|
||||
|
@ -89,6 +90,7 @@ class PublicFormViewFieldOptionsSerializer(FieldSerializer):
|
|||
"conditions",
|
||||
"condition_groups",
|
||||
"groups",
|
||||
"field_component",
|
||||
)
|
||||
|
||||
# @TODO show correct API docs discriminated by field type.
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
# Generated by Django 3.2.21 on 2023-10-23 11:36
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("database", "0132_add_filter_group"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="formviewfieldoptions",
|
||||
name="field_component",
|
||||
field=models.CharField(
|
||||
default="default",
|
||||
help_text="Indicates which field input component is used in the form. The value is only used in the frontend, and can differ per field.",
|
||||
max_length=32,
|
||||
),
|
||||
),
|
||||
]
|
|
@ -47,6 +47,9 @@ OWNERSHIP_TYPE_COLLABORATIVE = "collaborative"
|
|||
DEFAULT_OWNERSHIP_TYPE = OWNERSHIP_TYPE_COLLABORATIVE
|
||||
VIEW_OWNERSHIP_TYPES = [OWNERSHIP_TYPE_COLLABORATIVE]
|
||||
|
||||
# Must be the same as `modules/database/constants.js`.
|
||||
DEFAULT_FORM_VIEW_FIELD_COMPONENT_KEY = "default"
|
||||
|
||||
|
||||
def get_default_view_content_type():
|
||||
return ContentType.objects.get_for_model(View)
|
||||
|
@ -770,6 +773,12 @@ class FormViewFieldOptions(HierarchicalModelMixin, models.Model):
|
|||
help_text="Indicates whether all (AND) or any (OR) of the conditions should "
|
||||
"match before shown.",
|
||||
)
|
||||
field_component = models.CharField(
|
||||
max_length=32,
|
||||
default=DEFAULT_FORM_VIEW_FIELD_COMPONENT_KEY,
|
||||
help_text="Indicates which field input component is used in the form. The "
|
||||
"value is only used in the frontend, and can differ per field.",
|
||||
)
|
||||
# The default value is the maximum value of the small integer field because a newly
|
||||
# created field must always be last.
|
||||
order = models.SmallIntegerField(
|
||||
|
|
|
@ -543,6 +543,7 @@ class FormViewType(ViewType):
|
|||
"show_when_matching_conditions",
|
||||
"condition_type",
|
||||
"order",
|
||||
"field_component",
|
||||
]
|
||||
serializer_field_names = [
|
||||
"title",
|
||||
|
@ -978,6 +979,7 @@ class FormViewType(ViewType):
|
|||
}
|
||||
for condition in field_option.conditions.all()
|
||||
],
|
||||
"field_component": field_option.field_component,
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -325,6 +325,7 @@ def test_meta_submit_form_view(api_client, data_fixture):
|
|||
"condition_groups": [],
|
||||
"show_when_matching_conditions": False,
|
||||
"field": {"id": text_field.id, "type": "text", "text_default": ""},
|
||||
"field_component": "default",
|
||||
}
|
||||
assert response_json["fields"][1] == {
|
||||
"name": number_field.name,
|
||||
|
@ -341,6 +342,7 @@ def test_meta_submit_form_view(api_client, data_fixture):
|
|||
"number_decimal_places": 0,
|
||||
"number_negative": False,
|
||||
},
|
||||
"field_component": "default",
|
||||
}
|
||||
|
||||
|
||||
|
@ -871,6 +873,7 @@ def test_get_form_view_field_options(
|
|||
"group": condition_group_1.id,
|
||||
}
|
||||
],
|
||||
"field_component": "default",
|
||||
},
|
||||
str(text_field_2.id): {
|
||||
"name": "",
|
||||
|
@ -882,6 +885,7 @@ def test_get_form_view_field_options(
|
|||
"condition_type": "AND",
|
||||
"conditions": [],
|
||||
"condition_groups": [],
|
||||
"field_component": "default",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -940,6 +944,7 @@ def test_patch_form_view_field_options_conditions_create(
|
|||
"group": None,
|
||||
}
|
||||
],
|
||||
"field_component": "test",
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -970,6 +975,7 @@ def test_patch_form_view_field_options_conditions_create(
|
|||
"group": None,
|
||||
}
|
||||
],
|
||||
"field_component": "test",
|
||||
},
|
||||
str(text_field_2.id): {
|
||||
"name": "",
|
||||
|
@ -981,6 +987,7 @@ def test_patch_form_view_field_options_conditions_create(
|
|||
"order": 32767,
|
||||
"condition_groups": [],
|
||||
"conditions": [],
|
||||
"field_component": "default",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -1080,6 +1087,7 @@ def test_patch_form_view_field_options_condition_groups_create(
|
|||
"group": conditions[1]["group_id"],
|
||||
},
|
||||
],
|
||||
"field_component": "default",
|
||||
},
|
||||
str(text_field_2.id): {
|
||||
"name": "",
|
||||
|
@ -1091,6 +1099,7 @@ def test_patch_form_view_field_options_condition_groups_create(
|
|||
"order": 32767,
|
||||
"condition_groups": [],
|
||||
"conditions": [],
|
||||
"field_component": "default",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -1159,6 +1168,7 @@ def test_patch_form_view_field_options_conditions_update(
|
|||
"group": None,
|
||||
}
|
||||
],
|
||||
"field_component": "default",
|
||||
},
|
||||
str(text_field_2.id): {
|
||||
"name": "",
|
||||
|
@ -1170,6 +1180,7 @@ def test_patch_form_view_field_options_conditions_update(
|
|||
"order": 32767,
|
||||
"condition_groups": [],
|
||||
"conditions": [],
|
||||
"field_component": "default",
|
||||
},
|
||||
str(text_field_3.id): {
|
||||
"name": "",
|
||||
|
@ -1181,6 +1192,7 @@ def test_patch_form_view_field_options_conditions_update(
|
|||
"order": 32767,
|
||||
"condition_groups": [],
|
||||
"conditions": [],
|
||||
"field_component": "default",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -1291,6 +1303,7 @@ def test_patch_form_view_field_options_condition_groups_update(
|
|||
"group": condition_group_1.id,
|
||||
}
|
||||
],
|
||||
"field_component": "default",
|
||||
},
|
||||
str(text_field_2.id): {
|
||||
"name": "",
|
||||
|
@ -1302,6 +1315,7 @@ def test_patch_form_view_field_options_condition_groups_update(
|
|||
"order": 32767,
|
||||
"condition_groups": [],
|
||||
"conditions": [],
|
||||
"field_component": "default",
|
||||
},
|
||||
str(text_field_3.id): {
|
||||
"name": "",
|
||||
|
@ -1313,6 +1327,7 @@ def test_patch_form_view_field_options_condition_groups_update(
|
|||
"order": 32767,
|
||||
"condition_groups": [],
|
||||
"conditions": [],
|
||||
"field_component": "default",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -1386,6 +1401,7 @@ def test_patch_form_view_field_options_conditions_update_position(
|
|||
"order": 1,
|
||||
"condition_groups": [],
|
||||
"conditions": [],
|
||||
"field_component": "default",
|
||||
},
|
||||
str(text_field_3.id): {
|
||||
"name": "",
|
||||
|
@ -1405,6 +1421,7 @@ def test_patch_form_view_field_options_conditions_update_position(
|
|||
"group": None,
|
||||
}
|
||||
],
|
||||
"field_component": "default",
|
||||
},
|
||||
str(text_field_2.id): {
|
||||
"name": "",
|
||||
|
@ -1416,6 +1433,7 @@ def test_patch_form_view_field_options_conditions_update_position(
|
|||
"order": 3,
|
||||
"condition_groups": [],
|
||||
"conditions": [],
|
||||
"field_component": "default",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -1467,6 +1485,7 @@ def test_patch_form_view_field_options_conditions_delete(
|
|||
"order": 32767,
|
||||
"condition_groups": [],
|
||||
"conditions": [],
|
||||
"field_component": "default",
|
||||
},
|
||||
str(text_field_2.id): {
|
||||
"name": "",
|
||||
|
@ -1478,6 +1497,7 @@ def test_patch_form_view_field_options_conditions_delete(
|
|||
"order": 32767,
|
||||
"condition_groups": [],
|
||||
"conditions": [],
|
||||
"field_component": "default",
|
||||
},
|
||||
str(text_field_3.id): {
|
||||
"name": "",
|
||||
|
@ -1489,6 +1509,7 @@ def test_patch_form_view_field_options_conditions_delete(
|
|||
"order": 32767,
|
||||
"condition_groups": [],
|
||||
"conditions": [],
|
||||
"field_component": "default",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -1545,6 +1566,7 @@ def test_patch_form_view_field_options_condition_groups_delete(
|
|||
"order": 32767,
|
||||
"condition_groups": [],
|
||||
"conditions": [],
|
||||
"field_component": "default",
|
||||
},
|
||||
str(text_field_2.id): {
|
||||
"name": "",
|
||||
|
@ -1556,6 +1578,7 @@ def test_patch_form_view_field_options_condition_groups_delete(
|
|||
"order": 32767,
|
||||
"condition_groups": [],
|
||||
"conditions": [],
|
||||
"field_component": "default",
|
||||
},
|
||||
str(text_field_3.id): {
|
||||
"name": "",
|
||||
|
@ -1567,6 +1590,7 @@ def test_patch_form_view_field_options_condition_groups_delete(
|
|||
"order": 32767,
|
||||
"condition_groups": [],
|
||||
"conditions": [],
|
||||
"field_component": "default",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"type": "feature",
|
||||
"message": "Enable linking to multiple link to table entries in form view",
|
||||
"issue_number": 810,
|
||||
"bullet_points": [],
|
||||
"created_at": "2023-10-22"
|
||||
}
|
|
@ -100,7 +100,10 @@
|
|||
"count": "Count",
|
||||
"rollup": "Rollup",
|
||||
"lookup": "Lookup",
|
||||
"multipleCollaborators": "Collaborators"
|
||||
"multipleCollaborators": "Collaborators",
|
||||
"defaultFormViewComponent": "Default",
|
||||
"linkRowSingle": "Single",
|
||||
"linkRowMultiple": "Multiple"
|
||||
},
|
||||
"fieldErrors": {
|
||||
"invalidNumber": "Invalid number",
|
||||
|
|
|
@ -6,15 +6,11 @@
|
|||
justify-content: right;
|
||||
}
|
||||
|
||||
&.control--horizontal {
|
||||
&.control--horizontal,
|
||||
&.control--horizontal-variable {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
|
||||
& .control__elements {
|
||||
flex-basis: 70%;
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,6 +46,12 @@
|
|||
font-weight: 500;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.control--horizontal-variable & {
|
||||
flex: auto 0 0;
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.control__label-icon {
|
||||
|
@ -62,6 +64,18 @@
|
|||
position: relative;
|
||||
}
|
||||
|
||||
.control__elements {
|
||||
.control--horizontal & {
|
||||
flex-basis: 70%;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.control--horizontal-variable & {
|
||||
margin-top: 0;
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.control__elements--flex {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
|
@ -160,6 +160,17 @@
|
|||
}
|
||||
}
|
||||
|
||||
.flex {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: var(--gap, 10px);
|
||||
}
|
||||
|
||||
.flex-100 {
|
||||
width: 100%;
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0);
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
class="control"
|
||||
:class="{
|
||||
'control--horizontal': horizontal,
|
||||
'control--horizontal-variable': horizontalVariable,
|
||||
}"
|
||||
>
|
||||
<label
|
||||
|
@ -121,6 +122,11 @@ export default {
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
horizontalVariable: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
|
|
|
@ -121,6 +121,14 @@
|
|||
large
|
||||
icon-left="iconoir-search"
|
||||
/>
|
||||
<FormInput
|
||||
v-model="input"
|
||||
horizontal-variable
|
||||
label="Horizontal Large icon field left"
|
||||
placeholder="Enter something here"
|
||||
large
|
||||
icon-left="iconoir-search"
|
||||
/>
|
||||
<div class="control">
|
||||
<label class="control__label">Checkbox field</label>
|
||||
<div class="control__elements">
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<!-- prettier-ignore -->
|
||||
<div v-if="field.description" class="form-view__field-description whitespace-pre-wrap">{{ field.description }}</div>
|
||||
<component
|
||||
:is="getFieldComponent()"
|
||||
:is="selectedFieldComponent.component"
|
||||
:key="field.field.id"
|
||||
ref="field"
|
||||
:workspace-id="0"
|
||||
|
@ -18,7 +18,7 @@
|
|||
:read-only="false"
|
||||
:required="field.required"
|
||||
:touched="field._.touched"
|
||||
v-bind="getFieldComponentProperties()"
|
||||
v-bind="selectedFieldComponent.properties"
|
||||
@update="$emit('input', $event)"
|
||||
@touched="field._.touched = true"
|
||||
/>
|
||||
|
@ -29,6 +29,7 @@
|
|||
|
||||
<script>
|
||||
import FieldContext from '@baserow/modules/database/components/field/FieldContext'
|
||||
import { DEFAULT_FORM_VIEW_FIELD_COMPONENT_KEY } from '@baserow/modules/database/constants'
|
||||
|
||||
export default {
|
||||
name: 'FormPageField',
|
||||
|
@ -47,17 +48,20 @@ export default {
|
|||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
selectedFieldComponent() {
|
||||
const components = this.$registry
|
||||
.get('field', this.field.field.type)
|
||||
.getFormViewFieldComponents(this.field.field, this)
|
||||
return Object.prototype.hasOwnProperty.call(
|
||||
components,
|
||||
this.field.field_component
|
||||
)
|
||||
? components[this.field.field_component]
|
||||
: components[DEFAULT_FORM_VIEW_FIELD_COMPONENT_KEY]
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getFieldComponent() {
|
||||
return this.$registry
|
||||
.get('field', this.field.field.type)
|
||||
.getFormViewFieldComponent(this.field.field)
|
||||
},
|
||||
getFieldComponentProperties() {
|
||||
return this.$registry
|
||||
.get('field', this.field.field.type)
|
||||
.getFormViewFieldComponentProperties(this)
|
||||
},
|
||||
focus() {
|
||||
this.$el.scrollIntoView({ behavior: 'smooth' })
|
||||
this.$emit('focussed')
|
||||
|
|
|
@ -66,7 +66,7 @@
|
|||
></a>
|
||||
</div>
|
||||
<component
|
||||
:is="getFieldComponent()"
|
||||
:is="selectedFieldComponent.component"
|
||||
ref="field"
|
||||
:slug="view.slug"
|
||||
:workspace-id="database.workspace.id"
|
||||
|
@ -74,9 +74,31 @@
|
|||
:value="value"
|
||||
:read-only="readOnly"
|
||||
:lazy-load="true"
|
||||
:touched="false"
|
||||
:required="fieldOptions.required"
|
||||
@update="updateValue"
|
||||
/>
|
||||
<div class="form-view__field-options">
|
||||
<div
|
||||
v-if="Object.keys(fieldComponents).length > 1"
|
||||
class="control control--horizontal-variable"
|
||||
>
|
||||
<label class="control__label">
|
||||
{{ $t('formViewField.showFieldAs') }}
|
||||
</label>
|
||||
<div class="control__elements">
|
||||
<RadioButton
|
||||
v-for="(v, key) in fieldComponents"
|
||||
:key="key"
|
||||
:model-value="fieldOptions.field_component"
|
||||
:value="key"
|
||||
@input="
|
||||
$emit('updated-field-options', { field_component: $event })
|
||||
"
|
||||
>{{ v.name }}</RadioButton
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<SwitchInput
|
||||
:value="fieldOptions.required"
|
||||
:large="true"
|
||||
|
@ -150,6 +172,7 @@
|
|||
<script>
|
||||
import { isElement, onClickOutside } from '@baserow/modules/core/utils/dom'
|
||||
import { clone } from '@baserow/modules/core/utils/object'
|
||||
import { DEFAULT_FORM_VIEW_FIELD_COMPONENT_KEY } from '@baserow/modules/database/constants'
|
||||
import FieldContext from '@baserow/modules/database/components/field/FieldContext'
|
||||
import ViewFieldConditionsForm from '@baserow/modules/database/components/view/ViewFieldConditionsForm'
|
||||
|
||||
|
@ -205,6 +228,18 @@ export default {
|
|||
const index = this.fields.findIndex((f) => f.id === this.field.id)
|
||||
return this.fields.slice(0, index)
|
||||
},
|
||||
fieldComponents() {
|
||||
return this.getFieldType().getFormViewFieldComponents(this.field, this)
|
||||
},
|
||||
selectedFieldComponent() {
|
||||
const components = this.fieldComponents
|
||||
return Object.prototype.hasOwnProperty.call(
|
||||
components,
|
||||
this.fieldOptions.field_component
|
||||
)
|
||||
? components[this.fieldOptions.field_component]
|
||||
: components[DEFAULT_FORM_VIEW_FIELD_COMPONENT_KEY]
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
field: {
|
||||
|
@ -248,9 +283,6 @@ export default {
|
|||
getFieldType() {
|
||||
return this.$registry.get('field', this.field.type)
|
||||
},
|
||||
getFieldComponent() {
|
||||
return this.getFieldType().getFormViewFieldComponent(this.field)
|
||||
},
|
||||
resetValue() {
|
||||
this.value = this.getFieldType().getEmptyValue(this.field)
|
||||
},
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
<template>
|
||||
<div class="control__elements">
|
||||
<div
|
||||
v-for="(v, index) in value"
|
||||
:key="index + '-' + value[index].id"
|
||||
class="margin-bottom-2 flex"
|
||||
>
|
||||
<div class="flex-100">
|
||||
<PaginatedDropdown
|
||||
:fetch-page="fetchPage"
|
||||
:value="value[index].id"
|
||||
:initial-display-name="value[index].value"
|
||||
class="dropdown--tiny"
|
||||
:class="{
|
||||
'dropdown--error':
|
||||
touched && !valid && isInvalidValue(value[index]),
|
||||
}"
|
||||
:fetch-on-open="lazyLoad"
|
||||
:disabled="readOnly"
|
||||
:include-display-name-in-selected-event="true"
|
||||
@input="updateValue($event, index)"
|
||||
></PaginatedDropdown>
|
||||
</div>
|
||||
<div class="align-right">
|
||||
<Button
|
||||
tag="a"
|
||||
icon="iconoir-trash"
|
||||
type="ghost"
|
||||
@click="remove(index)"
|
||||
></Button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Button tag="a" icon="iconoir-plus" type="ghost" @click="add"></Button>
|
||||
</div>
|
||||
<div v-show="touched && !valid" class="error">
|
||||
{{ error }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PaginatedDropdown from '@baserow/modules/core/components/PaginatedDropdown'
|
||||
import rowEditField from '@baserow/modules/database/mixins/rowEditField'
|
||||
import ViewService from '@baserow/modules/database/services/view'
|
||||
import { clone } from '@baserow/modules/core/utils/object'
|
||||
|
||||
export default {
|
||||
name: 'FormViewFieldMultipleLinkRow',
|
||||
components: { PaginatedDropdown },
|
||||
mixins: [rowEditField],
|
||||
props: {
|
||||
slug: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
/**
|
||||
* In some cases, for example in the form view preview, we only want to fetch the
|
||||
* first related rows after the user has opened the dropdown. This will prevent a
|
||||
* race condition where the enabled state of the field might not yet been updated
|
||||
* before we fetch the related rows. If the state has not yet been changed in the
|
||||
* backend, it will result in an error.
|
||||
*/
|
||||
lazyLoad: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
created() {
|
||||
if (this.value.length === 0 && this.required) {
|
||||
this.add()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getValidationError(value) {
|
||||
const error = rowEditField.methods.getValidationError.call(this, value)
|
||||
|
||||
if (!this.required && error === null) {
|
||||
const empty = value.some((v) => this.isInvalidValue(v))
|
||||
if (empty) {
|
||||
return this.$t('error.requiredField')
|
||||
}
|
||||
}
|
||||
|
||||
return error
|
||||
},
|
||||
isInvalidValue(value) {
|
||||
return !Number.isInteger(value.id)
|
||||
},
|
||||
fetchPage(page, search) {
|
||||
const publicAuthToken =
|
||||
this.$store.getters['page/view/public/getAuthToken']
|
||||
return ViewService(this.$client).linkRowFieldLookup(
|
||||
this.slug,
|
||||
this.field.id,
|
||||
page,
|
||||
search,
|
||||
100,
|
||||
publicAuthToken
|
||||
)
|
||||
},
|
||||
add() {
|
||||
const newValue = clone(this.value)
|
||||
newValue.push({
|
||||
id: false,
|
||||
value: '',
|
||||
})
|
||||
this.$emit('update', newValue, this.value)
|
||||
},
|
||||
remove(index) {
|
||||
const newValue = clone(this.value)
|
||||
newValue.splice(index, 1)
|
||||
this.$emit('update', newValue, this.value)
|
||||
},
|
||||
updateValue({ value, displayName }, index) {
|
||||
const newValue = clone(this.value)
|
||||
newValue[index] = { id: value, value: displayName }
|
||||
this.$emit('update', newValue, this.value)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -47,7 +47,10 @@ export default {
|
|||
computed: {
|
||||
compatible() {
|
||||
const fieldType = this.$registry.get('field', this.field.type)
|
||||
return fieldType.getFormViewFieldComponent(this.field) !== null
|
||||
return (
|
||||
Object.keys(fieldType.getFormViewFieldComponents(this.field, this))
|
||||
.length > 0
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
2
web-frontend/modules/database/constants.js
Normal file
2
web-frontend/modules/database/constants.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
// Must be the same as `src/baserow/contrib/database/constants.py`.
|
||||
export const DEFAULT_FORM_VIEW_FIELD_COMPONENT_KEY = 'default'
|
|
@ -93,6 +93,7 @@ import RowHistoryFieldBoolean from '@baserow/modules/database/components/row/Row
|
|||
import RowHistoryFieldLinkRow from '@baserow/modules/database/components/row/RowHistoryFieldLinkRow'
|
||||
|
||||
import FormViewFieldLinkRow from '@baserow/modules/database/components/view/form/FormViewFieldLinkRow'
|
||||
import FormViewFieldMultipleLinkRow from '@baserow/modules/database/components/view/form/FormViewFieldMultipleLinkRow'
|
||||
|
||||
import { trueString } from '@baserow/modules/database/utils/constants'
|
||||
import {
|
||||
|
@ -111,6 +112,7 @@ import FieldLookupSubForm from '@baserow/modules/database/components/field/Field
|
|||
import FieldCountSubForm from '@baserow/modules/database/components/field/FieldCountSubForm'
|
||||
import FieldRollupSubForm from '@baserow/modules/database/components/field/FieldRollupSubForm'
|
||||
import RowEditFieldFormula from '@baserow/modules/database/components/row/RowEditFieldFormula'
|
||||
import { DEFAULT_FORM_VIEW_FIELD_COMPONENT_KEY } from '@baserow/modules/database/constants'
|
||||
import ViewService from '@baserow/modules/database/services/view'
|
||||
import FormService from '@baserow/modules/database/services/view/form'
|
||||
import { UploadFileUserFileUploadType } from '@baserow/modules/core/userFileUploadTypes'
|
||||
|
@ -181,19 +183,24 @@ export class FieldType extends Registerable {
|
|||
}
|
||||
|
||||
/**
|
||||
* By default the row edit field component is used in the form. This can
|
||||
* optionally be another component if needed. If null is returned, then the field
|
||||
* is marked as not compatible with the form view.
|
||||
* By default, the edit field component is used in the form. This can optionally be
|
||||
* replaced by another component if needed. If an empty object `{}` is returned,
|
||||
* then the field is marked as not compatible with the form view.
|
||||
*
|
||||
* The returned object should have one key with an empty string `''` as default
|
||||
* component. If the object has multiple keys, then the user will be presented
|
||||
* with these as options. This can be used to for example display a `single_select`
|
||||
* field type as dropdown or radio inputs.
|
||||
*/
|
||||
getFormViewFieldComponent(field) {
|
||||
return this.getRowEditFieldComponent(field)
|
||||
}
|
||||
|
||||
/*
|
||||
* Optional properties for the FormViewFieldComponent
|
||||
*/
|
||||
getFormViewFieldComponentProperties(context) {
|
||||
return {}
|
||||
getFormViewFieldComponents(field) {
|
||||
const { i18n } = this.app
|
||||
return {
|
||||
[DEFAULT_FORM_VIEW_FIELD_COMPONENT_KEY]: {
|
||||
name: i18n.t('fieldType.defaultFormViewComponent'),
|
||||
component: this.getRowEditFieldComponent(field),
|
||||
properties: {},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -856,8 +863,20 @@ export class LinkRowFieldType extends FieldType {
|
|||
return RowEditFieldLinkRow
|
||||
}
|
||||
|
||||
getFormViewFieldComponent() {
|
||||
return FormViewFieldLinkRow
|
||||
getFormViewFieldComponents(field) {
|
||||
const { i18n } = this.app
|
||||
const components = super.getFormViewFieldComponents(field)
|
||||
components[DEFAULT_FORM_VIEW_FIELD_COMPONENT_KEY].name = i18n.t(
|
||||
'fieldType.linkRowSingle'
|
||||
)
|
||||
components[DEFAULT_FORM_VIEW_FIELD_COMPONENT_KEY].component =
|
||||
FormViewFieldLinkRow
|
||||
components.multiple = {
|
||||
name: i18n.t('fieldType.linkRowMultiple'),
|
||||
component: FormViewFieldMultipleLinkRow,
|
||||
properties: {},
|
||||
}
|
||||
return components
|
||||
}
|
||||
|
||||
getCardComponent() {
|
||||
|
@ -1018,6 +1037,18 @@ export class LinkRowFieldType extends FieldType {
|
|||
getCanImport() {
|
||||
return true
|
||||
}
|
||||
|
||||
isEmpty(field, value) {
|
||||
if (super.isEmpty(field, value)) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (value.some((v) => !Number.isInteger(v.id))) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export class NumberFieldType extends FieldType {
|
||||
|
@ -1705,8 +1736,8 @@ export class CreatedOnLastModifiedBaseFieldType extends BaseDateFieldType {
|
|||
return FieldDateSubForm
|
||||
}
|
||||
|
||||
getFormViewFieldComponent() {
|
||||
return null
|
||||
getFormViewFieldComponents(field) {
|
||||
return {}
|
||||
}
|
||||
|
||||
getRowEditFieldComponent(field) {
|
||||
|
@ -2040,9 +2071,10 @@ export class FileFieldType extends FieldType {
|
|||
return RowHistoryFieldFile
|
||||
}
|
||||
|
||||
getFormViewFieldComponentProperties({ $store, $client, slug }) {
|
||||
getFormViewFieldComponents(field, { $store, $client, slug }) {
|
||||
const components = super.getFormViewFieldComponents(field)
|
||||
const userFileUploadTypes = [UploadFileUserFileUploadType.getType()]
|
||||
return {
|
||||
components[DEFAULT_FORM_VIEW_FIELD_COMPONENT_KEY].properties = {
|
||||
userFileUploadTypes,
|
||||
uploadFile: (file, progress) => {
|
||||
return FormService($client).uploadFile(
|
||||
|
@ -2053,6 +2085,7 @@ export class FileFieldType extends FieldType {
|
|||
)
|
||||
},
|
||||
}
|
||||
return components
|
||||
}
|
||||
|
||||
getCardComponent() {
|
||||
|
@ -2229,10 +2262,12 @@ export class SingleSelectFieldType extends FieldType {
|
|||
return RowHistoryFieldSingleSelect
|
||||
}
|
||||
|
||||
getFormViewFieldComponentProperties() {
|
||||
return {
|
||||
getFormViewFieldComponents(field) {
|
||||
const components = super.getFormViewFieldComponents(field)
|
||||
components[DEFAULT_FORM_VIEW_FIELD_COMPONENT_KEY].properties = {
|
||||
'allow-create-options': false,
|
||||
}
|
||||
return components
|
||||
}
|
||||
|
||||
getCardComponent() {
|
||||
|
@ -2419,10 +2454,12 @@ export class MultipleSelectFieldType extends FieldType {
|
|||
return RowEditFieldMultipleSelect
|
||||
}
|
||||
|
||||
getFormViewFieldComponentProperties() {
|
||||
return {
|
||||
getFormViewFieldComponents(field) {
|
||||
const components = super.getFormViewFieldComponents(field)
|
||||
components[DEFAULT_FORM_VIEW_FIELD_COMPONENT_KEY].properties = {
|
||||
'allow-create-options': false,
|
||||
}
|
||||
return components
|
||||
}
|
||||
|
||||
getCardComponent() {
|
||||
|
@ -2884,8 +2921,8 @@ export class FormulaFieldType extends FieldType {
|
|||
return true
|
||||
}
|
||||
|
||||
getFormViewFieldComponent() {
|
||||
return null
|
||||
getFormViewFieldComponents(field) {
|
||||
return {}
|
||||
}
|
||||
|
||||
canBeReferencedByFormulaField() {
|
||||
|
@ -3029,8 +3066,8 @@ export class MultipleCollaboratorsFieldType extends FieldType {
|
|||
return value
|
||||
}
|
||||
|
||||
getFormViewFieldComponent() {
|
||||
return null
|
||||
getFormViewFieldComponents(field) {
|
||||
return {}
|
||||
}
|
||||
|
||||
getEmptyValue() {
|
||||
|
|
|
@ -710,7 +710,8 @@
|
|||
"descriptionPlaceholder": "Description",
|
||||
"showWhenMatchingConditions": "show when conditions are met",
|
||||
"addCondition": "Add condition",
|
||||
"addConditionGroup": "Add condition group"
|
||||
"addConditionGroup": "Add condition group",
|
||||
"showFieldAs": "Show field as"
|
||||
},
|
||||
"duplicateFieldContext": {
|
||||
"duplicate": "Duplicate field",
|
||||
|
|
|
@ -53,7 +53,10 @@ export default {
|
|||
return true
|
||||
}
|
||||
const fieldType = this.$registry.get('field', field.type)
|
||||
return fieldType.getFormViewFieldComponent(field) !== null
|
||||
return (
|
||||
Object.keys(fieldType.getFormViewFieldComponents(field, this))
|
||||
.length > 0
|
||||
)
|
||||
})
|
||||
.forEach((field) => {
|
||||
newFieldOptions[field.id] = values
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue