Using ajax with sonata admin list view pagination - sonata-admin

I'm using sonata admin in my project. In the list view sonata refresh the page when I clik to get the second list from pagination. That's the default behavior of sonata.Is there any way to use ajax call with list view pagination !!!
Same question when using sortable list view.
Thanks.

This is possible, but you have to overwrite some of the basic functionality of the sonata admin bundle.
Tested with
symfony 2.6.6
sonata admin-bundle dev-master (4f23e1a30e49681bf8ebdbbae549848784be7699)
1. Edit your bundles services.yml
You have to implement your own CrudController and a new list template. Tell this in services.yml
sonata.admin.youradmin:
class: Your\Bundle\Admin\YourAdmin
tags:
- { name: sonata.admin, manager_type: orm, group: "groupname", label: "grouplabel" }
arguments:
- ~
- Your\Bundle\Entity\EntityClass
- YourBundle:YourNewCrud # <- add your crud class here
calls:
- [ setLabelTranslatorStrategy, ["#sonata.admin.label.strategy.underscore"]]
- [ setTemplate, [list, YourBundle:YourAdmin:list.html.twig]] # <- tell sonata you want this template for list
2. Add new twig template
Now add the following template to your bundles Resources/views/YourAdmin/list.html.twig:
{# extends from the normal base_list.html.twig, but when this template is
rendered via an ajax-call, extend from an ajax-list.html.twig, we'll
add later #}
{% extends app.request.xmlHttpRequest
? 'YourBundle:ajax-list.html.twig'
: 'SonataAdminBundle:CRUD:base_list.html.twig' %}
{# YourBundle:ajax-list.html.twig is the path where you have your template, if your template is inside views/Admin, then put it like this: YourBundle:Admin:ajax-list.html.twig #}
{# Here I copied the list_table block from the original list.html.twig
from sonata.. I added custom JS-code below to do the ajax call #}
{% block list_table %}
{# The code in this block is copied from sonata.. I just added the JS-code below, and added the 'actionList' id here #}
<div id="actionList" class="col-xs-12 col-md-12">
{% if admin.hasRoute('batch') %}
<form action="{{ admin.generateUrl('batch', {'filter': admin.filterParameters}) }}" method="POST" >
<input type="hidden" name="_sonata_csrf_token" value="{{ csrf_token }}">
{% endif %}
{# Add a margin if no pager to prevent dropdown cropping on window #}
<div class="box box-primary" {% if admin.datagrid.pager.lastPage == 1 %}style="margin-bottom: 100px;"{% endif %}>
<div class="box-body {% if admin.datagrid.results|length > 0 %}table-responsive no-padding{% endif %}">
{{ sonata_block_render_event('sonata.admin.list.table.top', { 'admin': admin }) }}
{% block list_header %}{% endblock %}
{% set batchactions = admin.batchactions %}
{% if admin.datagrid.results|length > 0 %}
<table class="table table-bordered table-striped sonata-ba-list">
{% block table_header %}
<thead>
<tr class="sonata-ba-list-field-header">
{% for field_description in admin.list.elements %}
{% if admin.hasRoute('batch') and field_description.getOption('code') == '_batch' and batchactions|length > 0 %}
<th class="sonata-ba-list-field-header sonata-ba-list-field-header-batch">
<input type="checkbox" id="list_batch_checkbox">
</th>
{% elseif field_description.getOption('code') == '_select' %}
<th class="sonata-ba-list-field-header sonata-ba-list-field-header-select"></th>
{% elseif field_description.name == '_action' and app.request.isXmlHttpRequest %}
{# Action buttons disabled in ajax view! #}
{% elseif field_description.getOption('ajax_hidden') == true and app.request.isXmlHttpRequest %}
{# Disable fields with 'ajax_hidden' option set to true #}
{% else %}
{% set sortable = false %}
{% if field_description.options.sortable is defined and field_description.options.sortable %}
{% set sortable = true %}
{% set sort_parameters = admin.modelmanager.sortparameters(field_description, admin.datagrid) %}
{% set current = admin.datagrid.values._sort_by == field_description or admin.datagrid.values._sort_by.fieldName == sort_parameters.filter._sort_by %}
{% set sort_active_class = current ? 'sonata-ba-list-field-order-active' : '' %}
{% set sort_by = current ? admin.datagrid.values._sort_order : field_description.options._sort_order %}
{% endif %}
{% spaceless %}
<th class="sonata-ba-list-field-header-{{ field_description.type}} {% if sortable %} sonata-ba-list-field-header-order-{{ sort_by|lower }} {{ sort_active_class }}{% endif %}">
{% if sortable %}<a href="{{ admin.generateUrl('list', sort_parameters) }}">{% endif %}
{{ admin.trans(field_description.label, {}, field_description.translationDomain) }}
{% if sortable %}</a>{% endif %}
</th>
{% endspaceless %}
{% endif %}
{% endfor %}
</tr>
</thead>
{% endblock %}
{% block table_body %}
<tbody>
{% include admin.getTemplate('outer_list_rows_' ~ admin.getListMode()) %}
</tbody>
{% endblock %}
{% block table_footer %}
{% endblock %}
</table>
{% else %}
<div class="info-box">
<span class="info-box-icon bg-aqua"><i class="fa fa-arrow-circle-right"></i></span>
<div class="info-box-content">
<span class="info-box-text">{{ 'no_result'|trans({}, 'SonataAdminBundle') }}</span>
<div class="progress">
<div class="progress-bar" style="width: 0%"></div>
</div>
<span class="progress-description">
{% if not app.request.xmlHttpRequest %}
{% include 'SonataAdminBundle:Button:create_button.html.twig' %}
{% endif %}
</span>
</div><!-- /.info-box-content -->
</div>
{% endif %}
{{ sonata_block_render_event('sonata.admin.list.table.bottom', { 'admin': admin }) }}
</div>
{% block list_footer %}
{% if admin.datagrid.results|length > 0 %}
<div class="box-footer">
<div class="form-inline clearfix">
{% if not app.request.isXmlHttpRequest %}
<div class="pull-left">
{% if admin.hasRoute('batch') and batchactions|length > 0 %}
{% block batch %}
<script>
{% block batch_javascript %}
jQuery(document).ready(function ($) {
$('#list_batch_checkbox').on('ifChanged', function () {
$(this)
.closest('table')
.find('td.sonata-ba-list-field-batch input[type="checkbox"], div.sonata-ba-list-field-batch input[type="checkbox"]')
.iCheck($(this).is(':checked') ? 'check' : 'uncheck')
;
});
$('td.sonata-ba-list-field-batch input[type="checkbox"], div.sonata-ba-list-field-batch input[type="checkbox"]')
.on('ifChanged', function () {
$(this)
.closest('tr, div.sonata-ba-list-field-batch')
.toggleClass('sonata-ba-list-row-selected', $(this).is(':checked'))
;
})
.trigger('ifChanged')
;
});
{% endblock %}
</script>
{% block batch_actions %}
<label class="checkbox" for="{{ admin.uniqid }}_all_elements">
<input type="checkbox" name="all_elements" id="{{ admin.uniqid }}_all_elements">
{{ 'all_elements'|trans({}, 'SonataAdminBundle') }}
({{ admin.datagrid.pager.nbresults }})
</label>
<select name="action" style="width: auto; height: auto" class="form-control">
{% for action, options in batchactions %}
<option value="{{ action }}">{{ options.label }}</option>
{% endfor %}
</select>
{% endblock %}
<input type="submit" class="btn btn-small btn-primary" value="{{ 'btn_batch'|trans({}, 'SonataAdminBundle') }}">
{% endblock %}
{% endif %}
</div>
<div class="pull-right">
{% if admin.hasRoute('export') and admin.isGranted("EXPORT") and admin.getExportFormats()|length %}
<div class="btn-group">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
<i class="glyphicon glyphicon-export"></i>
{{ "label_export_download"|trans({}, "SonataAdminBundle") }}
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
{% for format in admin.getExportFormats() %}
<li>
<a href="{{ admin.generateUrl('export', admin.modelmanager.paginationparameters(admin.datagrid, 0) + {'format' : format}) }}">
<i class="glyphicon glyphicon-download"></i>
{{ format|upper }}
</a>
<li>
{% endfor %}
</ul>
</div>
-
{% endif %}
{% block pager_results %}
{% include admin.getTemplate('pager_results') %}
{% endblock %}
</div>
{% endif %}
</div>
{% block pager_links %}
{% if admin.datagrid.pager.haveToPaginate() %}
<hr/>
{% include admin.getTemplate('pager_links') %}
{% endif %}
{% endblock %}
</div>
{% endif %}
{% endblock %}
</div>
{% if admin.hasRoute('batch') %}
</form>
{% endif %}
</div>
{# When this request is not via ajax, add the following JS-code
this will trigger an ajax-request, and uses the normal a-href location #}
{% if not app.request.xmlHttpRequest %}
<script>
$('body').on('click', '.pagination li a', function(e){
var url = $(this).attr('href');
$.post(url,
{},
function(response){
if(response.code == 100 && response.success){
$('#actionList').replaceWith(response.content);
}
}
);
e.preventDefault();
return false;
});
</script>
{% endif %}
{% endblock %}
3. Add ajax template
We do this to prevent the original bundle to output everything we don't need:
YourBundle/Resources/views/YourAdmin/ajax-list.html.twig:
{% block list_table %}{% endblock %}
4. Add custom CrudController
Now the only thing left to do is, that we want to overwrite the original listAction so that we can return JSON in stead of pure HTML. Add YourCrudController.php in YourBundle/Controller/
Add the following
<?php
namespace Your\Bundle\Controller;
use Sonata\AdminBundle\Controller\CRUDController as Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
class YourCrudController extends Controller
{
public function listAction(Request $request = null)
{
// call original listAction function to get the right HTML
$response = parent::listAction($request);
if($request->isXmlHttpRequest()) {
// disable the profile, to prevent profiler code
$this->container->get('profiler')->disable();
// return JsonResponse
return new JsonResponse(array('success' => true, 'code' => 100, 'content' => $response->getContent()));
}
return $response;
}
}
Sit back, hit f5, click the pager and enjoy!

Related

How do I make my list code run vertically instead of horizontal in Shopify?

I have created a vendor page for my Shopify store, and it seems to be running fine, however, now I want it to run the list alphabetically across 4 columns in a vertical setup. Currently, the list runs horizontally across the columns. Could someone please look at my code and see if this is possible?
{% assign counter = 0 %}
{% for vendor in shop.vendors %}
{% assign counter = counter | plus: 1 %}
{% endfor %}
{% assign counter_divided_by_3 = counter | divided_by: 3 | floor %}
<section class="section inner-page-section designer-section" id="section-{{ section.id }}" data-section-id="{{ section.id }}" data-section-type="vendor-list">
<div class="container">
{% if section.settings.enable_headline %}
<div class="row">
<div class="col-sm-12">
<h1 class="page-title text-center">{{ section.settings.headline | upcase }}</h1>
</div>
</div>
{% endif %}
<div class="desktop">
<div class="row vendor-row">
{%- for product_vendor in shop.vendors -%}
<ul class="col-sm-3 col-xs-12 designers-list">
{% assign its_a_match = false %}
{% capture my_collection_handle %} {{ product_vendor | handleize | strip | escape }} {% endcapture %}
{% assign my_collection_handle_stripped = my_collection_handle | strip | escape %}
{% for collection in collections %}
{% if my_collection_handle_stripped == collection.handle %}
{% assign its_a_match = true %}
{% endif %}
{% endfor %}
{% if its_a_match %}
<li class="">{{ product_vendor | upcase }}</li>
{% else %}
<li class="">{{ product_vendor | link_to_vendor | upcase }}</li>
{% endif %}
</ul>
{%- endfor -%}
</div>
</div>
</div>
</section>

Add link in TableBlock cell

I'm using a TableBlock in a StreamField. Rendering the page, including the table, is fine. But is there anyway to allow the user to enter a link into a table cell? Simply adding a URL has it rendered as text (as I would expect).
Does this require a custom renderer?
Our content team had asked for this feature as well. However, it is not supported in the underlying library that TableBlock uses. We ended up creating a custom StreamField type to display lists of links, rather than trying to kluge links into TableBlock.
I got this problem as well. I know this problem it is been posted a long time ago but I thought to share my solution anyway. I was giving up but then I tried to implement markdown instead as FlipperPA mentioned. And I realised that after installing wagtail-markdown (please follow the instructions), I could tweak my template like this:
<!-- added this at the top of my template -->
{% load wagtailmarkdown %}
....
....
<!-- then in the table replace the word `linebreaksbr` with the word `markdown` -->
<table
class="info-list table table-responsive">
{% if value.table.table_header %}
<thead>
<tr>
{% for column in value.table.table_header %}
{% with forloop.counter0 as col_index %}
<th scope="col" {% cell_classname 0 col_index %}>
{% if column.strip %}
{% if html_renderer %}
{{ column.strip|safe|markdown }} <-- HERE it was {{ column.strip|safe|linebreaksbr }} -->
{% else %}
{{ column.strip|markdown }} <-- HERE it was {{ column.strip|linebreaksbr }} -->
{% endif %}
{% endif %}
</th>
{% endwith %}
{% endfor %}
</tr>
</thead>
{% endif %}
<tbody>
{% for row in value.table.data %}
{% with forloop.counter0 as row_index %}
<tr>
{% for column in row %}
{% with forloop.counter0 as col_index %}
{% if first_col_is_header and forloop.first %}
<th scope="row"
{% cell_classname row_index col_index value.table.table_header %}>
{% if column.strip %}
{% if html_renderer %}
{{ column.strip|safe|markdown }} <-- HERE it was {{ column.strip|safe|linebreaksbr }} -->
{% else %}
{{ column.strip|markdown }} <-- HERE it was {{ column.strip|linebreaksbr }} -->
{% endif %}
{% endif %}
</th>
{% else %}
<td {% cell_classname row_index col_index value.table.table_header %}>
{% if column.strip %}
{% if html_renderer %}
{{ column.strip|safe|markdown }} <-- HERE it was {{ column.strip|safe|linebreaksbr }} -->
{% else %}
{% else %}
{{ column.strip|markdown }} <-- HERE it was {{ column.strip|linebreaksbr }} -->
{% endif %}
{% endif %}
</td>
{% endif %}
{% endwith %}
{% endfor %}
</tr>
{% endwith %}
{% endfor %}
</tbody>
</table>
And it will render your TableBlock in html. I hope this would help in the future.
Actually, it is pretty easy if you are OK with fact that user has to paste whole HTML of the link, not only href value.
You just need to pass dict with custom rendered option in table_options kwarg of TableBlock. It should look something like this:
TableBlock(table_options={'renderer': 'html'})
Check the docs:
Wagtail and Handsontable
Tested on Wagtail 2.16

How to assign values to an array using HubSpot's HubL

I'm currently trying to figure out how to allow a content manager to change the order in which elements appear within recent post link lists. Essentially, the concept involves them picking the index in a sorting array that will hold that element's type as a string, which will later be iterated over with output logic determining where each piece gets placed. Unfortunately, one of the drawbacks of using HubL is the lack of debugging or error reporting so I can't see where my syntax is failing. I was wondering if anyone in InternetLand has had any similar experience with this.
Here's the HubL:
<div class="theme-recent-posts">
{% if widget.title_name %}
<div class="theme-recent-posts-title">
<{{ widget.title_tag }}><span>{{ widget.title_name }}</span></{{ widget.title_tag }}>
</div>
{% endif %}
{% set posts = blog_recent_posts(widget.select_blog, widget.max_links) %}
{% for post in posts %}
{% set item_objects = [] %}
{% if widget.show_images == "true" %}
{% set item_objects[widget.sort_images] = "image" %}
{% endif %}
{% if widget.show_titles == "true" %}
{% set item_objects[widget.sort_titles] = "title" %}
{% endif %}
{% if widget.show_dates == "true" %}
{% set item_objects[widget.sort_dates] = "date" %}
{% endif %}
{% if widget.show_authors == "true" %}
{% set item_objects[widget.sort_authors] = "author" %}
{% endif %}
<div class="theme-recent-posts-item">
<a href="{{ post.absolute_url }}">
{% for object in item_objects %}
{% if object == "image" %}
<span class="theme-recent-posts-item-image"><img src="{{ post.featured_image }}" alt="{{ post.name }}" /></span>
{% endif %}
{% if object == "title" %}
<span class="theme-recent-posts-item-title">{{ post.name }}</span>
{% endif %}
{% if object == "date" %}
<span class="theme-recent-posts-item-date">{{ post.created }}</span>
{% endif %}
{% if object == "author" %}
<span class="theme-recent-posts-item-author">{{ post.author_name }}</span>
{% endif %}
{% endfor %}
</a>
</div>
{% endfor %}
</div>
Output currently results in:
<div class="theme-recent-posts">
<div class="theme-recent-posts-title">
<h2><span>Recent Posts</span></h2>
</div>
<div class="theme-recent-posts-item">
</div>
<div class="theme-recent-posts-item">
</div>
<div class="theme-recent-posts-item">
</div>
</div>
Assume the default values wired up in the widget panel:
widget.show_images = true
widget.show_images = true
widget.show_images = true
widget.show_images = true
widget.sort_images = 0
widget.sort_titles = 1
widget.sort_dates = 2
widget.sort_authors = 3
The output should essentially be this:
<div class="theme-recent-posts">
<div class="theme-recent-posts-title">
<h2><span>Recent Posts</span></h2>
</div>
<div class="theme-recent-posts-item">
<a href="//website/path/post-name-3">
<span class="theme-recent-posts-item-image"><img src="path/to/image-3.ext" alt="Post Name 3" /></span>
<span class="theme-recent-posts-item-title">Post Name 3</span>
<span class="theme-recent-posts-item-date">1234567890</span>
<span class="theme-recent-posts-item-author">FirstName LastName</span>
</a>
</div>
<div class="theme-recent-posts-item">
<a href="//website/path/post-name-2">
<span class="theme-recent-posts-item-image"><img src="path/to/image-2.ext" alt="Post Name 2" /></span>
<span class="theme-recent-posts-item-title">Post Name 2</span>
<span class="theme-recent-posts-item-date">1234567890</span>
<span class="theme-recent-posts-item-author">FirstName LastName</span>
</a>
</div>
<div class="theme-recent-posts-item">
<a href="//website/path/post-name-1">
<span class="theme-recent-posts-item-image"><img src="path/to/image-1.ext" alt="Post Name 1" /></span>
<span class="theme-recent-posts-item-title">Post Name 1</span>
<span class="theme-recent-posts-item-date">1234567890</span>
<span class="theme-recent-posts-item-author">FirstName LastName</span>
</a>
</div>
</div>
I'm going to tag Jinja in this post, because HubL is based on Jinja, though it's not a direct translation.
If someone with enough reputation is reading this, I would greatly appreciate the addition of the HubL language tag, as it currently does not exist to select (even though HubSpot was).
Update
Twig is also a close cousin to HubL, and so I looked up how to update elements in an array there, and came up with this answer:
Setting element of array from Twig
{% set item_objects = item_objects|merge({widget.sort_images:"image"}) %}
Implementation did not work as expected. Output remains unchanged.
While this is not an answer to the question I asked, it is an alternate solution, so I provide it as a work around until a real solution is determined.
<div class="theme-recent-posts">
{% if widget.title_name %}
<div class="theme-recent-posts-title">
<{{ widget.title_tag }}><span>{{ widget.title_name }}</span></{{ widget.title_tag }}>
</div>
{% endif %}
{% set object_order = [0, 1, 2, 3] %}
{% set posts = blog_recent_posts(widget.select_blog, widget.max_links) %}
{% for post in posts %}
<div class="theme-recent-posts-item">
<a href="{{ post.absolute_url }}">
{% for order in object_order %}
{% if widget.show_images == "true" and widget.sort_images == order %}
<span class="theme-recent-posts-item-image"><img src="{{ post.featured_image }}" alt="{{ post.name }}" /></span>
{% endif %}
{% if widget.show_titles == "true" and widget.sort_titles == order %}
<span class="theme-recent-posts-item-title">{{ post.name }}</span>
{% endif %}
{% if widget.show_dates == "true" and widget.sort_dates == order %}
<span class="theme-recent-posts-item-date">{{ post.created }}</span>
{% endif %}
{% if widget.show_authors == "true" and widget.sort_authors == order %}
<span class="theme-recent-posts-item-author">{{ post.author_name }}</span>
{% endif %}
{% endfor %}
</a>
</div>
{% endfor %}
</div>
This gives me the flexibility I was looking for and the code is actually lightened quite a bit from the original proposed method. The idea is to use the object_order list as a mask for the for order in object_order loop. This gets the job done, and while I know it's hacky, it has a touch of elegance to it.

Add div after every 3d element twig loop, modulus

Hi so i have this twig loop, and what i need to to is after every 3rd element to add some html to close div row and open new one.
I tried with various snippets from this website but no luck with any one of them
<div class="row">
{% for date, date_info in dates %}
<div class="col-sm-4">
<div class="event-box">
{% for category in date_info.events %}
{% for event in category %}
<div class="event-header">
{% if event.get_runtime( 'content_img_url' ) is empty %}
{{ event | avatar( [
'post_thumbnail',
'location_avatar',
'category_avatar'
]) | raw }}
{% endif %}
<div class="event-date">
{{ date | month }}
{{ date | day }}
</div>
<div class="event-footer">
<h3>{{ event.get_runtime( 'filtered_title' ) | raw }}</h3>
<div class="event-time">
{{ event | timespan( 'short' ) | raw }}...
</div>
<div class="event-content"> {{ event.get_runtime( 'filtered_content' ) | slice(0,200) | raw }}</div>
<a class="btn btn-success" href="{{ event.get_runtime( 'instance_permalink' ) | e('html_attr') }}">
{{ text_read_more }}
</a>
</div>
</div>
{% endfor %} {# event in category #}
{% endfor %} {# category in date_info.events #}
</div>
</div>
{% endfor %} {# date, date_info in dates #}
</div>
You can use the batch filter instead (New in 1.12.3).
{% for date, date_info in dates|batch(3) %}
.....
.....
{% endfor %}
I think this is better solution:
{% for date, date_info in dates %}
{% if loop.first %}
<div class="row"> {# open row #}
{% endif %}
{# your code here #}
{% if loop.index % 3 == 0 %}
</div><div class="row"> {# after 3 iterates close row and open new #}
{% endif %}
{% if loop.last %}
</div> {# close last row #}
{% endif %}
{% endfor %}
Got it working with {% if loop.index is divisible by(3) and not loop.last %}.
Here is my full working code example :
<div class="row">
{% for date, date_info in dates %}
<div class="col-sm-4">
<div class="event-box">
{% for category in date_info.events %}
{% for event in category %}
<div class="event-header">
{% if event.get_runtime( 'content_img_url' ) is empty %}
{{ event | avatar( [
'post_thumbnail',
'location_avatar',
'category_avatar'
]) | raw }}
{% endif %}
<div class="event-date">
{{ date | month }}
{{ date | day }}
</div>
<div class="event-footer">
<h3>{{ event.get_runtime( 'filtered_title' ) | raw }}</h3>
<div class="event-time">
{{ event | timespan( 'short' ) | raw }}...
</div>
<div class="event-content"> {{ event.get_runtime( 'filtered_content' ) | slice(0,200) | raw }}</div>
<a class="btn btn-success"
href="{{ event.get_runtime( 'instance_permalink' ) | e('html_attr') }}">
{{ text_read_more }}
</a>
</div>
</div>
{% endfor %} {# event in category #}
{% endfor %} {# category in date_info.events #}
</div>
</div>
{% if loop.index is divisibleby(3) and not loop.last %}
</div>
<div class="row">
{% endif %}
{% endfor %} {# date, date_info in dates #}
</div>
In reading this doc : http://twig.sensiolabs.org/doc/tags/for.html#the-loop-variable
I'll try this kind of code :
{% for date, date_info in dates %}
{% if loop.index % 3 == 0 %}
{# put your code to close your div #}
{% endif %}
{% endfor %}

Overriding app engine template block inside an if

I have a block 'left_area' defined in a base appengine template
{% block left_area %}
<div class="span3">
Left area content
</div>
{% endblock %}
In a child template, I want to override this block inside an if
{% if not user %}
{% block left_area %}
<div class="span2">
</div>
{% endblock %}
{% endif %}
This is not working for some reason. Any suggestion?
In jinja you can solve it with super(), which renders the parent block:
{% block left_area %}
{% if not user %}
<div class="span2">
</div>
{% else %}
{{ super() }}
{% endif %}
{% endblock %}

Resources