11ty one shorcode call another shortcode? - shortcode

Is it possible for me to write one shortcode, and it would trigger several others?
Let's say I have shorcode using njk:
{% card %}
inside, let's say the following code:
eleventyConfig.addNunjucksShortcode("card", function() {
return `
<div class="card">
</div>
`
});
And I have another shortcode:
{% img "path-to-img.jpg" %}.
It has an img src parameter, and will render to some img.
eleventyConfig.addNunjucksShortcode("img", function(path) {
return `
<img src="${path}">
`
});
I need that when I call the card shortcode, it called the img shortcode and passed the parameter to it.Let's say that I write:
{% card 'path-to-img.jpg' %}
And it will render at:
<div class="card">
<img src="path-to-img.jpg">
</div
Just in case, I need to call one shortcode, paired and nested shortcodes will not suit me:
{% column %}
{% img 'path-to-img.jpg' %}
{% endcolumn %}

While not really a 'shortcode calling a shortcode', I'd take the logic of img and abstract it into a method. Have the img shortcode call that to handle it's logic, and then card call the abstracted out method to get it's HTML result as well. Let me know if that doesn't make sense.

I got this way (there is not a img, but the meaning is the same):
eleventyConfig.addNunjucksShortcode("inner", function() {
return `<div class="inner">`;
});
eleventyConfig.addNunjucksShortcode("endinner", function() {
return `</div>`;
});
eleventyConfig.addShortcode("card", function(text) {
return `<div class="card">
${eleventyConfig.nunjucksShortcodes.inner()}
${text}
${eleventyConfig.nunjucksShortcodes.endinner()}
</div>`;
});
And call it:
{% card "text" %}

Related

Macro is working but keeps getting RESOLVEDATAMACRO errors

