Jekyll/Liquid form template with variables - jekyll

Is there a way to design a form template that is used by content pages with various parameters? Something like this:
_layouts/form.html
---
layout: default
---
<form method="POST" action="/info/{{ page.action }}">
{% for question in page.questions %}
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="question{{ question.id }}">
<label class="form-check-label" for="question{{ question.id }}">{{ question.text }}</label>
</div>
{% endfor %}
<button type="submit" class="btn btn-primary">Submit</button>
</form>
<script>
$("form").submit(function(event) {
var inputElems = $("input"),
count = 0;
for (var i=0; i<inputElems.length; i++) {
if (inputElems[i].type === "checkbox" && inputElems[i].checked === true){
count++;
}
}
if (count > {{ page.count }}) {
return;
} else {
event.preventDefault();
alert("Alert");
}
});
</script>
And a content page could consist of only a front matter that defines variables used by the template:
---
layout: form
title: Title
action: /results
questions: site.data.questions1
count: 10
---
The nicer solution would be to create some YAML file with variables definitions that are injected into the template accounting for the permalink of the form page. This file would be used to generate the end form pages.

Do you know about includes files in Jekyll?
Rather than determining the layout of a page so its hard to add stuff to it, the includes functionality is like a function or piece of reusable code. Which you can use on pages or layouts.
And it works well with parameters.
From the docs
_include/image.html
<figure>
<a href="{{ include.url }}">
<img src="{{ include.file }}" style="max-width: {{ include.max-width }};"
alt="{{ include.alt }}"/>
</a>
<figcaption>{{ include.caption }}</figcaption>
</figure>
Call like
---
---
{% include image.html url="http://jekyllrb.com"
max-width="200px" file="logo.png" alt="Jekyll logo"
caption="This is the Jekyll logo." %}
Or pass variable names instead of values, as defined in frontmatter or data file. Be careful not to override URL on the page with an image url or it can break things.
---
my_image:
url: '...'
alt: '...'
---
{% include image.html url=page.my_image.url alt=page.my_image.html ... %}
From _data/gallery.yaml
{% include image.html url=site.data.gallery.my_image.url
... %}
Or pass an object with attributes on it.
---
my_image:
url: '...'
alt: '...'
---
{% include image.html image_attributes=page.my_image %}

Related

Using variables in Jekylls Front Matter

I have created a data file with images which I use normally for posts.
ImageKey:
- url: "/assets/logos/Image.png"
title: "Image Title"
Now I want to use this image paths in my post headers.
---
layout: page
image:
- site.data.images.ImageKey
---
And my HTML looks like
{% for image in page.images %}
<div>
<div class="img-thumbnail">
<img class="img-responsive" src="{{site.baseurl}}{{image.url}}" alt="{{image.title}}">
</div>
</div>
{% endfor %}
But anything is wrong here. There will no picture be rendered.
It works if I use values in the fronter matter directly.
---
layout: page
image:
- url: "..."
title: "..."
---
I have the problem / request solved.
My _data\images.yml looks like
Image_Key_Name
url: /assets/file.png
alt: ....
title: ....
copyright: ....
My _posts\postXYZ.md
---
layout: post
author: Ben
titleImages:
- Image_Key_ Name
- Another_Image_Key_Name
abc...
---
And my _layouts\post.html
now iterates over the keys and uses them as array index.
<div class="title-images">
{% for titleImageKey in page.titleImages %}
{% assign titleImage = site.data.images[titleImageKey] %}
<img src="{{site.baseurl}}{{titleImage.url}}" alt="{{titleImage.title}}" />
{% endfor %}
That's it!

Include a twig template as an object to be passed into another template?

