Is it possible to provide conditional context in Handlebars.js? - javascript

I have a template
<div class="vehicle">
<div class="model">{{model}}</div>
<div class="price">{{price}}</div>
</div>
which is common for some context subparts (I can't change the context), let's call those subparts car and bike, and context has only one of those:
contractContext = {
car?,
bike?,
...
}
I need to make this template work regardless on which subpart is present. I can go:
{{#if car}}
<div class="vehicle">
<div class="model">{{car.model}}</div>
<div class="price">{{car.price}}</div>
</div>
{{else}}
<div class="vehicle">
<div class="model">{{bike.model}}</div>
<div class="price">{{bike.price}}</div>
</div>
{{/if}}
but my real-life templates are considerably bigger, so instead of duplicating those I'd like to use the context conditionally. I've tried:
{{#with car ? car : bike}}
<div class="vehicle">
<div class="model">{{this.model}}</div>
<div class="price">{{this.price}}</div>
</div>
{{/with}}
(and also {{#with car || bike}} which isn't enough for the real-life case because of more complicated checks), but with doesn't work this way (sandbox). Is there some way to set context in a conditional way, or I have to handle this in JS instead?

What you're trying to do is to implement some logic (vehicle selection) in a template. However handlebars and mustache are logicless templates that's why it's not superobvious thing to do in a template. I believe proper way of doing what you want is to have it handled in javascript (where all the logic should be).
Like :
const data = {
vehicle: car || bike
}
And template
<div class="vehicle">
<div class="model">{{vehicle.model}}</div>
<div class="price">{{vehicle.price}}</div>
</div>
Also there's a hacky way of doing it using customHelper. Just to show how cool and customizeable handlebars are:
template:
{{#choseContext car bike}}
<div>{{price}}</div>
<div>{{model}}</div>
{{/choseContext}}
context:
{
bike: {
price: 422,
model: 'foo'
}
}
helper:
Handlebars.registerHelper('choseContext', function(v1, v2, options) {
let context = v1 || v2;
return options.fn(context);
});

Related

Why Props data passing between Parent to Child not working in VueJS?

Here, i use some list of data in Javascript variable to pass to VueJs Template Loop.
My Javascript Variable,
var templates = {
0:{
'nb':1,
'column':2
},
1:{
'nb':1,
'column':2
}
}
My Parent Template is,
<div v-for="(te,index) in templates" class="row">
<campaign_segment :t_nb="te.nb" :t_column=te.column> </campaign_segment>
</div>
Parent Template's Source,
<template v-if="showTemplate" id="segment_body">
<b>#{{ t_nb }}</b>
<div v-for="a in t_nb">
<block v-if="t_column == 4 || t_column > 4"></block> // <== Child Template,
</div>
</template>
Child Template's Source,
<template id="campaignBlock">
<b>HIT</b>
</template>
My VueJS,
// For Parent Template
Vue.component(
'campaign_segment', {
template: '#segment_body',
props: ['t_nb', 't_column']
});
// For Child Template
Vue.component('block', {
template: '#campaignBlock'
});
In general,
<div v-for="a in t_nb">
<block v-if="t_column == 4 || t_column > 4"></block> // <== Child Template,
</div>
No loop were generated.
here, if i use,
<div v-for="a in 2">
<block v-if="t_column == 4 || t_column > 4"></block> // <== Child Template,
</div>
Loop is working fine.
Whats wrongs with this, why vuejs never uses the same instance to process the loop ?
What I see is, there can be two issees:
Having multiple components under template, so you can put all of them in a single div, as is solved here.
Using parseInt in v-for, as it can be passed as a string in props instead of an integer, you can also put data type of props as explained here.
HTML
<template>
<div v-if="showTemplate" id="segment_body">
<b>#{{ t_nb }}</b>
<div v-for="a in parseInt(t_nb)">
<block v-if="t_column == 4 || t_column > 4"></block> // <== Child Template,
</div>
</div>
</template>

How to bind to object in repeat.for with Aurelia

I am currently trying to create a custom element with a bindable property and bind to that property to the variable of a repeat.for loop. This seems like it should be a simple task but I cannot get it to work and am wondering if it perhaps has to do with the variable being an object. The code for my custom element is below:
game-card.js
import { bindable } from 'aurelia-framework';
export class GameCard {
#bindable gameData = null;
#bindable name = '';
bind() {
console.log('card-game-data: ' + JSON.stringify(this.gameData, null, 2));
console.log('name: ' + this.name);
}
}
game-card.html
<template>
<div class="card medium">
<h3 class="center-align left">${gameData.name}</h3>
<div class="right-align right">${gameData.isPublic}</div>
</div>
</template>
The view that is using the custom element is below:
<template>
<require from="./game-card"></require>
<div>
<div class="row">
<div repeat.for="game of games">
<game-card class="col s6 m4 l3" gameData.bind="game" name.bind="game.name"></game-card>
</div>
</div>
</div>
</template>
The games object looks as follows:
[{name: 'SomeName', isPublic: true}, {name: 'AnotherName', isPublic: false}]
Now, when I run the code, the console.log statements in the game-card.js bind method print out undefined for the gameData, but prints out the correct name of the game for the console.log('name: ' + this.name) statement. I can't figure out why the binding is working when I bind to a property of the game object, but not when I bind to the game object itself. Any help would be greatly appreciated, thank you so much!
You have to write game-data.bind instead of gameData.bind. From the first look, this should be the only problem

Object passed through ng-click function loses reference

When passing an object to a ng-click function, the object seems to lose its reference. What is the reason?
CodePen example
<div ng-app="app" ng-controller="controller">
<p>
<u>Object</u> : {{ obj | json }}
</p>
<p>
<button ng-click="obj = {}">obj = {}</button> <!-- works -->
<button ng-click="voidIt(obj)">voidIt(obj)</button> <!-- doesn't work -->
</p>
<p>
<button ng-click="reset()">Reset obj</button>
</p>
</div>
angular.module('app', []).controller('controller',
function($scope) {
$scope.voidIt = function(object) {
object = {}
}
$scope.reset = function() {
$scope.obj = { prop: "value" }
}
$scope.reset();
});
What a nested $scope refers to can become confusing, so it's much better to use the controllerAs syntax. For a good explanation, read AngularJS 'controllerAs' vs. '$scope'.
The Codepen with ControllerAs referencing.
But if you're looking for a clear reasoning why it doesn't work, I'm lost as well. I just find it's just better to take the safer path that avoids such pitfalls.

How to access functions of a controller from within another controller via scope?

I have the following problem, I want to call a function of another controller from within a controller I want to use for a guided tour (I'm using ngJoyRide for the tour). The function I want to call in the other controller is so to say a translator (LanguageController), which fetches a string from a database according to the key given as parameter. The LanguageController will, if the key is not found, return an error that the string could not be fetched from the database. In my index.html fetching the string works, but I want to use it in the overlay element of my guided tour, which does not work, but only shows the "not fetched yet"-error of the LanguageController.
My index.html looks like this:
<body>
<div class="container-fluid col-md-10 col-md-offset-1" ng-controller="LangCtrl as lc" >
<div ng-controller="UserCtrl as uc" mail='#email' firstname='#firstname'>
<div ng-controller="GuidedTourCtrl as gtc">
<div ng-joy-ride="startJoyRide" config="config" on-finish="onFinish()" on-skip="onFinish()">
...
{{lc.getTerm('system_lang_edit')}}
...
</div>
</div>
</div>
</div>
</body>
The controller I'm using for the guided Tour looks like this:
guidedTourModule.controller('GuidedTourCtrl',['$scope', function($scope) {
$scope.startJoyRide = false;
this.start = function () {
$scope.startJoyRide = true;
}
$scope.config = [
{
type: "title",
...
},
{
type: "element",
selector: "#groups",
heading: "heading",
text: " <div id='title-text' class='col-md-12'>\
<span class='main-text'>"\
+ $scope.lc.getTerm('system_navi_messages') + "\
text text text text\
</span>\
<br/>\
<br/>\
</div>",
placement: "right",
scroll: true,
attachToBody: true
}
];
...
}]);
And the output I ultimately get looks like this for the overlay element:
<div class="row">
<div id="pop-over-text" class="col-md-12">
<div id='title-text' class='col-md-12'>
<span class='main-text'>
not fetched yet: system_navi_messages
text text text text
</span>
<br/>
<br/>
</div>
</div>
</div>
...
I hope someone can see the error in my code. Thanks in advance!
Things needs clarity are,
How you defined the 'getTerm' function in your Language controller, either by using this.getTerm() or $scope.getTerm(). Since you are using alias name you will be having this.getTerm in Language controller.
Reason why you are able to access the getTerm function in your overlay element is, since this overlay element is inside the parent controller(Language Controller) and you are referencing it with alias name 'lc' while calling the getTerm function. Thats' why it is accessible.
But the string you pass as a parameter is not reachable to the parent controller. that's why the error message is rendered in the overlay HTML.
Please make a plunker of your app, so that will be helpful to answer your problem.

Meteor: Render a template to the DOM on click

I am having a seemingly simple problem that I cannot really find a solution to. I have 2 columns: One with an overview of different tasks, and one with an area where detailed information about a task should be displayed when the "More information" button attached to each task is clicked. My logic is:
Have 2 templates: task_short and task_long
When the button in task_short is clicked use Blaze.render to render task_long to a div in the second column.
when "More information" is clicked on another task_short, use Blaze.remove to remove the view.
My main problem is: How do I tell Meteor which task should be render in task_long? task_short gets its {{content}},{{name}} etc parameters through the each tasks loop. But how do I do it with a single task? Also, I don't really understand Blaze.remove. Where do I get the ViewId from that needs to be passed in?
I am insanely grateful for any help!
This can be solved with a session variable and some conditionals in your template. You shouldn't need to use Blaze.render/Blaze.remove unless you are doing something really fancy. I don't know exactly how your template is structured, but this example should give you and idea of what to do:
app.html
<body>
{{#each tasks}}
{{> task}}
{{/each}}
</body>
<template name="task">
<div class='short'>
<p>Here are the short instructions</p>
<button>More information</button>
</div>
{{#if isShowingLong}}
<div class='long'>
<p>Here are the long instructions</p>
</div>
{{/if}}
<hr>
</template>
app.js
if (Meteor.isClient) {
Template.body.helpers({
tasks: function () {
// return some fake data
return [{_id: '1'}, {_id: '2'}, {_id: '3'}];
}
});
Template.task.helpers({
isShowingLong: function () {
return (this._id === Session.get('currentTask'));
}
});
Template.task.events({
'click button': function () {
Session.set('currentTask', this._id);
}
});
}

Categories

Resources