Skip to content

[POC] Add a modal action whose content is loaded via an HTTP call. #164

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jan 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions assets/controllers/batch.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,14 @@ export default class extends Controller {

#updateIdentifierHolderHref(identifierHolder, identifierMap) {
let href;
let hrefHolder = 'href';

if (identifierHolder.dataset.hrefHolder !== undefined) {
hrefHolder = identifierHolder.dataset.hrefHolder;
}

try {
href = new URL(identifierHolder.href);
href = new URL(identifierHolder.dataset[hrefHolder], window.location.origin);
} catch (exception) {
return;
}
Expand All @@ -111,7 +116,7 @@ export default class extends Controller {
}
}

identifierHolder.href = href.toString();
identifierHolder.dataset[hrefHolder] = href.toString();
}

#updateIdentifierHolderDataParam(identifierHolder, identifierMap) {
Expand Down
27 changes: 27 additions & 0 deletions assets/controllers/bootstrap/modal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Controller } from '@hotwired/stimulus';
import { Modal } from 'bootstrap'; // Import Bootstrap

export default class extends Controller {
static targets = ['modal'];

static values = {
url: String,
}

open(event) {
event.preventDefault();
const modalContent = this.modalTarget;

fetch(this.urlValue)
.then(response => {
if (!response.ok) {
throw new Error('Error loading content.');
}
return response.text();
})
.then(html => {
modalContent.innerHTML = html;
})
;
}
}
7 changes: 6 additions & 1 deletion assets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@
"main": "controllers/state.js",
"fetch": "eager",
"enabled": true
},
"bootstrap-modal": {
"main": "controllers/bootstrap/modal.js",
"fetch": "eager",
"enabled": false
}
},
"importmap": {
Expand All @@ -34,4 +39,4 @@
"@hotwired/stimulus": "^3.0.0",
"sortablejs": "^1.15.0"
}
}
}
70 changes: 70 additions & 0 deletions src/Action/Type/ModalActionType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

declare(strict_types=1);

namespace Kreyu\Bundle\DataTableBundle\Action\Type;

use Kreyu\Bundle\DataTableBundle\Action\ActionInterface;
use Kreyu\Bundle\DataTableBundle\Action\ActionView;
use Kreyu\Bundle\DataTableBundle\Column\ColumnValueView;
use Kreyu\Bundle\DataTableBundle\Exception\LogicException;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

final class ModalActionType extends AbstractActionType
{
public function __construct(
private readonly UrlGeneratorInterface $urlGenerator,
) {
}

public function buildView(ActionView $view, ActionInterface $action, array $options): void
{
if (null === $options['href'] && null === $options['route']) {
throw new LogicException('No "route" or "href" provided.');
}

if ($view->parent instanceof ColumnValueView) {
$value = $view->parent->value;

foreach (['href', 'route', 'route_params'] as $optionName) {
if (isset($options[$optionName]) && is_callable($options[$optionName])) {
$options[$optionName] = $options[$optionName]($value);
}
}
} else {
foreach (['href', 'route', 'route_params'] as $optionName) {
if (isset($options[$optionName]) && is_callable($options[$optionName])) {
throw new LogicException(sprintf('Callable used for option "%s", but it\'s only available for RowActions.', $optionName));
}
}
}

$href = $options['href'] ?? $this->urlGenerator->generate($options['route'], $options['route_params']);

$view->vars = array_replace($view->vars, [
'href' => $href,
]);
}

public function configureOptions(OptionsResolver $resolver): void
{
$resolver
->define('route')
->default(null)
->allowedTypes('null', 'string', 'callable')
;

$resolver
->define('route_params')
->allowedTypes('array', 'callable')
->default([])
;

$resolver
->define('href')
->default(null)
->allowedTypes('null', 'string', 'callable')
;
}
}
8 changes: 8 additions & 0 deletions src/Resources/config/actions.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
use Kreyu\Bundle\DataTableBundle\Action\Type\Dropdown\LinkDropdownItemActionType;
use Kreyu\Bundle\DataTableBundle\Action\Type\FormActionType;
use Kreyu\Bundle\DataTableBundle\Action\Type\LinkActionType;
use Kreyu\Bundle\DataTableBundle\Action\Type\ModalActionType;
use Kreyu\Bundle\DataTableBundle\Action\Type\ResolvedActionTypeFactory;
use Kreyu\Bundle\DataTableBundle\Action\Type\ResolvedActionTypeFactoryInterface;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

