I have js function which returns hash:
Template.mainmenu.menuitem = function() {
var links = {};
links["mail"] = "http://some.net";
links["rss"] = "http://rss.com";
return links;
};>
I want to iterate over this hash in the HTML template and create set of links.
I has tried this code:
<template name="mainmenu">
{{#each menuitem}}
{{this}}
{{/each}}
</template>
But it return nothing. If I will change hash to array all works fine.
How can I iterate over hash and construct html links:
{{this.key}}
I don't think what handelbars has bult-in helper for iterating over js hash, but you can write your own helper which will return content of the hash or you can to use "with":
{{#with links}}
<p>{{{mail}}}</p>
<p>{{{rss}}}</p>
{{/with}}
As Meteor includes underscore by default, you can use underscore methods to extract contents of the hash:
Template.mainmenu.menuitem = function() {
var links = {};
links["mail"] = "http://some.net";
links["rss"] = "http://rss.com";
return _.values(links);
};
To continue where Hubert OG left off:
Template.mainmenu.menuitem = function() {
var links = {};
links["mail"] = "http://some.net";
links["rss"] = "http://rss.com";
return _.map(links, function (value, key) {return {_id: key, key: key, value: value}});
};
It's important to have an _id field as well so that Meteor's Spark rendering engine better understands what to redraw when things change.
Related
From the one of answer to Creating a new DOM element from an HTML string using built-in DOM methods or Prototype question:
Most current browsers support HTML elements, which provide
a more reliable way of turning creating elements from strings.
🌎 Source
Well, but generally it's useless to retrieve the template element without interpolating some data into it. But how to do it?
Concrete example
Retrieve below template from DOM and fill h1 and p elements by the data. It's allowed to modify the template tag content to make the access to elements easier, but let me repeat: please, no frameworks.
<template id="CatCardTemplate">
<div class="CatCard">
<h1></h1>
<p></p>
</div>
</template>
DOM manipulation solution
In the MDN example, the interpolation is being provided by DOM manipulations:
var tbody = document.querySelector("tbody");
var template = document.querySelector('#productrow');
var clone = template.content.cloneNode(true);
var td = clone.querySelectorAll("td");
td[0].textContent = "1235646565";
td[1].textContent = "Stuff";
tbody.appendChild(clone);
var clone2 = template.content.cloneNode(true);
td = clone2.querySelectorAll("td");
td[0].textContent = "0384928528";
td[1].textContent = "Acme Kidney Beans 2";
tbody.appendChild(clone2);
I can't believe if it's best that we can in 2021 without frameworks.
My efforts: musings about Handlebars solution
Because Handlebars is not a framework, it's allowed alternative.
If Handlebars code is a valid HTML, we can use it inside template elements:
<template id="CatCardTemplate">
<div class="CatCard">
<h1>{{name}}</h1>
<p>Age: {{age}}</p>
</div>
</template>
Now we can pick up the template and interpolate the data by Handlebars:
/*
* ⇩ Works, but causes "ModuleNotFoundError: Module not found: Error: Can't resolve 'fs' in ...".
* It's knows issue but I did not fount the acceptable solution yet.
* #see https://github.com/handlebars-lang/handlebars.js/issues/1174
*/
import Handlebars from "handlebars";
type CatData = {
name: string;
age: number;
};
const catCardTemplate: HTMLTemplateElement | null = document.getElementById<HTMLTemplateElement>("CatCardTemplate");
// Handle null case...
const renderCatCard: Handlebars.TemplateDelegate<Cat> = Handlebars.compile(catCardTemplate.outerHTML);
console.log(renderTab({
cat: "Luna",
age: 2
}))
Not exactly sure what you want. But you can definitely convert the template into string first, then use regular expression to analysis the code by updating the variable, {{__variable_name__}} to the variable you want.
Here is a stupid try, that do that handle any of the exception, but can give you a glimpse on how you can actually do the job.
var tbody = document.querySelector("tbody");
const arr = [
{
code: "1235646565",
productName: "Stuff"
},
{
code: "0384928528",
productName: "Acme Kidney Beans 2"
},
{
codeX: "17263871263", // typo, then will not display
productName: "Coca Cola"
}
];
arr.forEach(row => {
// console.log(convertTemplate("#productrow", row));
tbody.appendChild(convertTemplate("#productrow", row));
});
function convertTemplate(templateSelector, variablesObj) {
const template = document.querySelector(templateSelector);
const templateStr = template.innerHTML; // get the corresponding DOM as string as that we can manipulate on
// find all the corresponding {{ code }} there, and extract the name inside
const regex = /{{([^}]+)}}/g
const result = [];
let arr;
while (arr = regex.exec(templateStr)) {
if (arr.length > 1) {
const variableName = arr[1];
result.push(variableName);
}
}
let finalTemplateStr = templateStr;
result.forEach(variableName => {
const value = variablesObj[variableName] || '';
if (!value) {
console.error('no value are available set as empty first');
}
finalTemplateStr = finalTemplateStr.replace(`{{${variableName}}}`, value)
});
// create element same as root element described
const rootElemRegex = new RegExp("<([^/>]+)/?>", "i");
arr = rootElemRegex.exec(finalTemplateStr);
const rootElemName = arr[1];
const div = document.createElement(rootElemName);
div.innerHTML = finalTemplateStr;
return div;
}
<template id="productrow">
<tr>
<td class="record">{{code}}</td>
<td>{{productName}}</td>
</tr>
</template>
<table>
<thead>
<tr>
<td>UPC_Code</td>
<td>Product_Name</td>
</tr>
</thead>
<tbody>
<!-- existing data could optionally be included here -->
</tbody>
</table>
P.S. I have no knowledge on HTML native template module, if it already includes some easy to use tool, just share with us. Thanks.
Is it possible to parse out a list of keypaths used by a dynamic partial / component?
If I start with a completely empty data object - and dynamically add a partial / component. Is possible to step through the dynamically added partial's html and discover which keypaths are used.
My intent is to then apply this list of keypaths to my data object on the fly. I'm building a drag and drop wysiwyg ui - and want designers to be able to add templates without touching ractive...
Here is some pseudo code which I hope illustrates what I'm trying to achieve.
<script id="foo" type="text/ractive">
<p>{{blah}}</p>
<p>{{blahblah}}</p>
</script>
-
var ractive = new Ractive({
el: '#container',
template: '{{#items}}{{partial}}{{/items}}',
data: {
items : [] // empty to start with
}
});
ractive.on( 'addpartial', function ( event ) {
// partial gets added
// process the partial to find out what keypaths it contains.. put those keypaths into array
var partialDataArray = [{'blah':''},{'blahblah':''}]
this.push('items' , { 'partial' : 'foo', partialDataArray }
});
The other option would be to set each 'partial' as a component - but I'd be repeating myself loads (and I'm trying to be all DRY etc)
Cheers,
Rob
This code borrows heavily from an example on dynamic components given by Martydpx https://github.com/martypdx - although I am unable to find the post where I found it.
I've created a setup function that basically parses everything out for me. It means that I can supply a file with a long list of templates (to be used by components)
<div data-component="first_component">
<h1>{{h1}}</h1>
<p>{{p1}}</p>
</div>
<div data-component="second_component">
<p>{{p1}}</p>
</div>
-- and here is the JS. Edit - please see JavaScript regex - get string from within infinite number of curly braces for correct regex.
var htmlSnippets = [];
var setup = function() {
// load in our ractive templates - each one is wrapped in a div with
// a data attribute of component.
$.get("assets/snippets/snippets2.htm", function(data) {
var snippets = $.parseHTML(data);
// Each over each 'snippet / component template' parsing it out.
$.each(snippets, function(i, el) {
if ($(el).attr('data-component')) {
var componentName = $(el).attr('data-component')
// reg ex to look for curly braces {{ }} - used to get the names of each keypath
var re = /[^{]+(?=}})/g;
// returns an array containing each keypath within the snippet nb: ['foo' , 'bar']
var componentData = $(el).html().match(re);
// this is probably a bit messy... adding a value to each keypath...
// this returns an array of objects.
componentData = $.map( componentData, function( value ) {
return { [value] : 'Lorem ipsum dolor sit amet' };
});
// combine that array of objects - so its a single data object
componentData = componentData.reduce(function(result, currentObject) {
for(var key in currentObject) {
if (currentObject.hasOwnProperty(key)) {
result[key] = currentObject[key];
}
}
return result;
}, {});
// and add the component name to this data object
componentData['componentName'] = componentName;
// We need to use the data elsewhere - so hold it here...
htmlSnippets.push(componentData );
// and lastly set our component up using the html snippet as the template
Ractive.components[componentName] = Ractive.extend({
template: $(el).html()
});
}
});
Ractive.components.dynamic = Ractive.extend({
template: '<impl/>',
components: {
impl: function(){
return this.components[this.get('type')]
}
}
});
});
}();
var ractive = new Ractive({
el: '#container',
template: '#template',
data: {
widgets: htmlSnippets,
pageContent: []
}
});
I have an array whose items I want to group, and then display in this grouped fashion. It's all terribly confusing:
App.GroupedThings = Ember.ArrayProxy.extend({
init: function(modelToStartWith) {
this.set('content', Ember.A());
this.itemsByGroup = {};
modelToStartWith.addArrayObserver(this, {
willChange: function(array, offset, removeCount, addCount) {},
didChange: function(array, offset, removeCount, addCount) {
if (addCount > 0)
// Sort the newly added items into groups
this.add(array.slice(offset, offset + addCount))
}
});
},
add : function(things) {
var this$ = this;
// Group all passed things by day
things.forEach(function(thing) {
var groupKey = thing.get('date').clone().hours(0).minutes(0).seconds(0);
// Create data structure for new groups
if (!this$.itemsByGroup[groupKey]) {
var newArray = Ember.A();
this$.itemsByGroup[groupKey] = newArray;
this$.get('content').pushObject({'date': groupKey, 'items': newArray});
}
// Add to correct group
this$.itemsByGroup[groupKey].pushObject(thing);
});
}
});
App.ThingsRoute = Ember.Route.extend({
model: function() {
return new App.GroupedThings(this.store.find('thing'));
},
});
This only works if I use the following template:
{{#each model.content }}
These don't render anything (an ArrayController is used):
{{#each model }}
{{#each content }}
{{#each}}
Why? Shouldn't the ArrayController proxy to "model" (which is GroupedThings), which should proxy to "content"?
The reason this becomes a problem is that I then want to sort these groups, and as soon as I change the entire contents array (even using ArrayProxy.replaceContent()), the whole views rebuilt, even if only a single item is added to a single group.
Is there a different approach to this entirely?
I've tended to use ArrayProxies slightly differently when doing such things.
I'd probably get Ember to do all the heavy lifting, and for sorting get it to create ArrayProxies based around a content collection, that way you can sort them automatically:
(note I haven't run this code, but it should push you off in the right direction)
App.GroupedThings = Em.ArrayProxy.extend({
groupKey: null,
sortKey: null,
groupedContent: function() {
var content = this.get('content');
var groupKey = this.get('groupKey');
var sortKey = this.get('sortKey');
var groupedArrayProxies = content.reduce(function(previousValue, item) {
// previousValue is the reduced value - ie the 'memo' or 'sum' if you will
var itemGroupKeyValue = item.get('groupKey');
currentArrayProxyForGroupKeyValue = previousValue.get(itemGroupKeyValue);
// if there is no Array Proxy set up for this item's groupKey value, create one
if(Em.isEmpty(currentArrayProxyForGroupKeyValue)) {
var newArrayProxy = Em.ArrayProxy.createWithMixins(Em.SortableMixin, {sortProperties: [sortKey], content: Em.A()});
previousValue.set(itemGroupKeyValue, newArrayProxy);
currentArrayProxyForGroupKeyValue = newArrayProxy;
}
currentArrayProxyForGroupKeyValue.get('content').addObject(item);
return previousValue;
}, Em.Object.create());
return groupedArrayProxies;
}.property('content', 'groupKey', 'sortKey')
);
You'd then Create a GroupedThings instance like this:
var aGroupedThings = App.GroupedThings.create({content: someArrayOfItemsThatYouWantGroupedThatHaveBothSortKeyAndGroupKeyAttributes, sortKey: 'someSortKey', groupKey: 'someGroupKey'});
If you wanted to get the groupedContent, you'd just get your instance and get.('groupedContent'). Easy! :)
...and it'll just stay grouped and sorted (the power of computed properties)... if you want an 'add' convenience method you could add one to the Em.Object.Extend def above, but you can just as easily use the native ones in Em.ArrayProxy, which are better IMHO:
aGroupedThings.addObjects([some, set, of, objects]);
or
aGroupedThings.addObject(aSingleObject);
H2H
I tried figuring this out by reading both how the Tags are handled in the Todo-List Example and how the RSVPS are handled in the Parties example, but I could not figure out a general way to achieve my Goal.
I have a Plan Collection which has a name ownerid and excerciselist
Plans.insert({name: names[i], ownerId: 1, excerciselist: excercises});
Now in this Excerciselist, I want to add an undefined Amout of Excercises by ID.
I have an Excercisecollection with the following:
Excercises.insert({name: names[i], muscle: muscles[i], device: devices[i], description: descriptions[i]});
Now all these Excercises have a unique _id element by default.
Adding things to the excerciselist no Problem I can do that by Push.
But what I can not figure out is, how I can access only certain ID's in the excerciselist via it's Index.
I can only access the whole Stringarray and output in in HTML via
{{#each planarray}}
{{excerciselist}}
{{/each}}
But there is no possiblity to do smoething like
{{ excerciselist }}
I have also tried returning only excerciselist to the planarray, but the problem is that because it is only indexed and not mapped it can not be accessed by the LiveHTML.
Does anyone have an Idea how this problem could be solved?
Why don't you add a field for the unique id to the Excersies insert?
Excercises.insert({ uniqueID: [i], name: names[i], muscle: muscles[i], device: devices[i], description: descriptions[i]});
This way you can get just the excercise you want based on the uniqueID-field.
Oh and you should probably call "uniqueID" something that makes more sense.
I found a little Workaround which is not exactly what I had in mind but it gets the job done.
Template.editplan.excercises = function() {
var names = [];
var add = [];
var adder = null;
for(var i = 0; i < this.excerciselist.length; i++)
{
add[i] = [];
adder = Excercises.find({_id: this.excerciselist[i]});
add[i]['_id'] = this.excerciselist[i];
add[i]['location'] = i;
adder.forEach(function (obj) {
add[i]['name'] = obj.name;
});
names.push(add[i]);
}
return names;
};
Basically I made a new Array in which i put the Data I want to have so I can read it in the LiveHTML see example below
{{#each planarray}}
<h1>{{name}}</h1>
{{#each excercises}}
{{name}}
{{/each}}
<select name="selectedexcercise{{_id}}" id="selectedexcercise{{_id}}">
{{> excerciseoption}}
</select>
<input type="button" class="addtoplan" value="Eine Übung hinzfügen">
{{/each}}
But there must be a more efficient or nice way.... At least I hope so!
I'm using Handlebars templates and JSON data is already represented in [Object object], how do I parse this data outside of the Handlebars? For example, I'm trying to populate a JavaScript variable on the page through a handlebars tag, but this doesn't work.
Any suggestions? Thank you!
EDIT:
To clarify, I'm using ExpressJS w/ Handlebars for templating. In my route, I have this:
var user = {}
user = {'id' : 123, 'name' : 'First Name'}
res.render('index', {user : user});
Then in my index.hbs template, I now have a {{user}} object. I can use {{#each}} to iterate through the object just fine. However, I'm also using Backbonejs and I want to pass this data to a View, such as this:
myView = new myView({
user : {{user}}
});
The problem is that {{user}} simply shows [Object object] in the source. If I put it in console.log I get an error that says 'Unexpected Identifier'.
When outputting {{user}}, Handlebars will first retrieve the user's .toString() value. For plain Objects, the default result of this is the "[object Object]" you're seeing.
To get something more useful, you'll either want to display a specific property of the object:
{{user.id}}
{{user.name}}
Or, you can use/define a helper to format the object differently:
Handlebars.registerHelper('json', function(context) {
return JSON.stringify(context);
});
myView = new myView({
user : {{{json user}}} // note triple brackets to disable HTML encoding
});
You can simple stringify the JSON:
var user = {}
user = {'id' : 123, 'name' : 'First Name'};
// for print
user.stringify = JSON.stringify(user);
Then in template print by:
{{{user.stringify}}};
I'm using server-side templating in node-js, but this may apply client-side as well. I register Jonathan's json helper in node. In my handler, I add context (such as addressBook) via res.locals. Then I can store the context variable client-side as follows:
<script>
{{#if addressBook}}
console.log("addressBook:", {{{json addressBook}}});
window.addressBook = {{{json addressBook}}};
{{/if}}
</script>
Note the triple curlies (as pointed out by Jim Liu).
You are trying to pass templating syntax {{ }} inside a JSON object which is not valid.
You may need to do this instead:
myView = new myView({ user : user });
In the Node Router - Stringify the response object. See below line.
response.render("view", {responseObject:JSON.stringify(object)});
In HTML Script tag - user Template literals (Template strings) and use JSON.parse.
const json= `{{{responseObject}}}`;
const str = JSON.parse(json);
Worked like a charm!
You can render the keys/values of a list or object in a Handlebars template like this:
{{#each the_object}}
{{#key}}: {{this}}
{{/each}}
If you want more control over the output formatting you can write your own helper. This one has a recursive function to traverse nested objects.
Handlebars.registerHelper('objToList', function(context) {
function toList(obj, indent) {
var res=""
for (var k in obj) {
if (obj[k] instanceof Object) {
res=res+k+"\n"+toList(obj[k], (" " + indent)) ;
}
else{
res=res+indent+k+" : "+obj[k]+"\n";
}
}
return res;
}
return toList(context,"");
});
We used handlebars for email templates and this proved useful for a user like the following
{
"user": {
"id": 123,
"name": "First Name",
"account": {
"bank": "Wells Fargo",
"sort code": " 123-456"
}
}
}
To condense (what I found to be) the most helpful answers...
JSON helper for handlebars (credit):
Handlebars.registerHelper("json", function (context) {
return JSON.stringify(context);
});
JSON helper for express-handlebars (credit and I updated to newest conventions):
app.engine(
"handlebars",
exphbs.engine({
defaultLayout: "main",
helpers: {
json: function (context) {
return JSON.stringify(context);
}
}
})
);
And then on the templating side: {{json example}}
Just improving the answer from #sajjad.
Adding a 'pre' tag will make it look a lot nicer.
<pre>
{{#each the_object}}
{{#key}}: {{this}}
{{/each}}
</pre>