We are getting tons of MACRORESOLVER error on one of our pages and I was able to pinpoint where it is coming from. We have a macro in a static html web part like the code below and I can't figure out what's wrong. It is working as expected too so I'm confused. Is the syntax wrong? Hoping someone can help me.
<div class="row">
<div class="col-sm-4 newsltr-two-btn">
{% if(!String.IsNullOrEmpty(Document)) { %}
<a target="_blank" href="~/getattachment/{% Document %}/attachment.aspx" download>{% DocumentText %}</a>
{% } else if (!String.IsNullOrEmpty(ExternalDocumentUrl)) { %}
<a target="_blank" href="{% ExternalDocumentUrl %}">{% DocumentText %}</a>
{% } #%}
</div>
</div>
Event Log:
Error while evaluating expression: if(!string.IsNullOrEmpty(Document)) { print(ResolveMacros("\r\n {% DocumentText %} \r\n ")) } else if (!string.IsNullOrEmpty(ExternalDocumentUrl)) { print(ResolveMacros("\r\n {% DocumentText %} \r\n ")) } |(user)paulo|(hash)7b864efc818f93934b7bfb4c7d92234dc8c506e841ed30c3b661f8bee604d86b
System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
at System.ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument argument, ExceptionResource resource)
at CMS.MacroEngine.ExpressionEvaluator.EvaluateCondition(Boolean& match, Boolean& securityPassed, DataRow drLogItem, DateTime evaluationStarted)
Looks like your syntax has an error in it. See notation below:
<div class="row">
<div class="col-sm-4 newsltr-two-btn">
{% if(!String.IsNullOrEmpty(Document)) { %}
<a target="_blank" href="~/getattachment/{% Document %}/attachment.aspx" download>{% DocumentText %</a> // your error is here, missing a closing curly brace after the percent sign for DocumentText
{% } else if (!String.IsNullOrEmpty(ExternalDocumentUrl)) { %}
<a target="_blank" href="{% ExternalDocumentUrl %}">{% DocumentText %}</a>
{% } #%}
</div>
</div>
Also, if you're using a static HTML webpart, I'd suggest moving it to a static text webpart. Reason being the static text doesn't validate AND correct any of the HTML you input. This could also be the issue that the WYSIWYG editor is modifying your markup and causing problems.

How can I render search results using Symfony?

I've created a form for searching data in the DB.
This is the form:
<form role="search" method="post" action="{{ path('search') }}">
<div>
<input type="text" placeholder="Cerca" name ="search">
<span>
<button type="submit"></button>
</span>
</div>
</form>
And of course I also have the action in the controller:
public function searchAction(Request $request)
{
$request = $this->getRequest();
$data = $request->request->get('search');
$em = $this->getDoctrine()->getManager();
$query = $em->createQuery('SELECT a FROM AppBundle:Article a WHERE a.titolo LIKE :data')
->setParameter('data', $data);
$result = $query->getResult();
return $this->render('default/search.html.twig', array('result' => $result));
}
In this way, I can write in the field what I want to search, and submit the form. The form is submitted, but it renders the form again without any message or results.
If I want to render the results, do I have to create a results template where I use a for loop to render all the results?
How can I render the results?
You're already passing $results to the render function. So with the required code in your template it should work without much hassle:
E.g.
{% for result in results %}
<div>
// use result, e.g. {{ result.title }} or whatever your data has to offer
</div>
{% else %}
<em>no results found</em>
{% endfor %}

Symfony2 - adding a CSS class to an expanded choice field (radio)

I need to be able to add a CSS class to each individual radio button of my choice field(s). Unfortunately, Symfony2 stuffs expanded choices in a div, which gets any passed in CSS class rather than the buttons themselves.
Here's the default widget theming (in PHP):
<div <?php echo $view['form']->block($form, 'widget_container_attributes') ?>>
<?php foreach ($form as $child): ?>
<?php echo $view['form']->widget($child) ?>
<?php echo $view['form']->label($child) ?>
<?php endforeach ?>
</div>
Here's my div-less version (in twig):
{% block choice_widget_expanded %}
{% for child in form %}
{{ form_widget(child) }}
{{ form_label(child) }}
{% endfor %}
{% endblock %}
With that, how can I pass the CSS class to the actual widget? I'm passing it in like:
{% form_widget(form.blah, { 'attr' : { 'class': 'css-class' } }) %}
In my view, but I'm not sure how to grab it in my widget theme.
I can't not use radio buttons for this, so telling me to switch to a select or checkboxes isn't an option. And I really, really don't want to hard code the radios into my form view if I can help it.
EDIT: I've tried:
{% block choice_widget_expanded %}
{% for child in form %}
<input type="radio" value="{{ child.vars.value }}" {{ block('widget_attributes') }} />
{% endfor %}
{% endblock %}
Yet it still renders a containing div in the source, and passes my CSS class to that div instead of the radio inputs. I know that the theme is 'working' because I had a few exceptions thrown during my fiddling.
EDIT 2: With the suggestion below, I've created my own radio_widget theme:
{% block radio_widget %}
<input type="radio" class="star {split:2}" {{ block('widget_attributes') }} value="{{ value }}" {% if checked == true %}checked="checked"{% endif %} />
{% endblock %}
But, unfortunately, it's not generating the radios with the class I added above. I'm not sure if I need to do some inheritance work.
Interesting, why you want every radio option to have a class attribute. But...
Because choice is a single field (no matter how many options it has) you can add class to the wrapper div only. For the theming you should use CSS.
Speaking about form theming if you want to override radio option you should override block radio_widget (you can find original one in form_div_layout.html.twig)
And of course don't forget to tell you template to use form theme:
{% form_theme form _self %}

Symfony 2 custom form field type: how to add javascript and css only once?

I want to use javascript in custom Symfony 2 form field type extension. So, I have Twig extension template like this:
{% block some_widget %}
<input ... />
<script src="some.js"></script>
<link href="some.css" />
{% endblock %}
But I want to have these script and link tags only once in my HTML, ideally in head tag, without modifing base template. I tried to extend Twig blocks, but I have no access to action template blocks inside form template. Or maybe something like this:
{# widget tempate #}
{% block some_widget %}
<input ... />
{{ use_javascript('some.js') }}
{{ use_css('some.css') }}
{% endblock %}
{# main action template #}
...
<head>
{{ dump_javascripts() }}
{{ dump_css() }}
</head>
...
How to do this with Symfony 2 Forms + Twig?
P.S. Sorry for my bad english.
I had to write a self contained form widget that requires javascript, I was able to achieve what you are trying to do through the event_dispatcher listening on the kernel.response to append the javascript at the end of the Symfony\Component\HttpFoundation\Response. Here's a snippet of my form type :
<?php
namespace AcmeBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
class AcmeFileType extends AbstractType{
private $twig;
private $dispatcher;
public function __construct(\Twig_Environment $twig, EventDispatcherInterface $dispatcher){
$this->twig = $twig;
$this->dispatcher = $dispatcher;
}
public function buildView(FormView $view, FormInterface $form, array $options){
$javascriptContent = $this->twig->render('AcmeBundle:Form:AcmeFileType.js.twig', array());
$this->dispatcher->addListener('kernel.response', function($event) use ($javascriptContent) {
$response = $event->getResponse();
$content = $response->getContent();
// finding position of </body> tag to add content before the end of the tag
$pos = strripos($content, '</body>');
$content = substr($content, 0, $pos).$javascriptContent.substr($content, $pos);
$response->setContent($content);
$event->setResponse($response);
});
}
...
When you define your form type in your services.yml it looks like this :
acme.form.acme_file_type:
class: AcmeBundle\Form\AcmeFileType
arguments:
- #twig
- #event_dispatcher
tags:
- { name: form.type, alias: acmefile }
So now, everytime you build a form with acmefile the javascript will be appended to the <body>. This solution does not prevent the javascript from being present multiple time though, but you should easily be able to improve this to suit your needs.
You can also play around with the $response object to modify the headers instead if you wish.
The best way is to provide the separate template with css & scripts loading.
With the comments in readme so only thing developer will must do is to
{% block stylesheets %}
{{ parent() }}
include "#MyBestBundle/Resources/view/styles.html.twig"
{% endblock %}
or try to intercept the form rendering with DI and add the assets. But it more difficult to do , if possible to implement.
My way of doing was by creating a custom twig extension where I add JS to a buffer and during form rendering and later dump it at the of my layout.
Something like this:
<?php
namespace AppBundle\Twig;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
class WysiwygExtension extends AbstractExtension
{
/**
* #var array
*
* A pool of elements IDs for Wysiwyg binding.
*/
private $wysiwygElements = [];
/**
* {#inheritdoc}
*/
public function getFunctions()
{
return array(
new TwigFunction('addWysiwygBinding', [$this, 'addWysiwygBinding']),
new TwigFunction('popWysiwygBindings', [$this, 'popWysiwygBindings']),
);
}
public function addWysiwygBinding(string $id): void
{
$this->wysiwyglements[] = $id;
}
public function popWysiwygBindings(): array
{
$elements = array_unique($this->wysiwygElements);
$this->wysiwygElements = [];
return $elements;
}
}
Then form-fields.html.twig:
...
{% block wysiwyg_widget %}
{% apply spaceless %}
{{ form_widget(form) }}
{% do addWysiwygBinding(id) %}
{% endapply %}
{% endblock %}
...
Then layout.html.twig:
<!DOCTYPE html>
<html>
<head>
...
</head>
<body>
...
{% set ids = popWysiwygBindings() %}
{% if ids is not empty %}
{% javascripts
'bundles/admin/plugins/wysiwyg_1.js'
'bundles/admin/plugins/wysiwyg_2.js'
%}
<script type="text/javascript" src="{{ asset_url }}"></script>
{% endjavascripts %}
{% endif %}
{% for id in ids %}
{{ include('_wysiwyg.html.twig', { id: id }) }}
{% endfor %}
</body>
</html>
This is how I use it. Hope it's what you're looking for.
base.html.twig
<head>
{% block stylesheets %}
css...
{% endblock %}
</head>
foo.html.twig
{% extends '::base.html.twig' %}
{% block stylesheets %}
{{ parent() }}
css that you need in foo.html.twig
{% endblock %}
I found some 'dirty' method used by many peoples in other situations.
We check the loading of script on client side. In case we have a zlkladr.js file, that have a global object 'zlkladr'
{% block our_widget %}
{% spaceless %}
...
<script>
// We must load the script only once, even if many widgets on form
if ( !window.zlkladr ) {
document.write('<script src="{{ asset('bundles/kladr/js/zlkladr.js') }}"></sc'+'ript>');
}
</script>
{% endspaceless %}
{% endblock %}

How to customize the data-prototype attribute in Symfony 2 forms

Since umpteens days, I block on a problem with Symfony 2 and forms.
I got a form of websites entities. "Websites" is a collection of website's entities and each website contains two attributes : "type" and "url".
If I want to add more of one website in my database, I can click on a "Add another website" link, which add another website row to my form. So when you click on the submit button, you can add one or X website(s) at the same time.
This process to add a row use the data-prototype attribute, which can generate the website sub-form.
The problem is that I customize my form to have a great graphic rendering... like that :
<div class="informations_widget">{{ form_widget(website.type.code) }}</div>
<div class="informations_error">{{ form_errors(website.type) }}</div>
<div class="informations_widget">{{ form_widget(website.url) }}</div>
<div class="informations_error">{{ form_errors(website.url) }}</div>
But the data-prototype doesn't care about this customization, with HTML and CSS tags & properties. I keep the Symfony rendering :
<div>
<label class=" required">$$name$$</label>
<div id="jobcast_profilebundle_websitestype_websites_$$name$$">
<div>
<label class=" required">Type</label>
<div id="jobcast_profilebundle_websitestype_websites_$$name$$_type">
<div>
<label for="jobcast_profilebundle_websitestype_websites_$$name$$_type_code" class=" required">label</label>
<select id="jobcast_profilebundle_websitestype_websites_$$name$$_type_code" name="jobcast_profilebundle_websitestype[websites][$$name$$][type][code]" required="required">
<option value="WEB-OTHER">Autre</option>
<option value="WEB-RSS">Flux RSS</option>
...
</select>
</div>
</div>
</div>
<div>
<label for="jobcast_profilebundle_websitestype_websites_$$name$$_url" class=" required">Adresse</label>
<input type="url" id="jobcast_profilebundle_websitestype_websites_$$name$$_url" name="jobcast_profilebundle_websitestype[websites][$$name$$][url]" required="required" value="" />
</div>
</div>
</div>
Does anyone have an idea to make that hack ?
A bit old, but here is a deadly simple solution.
The idea is simply to render the collection items through a Twig template, so you have full ability to customize the prototype that will be placed in your data-prototype="..." tag. Just as if it was a normal, usual form.
In yourMainForm.html.twig:
<div id="collectionContainer"
data-prototype="
{% filter escape %}
{{ include('MyBundle:MyViewsDirectory:prototype.html.twig', { 'form': form.myForm.vars.prototype }) }}
{% endfilter %}">
</div>
And in MyBundle:MyViewsDirectory:prototype.html.twig:
<div>
<!-- customize as you wish -->
{{ form_label(form.field1) }}
{{ form_widget(form.field1) }}
{{ form_label(form.field2) }}
{{ form_widget(form.field2) }}
</div>
Credit: adapted from https://gist.github.com/tobalgists/4032213
I know this question is quite old, but I had the same problem and this is how I soved it. I'm using a twig macro to accomplish this. Macros are like functions, you can render them with different arguments.
{% macro information_prototype(website) %}
<div class="informations_widget">{{ form_widget(website.type.code) }}</div>
<div class="informations_error">{{ form_errors(website.type) }}</div>
<div class="informations_widget">{{ form_widget(website.url) }}</div>
<div class="informations_error">{{ form_errors(website.url) }}</div>
{% endmacro %}
now you can render this macro wherever you want. Note that information_prototype() is just the name of the macro, you can name it whatever you want. If you want to use the macro to render the given items and the prototype the same way, do something like this:
<div class="collection" data-prototype="{{ _self.information_prototype(form.websites.vars.prototype)|e }}">
{% for website in form.websites %}
{{ _self.information_prototype(website) }}
{% endfor %}
<button class="add-collection">Add Information</button>
</div>
form.websites.vars.prototype holds the prototype data of the form with the prototype_name you specified. Use _self.+macroname if you want to use the macro in the same template.
You can find out more about macros in the Twig documentation
You probably have found out since but here is the solution for others.
Create a new template and copy/paste this code in it:
https://gist.github.com/1294186
Then in the template containing the form you want to customise, apply it to your form by doing this:
{% form_theme form 'YourBundle:Deal:Theme/_field-prototype.html.twig' %}
I've run into similar problem recently. Here's how you can override the collection prototype without having to explicitly set it in the html:
{% set customPrototype %}
{% filter escape %}
{% include 'AcmeBundle:Controller:customCollectionPrototype.html.twig' with { 'form': form.collection.vars.prototype } %}
{% endfilter %}
{% endset %}
{{ form_label(form.collection) }}
{{ form_widget(form.collection, { 'attr': { 'data-prototype': customPrototype } }) }}
You can do whatever you want then in your custom twig. For example:
<div data-form-collection="item" data-form-collection-index="__name__" class="collection-item">
<div class="collection-box col-sm-10 col-sm-offset-2 padding-top-20">
<div class="row form-horizontal form-group">
<div class="col-sm-4">
{{ form_label(form.field0) }}
{{ form_widget(form.field0) }}
</div>
<div class="col-sm-3">
{{ form_label(form.field1) }}
{{ form_widget(form.field1) }}
</div>
<label class="col-sm-3 control-label text-right">
<button data-form-collection="delete" class="btn btn-danger">
<i class="fa fa-times collection-button-remove"></i>{{ 'form.collection.delete'|trans }}
</button>
</label>
</div>
</div>
Useful when you only have to do it in specific places and don't need a global override that's applicable to all collections.
I know that answer is very late but it maybe useful for visitors.
on your theme file you can simply use one block for rendering every collection entry of websites widget as following:
{% block _jobcast_profilebundle_websitestype_websites_entry_widget %}
<div class="informations_widget">{{ form_widget(form.type.code) }}</div>
<div class="informations_error">{{ form_errors(form.type) }}</div>
<div class="informations_widget">{{ form_widget(form.url) }}</div>
<div class="informations_error">{{ form_errors(form.url) }}</div>
{% endblock %}
also create theme block for your collection widget row as following:
{% block _quiz_question_answers_row %}
{% if prototype is defined %}
{%- set attr = attr | merge({'data-prototype': form_row(prototype) }) -%}
{% endif %}
{{ form_errors(form) }}
{% for child in form %}
{{ form_row(child) }}
{% endfor %}
{% endblock %}
now the prototype and the rendered collection entry will be the same.
I had a somewhat similar issue. You might have to tweak this to work for your case, but someone may find it helpful.
Create a new template file to hold your custom form 'theme'
./src/Company/TestBundle/Resources/views/Forms/fields.html.twig
Normally, you can use the form_row function to display a field's label, error, and widget. But in my case I just wanted to display the widget. As you say, using the data-prototype feature would also display the label, so in our new fields.html.twig type your custom code for how you want the field to look:
{% block form_row %}
{% spaceless %}
{{ form_widget(form) }}
{% endspaceless %}
{% endblock form_row %}
I removed the container div, and the label and error, and just left the widget.
Now in the twig file that displays the form, simply add this after the {% extends ... %}
{% form_theme form 'CompanyTestBundle:Form:fields.html.twig' %}
And now the form_widget(form.yourVariable.var.prototype) will only render the field and nothing else.
Application wide form theming will be applied to the prototype.
See Making Application-wide Customizations
Here is sample code for custom data-prototype:
{{ form_widget(form.emails.get('prototype')) | e }}
where emails — your collection.
To customize differently existing collection items VS prototype, you can override collection_widget like this:
{%- block collection_widget -%}
{% if prototype is defined %}
{%- set attr = attr|merge({'data-prototype': form_row(prototype, {'inPrototype': true} ) }) -%}
{% endif %}
{{- block('form_widget') -}}
{%- endblock collection_widget -%}
And then in your custom entry:
{% block _myCollection_entry_row %}
{% if(inPrototype is defined) %}
{# Something special only for prototype here #}
{% endif %}
{% endblock %}
If you do not need to define a template system-wide, simply set a template in your twig template, and ask twig to use it.
{# using the profiler, you can find the block widget tested by twig #}
{% block my_block_widget %}
<div >
<p>My template for collection</p>
<div >
{{ form_row(form.field1)}}
</div>
<div>
{{ form_row(form.field2)}}
</div>
</div>
{% endblock %}
{% form_theme form.my_collection _self %}
<button data-form-prototype="{{ form_widget(form.my_collection.vars.prototype) )|e }}" >Add a new entry</button>
There are two blocks that you can possibly target to add a custom theme to a collection field:
_myCollection_row and _myCollection_entry_row
_myCollection_row - renders the whole collection.
_myCollection_entry_row - renders a single item.
The prototype relies on _myCollection_entry_row so if you theme _myCollection_row only your theme will appear in the form, but not the prototype. The prototype uses _myCollection_entry_row.
So it's best to theme _myCollection_entry_row first, and then theme _myCollection_row only if required. BUT - if you theme _myCollection_row make sure it calls _myCollection_entry_row to render each item in your collection.
This post focuses on using pre-existing conventions within the twig template.
Basing off "How to Embed a Collection of Forms" from the Symfony Cookbook (http://symfony.com/doc/master/cookbook/form/form_collections.html), you can just enter whatever html_escaped form data you wish in the data-prototype (maybe considered a hack, but works wonderfully) and only pages using that template will change.
In the example, they tell you to put:
<ul class="tags" data-prototype="{{ form_widget(form.tags.vars.prototype)|e }}">
...
</ul>
This can be successfully replaced with something such as:
<table class="tags" data-prototype="<tr> <td><div><input type="text" id="task_tags__name__tagId" name="task[tags][__name__][taskId]" disabled="disabled" required="required" size="10" value="" /></div></td> <td><div><input type="text" id="task_tags__name__tagName" name="task[tags[__name__][tagName]" required="required" value="" /></div></td></tr>">
<tr>
<th>Id</th>
<th>Name</th>
</tr>
<tr>
...pre existing data here...
</tr>
</table>
Where the data-type attribute of the table with the class "tags" above is the html-escaped version (and line breaks removed though spaces are ok and required) of:
<tr>
<td><div><input type="text" id="task_tags__name__tagId" name="task[tags][__name__][taskId]" disabled="disabled" required="required" size="10" value="" /></div></td>
<td><div><input type="text" id="task_tags__name__tagName" name="task[tags[__name__][tagName]" required="required" value="" /></div></td>
</tr>
...but you must also adjust the javascript in the example to add tr's instead of li elements:
function addTagForm(collectionHolder, $newLinkTr) {
...
// Display the form in the page in an tr, before the "Add a question" link tr
var $newFormTr = $('<tr></tr>').append(newForm);
...
};
...
// setup an "add a tag" link
var $addTagLink = $('Add a tag');
var $newLinkTr = $('<tr></tr>').append($addTagLink);
...
For me, the next step is figuring out how to define the prototype in an external file that I can somehow call in the twig template for the data-prototype that dynamically works with the form. Something like:
<table class="tags" data-prototype="{{somefunction('App\Bundle\Views\Entity\TagsPrototypeInTable')}}">
So if one of the other posts is describing this and I am too dense or if someone knows how to do such, say so!
There is a link to something from gitHub from Francois, but I didn't see any explanation so I think that is probably the more dynamic method I'll get to one of these near-future days.
Peace,
Steve
Update:
One can also use just parts of the prototype:
data-prototype="<tr> <td>{{ form_row(form.tags.vars.prototype.tagId) | e }}</td> <td>{{ form_row(form.tags.vars.prototype.tagName) | e }}</td></tr>"
Where the data-type attribute of the table with the class "tags" above is the html-escaped version (and line breaks removed though spaces are ok and required) of:
<td>{{ form_row(form.tags.vars.prototype.tagId) | e }}</td>
<td>{{ form_row(form.tags.vars.prototype.tagName) | e }}</td>
(I used http://www.htmlescape.net/htmlescape_tool.html.)
Symfony will replace the information between the {{}} with an html_escaped (because of the "|e") rendered field when the page is rendered. In this way, any customization at the field-level is not lost, but! you must manually add and remove fields to the prototype as you do so with the entity :)