How do I access properties from within an Ember 2 component? - ember.js

I am having some trouble accessing properties passed to my Ember component, which is as follows:
import Ember from 'ember';
export default Ember.Component.extend({
isRowEditorActive: function() {
return this.get('items').length > 0;
}.property('items'),
actions: {
// My actions here
}
});
The items (list of strings) that I pass in, can be accessed without problems within the template {{line-items-table items=['asd', 'asd']}}
However trying to get them within the component just returns undefined. Any suggestions?

As #kristjan says, you'll need to define your items for the line-item-table in the parent.
This is due to that the current version of handlebars don't support inline arrays, https://github.com/wycats/handlebars.js/issues/1058

Related

dynamic looping on a controller property

I am using ember 2.17.
I added this property to a controller:
export default Controller.extend({
newAttachments: new Array()
...
})
I add elements in it through this controller action:
setAttachment(file) {
console.log('trying');
this.get('newAttachments').push(file);
}
When I use the action, the message is displayed in the console, and in Ember inspector I can see the array is no longer empty :
However, the following code in the view has no output :
{{#each newAttachments as |file|}}
<p>in loop</p>
{{/each}}
Why is it not displaying anything? In a component it would work, why not here ?
Ember can't observe native arrays. Therefor the framework doesn't know that a value is pushed into the array. You should use ember's own Ember.NativeArray and it's pushObject method instead. That one ensures that the framework is informed if an entry is added to or removed from array. Changed code would look like this:
import { A } from '#ember/array';
export default Controller.extend({
newAttachments: A(),
setAttachment(file){
this.get('newAttachments').pushObject(file)
}
})
You shouldn't add the array as a property of an EmberObject as this might introduce a leak between instances. That's not a production issue in that case cause controllers are singletons in ember.js. But you might see strange behavior in tests. Refactoring for native classes will resolve that issues as class fields are not leaked between instances. For old EmberObject based classes initializing the value in init hook or using a computed property are common ways to deal with that issue:
// computed property
import { computed } from '#ember/object';
import { A } from '#ember/array';
export default Controller.extend({
newAttachments: computed(() => A()),
});
// init hook
import { A } from '#ember/array';
export default Controller.extend({
init() {
this._super(...arguments);
this.set('newAttachments', A());
}
});
Please note that you don't need to use get() if running Ember >= 3.1.

Ember tooltip pass variable

I am building an Ember tooltip module to create dynamic content on hover.
<div class="custom-tool-wrapper">
{{#custom-tool-tipster
side="right"
content=(or getContent question.id)
contentAsHTML=true
class="tool-tipster-field"}}
Preview
{{/custom-tool-tipster}}
</div>
in the ember controller - the function doesn't return the variable "question.id" --- it comes back as 0 always - when it should be a string "q-1"
export default Ember.Component.extend({
getContent(tips){
console.log("tips1")
console.log("question", tips);
},
});
I think what you're actually trying to achieve is best done via computed property on the question model object (your question is still really vague).
content: computed('id', function(){
//this.tips is a part of the model object
//compute and return whatever the content is
return "content";
}
and then just say:
{{#custom-tool-tipster
side="right"
content=model.content
contentAsHTML=true
class="tool-tipster-field"}}
Preview
{{/custom-tool-tipster}}
If you needed to actually invoke a function (which it's rare to think of an instance where the computed property isn't a better solution whenever state is involved), you would use a custom handlebars helper.
(or a b) is (a || b) and isn't function invocation like you're attempting if you're using the ember truth helpers lib for the or helper. It looks like you're trying to accomplish what ember-invoke allows
import Ember from 'ember';
import { helper } from '#ember/component/helper';
export function invokeFunction([context, method, ...rest]) {
if (typeof context[method] !== 'function') {
throw new Error(`Method '${method}' is not defined or cannot be invoked.`);
}
return Ember.get(context,method).apply(context, rest);
}
export default helper(invokeFunction);
which can be used like content=(invoke this "getContent" question.id) to invoke and return the value of a function on the passed in context object (the controller if this in the case of a route's template). Let me be clear, I think this invoke approach is a terrible idea and really gets rid of your separation of concerns and I'm not advocating that you do it. Templates shouldn't contain your logic and definitely shouldn't be calling arbitrary functions on the controller when you have such a nice facility like computed properties.

Limit to the first 10 elements of a model in the controller

I'm struggling to understand how promises work in the controller. I'd like to display just the first 10 sortedShips in my template but I can't find a way to get slice(0,10) working in my controller.
How can I limit sortedShips or a new property to the first 10 elements only?
app/controllers/index.js
import Ember from 'ember';
export default Ember.Controller.extend({
shipSort: ['name:asc'],
sortedShips: Ember.computed.sort('model.ships', 'shipSort').property('model.ships')
});
Not sure what split() is, but Ember's computed.filter function should do the trick:
import Ember from 'ember';
export default Ember.Controller.extend({
shipSort: ['name:asc'],
// You don't need the .property() here, Ember does that for you
sortedShips: Ember.computed.sort('model.ships', 'shipSort'),
firstTenShips: Ember.computed.filter('sortedShips', function(ship, index) {
return (index < 10);
})
});

Make data from store app-wide available?

I've got a base-route encapsulating all other routes in my application.
I'd like to make the categories I retrieve from the store via this.store.find('category') available everywhere in my application.
I tried retrieving it in the base-controller with this code:
import Ember from 'ember';
export default Ember.ArrayController.extend({
// retrieve all categories as they are needed in several places
categories: function() {
return this.get('store').find('category');
}
});
and creating an alias from child-controllers via:
categories: Ember.computed.alias('controllers.base.categories')
but it gives this error-message:
Uncaught Error: Assertion Failed: The value that #each loops over must be an Array. You passed function () {
return this.get('store').find('category');
}
How can I solve my problem?
Make it a computed property.
categories: function() {
return this.get('store').find('category');
}.property()
Controllers are being deprecated in 2.0, so I'd look into using a service architecture instead of a base controller architecture.

Computed Property Macros with Ember CLI

I'm attempting to DRY up my application and move some functionality into macros with Ember CLI. After reading this article, I thought I could get things working but I'm getting an undefined is not a function TypeError: undefined is not a function error when trying to use the macro with any arguments. If I don't pass any arguments, ember doesn't throw the error. To generate the file I'm using the command ember generate util calc-array
// utils/calc-array.js
import Ember from 'ember';
export default function calcArray(collection, key, calculation) {
return function() {
...
}.property('collection.#each');
}
// controller/measurements.js
import Ember from 'ember';
import calculate from '../../utils/calc-array';
export default Ember.ArrayController.extend({
high: calculate(this.get('model'), 'value', 'high'),
...
});
this.get('model') causes the problem - this points to global object, not controller instance. Pass the string (i.e. model) and use this.get inside computed property.
Also collection.#each will not work, it's not a valid path.
Summing it up:
export default function calcArray(collectionPath, key, calculation) {
return function() {
var collection = this.get(collectionPath);
...
}.property(collectionPath + '.#each');
}