Ember hasMany and size of array(s) - ember.js

I have a relation hasMany and I want get the next index number. So, in my controller I have :
nextStepIndex: Ember.computed(function () {
return this.get('floor').get('steps').get('length') + 1;
}).property('nextStepIndex')
I used too :
nextStepIndex: Ember.computed(function () {
return this.get('floor').get('steps.length') + 1;
}).property('nextStepIndex')
But the length is always 0. The step is not saved and the floor too. All object was just created with createdRecord and the step is added with pushObject.
I saw in the steps relation they have a lot or array :
DS.PromiseManyArray
DS.PromiseArray
ArrayProxy
MutableArray
Array
My "step" object is in the ArrayProxy. So when I call the length method what size I will obtain?
So, if I can get the child record in the {{#each}} template directive, I expect to have the right size when I call the length method.
I known I can get the ArrayProxy size, but I don't want make a assumption on the implementation.

It looks as if your "property" is not set correctly
property('floor.steps.[]')
Also keep in mind it won't get triggered unless you call it from template or use it in another relation in component/controller.
Btw if your're dealing with async "steps" then computed property wont just do it out of box. You would need to use
nextStepIndex: function() {
var stepsPromise = this.get('floor.steps');
return DS.PromiseArray.create({
promise: stepsPromise.then(function(steps) {
// return your logics here
})
});
}.property('floor.steps.[]')

Related

Recompute Ember custom helper on change of a controller property that is passed to the helper

I'm changing the value of a property in my controller and the helper fails to recompute them.
Sample code here:
My template looks like,
{{#if (my-helper info)}}
<span>Warning</span>
{{/if}}
In my controller,
changeAction: function() {
let that = this,
info = that.get("info");
set(info, "showWarning", true);
}
my helper,
import { helper as buildHelper } from '#ember/component/helper';
export default buildHelper(function(params) {
let that = this,
info = that.get("info");
if(info.showWarning ) {
return true;
} else {
return false
}
});
I see several issues with your code:
The template helper seems to get an object as it's first and only position param: {{my-helper info}} while info is { showWarning: true }. A template helper does recompute if the value passed it changes but not if a property of that value changes. A quick fix would be {{my-helper info.showWarning}}.
In your template helper your are trying to access the property on it's this context. As far as I know that's not supported. As you are using a positional param and it's the first one, it's available as first entry inparams array. So your template helper should look like:
export default buildHelper(function([info]) {
if(info.showWarning ) {
return true;
} else {
return false
}
});
What version of Ember are you using? If it's >= 3.1 you don't need to use this.get() in your controller. If you are using Ember < 3.1 you need to use info.get() also in template helper.
But as described before I would not recommend passing an object to a template helper as it's only updated if the object itself is replaced. Changing a property of it is not enough. You might be able to do so using Class-based Helpers but I wouldn't recommend to do so as it's error prune.

How to refactor {{link-to}} helper into a function that can be used in transitionTo?

I currently use a {{link-to}} helper that was written by someone else to explicitly state the query params to pass to the next route and strip out others that are not stated. It looks like this:
//link-to example
{{#link-to 'route' (explicit-query-params fromDate=thisDate toDate=thisDate)} Link Text {{/link-to}}
//the helper
import {helper} from '#ember/component/helper';
import Object from '#ember/object';
import {assign} from '#ember/polyfills';
export function explicitQueryParams(params, hash) {
let values = assign({}, hash);
values._explicitQueryParams = true;
return Object.create({
isQueryParams: true,
values,
});
}
export default helper(explicitQueryParams);
// supporting method in router.js
const Router = EmberRouter.extend({
_hydrateUnsuppliedQueryParams(state, queryParams) {
if (queryParams._explicitQueryParams) {
delete queryParams._explicitQueryParams;
return queryParams;
}
return this._super(state, queryParams);
},
});
I've recently had a use case where I need to apply the same logic to a transitionTo() that is being used to redirect users from a route based on their access:
beforeModel() {
if (auth) {
this.transitionTo('route')
} else {
this.transitionTo('access-denied-route')
}
},
I am really struggling to see how I can refactor what I have in the handlebars helper to a re-usable function in the transitionTo() segment. I'm even unsure if transitionTo() forwards the same arguments as {{link-to}} or if I will have to fetch the queryParams somehow from a different location.
Any insight would be greatly appreciated.
Well first off, tapping into private methods like _hydrateUnsuppliedQueryParams is risky. It will make upgrading more difficult. Most people use resetController to clear stick query params. You could also explicitly clear the default values by passing empty values on the transition.
But, ill bite because this can be fun to figure out :) Check this ember-twiddle that does what you're wanting.
If you work from the beginning in the transitionTo case, we can see that in the router.js implementation:
transitionTo(...args) {
let queryParams;
let arg = args[0];
if (resemblesURL(arg)) {
return this._doURLTransition('transitionTo', arg);
}
let possibleQueryParams = args[args.length - 1];
if (possibleQueryParams && possibleQueryParams.hasOwnProperty('queryParams')) {
queryParams = args.pop().queryParams;
} else {
queryParams = {};
}
let targetRouteName = args.shift();
return this._doTransition(targetRouteName, args, queryParams);
}
So, if the last argument is an object with a query params obj, that's going directly into _doTransition, which ultimately calls:
_prepareQueryParams(targetRouteName, models, queryParams, _fromRouterService) {
let state = calculatePostTransitionState(this, targetRouteName, models);
this._hydrateUnsuppliedQueryParams(state, queryParams, _fromRouterService);
this._serializeQueryParams(state.handlerInfos, queryParams);
if (!_fromRouterService) {
this._pruneDefaultQueryParamValues(state.handlerInfos, queryParams);
}
}
which has the _hydrateUnsuppliedQueryParams function. So, to make this all work, you can't share the function directly from the helper you've created. Rather, just add _explicitQueryParams: true to your query params. Job done :)
The link-to version is different. The query params use
let queryParams = get(this, 'queryParams.values');
since the link-to component can take a variable number of dynamic segments and there needs to be some way to distinguish between the passed dynamic segments, a passed model, and query params.

Call method from other template after rendering

Using Meteor I got a template which consists of 2 other templates A & B.
A got a <select> Tag and B got a <canvas> on which something will drawn depending on the select of template A.
When the selection changes, A should call someMethod` of B, and this method should get some data from the db and then draw the canvas. How can I achieve this behaviour?
Simply using Template.B.someMethod = function (bla) ... and then in the JS of A calling Template.B.someMethod(hi) causes an error because someMethod accesses the DOM canvas Element of B which is not rendered when A changes for the first time at the loading.
What would be a fitting way to call the method? Or should I merge the two templates together?
no need to combine A and B templates. the purpose of the parent here is to coordinate data changes between A and B. i.e. you won't be calling functions on B, but rather giving it the data it needs so it can respond.
P (the parent) can push into A a function that A calls when its data changes via the select. P can then push that data into B through Meteor's normal reactivity. B can then respond to that change.
e.g.
<template name="P">
{{A changeHandler=getChangeHandler}}
{{B selection=selectedData}}
</template>
P.js:
Template.P.onCreated(function() {
this.data = new ReactiveVar();
});
Template.P.helpers({
getChangeHandler() {
let template = Template.instance();
return function(data) {
template.data.set(data);
}
},
selectedData() {
return Template.instance().data.get();
}
});
A.js:
Template.A.onCreated({
this.changeHandler = new ReactiveVar(Template.currentData().changeHandler); // i think this is right
});
and whenever A data changes:
let changeHandlerFn = template.changeHandler.get();
if (_.isFunction(changeHandlerFn)) {
changeHandlerFn(updatedData);
}
Besides #zim's answer, you can also use a variation of https://stackoverflow.com/a/43687311/5108796:
HTML (actually Spacebars)
<template name="Parent">
{{> Child1 sharedVar1=sharedVar}}
{{> Child2 sharedVar2=sharedVar}}
</template>
JavaScript
import { ReactiveVar } from 'meteor/reactive-var';
// Just initialize the variable. Could also be within the scope of a template.
var myReactiveVar = new ReactiveVar();
Template.Parent.helpers({
// This is what will be sent to Child1 and Child2.
sharedVar: function () {
return myReactiveVar;
}
});
Template.Child1.events({
'change select': function (event, template) {
// This will trigger a re-execution of Child2 autorun.
template.data.sharedVar1.set(myNewValue);
}
});
Template.Child2.onCreated(function () {
var sharedVar2 = this.data.sharedVar2;
this.autorun(function () {
// As usual, this is reactive.
var newValue = sharedVar2.get();
// Perform some operation using newValue…
});
});
(of course you can split these into several JS files)

Meteor.js - Calling a template helper within template rendered

I'm using Meteor 1.0.
I have a Template.*name*.rendered function that makes a number of calculations. At the end of the calculations, I would like the output to make its way into a Template.*name*.helpers so I can use it in the corresponding html page.
Here's a simplified version of the code:
Template.myTemplate.rendered = function () {
var x = Math.random();
Template.otherTemplate.helpers({
randomNum: x
});
}
When I call {{randomNum}} in otherTemplate, nothing happens.
I have also tried putting the Template.*name*.helpers outside of Template.*name*.rendered, in which case, I get the error:
Uncaught ReferenceError: x is not defined
Thoughts?
This isn't really the right way of going about things as the way Meteor works is by compiling templates before the application starts, rather than at run-time. Whilst something along these lines may be possible (for example by using Template.registerHelper), it would be much better to set a reactive variable to a specific value in the rendered callback and have the helper set to return that instead:
Session.setDefault('randomNum', 0);
Template.myTemplate.rendered = function () {
Session.set('randomNum', Math.random());
}
Template.otherTemplate.helpers({
randomNum: Session.get('randomNum')
});
If you'd rather use a private variable for the randomNum, have a look at ReactiveVar. It could be any reactive data source and it would work.
You used to create helpers as an object of the template but since Meteor has deprecated that you now have to create the helpers within the helper function.
Now in order to call the helper via javascript you must use this function
Template.*TemplateName*.__helpers.get('*HelperName*')(*Params*);
Its a pretty simple way of doing this and it keeps the functions out of the global scope so its pretty clean.
Here is an example of how I am using this
~~~
Template.home.events({
'click .pair': function(event) {
var _this = $(event.currentTarget);
Template.home.__helpers.get('pairDevice')(_this);
}
});
Template.home.helpers({
'devices' : function() {
return Session.get('devices');
},
'pairDevice' : function(elm) {
elm.fadeOut();
$('.home-page').addClass('paired');
var deviceList = [
{
'name' : 'Patrick\'s Phone',
'UUID' : '234123,4n123k4nc1l2k3n4 l1k23n4l12k3nc4l12'
},
{
'name' : 'Mike\'s Phone',
'UUID' : '734k23k4l2k34l2k34l2k34l2k3m'
},
{
'name' : 'Edgar\'s Phone',
'UUID' : '567k56l7k4l56k7l5k46l74k56l74k5'
}
];
Session.set('devices', deviceList);
}
});
~~~

Returning a Promise from a Computed Property

I realize there have been several questions similar to this, but none of those answers seems to be resolving my issue. My objective is to take a list of language's, and filter them so that my template can display a subset of the full list.
I started off by verifying that my computed property is working:
MyController.js
// Works as expected
languagesFiltered: function() {
return this.get('languages');
}.property('languages')
Then I added in a filter function, but here's where I ran into trouble:
MyController.js
languagesFiltered: function() {
// console.log shows that languages is actually a promise
var languages = this.get('languages');
// all of this returns a promise, but Handlebars can't handle the promise
return languages.then( function( languagesArray ) {
return languagesArray.filter( function( item, index, enumerable) {
return item.get('name') !== 'English';
});
})
}.property('languages')
I'm attempting to use the Ember.Array.filter method (http://emberjs.com/api/classes/Ember.ArrayProxy.html#method_filter). The filter seems to be working correctly, but now languagesFiltered returns a promise, and Handlebars can't handle that.
I tried one last alternative:
MyController.js
languagesFiltered: function() {
var languages = this.get('languages');
// "return languages;" works
// But "return languages.filter" returns an empty array
return languages.filter( function( item, index, enumerable ) {
console.log(item);
return true;
});
}.property('languages')
And console.log(item) never gets called. So my questions are:
What is the best way to implement the simple filter I'm after?
This is a read-only computed property, but what are best practices for handling async values in computed properties?
I'm using Ember 1.7.0-beta4, Ember Data 1.0.0-beta10, and ember-cli 0.44. I'd upgrade to Ember 1.7.0, but there's a small bug that affects another part of our app, so we're waiting until 1.7.1. Thanks for your input!
You can try returning a PromiseArray instead of just the promise.
You should be able to do something like..
languagesFiltered: function() {
// all of this returns a promise, but Handlebars can't handle the promise
var promise = this.get('languages').then( function( languagesArray ) {
return languagesArray.filter( function( item, index, enumerable) {
return item.get('name') !== 'English';
});
})
return DS.PromiseArray.create({
promise: promise
});
}.property('languages')
Now there are few better solutions. I use ember-promise-helpers.
So can keep your languagesFiltered code intact and do the following inside your hbs:
{{#each (await languagesFiltered) as|lang|}}
...
...
More – https://emberigniter.com/guide-promises-computed-properties/