use function Symfony\Component\DependencyInjection\Loader\Configurator\service;
use function Symfony\Component\DependencyInjection\Loader\Configurator\tagged_iterator;
Expand Down Expand Up @@ -63,6 +65,12 @@
->tag('kreyu_data_table.action.type')
;

$services
->set('kreyu_data_table.action.type.modal', ModalActionType::class)
->tag('kreyu_data_table.action.type')
->args([service(UrlGeneratorInterface::class)])
;

$services
->set('kreyu_data_table.action.type.dropdown', DropdownActionType::class)
->tag('kreyu_data_table.action.type')
Expand Down
3 changes: 2 additions & 1 deletion src/Resources/translations/KreyuDataTable.en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,5 @@ Clear all filters: Clear all filters
Action confirmation: Action execution confirmation
Are you sure you want to execute this action?: Are you sure you want to execute this action?
Confirm: Confirm
Cancel: Cancel
Cancel: Cancel
Loading: Loading
1 change: 1 addition & 0 deletions src/Resources/translations/KreyuDataTable.fr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,4 @@ Action confirmation: Confirmation de l'action
Are you sure you want to execute this action?: Êtes-vous sûr de vouloir exécuter cette action ?
Confirm: Confirmer
Cancel: Annuler
Loading: Chargement
13 changes: 13 additions & 0 deletions src/Resources/views/themes/base.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,19 @@
</form>
{% endblock %}


{% block action_modal_control %}
{% set attr = { href }|filter(v => v != null)|merge(attr|default({})) %}

{% if batch %}
{% set attr = { 'data-kreyu--data-table-bundle--batch-target': 'identifierHolder' }|merge(attr) %}
{% endif %}

<button {% with { attr } %}{{- block('attributes') -}}{% endwith %}>
{% with { attr: {} } %}{{- block('action_control', theme, _context) -}}{% endwith %}
</button>
{% endblock %}

{% block action_dropdown_control %}
{# Themes that extend base theme should provide their own implementation of dropdown #}
<button>{{- block('action_control', theme, _context) -}}</button>
Expand Down
31 changes: 31 additions & 0 deletions src/Resources/views/themes/bootstrap_5.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,37 @@
{% endif %}
{% endblock %}


{% block action_modal_control %}
<div
data-controller="kreyu--data-table-bundle--bootstrap-modal"
data-kreyu--data-table-bundle--bootstrap-modal-url-value="{{ href }}"

{% if batch %}
data-kreyu--data-table-bundle--batch-target="identifierHolder"
data-href-holder="kreyu-DataTableBundle-BootstrapModalUrlValue"
{% endif %}
>
{% set modalId = 'modalId-' ~ random() %}
<button
data-action="kreyu--data-table-bundle--bootstrap-modal#open"
type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#{{ modalId }}">
{% with { attr: {} } %}{{- block('action_control', theme, _context) -}}{% endwith %}
</button>

<div class="modal fade" id="{{ modalId }}" tabindex="-1" aria-labelledby="{{ modalId }}Label" aria-hidden="true"
data-kreyu--data-table-bundle--bootstrap-modal-target="modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
{{ 'Loading'|trans }}
</div>
</div>
</div>
</div>
</div>
{% endblock %}

{% block action_dropdown_control %}
<div class="dropdown d-inline-block">
{% set attr = {
Expand Down
Loading