Skip to content

Commit

Permalink
Merge pull request #3041 from vlad-ghita/allow_empty
Browse files Browse the repository at this point in the history
New feature: Allow empty value for select form controls, based on user setting.
  • Loading branch information
bobdenotter committed Jan 19, 2022
2 parents 84336d0 + 093b9dc commit 6c15cbc
Show file tree
Hide file tree
Showing 11 changed files with 54 additions and 22 deletions.
2 changes: 0 additions & 2 deletions assets/js/app/editor/Components/Select.vue
Expand Up @@ -3,7 +3,6 @@
<multiselect
ref="vselect"
v-model="selected"
:allow-empty="allowempty"
:limit="1000"
:multiple="multiple"
:options="options"
Expand Down Expand Up @@ -73,7 +72,6 @@ export default {
options: Array,
optionslimit: Number,
multiple: Boolean,
allowempty: Boolean,
taggable: Boolean,
readonly: Boolean,
classname: String,
Expand Down
4 changes: 2 additions & 2 deletions src/Cache/RelatedOptionsUtilityCacher.php
Expand Up @@ -10,10 +10,10 @@ class RelatedOptionsUtilityCacher extends RelatedOptionsUtility implements Cachi

public const CACHE_CONFIG_KEY = 'related_options';

public function fetchRelatedOptions(string $contentTypeSlug, string $order, string $format, bool $required, int $maxAmount): array
public function fetchRelatedOptions(string $contentTypeSlug, string $order, string $format, bool $required, ?bool $allowEmpty, int $maxAmount): array
{
$this->setCacheKey([$contentTypeSlug, $order, $format, (string) $required, $maxAmount]);

return $this->execute([parent::class, __FUNCTION__], [$contentTypeSlug, $order, $format, $required, $maxAmount]);
return $this->execute([parent::class, __FUNCTION__], [$contentTypeSlug, $order, $format, $required, $allowEmpty, $maxAmount]);
}
}
41 changes: 38 additions & 3 deletions src/Entity/Field.php
Expand Up @@ -16,7 +16,7 @@
use Knp\DoctrineBehaviors\Model\Translatable\TranslatableTrait;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Annotation\SerializedName;
use Tightenco\Collect\Support\Collection as LaravelCollection;
use Tightenco\Collect\Support\Collection;
use Twig\Environment;
use Twig\Markup;

Expand Down Expand Up @@ -155,7 +155,7 @@ private function setDefinitionFromContentDefinition(): void
}
}

public function setDefinition($name, LaravelCollection $definition): void
public function setDefinition($name, Collection $definition): void
{
$this->fieldTypeDefinition = FieldType::mock($name, $definition);
}
Expand All @@ -177,7 +177,7 @@ public function get($key)
$default = $this->getDefaultValue();

