How would you embed atomic partials into a template data object - json

I'm using handlebars and assemble with yeoman and gulp.
I want to have some globalized partials that are able to be nested or injected into another partial by calling it within the context of a data object.
A simple example of that would be having a list of links that I could reference inside content throughout the site. The reason behind this, is the need for consistency. If for example, if I have a link within text on a page that I reference a 15 times throughout an entire website, but then realize I need to add a trade mark or modify the text, I want to update it once, not 15 times.
This is an example of what I want to do. Define global data inside a json file:
links.json
{
"apple": {
"linktext": "apple",
"target": "_blank",
"href": "http://www.apple.com"
},
"blog-article-foo-bar": {
"linktext": "foo bar",
"href": "http://www.foobar.com"
},
"dell": {
"linktext": "dell",
"target": "_parent",
"href": "http://www.dell.com"
}
}
Generate a partial from that content using a simple or complex template:
links.hbs
<a href="{{href}}" {{#if target}}target="{{target}}"{{/target}}>{{linktext}}</a>
And be able to embed that partial into another one by referencing it some how. This didn't work, but I've been reading about custom helpers, but can't figure out how I would intercept the partial and bind it into the other partial.
text.json
{
"text": "If you need a computer, go to {{> link link.apple}}."
}
text.hbs
<p>
{{text}}
</p>
compiled.html
<p>
If you need a computer, go to apple.
</p>
If you have suggestions or examples that might help me understand how to achieve this, I'd really appreciate the support. Thanks in advance.

There is some information about Handlebars helpers in their docs but not that much.
Since you're trying to use handlebars syntax in the value of a property on the context (e.g. text), handlebars won't render the value since it's already rendering the template. You can create your own helper that can render the value like this:
Handlebars.registerHelper('render', function(template, options) {
// first compile the template
const fn = Handlebars.compile(template);
// render the compiled template passing the current context (this) to
// ensure the same context is use
const str = fn(this);
// SafeString is used to allow HTML to be returned without escaping it
return new Handlebars.SafeString(str);
});
Then you would use the helper in your templates like this:
{{render text}}

Thanks for the example #doowb, your code did work but not for what I was trying to do. I really wanted something more complicated but I simplified my question not knowing it would be an issue. The code you provided worked (I think after a slight tweak) for a simple render of a template, but my templates use helpers such as #each and #if which caused the issue. Where the helpers were in my template, I ended up getting async placeholders. For example: <a $ASYNC$1$3...> I later learned this has to do with how partials are rendered. Understanding that lead me to subexpressions and the below solution.
Keeping my example above with some modifications, this is how I was able to merge partials.
First, I simplified the placeholder in text.json to basically a unique ID, instead of trying to render the partial there.
On the hbs template that I'm rendering to, such as a page or whatever, I included the insert helper with 3 arguments. The first two are subexpressions, each return a flattened partials as strings. The key here is that subexpressions process and return a result before finishing the current process with the helper. So two flattened templates are then sent to the helper along with the placeholder to search for.
The helper uses the third argument in a regex pattern. It searches the second argument (flattened parent template) for this pattern. When found, it replaces each instance of the pattern with the first argument (yes its a global fine replace).
So, the flattened child string gets inserted into parent each time placeholder is found.
First argument
(partial "link" link.apple)
Returns
'apple'
Second argument
(partial "text" text.text-example)
Returns
'<p class="text font--variant">If you need a computer, go to {{linkToApple}}.</p>'
Third argument
'linkToApple'
text.json
{
"text-example": {
"elm": "quote",
"classes": [
"text",
"font--variant"
],
"text": "If you need a computer, go to {{linkToApple}}."
}
}
text.hbs
<{{elm}} class="{{#eachIndex classes}}{{#isnt index 0}} {{/isnt}}{{item}}{{/eachIndex}}">{{text}}</{{elm}}>
compile.hbs
{{insert (partial "link" link.apple) (partial "text" text) 'linkToApple' }}
compile.html
<p class="text font--variant">If you need a computer, go to apple.</p>
gulpfile.js
app.helper('insert', function(child, parent, name) {
const merged = parent.replace(new RegExp('\{\{(?:\\s+)?(' + name + ')(?:\\s+)?\}\}', 'g'), child);
const html = new handlebars.SafeString(merged);
return html;
});
Hope this helps someone else. I know this can use improvements, I'll try to update it when I get back to cleaning up my gulp file.

Related

Can I set a simple key-value pair using amp-state?

I am trying to set a simple {key: value} pair using amp-state. Currently in my code there I have to initialize on every set state for key - value pairs.
The project is using express-handlebar templates and AMP for state management in the templates. There is a default handlebar value for each key.
But when one value is changed I have to initialize all the amp state values. This leads to a lot of duplicate code that could be improved a lot by setting state an initial. But to refactor all amp state into a big object containing the key-value pairs will complicate my code quite a bit so i'd rather not use the exact examples you can find on amp documentation.
For example I would like to replace something like this:
tap:AMP.setState({
lng: 'en',
selectedVal: selectedVal || '{{baseVal}}',
rate: rate || {{lookup currencyRates baseCurrency}},
symbol: symbol || '{{lookup symbols baseCurrency}}'
}),
with
tap:AMP.setState({
lng: 'en',
}),
In order to do this (and avoid errors) I would need to initialize all fields at the bottom of the page using <amp-state>...</amp-state> (and only changing the language in this case)
I tried to search for a solution, but I was not able. Does anyone know if there is a solution? If yes witch? If not, does anyone knows why?
Thanks in advice.
AMP.setState allows you to deep merge state objects:
https://amp.dev/documentation/components/amp-bind/?format=websites#deep-merge-with-amp.setstate()
I think you're missing initialisation of a state object which will allow you to populate state with your default values.
https://amp.dev/documentation/components/amp-bind/?format=websites#initializing-state-with-amp-state
You can do this either by a) rendering JSON directly into an amp-state component or b) by using the src
attribute to retrieve data from an endpoint.
a) Server-rendered state
<amp-state id="myStateObject">
<script type="application/json">
{
"lng": "en",
"selectedVal": "lookup baseVal",
"rate": "lookup currencyRates baseCurrency",
"symbol": "lookup symbols baseCurrency"
}
</script>
</amp-state>
OR
b) Using the src attribute
<amp-state id="myStateObject" src="/some/json/endpoint"></amp-state>
This gives you access to myStateObject which you can then use to merge an object literal with your current state.
<button
on="tap:AMP.setState({
myStateObject: {
selectedVal: 'newSelectedVal'
}
})"
>
Click me
</button>
I finally found my answer. Maybe it will help others too.
So even tough in the amp bind documentation there are no examples with just a key-value pair, seems like on this page there is.
The conclusion is you can load a simple key-value pair by just inserting a simple string inside the script tag (similar to this example they have on the second page):
<amp-state id="myText">
<script type="application/json">
"World"
</script>
</amp-state>
I found it quite confusing because I was expecting that inside the script tag you should place a json object. But this seems to work just fine.

