mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-14 17:18:33 +00:00
Merge branch '2517-improve-dropdow-element-to-allow-to-change-the-display' into 'develop'
Resolve "Improve Dropdown element to allow to change the display" Closes #2517 See merge request baserow/baserow!2294
This commit is contained in:
commit
71d9ccae3c
41 changed files with 662 additions and 340 deletions
backend
src/baserow
tests/baserow/contrib/builder
changelog/entries/unreleased/feature
web-frontend
modules
builder
core
assets
icons
scss
components/builder/elements
variables.scsscomponents
mixins
database/components/field
test/unit/builder
|
@ -11,8 +11,8 @@ from baserow.contrib.builder.api.workflow_actions.serializers import (
|
|||
BuilderWorkflowActionSerializer,
|
||||
)
|
||||
from baserow.contrib.builder.elements.models import (
|
||||
ChoiceElementOption,
|
||||
CollectionField,
|
||||
DropdownElementOption,
|
||||
Element,
|
||||
)
|
||||
from baserow.contrib.builder.elements.registries import (
|
||||
|
@ -303,7 +303,7 @@ class UpdateCollectionFieldSerializer(serializers.ModelSerializer):
|
|||
value = FormulaSerializerField(allow_blank=True)
|
||||
|
||||
|
||||
class DropdownOptionSerializer(serializers.ModelSerializer):
|
||||
class ChoiceOptionSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = DropdownElementOption
|
||||
model = ChoiceElementOption
|
||||
fields = ["id", "value", "name"]
|
||||
|
|
|
@ -172,8 +172,8 @@ class BuilderConfig(AppConfig):
|
|||
from .elements.element_types import (
|
||||
ButtonElementType,
|
||||
CheckboxElementType,
|
||||
ChoiceElementType,
|
||||
ColumnElementType,
|
||||
DropdownElementType,
|
||||
FormContainerElementType,
|
||||
HeadingElementType,
|
||||
IFrameElementType,
|
||||
|
@ -196,7 +196,7 @@ class BuilderConfig(AppConfig):
|
|||
element_type_registry.register(TableElementType())
|
||||
element_type_registry.register(RepeatElementType())
|
||||
element_type_registry.register(FormContainerElementType())
|
||||
element_type_registry.register(DropdownElementType())
|
||||
element_type_registry.register(ChoiceElementType())
|
||||
element_type_registry.register(CheckboxElementType())
|
||||
element_type_registry.register(IFrameElementType())
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ from django.db.models.functions import Cast
|
|||
from rest_framework import serializers
|
||||
from rest_framework.exceptions import ValidationError as DRFValidationError
|
||||
|
||||
from baserow.contrib.builder.api.elements.serializers import DropdownOptionSerializer
|
||||
from baserow.contrib.builder.api.elements.serializers import ChoiceOptionSerializer
|
||||
from baserow.contrib.builder.data_providers.exceptions import (
|
||||
FormDataProviderChunkInvalidException,
|
||||
)
|
||||
|
@ -26,9 +26,9 @@ from baserow.contrib.builder.elements.models import (
|
|||
WIDTHS,
|
||||
ButtonElement,
|
||||
CheckboxElement,
|
||||
ChoiceElement,
|
||||
ChoiceElementOption,
|
||||
ColumnElement,
|
||||
DropdownElement,
|
||||
DropdownElementOption,
|
||||
Element,
|
||||
FormContainerElement,
|
||||
HeadingElement,
|
||||
|
@ -1127,10 +1127,17 @@ class CheckboxElementType(InputElementType):
|
|||
}
|
||||
|
||||
|
||||
class DropdownElementType(FormElementTypeMixin, ElementType):
|
||||
type = "dropdown"
|
||||
model_class = DropdownElement
|
||||
allowed_fields = ["label", "default_value", "required", "placeholder", "multiple"]
|
||||
class ChoiceElementType(FormElementTypeMixin, ElementType):
|
||||
type = "choice"
|
||||
model_class = ChoiceElement
|
||||
allowed_fields = [
|
||||
"label",
|
||||
"default_value",
|
||||
"required",
|
||||
"placeholder",
|
||||
"multiple",
|
||||
"show_as_dropdown",
|
||||
]
|
||||
serializer_field_names = [
|
||||
"label",
|
||||
"default_value",
|
||||
|
@ -1138,6 +1145,7 @@ class DropdownElementType(FormElementTypeMixin, ElementType):
|
|||
"placeholder",
|
||||
"options",
|
||||
"multiple",
|
||||
"show_as_dropdown",
|
||||
]
|
||||
request_serializer_field_names = [
|
||||
"label",
|
||||
|
@ -1146,6 +1154,7 @@ class DropdownElementType(FormElementTypeMixin, ElementType):
|
|||
"placeholder",
|
||||
"options",
|
||||
"multiple",
|
||||
"show_as_dropdown",
|
||||
]
|
||||
|
||||
class SerializedDict(ElementDict):
|
||||
|
@ -1155,6 +1164,7 @@ class DropdownElementType(FormElementTypeMixin, ElementType):
|
|||
default_value: BaserowFormula
|
||||
options: List
|
||||
multiple: bool
|
||||
show_as_dropdown: bool
|
||||
|
||||
@property
|
||||
def serializer_field_overrides(self):
|
||||
|
@ -1162,36 +1172,41 @@ class DropdownElementType(FormElementTypeMixin, ElementType):
|
|||
|
||||
overrides = {
|
||||
"label": FormulaSerializerField(
|
||||
help_text=DropdownElement._meta.get_field("label").help_text,
|
||||
help_text=ChoiceElement._meta.get_field("label").help_text,
|
||||
required=False,
|
||||
allow_blank=True,
|
||||
default="",
|
||||
),
|
||||
"default_value": FormulaSerializerField(
|
||||
help_text=DropdownElement._meta.get_field("default_value").help_text,
|
||||
help_text=ChoiceElement._meta.get_field("default_value").help_text,
|
||||
required=False,
|
||||
allow_blank=True,
|
||||
default="",
|
||||
),
|
||||
"required": serializers.BooleanField(
|
||||
help_text=DropdownElement._meta.get_field("required").help_text,
|
||||
help_text=ChoiceElement._meta.get_field("required").help_text,
|
||||
default=False,
|
||||
required=False,
|
||||
),
|
||||
"placeholder": serializers.CharField(
|
||||
help_text=DropdownElement._meta.get_field("placeholder").help_text,
|
||||
help_text=ChoiceElement._meta.get_field("placeholder").help_text,
|
||||
required=False,
|
||||
allow_blank=True,
|
||||
default="",
|
||||
),
|
||||
"options": DropdownOptionSerializer(
|
||||
source="dropdownelementoption_set", many=True, required=False
|
||||
"options": ChoiceOptionSerializer(
|
||||
source="choiceelementoption_set", many=True, required=False
|
||||
),
|
||||
"multiple": serializers.BooleanField(
|
||||
help_text=DropdownElement._meta.get_field("multiple").help_text,
|
||||
help_text=ChoiceElement._meta.get_field("multiple").help_text,
|
||||
default=False,
|
||||
required=False,
|
||||
),
|
||||
"show_as_dropdown": serializers.BooleanField(
|
||||
help_text=ChoiceElement._meta.get_field("show_as_dropdown").help_text,
|
||||
default=True,
|
||||
required=False,
|
||||
),
|
||||
}
|
||||
|
||||
return overrides
|
||||
|
@ -1200,12 +1215,12 @@ class DropdownElementType(FormElementTypeMixin, ElementType):
|
|||
def request_serializer_field_overrides(self):
|
||||
return {
|
||||
**self.serializer_field_overrides,
|
||||
"options": DropdownOptionSerializer(many=True, required=False),
|
||||
"options": ChoiceOptionSerializer(many=True, required=False),
|
||||
}
|
||||
|
||||
def serialize_property(
|
||||
self,
|
||||
element: DropdownElement,
|
||||
element: ChoiceElement,
|
||||
prop_name: str,
|
||||
files_zip=None,
|
||||
storage=None,
|
||||
|
@ -1214,7 +1229,7 @@ class DropdownElementType(FormElementTypeMixin, ElementType):
|
|||
if prop_name == "options":
|
||||
return [
|
||||
self.serialize_option(option)
|
||||
for option in element.dropdownelementoption_set.all()
|
||||
for option in element.choiceelementoption_set.all()
|
||||
]
|
||||
|
||||
return super().serialize_property(
|
||||
|
@ -1260,7 +1275,7 @@ class DropdownElementType(FormElementTypeMixin, ElementType):
|
|||
cache=None,
|
||||
**kwargs,
|
||||
) -> T:
|
||||
dropdown_element = super().import_serialized(
|
||||
choice_element = super().import_serialized(
|
||||
parent,
|
||||
serialized_values,
|
||||
id_mapping,
|
||||
|
@ -1272,13 +1287,13 @@ class DropdownElementType(FormElementTypeMixin, ElementType):
|
|||
|
||||
options = []
|
||||
for option in serialized_values.get("options", []):
|
||||
option["dropdown_id"] = dropdown_element.id
|
||||
option["choice_id"] = choice_element.id
|
||||
option_deserialized = self.deserialize_option(option)
|
||||
options.append(option_deserialized)
|
||||
|
||||
DropdownElementOption.objects.bulk_create(options)
|
||||
ChoiceElementOption.objects.bulk_create(options)
|
||||
|
||||
return dropdown_element
|
||||
return choice_element
|
||||
|
||||
def create_instance_from_serialized(
|
||||
self,
|
||||
|
@ -1299,15 +1314,15 @@ class DropdownElementType(FormElementTypeMixin, ElementType):
|
|||
**kwargs,
|
||||
)
|
||||
|
||||
def serialize_option(self, option: DropdownElementOption) -> Dict:
|
||||
def serialize_option(self, option: ChoiceElementOption) -> Dict:
|
||||
return {
|
||||
"value": option.value,
|
||||
"name": option.name,
|
||||
"dropdown_id": option.dropdown_id,
|
||||
"choice_id": option.choice_id,
|
||||
}
|
||||
|
||||
def deserialize_option(self, value: Dict):
|
||||
return DropdownElementOption(**value)
|
||||
return ChoiceElementOption(**value)
|
||||
|
||||
def get_pytest_params(self, pytest_data_fixture) -> Dict[str, Any]:
|
||||
return {
|
||||
|
@ -1316,39 +1331,37 @@ class DropdownElementType(FormElementTypeMixin, ElementType):
|
|||
"required": False,
|
||||
"placeholder": "'some placeholder'",
|
||||
"multiple": False,
|
||||
"show_as_dropdown": True,
|
||||
}
|
||||
|
||||
def after_create(self, instance: DropdownElement, values: Dict):
|
||||
def after_create(self, instance: ChoiceElement, values: Dict):
|
||||
options = values.get("options", [])
|
||||
|
||||
DropdownElementOption.objects.bulk_create(
|
||||
[DropdownElementOption(dropdown=instance, **option) for option in options]
|
||||
ChoiceElementOption.objects.bulk_create(
|
||||
[ChoiceElementOption(choice=instance, **option) for option in options]
|
||||
)
|
||||
|
||||
def after_update(self, instance: DropdownElement, values: Dict):
|
||||
def after_update(self, instance: ChoiceElement, values: Dict):
|
||||
options = values.get("options", None)
|
||||
|
||||
if options is not None:
|
||||
DropdownElementOption.objects.filter(dropdown=instance).delete()
|
||||
DropdownElementOption.objects.bulk_create(
|
||||
[
|
||||
DropdownElementOption(dropdown=instance, **option)
|
||||
for option in options
|
||||
]
|
||||
ChoiceElementOption.objects.filter(choice=instance).delete()
|
||||
ChoiceElementOption.objects.bulk_create(
|
||||
[ChoiceElementOption(choice=instance, **option) for option in options]
|
||||
)
|
||||
|
||||
def is_valid(self, element: DropdownElement, value: Union[List, str]) -> bool:
|
||||
def is_valid(self, element: ChoiceElement, value: Union[List, str]) -> bool:
|
||||
"""
|
||||
Responsible for validating `DropdownElement` form data. We handle
|
||||
Responsible for validating `ChoiceElement` form data. We handle
|
||||
this validation a little differently to ensure that if someone creates
|
||||
an option with a blank value, it's considered valid.
|
||||
|
||||
:param element: The dropdown element.
|
||||
:param value: The dropdown value we want to validate.
|
||||
:param element: The choice element.
|
||||
:param value: The choice value we want to validate.
|
||||
:return: Whether the value is valid or not for this element.
|
||||
"""
|
||||
|
||||
options = set(element.dropdownelementoption_set.values_list("value", flat=True))
|
||||
options = set(element.choiceelementoption_set.values_list("value", flat=True))
|
||||
|
||||
if element.multiple:
|
||||
try:
|
||||
|
|
|
@ -28,6 +28,8 @@ from baserow.core.db import specific_iterator
|
|||
from baserow.core.exceptions import IdDoesNotExist
|
||||
from baserow.core.utils import MirrorDict, extract_allowed
|
||||
|
||||
old_element_type_map = {"dropdown": "choice"}
|
||||
|
||||
|
||||
class ElementHandler:
|
||||
allowed_fields_create = [
|
||||
|
@ -634,6 +636,11 @@ class ElementHandler:
|
|||
id_mapping["builder_page_elements"] = {}
|
||||
|
||||
element_type = element_type_registry.get(serialized_element["type"])
|
||||
|
||||
if element_type in old_element_type_map:
|
||||
# We met an old element type name. Let's migrate it.
|
||||
element_type = old_element_type_map[element_type]
|
||||
|
||||
created_instance = element_type.import_serialized(
|
||||
page,
|
||||
serialized_element,
|
||||
|
|
|
@ -621,13 +621,14 @@ class InputTextElement(FormElement):
|
|||
)
|
||||
|
||||
|
||||
class DropdownElement(FormElement):
|
||||
class ChoiceElement(FormElement):
|
||||
label = FormulaField(
|
||||
default="",
|
||||
help_text="The text label for this dropdown",
|
||||
help_text="The text label for this choice",
|
||||
)
|
||||
default_value = FormulaField(
|
||||
default="", help_text="This dropdowns input's default value."
|
||||
default="",
|
||||
help_text="This choice's input default value.",
|
||||
)
|
||||
placeholder = FormulaField(
|
||||
default="",
|
||||
|
@ -635,19 +636,31 @@ class DropdownElement(FormElement):
|
|||
)
|
||||
multiple = models.BooleanField(
|
||||
default=False,
|
||||
help_text="Whether this dropdown allows users to choose multiple values.",
|
||||
help_text="Whether this choice allows users to choose multiple values.",
|
||||
null=True, # TODO zdm remove me in next release
|
||||
)
|
||||
show_as_dropdown = models.BooleanField(
|
||||
default=True,
|
||||
help_text="Whether to show the choices as a dropdown.",
|
||||
null=True, # TODO zdm remove me in next release
|
||||
)
|
||||
|
||||
|
||||
class DropdownElementOption(models.Model):
|
||||
class ChoiceElementOption(models.Model):
|
||||
value = models.TextField(
|
||||
blank=True, default="", help_text="The value of the option"
|
||||
blank=True,
|
||||
default="",
|
||||
help_text="The value of the option",
|
||||
)
|
||||
name = models.TextField(
|
||||
blank=True, default="", help_text="The display name of the option"
|
||||
blank=True,
|
||||
default="",
|
||||
help_text="The display name of the option",
|
||||
)
|
||||
choice = models.ForeignKey(
|
||||
ChoiceElement,
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
dropdown = models.ForeignKey(DropdownElement, on_delete=models.CASCADE)
|
||||
|
||||
|
||||
class CheckboxElement(FormElement):
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
# Generated by Django 4.1.13 on 2024-06-04 08:05
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
import baserow.core.formula.field
|
||||
|
||||
|
||||
def populate_show_as_dropdown_default_value(apps, schema_editor):
|
||||
"""
|
||||
Sets the default value for new `show_as_dropdown` property.
|
||||
"""
|
||||
|
||||
ChoiceElement = apps.get_model("builder", "choiceelement")
|
||||
ChoiceElement.objects.update(show_as_dropdown=True)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("builder", "0021_dropdownelement_multiple"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameModel(old_name="DropdownElementOption", new_name="ChoiceElementOption"),
|
||||
migrations.RenameModel(old_name="DropdownElement", new_name="ChoiceElement"),
|
||||
migrations.AlterField(
|
||||
model_name="choiceelement",
|
||||
name="default_value",
|
||||
field=baserow.core.formula.field.FormulaField(
|
||||
default="", help_text="This choice's input default value."
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="choiceelement",
|
||||
name="label",
|
||||
field=baserow.core.formula.field.FormulaField(
|
||||
default="", help_text="The text label for this choice"
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="choiceelement",
|
||||
name="multiple",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
help_text="Whether this choice allows users to choose multiple values.",
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="choiceelementoption",
|
||||
old_name="dropdown",
|
||||
new_name="choice",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="choiceelement",
|
||||
name="show_as_dropdown",
|
||||
field=models.BooleanField(
|
||||
default=True,
|
||||
help_text="Whether to show the choices as a dropdown.",
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
migrations.RunPython(populate_show_as_dropdown_default_value, reverse_code=migrations.RunPython.noop),
|
||||
]
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Any
|
||||
from typing import Any, List
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
|
@ -9,12 +9,12 @@ from baserow.contrib.database.fields.constants import (
|
|||
from baserow.core.utils import flatten
|
||||
|
||||
|
||||
def ensure_boolean(value):
|
||||
def ensure_boolean(value: Any) -> bool:
|
||||
"""
|
||||
Ensures that the value is a boolean or converts it.
|
||||
|
||||
@param value: The value to ensure as a boolean.
|
||||
@returns: The value as a boolean.
|
||||
:param value: The value to ensure as a boolean.
|
||||
:return: The value as a boolean.
|
||||
"""
|
||||
|
||||
if value in BASEROW_BOOLEAN_FIELD_TRUE_VALUES:
|
||||
|
@ -45,7 +45,7 @@ def ensure_integer(value: Any) -> int:
|
|||
) from exc
|
||||
|
||||
|
||||
def ensure_string(value, allow_empty=True):
|
||||
def ensure_string(value: Any, allow_empty: bool = True) -> str:
|
||||
"""
|
||||
Ensures that the value is a string or try to convert it.
|
||||
|
||||
|
@ -64,7 +64,7 @@ def ensure_string(value, allow_empty=True):
|
|||
return str(value)
|
||||
|
||||
|
||||
def ensure_array(value, allow_empty=True):
|
||||
def ensure_array(value: Any, allow_empty: bool = True) -> List[Any]:
|
||||
"""
|
||||
Ensure that the value is an array or try to convert it.
|
||||
Strings will be treated as comma separated values.
|
||||
|
@ -73,7 +73,6 @@ def ensure_array(value, allow_empty=True):
|
|||
:param value: The value to ensure as an array.
|
||||
:param allow_empty: Whether we should raise an error if `value` is empty.
|
||||
:return: The value as an array.
|
||||
:rtype: list
|
||||
:raises ValueError: if not allow_empty and `value` is empty.
|
||||
"""
|
||||
|
||||
|
|
|
@ -2,9 +2,9 @@ from copy import deepcopy
|
|||
|
||||
from baserow.contrib.builder.elements.models import (
|
||||
ButtonElement,
|
||||
ChoiceElement,
|
||||
CollectionField,
|
||||
ColumnElement,
|
||||
DropdownElement,
|
||||
FormContainerElement,
|
||||
HeadingElement,
|
||||
ImageElement,
|
||||
|
@ -93,8 +93,8 @@ class ElementFixtures:
|
|||
)
|
||||
return element
|
||||
|
||||
def create_builder_dropdown_element(self, user=None, page=None, **kwargs):
|
||||
element = self.create_builder_element(DropdownElement, user, page, **kwargs)
|
||||
def create_builder_choice_element(self, user=None, page=None, **kwargs):
|
||||
element = self.create_builder_element(ChoiceElement, user, page, **kwargs)
|
||||
return element
|
||||
|
||||
def create_builder_repeat_element(self, user=None, page=None, **kwargs):
|
||||
|
|
|
@ -10,7 +10,7 @@ from rest_framework.status import (
|
|||
)
|
||||
|
||||
from baserow.contrib.builder.elements.models import (
|
||||
DropdownElementOption,
|
||||
ChoiceElementOption,
|
||||
Element,
|
||||
LinkElement,
|
||||
)
|
||||
|
@ -466,7 +466,7 @@ def test_child_type_not_allowed_validation(api_client, data_fixture):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_dropdown_options_created(api_client, data_fixture):
|
||||
def test_choice_options_created(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
page = data_fixture.create_builder_page(user=user)
|
||||
|
||||
|
@ -475,7 +475,7 @@ def test_dropdown_options_created(api_client, data_fixture):
|
|||
url = reverse("api:builder:element:list", kwargs={"page_id": page.id})
|
||||
response = api_client.post(
|
||||
url,
|
||||
{"type": "dropdown", "options": options},
|
||||
{"type": "choice", "options": options},
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {token}",
|
||||
)
|
||||
|
@ -483,23 +483,21 @@ def test_dropdown_options_created(api_client, data_fixture):
|
|||
response_json = response.json()
|
||||
assert response.status_code == HTTP_200_OK
|
||||
assert response_json["options"][0]["value"] == "'hello'"
|
||||
assert DropdownElementOption.objects.count() == len(options)
|
||||
assert ChoiceElementOption.objects.count() == len(options)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_dropdown_options_updated(api_client, data_fixture):
|
||||
def test_choice_options_updated(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
page = data_fixture.create_builder_page(user=user)
|
||||
dropdown_element = data_fixture.create_builder_dropdown_element(page=page)
|
||||
choice_element = data_fixture.create_builder_choice_element(page=page)
|
||||
|
||||
# Add an existing option
|
||||
DropdownElementOption.objects.create(dropdown=dropdown_element)
|
||||
ChoiceElementOption.objects.create(choice=choice_element)
|
||||
|
||||
options = [{"value": "'hello'", "name": "'there'"}]
|
||||
|
||||
url = reverse(
|
||||
"api:builder:element:item", kwargs={"element_id": dropdown_element.id}
|
||||
)
|
||||
url = reverse("api:builder:element:item", kwargs={"element_id": choice_element.id})
|
||||
response = api_client.patch(
|
||||
url,
|
||||
{"options": options},
|
||||
|
@ -510,21 +508,19 @@ def test_dropdown_options_updated(api_client, data_fixture):
|
|||
response_json = response.json()
|
||||
assert response.status_code == HTTP_200_OK
|
||||
assert response_json["options"][0]["value"] == "'hello'"
|
||||
assert DropdownElementOption.objects.count() == len(options)
|
||||
assert ChoiceElementOption.objects.count() == len(options)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_dropdown_options_deleted(api_client, data_fixture):
|
||||
def test_choice_options_deleted(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
page = data_fixture.create_builder_page(user=user)
|
||||
dropdown_element = data_fixture.create_builder_dropdown_element(page=page)
|
||||
choice_element = data_fixture.create_builder_choice_element(page=page)
|
||||
|
||||
# Add an existing option
|
||||
DropdownElementOption.objects.create(dropdown=dropdown_element)
|
||||
ChoiceElementOption.objects.create(choice=choice_element)
|
||||
|
||||
url = reverse(
|
||||
"api:builder:element:item", kwargs={"element_id": dropdown_element.id}
|
||||
)
|
||||
url = reverse("api:builder:element:item", kwargs={"element_id": choice_element.id})
|
||||
response = api_client.delete(
|
||||
url,
|
||||
format="json",
|
||||
|
@ -532,7 +528,7 @@ def test_dropdown_options_deleted(api_client, data_fixture):
|
|||
)
|
||||
|
||||
assert response.status_code == HTTP_204_NO_CONTENT
|
||||
assert DropdownElementOption.objects.count() == 0
|
||||
assert ChoiceElementOption.objects.count() == 0
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
|
|
@ -4,8 +4,8 @@ from rest_framework.exceptions import ValidationError
|
|||
from baserow.contrib.builder.elements.element_types import (
|
||||
ButtonElementType,
|
||||
CheckboxElementType,
|
||||
ChoiceElementType,
|
||||
ColumnElementType,
|
||||
DropdownElementType,
|
||||
HeadingElementType,
|
||||
IFrameElementType,
|
||||
ImageElementType,
|
||||
|
@ -144,7 +144,7 @@ def test_column_element_type_can_have_children(data_fixture):
|
|||
place_in_container="1",
|
||||
)
|
||||
element_inside_container_nine = ElementHandler().create_element(
|
||||
DropdownElementType(),
|
||||
ChoiceElementType(),
|
||||
page=page,
|
||||
parent_element_id=container.id,
|
||||
place_in_container="1",
|
||||
|
|
|
@ -563,9 +563,7 @@ def test_get_first_ancestor_of_type(data_fixture, django_assert_num_queries):
|
|||
parent = data_fixture.create_builder_form_container_element(
|
||||
parent_element=grandparent, page=page
|
||||
)
|
||||
child = data_fixture.create_builder_dropdown_element(
|
||||
page=page, parent_element=parent
|
||||
)
|
||||
child = data_fixture.create_builder_choice_element(page=page, parent_element=parent)
|
||||
|
||||
with django_assert_num_queries(7):
|
||||
nearest_column_ancestor = ElementHandler().get_first_ancestor_of_type(
|
||||
|
|
|
@ -14,7 +14,7 @@ from baserow.contrib.builder.data_providers.exceptions import (
|
|||
)
|
||||
from baserow.contrib.builder.elements.element_types import (
|
||||
CheckboxElementType,
|
||||
DropdownElementType,
|
||||
ChoiceElementType,
|
||||
IFrameElementType,
|
||||
ImageElementType,
|
||||
InputTextElementType,
|
||||
|
@ -26,8 +26,8 @@ from baserow.contrib.builder.elements.mixins import (
|
|||
)
|
||||
from baserow.contrib.builder.elements.models import (
|
||||
CheckboxElement,
|
||||
DropdownElement,
|
||||
DropdownElementOption,
|
||||
ChoiceElement,
|
||||
ChoiceElementOption,
|
||||
HeadingElement,
|
||||
IFrameElement,
|
||||
ImageElement,
|
||||
|
@ -209,137 +209,137 @@ def test_input_text_element_is_valid(data_fixture):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_dropdown_element_import_serialized(data_fixture):
|
||||
def test_choice_element_import_serialized(data_fixture):
|
||||
parent = data_fixture.create_builder_page()
|
||||
dropdown_element = data_fixture.create_builder_dropdown_element(
|
||||
choice_element = data_fixture.create_builder_choice_element(
|
||||
page=parent, label="'test'"
|
||||
)
|
||||
DropdownElementOption.objects.create(
|
||||
dropdown=dropdown_element, value="hello", name="there"
|
||||
ChoiceElementOption.objects.create(
|
||||
choice=choice_element, value="hello", name="there"
|
||||
)
|
||||
serialized_values = DropdownElementType().export_serialized(dropdown_element)
|
||||
serialized_values = ChoiceElementType().export_serialized(choice_element)
|
||||
id_mapping = {}
|
||||
|
||||
dropdown_element_imported = DropdownElementType().import_serialized(
|
||||
choice_element_imported = ChoiceElementType().import_serialized(
|
||||
parent, serialized_values, id_mapping
|
||||
)
|
||||
|
||||
assert dropdown_element.id != dropdown_element_imported.id
|
||||
assert dropdown_element.label == dropdown_element_imported.label
|
||||
assert choice_element.id != choice_element_imported.id
|
||||
assert choice_element.label == choice_element_imported.label
|
||||
|
||||
options = dropdown_element_imported.dropdownelementoption_set.all()
|
||||
options = choice_element_imported.choiceelementoption_set.all()
|
||||
|
||||
assert DropdownElementOption.objects.count() == 2
|
||||
assert ChoiceElementOption.objects.count() == 2
|
||||
assert len(options) == 1
|
||||
assert options[0].value == "hello"
|
||||
assert options[0].name == "there"
|
||||
assert options[0].dropdown_id == dropdown_element_imported.id
|
||||
assert options[0].choice_id == choice_element_imported.id
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_dropdown_element_is_valid(data_fixture):
|
||||
def test_choice_element_is_valid(data_fixture):
|
||||
user = data_fixture.create_user()
|
||||
page = data_fixture.create_builder_page(user=user)
|
||||
dropdown = ElementService().create_element(
|
||||
choice = ElementService().create_element(
|
||||
user=user,
|
||||
element_type=element_type_registry.get("dropdown"),
|
||||
element_type=element_type_registry.get("choice"),
|
||||
page=page,
|
||||
)
|
||||
dropdown.dropdownelementoption_set.create(value="uk", name="United Kingdom")
|
||||
dropdown.dropdownelementoption_set.create(value="it", name="Italy")
|
||||
choice.choiceelementoption_set.create(value="uk", name="United Kingdom")
|
||||
choice.choiceelementoption_set.create(value="it", name="Italy")
|
||||
|
||||
dropdown.required = True
|
||||
choice.required = True
|
||||
|
||||
with pytest.raises(FormDataProviderChunkInvalidException):
|
||||
DropdownElementType().is_valid(dropdown, "")
|
||||
ChoiceElementType().is_valid(choice, "")
|
||||
|
||||
assert DropdownElementType().is_valid(dropdown, "uk") == "uk"
|
||||
assert ChoiceElementType().is_valid(choice, "uk") == "uk"
|
||||
|
||||
dropdown.multiple = True
|
||||
choice.multiple = True
|
||||
|
||||
with pytest.raises(FormDataProviderChunkInvalidException):
|
||||
DropdownElementType().is_valid(dropdown, [])
|
||||
ChoiceElementType().is_valid(choice, [])
|
||||
|
||||
with pytest.raises(FormDataProviderChunkInvalidException):
|
||||
DropdownElementType().is_valid(dropdown, [""])
|
||||
ChoiceElementType().is_valid(choice, [""])
|
||||
|
||||
assert DropdownElementType().is_valid(dropdown, ["uk"]) == ["uk"]
|
||||
assert DropdownElementType().is_valid(dropdown, "uk") == ["uk"]
|
||||
assert DropdownElementType().is_valid(dropdown, ["uk", "it"]) == ["uk", "it"]
|
||||
assert DropdownElementType().is_valid(dropdown, "uk,it") == ["uk", "it"]
|
||||
assert ChoiceElementType().is_valid(choice, ["uk"]) == ["uk"]
|
||||
assert ChoiceElementType().is_valid(choice, "uk") == ["uk"]
|
||||
assert ChoiceElementType().is_valid(choice, ["uk", "it"]) == ["uk", "it"]
|
||||
assert ChoiceElementType().is_valid(choice, "uk,it") == ["uk", "it"]
|
||||
|
||||
with pytest.raises(FormDataProviderChunkInvalidException):
|
||||
DropdownElementType().is_valid(dropdown, ["uk", "it", "pt"])
|
||||
ChoiceElementType().is_valid(choice, ["uk", "it", "pt"])
|
||||
|
||||
with pytest.raises(FormDataProviderChunkInvalidException):
|
||||
DropdownElementType().is_valid(dropdown, "uk,it,pt")
|
||||
ChoiceElementType().is_valid(choice, "uk,it,pt")
|
||||
|
||||
dropdown.multiple = False
|
||||
dropdown.required = False
|
||||
choice.multiple = False
|
||||
choice.required = False
|
||||
|
||||
assert DropdownElementType().is_valid(dropdown, "") == ""
|
||||
assert DropdownElementType().is_valid(dropdown, "uk") == "uk"
|
||||
assert ChoiceElementType().is_valid(choice, "") == ""
|
||||
assert ChoiceElementType().is_valid(choice, "uk") == "uk"
|
||||
|
||||
dropdown.multiple = True
|
||||
choice.multiple = True
|
||||
|
||||
assert DropdownElementType().is_valid(dropdown, []) == []
|
||||
assert DropdownElementType().is_valid(dropdown, "") == []
|
||||
assert ChoiceElementType().is_valid(choice, []) == []
|
||||
assert ChoiceElementType().is_valid(choice, "") == []
|
||||
|
||||
with pytest.raises(FormDataProviderChunkInvalidException):
|
||||
DropdownElementType().is_valid(dropdown, [""])
|
||||
ChoiceElementType().is_valid(choice, [""])
|
||||
|
||||
assert DropdownElementType().is_valid(dropdown, ["uk"]) == ["uk"]
|
||||
assert DropdownElementType().is_valid(dropdown, ["uk", "it"]) == ["uk", "it"]
|
||||
assert DropdownElementType().is_valid(dropdown, "uk,it") == ["uk", "it"]
|
||||
assert ChoiceElementType().is_valid(choice, ["uk"]) == ["uk"]
|
||||
assert ChoiceElementType().is_valid(choice, ["uk", "it"]) == ["uk", "it"]
|
||||
assert ChoiceElementType().is_valid(choice, "uk,it") == ["uk", "it"]
|
||||
|
||||
with pytest.raises(FormDataProviderChunkInvalidException):
|
||||
DropdownElementType().is_valid(dropdown, ["uk", "it", "pt"])
|
||||
ChoiceElementType().is_valid(choice, ["uk", "it", "pt"])
|
||||
|
||||
dropdown.dropdownelementoption_set.create(value="", name="Blank")
|
||||
dropdown.multiple = False
|
||||
dropdown.required = True
|
||||
choice.choiceelementoption_set.create(value="", name="Blank")
|
||||
choice.multiple = False
|
||||
choice.required = True
|
||||
|
||||
assert DropdownElementType().is_valid(dropdown, "") == ""
|
||||
assert ChoiceElementType().is_valid(choice, "") == ""
|
||||
|
||||
dropdown.multiple = True
|
||||
choice.multiple = True
|
||||
with pytest.raises(FormDataProviderChunkInvalidException):
|
||||
DropdownElementType().is_valid(dropdown, [])
|
||||
ChoiceElementType().is_valid(choice, [])
|
||||
with pytest.raises(FormDataProviderChunkInvalidException):
|
||||
DropdownElementType().is_valid(dropdown, "")
|
||||
ChoiceElementType().is_valid(choice, "")
|
||||
|
||||
assert DropdownElementType().is_valid(dropdown, [""]) == [""]
|
||||
assert DropdownElementType().is_valid(dropdown, ["uk"]) == ["uk"]
|
||||
assert DropdownElementType().is_valid(dropdown, "uk") == ["uk"]
|
||||
assert DropdownElementType().is_valid(dropdown, ["uk", "it"]) == ["uk", "it"]
|
||||
assert DropdownElementType().is_valid(dropdown, "uk,it") == ["uk", "it"]
|
||||
assert DropdownElementType().is_valid(dropdown, ["uk", "it", ""]) == [
|
||||
assert ChoiceElementType().is_valid(choice, [""]) == [""]
|
||||
assert ChoiceElementType().is_valid(choice, ["uk"]) == ["uk"]
|
||||
assert ChoiceElementType().is_valid(choice, "uk") == ["uk"]
|
||||
assert ChoiceElementType().is_valid(choice, ["uk", "it"]) == ["uk", "it"]
|
||||
assert ChoiceElementType().is_valid(choice, "uk,it") == ["uk", "it"]
|
||||
assert ChoiceElementType().is_valid(choice, ["uk", "it", ""]) == [
|
||||
"uk",
|
||||
"it",
|
||||
"",
|
||||
]
|
||||
|
||||
with pytest.raises(FormDataProviderChunkInvalidException):
|
||||
DropdownElementType().is_valid(dropdown, ["uk", "it", "", "pt"])
|
||||
ChoiceElementType().is_valid(choice, ["uk", "it", "", "pt"])
|
||||
|
||||
dropdown.multiple = False
|
||||
dropdown.required = False
|
||||
choice.multiple = False
|
||||
choice.required = False
|
||||
|
||||
assert DropdownElementType().is_valid(dropdown, "uk") == "uk"
|
||||
assert ChoiceElementType().is_valid(choice, "uk") == "uk"
|
||||
|
||||
dropdown.multiple = True
|
||||
choice.multiple = True
|
||||
|
||||
assert DropdownElementType().is_valid(dropdown, []) == []
|
||||
assert DropdownElementType().is_valid(dropdown, [""]) == [""]
|
||||
assert DropdownElementType().is_valid(dropdown, ["uk"]) == ["uk"]
|
||||
assert DropdownElementType().is_valid(dropdown, ["uk", "it"]) == ["uk", "it"]
|
||||
assert DropdownElementType().is_valid(dropdown, ["uk", "it", ""]) == [
|
||||
assert ChoiceElementType().is_valid(choice, []) == []
|
||||
assert ChoiceElementType().is_valid(choice, [""]) == [""]
|
||||
assert ChoiceElementType().is_valid(choice, ["uk"]) == ["uk"]
|
||||
assert ChoiceElementType().is_valid(choice, ["uk", "it"]) == ["uk", "it"]
|
||||
assert ChoiceElementType().is_valid(choice, ["uk", "it", ""]) == [
|
||||
"uk",
|
||||
"it",
|
||||
"",
|
||||
]
|
||||
|
||||
with pytest.raises(FormDataProviderChunkInvalidException):
|
||||
DropdownElementType().is_valid(dropdown, ["uk", "it", "", "pt"])
|
||||
ChoiceElementType().is_valid(choice, ["uk", "it", "", "pt"])
|
||||
|
||||
|
||||
def test_element_type_import_element_priority():
|
||||
|
@ -514,13 +514,13 @@ def test_image_element_import_export(data_fixture, fake, storage):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_dropdown_element_import_export(data_fixture):
|
||||
def test_choice_element_import_export(data_fixture):
|
||||
page = data_fixture.create_builder_page()
|
||||
data_source_2 = data_fixture.create_builder_local_baserow_get_row_data_source()
|
||||
element_type = DropdownElementType()
|
||||
element_type = ChoiceElementType()
|
||||
|
||||
exported_element = data_fixture.create_builder_element(
|
||||
DropdownElement,
|
||||
ChoiceElement,
|
||||
label=f"get('data_source.42.field_1')",
|
||||
default_value=f"get('data_source.42.field_1')",
|
||||
placeholder=f"get('data_source.42.field_1')",
|
||||
|
@ -546,14 +546,14 @@ def test_dropdown_element_import_export(data_fixture):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_dropdown_element_import_old_format(data_fixture):
|
||||
def test_choice_element_import_old_format(data_fixture):
|
||||
page = data_fixture.create_builder_page()
|
||||
element_type = DropdownElementType()
|
||||
element_type = ChoiceElementType()
|
||||
|
||||
serialized = {
|
||||
"id": 1,
|
||||
"order": "1.00000000000000000000",
|
||||
"type": "dropdown",
|
||||
"type": "dropdown", # Element type is the old one
|
||||
"parent_element_id": None,
|
||||
"place_in_container": None,
|
||||
"visibility": "all",
|
||||
|
@ -576,10 +576,17 @@ def test_dropdown_element_import_old_format(data_fixture):
|
|||
"required": False,
|
||||
"placeholder": "'test'",
|
||||
"default_value": "'default'",
|
||||
"options": [],
|
||||
"options": [
|
||||
{"value": "Option 1", "name": "option1"},
|
||||
{"value": "Option 2", "name": "option2"},
|
||||
],
|
||||
# multiple property is missing
|
||||
# show_as_dropdown property is missing
|
||||
}
|
||||
|
||||
imported_element = element_type.import_serialized(page, serialized, {})
|
||||
|
||||
assert isinstance(imported_element.specific, ChoiceElement)
|
||||
assert imported_element.multiple is False
|
||||
assert imported_element.show_as_dropdown is True
|
||||
assert len(imported_element.choiceelementoption_set.all()) == 2
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"type": "feature",
|
||||
"message": "[Builder] Rename dropdown element to choice element and allow to display element as dropdown/checkbox/radio",
|
||||
"issue_number": 2517,
|
||||
"bullet_points": [],
|
||||
"created_at": "2024-04-17"
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div @click="toggle">
|
||||
<div class="ab-checkbox" @click="toggle">
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="value"
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
@mousemove="hover(value, disabled)"
|
||||
>
|
||||
<div class="ab-dropdownitem__item-name">
|
||||
<div v-if="multiple">
|
||||
<div v-if="multiple.value">
|
||||
<Checkbox :disabled="disabled" :checked="isActive(value)"></Checkbox>
|
||||
</div>
|
||||
<slot>
|
||||
|
@ -39,7 +39,7 @@
|
|||
</div>
|
||||
</a>
|
||||
<i
|
||||
v-if="!multiple"
|
||||
v-if="!multiple.value"
|
||||
class="ab-dropdownitem__item-active-icon iconoir-check"
|
||||
></i>
|
||||
</li>
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
:class="{
|
||||
'ab-form-group--horizontal': horizontal,
|
||||
'ab-form-group--horizontal-variable': horizontalVariable,
|
||||
'ab-form-group--with-label': label,
|
||||
}"
|
||||
>
|
||||
<label
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
<template>
|
||||
<div class="ab-radio" @click="toggle">
|
||||
<input
|
||||
type="radio"
|
||||
:checked="value"
|
||||
:required="required"
|
||||
class="ab-radio__input"
|
||||
:disabled="disabled"
|
||||
:class="{
|
||||
'ab-radio--error': error,
|
||||
'ab-radio--readonly': readOnly,
|
||||
}"
|
||||
:aria-disabled="disabled"
|
||||
/>
|
||||
<label v-if="hasSlot" class="ab-radio__label">
|
||||
<slot></slot>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ABRadio',
|
||||
props: {
|
||||
/**
|
||||
* The state of the radio.
|
||||
*/
|
||||
value: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
* Whether the radio is disabled.
|
||||
*/
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
* Whether the radio is required.
|
||||
*/
|
||||
required: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
* Whether the radio is in error state.
|
||||
*/
|
||||
error: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
* Whether the radio is readonly.
|
||||
*/
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
hasSlot() {
|
||||
return !!this.$slots.default
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggle() {
|
||||
if (this.disabled || this.readOnly) return
|
||||
this.$emit('input', !this.value)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -1,11 +1,10 @@
|
|||
<template>
|
||||
<div class="checkbox-element-wrapper">
|
||||
<div>
|
||||
<ABCheckbox
|
||||
v-model="inputValue"
|
||||
:required="element.required"
|
||||
:read-only="isEditMode"
|
||||
:error="displayFormDataError"
|
||||
class="checkbox-element"
|
||||
>
|
||||
{{ resolvedLabel }}
|
||||
<span
|
||||
|
@ -27,11 +26,9 @@ import {
|
|||
ensureBoolean,
|
||||
ensureString,
|
||||
} from '@baserow/modules/core/utils/validator'
|
||||
import ABCheckbox from '@baserow/modules/builder/components/elements/baseComponents/ABCheckbox'
|
||||
|
||||
export default {
|
||||
name: 'CheckboxElement',
|
||||
components: { ABCheckbox },
|
||||
mixins: [formElement],
|
||||
computed: {
|
||||
defaultValueResolved() {
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
<template>
|
||||
<ABFormGroup
|
||||
:label="labelResolved"
|
||||
:required="element.required"
|
||||
:error-message="displayFormDataError ? $t('error.requiredField') : ''"
|
||||
>
|
||||
<ABDropdown
|
||||
v-if="element.show_as_dropdown"
|
||||
v-model="inputValue"
|
||||
class="choice-element"
|
||||
:class="{
|
||||
'choice-element--error': displayFormDataError,
|
||||
}"
|
||||
:placeholder="
|
||||
element.options.length
|
||||
? placeholderResolved
|
||||
: $t('choiceElement.addOptions')
|
||||
"
|
||||
:show-search="false"
|
||||
:multiple="element.multiple"
|
||||
@hide="onFormElementTouch"
|
||||
>
|
||||
<ABDropdownItem
|
||||
v-for="option in element.options"
|
||||
:key="option.id"
|
||||
:name="option.name || option.value"
|
||||
:value="option.value"
|
||||
/>
|
||||
</ABDropdown>
|
||||
<template v-else>
|
||||
<template v-if="element.options.length">
|
||||
<template v-if="element.multiple">
|
||||
<ABCheckbox
|
||||
v-for="option in optionsBooleanResolved"
|
||||
:key="option.id"
|
||||
:read-only="isEditMode"
|
||||
:error="displayFormDataError"
|
||||
:value="option.booleanValue"
|
||||
@input="onOptionChange(option, $event)"
|
||||
>
|
||||
{{ option.name || option.value }}
|
||||
</ABCheckbox>
|
||||
</template>
|
||||
<template v-else>
|
||||
<ABRadio
|
||||
v-for="option in element.options"
|
||||
:key="option.id"
|
||||
:read-only="isEditMode"
|
||||
:error="displayFormDataError"
|
||||
:value="option.value === inputValue"
|
||||
@input="onOptionChange(option, $event)"
|
||||
>
|
||||
{{ option.name || option.value }}
|
||||
</ABRadio>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>{{ $t('choiceElement.addOptions') }}</template>
|
||||
</template>
|
||||
</ABFormGroup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import formElement from '@baserow/modules/builder/mixins/formElement'
|
||||
import {
|
||||
ensureString,
|
||||
ensureArray,
|
||||
} from '@baserow/modules/core/utils/validator'
|
||||
|
||||
export default {
|
||||
name: 'ChoiceElement',
|
||||
mixins: [formElement],
|
||||
props: {
|
||||
/**
|
||||
* @type {Object}
|
||||
* @property {string} label - The label displayed above the choice element
|
||||
* @property {string} default_value - The default value selected
|
||||
* @property {string} placeholder - The placeholder value of the choice element
|
||||
* @property {boolean} required - If the element is required for form submission
|
||||
* @property {boolean} multiple - If the choice element allows multiple selections
|
||||
* @property {boolean} show_as_dropdown - If the choice element should be displayed as a dropdown
|
||||
* @property {Array} options - The options of the choice element
|
||||
*/
|
||||
element: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
labelResolved() {
|
||||
return ensureString(this.resolveFormula(this.element.label))
|
||||
},
|
||||
placeholderResolved() {
|
||||
return ensureString(this.resolveFormula(this.element.placeholder))
|
||||
},
|
||||
defaultValueResolved() {
|
||||
if (this.element.multiple) {
|
||||
return ensureArray(
|
||||
this.resolveFormula(this.element.default_value)
|
||||
).reduce((acc, value) => {
|
||||
if (
|
||||
!acc.includes(value) &&
|
||||
this.element.options.some((o) => o.value === value)
|
||||
) {
|
||||
acc.push(value)
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
} else {
|
||||
// Always return a string if we have a default value, otherwise
|
||||
// set the value to null as single select fields will only skip
|
||||
// field preparation if the value is null.
|
||||
const resolvedSingleValue = ensureString(
|
||||
this.resolveFormula(this.element.default_value)
|
||||
)
|
||||
return resolvedSingleValue.length ? resolvedSingleValue : null
|
||||
}
|
||||
},
|
||||
optionsBooleanResolved() {
|
||||
return this.element.options.map((option) => ({
|
||||
...option,
|
||||
booleanValue: this.inputValue.includes(option.value),
|
||||
}))
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
defaultValueResolved: {
|
||||
handler(newValue) {
|
||||
this.inputValue = newValue
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
'element.multiple'() {
|
||||
this.setFormData(this.defaultValueResolved)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onOptionChange(option, value) {
|
||||
if (value) {
|
||||
if (this.element.multiple) {
|
||||
this.inputValue = [...this.inputValue, option.value]
|
||||
} else {
|
||||
this.inputValue = option.value
|
||||
}
|
||||
} else if (this.element.multiple) {
|
||||
this.inputValue = this.inputValue.filter((v) => v !== option.value)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -1,104 +0,0 @@
|
|||
<template>
|
||||
<div class="control">
|
||||
<label v-if="element.label" class="control__label">
|
||||
{{ labelResolved }}
|
||||
<span
|
||||
v-if="element.label && element.required"
|
||||
:title="$t('error.requiredField')"
|
||||
>*</span
|
||||
>
|
||||
</label>
|
||||
<ABDropdown
|
||||
v-model="inputValue"
|
||||
class="dropdown-element"
|
||||
:class="{
|
||||
'dropdown-element--error': displayFormDataError,
|
||||
}"
|
||||
:placeholder="placeholderResolved"
|
||||
:show-search="false"
|
||||
:multiple="element.multiple"
|
||||
@hide="onFormElementTouch"
|
||||
>
|
||||
<DropdownItem
|
||||
v-for="option in element.options"
|
||||
:key="option.id"
|
||||
:name="option.name || option.value"
|
||||
:value="option.value"
|
||||
></DropdownItem>
|
||||
</ABDropdown>
|
||||
<div v-if="displayFormDataError" class="error">
|
||||
<i class="iconoir-warning-triangle"></i>
|
||||
{{ $t('error.requiredField') }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import formElement from '@baserow/modules/builder/mixins/formElement'
|
||||
import {
|
||||
ensureString,
|
||||
ensureArray,
|
||||
} from '@baserow/modules/core/utils/validator'
|
||||
|
||||
export default {
|
||||
name: 'DropdownElement',
|
||||
mixins: [formElement],
|
||||
props: {
|
||||
/**
|
||||
* @type {Object}
|
||||
* @property {string} label - The label displayed above the dropdown
|
||||
* @property {string} default_value - The default value selected
|
||||
* @property {string} placeholder - The placeholder value of the dropdown
|
||||
* @property {boolean} required - If the element is required for form submission
|
||||
* @property {boolean} multiple - If the dropdown allows multiple selections
|
||||
* @property {Array} options - The options of the dropdown
|
||||
*/
|
||||
element: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
labelResolved() {
|
||||
return ensureString(this.resolveFormula(this.element.label))
|
||||
},
|
||||
placeholderResolved() {
|
||||
return ensureString(this.resolveFormula(this.element.placeholder))
|
||||
},
|
||||
defaultValueResolved() {
|
||||
if (this.element.multiple) {
|
||||
return ensureArray(
|
||||
this.resolveFormula(this.element.default_value)
|
||||
).reduce((acc, value) => {
|
||||
if (
|
||||
!acc.includes(value) &&
|
||||
this.element.options.some((o) => o.value === value)
|
||||
) {
|
||||
acc.push(value)
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
} else {
|
||||
// Always return a string if we have a default value, otherwise
|
||||
// set the value to null as single select fields will only skip
|
||||
// field preparation if the value is null.
|
||||
const resolvedSingleValue = ensureString(
|
||||
this.resolveFormula(this.element.default_value)
|
||||
)
|
||||
return resolvedSingleValue.length ? resolvedSingleValue : null
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
defaultValueResolved: {
|
||||
handler(newValue) {
|
||||
this.inputValue = newValue
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
'element.multiple'() {
|
||||
this.setFormData(this.defaultValueResolved)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -18,13 +18,39 @@
|
|||
:placeholder="$t('generalForm.placeholderPlaceholder')"
|
||||
:data-providers-allowed="DATA_PROVIDERS_ALLOWED_FORM_ELEMENTS"
|
||||
></ApplicationBuilderFormulaInputGroup>
|
||||
|
||||
<FormGroup :label="$t('generalForm.requiredTitle')">
|
||||
<Checkbox v-model="values.required"></Checkbox>
|
||||
</FormGroup>
|
||||
<FormGroup :label="$t('dropdownElementForm.multiple')">
|
||||
|
||||
<FormGroup :label="$t('choiceElementForm.multiple')">
|
||||
<Checkbox v-model="values.multiple"></Checkbox>
|
||||
</FormGroup>
|
||||
<DropdownOptionsSelector
|
||||
|
||||
<FormGroup :label="$t('choiceElementForm.display')">
|
||||
<RadioButton
|
||||
v-model="values.show_as_dropdown"
|
||||
icon="iconoir-list"
|
||||
:value="true"
|
||||
>
|
||||
{{ $t('choiceElementForm.dropdown') }}
|
||||
</RadioButton>
|
||||
<RadioButton
|
||||
v-model="values.show_as_dropdown"
|
||||
:icon="
|
||||
values.multiple ? 'baserow-icon-check-square' : 'iconoir-check-circle'
|
||||
"
|
||||
:value="false"
|
||||
>
|
||||
{{
|
||||
values.multiple
|
||||
? $t('choiceElementForm.checkbox')
|
||||
: $t('choiceElementForm.radio')
|
||||
}}
|
||||
</RadioButton>
|
||||
</FormGroup>
|
||||
|
||||
<ChoiceOptionsSelector
|
||||
:options="values.options"
|
||||
@update="optionUpdated"
|
||||
@create="createOption"
|
||||
|
@ -37,12 +63,12 @@
|
|||
import ApplicationBuilderFormulaInputGroup from '@baserow/modules/builder/components/ApplicationBuilderFormulaInputGroup.vue'
|
||||
import { DATA_PROVIDERS_ALLOWED_FORM_ELEMENTS } from '@baserow/modules/builder/enums'
|
||||
import form from '@baserow/modules/core/mixins/form'
|
||||
import DropdownOptionsSelector from '@baserow/modules/builder/components/elements/components/forms/general/dropdown/DropdownOptionsSelector.vue'
|
||||
import ChoiceOptionsSelector from '@baserow/modules/builder/components/elements/components/forms/general/choice/ChoiceOptionsSelector.vue'
|
||||
import { uuid } from '@baserow/modules/core/utils/string'
|
||||
|
||||
export default {
|
||||
name: 'DropdownElementForm',
|
||||
components: { DropdownOptionsSelector, ApplicationBuilderFormulaInputGroup },
|
||||
name: 'ChoiceElementForm',
|
||||
components: { ChoiceOptionsSelector, ApplicationBuilderFormulaInputGroup },
|
||||
mixins: [form],
|
||||
inject: ['page'],
|
||||
data() {
|
||||
|
@ -54,6 +80,7 @@ export default {
|
|||
'placeholder',
|
||||
'options',
|
||||
'multiple',
|
||||
'show_as_dropdown',
|
||||
],
|
||||
values: {
|
||||
label: '',
|
||||
|
@ -62,6 +89,7 @@ export default {
|
|||
placeholder: '',
|
||||
options: [],
|
||||
multiple: false,
|
||||
show_as_dropdown: true,
|
||||
},
|
||||
}
|
||||
},
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
<script>
|
||||
export default {
|
||||
name: 'DropdownOption',
|
||||
name: 'ChoiceOption',
|
||||
props: {
|
||||
option: {
|
||||
type: Object,
|
|
@ -1,14 +1,14 @@
|
|||
<template>
|
||||
<FormGroup :label="$t('dropdownOptionSelector.label')" small-label>
|
||||
<FormGroup :label="$t('choiceOptionSelector.label')" small-label>
|
||||
<div class="dropdown-option-selector__heading margin-bottom-1">
|
||||
<div>
|
||||
{{ $t('dropdownOptionSelector.value') }}
|
||||
{{ $t('choiceOptionSelector.value') }}
|
||||
</div>
|
||||
<div class="dropdown-option-selector__name-heading">
|
||||
{{ $t('dropdownOptionSelector.name') }}
|
||||
{{ $t('choiceOptionSelector.name') }}
|
||||
</div>
|
||||
</div>
|
||||
<DropdownOption
|
||||
<ChoiceOption
|
||||
v-for="option in options"
|
||||
:key="option.id"
|
||||
:option="option"
|
||||
|
@ -22,16 +22,16 @@
|
|||
:loading="loading"
|
||||
@click="$emit('create')"
|
||||
>
|
||||
{{ $t('dropdownOptionSelector.addOption') }}
|
||||
{{ $t('choiceOptionSelector.addOption') }}
|
||||
</ButtonText>
|
||||
</FormGroup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DropdownOption from '@baserow/modules/builder/components/elements/components/forms/general/dropdown/DropdownOption.vue'
|
||||
import ChoiceOption from '@baserow/modules/builder/components/elements/components/forms/general/choice/ChoiceOption.vue'
|
||||
export default {
|
||||
name: 'DropdownOptionsSelector',
|
||||
components: { DropdownOption },
|
||||
name: 'ChoiceOptionsSelector',
|
||||
components: { ChoiceOption },
|
||||
props: {
|
||||
options: {
|
||||
type: Array,
|
|
@ -27,8 +27,8 @@ import RuntimeFormulaContext from '@baserow/modules/core/runtimeFormulaContext'
|
|||
import { resolveFormula } from '@baserow/modules/core/formula'
|
||||
import FormContainerElement from '@baserow/modules/builder/components/elements/components/FormContainerElement.vue'
|
||||
import FormContainerElementForm from '@baserow/modules/builder/components/elements/components/forms/general/FormContainerElementForm.vue'
|
||||
import DropdownElement from '@baserow/modules/builder/components/elements/components/DropdownElement.vue'
|
||||
import DropdownElementForm from '@baserow/modules/builder/components/elements/components/forms/general/DropdownElementForm.vue'
|
||||
import ChoiceElement from '@baserow/modules/builder/components/elements/components/ChoiceElement.vue'
|
||||
import ChoiceElementForm from '@baserow/modules/builder/components/elements/components/forms/general/ChoiceElementForm.vue'
|
||||
import CheckboxElement from '@baserow/modules/builder/components/elements/components/CheckboxElement.vue'
|
||||
import CheckboxElementForm from '@baserow/modules/builder/components/elements/components/forms/general/CheckboxElementForm.vue'
|
||||
import IFrameElement from '@baserow/modules/builder/components/elements/components/IFrameElement.vue'
|
||||
|
@ -1210,17 +1210,17 @@ export class ButtonElementType extends ElementType {
|
|||
}
|
||||
}
|
||||
|
||||
export class DropdownElementType extends FormElementType {
|
||||
export class ChoiceElementType extends FormElementType {
|
||||
static getType() {
|
||||
return 'dropdown'
|
||||
return 'choice'
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.app.i18n.t('elementType.dropdown')
|
||||
return this.app.i18n.t('elementType.choice')
|
||||
}
|
||||
|
||||
get description() {
|
||||
return this.app.i18n.t('elementType.dropdownDescription')
|
||||
return this.app.i18n.t('elementType.choiceDescription')
|
||||
}
|
||||
|
||||
get iconClass() {
|
||||
|
@ -1228,11 +1228,11 @@ export class DropdownElementType extends FormElementType {
|
|||
}
|
||||
|
||||
get component() {
|
||||
return DropdownElement
|
||||
return ChoiceElement
|
||||
}
|
||||
|
||||
get generalFormComponent() {
|
||||
return DropdownElementForm
|
||||
return ChoiceElementForm
|
||||
}
|
||||
|
||||
formDataType(element) {
|
||||
|
@ -1262,10 +1262,10 @@ export class DropdownElementType extends FormElementType {
|
|||
}
|
||||
|
||||
/**
|
||||
* Responsible for validating the dropdown form element. It behaves slightly
|
||||
* differently so that dropdown options with blank values are valid. We simply
|
||||
* test if the value is one of the dropdown's own values.
|
||||
* @param element - The dropdown form element
|
||||
* Responsible for validating the choice form element. It behaves slightly
|
||||
* differently so that choice options with blank values are valid. We simply
|
||||
* test if the value is one of the choice's own values.
|
||||
* @param element - The choice form element
|
||||
* @param value - The value we are validating.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
|
@ -1276,6 +1276,10 @@ export class DropdownElementType extends FormElementType {
|
|||
return !(element.required && !validOption)
|
||||
}
|
||||
|
||||
isInError({ element, builder }) {
|
||||
return element.options.length === 0
|
||||
}
|
||||
|
||||
getDataSchema(element) {
|
||||
const type = this.formDataType(element)
|
||||
if (type === 'string') {
|
||||
|
|
|
@ -87,8 +87,8 @@
|
|||
"tableDescription": "A table element",
|
||||
"formContainer": "Form",
|
||||
"formContainerDescription": "A form element",
|
||||
"dropdown": "Dropdown",
|
||||
"dropdownDescription": "Dropdown element",
|
||||
"choice": "Choice",
|
||||
"choiceDescription": "For single/multiple value selection",
|
||||
"checkbox": "Checkbox",
|
||||
"checkboxDescription": "Checkbox element",
|
||||
"iframe": "IFrame",
|
||||
|
@ -408,8 +408,12 @@
|
|||
"fontSidePanelForm": {
|
||||
"label": "Font color"
|
||||
},
|
||||
"dropdownElementForm": {
|
||||
"multiple": "Allow multiple values"
|
||||
"choiceElementForm": {
|
||||
"multiple": "Allow multiple values",
|
||||
"display": "Display",
|
||||
"dropdown": "Dropdown",
|
||||
"checkbox": "Checkbox",
|
||||
"radio": "Radio"
|
||||
},
|
||||
"tableElementForm": {
|
||||
"dataSource": "Data source",
|
||||
|
@ -512,7 +516,7 @@
|
|||
"resetToInitialValuesTitle": "Reset to default values after submission",
|
||||
"resetToInitialValuesDescription": "If checked, the form's default values will be used to reset the form after successful submission. If unchecked, the user's values will remain."
|
||||
},
|
||||
"dropdownOptionSelector": {
|
||||
"choiceOptionSelector": {
|
||||
"label": "Options",
|
||||
"value": "Value",
|
||||
"name": "Name",
|
||||
|
@ -550,6 +554,9 @@
|
|||
"passwordPlaceholder": "Enter your password...",
|
||||
"selectOrConfigureUserSourceFirst": "Choose a user source to begin using this login element."
|
||||
},
|
||||
"choiceElement": {
|
||||
"addOptions": "Add options to begin using this element..."
|
||||
},
|
||||
"userSourceUsersContext": {
|
||||
"searchPlaceholder": "Search user",
|
||||
"anonymous": "Anonymous",
|
||||
|
|
|
@ -36,7 +36,7 @@ import {
|
|||
ButtonElementType,
|
||||
TableElementType,
|
||||
FormContainerElementType,
|
||||
DropdownElementType,
|
||||
ChoiceElementType,
|
||||
CheckboxElementType,
|
||||
IFrameElementType,
|
||||
RepeatElementType,
|
||||
|
@ -178,7 +178,7 @@ export default (context) => {
|
|||
app.$registry.register('element', new ColumnElementType(context))
|
||||
app.$registry.register('element', new FormContainerElementType(context))
|
||||
app.$registry.register('element', new InputTextElementType(context))
|
||||
app.$registry.register('element', new DropdownElementType(context))
|
||||
app.$registry.register('element', new ChoiceElementType(context))
|
||||
app.$registry.register('element', new CheckboxElementType(context))
|
||||
app.$registry.register('element', new RepeatElementType(context))
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@ import ABLink from '@baserow/modules/builder/components/elements/baseComponents/
|
|||
import ABHeading from '@baserow/modules/builder/components/elements/baseComponents/ABHeading'
|
||||
import ABDropdown from '@baserow/modules/builder/components/elements/baseComponents/ABDropdown'
|
||||
import ABDropdownItem from '@baserow/modules/builder/components/elements/baseComponents/ABDropdownItem'
|
||||
import ABCheckbox from '@baserow/modules/builder/components/elements/baseComponents/ABCheckbox.vue'
|
||||
import ABRadio from '@baserow/modules/builder/components/elements/baseComponents/ABRadio.vue'
|
||||
|
||||
function setupVueForAB(Vue) {
|
||||
Vue.component('ABButton', ABButton)
|
||||
|
@ -16,6 +18,8 @@ function setupVueForAB(Vue) {
|
|||
Vue.component('ABHeading', ABHeading)
|
||||
Vue.component('ABDropdown', ABDropdown)
|
||||
Vue.component('ABDropdownItem', ABDropdownItem)
|
||||
Vue.component('ABCheckbox', ABCheckbox)
|
||||
Vue.component('ABRadio', ABRadio)
|
||||
}
|
||||
|
||||
setupVueForAB(Vue)
|
||||
|
|
1
web-frontend/modules/core/assets/icons/check-square.svg
Normal file
1
web-frontend/modules/core/assets/icons/check-square.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><svg width="64px" height="64px" viewBox="0 0 24 24" stroke-width="1.5" fill="none" xmlns="http://www.w3.org/2000/svg" color="#000000"><path d="M3 20.4V3.6C3 3.26863 3.26863 3 3.6 3H20.4C20.7314 3 21 3.26863 21 3.6V20.4C21 20.7314 20.7314 21 20.4 21H3.6C3.26863 21 3 20.7314 3 20.4Z" stroke="#000000" stroke-width="1.5"></path><path d="M7 12.5L10 15.5L17 8.5" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>
|
After (image error) Size: 493 B |
|
@ -1,3 +1,13 @@
|
|||
.ab-checkbox {
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:nth-child(n + 2) {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.ab-checkbox__input {
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
|
@ -23,4 +33,5 @@
|
|||
color: $black;
|
||||
user-select: none;
|
||||
padding-bottom: 2px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
.ab-form-group {
|
||||
margin-bottom: 20px;
|
||||
&.ab-form-group--with-label {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
&.ab-form-group--horizontal,
|
||||
&.ab-form-group--horizontal-variable {
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
.ab-radio {
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:nth-child(n + 2) {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.ab-radio__input {
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
border: 1px solid $black;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ab-radio--error {
|
||||
border-color: $color-error-300;
|
||||
}
|
||||
|
||||
.ab-radio--readonly {
|
||||
accent-color: $palette-blue-500;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.ab-radio__label {
|
||||
cursor: pointer;
|
||||
color: $black;
|
||||
user-select: none;
|
||||
padding-bottom: 2px;
|
||||
margin-left: 10px;
|
||||
}
|
|
@ -8,3 +8,4 @@
|
|||
@import 'ab_link';
|
||||
@import 'ab_paragraph';
|
||||
@import 'ab_tag';
|
||||
@import 'ab_radio';
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
@import 'button_element';
|
||||
@import 'table_element';
|
||||
@import 'baserow_table';
|
||||
@import 'checkbox_element';
|
||||
@import 'dropdown_element';
|
||||
@import 'choice_element';
|
||||
@import 'iframe_element';
|
||||
@import 'repeat_element';
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
.checkbox-element {
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
.dropdown-element {
|
||||
.choice-element {
|
||||
border: 1px solid $black;
|
||||
border-radius: 0;
|
||||
|
||||
|
@ -30,6 +30,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.dropdown-element--error {
|
||||
.choice-element--error {
|
||||
border-color: $color-error-300;
|
||||
}
|
|
@ -69,11 +69,11 @@ $baserow-icon-prefix: 'baserow-icon-';
|
|||
// This list must contain all the svg icons in the `modules/core/assets/icons` that must
|
||||
// automatically become under `baserow-icon-$icon`. All the icons must be designed on a
|
||||
// 24px by 24px grid.
|
||||
$baserow-icons: 'circle-empty', 'circle-checked', 'formula', 'hashtag',
|
||||
'more-horizontal', 'more-vertical', 'reddit', 'single-select', 'twitter',
|
||||
'lock-open', 'flag', 'heart', 'star', 'thumbs-up', 'history', 'facebook',
|
||||
'linkedin', 'gitlab', 'file-csv', 'file-pdf', 'file-image', 'file-audio',
|
||||
'file-video', 'file-code', 'tablet', 'form', 'file-excel', 'kanban',
|
||||
'file-word', 'file-archive', 'gallery', 'file-powerpoint', 'calendar', 'smile',
|
||||
'smartphone', 'plus', 'heading-1', 'heading-2', 'heading-3', 'paragraph',
|
||||
'ordered-list', 'enlarge';
|
||||
$baserow-icons: 'circle-empty', 'circle-checked', 'check-square', 'formula',
|
||||
'hashtag', 'more-horizontal', 'more-vertical', 'reddit', 'single-select',
|
||||
'twitter', 'lock-open', 'flag', 'heart', 'star', 'thumbs-up', 'history',
|
||||
'facebook', 'linkedin', 'gitlab', 'file-csv', 'file-pdf', 'file-image',
|
||||
'file-audio', 'file-video', 'file-code', 'tablet', 'form', 'file-excel',
|
||||
'kanban', 'file-word', 'file-archive', 'gallery', 'file-powerpoint',
|
||||
'calendar', 'smile', 'smartphone', 'plus', 'heading-1', 'heading-2',
|
||||
'heading-3', 'paragraph', 'ordered-list', 'enlarge';
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
@mousemove="hover(value, disabled)"
|
||||
>
|
||||
<div class="select__item-name">
|
||||
<div v-if="multiple">
|
||||
<div v-if="multiple.value">
|
||||
<Checkbox :disabled="disabled" :checked="isActive(value)"></Checkbox>
|
||||
</div>
|
||||
<slot>
|
||||
|
@ -34,7 +34,10 @@
|
|||
{{ description }}
|
||||
</div>
|
||||
</a>
|
||||
<i v-if="!multiple" class="select__item-active-icon iconoir-check"></i>
|
||||
<i
|
||||
v-if="!multiple.value"
|
||||
class="select__item-active-icon iconoir-check"
|
||||
></i>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -14,7 +14,9 @@ export default {
|
|||
return {
|
||||
// This is needed to tell all the child components that the dropdown is going
|
||||
// to be in multiple state.
|
||||
multiple: this.multiple,
|
||||
// The reactiveMultiple is an object to deal with the reactivity issue when you
|
||||
// use provide inject pattern. Don't change it.
|
||||
multiple: this.reactiveMultiple,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
|
@ -99,6 +101,7 @@ export default {
|
|||
hasDropdownItem: true,
|
||||
hover: null,
|
||||
fixedItemsImmutable: this.fixedItems,
|
||||
reactiveMultiple: { value: this.multiple }, // Used for provide
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -128,6 +131,9 @@ export default {
|
|||
this.forceRefreshSelectedValue()
|
||||
})
|
||||
},
|
||||
multiple(newValue) {
|
||||
this.reactiveMultiple.value = newValue
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
// When the component is mounted we want to forcefully reload the selectedName and
|
||||
|
|
|
@ -74,7 +74,7 @@ export default {
|
|||
return this.name.match(regex)
|
||||
},
|
||||
isActive(value) {
|
||||
if (this.multiple) {
|
||||
if (this.multiple.value) {
|
||||
return this.$parent.value.includes(value)
|
||||
} else {
|
||||
return this.$parent.value === value
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
@click="select(value, disabled)"
|
||||
@mousemove="hover(value, disabled)"
|
||||
>
|
||||
<div v-if="multiple">
|
||||
<div v-if="multiple.value">
|
||||
<Checkbox :disabled="disabled" :checked="isActive(value)"></Checkbox>
|
||||
</div>
|
||||
<div
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
CheckboxElementType,
|
||||
DropdownElementType,
|
||||
ChoiceElementType,
|
||||
ElementType,
|
||||
InputTextElementType,
|
||||
} from '@baserow/modules/builder/elementTypes'
|
||||
|
@ -47,8 +47,8 @@ describe('elementTypes tests', () => {
|
|||
).toBe(elementType.name)
|
||||
expect(elementType.getDisplayName({}, {})).toBe(elementType.name)
|
||||
})
|
||||
test('DropdownElementType label, default_value & placeholder variations', () => {
|
||||
const elementType = testApp.getRegistry().get('element', 'dropdown')
|
||||
test('ChoiceElementType label, default_value & placeholder variations', () => {
|
||||
const elementType = testApp.getRegistry().get('element', 'choice')
|
||||
expect(elementType.getDisplayName({ label: "'Animals'" }, {})).toBe(
|
||||
'Animals'
|
||||
)
|
||||
|
@ -303,16 +303,16 @@ describe('elementTypes tests', () => {
|
|||
const elementType = new CheckboxElementType()
|
||||
expect(elementType.isValid({ required: false }, true)).toBe(true)
|
||||
})
|
||||
test('DropdownElementType | required | no value.', () => {
|
||||
const elementType = new DropdownElementType()
|
||||
test('ChoiceElementType | required | no value.', () => {
|
||||
const elementType = new ChoiceElementType()
|
||||
const element = {
|
||||
required: true,
|
||||
options: [{ id: 1, value: 'uk', name: 'UK' }],
|
||||
}
|
||||
expect(elementType.isValid(element, '')).toBe(false)
|
||||
})
|
||||
test('DropdownElementType | required | blank option.', () => {
|
||||
const elementType = new DropdownElementType()
|
||||
test('ChoiceElementType | required | blank option.', () => {
|
||||
const elementType = new ChoiceElementType()
|
||||
const element = {
|
||||
required: true,
|
||||
options: [
|
||||
|
@ -322,24 +322,24 @@ describe('elementTypes tests', () => {
|
|||
}
|
||||
expect(elementType.isValid(element, '')).toBe(true)
|
||||
})
|
||||
test('DropdownElementType | required | valid value.', () => {
|
||||
const elementType = new DropdownElementType()
|
||||
test('ChoiceElementType | required | valid value.', () => {
|
||||
const elementType = new ChoiceElementType()
|
||||
const element = {
|
||||
required: true,
|
||||
options: [{ id: 1, value: 'uk', name: 'UK' }],
|
||||
}
|
||||
expect(elementType.isValid(element, 'uk')).toBe(true)
|
||||
})
|
||||
test('DropdownElementType | not required | no value.', () => {
|
||||
const elementType = new DropdownElementType()
|
||||
test('ChoiceElementType | not required | no value.', () => {
|
||||
const elementType = new ChoiceElementType()
|
||||
const element = {
|
||||
required: false,
|
||||
options: [{ id: 1, value: 'uk', name: 'UK' }],
|
||||
}
|
||||
expect(elementType.isValid(element, '')).toBe(true)
|
||||
})
|
||||
test('DropdownElementType | not required | valid value.', () => {
|
||||
const elementType = new DropdownElementType()
|
||||
test('ChoiceElementType | not required | valid value.', () => {
|
||||
const elementType = new ChoiceElementType()
|
||||
const element = {
|
||||
required: false,
|
||||
options: [{ id: 1, value: 'uk', name: 'UK' }],
|
||||
|
|
Loading…
Add table
Reference in a new issue