if ($this->isNew() && $default !== null) {
if (! $default instanceof LaravelCollection) {
if (! $default instanceof Collection) {
throw new \RuntimeException('Default value of field ' . $this->getName() . ' is ' . gettype($default) . ' but it should be an array.');
}

Expand Down Expand Up @@ -439,4 +439,39 @@ public static function setTwig(Environment $twig): void
{
self::$twig = $twig;
}

public function allowEmpty(): bool
{
return self::definitionAllowsEmpty($this->getDefinition());
}

public static function definitionAllowsEmpty(Collection $definition): bool
{
return self::settingsAllowEmpty(
$definition->get('allow_empty', null),
$definition->get('required', null)
);
}

/**
* True if settings allow empty value.
*
* Settings priority:
* - allow_empty
* - required
*
* Defaults to true.
*/
public static function settingsAllowEmpty(?bool $allowEmpty, ?bool $required): bool
{
if (!is_null($allowEmpty)) {
return boolval($allowEmpty);
}

if (!is_null($required)) {
return !boolval($required);
}

return true;
}
}
2 changes: 1 addition & 1 deletion src/Entity/Field/SelectField.php
Expand Up @@ -44,7 +44,7 @@ public function getValue(): ?array
{
$value = parent::getValue();

if (empty($value) && $this->getDefinition()->get('required')) {
if (empty($value) && !$this->allowEmpty()) {
$value = $this->getDefinition()->get('values');

// Pick the first key from Collection, or the full value as string, like `entries/id,title`
Expand Down
4 changes: 2 additions & 2 deletions src/Twig/ContentExtension.php
Expand Up @@ -507,7 +507,7 @@ public function taxonomyOptions(Collection $taxonomy): Collection
// We need to add this as a 'dummy' option for when the user is allowed
// not to pick an option. This is needed, because otherwise the `select`
// would default to the first option.
if ($taxonomy['required'] === false) {
if (Field::definitionAllowsEmpty($taxonomy)) {
$options[] = [
'key' => '',
'value' => '',
Expand Down Expand Up @@ -549,7 +549,7 @@ public function taxonomyValues(\Doctrine\Common\Collections\Collection $current,
$orders = $orders[$taxonomy['slug']] ?? [];
}

if (empty($values) && $taxonomy['required']) {
if (empty($values) && !Field::definitionAllowsEmpty($taxonomy)) {
$values[] = key($taxonomy['options']);
$orders = array_fill(0, count($values), 0);
}
Expand Down
8 changes: 4 additions & 4 deletions src/Twig/FieldExtension.php
Expand Up @@ -169,7 +169,7 @@ public function getListTemplates(TemplateselectField $field): Collection

$options = [];

if ($definition->get('required') === false) {
if ($field->allowEmpty()) {
$options = [[
'key' => '',
'value' => '(choose a template)',
Expand Down Expand Up @@ -227,7 +227,7 @@ private function selectOptionsArray(Field $field): Collection
// We need to add this as a 'dummy' option for when the user is allowed
// not to pick an option. This is needed, because otherwise the `select`
// would default to the one.
if (! $field->getDefinition()->get('required', true)) {
if ($field->allowEmpty()) {
$options[] = [
'key' => '',
'value' => '',
Expand Down Expand Up @@ -259,7 +259,7 @@ private function selectOptionsContentType(Field $field): Collection
// We need to add this as a 'dummy' option for when the user is allowed
// not to pick an option. This is needed, because otherwise the `select`
// would default to the one.
if (! $field->getDefinition()->get('required', true)) {
if ($field->allowEmpty()) {
$options[] = [
'key' => '',
'value' => '',
Expand Down Expand Up @@ -307,4 +307,4 @@ public function selectOptionsHelper(string $contentTypeSlug, array $params, Fiel

return $options;
}
}
}
4 changes: 2 additions & 2 deletions src/Twig/RelatedExtension.php
Expand Up @@ -138,7 +138,7 @@ private function extractContentFromRelation(Relation $relation, Content $source)
return null;
}

public function getRelatedOptions(string $contentTypeSlug, ?string $order = null, string $format = '', ?bool $required = false): Collection
public function getRelatedOptions(string $contentTypeSlug, ?string $order = null, string $format = '', ?bool $required = false, ?bool $allowEmpty = false): Collection
{
$maxAmount = $this->config->get('maximum_listing_select', 1000);

Expand All @@ -148,7 +148,7 @@ public function getRelatedOptions(string $contentTypeSlug, ?string $order = null
$order = $contentType->get('order');
}

$options = $this->optionsUtility->fetchRelatedOptions($contentTypeSlug, $order, $format, $required, $maxAmount);
$options = $this->optionsUtility->fetchRelatedOptions($contentTypeSlug, $order, $format, $required, $allowEmpty, $maxAmount);

return new Collection($options);
}
Expand Down
5 changes: 3 additions & 2 deletions src/Utils/RelatedOptionsUtility.php
Expand Up @@ -3,6 +3,7 @@
namespace Bolt\Utils;

use Bolt\Entity\Content;
use Bolt\Entity\Field;
use Bolt\Storage\Query;

/**
Expand All @@ -27,7 +28,7 @@ public function __construct(Query $query, ContentHelper $contentHelper)
/**
* Decorated by `Bolt\Cache\RelatedOptionsUtilityCacher`
*/
public function fetchRelatedOptions(string $contentTypeSlug, string $order, string $format, bool $required, int $maxAmount): array
public function fetchRelatedOptions(string $contentTypeSlug, string $order, string $format, bool $required, ?bool $allowEmpty, int $maxAmount): array
{
$pager = $this->query->getContent($contentTypeSlug, ['order' => $order])
->setMaxPerPage($maxAmount)
Expand All @@ -40,7 +41,7 @@ public function fetchRelatedOptions(string $contentTypeSlug, string $order, stri
// We need to add this as a 'dummy' option for when the user is allowed
// not to pick an option. This is needed, because otherwise the `select`
// would default to the first one.
if ($required === false) {
if (Field::settingsAllowEmpty($allowEmpty, $required)) {
$options[] = [
'key' => '',
'value' => '',
Expand Down
1 change: 0 additions & 1 deletion templates/_partials/fields/select.html.twig
Expand Up @@ -36,6 +36,5 @@
:autocomplete="{{ autocomplete }}"
:readonly="{{ readonly|json_encode }}"
:errormessage='{{ errormessage|json_encode }}'
:allowempty="{{ required ? 'false' : 'true' }}"
></editor-select>
{% endblock %}
4 changes: 2 additions & 2 deletions templates/content/_relationships.html.twig
Expand Up @@ -2,7 +2,8 @@

{% for contentType, relation in record.definition.relations %}

{% set options = related_options(contentType, relation.order|default(), relation.format|default(), relation.required) %}
{% set options = related_options(contentType, relation.order|default(), relation.format|default(), relation.required, relation.allow_empty) %}

{% set value = record|related_values(contentType) %}

<div class="form-group is-normal">
Expand All @@ -26,7 +27,6 @@
:options="{{ options }}"
:multiple="{{ relation.multiple ? 'true' : 'false' }}"
:taggable=false
:allowempty="{{ relation.required ? 'false' : 'true' }}"
:searchable=true
></editor-select>
</div>
Expand Down
1 change: 0 additions & 1 deletion templates/content/_taxonomies.html.twig
Expand Up @@ -39,7 +39,6 @@
:options="{{ options }}"
:multiple="{{ definition.multiple ? 'true' : 'false' }}"
:taggable="{{ (definition.behaves_like == 'tags') ? 'true' : 'false' }}"
:allowempty="{{ definition.required ? 'false' : 'true' }}"
></editor-select>
{% if definition.has_sortorder %}
</div>
Expand Down

0 comments on commit 6c15cbc

Please sign in to comment.