Scala Play template vararg HtmlContent

I have a generic template in play 2.6, that I want to pass in a variable amount of HtmlContents. I've defined the template like this (including the implicit parameter I have in case that changes anything):
#(foo: String)(content: Html*)(implicit bar: Bar)
On the template side, this works fine-- I can dissect content with for and render it as I want. However, I haven't been able to figure out a clean way to invoke the variable arguments from the underlying template.
e.g, I have a view named "Baz":
#(something: String)(implicit bar: Bar)
In it, I try to invoke the template with multiple Html arguments. I've tried the following:
#template("fooString"){{123},{abc}}
and
#template("fooString")({123}, {abc})
and
#template("fooString"){{123}, {abc}})
And various other permutations, but inside of an enclosing bracket it seems to interpret everything literally as a single parameter in the HtmlContent vararg.
However, this ended up working as I intended, passing in multiple HtmlContents:
#template("fooString")(Html("123"), Html("abc"))
So that works, and I can use a triple-quoted interpolated string for a large Html block-- but it seems like there should be a cleaner way to do this, and the string interpolation is dangerous as it doesn't do html escaping.
Is there a way to do this using the { enclosed syntax? I'd like to understand more what is actually happening on an underlying level, and how play parses and generates HtmlContent in brackets.
So consider you have below template
// main.scala.html
#(title: String)(contents: Html*)
There are different ways you can call this template
Option #1
This is what you already posted in the question
#main("This is a title")(Html("abc"), Html("123"))
Options #2
#html1 = {
Hello
}
#html2 = {
<div>Tarun</div>
}
#main("This is a title")(html1, html2)
Option #3
#main("This is a title")(Html(<div>Tarun
</div>.toString), Html(<div>
Lalwani
</div>.toString))
Options #4
This is not exactly same option, but needs change in Template signature itself
#(title: String)(contents: List[String])
And then calling it like below
#main("This is a title")(List(
"""<div>
Tarun
</div>
""", """Hello"""))
Option #5
This requires code files and was already answered on another SO thread
Paul Draper's answer on Why doesn't this pass multiple HTML parameters to template

