Template Partial/Snippet only Renders Plain Text - ruby-on-rails

How do I get blocks inside a Liquid template snippet to render inside Rails? Currently, I am able to render plain text snippets, for example a layout might be:
<!DOCTYPE html>
<html>
<head>
{% include 'stylesheets' %}
</head>
<body>
</body>
</html>
If _stylesheets.liquid is plain text, this works fine. However, if I do something slightly more complicated, such as:
<!-- _stylesheets.liquid -->
{% for stylesheet in stylesheets %}
{{ stylesheet.file_url | stylesheet_tag }}
{% endfor %}
This will render nothing. When placing the exact code in the layout, it renders the expected results:
<!-- This Works -->
<!DOCTYPE html>
<html>
<head>
{% for stylesheet in stylesheets %}
{{ stylesheet.file_url | stylesheet_tag }}
{% endfor %}
</head>
<body>
</body>
</html>
Here is how I am rendering the liquid, in my rails view:
<%= Liquid::Template.parse(#theme.layout)
.render('stylesheets' => #theme.stylesheets)
.html_safe
%>

I was able to get the snippet partial working by changing the code to the following:
<!-- Layout -->
<head>
{% include 'stylesheets' %}
</head>
<!-- Partial: _stylesheets.liquid -->
{{ stylesheets.file_url | stylesheet_tag }}
This iterates through each stylesheet, but I cannot for the life of me find out why it does so without a liquid for loop.
Any ideas why this works?

Related

Define and insert Thymeleaf fragments in TEXT templates

I'm trying to create email templates in both plain text and HTML with Thymeleaf. Because I don't want to duplicate the common parts I want to define these parts separately and insert them into the more specific templates.
It works for HTML, but for plain text variables in the common parts are not replaced:
HTML
common.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<div th:fragment="header">
<p>
Hello, [( ${name} )]
</p>
</div>
<div th:fragment="footer">
<p>
Bye.
</p>
</div>
</body>
</html>
specific.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<div th:replace="html/common::header"></div>
<p>
<a th:href="${myLink}">[( ${myLink} )]</a>
</p>
<div th:replace="html/common::footer"></div>
</body>
</html>
Plain text
header.txt
Hello ${name}
footer.txt
Bye
specific.txt
[( ~{text/header} )]
[( ${myLink} )]
[( ~{text/footer} )]
Result
It all works well for HTML but for the plain text version the ${name} variable from the inserted header.txt template is not replaced:
Hello, [#th:block th:utext="${name}"][/th:block]
http://example.com
Bye.
The HTML result looks correct:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<div>
<p>
Hello, name-value
</p>
</div>
<p>
http://example.com
</p>
<div>
<p>
Bye.
</p>
</div>
</body>
</html>
My questions
Is there an elegant solution for the plain text version?
Is there a way to define and use fragments also for textual Thymeleaf templates?
Any general recommendations, as I'm only starting to use Thymeleaf?
Variables in the Plain Text Version
For the plain text issue, you can use the [#th:block] syntax.
Specifically, instead of using this in your specific.txt:
[( ~{text/header} )]
you can use this:
[#th:block th:replace="text/header"][/th:block]
Also, in the header.txt file, instead of using this:
Hello ${name}
you need to use this:
Hello [( ${name} )]
This is expression inlining - which you have already used - and is presented here, for reference.
Some additional examples of the [#th:block] syntax are presented here.
Defining and Using Fragments for Text
You might think that the [#th:block] syntax would now allow us to use fragments, in a similar way to the HTML approach. For example, something like this:
DOES NOT WORK:
[#th:block th:replace="text/common :: header"][/th:block]
together with a common.txt fragment like this:
ALSO DOES NOT WORK:
[#th:block th:fragment="header"]
Hello, [( ${name} )]
[/th:block]
If you try this, you will get the following error:
java.lang.IllegalArgumentException: Template selectors cannot be specified for a template using a TEXT template mode: template insertion operations must be always performed on whole template files, not fragments
General Comments
The only other thing I would mention here, if you have not already seen or used it, is parameterized fragments. They can make HTML fragments more flexible and re-usable.

What type of operation and syntax for them are possible on Thymeleaf 3 fragment expression?

With Thymeleaf 3 it is possible to pass fragment from page to template via ~{:: selector} syntax.
What kind of operation are possible on that object?
Fragment can be used inside expression:
<div th:fragment="name(arg)">
<div th:replace="${arg} :? _"></div>
</div>
Can I extract only part of fragment inside fragment with something like (following is incorrect syntax!!):
<div th:fragment="name(arg)">
<div th:replace="${arg :: script} :? _"></div>
<div th:replace="${arg}.filter('script'} :? _"></div>
<div th:replace="${xpath(${arg},'script')} :? _"></div>
</div>
UPDATE I introspected to what fragment expression is resolved with:
<th:block th:text="${bodyContent.class}" />
which is org.thymeleaf.standard.expression.Fragment. It has:
<th:block th:text="${bodyContent.templateModel.class}" />
TemplateModel which can be rendered via toString() or write(Writer writer). I don't see easy way to filter Fragment content...
I saw Thymeleaf templates - Is there a way to decorate a template instead of including a template fragment? technique which I tried to employ.
Thymeleaf v2.1 and 3 allows referencing templates/fragment mix to itself.
Lets look to template:
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<body>
<nav></nav>
<div th:replace="this :: body"/>
</body>
</html>
and to page:
<html lang="en" xmlns:th="http://www.thymeleaf.org"
th:replace="thymeleaf/layout/default :: html">
<body>
XXX
</body>
</html>
Above code produces infinite sequence of <body><nav></nav> as CSS selector referenced from template to body's template.
To move reference to page I added more complicated CSS style selector:
<html lang="en" xmlns:th="http://www.thymeleaf.org" class="htmlFrag">
<body>
<nav></nav>
<div th:replace="this :: html[!class]/body"/>
</body>
</html>
I am not sure how it is possible to have template and page in same scope for selector matching but it works...
Advanced templating with CSS/JS handling can be represented as:
<html lang="en" xmlns:th="http://www.thymeleaf.org" class="htmlFrag">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title th:text="~{::html[!class]/head/title/text()}"></title>
<link rel='stylesheet' href='/webjars/...">
<div th:replace="this :: html[!class]/head/link"/>
<script src="/webjars/..."></script>
<div th:replace="this :: html[!class]/head/script"/>
</head>
<body>
<nav></nav>
<div th:replace="this :: html[!class]/body"/>
</body>
</html>
UPDATE I've got responce from developers https://github.com/thymeleaf/thymeleaf/issues/626
Thymeleaf uses pull-based or fragment-inclusion-based layout architecture or by default.
With Layout Dialect it is possible to use hierarchical layout style and it is preferred to do this.

Script block is not working in GSP page. Any solution?

I have the script block in GSP page as shown in the code below. But the script block is not responding. Instead of printing hello in the console of the page, I get the error undefined operator $. What can be the reason?
getSpecificQuestion.gsp
<html>
<head>
<script>
$('.index').click(function() {
console.log("hello")
});
</script>
<title> Test </title>
</head>
<body>
<div id="question">
<%= "question = $question"%>
</div>
<div id="indexButtons">
<g:each in="${(1..<11)}" var="i">
<input type="button" class="index" value="${i}"/>
</g:each>
</div>
</body>
</html>
Apparently your gsp did not include any jquery in it so it complained about the missing $
Make sure that you include jquery in your gsp before the your script block. If you are using the resources plugin you can do something like this:
<html>
<head>
<r:require module="jQuery1111"/>
<r:script>
$(function() {
$('.index').click(function() {
console.log("hello")
});
})
</r:script>
<title> Test </title>
</head>
<body>
<div id="question">
<%= "question = $question"%>
</div>
<div id="indexButtons">
<g:each in="${(1..<11)}" var="i">
<input type="button" class="index" value="${i}"/>
</g:each>
</div>
</body>
</html>
Using the tag will render your javascript at the bottom of the page after all the DOM elements have been rendered and ready to be manipulated by the scripts.

Define a parent template and extend it in ruby on rails 4 like it is done in twig with "extends"

In twig i can have a parent layout which defines some standard
<!DOCTYPE html>
<html>
<head>
{% block head %}
<link rel="stylesheet" href="style.css" />
<title>{% block title %}{% endblock %} - My Webpage</title>
{% endblock %}
</head>
<body>
<div id="content">{% block content %}{% endblock %}</div>
<div id="footer">
{% block footer %}
© Copyright 2011 by you.
{% endblock %}
</div>
</body>
</html>
and then have a child layout which override some or all the blocks
{% extends "base.html" %}
{% block title %}Index{% endblock %}
{% block head %}
{{ parent() }}
<style type="text/css">
.important { color: #336699; }
</style>
{% endblock %}
{% block content %}
<h1>Index</h1>
<p class="important">
Welcome on my awesome homepage.
</p>
{% endblock %}
how can i do something similar in Rails 4?I looked around a bit but didn't find it.
You can create a layout for each controller. news.html.erb for NewsController etc.
You can then use content_for?, yield and render to nest the controller specific layouts inside application.erb.html
Here's an example from official rails guides.
In app/views/layouts/application.html.erb:
<html>
<head>
<title><%= #page_title or "Page Title" %></title>
<%= stylesheet_link_tag "layout" %>
<style><%= yield :stylesheets %></style>
</head>
<body>
<div id="top_menu">Top menu items here</div>
<div id="menu">Menu items here</div>
<div id="content"><%= content_for?(:content) ? yield(:content) : yield %></div>
</body>
</html>
In app/views/layouts/news.html.erb:
<% content_for :stylesheets do %>
#top_menu {display: none}
#right_menu {float: right; background-color: yellow; color: black}
<% end %>
<% content_for :content do %>
<div id="right_menu">Right menu items here</div>
<%= content_for?(:news_content) ? yield(:news_content) : yield %>
<% end %>
<%= render template: "layouts/application" %>
Source: http://guides.rubyonrails.org/layouts_and_rendering.html#using-nested-layouts

Master/application layouts with Erlydtl

What's a nice way to do master/application layouts with Erlydtl? With master/application layout I mean separating the main page layout template from template with the pagecontent, but rendering them as one. Think Ruby on Rails application layouts or ASP.NET Master Pages.
Are you trying to achieve something like:
main.dtl
<html>
<head></head>
<body>
<div id="menu">Menu</div>
<div id="content">
{% block content %}{% endblock %}
</div>
</body>
</html>
page.dtl
{% extends "main.dtl" %}
{% block content %}
Here's my content
{% endblock %}

Resources