I'm trying to generate a nested ul>li list mark-up by Handlebars. So, I'm using partials in handlebars.
But, somehow the following code is not working. Can you please mention, what am doing wrong.
var handlebars = require('handlebars');
var source = '<ul>{{> list}}</ul>';
handlebars.registerPartial('list',
'{{#children}}' +
' <li>' +
' {{name}}' +
' {{#children}}' +
' <ul>' +
' {{> list}}' +
' </ul>' +
' {{/children}}' +
' </li>' +
'{{/children}}');
var template = handlebars.compile(source);
var children = [
{
name: 'Abcd',
children: [
{
name: 'dfrt',
children: [
{
name: 'fgtd',
children: [
]
},
{
name: 'ghty',
children: [
]
}
]
}
]
}
];
var generatedString = template(children);
console.log(generatedString); // Only produces '<ul></ul>'
Unlike this mustache example your example resembles of, you can just use the Handlebars #each helper
Source:
var source = ' <ul>{{#each this}}{{> list this}} {{/each}}</ul>'
The helper:
<li>
{{name}}
{{if children}}
<ul>
{{#each children}}
{{> list this}}
{{/each}}
</ul>
{{/if}}
</li>
Result:
Abcd
dfrt
fgtd
ghty
Related
I use express-handlebars with Node JS and I am trying to display informations from the array games on my HTML page using #each. But handlebars won't display the values inside my object. Even if it is not a nested object.
But if I only write {{this}}, it displays all the object like if it was a string. And if I do {{this.time}}, nothing is displayed.
I'm using Node JS v14.18.1, express v4.17.2 and express-handlebars v6.0.2.
Thank you in advance for your help !
getHomePage: async (req, res) => {
try {
const games = await gameModel.find().sort({time: -1}).limit(3).populate('user');
res.render('home', {title: 'Hello world', games});
} catch (error) {
console.error(error);
res.status(500).json({error});
}
}
<table>
<thead>
<th>Position</th>
<th>Score</th>
<th>Nom</th>
</thead>
<tbody>
{{#each games}}
<tr>
<td>#{{#index}}</td>
<td>{{this}}</td>
<td>{{this.time}}</td>
</tr>
{{/each}}
</tbody>
</table>
Either {{this.time}} or {{time}} should work, see snippet with output below, {{this.team}} and {{time}} is used.
If time still shows nothing, try confirming games is defined as expected.
// shortened data for brevity
const data = {
games: [
{team: 'Man U', time: '10am'},
{team: 'Arsenal', time: '1pm'}
]
};
//inline template for snippet simplicity
const template = '' +
'<table>' +
'<thead>' +
'<tr>'+
'<th>#</th>' +
'<th>Team Name</th>' +
'<th>Time</th>' +
'</tr>' +
'</thead>' +
'<tbody>' +
'{{#each games}}' +
'<tr>' +
// BEGIN
'<td>{{#index}}</td>' +
'<td>{{this.team}}</td>' +
'<td>{{time}}</td>' +
// END
'</tr>' +
'{{/each}}' +
'</tbody>' +
'</table>';
var output = Handlebars.compile(template)(data);
console.log(output)
// for snippet simplicity
$('body').html(output);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.7.7/handlebars.min.js"></script>
I thought the problem came from Handlebars or NodeJS. But it came from my MongoDB request.
I resolved it by adding .lean() at the end of my Mongo request. By default, Mongo returns a document, not a javascript object. So we have to "convert" it to a javascript to allow handlebars to display it:
gameModel.find().sort({time: -1}).limit(3).populate('user').lean()
I have a list of items where the title is a link to display a detailed view of the item. Click the title and it correctly goes to url + Id. In the Vue tolls the detail page retrieves the item with matching ID but as and array not an object and the template does not display any properties - what am I missing?
<script>
import axios from "axios";
export default {
name: "Report",
data() {
return {
report: {}
};
},
mounted: function() {
this.getReport();
},
methods: {
getReport() {
let uri = "http://localhost:5000/api/reports/" + this.$route.params.id;
axios.get(uri).then(response => {
this.report = response.data;
});
}
}
};
</script>
The template is so
<template>
<v-content>
<h1>report detail page</h1>
<p>content will go here</p>-
<h3>{{ report.month }}</h3>
<pre>{{ report._id }}</pre>
</v-content>
</template>
any comments appreciated
url + Id
It sounds like your issue is that you are receiving an array not an object.
You can pull out objects encapsulated inside arrays easily.
For example, if we had the following data:
var bus1 = {passengers:10, shift:1}
var busArr = [bus1]
which we can assert: busArr === [{passengers:10, shift:1}]
We could then pull out bus1 by referencing the index 0:
var bus1New = busArr[0]
If you want to avoid the data transformation and just output the structure you can consider a v-for in your template.
<p v-for="val in report">
_id: {{val._id}}
<br>
month: {{val.month}}
</p>
I have an image that i am binding the source to pull in dynamic data:
<img :src="'/public/images/' + media.client.name + '_' + media.client.id + '/' + media.type + '/' + media.fileName + '.' + media.ext " alt >
The media.client.name returns a string that has %20 instead of spaces.
I have created a filter:
removeSpecial(value) {
return value.replace("%20", " ");
}
How can I use this filter in the data binding of source please?
I have tried:
<img :src="'/public/images/' + media.client.name | removeSpecial + '_' + media.client.id + '/' + media.type + '/' + media.fileName + '.' + media.ext " alt >
and
<img :src="'/public/images/' + {{ media.client.name | removeSpecial }} + '_' + media.client.id + '/' + media.type + '/' + media.fileName + '.' + media.ext " alt >
Neither seem to work unfortunately.
You can make method which will return prepared url computed method like this:
imageUrl(media){
return '/public/images/' + media.client.name.replace("%20", " ") + '_' + media.client.id + '/' + media.type + '/' + media.fileName + '.' + media.ext;
}
Or if media is assigned in data you can use computed method which will return you same url
computed: {
imageUrl(){
return '/public/images/' + this.media.client.name.replace("%20", " ") + '_' + this.media.client.id + '/' + this.media.type + '/' + this.media.fileName + '.' + media.ext;
}
}
That would depend on how you want to decode the URI component. If this media data is populated dynamically (I suppose so), you could make a method for parsing and decoding it (see example below). If you need this decoder as a filter, however, here's an excerpt from the docs:
Filters should be appended to the end of the JavaScript expression, denoted by the "pipe" symbol.
So your best bet is probably to use a computed property so you can "pipe" it. And yes, you could utilize the native method decodeURIComponent() for this exact purpose.
Approach #1: Method only
new Vue({
el: '#app',
data() {
return {
rootPath: '/public/images/',
media: {
client: {
name: 'Music%20file%20with%20spaces',
id: 123
},
type: 'music-file',
fileName: 'some_music',
ext: 'mp3'
}
}
},
methods: {
getImgSource(media) {
// Destructures the properties for improved readability.
const { client, type, fileName, ext } = media;
const { name, id } = client;
// Uses template literals. Particularly useful for this situation
// where you have several, unique delimiters.
// Helps you see clearer how each of these URI components is combined.
return `${this.rootPath}${decodeURIComponent(name)}_${id}/${type}/${fileName}.${ext}`;
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<!-- Commented out for demo purposes, because I don't have the file.
<img :src="getImgSource(media)" :alt="media.fileName" /> -->
<!-- Let's assume this is an image element -->
<div v-text="getImgSource(media)"></div>
</div>
Approach #2: Computed property + filter
new Vue({
el: '#app',
data() {
return {
rootPath: '/public/images/',
media: {
client: {
name: 'Music%20file%20with%20spaces',
id: 123
},
type: 'music-file',
fileName: 'some_music',
ext: 'mp3'
}
}
},
computed: {
mediaFile() {
const { client, type, fileName, ext } = this.media;
const { name, id } = client;
return `${this.rootPath}${name}_${id}/${type}/${fileName}.${ext}`;
}
},
filters: {
decodeName: window.decodeURIComponent
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<!-- Let's assume this is an image element -->
<div>{{ mediaFile | decodeName }}</div>
</div>
Hope that helps.
I have implemented bootstrap-typeahead and when doing a search of, for example, the word "vision" (without accent), I want typeahead to find the coincidences that there is both "visión" (with accent) and "vision".
I have seen several examples to do this, like: accent insensitive regex but I do not understand the form to implement it in typeahaead. And I saw this too: Typeahead insensitive accent and I have created a new file bootstrap3-typeahead-ci.min.js as in this answer is shown, but this not working. Some help? Thanks.
EDITED
To complement the question
this is my typeahead.js (reduced)
$(document).ready(function(){
function buscar(texto){
$('#texto').val(texto);
$('#buscar').submit();
}
if ($('.typetitulo').length) {
var lang = $("#lang_js").data('value');
var json_location = 'storage/json/';
var noticia_location = 'actualidad/';
var noticias = new Bloodhound({
prefetch: {
url: json_location + lang + '/' + 'noticia.json',
cache: false
},
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('title', 'lead'),
queryTokenizer: Bloodhound.tokenizers.whitespace
});
var documentos = new Bloodhound({
prefetch: {
url: json_location + '/' + 'documento.json',
cache: false
},
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name', 'description'),
queryTokenizer: Bloodhound.tokenizers.whitespace
});
$('.typetitulo').typeahead(
{
name: 'noticias',
display: 'title',
source: noticias,
templates: {
header: "<h3>"+ tit_actualidad +"</h3>",
suggestion: function (item) {
var enlace = noticia_location + item.id + '/' + item.slug;
return "<div><a href='"+enlace+"'>" + item.title + "</a></div>";
}
}
},
{
name: 'documentos',
display: 'name',
source: documentos,
templates: {
header: "<h3>"+ tit_documentos +"</h3>",
suggestion: function (item) {
var enlace = item.path;
return "<div><a href='"+enlace+"'>" + item.name + "</a></div>";
}
}
}).on('typeahead:selected', function(e){
e.target.form.submit();
});
}
});
In the view:
{!! Form::open([
'route' => 'buscar',
'id' => 'buscar',
'name' => 'buscar',
'class' => 'buscador col-xs-12',
'method' => 'POST',
'accept-charset' => 'utf-8'
]) !!}
<input name="texto" class="input_buscador typetitulo" autocomplete="off" type="text"/>
<input name="lang" type="hidden" value="{{$lang}}"/>
{!! HTML::image('images/web/icons/lupa.svg', '', array('height' => '30', 'class' => 'boton_buscador', 'onclick' => 'document.buscar.submit()') ) !!}
{!! Form::close() !!}
// .... //
#if(isset($data['noticias']) && $data['noticias']->count() !== 0)
<div class="col-xs-12 pad_inf_2">
<h3>#lang('header.actualidad')</h3>
#foreach($data['noticias'] as $value)
<span class="item">
{{$value['title']}}
</span>
#endforeach
</div>
#endif
#if(isset($data['docs']) && $data['docs']->count() !== 0)
<div class="col-xs-12 pad_inf_2">
<h3>#lang('header.biblioteca')</h3>
#foreach($data['docs'] as $value)
<span class="item">
{{$value['name']}}
</span>
#endforeach
</div>
#endif
This is the typeahead-insensitive.js as in this answer is shown: Typeahead insensitive accent
// function for making a string accent insensitive
$.fn.typeahead.Constructor.prototype.normalize = function (str) {
// escape chars
var normalized = str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
// map equivalent chars
normalized = normalized.replace(/[aãáàâ]/gi, '[aãáàâ]');
normalized = normalized.replace(/[eẽéèê]/gi, '[eẽéèê]');
normalized = normalized.replace(/[iĩíìî]/gi, '[iĩíìî]');
normalized = normalized.replace(/[oõóòô]/gi, '[oõóòô]');
normalized = normalized.replace(/[uũúùû]/gi, '[uũúùû]');
normalized = normalized.replace(/[cç]/gi, '[cç]');
// convert string to a regular expression
// with case insensitive mode
normalized = new RegExp(normalized, 'gi');
// return regular expresion
return normalized;
}
// change 'matcher' method so it became accent insensitive
$.fn.typeahead.Constructor.prototype.matcher = function (item) {
// get item to be evaluated
var source = this.displayText(item);
// make search value case insensitive
var normalized = this.normalize(this.query);
// search for normalized value
return source.match(normalized);
}
// change 'highlighter' method so it became accent insensitive
$.fn.typeahead.Constructor.prototype.highlighter = function (item) {
// get html output
var source = this.displayText(item);
// make search value case insensitive
var normalized = this.normalize(this.query);
// highlight normalized value in bold
return source.replace(normalized, '<strong>$&</strong>');
}
And in the layout I added:
{{-- Typeahead --}}
{!! HTML::script('https://cdnjs.cloudflare.com/ajax/libs/typeahead.js/0.11.1/typeahead.bundle.min.js') !!}
{!! HTML::script('js/web/typeahead-insensitive.js') !!}
{!! HTML::script('js/web/typeahead.js') !!}
I have an object called boundedArea which contains an array of boundedArea objects in a field children and I would like to create a tree of unordered lists.
I have the following code:
- for (var index = 0; index < rootAreas.length; index++) {
- var boundedArea = rootAreas[index];
div(class='panel panel-default')
div.panel-heading
| #{boundedArea.NAME}
div.panel-body
- printChildren(boundedArea, 0);
- }
-
- function printChildren(boundedArea, depth) {
- var children = boundedArea.children;
- if (children == null || children.length == 0) {
- return;
- }
ul
- for (var index = 0; index < children.length; index++) {
- var child = children[index];
li #{child.NAME}
- console.log("Printing %s child of %s", child.NAME, boundedArea.NAME);
- printChildren(child, depth + 1);
- }
- }
Now obviously this sort of works in that it prints out all the values. However because the ul and li tags are a fixed indentation in they do not nest and just ended up printing sequentially.
Is there any way to dynamically set the level of indent or to force these to nest. Or should I be using a completely different model of nesting altogether.
I tried cretaing a javascript variable indent filled with two spaces for each depth level and then tried to use #{indent} but that just ended up creating tags with spaces in which was not what I wanted. Though that implies that something around this idea could work as it must be resolved at some level before but the it is picked up as a token of some kind.
Try using a mixin instead of a function. Mixins respect/remember the level of indentation (not really sure why functions don't).
mixin printChildren(boundedArea, depth)
- var children = boundedArea.children;
- if (children == null || children.length == 0)
- return;
ul
- for (var index = 0; index < children.length; index++)
- var child = children[index];
li #{child.NAME}
+printChildren(child, depth + 1)
- for (var index = 0; index < rootAreas.length; index++)
- var boundedArea = rootAreas[index];
div(class='panel panel-default')
div.panel-heading
| #{boundedArea.NAME}
div.panel-body
+printChildren(boundedArea, 0)
I tweaked your code a bit. Mixins are invoked using a + instead of a - and they need to be defined before they are used.
I tested it with this sample data:
{
rootAreas: [
{
NAME: 'area1',
children: [
{ NAME: 'child1' },
{ NAME: 'child2' },
{
children: [
{ NAME: 'child3' },
{ NAME: 'child4' },
]
},
]
},
{
NAME: 'area2',
children: [
{ NAME: 'child5' },
{ NAME: 'child6' },
{ NAME: 'child7' },
]
}
]
}
And the template yielded this HTML code:
<div class="panel panel-default">
<div class="panel-heading">area1</div>
<div class="panel-body">
<ul>
<li>child1</li>
<li>child2</li>
<li>
<ul>
<li>child3</li>
<li>child4</li>
</ul>
</li>
</ul>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">area2</div>
<div class="panel-body">
<ul>
<li>child5</li>
<li>child6</li>
<li>child7</li>
</ul>
</div>
</div>
If I understood you correctly, this is what you're looking for.