Im using gulp-twig: https://github.com/zimmen/gulp-twig
I have a twig file for my container component:
{# container.twig #}
<div class="container">
{% for item in items %}
<div class="container__item">
{{ item }}
</div>
{% endfor %}
</div>
I also have a snippet file:
{# snippet.twig #}
<div class="snippet">
<h2>{{ title }}</h2>
</div>
Im demoing these in page.twig. I need to render the snippet as the {{ item }} within the container. So when viewing page.twig this should be the output:
<div class="container">
<div class="container__item">
<div class="snippet">
<h2>title</h2>
</div>
</div>
<div class="container__item">
<div class="snippet">
<h2>title</h2>
</div>
</div>
<div class="container__item">
<div class="snippet">
<h2>title</h2>
</div>
</div>
</div>
Now here is where it gets tricky. container.twig and snippet.twig are being pulled into another application. As such {{ item }} within container.twig cant be changed to something like {{ itemRenderer(item) }}.
However page.twig is not being used anywhere else so I can edit it however I like. Is there a way in page.twig to render container.twig with snippet.twig as it's item, without modifying container.twig or snippet.twig?
This is my gulp task:
var gulp = require('gulp'),
config = require('../config'),
utilities = require('../build-utilities'),
src = config.path.src,
dest = config.path.dest,
opts = config.pluginOptions,
env = utils.getEnv(),
plugins = require('gulp-load-plugins')(opts.load);
var compile = function() {
var notProdOrTest = env.deploy && !env.prod && !env.test,
deployPath = env.deployPath,
sources = (env.deploy) ? ((env.styleguide) ? src.twig.styleguide: src.twig.testing): src.twig.all;
return gulp.src(sources, {base: 'src/'})
.pipe(plugins.twig({
data: {
component: utils.getDirectories('src/component/'),
deploy : env.deploy,
test : env.test,
prod : env.prod
}
}))
.pipe(plugins.htmlmin(opts.htmlmin))
.pipe(plugins.tap(function(file){
file.path = file.path.replace('testing/', '');
}))
.pipe((notProdOrTest) ? plugins.replace(/src="\//g, 'src="/' + deployPath.root + '/'): plugins.gutil.noop())
.pipe((notProdOrTest) ? plugins.replace(/href="\//g, 'href="/' + deployPath.root + '/'): plugins.gutil.noop())
.pipe((notProdOrTest) ? plugins.replace(/srcset="\//g, 'srcset="/' + deployPath.root + '/'): plugins.gutil.noop())
.pipe((notProdOrTest) ? plugins.replace(/url\('\//g, 'url(\'/' + deployPath.root + '/'): plugins.gutil.noop())
.pipe(gulp.dest((env.deploy) ? deployPath.markup: dest.markup));
},
watch = function() {
gulp.watch(src.twig.watch, ['twig:compile']);
};
module.exports = {
compile: compile,
watch : watch
};
This could be done with macros:
{# macros.html.twig #}
{% macro thisItem(item) %}
<div class="this-snippet">
<h2>{{ item.title }}</h2>
</div>
{% endmacro %}
{% macro thatItem(item) %}
<div class="other-snippet">
<h2>{{ item.title }}</h2>
</div>
{% endmacro %}
{% macro container(itemRenderer, items) %}
<div class="container">
{% for item in items %}
<div class="container__item">
{{ itemRenderer(item) }}
</div>
{% endfor %}
</div>
{% endmacro %}
And then in the template:
{# template.html.twig #}
{% from "macros.html.twig" import thisItem as itemRenderer, container %}
{% container(itemRenderer, items) %}
And in another template:
{# template2.html.twig #}
{% from "macros.html.twig" import thatItem as itemRenderer, container %}
{% container(itemRenderer, items) %}
The same thing can be achieved with regular includes, although both offer the same possibilities, I think the macro solution is cleaner.
{# snippet.html.twig #}
<div class="this-snippet">
<h2>{{ item.title }}</h2>
</div>
{# container.html.twig #}
<div class="container">
{% for item in items %}
<div class="container__item">
{% include snippetTmpl with { 'item': item } only %}
</div>
{% endfor %}
</div>
{# page.html.twig #}
{% include "container.html.twig" with { 'snippetTmpl': 'snippet.html.twig', 'items': items } only %}
I don't see how it could be possible without modifying container.html.twig, since you are trying to render {{ item }}, which is intended to be HTML, without the raw filter, which is mandatory to mark the content of {{ item }} as HTML-safe.
If you are the owner of the container.html.twig file origin (not sure what you meant by
container.twig and snippet.twig are being pulled into another
application
), maybe you could change {{ item }} to {{ item|raw }}. Then you would just need to be sure the items parameter passed to container.html.twig contains HTML generated by a renderView of snippet.html.twig. Then just be careful container.html.twig is not used somewhere else with HTML-unsafe items.
If you really don't have your hands on it, you may also try to render your template with a Twig environment that has autoescape disabled.
Hope this helps!
EDIT: Since you must do this using gulp-twig, what about something like this:
var titles = ['First snippet', 'second snippet'];
var i, items;
for (i = 0; i < titles.length; i++) {
gulp.src('/path/to/snippet.html.twig')
.pipe(plugins.twig({
data: {
title: titles[i]
}
}))
.pipe(plugins.intercept(function(file){
items[i] = file.contents.toString();
return file;
}));
}
gulp.src('/path/to/container.html.twig')
.pipe(plugins.twig({
data: {
items: items
}
}))
.dest('/path/to/dest.html');

Login, 2 views, 1 template - DJANGO

I have a homepage and in this homepage I include my connexion.html that I connect. But I have this error :
'str' object has no attribute 'visible_fields'
my homepage HTML :
{% extends "base.html" %} /* There are a css link of Bootstrap :bootstrap.min.css
<form method="POST" action="{% url 'connexion' %}" >
{% csrf_token %}
{{ form|bootstrap }}
<input type="submit" class="btn btn-success" value="Je m'inscris" >
</form>
...
<div class="panel-body">
{% load bootstrap %}
{% include "monsite/connexion.html" %}
</div>
...
My connexion.html :
<link rel="stylesheet" href="/static/css/bootstrap.min.css"/>
{% load bootstrap %,=
<form method="POST" action="{% url 'connexion' %}" >
{% csrf_token %}
{{ form|bootstrap }}
<input type="submit" class="btn btn-success" value="Login" >
</form>
My view of connexion : I have also home view
def connexion(request):
error = False
if request.method == "POST":
form = connexionForm(request.POST)
if form.is_valid():
pseudo = form.cleaned_data["pseudo"]
mdp = form.cleaned_data["mdp"]
user = authenticate(username=pseudo, password=mdp)
if user != None :
login(request,user)
return redirect(reverse(accueil))
else :
error = True
else :
form = connexionForm()
return render(request,"monsite/connexion.html",locals())
connexionForm is just a forms with 2 fields in forms.py
So I would like to login from my homepage...
Views don't work like that.
You can't include a template and expect it to somehow call a view. A template doesn't have its own view, and templates generally don't know or care which views they're called from. Including "connexion.html" just renders it with the current template context, and in this case that doesn't have any variable named "form".
If you want to include a template with a custom context, you need to use an inclusion template tag.
In locals(), there are form and all variables;
The error is in bootstrap|form in connexion.html but I dont know where

twig autoescaping html tags

{% autoescape false %}
{% set label = '<i class="flaticon solid plus-1"></i>Add User'|raw %}
{{ form_row(form.submit, { 'label': label }) }}
{% endautoescape %}
This is the output
<i class="flaticon solid plus-1"></i>Add User
How do I get it to not escape? If I just print out label instead of supplying it as a parameter to the "form_row" function, it prints out properly.
You're using the |raw filter at the wrong place - it is only processed when outputting data, not when setting a variable. You should patch it into the form_row function, or append it to it's call - can't be sure without seeing how that function works.
Most probably this will fix it:
{{ form_row(form.submit, { 'label': label })|raw }}
Since I assume it returns the modified string and lets the {{ tags handle output.
in form_div_layout.html.twig
change
<button type="{{ type|default('button') }}" {{ block('button_attributes') }}>{{ label|trans({}, translation_domain) }}</button>
to
<button type="{{ type|default('button') }}" {{ block('button_attributes') }}>{{ label|trans({}, translation_domain)|raw }}</button>
This seems like a hacky solution, since I'm modifying the stuff in the vendor folder.

jQuery html() to replace div contents works only for the first time

I'm using an AJAX request to handle some part of my app that deals with managing photos...the user can click on a '<' or '>' button to change the ordering of the photos. All works well, but only for the first time I click the button...subsequent clicks do not trigger anything.
Main template:
<script type="text/javascript">
$(function() {
$(".manage_photo").click(function(event) {
event.preventDefault();
var id = $(this).attr("id");
var action = $(this).attr("name");
var data = { id: id, action: action };
$.ajax({
type: "POST",
url: "{% url managePhotos %}",
data: data,
success: function(results) {
$("#list").html(results);
},
});
})
})
</script>
....
{% if photos %}
<p><strong>{{ photos.count }} photo(s) added</strong></p>
<div class="highslide-gallery">
<div id="list">
{% include "ajax/photos.html" %}
</div>
</div>
<div class="cleaner"></div>
{% else %}
<h5>Photos not yet added</h5>
{% endif %}
Code for ajax/photos.html:
{% for photo in photos %}
<div class="vehicle_photo">
<button class="manage_photo" name="incr" id="{{ photo.id }}"
{% if forloop.first %} disabled="disabled"{%endif %} style="float: left">
<
</button>
<button class="manage_photo" name="decr" id="{{ photo.id }}"
style="float: right">
>
</button>
<br />
<a href="{{ photo.original_image.url }}" class="highslide"
onclick="return hs.expand(this)">
<img class="ui-corner-all" src="{{ photo.thumbnail_image.url }}" />
</a>
<br />
{{ photo.position_number }}
</div>
{% endfor %}
My view returns a render_to_response version of photos.html after changing the ordering for the selected photo, with results containing the queryset for all photos in that photo's set, and a status message ie. success, failed:
return render_to_response('ajax/photos.html', results)
What could be my issue? I tried the suggestions at: this SO question, but none work out for me. Any insight would be very much appreciated since I've been at this since yesterday.
when you do the $(function...) things that are in the DOM get bound, but when you replace stuff with other stuff, the new stuff is not bound. You could use the .live command to make it work for old and new stuff, or you could bind the new stuff again (run the $(".manage_photo").click(...) again after the ajax.
BTW, you could replace the block of ajax with a simple $("#list").load("{% url managePhotos %}")