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

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.

Related

Liquid Loop prints duplicates

I'm editing a "freshdesk knowledgebase" theme which uses (parts of?) liquid. I don't have a lot of experience.
The knowledgebase uses a structure of category: -> folder -> article
Here's the loop i'm having trouble with. While it outputs the list of available categories, it also prints a duplicate depending on how many articles are inside the folder.
<div class="category-list__items">
{% for category in portal.solution_categories%}
{% for folder in category.folders %}
{% if folder.articles_count > 0 %}
<div class="category-list-item">
<a href="{{category.url}}" class="category-list-item__link">
<div class="category-list-item__content">
<h3 class="category-list-item__title">{{category.name}}({{ folder.articles_count }})</h3>
</div>
</a>
</div>
{% endif %}
{% endfor %}
{% endfor %}
</div>
What I am wanting to output is a just a list of categories that have at least 1 article inside.
I'm getting:
Fruits (2)
Fruits (2)
Vegetables (1)
When I just want:
Fruits (2)
Vegetables (1)
You can use {% break %} to break the for loop conditionally.
So if you want to render a category only once when it finds a first folder with articles_count > 0 then break the loop and continue for next category like below.
<div class="category-list__items">
{% for category in portal.solution_categories%}
{% for folder in category.folders %}
{% if folder.articles_count > 0 %}
<div class="category-list-item">
<a href="{{category.url}}" class="category-list-item__link">
<div class="category-list-item__content">
<h3 class="category-list-item__title">{{category.name}}({{ folder.articles_count }})</h3>
</div>
</a>
</div>
{% break %}
{% endif %}
{% endfor %}
{% endfor %}
</div>

I would like to display in a loop a div class one time in two (Symfony)

I'm working on a personal project and I want to do this:
So there is my code with a loop which works well:
{% for uneInfo in lesInfos %}
<div class="row featurette ">
<div class="col-md-7 {% if (loop.first or loop.last) %} order-md-0 {% else %} order-md-2 {% endif %}">
<h2 class="featurette-heading"> {{ uneInfo.title }} </h2>
<p class="lead">{{ uneInfo.description }}</p>
</div>
<div class="col-md-5 {% if (loop.first or loop.last) %} order-md-0 {% else %} order-md-1 {% endif %} ">
<img class="featurette-image img-fluid mx-auto" data-src="holder.js/500x500/auto"
alt="Generic placeholder image">
</div>
</div>
<hr class="featurette-divider">
{% endfor %}
On my page it look like this:
How can I alternate the display in my loop? I work with Symfony.
You could use a Remainder operator with two as modulus to decide if you're in an even or uneven loop index.
{# if loop.index % 2 == 0 #}
0 % 2 = 0
1 % 2 = 1
2 % 2 = 0
3 % 2 = 1
4 % 2 = 0
5 % 2 = 1
etc...
another opportunity
{% for i in 1..10 if i is not odd %}
{{ i }},
{% endfor %}
or
{% for i in range(low=2, high=10, step=2) %}
{{ i }},
{% endfor %}
or
{% for i in 1..10 %}
{% if loop.index is not odd %}
{{ i }},
{% endif %}
{% endfor %}

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>

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!

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 %}

Resources