vendor/easycorp/easyadmin-bundle/src/Resources/views/default/list.html.twig line 1

Open in your IDE?
  1. {% set _entity_config = easyadmin_entity(app.request.query.get('entity')) %}
  2. {% trans_default_domain _entity_config.translation_domain %}
  3. {% set _trans_parameters = { '%entity_name%': _entity_config.name|trans, '%entity_label%': _entity_config.label|trans } %}
  4. {% extends _entity_config.templates.layout %}
  5. {% set _request_parameters = app.request.query.all|merge(_request_parameters|default({}))|merge({
  6.     action: app.request.get('action'),
  7.     entity: _entity_config.name,
  8.     menuIndex: app.request.get('menuIndex'),
  9.     submenuIndex: app.request.get('submenuIndex'),
  10.     sortField: app.request.get('sortField'),
  11.     sortDirection: app.request.get('sortDirection'),
  12.     page: app.request.get('page', 1),
  13.     filters: app.request.get('filters', []),
  14.     referer: null
  15. }) %}
  16. {% if 'search' == app.request.get('action') %}
  17.     {% set _request_parameters = _request_parameters|merge({
  18.         query: app.request.get('query')|default(''),
  19.     }) %}
  20. {% endif %}
  21. {% set _request_parameters = _request_parameters|merge({ referer: path('easyadmin', _request_parameters)|url_encode }) %}
  22. {% set _has_batch_actions = batch_form is defined and batch_form.vars.batch_actions|length > 0 %}
  23. {% set _has_filters = _entity_config.list.filters|default(false) %}
  24. {% block body_id 'easyadmin-list-' ~ _entity_config.name %}
  25. {% block body_class 'list list-' ~ _entity_config.name|lower %}
  26. {% block content_title %}
  27.     {% apply spaceless %}
  28.         {% if 'search' == app.request.get('action') %}
  29.             {% set _default_title = 'search.page_title'|transchoice(paginator.nbResults, {}, 'EasyAdminBundle') %}
  30.             {{ (_entity_config.search.title is defined ? _entity_config.search.title|transchoice(paginator.nbResults) : _default_title)|raw }}
  31.         {% else %}
  32.             {% set _default_title = 'list.page_title'|trans(_trans_parameters, 'EasyAdminBundle') %}
  33.             {{ (_entity_config.list.title is defined ? _entity_config.list.title|trans(_trans_parameters) : _default_title)|raw }}
  34.         {% endif %}
  35.     {% endapply %}
  36. {% endblock %}
  37. {% block global_actions %}
  38.     {% if easyadmin_action_is_enabled_for_list_view('search', _entity_config.name) %}
  39.         {% set _action = easyadmin_get_action_for_list_view('search', _entity_config.name) %}
  40.         {% block search_action %}
  41.             <div class="form-action form-action-search {{ _action.css_class|default('') }}">
  42.                 <form method="get" action="{{ path('easyadmin') }}">
  43.                     {% block search_form %}
  44.                         <input type="hidden" name="action" value="search">
  45.                         <input type="hidden" name="entity" value="{{ _request_parameters.entity }}">
  46.                         {# if the Request query doesn't define 'sortField' and 'sortDirection', we add them in the
  47.                            controller (but they don't appear in the request URI). When users click on a column to
  48.                            sort results, 'sortField' and 'sortDirection' are added to the Request query and they
  49.                             appear in the request URI too. So, checking if 'sortField' and 'sortDirection' is the
  50.                             only way to differentiate between sorting made by user and sorting made by us. We only
  51.                             need to persist the sorting if it's explicitly made by the user. #}
  52.                         {% if 'sortField' in app.request.uri %}
  53.                             <input type="hidden" name="sortField" value="{{ _request_parameters.sortField }}">
  54.                         {% endif %}
  55.                         {% if 'sortDirection' in app.request.uri %}
  56.                             <input type="hidden" name="sortDirection" value="{{ _request_parameters.sortDirection }}">
  57.                         {% endif %}
  58.                         <input type="hidden" name="menuIndex" value="{{ _request_parameters.menuIndex }}">
  59.                         <input type="hidden" name="submenuIndex" value="{{ _request_parameters.submenuIndex }}">
  60.                         {% for name, value in _request_parameters.filters|easyadmin_form_hidden_params('filters') %}
  61.                         <input type="hidden" name="{{ name }}" value="{{ value }}">
  62.                         {% endfor %}
  63.                         <div class="form-group">
  64.                             <div class="form-widget">
  65.                                 <input class="form-control" type="search" name="query" value="{{ app.request.get('query')|default('') }}" placeholder="{{ _action.label|default('action.search')|trans(_trans_parameters) }}">
  66.                             </div>
  67.                         </div>
  68.                     {% endblock %}
  69.                 </form>
  70.             </div>
  71.         {% endblock search_action %}
  72.     {% endif %}
  73.     {% if _has_filters %}
  74.         {% set _applied_filters = app.request.attributes.get('easyadmin').filters.applied|default(false) %}
  75.         {% block filters_action %}
  76.             <div class="btn-group action-filters">
  77.                 <a href="{{ path('easyadmin', _request_parameters|merge({ action: 'filters', referer_action: app.request.get('action') })) }}" class="btn btn-secondary btn-labeled btn-labeled-right action-filters-button {{ _applied_filters ? 'action-filters-applied' }}" data-modal="#modal-filters">
  78.                     <i class="fa fa-filter fa-fw"></i> {{ 'filter.title'|trans(_trans_parameters, 'EasyAdminBundle') }}{% if _applied_filters %} <span class="text-primary">({{ _applied_filters|length }})</span>{% endif %}
  79.                 </a>
  80.                 {% if _applied_filters %}
  81.                     <a href="{{ path('easyadmin', _request_parameters|merge({ filters: null })) }}" class="btn btn-secondary action-filters-reset">
  82.                         <i class="fa fa-close"></i>
  83.                     </a>
  84.                 {% endif %}
  85.             </div>
  86.         {% endblock filters_action %}
  87.     {% endif %}
  88.     {% if easyadmin_action_is_enabled_for_list_view('new', _entity_config.name) %}
  89.         {% set _action = easyadmin_get_action_for_list_view('new', _entity_config.name) %}
  90.         {% block new_action %}
  91.             <div class="button-action">
  92.                 <a class="{{ _action.css_class|default('') }}" href="{{ path('easyadmin', _request_parameters|merge({ action: _action.name })) }}" target="{{ _action.target }}">
  93.                     {% if _action.icon %}<i class="fa fa-fw fa-{{ _action.icon }}"></i>{% endif %}
  94.                     {{ _action.label is defined and not _action.label is empty ? _action.label|trans(_trans_parameters) }}
  95.                 </a>
  96.             </div>
  97.         {% endblock new_action %}
  98.     {% endif %}
  99. {% endblock global_actions %}
  100. {% block batch_actions %}
  101.     {% if _has_batch_actions %}
  102.         <div class="batch-actions" style="display: none">
  103.             {% form_theme batch_form with easyadmin_config('design.form_theme') only %}
  104.             {{ form(batch_form) }}
  105.             {{ include('@EasyAdmin/default/includes/_batch_action_modal.html.twig', {
  106.                 _translation_domain: _entity_config.translation_domain,
  107.                 _trans_parameters: _trans_parameters,
  108.                 _entity_config: _entity_config,
  109.             }, with_context = false) }}
  110.         </div>
  111.     {% endif %}
  112. {% endblock batch_actions %}
  113. {% block content_header %}
  114.     {{ parent() }}
  115.     {{ block('batch_actions') }}
  116.     {% if _has_filters %}
  117.         {{ include('@EasyAdmin/default/includes/_filters_modal.html.twig') }}
  118.     {% endif %}
  119. {% endblock content_header %}
  120. {% block main %}
  121.     {% set _fields_visible_by_user = fields|filter((metadata, field) => easyadmin_is_granted(metadata.permission)) %}
  122.     {% set _number_of_hidden_results = 0 %}
  123.     {% set _list_item_actions = easyadmin_get_actions_for_list_item(_entity_config.name) %}
  124.     <table class="table datagrid">
  125.         <thead>
  126.         {% block table_head %}
  127.             <tr>
  128.                 {% if _has_batch_actions %}
  129.                     <th width="1px"><span><input type="checkbox" class="form-batch-checkbox-all"></span></th>
  130.                 {% endif %}
  131.                 {% for field, metadata in _fields_visible_by_user %}
  132.                     {% set isSortingField = (metadata.property == app.request.get('sortField')) or ('association' == metadata.type and app.request.get('sortField') starts with metadata.property ~ '.') %}
  133.                     {% set nextSortDirection = isSortingField ? (app.request.get('sortDirection') == 'DESC' ? 'ASC' : 'DESC') : 'DESC' %}
  134.                     {% set _column_label = metadata.label|trans(_trans_parameters) %}
  135.                     {% set _column_icon = isSortingField ? (nextSortDirection == 'DESC' ? 'fa-arrow-up' : 'fa-arrow-down') : 'fa-sort' %}
  136.                     <th class="{{ isSortingField ? 'sorted' }} {{ metadata.virtual ? 'virtual' }} {{ metadata.dataType|lower }} {{ metadata.css_class }}" {{ easyadmin_config('design.rtl') ? 'dir="rtl"' }}>
  137.                         {% if metadata.sortable %}
  138.                             <a href="{{ path('easyadmin', _request_parameters|merge({ page: 1, sortField: metadata.property, sortDirection: nextSortDirection })) }}">
  139.                                 {{ _column_label|raw }} <i class="fa fa-fw {{ _column_icon }}"></i>
  140.                             </a>
  141.                         {% else %}
  142.                             <span>{{ _column_label|raw }}</span>
  143.                         {% endif %}
  144.                     </th>
  145.                 {% endfor %}
  146.                 {% if _list_item_actions|length > 0 %}
  147.                     <th {% if _entity_config.list.collapse_actions %}width="10px"{% endif %} {{ easyadmin_config('design.rtl') ? 'dir="rtl"' }}>
  148.                         <span class="sr-only">{{ 'list.row_actions'|trans(_trans_parameters, 'EasyAdminBundle') }}</span>
  149.                     </th>
  150.                 {% endif %}
  151.             </tr>
  152.         {% endblock table_head %}
  153.         </thead>
  154.         <tbody>
  155.         {% block table_body %}
  156.             {% for item in paginator.currentPageResults %}
  157.                 {% if not easyadmin_is_granted(_entity_config.list.item_permission, item) %}
  158.                     {% set _number_of_hidden_results = _number_of_hidden_results + 1 %}
  159.                 {% else %}
  160.                     {# the empty string concatenation is needed when the primary key is an object (e.g. an Uuid object) #}
  161.                     {% set _item_id = '' ~ attribute(item, _entity_config.primary_key_field_name) %}
  162.                     <tr data-id="{{ _item_id }}">
  163.                         {% if _has_batch_actions %}
  164.                             <td><input type="checkbox" class="form-batch-checkbox" value="{{ _item_id }}"></td>
  165.                         {% endif %}
  166.                         {% for field, metadata in _fields_visible_by_user %}
  167.                             {% set isSortingField = metadata.property == app.request.get('sortField') %}
  168.                             {% set _column_label =  (metadata.label ?: field|humanize)|trans(_trans_parameters)  %}
  169.                             <td class="{{ isSortingField ? 'sorted' }} {{ metadata.dataType|lower }} {{ metadata.css_class }}" {{ easyadmin_config('design.rtl') ? 'dir="rtl"' }}>
  170.                                 {{ easyadmin_render_field_for_list_view(_entity_config.name, item, metadata) }}
  171.                             </td>
  172.                         {% endfor %}
  173.                         {% if _list_item_actions|length > 0 %}
  174.                             {% set _column_label =  'list.row_actions'|trans(_trans_parameters, 'EasyAdminBundle') %}
  175.                             <td class="actions">
  176.                                 {% block item_actions %}
  177.                                     {% set _actions_template = _entity_config.list.collapse_actions
  178.                                         ? '@EasyAdmin/default/includes/_actions_dropdown.html.twig'
  179.                                         : '@EasyAdmin/default/includes/_actions.html.twig'
  180.                                     %}
  181.                                     {{ include(_actions_template, {
  182.                                         actions: _list_item_actions,
  183.                                         entity_config: _entity_config,
  184.                                         request_parameters: _request_parameters,
  185.                                         translation_domain: _entity_config.translation_domain,
  186.                                         trans_parameters: _trans_parameters,
  187.                                         item_id: _item_id,
  188.                                         item: item
  189.                                     }, with_context = false) }}
  190.                                 {% endblock item_actions %}
  191.                             </td>
  192.                         {% endif %}
  193.                     </tr>
  194.                 {% endif %}
  195.             {% else %}
  196.                 <tr>
  197.                     <td class="no-results" colspan="{{ _fields_visible_by_user|length + 1 }}">
  198.                         {{ 'search.no_results'|trans(_trans_parameters, 'EasyAdminBundle') }}
  199.                     </td>
  200.                 </tr>
  201.             {% endfor %}
  202.             {% if _number_of_hidden_results > 0 %}
  203.                 <tr class="datagrid-row-empty">
  204.                     <td class="text-center" colspan="{{ _fields_visible_by_user|length + 1 }}">
  205.                         <span class="datagrid-row-empty-message"><i class="fa fa-lock mr-1"></i> {{ 'security.list.hidden_results'|trans({}, 'EasyAdminBundle') }}</span>
  206.                     </td>
  207.                 </tr>
  208.             {% endif %}
  209.         {% endblock table_body %}
  210.         </tbody>
  211.     </table>
  212.     {% block delete_form %}
  213.         {% set referer = paginator.currentPage == paginator.nbPages and 1 != paginator.currentPage and 1 == paginator.currentPageResults|length
  214.             ? path('easyadmin', app.request.query|merge({ page: app.request.query.get('page') - 1 }))
  215.             : app.request.requestUri
  216.         %}
  217.         {{ include('@EasyAdmin/default/includes/_delete_form.html.twig', {
  218.             view: 'list',
  219.             referer: referer,
  220.             delete_form: delete_form_template,
  221.             _translation_domain: _entity_config.translation_domain,
  222.             _trans_parameters: _trans_parameters,
  223.             _entity_config: _entity_config,
  224.         }, with_context = false) }}
  225.     {% endblock delete_form %}
  226. {% endblock main %}
  227. {% block content_footer %}
  228.     {% block paginator %}
  229.         {{ include(_entity_config.templates.paginator) }}
  230.     {% endblock paginator %}
  231. {% endblock %}
  232. {% block body_javascript %}
  233.     {{ parent() }}
  234.     <script type="text/javascript">
  235.         $(function() {
  236.             const toggles = document.querySelectorAll('.checkbox-switch input[type="checkbox"]');
  237.             for (i = 0; i < toggles.length; i++) {
  238.                 toggles[i].addEventListener('change', function () {
  239.                     const toggle = this;
  240.                     const newValue = this.checked;
  241.                     const oldValue = !newValue;
  242.                     const propertyName = this.closest('.checkbox-switch').dataset.propertyname;
  243.                     const toggleUrl = "{{ path('easyadmin', { action: 'edit', entity: _entity_config.name, view: 'list' })|raw }}"
  244.                         + "&id=" + this.closest('tr').dataset.id
  245.                         + "&property=" + propertyName
  246.                         + "&newValue=" + newValue.toString();
  247.                     let toggleRequest = $.ajax({ type: "GET", url: toggleUrl, data: {} });
  248.                     toggleRequest.done(function(result) {});
  249.                     toggleRequest.fail(function() {
  250.                         // in case of error, restore the original value and disable the toggle
  251.                         toggle.checked = oldValue;
  252.                         toggle.disabled = true;
  253.                         toggle.closest('.checkbox-switch').classList.add('disabled');
  254.                     });
  255.                 });
  256.             }
  257.             $('.action-delete').on('click', function(e) {
  258.                 e.preventDefault();
  259.                 const id = $(this).parents('tr').first().data('id');
  260.                 $('#modal-delete').modal({ backdrop: true, keyboard: true })
  261.                     .off('click', '#modal-delete-button')
  262.                     .on('click', '#modal-delete-button', function () {
  263.                         let deleteForm = $('#delete-form');
  264.                         deleteForm.attr('action', deleteForm.attr('action').replace('__id__', id));
  265.                         deleteForm.trigger('submit');
  266.                     });
  267.             });
  268.             {% if _has_filters %}
  269.             // HTML5 specifies that a <script> tag inserted with innerHTML should not execute
  270.             // https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML#Security_considerations
  271.             // That's why we can't use just 'innerHTML'. See https://stackoverflow.com/a/47614491/2804294
  272.             var setInnerHTML = function(element, htmlContent) {
  273.                 element.innerHTML = htmlContent;
  274.                 Array.from(element.querySelectorAll('script')).forEach( oldScript => {
  275.                     const newScript = document.createElement('script');
  276.                     Array.from(oldScript.attributes)
  277.                         .forEach(attr => newScript.setAttribute(attr.name, attr.value));
  278.                     newScript.appendChild(document.createTextNode(oldScript.innerHTML));
  279.                     oldScript.parentNode.replaceChild(newScript, oldScript);
  280.                 });
  281.             };
  282.             document.querySelector('.action-filters-button').addEventListener('click', function(event) {
  283.                 let filterButton = event.currentTarget;
  284.                 let filterModal = document.querySelector(filterButton.dataset.modal);
  285.                 let filterModalBody = filterModal.querySelector('.modal-body');
  286.                 $(filterModal).modal({ backdrop: true, keyboard: true });
  287.                 filterModalBody.innerHTML = '<div class="fa-3x px-3 py-3 text-muted text-center"><i class="fas fa-circle-notch fa-spin"></i></div>';
  288.                 $.get(filterButton.getAttribute('href'), function (response) {
  289.                     setInnerHTML(filterModalBody, response);
  290.                 });
  291.                 event.preventDefault();
  292.                 event.stopPropagation();
  293.             });
  294.             {% endif %}
  295.             {% if _has_batch_actions %}
  296.             const titleContent = $('.content-header-title > .title').html();
  297.             $(document).on('click', '.deselect-batch-button', function () {
  298.                 $(this).closest('.content').find(':checkbox.form-batch-checkbox-all').prop('checked', false).trigger('change');
  299.             });
  300.             $(document).on('change', '.form-batch-checkbox-all', function () {
  301.                 $(this).closest('.content').find(':checkbox.form-batch-checkbox').prop('checked', $(this).prop('checked')).trigger('change');
  302.             });
  303.             $(document).on('change', '.form-batch-checkbox', function () {
  304.                 const $content = $(this).closest('.content');
  305.                 let $input = $content.find(':hidden#batch_form_ids');
  306.                 let ids = $input.val() ? $input.val().split(',') : [];
  307.                 const id = $(this).val();
  308.                 if ($(this).prop('checked')) {
  309.                     if (-1 === ids.indexOf(id)) {
  310.                         ids.push(id);
  311.                     }
  312.                 } else {
  313.                     ids = ids.filter(function(value) { return value !== id });
  314.                     $content.find(':checkbox.form-batch-checkbox-all').prop('checked', false);
  315.                 }
  316.                 if (0 === ids.length) {
  317.                     $content.find('.global-actions').show();
  318.                     $content.find('.batch-actions').hide();
  319.                     $content.find('table').removeClass('table-batch');
  320.                 } else {
  321.                     $content.find('.batch-actions').show();
  322.                     $content.find('.global-actions').hide();
  323.                     $content.find('table').addClass('table-batch');
  324.                 }
  325.                 $input.val(ids.join(','));
  326.                 $content.find('.content-header-title > .title').html(0 === ids.length ? titleContent : '');
  327.             });
  328.             let modalTitle = $('#batch-action-confirmation-title');
  329.             const titleContentWithPlaceholders = modalTitle.text();
  330.             $('button[name="batch_form[name]"].batch-action-requires-confirmation').on('click', function (event) {
  331.                 event.preventDefault();
  332.                 event.stopPropagation();
  333.                 let $button = $(this);
  334.                 const actionName = $button.text();
  335.                 const numberOfSelectedItems = $('input[type="checkbox"].form-batch-checkbox:checked').length;
  336.                 modalTitle.text(titleContentWithPlaceholders
  337.                     .replace('%action_name%', actionName)
  338.                     .replace('%num_items%', numberOfSelectedItems));
  339.                 $('#modal-batch-action').modal({ backdrop : true, keyboard : true })
  340.                     .off('click', '#modal-batch-action-button')
  341.                     .on('click', '#modal-batch-action-button', function () {
  342.                         $button.unbind('click');
  343.                         $button.trigger('click');
  344.                         modalTitle.text(titleContentWithPlaceholders);
  345.                     });
  346.             });
  347.             {% endif %}
  348.         });
  349.     </script>
  350.     {% if 'search' == app.request.get('action') %}
  351.         <script type="text/javascript">
  352.             const _search_query = "{{ app.request.get('query')|default('')|e('js') }}";
  353.             // the original query is prepended to allow matching exact phrases in addition to single words
  354.             $('#main').find('table tbody td:not(.actions)').highlight($.merge([_search_query], _search_query.split(' ')));
  355.         </script>
  356.     {% endif %}
  357. {% endblock %}