I have a model like this:
App.Category = DS.Model.extend({
title: DS.attr('string'),
items: DS.hasMany('item', {async: true}),
itemCount: function() {
return this.get('items').get('length');
}.property('items')
});
and it seems I cannot use "property" there if I want to have the UI update everytime a user adds or removes items.
From what I can tell I should be using "observes", but when I use that in place of "property" the handlebars {{itemCount}} tag just renders the function itself as a string.
Any help on getting this to render properly is much appreciated.
I think you can simply use :
{{items.length}}
in your handlebars template.
There's absolutely no need for an observer, computed properties do updates themselves.
And if you really want a computed property named itemCount, it would be :
itemCount: function() {
return this.get('items.length');
}.property('items.length')
Or even better :
itemCount: Ember.computed.alias('items.length')
Like #florent-blanvillain said, just use Ember.computed.alias. But in the future, when writing computed properties based on arrays, you need to use the #each syntax to get it to respond to changes in property values:
itemCount: function() {
return this.get('items').filterBy('isSelected');
}.property('items.#each.isSelected')
Something like that. See the docs on computed properties for more info.
Related
Having an object similar to:
const data = {
tasks: {
projects: [
name:'Project Name',
filters: [
{
name:'First Project Filter',
checked:false,
onChange:(event) => {
console.log(this.checked)
}
},
...
],
...
],
...
},
...
}
The problem at hand is how to reference the checked property without drilling through the entire object.
In the case above, it throws an error because this is undefined, so referencing this.checked is invalid.
I could extract from the event data properties so that I can get the
whole reference such as tasks.projects[0].filters[0].checked, but I
am wondering if an easier method is available.
The ideal solution would be a way to reference the surrounding properties of the function without traversing the entire object. Surely the function has a way to know that it is inside of an object so maybe something like parent().checked ?
If relative: I am using node.js and react to use this object to render a filtered sidebar that works with context to filter the data-set. I don't think that is relative as this seems like a pure JavaScript OOP situation.
I have a widget model which has a shallow parent-child relationship. A given widget may be a "root" widget and not have any parent, or it may be a child widget which has a parent.
The ember data model looks like this:
export default DS.Model.extend({
name: DS.attr('string'),
parentWidget: DS.belongsTo('widget', { async: true, inverse: null }),
isSubWidget: DS.attr('boolean')
})
I'm trying to add a "displayName" property that will show the name for root widgets, or "parent name - child name" for child widgets
displayName: Ember.computed('name', 'parentWidget.name', 'isSubLob', function() {
if this.get('isSubWidget') {
return "#{this.get('parentWidget.name')} - #{#get('name')}"
}
else {
return "#{this.get('name')}"
}
})
This is not working, however. The child lob's displayName always comes as
undefined - WidgetName
The json is being returned like so:
{
"widgets": [
{
"id": 2,
"name": "Widget Name",
"is_sub_widget": true,
"parent_widget_id": 1
},
...
}
For the record, all the records are being returne by the json at the same time.
I feel like Ember should be asyncronously resolving the parent widget and the string should be updated as well, however it doesn't seem to be working. Any idea what I'm doing wrong here?
I would say you have two issues:
You're not declaring an inverse to your parentWidget relationship, which means that Ember Data is guessing the inverse (and likely guessing wrong). You should change that declaration to look like this, just to be sure:
parentWidget: DS.belongsTo('widget', { async: true, inverse: null }),
I doubt that will fix your issue, but it's good practice.
You're not waiting for your promise to resolve before trying to use the name. You've specified the parentWidget relationship as being asynchronous, which means that #get('parentWidget') will not return a model. It's going to return a promise that will eventually resolve to your model. Normally this would be fine as the computed property would just recompute when the promise resolves, except that you're not watching the proper key.
/* PS: Assuming that your comma was misplaced on this line */
displayName: Ember.computed('name', 'parentWidget', function() {
^^^^^^^^^^^^
As seen, you're only watching the parentWidget property. So if the name property on the parentWidget every updates, you won't be notified. Change that line to this and you should be good to go:
displayName: Ember.computed('name', 'parentWidget.name', function() {
Just keep in mind that the first few times through, parentWidget.name will still be undefined. It won't be the value you want until the promise resolves, which means the computed property could run several times before it does resolve.
I'm trying to get a computed value in my model to update using the .property('key') syntax. My model is like:
App.Camera = Em.Model.extend({
id: attr(),
uid: attr(),
name: attr(),
type: attr(),
refresh: attr(),
thumbnailUrl384x216: function() {
return '%#/devices/%#/thumbnail?width=384&height=216'.fmt(apiBaseUri, this.get('uid'));
}.property('uid', 'refresh'),
thumbnailUrlFull: function() {
return '%#/devices/%#/thumbnail?width=1280&height=720'.fmt(apiBaseUri, this.get('uid'));
}.property('uid', 'refresh')
});
In my camera route I am modifying the refresh variable on an interval, but it is not causing the thumbnailUrl's to update. Am I doing something wrong or does ember-model not support the .property() feature.
I'm able to use the refresh attribute in my template and I see it updating. Any ideas?
The computed value was not updating because I wasn't using the changing refresh value in the computation. Adding that fixed it.
I'm trying to push the object that populated a view into an array, but the reference is somehow getting lost. I've got an Ember view, with a defined eventManager:
FrontLine.NewProductButton = Em.View.extend({
tagName: 'button',
classNames: ['addtl_product',],
templateName: 'product-button',
eventManager: Ember.Object.create({
click: function(event, view) {
FrontLine.ProductsController.toggleProductToCustomer(event, view);
}
})
})
That view renders a bunch of buttons that are rendered with properties that come from objects in the ProductsController using the #each helper. That part works great. And when I click on any of those buttons, the click event is firing and doing whatever I ask, including successfully calling the handler function (toggleProductToCustomer) I've designated from my ProductsController:
FrontLine.ProductsController = Em.ArrayController.create({
content: [],
newProduct: function(productLiteral) {
this.pushObject(productLiteral);
},
toggleProductToCustomer: function(event, view){
FrontLine.CustomersController.currentCustomer.productSetAdditional.pushObject(view.context);
}
});
I'm trying to use that function to push the object whose properties populated that view into an array. Another place in my app (a simple search field), that works perfectly well, using pushObject(view.context). Here, however, all that gets pushed into the array is undefined. I tried using view.templateContext instead, but that doesn't work any better. When I try console.log-ing the button's view object from inside those functions, I get what I'd expect:
<(subclass of FrontLine.NewProductButton):ember623>
But either view.context or view.templateContext return undefined. How do I access the object I'm after, so I can add it to my array?
The simple answer is that it was one letter's difference:
view.content
or:
view.get('content')
provides the source object in that particular situation, rather than view.context.
(My only real challenge with Ember so far is that accessors for objects and properties vary so much from situation to situation, and there's no real documentation for that. Sometimes the object is at view.context, sometimes it's at view.content, sometimes _parentView.content, etc., etc. It would be awesome if there were a chart with the umpteen different syntaxes for accessing the same data, depending on which particular aperture you're reaching through to get it. I'm still discovering them...)
I'm trying to allow a user to create a casting and add an array of categories to this casting object. I was trying to use knockout's foreach binding to the array of categories and let users add new categories to the casting. I have created a jsfiddle to illustrate what I'm trying to explain here.
http://jsfiddle.net/msell/ueNg7/16/
The JSON object gets built up correctly as a user modifies a casting, but I cant quite get the list of castings to display.
You have several problems:
You are using Knockout 1.2.1
The foreach binding was not added until Knockout 2.0.
You are not using an observableArray
You need to modify your categories property to be a ko.observableArray(), instead of just an empty array. Otherwise Knockout will not be able to observe when you push to it, and the remove method will not exist.
Your this binding is wrong.
When called from event handlers, this will be set incorrectly. You can fix this in various ways, discussed in length in the Knockout documentation, but one easy fix is to change the references to viewModel instead of to this.
To fix all these, you should upgrade to Knockout 2.0, and change your view model declaration to be
var viewModel = {
name: ko.observable(''),
description: ko.observable(''),
categories: ko.observableArray(),
categoryToAdd: ko.observable(''),
removeCategory: function(category) {
viewModel.categories.remove(category);
},
addCategory: function() {
viewModel.categories.push(new Category(viewModel.categoryToAdd()));
viewModel.categoryToAdd('');
}
};
Here is a corrected JSFiddle: http://jsfiddle.net/ueNg7/19/
You need to use ko.observableArray for you array otherwise Knockout wont know when you change your array and wont update, also you should use a template instead, read here http://knockoutjs.com/documentation/template-binding.html#note_2_using_the_foreach_option_with_a_named_template
var viewModel = {
name: ko.observable(''),
description: ko.observable(''),
categories: ko.observableArray([]),
categoryToAdd: ko.observable(''),
removeCategory: function(category) {
this.categories.remove(category);
},
addCategory: function() {
this.categories.push(new Category(this.categoryToAdd()));
this.categoryToAdd('');
}
};