Skip to content

Commit

Permalink
Merge pull request #3485 from xiaohutai/feature/ajax-select
Browse files Browse the repository at this point in the history
Allow `lazy` as an option for select fields using contenttype-queries
  • Loading branch information
bobdenotter authored Oct 13, 2023
2 parents 1d5ef63 + fee6eb7 commit 7b12b2c
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 20 deletions.
69 changes: 53 additions & 16 deletions assets/js/app/editor/Components/Select.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
tag-placeholder="Add this as new tag"
tag-position="bottom"
track-by="key"
:loading="isLoading"
@tag="addTag"
>
<template v-if="name === 'status'" slot="singleLabel" slot-scope="props">
Expand Down Expand Up @@ -95,6 +96,7 @@ export default {
autocomplete: Boolean,
errormessage: String | Boolean, //string if errormessage is set, and false otherwise
required: String | Boolean,
fetchurl: String,
},
data: () => {
return {
Expand All @@ -119,28 +121,63 @@ export default {
},
},
mounted() {
const _values = !this.value ? [] : this.value.map ? this.value : [this.value];
const _options = this.options;
/**
* Filter method is necessary for required fields because the empty option is not
* set. If the field is empty, "filterSelectedItems" will contain an undefined
* element and "select" will not be filled with the first available option.
*/
let filterSelectedItems = _values
.map(value => {
const item = _options.filter(opt => opt.key === value);
if (item.length > 0) {
return item[0];
}
})
.filter(item => undefined !== item);
if (!!this._props.required && filterSelectedItems.length === 0) {
filterSelectedItems = [_options[0]];
}
const fixSelectedItems = function() {
const _values = !this.value ? [] : this.value.map ? this.value : [this.value];
let filterSelectedItems = _values
.map(value => {
const item = this.options.filter(opt => opt.key === value);
if (item.length > 0) {
return item[0];
}
})
.filter(item => undefined !== item);
if (!!this._props.required && filterSelectedItems.length === 0) {
filterSelectedItems = [this.options[0]];
}
this.selected = filterSelectedItems;
};
this.selected = filterSelectedItems;
/**
* If `fetchurl` is defined then pre-fill using a call. One problem is that on initialization
* for existing selects, multiple requests will be done at the same time. Subsequent queries
* can make use of the cache. Important part here is to have server-side caching.
*/
if (this.fetchurl) {
window.selectCache = window.selectCache || {};
window.requestCache = window.requestCache || {};
if (window.selectCache[this.fetchurl]) {
this.options = window.selectCache[this.fetchurl];
fixSelectedItems.call(this);
} else if (window.requestCache[this.fetchurl]) {
window.requestCache[this.fetchurl].then(response => {
this.options = response;
fixSelectedItems.call(this);
});
} else {
this.isLoading = true;
window.requestCache[this.fetchurl] = $.ajax({ url: this.fetchurl, dataType: 'json', cache: true });
window.requestCache[this.fetchurl].then(
response => {
this.options = response;
window.selectCache[this.fetchurl] = response;
this.isLoading = false;
fixSelectedItems.call(this);
},
);
}
} else {
fixSelectedItems.call(this);
}
},
methods: {
addTag(newTag) {
Expand Down
74 changes: 74 additions & 0 deletions src/Controller/Backend/Async/SelectOptionsController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

declare(strict_types=1);

namespace Bolt\Controller\Backend\Async;

use Bolt\Configuration\Config;
use Bolt\Twig\FieldExtension;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Tightenco\Collect\Support\Collection;

/**
* @Security("is_granted('upload')")
*/
class SelectOptionsController extends AbstractController implements AsyncZoneInterface
{
/** @var Config */
private $config;

/** @var FieldExtension */
private $fieldExtension;

public function __construct(Config $config, FieldExtension $fieldExtension)
{
$this->config = $config;
$this->fieldExtension = $fieldExtension;
}

/**
* Based on Bolt\Twig\FieldExtension.
*
* @Route("/select-options", name="bolt_async_select_options", methods={"GET"})
*/
public function handleSelectOptions(Request $request): JsonResponse
{
[ $contentTypeSlug, $format ] = explode('/', $request->get('values'));

if (empty($maxAmount = $request->get('limit'))) {
$maxAmount = $this->config->get('general/maximum_listing_select', 200);
}

$order = $request->get('order');

$options = [];

// 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->allowEmpty()) {
$options[] = [
'key' => '',
'value' => '',
'selected' => false,
];
}
*/

$params = [
'limit' => $maxAmount,
'order' => $order,
];

$field = $this->fieldExtension->fieldFactory($request->get('name'));

$options = array_merge($options, $this->fieldExtension->selectOptionsHelper($contentTypeSlug, $params, $field, $format));

return new JsonResponse(new Collection($options));
}
}
14 changes: 11 additions & 3 deletions src/Twig/FieldExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ public function getFunctions(): array
new TwigFunction('field_factory', [$this, 'fieldFactory']),
new TwigFunction('list_templates', [$this, 'getListTemplates']),
new TwigFunction('select_options', [$this, 'selectOptions']),
new TwigFunction('select_options_url', [$this, 'selectOptionsUrl']),
];
}

Expand Down Expand Up @@ -210,6 +211,16 @@ public function getListTemplates(TemplateselectField $field): Collection
return new Collection($options);
}

public function selectOptionsUrl(Field\SelectField $field): String
{
return $this->router->generate('bolt_async_select_options', [
'name' => $field->getDefinition()->get('name', ''),
'values' => $field->getDefinition()->get('values'),
'limit' => $field->getDefinition()->get('limit', ''),
'order' => $field->getDefinition()->get('order', ''),
]);
}

public function selectOptions(Field $field): Collection
{
if (! $field instanceof SelectField) {
Expand Down Expand Up @@ -303,7 +314,6 @@ public function selectOptionsHelper(string $contentTypeSlug, array $params, Fiel
// If we use `cache/list_format`, delegate it to that Helper
if ($this->config->get('general/caching/list_format')) {
$options = $this->listFormatHelper->getSelect($contentTypeSlug, $params);
// dump($options);
return $options;
}

Expand All @@ -327,8 +337,6 @@ public function selectOptionsHelper(string $contentTypeSlug, array $params, Fiel
}
}

// dump($options);

return $options;
}
}
1 change: 0 additions & 1 deletion templates/_partials/fields/_base.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -160,4 +160,3 @@

{{ separator|raw }}
</div>

9 changes: 9 additions & 0 deletions templates/_partials/fields/select.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@
{% endif %}
{% endblock %}

{% set fetchUrl = '' %}
{% if field is defined
and field.definition.get('values') is not iterable
and field.definition.get('lazy')|default(false) %}
{% set options = [] %}
{% set fetchUrl = select_options_url(field) %}
{% endif %}

{% if options is not defined %}
{% set options = select_options(field) %}
{% endif %}
Expand Down Expand Up @@ -43,5 +51,6 @@
:readonly="{{ readonly|json_encode }}"
:errormessage='{{ errormessage|json_encode }}'
:required='{{ required|json_encode }}'
:fetchurl='{{ fetchUrl|json_encode }}'
></editor-select>
{% endblock %}

0 comments on commit 7b12b2c

Please sign in to comment.