In Handlebars.js, how can I use #index to subscript into another parallel array in the object I am passing to a template?
For example, say I have an object set up like the following:
var table = {
cols : [
{ name: "Column 1" },
{ name: "Column 2" },
{ name: "Column 3", highlighted: true }
],
rows : [
{
label: "Row 1",
data: [
{ val: 5 },
{ val: 3 },
{ val: 8 }
]
},
{
label: "Row 2",
data: [
{ val: 8 },
{ val: 4 },
{ val: 0 }
]
}
]
};
I need to be able to use the #index from an {{#each rows}}{{#each data}} loop to check if the column is highlighted to apply a style to cells in the column, but Handlebars.js does not appear to allow using #index in a subscript operator.
E.g.
{{#index}} <!-- Index of current rows.data is 2. -->
{{#if ../../cols.[#index].highlighted }}
<!-- Never Executed -->
{{/if}}
{{#if ../../cols.[2].highlighted }}
<!-- Executes -->
{{/if}}
Is this not supported? Am I doing something wrong? How can I get this to work easily?
I posted an example on jsfiddle.net.
#key and #index behave specially, not as variables. So, even when you can use myvar.[123] you cannot use myvar.[#key] even though #key is 123. (At least, as of Handlebars 1.3.0)
There are two solutions. The first is to write your own handlebars helper, that takes both arrays/objects. The second is to restructure your data, i.e. merge your two parallel arrays.
As an example of the latter approach, if you have these two arrays:
var X={
cat:"meow",
sheep:"baaa"
};
var Y={
cat:3,
sheep:5
};
Then you could do:
var Z={};
for(var ix in X){
Z[ix]={X:X[ix],Y:Y[ix]};
}
And then pass Z to your handlebars template:
myTemplate({animals:Z});
which might look like this:
{{#each animals}}
The {{#key}} goes {{this.X}}; we have {{this.Y}} of them.
{{/each}}
Related
I am using the vue-select library. How can I force input entries to make all characters lower case? Right now, when I type the word "Baseball" into the input field, the tag is saved as "Baseball". I would like all tags to only keep a lower case version of the entry such as "baseball".
I have a sandbox here: https://stackblitz.com/edit/vue-ranqmt?file=src/App.vue
<template>
<div id="app">
<h3>Vue Select</h3>
<v-select
v-model="selected"
taggable
multiple
:options="options"
#input="setSelected"
></v-select>
<br /><br />
selected: <br />
<pre>{{ selected }}</pre>
</div>
</template>
<script>
import vSelect from 'vue-select';
import 'vue-select/dist/vue-select.css';
export default {
name: 'App',
components: {
'v-select': vSelect,
},
data() {
return {
selected: null,
options: [],
};
},
methods: {
setSelected(value) {
console.log(value);
},
},
};
</script>
a - You could add a computed property which returns the lowercased version to use in whichever part of your app you need it to be.
computed: {
selectedLowerCase() {
return this.selected.toLowerCase();
}
}
b - if you are using this for something like an API call, then you can turn the variable(s) into lowercase before submitting.
c - if you want the variable to appear as lowercase even in the input field you need to add the #input action to your input field and point it to a function to lowercase your input
methods: {
setSelected(value) {
this.selected = this.selected.toLowerCase();
console.log(value);
},
},
EDIT: clone this repository for a non working reproduction. https://github.com/FickleLife/meteor-c3-test
I am using https://github.com/peernohell/meteor-c3.js/
I pull two examples off the C3 site http://c3js.org/examples.html and can get them to display once on the page, but when I try to add a second on the same page the first disappears. There's no console error and the javascript variables are different.
chart 1 html template:
<template name="chart_cp_overview">
<div id="cpOverview"></div>
</template>
chart 1 js helper:
Template.chart_cp_overview.rendered = function () {
var cpOverview = c3.generate({
data: {
columns: [
['data1', 30, 200],
['data2', 130, 100]
],
type: 'bar',
groups: [
['data1', 'data2']
]
},
grid: {
y: {
lines: [{value:0}]
}
}
});
}
chart 2 html template:
<template name="chart_status">
<div id="chart"></div>
</template>
chart 2 helper:
Template.chart_status.rendered = function() {
var chart = c3.generate({
data: {
columns: [
['Dropped', 30],
['On Course', 120],
['DNS', 20],
['Finished', 40]
],
colors: {
'Dropped': '#E60000',
'On Course': '#00ACED',
'DNS': '#DBDBDB',
'Finished': '#00BD07'
},
type : 'donut',
onclick: function (d, i) { console.log("onclick", d, i); }
// onmouseover: function (d, i) { console.log("onmouseover", d, i); },
// onmouseout: function (d, i) { console.log("onmouseout", d, i); }
},
donut: {
title: "Entrant Status",
label: {
format: function (value, ratio) {
return value;
}
}
}
});
};
display code :
<div class="row">
<div class="col-md-6">
{{> chart_cp_overview}}
</div>
<div class="col-md-6">
{{> chart_status}}
</div>
</div>
This code above displays only the last chart - chart_status. If I remove any one of the handlebars reference the other chart displays fine, or if I have multiple handlebars to multiple charts whatever was last declared is displayed.
How can I get multiple charts to display within one page? Example is on github at https://github.com/FickleLife/meteor-c3-test
It looks like maybe you are intending the two variable names you have chosen in your template rendered functions, cpOverview and chart, to bind to the dom elements with those ids. It won't work that way.
The variable names you have chosen are local to their functions and in any case would not automatically attach to elements with that id even if they were global. So c3 is binding all these charts to the same dom element (the docs say the default is #chart), and the last one is overriding the prior ones.
You want to bind each chart to its respective element. You can use this.firstNode inside your rendered function (based on the way you have it set up), or use jquery, or this.find("div#cpOverview"), and then use the c3 api to bind the chart to it - it looks like { bindto: "div#cpOverview" } may be the one you want.
I have the data:
priceGroups:
[
{
label: 'rate1",
price: 1.00,
numPeople: 1
},
{
label: 'rate 2",
price: 2.00,
numPeople: 2
},
{
label: 'rate 3",
price: 15.00,
numPeople: 4
},
];
and
labels:
[
'Solo',
'Duo',
'Trio'
'4 People',
'5 People
];
I want to be able to do this:
{{#priceGroups}}
{{for (a = 1; a <= {{this.numPeople}}; ++a)}}
<th>{{label[a]}}</th>
{{/for}}
{{/priceGroups}}
I have tried so much and nothing works.
Please help. It is important.
I think you can just use this:
{{#each priceGroups}}<th>{{label}}</th>{{/each}}
The {{#each priceGroups}} gets you an iteration of the priceGroups array and then each item of the array (which is an object) is presented to the template one after another so you can then reference the {{label}} property in that object.
You don't embed javascript directly into handlerbars templates like you're trying to do.
When I go to a child state, I want to hide a ui-view component of a quadrant ui-view in root state. How can achieve this.
##index.html
<div ui-view="a">
</div>
<div ui-view="b">
</div>
<div ui-view="c">
</div>
##b.html
<div ui-view>
</div>
##config
$stateProvider.state('start', {
'views': {
'a': {
templateUrl: ...
},
'b': {
templateUrl: 'b.html'
},
'c': {
templateUrl: ...
}
},
controller: 'indexController
}).state('start.all', {
templateUrl: 'd.html',
controller: 'allController'
});
So when I reach start.all, I would like that the ui-view tagged c vanishes. How can I accomplish this.
There is an example demonstrating approach discussed below. The native way of ui-router, I'd say, is to manage all the views from current (active) state. We can do it with :
View Names - Relative vs. Absolute Names
... Behind the scenes, every view gets assigned an absolute name that follows a scheme of viewname#statename, where viewname is the name used in the view directive and state name is the state's absolute name, e.g. contact.item
In our case, the full name of the view 'c' would be c#, i.e. c as view name, # as delimiter and empty string representing the root (a bit weird but in fact logical).
Having that we can change the start.all definition like this:
.state('start.all', {
url : '/all',
'views': {
'': {
template: '<span>this is start ALL</span>',
},
'c#': {
template: '<span></span>',
},
},
})
And we will change the content of the c view in the root. And that should be the most native way with ui-router. It does not effectively remove it, but we can replace it with some empty stuff.
Also, into your example above, I placed controller called bController as contra example to the indexController:
.state('start', {
url : '/start',
'views': {
'a': {
template: ...
},
'b': {
template: ...
// HERE a new controller bController
controller: 'bController',
},
'c': {
template: ...
}
},
// the orginal contoller
controller: 'indexController',
})
and also defined them this way:
.controller('indexController', function($scope, $state, $stateParams) {
console.log('indexConroller was invoked');
})
.controller('bController', function($scope, $state, $stateParams) {
console.log('bConroller was invoked');
})
Why? to show you, that indexController will never be invoked. Contollers belongs to templates/views not to state...
Check all that together here
You could do it in a few ways. One way would be to have an abstract state containing views a and b. That abstract state then has two concrete child states: start, which adds view c, and all, which adds view d.
Another option is to just use the ng-show directive on the c view's root element bound to some scope variable. I would go with the first option.
Notably this does not answer the question because all is no longer a child of start. If there is a real need for all to inherit from start (there appears to be no need at present) you can just make start abstract and create a start.main and start.all.
Though Radim's solution is very clever and much appreciated, I think this is much more readable and intuitive than overriding a parent view with an empty template.
<body>
<div ng-app="myApp">
<a ui-sref="start.main">start</a> | <a ui-sref="start.all">all</a>
<hr />
<div class="rootView" ui-view="a"></div>
<div class="rootView" ui-view="b"></div>
<div class="rootView" ui-view=""></div>
</div>
<script>
var myApp = angular.module('myApp', ['ui.router']);
myApp.config(function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/start/main');
$stateProvider
// Content common to all views
.state('shell', {
abstract: true,
views: {
"a": { template: '<div>View a here.</div>' },
"b": { template: '<div>View b here.</div>' },
"": { template: '<div ui-view></div>' }
}
})
// Content common to all 'start' views (currently nothing)
.state('start', {
parent: 'shell',
url: '/start',
abstract: true,
template: '<div ui-view></div>'
})
.state('start.main', {
url: '/main',
template: '<div>View c is here</div>'
})
.state('start.all', {
url: '/all',
template: '<div>View d is here</div>'
});
});
</script>
</body>
Basically I want to this: In Mustache templating is there an elegant way of expressing a comma separated list without the trailing comma? in a Ractive template.
For the object
{
"items": [
{"name": "red"},
{"name": "green"},
{"name": "blue"}
]
}
I want to produce "red, green, blue"
I want to know if I am at the last item, so I can know whether to print the separator. Something like:
{{#items:i}}{{name}} {{#i.is_last}},{{/i}}{{/items}}
Can't easily test this right now, but wouldn't something like the following work?
{{#items:i}}
{{name}} {{ i < (items.length-1) ? "," : "" }}
{{/items}}
Can confirm that Stephen Thomas' answer works. Another option would be to join the array items like so:
ractive = new Ractive({
el: 'body',
template: '{{ items.map( getName ).join( ", " ) }}',
data: {
items: [{ name: 'red' }, { name: 'green' }, { name: 'blue' }],
getName: function ( item ) {
return item.name;
}
}
});
I actually wanted a solution that would allow me to put something more complicated instead of a comma, say, a DOM element. I figured out (another) method that works.
{{#items:i}}
{{name}}{{#(i<(items.length-1))}}, {{/end}}
{{/items}}
Recently #index and #last magic variables are introduced, so the example now becomes more readable:
{{#items}}
{{.name}}{{##index !== #last}}, {{/}}
{{/}}