Returning functional HTML code through Angular JS filter

I'm trying to filter any text through angular to return a font-awesome icon. Instead It's just returning plain text. Anyone know a fix? Thanks!
.filter('textToIcon', function($sce) {
return function(text) {
if(text!==''){
return '<i class="fa fa-child"></i>';
}
}
This is "Strict Conceptual Escaping" (SCE) in action.
For more information on what it is and how it works, take a look at this answer.
In a few words, Angular tries to protect your users from untrusted content that could do bad things to them. If you know that the values are safe to be interpreted as HTML, there are two approaches you can take:
Import ngSanitize (it is available as a separate module) and add it as a dependency in your module (e.g. angular.module('myApp', ['ngSanitize'])). You are set (as long as you pass safe values to ngBindHtml) !
Use $sce to specify that certain stuff is explicitly trusted (by you) to be used as HTML:
.filter('textToIcon', function ($sce) {
return function (text) {
if (text) {
return $sce.trustAsHtml('');
}
};
})
The former approach is strongly recommended !

Load html conent after loading page?

I have working with one angularjs example i have face one problem is that i have load html view then after one div is content html data that is come from controler(database call) also data content html tag like <hr> <images> but that are display as it is not render html so i want to render that html part to.
I know my problem is delayed data come from data base so that will dispay as a plan text.
I use ng-bind-html till they are display pain text not render html tags.
I have one answer is late page loading that will succesfully work but that is not the proper way bcoz some time database data may take long time that is not working in this type of condition.
Hi jay you have to make directive for your example which is name bind-unsafe-html pass your html string or content in that and then it will re render your html content.
For Example.
app.directive('bindUnsafeHtml', ['$compile', function ($compile) {
return function(scope, element, attrs) {
console.log("in directive");
scope.$watch(
function(scope) {
// watch the 'bindUnsafeHtml' expression for changes
return scope.$eval(attrs.bindUnsafeHtml);
},
function(value) {
// when the 'bindUnsafeHtml' expression changes
// assign it into the current DOM
element.html(value);
// compile the new DOM and link it to the current
// scope.
// NOTE: we only compile .childNodes so that
// we don't get into infinite loop compiling ourselves
$compile(element.contents())(scope);
}
);
};
}]);
I can understand your problem it's a problem of asynchronous jscript call.
http://www.w3schools.com/tags/att_script_async.asp
you can not expect the script embedded in your HTML code to work properly.
You need to separate it from your HTML and make it async.

Best practices for Storing JSON in DOM

I want to render some json data using HTML template.
I haven't started implementing anything yet, but I would like to be able to "set" values of data from json to html element which contains template for one record, or to render some collection of items using some argument which is template html for each item, but also to be able to get JSON object back in same format as source JSON which was used to render item (I want my initial JSON to contain some more information about behavior of record row, without the need to make ajax request to check if user can or can't do something with this record, and not all of this info is visible in template).
I know that I could make hidden form with an input element for each property of object to store, and mapper function to/from JSON, but it sounds like overkill to me, and I don't like that, I want some lighter "envelope".
I was wondering is there some JS library that can "serialize" and "deserialize" JSON objects into html so I can store it somewhere in DOM (i.e. in element which contains display for data, but I want to be able to store additional attributes which don't have to be shown as form elements)?
UPDATE As first answer suggested storing JSON in global variable, I also have thought about that, and my "best" mental solution was to make JavaScript module (or jQuery plugin) which would do "mapping" of JSON to html, and if not possible to store values in html then it can store them in internal variable, so when I want to "get" data from html element it can pull it from its local copy. I want to know is there better way for this? If there is some library that stores this info in variable, but does real-time "binding" of that data with html, I would be very happy with that.
UPDATE 2 This is now done using http://knockoutjs.com/, no need to keep json in DOM anymore, knockout does the JSON<=>HTML mapping automatically
Why not store it as nature intended: as a javascript object? The DOM is a horrible place.
That said, jQuery has the data method that allows just this.
So you want to keep a reference to the JSON data that created your DOMFragment from a template?
Let's say you have a template function that takes a template and data and returns a DOM node.
var node = template(tmpl, json);
node.dataset.origJson = json;
node.dataset.templateName = tmpl.name;
You can store the original json on the dataset of a node. You may need a dataset shim though.
There is also no way to "map" JSON to HTML without using a template engine. Even then you would have to store the template name in the json data (as meta data) and that feels ugly to me.
I have done this in the past as well in a couple of different ways.
The $('selector').data idea is probably one of the most useful techniques. I like this way of storing data because I can store the data in a logical, intuitive and orderly fashion.
Let's say you have an ajax call that retrieves 3 articles on page load. The articles may contain data relating to the headline, the date/time, the source etc. Let's further assume you want to show the headlines and when a headline is clicked you want to show the full article and its details.
To illustrate the concept a bit let's say we retrieve json looking something like:
{
articles: [
{
headline: 'headline 1 text',
article: 'article 1 text ...',
source: 'source of the article, where it came from',
date: 'date of the article'
},
{
headline: 'headline 2 text',
article: 'article 2 text ...',
source: 'source of the article, where it came from',
date: 'date of the article'
},
{
headline: 'headline 3 text',
article: 'article 3 text ...',
source: 'source of the article, where it came from',
date: 'date of the article'
}
]
}
From an ajax call like this . . .
$.ajax({
url: "news/getArticles",
data: { count: 3, filter: "popular" },
success: function(data){
// check for successful data call
if(data.success) {
// iterate the retrieved data
for(var i = 0; i < data.articles.length; i++) {
var article = data.articles[i];
// create the headline link with the text on the headline
var $headline = $('<a class="headline">' + article.headline + '</a>');
// assign the data for this article's headline to the `data` property
// of the new headline link
$headline.data.article = article;
// add a click event to the headline link
$headline.click(function() {
var article = $(this).data.article;
// do something with this article data
});
// add the headline to the page
$('#headlines').append($headline);
}
} else {
console.error('getHeadlines failed: ', data);
}
}
});
The idea being we can store associated data to a dom element and access/manipulate/delete that data at a later time when needed. This cuts down on possible additional data calls and effectively caches data to a specific dom element.
anytime after the headline link is added to the document the data can be accessed through a jquery selector. To access the article data for the first headline:
$('#headlines .headline:first()').data.article.headline
$('#headlines .headline:first()').data.article.article
$('#headlines .headline:first()').data.article.source
$('#headlines .headline:first()').data.article.date
Accessing your data through a selector and jquery object is sorta neat.
I don't think there are any libraries that store json in dom.
You could render the html using the data from json and keep a copy of that json variable as a global variable in javascript.