Helper loses parent context when wrapped in a custom block - javascript

I have a block of Handlebars that was rendering fine until I wrapped it in a custom block. Specifically there is a call to the parent context to get the Currency type. Bear in mind that this Handlebars block is wrapped in an each:
{{#each this.SubscriptionOptions.MonthlySubscriptions}}
So clearly I know where the problem is, I'm just not sure how to solve it.
Here is the block of Handlebars:
<p class="lead" style="font-size:40px">
{{#ifGreaterThanZero PricePerBillingPeriod}}
<strong>{{currency ../Currency}}{{priceFormat PricePerBillingPeriod}}</strong>
{{else}}
<strong>FREE</strong>
{{/ifGreaterThanZero}}
</p>
The piece that is now failing is this:
{{currency ../Currency}}
Here is the ifGreaterThanZero helper code:
Handlebars.registerHelper('ifGreaterThanZero', function(value, options) {
var intVal = parseInt(value);
if (intVal) {
return options.fn(this);
} else {
options.inverse(this);
}
});
I took a look at this, and it is in fact the subscription option itself, so it contains the PricePerBillingPeriod for example.
The question is, how do I get it so that reaching the parent context works again?

Okay, so I figured it out. You have to go up one more level. It's not necessarily parent context in the object sense, it's helper parent context and so the deeper you go, it's moving up the stack like a directory structure.
The new code looks like this:
<strong>{{currency ../../Currency}}{{priceFormat PricePerBillingPeriod}}</strong>

Related

Reusing a blaze template, how do I access other template's helper functions?

I'm trying to set up an MDC dialog warning. Instead of copy-pasting it into every view that requires it, I'm wrapping the dialog in its own template. The template seems to work, the dialog opens up and functions as normal, however, I can't set a helper function for it that works. I tried using the helper function of the parent template, and even creating the new template its own js file. Neither of these solutions grab the data correctly.
<template name="transactionAlert">
...
<div class="mdc-dialog__content" ><p>Are you sure you wish to continue with this transaction? It could cost up to: <b class="warning-value">${{maxCost}} USD</b></p>
...
</template>
<template name="transactionCreate">
...
{{>transactionAlert}}
</template>
Template.transactionAlert.onCreated(function transactionAlertOnCreated() {
console.log('test')
})
Template.transactionAlert.helpers({
maxCost(){
console.log('test 2')
const instance = Template.instance()
return instance.maxTxCost.get().toString().slice(0,5);
}
})
I tried using the helper function of the parent template
Such problems are often caused by design issues, rather than missing or wrong implementation. If we consider the your transactionAlert to be stateless (it does not contain any relevant view logic or internal state management) then it should also neither access properties nor helpers that are out of it's scope.
Otherwise you will create such a tight coupling that it will throw back in your face in two years or so (when the refactoring session is calling).
In contrast the responsibilities of the parent Template are to
manage state of the data (subscriptions, data post-processing etc.)
check the conditions, whether the transactionAlert should appear or disappear
pass the proper parameters to the transactionAlert Template
As a consequence you may design your transaction alert as a parameterized template:
<template name="transactionAlert">
...
<div class="mdc-dialog__content" ><p>Are you sure you wish to continue with this transaction? It could cost up to: <b class="warning-value">${{maxCost}} USD</b></p>
...
</template>
As you can see it looks exactly the same. The difference is, that you remove the Template.transactionAlert.helpers and cause the Template to look for maxCost being passed to the template.
Now in your parent Template you will pass the data to the transactionalert, once the condition for alerting applies:
<template name="transactionCreate">
{{#if showAlert}}
{{>transactionAlert maxCost=getMaxCost}}
{{/if}}
</template>
where the helper is now:
Template.transactionCreate.helpers({
showAlert () {
return Template.instance().showAlert.get()
},
getMaxCost(){
const instance = Template.instance()
return instance.maxTxCost.get().toString().slice(0,5);
}
})
Because you need reactivity to show/hide the alert you will make use of the Template's internal Tracker:
Template.transactionCreate.onCreated(function () {
const instance = this
instance.showAlert = new ReactiveVar(false)
instance.autorun(() => {
const maxCost = instance.maxTxCost.get()
if (/* max cost exceeds limit */) {
instance.showAlert.set(true)
} else {
instance.showAlert.set(false)
}
})
})
Edit: Additional information on reactivity
Reactivity is a main concept of Meteor's client ecosystem. It bases on the Tracker package, which is linked to any Template instance. The guide to the reactive data stores explains the concept a bit further: https://guide.meteor.com/data-loading.html#stores

Handlebars decorator in loop

For few days, I've begun to use Handlebars and I'm currently trying Decorators. I've understood how it works when it remains simple :
Ex: decorate the name alone : bill gaTes -> Mr Bill GATES
I then tried to use decorator in a "each" loop, to do exactly the same as before but with a list of people instead of just one. The problem is: when I am in the decorator function, I need to know which element (from the array) I am looking at.
So I would like to either give in argument the index of the "each" loop (then, as I have access to the data, I could retrieve the current element) or the current element.
I tried to use #index (which usually works well), but I get undefined when debugging.
And I can't find a way to get the current element in my decorator.
Here is the code:
index.html
<!doctype html>
<html>
<head>
<title>Test Handlebars Decorators 4</title>
</head>
<body>
<div id = "main"> </div>
<script id = "rawTemplate" type = "text/x-handlebars-template">
{{#each this}}
Decorated: {{formatGenderHelper this}}
{{* activateFormatter #index}}
<br />
{{/each}}
</script>
<script type = "text/javascript" src = "js/lib/handlebars.min-latest.js"></script>
<script type = "text/javascript" src = "js/lib/jquery-2.2.4.min.js"></script>
<script type = "text/javascript" src = "js/data.js"></script>
<script type = "text/javascript" src = "js/index.js"></script>
</body>
</html>
data.js
var data = [{
"firstname": "Bill",
"lastname": "Gates",
"gender": "MALE"
}, {
"firstname": "Hillary",
"lastname": "Clinton",
"gender": "FEMALE"
}];
function formatMale(author) {
return "M. " + author.firstname + " " + author.lastname.toUpperCase();
}
function formatFemale(author) {
return "Mme " + author.firstname + " " + author.lastname.toUpperCase();
}
Handlebars.registerDecorator('activateFormatter', function(program, props, container, context) {
var genderHelper;
var gender = context.args[0] || context.data.root[0].gender;
switch(gender) {
case "MALE":
genderHelper = formatMale;
break;
case "FEMALE":
genderHelper = formatFemale;
break;
default:
console.log('Gender format not set. Please set before rendering template.');
genderHelper = function() {};
}
container.helpers = {
formatGenderHelper: genderHelper
};
});
index.js
// Get the template
var rawTemplate = $('#rawTemplate').html();
// Compile the template
var compiledTemplate = Handlebars.compile(rawTemplate);
// Apply the template on the data
var content = compiledTemplate(data);
// Finally, re-inject the rendered HTML into the DOM
$('#main').html(content);
If you need further information, please let me know.
Thanks you for helping :)
There are two issues that make your example fail. One is a slight issue with your code, and the other is the way decorators seem to work, which is essentially just "not in loops" (unless you're using partials, see the last section of this answer).
First, you haven't told the decorator what to do with the #index that you're passing to it. You could change the decorator function, but since you have the decorator inside the #each block, the this is the same one that gets passed to formatGenderHelper, meaning that we can pass the decorator this.gender which resolves to just the gender string. This means that the writer of the template knows exactly what they're passing to the decorator, and the logic isn't trying to second-guess what the template is telling it.
index.html
...
{{#each this}}
Decorated: {{formatGenderHelper this}}
{{* activateFormatter this.gender}}
<br />
{{/each}}
...
Also, I figure you are basing your example on Ryan Lewis's sitepoint demo. One problem/limitation his code has that isn't immediately obvious (because in the simple case it isn't even an issue), is that his decorator overwrites of all of the available helpers except for the formatting one it provides. To avoid "Error(s): TypeError: Cannot read property 'call' of undefined" errors when doing things that are a little bit more complex, I recommend using this in your decorator function.
data.js
Handlebars.registerDecorator('activateFormatter', function(program, props, container, context) {
// leaving out the code here for brevity
container.helpers = Object.assign({}, container.helpers, {
formatGenderHelper: genderHelper
});
});
The second issue is a general one with using decorators in loops, that is simply a result of the way Handlebars works with decorators. According to the decorators API doc,
Decorators are executed when the block program is instantiated and are passed (program, props, container, context, data, blockParams, depths).
This means (afaict) that when the template for the block is first instantiated, meaning before any of the other helpers or programs that will need to run on the block, it executes the decorator, and the decorator makes the modifications it needs, before executing the program that created the block (in this case the #each). This means that to get the decorator to differentiate each iteration of the #each differently, it needs to run in a grandchild block of the #each, so it is executed after the #each but before the formatGenderHelper, however, if you're doing this and redefining the decorated helper with each iteration based on the iterated-over context, you're better off just registering a helper that has the gender formatting logic baked in.
However, that's sort of a non-answer. So, to answer your question, I've found that you can make handlebars do what you're trying to do, but it's sort of a hack. Since the trick is that you need the block with the decorator to be rendered from a sub-sub block of the #each we can render it in a partial. To do this we can stick it in another file and register that as a partial, but that's not really convenient, so we have two better options. 1) we can wrap the code in an undefined partial-block and let Handlebars render it as the fallback block when that partial fails, or (the slicker option) 2) we can use the provided builtin decorator #*inline to define an inline partial.
Fail-over partial block method
index.html
...
{{#each this}}
{{#>nothing}}
Decorated: {{formatGenderHelper this}}
{{* activateFormatter this.gender}}
<br />
{{/nothing}}
{{/each}}
...
#*inline method
index.html
...
{{#*inline "formattedAuthor"}}
Decorated: {{formatGenderHelper this}}
<br />
{{/inline}}
{{#each this}}
{{#>formattedAuthor}}
{{* activateFormatter this.gender}}
{{/formattedAuthor}}
{{/each}}
...
The key here is that we use our *activeFormatter decorator to dynamically reconfigure the partial each time it gets called. The second, inline example demonstrates this more clearly. There may certainly be other good use-cases, but this is where I see decorators really shining, i.e. allowing us to dynamically reconfigure the logic or helpers of partials right from where we call them.
However, there is a caveat: if our partial uses a helper that is only provided in a decorator that is called from outside of that partial's definition (like we have above in example 2) the partial will not be able to find the helper if that decorator is not called in the right place. This is also why it's better to use either of the two methods above for providing the partial: we keep the definition of the partial in the same file as the decorator call, so we can know that the helper is only provided dynamically and not registered globally.

RiotJS - How to pass events between subtags using Observable pattern?

Im not really sure if Im understanding correctly the way observables work and how to get references from mounted tags. I have a component. Within this component we have a component and a component. The purpose is to avoid coupling between components. Because of that, I would like that my search component triggers an event when a search is done(a button is clicked). This event should be caught by the component which will filter the collection data based on the search.
The index.html file load the tag by using:
index.html
riot.mount(".content", "page", null);
The page is defined as follow:
page.js
<page>
<!-- Search tag controls -->
<search id="searchTag"></search>
<!-- Collection data to display -->
<collection id="collectionTag"></collection>
</page>
The component script is briefly defined like:
search.js
var self = this;
riot.observable(self);
<!-- This function is called when the user click on the button. -->
self.filtering = function()
{
<!-- We get data from inputs -->
var info = Getting data from inputs;
<!-- Trigger the event hoping that someone will observe it -->
self.trigger("filterEvent", info);
}
How can I make the component observe for that event?
To me it seems that I should be able to get references from search tag and collection tag in the page.js. By doing so I could connect the events like follow:
searchComponent = riot.mount('search');
collectionComponent = riot.mount('collection');
searchComponent.on('filterEvent', function()
{
<!-- Trigger function to filter collection data -->
collectionComponent.trigger('filterData');
});
Right now I cannot make it work like that.
At the point of execution, searchComponent and collectionComponent are not defined.
I tried also getting references of these component by using this.searchTag and this.collectionTag instead of mounting them but at the time the code is executed, the components have not been mounted and so I dont get a reference to them.
Any ideas to make it work?
Inspired by the answer given by #gius, this is now my preferred method for sending events in RiotJS from one tag to another.. and it is great to work with!
The difference from #gius approach being that, if you use a lot of nested tags, passing a shared Observable to each tag falls short, because you would need to pass it again and again to each child tag (or call up from the child tags with messy this.parent calls).
Defining a simple Mixin, like this (below), that simply defines an Observable, means that you can now share that in any tag you want.
var SharedMixin = {
observable: riot.observable()
};
Add this line to your tags..
this.mixin(SharedMixin);
And now, any tag that contains the above line can fire events like..
this.observable.trigger('event_of_mine');
..or receive events like this..
this.observable.on('event_of_mine',doSomeStuff());
See my working jsfiddle here http://jsfiddle.net/3b32yqb1/5/ .
Try to pass a shared observable to both tags.
var sharedObservable = riot.observable();
riot.mount('search', {observable: sharedObservable}); // the second argument will be used as opts
riot.mount('collection', {observable: sharedObservable});
And then in the tags, just use it:
this.opts.observable.trigger('myEvent');
this.opts.observable.on('myEvent', function() { ... });
EDIT:
Or even better, since your search and collection tags are child tags of another riot tag (page) (and thus you also don't need to mount them manually), you can use the parent as the shared observable. So just trigger or handle events in your child tags like this:
this.parent.trigger('myEvent');
this.parent.on('myEvent', function() { ... });
Firstly I do not understand your file structure !
In your place I would change filenames :
page.js --> page.tag
search.js --> search.tag
And i dont see your search tag in search.js code.
So I dont see your Collection tag file ...
Are you sure that this one use this code ?
riot.observable({self|this});
Because it's him who will receive an Event.
For me when I use Riot.js(2.2.2) in my browser, if I use
searchComponent = riot.mount('search');
searchComponent will be undefined
But with this code you can save your monted tag reference :
var searchComponent ={};
riot.compile(function() {
searchComponent = riot.mount('search')[0];
});
Another option is to use global observables, which is probably not always best practice. We use Riot's built in conditionals to mount tags when certain conditions are met rather than directly mounting them via JS. This means tags are independent of each other.
For example, a single observable could be used to manage all communication. This isn't a useful example on its own, it's just to demonstrate a technique.
For example, in a plain JS file such as main.js:
var myApp = riot.observable();
One tag file may trigger an update.
var self = this;
message = self.message;
myApp.trigger('NewMessage', message);
Any number of other tag files can listen for an update:
myApp.on('NewMessage', function(message) {
// Do something with the new message "message"
console.log('Message received: ' + message);
});
Maybe overkill but simple. let riot self observable
riot.observable(riot);
So you can use
riot.on('someEvent', () => {
// doing something
});
in a tag, and
riot.trigger('someEvent');
in another.
It's not good to use global variable, but use an already exists one maybe acceptable.

MV* in Polymer, models and services as polymer-elements?

Say I want two views (polymer-elements) to share a model for example.
In Angular the model would live in a singleton service that gets injected into the views, both views read from the same source.
I tried emulating this approach with Polymer so I can do something like:
<polymer-element name="view1">
<template>
<my-model></my-model>
...
</template>
...
</polymer-element>
<polymer-element name="view2">
<template>
<my-model></my-model>
...
</template>
...
</polymer-element>
I like this approach because it's a declarative way of defining dependencies, and it basically works the same as <core-ajax> and other "out of the box" Polymer elements.
With this way I need to wait for the domReady lifecycle callback before I can interface with any element declared in the template, so this is where I'm holding my initialisation logic at the minute. The problem is that this callback gets called once for each <my-model> element declared (so <my-model> would be initialised twice in this example because it's present both in <view1> and <view2>). To make sure that my model follows the singleton pattern I have to move state outside of the element instance, something like this:
<polymer-element name="my-model">
<script>
(function(){
// private shared state
var instances = [], registered; // pattern variables
var foo; // state, model, whatever
// element init logic
Polymer('my-model', {
// Polymer callbacks
domReady: function(){
if (registered === (registered=true)) return;
// singleton init logic
foo = 'something';
// event handlers
this.addEventListener('foo', function(){ foo += 'baz'; });
},
attached: function() { instances.push(this); },
detached: function(){
instances = instances.filter(function(instance){
return instance !== this;
}.bind(this));
},
// element API
update: doSomething,
get state() { return foo; }
});
// private functions
function doSomething(){ foo += 'bar' }
})();
</script>
</polymer-element>
So it works but it looks wrong to me. Is using <polymer-element> generally incompatible with the singleton pattern? Should I move away from Polymer for models and services? How do Polymer core-elements get away with it?
[EDIT] I added some event listeners to the initialising code above. They're only registered in one instance to avoid the listeners triggering multiple times across multiple instances. What would happen if the instance where the event handlers are declared gets removed? Will that not break the asynchronous logic?
I'd go like this:
Define your model on the main page and call it from your views.
if it gets removed you could:
1 - listen for the "detached" lifecycle callback and inside it register it imperatively or
2 - store stuff on a prototype build in a higher level object and access it the way you fancy the most.
3 - if all fails, (i'm not sure it will but i guess so as i've yet to use this kind of implementation, as of now i talk to php and pass around objects i need persistent) you could use a "prepareForRemoval" knowing you will leave the instance, local storage your stuff and do number 1 then "recoverFromRemoval" if you know what i mean by camel casing prototype suggestions.
Anyways i'm not very fond of singletons. Polymer is powerful front-end stuff but i'm not sure it's the best way to go about it.
in the API docs they do not mention the possibility of getting it cut off (as you can see)
but i honestly think you're right and you would lose your stuff.
That's just my 2 cents actually just a inellegant sollution i came up for at this very moment, maybe #ebidel, #DocDude or #dodson can help us in that matter but you can't really tag em here on SO i'll tag em on G+ for us, you sir got me intrigued.
BTW why would you move away from your main page? there's no point for it in polymer you should change the content dynamically not get away from it. what would be the usage scenario?
ps.: sorry, i hate capitalizing proper nouns.Get over it
EDIT (wouldn't fit on the comments):
I expressed myself wrong. Anyways i strongly think i wasn't understanding what you wanted.
Well, if i got it right this time yes it will fire multiple times (they are supposed to), but it shouldn't cut others out once a particular view gets removed.
As for your initialisation logic i would go about adding a listener to the window or document (i think window is more advisable) itself waiting for the 'polymer-ready' event.
"To make sure that my model follows the singleton pattern I have to
move state outside of the element instance"
Yes thats right. but don't wait for the domready in it's prototype, instead use a construct or contruct-like and call it it as the callback of the aforementioned event listener. i'll edit my answer to make it clearer (if it's not, let me know) when i get back home. i hope you got i meant.
if you don't i'll be back soon.
In browsers, window == singleton object by definition.
Simple use:
var window.instances = [];
var window.registered;
var window.foo;
instead.
Another way is to use Polymer core-meta element:
<core-meta id="x-foo" label="foo"></core-meta>
<core-meta id="x-bar" label="bar"></core-meta>
<core-meta id="x-zot" label="zot"></core-meta>
<core-meta id="apple" label="apple" type="fruit"></core-meta>
<core-meta id="orange" label="orange" type="fruit"></core-meta>
<core-meta id="grape" label="grape" type="fruit"></core-meta>
<h2>meta-data</h2>
<template id="default" repeat="{{metadata}}">
<div>{{label}}</div>
</template>
<h2>meta-data (type: fruit)</h2>
<template id="fruit" repeat="{{metadata}}">
<div>{{label}}</div>
</template>
<script>
document.addEventListener('polymer-ready', function() {
var meta = document.createElement('core-meta');
document.querySelector('template#default').model = {
metadata: meta.list
};
var fruitMeta = document.createElement('core-meta');
fruitMeta.type = 'fruit';
document.querySelector('template#fruit').model = {
metadata: fruitMeta.list
};
});
</script>

KnockoutJS data update writes internal function to UI

I'm having this odd issue when I update my viewmodel...basically with every update, there appears to be a random chance that each observable will contain this data:
function observable() {
if (arguments.length > 0) {
// Write
// Ignore writes if the value hasn't changed
if ((!observable['equalityComparer']) || !observable['equalityComparer'](_latestValue, arguments[0])) {
observable.valueWillMutate();
_latestValue = arguments[0];
observable.valueHasMutated();
}
return this; // Permits chained assignments
} else {
// Read
ko.dependencyDetection.registerDependency(observable); // The caller only needs to be notified of changes if they did a "read" operation
return _latestValue;
}
}
I've been using KnockoutJS for a while, and I've never seen anything like this. My guess is that it has something to do with my template binding, but I'm really not sure. I'm going to dig into it, but I figured I'd post it here in case anyone else is having this issue, or has a solution. Like I said, it doesn't happen consistently, only on occasion.
//// More Information ////
So Matt below referenced this (http://stackoverflow.com/questions/9763211/option-text-becomes-a-function-string-after-updated-with-fromjs), which is roughly the same issue. The only difference is that I'm using the native template binding in a style like this:
<div data-bind="template: {name: 'issueTemplate', data: incidents}"></div>
<script id="dashboardIssueTemplate" type="text/html">
<!--ko foreach: $data-->
<div data-bind="text: title"></div>
</script>
It was my assumption that KnockoutJS handled the unwrapping by itself when you pass the observableArray into the template binder. I know I can't say "title()" in this example, because that doesn't exist. Am I supposed to be binding with a command like $root.title()?
//// Even More Information ////
It appears that this problem occurs as a result of having two "applyBindings" on one page. My application contains an external widget which adds it's DOM to the host page DOM at runtime. That widget is using the ko.applyBindings(vm, ROOTNODE) syntax which should allow for the host page to run it's own ko.applyBindings(hostVm).
In fact, it does, and it works correctly every refresh. The problem however is when the host page does a viewModel update with no refresh. Somehow, the UI rendering spits out this internal function on EVERY data-bound node. I've debugged through KnockoutJS and actually confirmed that the viewModel and rootNode are correct...something outside of the actual binding is taking over.
This has something to do with the "()" appended onto the data object in the template. What I've found is that during the first render (page load) writing the template like this:
<div data-bind="template: {name: 'issueTemplate', data: incidents}"></div>
<script id="dashboardIssueTemplate" type="text/html">
<div data-bind="text: title"></div>
</script>
works just fine. However, once you run the update on the observableArray my "title" object becomes that function. If I write the template using this style:
<div data-bind="text: title()"></div>
It seems to work on every update.
I am not certain why this is the solution. From the looks of it, the data object being passed to the Knockout binder is the exact same on both page load and update. I'll post this as an answer, but I'm not marking it as an answer until I understand why this is happening.

Categories