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 4 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\RouterInterface;

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

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->router->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')
;
}
}
7 changes: 7 additions & 0 deletions src/Resources/config/actions.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Kreyu\Bundle\DataTableBundle\Action\Type\ButtonActionType;
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;
Expand Down Expand Up @@ -60,4 +61,10 @@
->set('kreyu_data_table.action.type.form', FormActionType::class)
->tag('kreyu_data_table.action.type')
;

$services
->set('kreyu_data_table.action.type.modal', ModalActionType::class)
->tag('kreyu_data_table.action.type')
->args([service('router')])
;
};
12 changes: 12 additions & 0 deletions src/Resources/views/themes/base.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,18 @@
</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 sort_arrow_none %}{% endblock %}

{% block sort_arrow_asc %}↑{% endblock %}
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...
</div>
</div>
</div>
</div>
</div>
{% endblock %}

{% block sort_arrow_none %}
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chevron-expand" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M3.646 9.146a.5.5 0 0 1 .708 0L8 12.793l3.646-3.647a.5.5 0 0 1 .708.708l-4 4a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 0-.708zm0-2.292a.5.5 0 0 0 .708 0L8 3.207l3.646 3.647a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0l-4 4a.5.5 0 0 0 0 .708z"/>
Expand Down
Loading