Add link in TableBlock cell - wagtail

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

Related

Shopify loop - Skipping a product variant if variant has no image

I need to do two things; add products with extra colours to the collections loop (which I've done below, via a snippet elsewhere), however I need to skip when the variant doesn't have its own image which I've yet to figure out.
The break I attempted is in the preview code:
{% for option in product.options %}
{% if option == 'Colour' %}
{% assign index = forloop.index0 %}
{% assign colourlist = '' %}
{% assign colour = '' %}
{% for variant in product.variants %}
{% capture colour %}
{{ variant.options[index] }}
{% endcapture %}
{% if variant.image.src %}
{% break %}
{% endif %}
{% unless colourlist contains colour %}
{% include 'product-grid-item' %}
{% capture tempList %}
{{colourlist | append: colour | append: " " }}
{% endcapture %}
{% assign colourlist = tempList %}
{% endunless %}
{% endfor %}
{% endif %}
{% else %}
<div class="grid-item">
<p>{{ 'collections.results.no_products' | t }}</p>
</div>
{% endfor %}
{% endfor %}
The keyword you're looking for, to skip the current iteration of the loop and move to the next, is {% continue %}
For example:
{% for variant in product.variants %}
{% if variant.featured_image == blank %}
{% continue %}
{% endif %}
<!-- HTML STUFF -->
{% endfor %}

How to Display key labels and values in Jekyll for loop data that has been grouped

I got the following to work:
{% assign cars = site.data.inventory %}
{% for item in cars %}
{{item}}
{% endfor %}
The result looks like a jumble of key pairs.
{"brand"=>"Toyota", "model"=>"Celica"}
etc.
All good. Then I threw it a curve ball.
{% assign cars = site.data.inventory | group_by:"model" %}
Now the result looks different, and that makes sense, but it's causing confusion.
{"name"=>"Celica","items"=>[{"brand"=>"Toyota", "model"=>"Celica"}], "size"=>1}
Where this becomes a challenge:
I want to iterate through the "items" in the 2nd result, pulling only the key labels. If I remove the grouping, I can do this:
{% for e in cars %}
{% if forloop.first == true %}
{% for item in e %}
{{item[0]}}
{% endfor %}
{% endif %}
{% for item in e %}
{{item[1]}}
{% endfor %}
{% endfor %}
Works like a charm. The first result is the label, the remaining results show the data. A simple way to make a column header.
However, if I add the group_by in the assign, my {{item[0]}} becomes "name", "items", "size", instead of "brand", "model" where items becomes the entire list {{item}} from the first example.
How do I iterate through the values in bold?
[{"brand"=>"Toyota", "model"=>"Celica"}] while they are grouped?
I think that's what you're trying to do :
{% assign models = site.data.inventory | group_by:"model" %}
<table>
{% for model in models %}
{% if forloop.first == true %}
<tr>
{% for item in model.items.first %}
<th>{{ item[0] }}</th>
{% endfor %}
</tr>
{% endif %}
{% for car in model.items %}
<tr>
{% for field in car %}
<td>{{ field[1] }}</td>
{% endfor %}
</tr>
{% endfor %}
{% endfor %}
</table>

Proper way to fill an html array using django/python

I encounter a small issue about filling my html array in a django template.
I wanted to print one form to one row. Instead of one row to all forms ( that's what i get with the sample code just right there ) :
<thead> <!-- cétait td à la place de th -->
<tr>
{% for entry in headers %}
<th>
{{ entry }}
</th>
{% endfor %}
</tr>
</thead>
<tbody>
<tr>
{%for entry in entries%}
{%for cle, valeur in entry.all_form_entry_json%}
{%for key, valor in headersLoop%}
{%if key == cle%}
<td> {{valeur}}</td>
{% endif %}
{% endif %}
{% endfor %}
{% endfor %}
{% endfor %}
</tr>
</tbody>
The result is such as follow :
You can notice that all the datas are printed to the right corner and go beyond the array limit.
How can i have a result like this ?
Is this possible to do it only with html ? Or should i create a js or css file and import it to this template.html ?
Thank you for your answers !
I think you just wrong place for the html <tr> tag,
the tag <tr> should inside {% for loop..
<thead>
<tr>
{% for entry in headers %}
<th>{{ entry }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for entry in entries %}
<tr>
{% for cle, valeur in entry.all_form_entry_json %}
{% for key, valor in headersLoop %}
{% if key == cle %}
<td>{{ valeur }}</td>
{% endif %}
{% endfor %}
{% endfor %}
</tr>
{% endfor %}
</tbody>

Using ajax with sonata admin list view pagination

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!

How to render a tree in Twig

I would like to render a tree with an undetermined depth (children of children of children, etc.). I need to loop through the array recursively; how can I do this in Twig?
I played around with domi27's idea and came up with this. I made a nested array as my tree, ['link']['sublinks'] is null or another array of more of the same.
Templates
The sub-template file to recurse with:
<!--includes/menu-links.html-->
{% for link in links %}
<li>
{{ link.name }}
{% if link.sublinks %}
<ul>
{% include "includes/menu-links.html" with {'links': link.sublinks} %}
</ul>
{% endif %}
</li>
{% endfor %}
Then in the main template, call this (kind of redundant 'with' stuff there):
<ul class="main-menu">
{% include "includes/menu-links.html" with {'links':links} only %}
</ul>
Macros
A similar effect can be achieved with macros:
<!--macros/menu-macros.html-->
{% macro menu_links(links) %}
{% for link in links %}
<li>
{{ link.name }}
{% if link.sublinks %}
<ul>
{{ _self.menu_links(link.sublinks) }}
</ul>
{% endif %}
</li>
{% endfor %}
{% endmacro %}
In the main template, do this:
{% import "macros/menu-macros.html" as macros %}
<ul class="main-menu">
{{ macros.menu_links(links) }}
</ul>
Twig 2.0 - 2.11
If you want to use a macro in the same template, you should use something like this to stay compatible with Twig 2.x:
{% macro menu_links(links) %}
{% import _self as macros %}
{% for link in links %}
<li>
{{ link.name }}
{% if link.sublinks %}
<ul>
{{ macros.menu_links(link.sublinks) }}
</ul>
{% endif %}
</li>
{% endfor %}
{% endmacro %}
{% import _self as macros %}
<ul class="main-menu">
{{ macros.menu_links(links) }}
</ul>
This extends random-coder's answer and incorporates dr.scre's hint to the Twig documentation about macros to now use _self, but import locally.
Twig >= 2.11
As of Twig 2.11, you can omit the {% import _self as macros %}, as inlined macros are imported automatically under the _self namespace (see Twig announcement: Automatic macro import):
{# {% import _self as macros %} - Can be removed #}
<ul class="main-menu">
{{ _self.menu_links(links) }} {# Use _self for inlined macros #}
</ul>
If you're running PHP 5.4 or higher, there is a wonderful new solution (as of May 2016) to this problem by Alain Tiemblo: https://github.com/ninsuo/jordan-tree.
It's a "tree" tag that serves this exact purpose. Markup would look like this:
{% tree link in links %}
{% if treeloop.first %}<ul>{% endif %}
<li>
{{ link.name }}
{% subtree link.sublinks %}
</li>
{% if treeloop.last %}</ul>{% endif %}
{% endtree %}
First I thought this may be solved in a straightforward way, but it isn't that easy.
You need to create logic, maybe with a PHP class method, when to include a Twig subtemplate and when not.
<!-- tpl.html.twig -->
<ul>
{% for key, item in menu %}
{# Pseudo Twig code #}
{% if item|hassubitem %}
{% include "subitem.html.tpl" %}
{% else %}
<li>{{ item }}</li>
{% endif %}
{% endfor %}
</ul>
So you could use the special Twig loop variable, which is available inside a Twig for loop. But I'm not sure about the scope of this loop variable.
This and other information are available on Twigs "for" Docu!
Took flu's answer and modified it a little:
{# Macro #}
{% macro tree(items) %}
{% import _self as m %}
{% if items %}
<ul>
{% for i in items %}
<li>
{{ i.title }}
{{ m.tree(i.items) }}
</li>
{% endfor %}
</ul>
{% endif %}
{% endmacro %}
{# Usage #}
{% import 'macros.twig' as m %}
{{ m.tree(items) }}
The answers here lead my to my solution.
I have a category entity with a self-referencing many-to-one association (parent to children).
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="children")
*/
private $parent;
/**
* #ORM\OneToMany(targetEntity="Category", mappedBy="parent")
*/
private $children;
In my Twig template I am rendering the tree view like this:
<ul>
{% for category in categories %}
{% if category.parent == null %}
<li>
{{ category.name }}
{% if category.children|length > 0 %}
<ul>
{% for category in category.children %}
<li>
{{ category.name }}
</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endif %}
{% endfor %}
</ul>

Resources