JSrender: cannot access parent variable inside if condition - javascript

as the title says, the problem is clear:
within the construction:
{{if is_completed == 1}}
<div>
<p>{{:#parent.parent.data.myproperty}}</p>
</div>
{{/if}}
the parent properties are not visible!
I solved it by creating construction like:{{for movies ~myproperty=myproperty}} in parent loop, and ~myproperty is visible inside IF conditions, but what if I have several variables, what if I have many-level nesting data structure?

The {{if ...}} block adds another view, so means you need to add a .parent to step up through that view, as in: {{:#parent.parent.parent.data.myproperty}}
You can pass in variables as you said (~myproperty=...) and they will be visible to any depth of nesting.
Your variable can be an object too such as the current data object: ~myObj=#data:
{{sometag a=b ~myObj=#data}}
....
{{:~myObj.myproperty}}
...
{{/sometag}}
so you don't need a separate variable for each property.
You can also access the top-level data object and drill down from there:
{{:~root.foo...myproperty}}.
And finally you can use #get("item") to step up through any number of {{if}} blocks and get the nearest "item" view (i.e. the item view for a repeating {{for ...}} block).
So you would write:
{{:#get("item").data.myproperty}}

Related

Node.js Handlebars {{object}} does not render inside {{each}} loop, but renders outside of loop (and other {{object_names}} render within the loop)

I am using Express, Node, & Handlebars and I can't figure out why my {{object_one}} won't render within an {{each}} loop, but it renders {{object_two}} from within and {{object_one}} outside.
Here is example since I'm not sure I'm describing this well:
{{#each inline_upsell_amounts}}
<div class="col-4">$ {{this}}</div>
{{/each}}
<p>This shows the 'base_url' value: {{base_url}}</p>
So inside the loop base_url renders as empty or ''. the value of base_url is a string (url 'http://something.com' ).
It renders correct outside of the each loop, and {{inline_ab_refcode}} also a string renders fine within the loop.
I've tried different names, different values, making a helper, wrapping with IF. I can't figure out this odd bug I have.
Hopefully it's something silly that you can see?!
You miss something : in the loop {{base_url}} is not the same as outside the loop :
In the loop {{base_url}} is actually {{inline_upsell_amounts.0.base_url}} or {{inline_upsell_amounts.1.base_url}} and so on ... outside of it it is {{base_url}}. Because the argument of the loop becomes the new reference.
What you can do is refer to the parent item and use {{../base_url}} inside the loop and {{base_url}} outside. If you need a reference that is always the same you can also use {{#root/base_url}} that will always be the base_url of the root element.
So your code will look like that :
{{#each inline_upsell_amounts}}
<div class="col-4">$ {{this}}</div>
{{/each}}
<p>This shows the 'base_url' value: {{base_url}}</p>
I hope I've been clear enough.

What is a good way to create a variable in JsRender?

In a JsRender template, blocks can create variables, so I could write this:
{{if true ~myVariable=myExpensiveFunction()}}
{{:~myVariable}} {{:~myVariable}}
{{/if}}
However, the bogus if is annoying. Is there a better way?
If you can initialize myVariable outside the template, then you can make it a helper variable, and pass it in:
var html = myTmpl.render(data, {myVariable:myExpensiveFunction()});
If you need to initialize in a specific template/data context then as you say, you can use contextual parameters, which are scoped to any template block. Use the tag that wraps that context.
If the context is is top-level in a template, then:
either this is top-level data and you can pass in a helper as above
or it is a 'partial' template rendered from anothor (layout) template, using {{someTag tmpl=.../}}, in which case you can set up your contextual parameter from the calling tag in the other template
or else it is rendering against an array
For that last case you can use {{include}}:
{{include ~myVariable=myExpensiveFunction()}}
{{:~myVariable}} {{:~myVariable}}
{{/include}}
or you can call with noIteration set to true:
var html = myTmpl.render(data, [helpers,] true);
and then wrap in {{for}}:
{{for ~myVariable=myExpensiveFunction()}}
{{:~myVariable}} {{:~myVariable}}
{{/for}}
EDIT:
In your particular case, based on your comments added below, you need to initialize in the context of an item block within a {{for someArray}} - in order to get the item data.
(Actually your item block is a 'partial' template called using {{for someArray tmpl=...}}, but the basic problem is the same).
In this scenario you will indeed need to add an {{include}} wrapper, at top-level (for you) or within the {{for}} if not doing template composition.

Updating parent's object data from child controller

I have a object $scope.object in parentCtrl, and I have a isolated scope directive. Inside directive, I have a functionality wherein I need to make an API call and fetch one of the property of $scope.object (this property is an array of object) and replace this property in original $scope.object.
I have tried various ways of doing it but somehow I am not exactly getting at it. I have tried updating it using $scope.$parent.object from directive's controller.
After this I tried sending the fetched array in to the parent's controller and replacing it there.
Once the $scope.object gets updated, the directive should run as the directive's template is binded with properties of $scope.object.
The interesting thing is that the view gets updated with new data but the data in original object somehow again becomes null.
I guess, there is some issue with the way my directive is called as the same directive is called nestedly. As shown below:
<li ng-repeat="t in A" ng-if="t.selected">
<div directive> <div>
</li>
where, A is the fetched property and t.selected is true. Now in the fetched data t.selected is true for two elements of array, while the directive is called 4 times, twice for each element. In the first iteration the data remains intact in parent object, while in 2nd iteration, data becomes null
I want to know, how do I update the data of parent object permanently
Directive:
<li ng-repeat="(key, t) in fetch.data>
<div layout="row" layout-wrap directive target="t" ></div>
</li>
and this target is double way binded in directive's scope with parent's scope

handlebars access global variable: if statement

I've got a hbs template where I've got an array of objects and a boolean toggle variable (toggles the template behavior), let's say:
{
objs: list,
mode: true
}
I'm not able to access the mode variable when inside the loop over objs (the context is changed). What I want is to make an if-statement using the upper variable. I found that I can write a custom helper. But is there no other way to access the variable? I also found out, that inside the loop the variable is accessible via {{../mode}} - but still, don't know how to access that.
Finally, I've found a solution:
{{#if ../mode}}xyz{{/if}}

Pass parent scope value into ng-repeat loop in Angular

This should be an extremely simple question, but all of the workarounds I've found are complex. I'm looping through an array of objects in using ng-repeat in a template as follows:
<div class="row-fluid" ng-repeat="message in messages.current|filter:'draft'">
{{ message.subject }} ... {{ campaign.name }} ...
</div>
Since the ng-repeat creates a new scope, the 'campaign' object from the controller doesn't seem to be accessable. Is there any way (aside from adding the campaign object to every item in my array) of getting that value?
Thanks in advance.
You can access the parent scope by using $parent
<div class="row-fluid" ng-repeat="message in messages.current|filter:'draft'">
{{ message.subject }} ... {{ $parent.campaign.name }} ...
</div>
This is a way that works that doesn't use $parent. It searches upwards through the nested scopes to find the object you're using, however many scopes it has to go through.
In the scope that contains the list, you can define an object with the list as a property, like this:
$scope.obj = {};
$scope.obj.items = ['item1','item2','item3'];
Then have the ng-repeat look like this:
<div ng-repeat="item in obj.items | filter:'item3' track by $index">
{{obj.items[ obj.items.indexOf(item) ]}}
</div>
(you need to use obj.items[ obj.items.indexOf(item) ] rather than obj.items[ $index ] because $index is the index of the filtered array, not the original)
The reason this works is because while obj doesn't exist in the current scope, as you attempt to access its property, Angular will look above the current scope rather than give you an error (if you just tried {{obj}} it would be undefined, and Angular would be happy with giving you nothing instead of looking through higher scopes). This is a helpful link about nested scopes: http://www.angularjshub.com/examples/basics/nestedcontrollers/
In my case I needed the track by $index, because I had an input with ng-model bound to an item in the array, and whenever the model updated, the input would blur because I think the HTML was being re-rendered. A consequence of using track by $index is that items in the array with identical values will be repeated. If you modify one of those other than the 1st one, weird things will happen. Maybe you can filter for uniqueness to avoid that.
I'm relatively new to AngularJS, so please comment if there is anything big I'm missing. But this works, so I'm using it at least.
Another method might be to pass parent scope as a scope variable to the directive i.e.
<my-directive
md-parent-scope="this"
ng-repeat="item in items"></my-directive>
It's a bit messy, but you have more control over what the parent actually is and can pass anything in.

Categories