Ember.js: dealing with a large number of properties in a model - ember.js

I have a model with a large number of properties, about 20.
App.Foo = Ember.Object.extend({
foo01: null,
foo02: null,
...
foo20: null
Because I need to differentiate between values of null and "", I also have all these properties:
isFoo01Set: function() { return foo01 !== null; }.property('foo01'),
isFoo02Set: function() { return foo02 !== null; }.property('foo02'),
...
isFoo20Set: function() { return foo01 !== null; }.property('foo20')
Please mind that the actual names in my real-world case are not foo01..20, but meaningful names that are all different from one another.
Then I need to have a whole bunch of actions to set/unset all those properties.
In my router:
setFoo01: function(router, instance) {
router.get('myController').setFoo01(instance.context);
},
unsetFoo01: function(router, instance) {
router.get('myController').unsetFoo01(instance.context);
},
So on until foo20. And then in my controller:
setFoo01: function(obj) {
obj.set('foo01', '');
},
unsetFoo01: function(obj) {
obj.set('foo01', null);
And so on until foo20.
Obviously I must be doing something wrong. But I found that because of handlebars limitations (by design), I can't do anything in a template.
I would like to have something like this:
{{# each property in obj}}
<a {{action set obj property}}>Set {{property}}</a>
{{/each}}
but it doesn't look like this is possible.
So, please, can you tell me that there is a better way, and what it is?

I am going to give a vague idea...hope this helps
App.Foo = Ember.Object.create({
name: "",
presence: function(){
return this.get('name')!==null;
}.property('name'),
setFoo: function(){
this.set('name', "");
},
unsetFoo: function(){
this.set('name', null);
}
})
App.fooArray = []
for(var i=0;i<=5;i++){
App.fooArray.pushObject(App.Foo.create({name:"foo"+i}));
}
{{#each element in App.fooArray}}
<a {{action element.unsetFoo}} href="#">un-set {{element.name}}</a>
<a {{action element.setFoo}} href="#">Set {{element.name}}</a>
{{/each}}

Related

How to set default values for query params based on dynamic segment?

I am creating universal grid for some entities. For that I added this in routes:
this.route('record', { path: '/record' },function() {
this.route('index', {path: '/:entity'});
this.route('view', {path: '/:entity/:record_id'});
});
and created new "index" route:
export default Ember.Route.extend({
entity: '',
queryParams: {
sort: {
refreshModel: true
}
},
beforeModel: function(transition) {
var entity = transition.params[this.get('routeName')].entity;
this.set('entity', entity);
},
model: function(params) {
delete params.entity;
return this.store.findQuery(this.get('entity'), params);
},
}
my controller
export default Ember.ArrayController.extend({
queryParams: ['sort'],
sort: ''
}
how can I set default value for the "sort" based on dynamic segment?
For example in my settings I store sort values for all entites:
'settings.user.sort': 'email ASC',
'settings.company.sort': 'name ASC',
I tried to define "sort" as computed property, but its "get" method is called in time when I can't get a value of dynamic segment from currentHadlerInfo or from route.
Also defining of the property "sort" as computed property has strange effect, for example, when I define it as
sort: 'email ASC'
in my template it is displayed via {{sort}} as expected (email ASC).
but when I return a value from computed property, I see empty value in my template and this affects on a work of components (I can't get current sorted column)
What can I do?..
Here is a rough implementation of setting the sort properties based on dynamic segment values. The code will look like
<script type="text/x-handlebars" data-template-name="index">
{{#link-to 'sort' model.settings.user.sort}}Sort{{/link-to}}
</script>
<script type="text/x-handlebars" data-template-name="sort">
<ul>
{{#each arrangedContent as |item|}}
<li>{{item.val}}</li>
{{/each}}
</ul>
</script>
var settings = Em.Object.create({
settings: {
user: {
sort: 'val ASC'
}
}
});
App.Router.map(function() {
this.route('sort', {path: '/:sortParams'});
});
App.SortRoute = Ember.Route.extend({
params: null,
model: function(params) {
this.set('params', params);
return [{val:1}, {val:5}, {val:0}];
},
setupController: function(controller, model) {
this._super(controller, model);
var props = this.get('params').sortParams.split(' ');
var property = [props[0]];
var order = props[1] === 'ASC'? true : false;
controller.set('sortProperties', property);
controller.set('sortAscending', order);
}
});
The working demo can be found here..
As you can see I access the params object in the model hook and store it on the route. In the setupController hook I access the params and set the required values on the controller.

Ember-rails: function returning 'undefined' for my computed value

Both functions here return 'undefined'. I can't figure out what's the problem.. It seems so straight-forward??
In the controller I set some properties to present the user with an empty textfield, to ensure they type in their own data.
Amber.ProductController = Ember.ObjectController.extend ({
quantity_property: "",
location_property: "",
employee_name_property: "",
//quantitySubtract: function() {
//return this.get('quantity') -= this.get('quantity_property');
//}.property('quantity', 'quantity_property')
quantitySubtract: Ember.computed('quantity', 'quantity_property', function() {
return this.get('quantity') - this.get('quantity_property');
});
});
Inn the route, both the employeeName and location is being set...
Amber.ProductsUpdateRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('product', params.product_id);
},
//This defines the actions that we want to expose to the template
actions: {
update: function() {
var product = this.get('currentModel');
var self = this; //ensures access to the transitionTo method inside the success (Promises) function
/* The first parameter to 'then' is the success handler where it transitions
to the list of products, and the second parameter is our failure handler:
A function that does nothing. */
product.set('employeeName', this.get('controller.employee_name_property'))
product.set('location', this.get('controller.location_property'))
product.set('quantity', this.get('controller.quantitySubtract()'))
product.save().then(
function() { self.transitionTo('products') },
function() { }
);
}
}
});
Nothing speciel in the handlebar
<h1>Produkt Forbrug</h1>
<form {{action "update" on="submit"}}>
...
<div>
<label>
Antal<br>
{{input type="text" value=quantity_property}}
</label>
{{#each error in errors.quantity}}
<p class="error">{{error.message}}</p>
{{/each}}
</div>
<button type="update">Save</button>
</form>
get rid of the ()
product.set('quantity', this.get('controller.quantitySubtract'))
And this way was fine:
quantitySubtract: function() {
return this.get('quantity') - this.get('quantity_property');
}.property('quantity', 'quantity_property')
Update:
Seeing your route, that controller wouldn't be applied to that route, it is just using a generic Ember.ObjectController.
Amber.ProductController would go to the Amber.ProductRoute
Amber.ProductUpdateController would go to the Amber.ProductUpdateRoute
If you want to reuse the controller for both routes just extend the product controller like so.
Amber.ProductController = Ember.ObjectController.extend ({
quantity_property: "",
location_property: "",
employee_name_property: "",
quantitySubtract: function() {
return this.get('quantity') - this.get('quantity_property');
}.property('quantity', 'quantity_property')
});
Amber.ProductUpdateController = Amber.ProductController.extend();
I ended up skipping the function and instead do this:
product.set('quantity',
this.get('controller.quantity') - this.get('controller.quantity_property'))
I still dont understand why I could not use that function.. I also tried to rename the controller.. but that was not the issue.. as mentioned before the other two values to fetches to the controller...
Anyways, thanks for trying to help me!

Update UI for a particular model value in Ember

How to iterate over each model value and based on the value update the handlebar UI.
I am using ArrayController. Basically for a particular value in the model I want to change how I display it.
I am not sure what is wrong in the above code. But it does not function as required.
App.SomeStat = Ember.Object.extend({
target: null,
starts: null
}
{{#each stat in controller}}
{{#if isRestricted}} Do something..
{{/if}}
{{/each}}
App.SomestatController = Ember.ArrayController.extend({
isRestricted: function () {
this.forEach(function(target) {
var t= target.get('target');
return t >= MAGIC_NUMBER;
});
}.property('model.#each.target'),
});
You should setup the ArrayController itemController property to an ObjectController which extends the content for each array content.
App.ExtendIndexController = Ember.ObjectController.extend({
isRestricted: Em.computed(function () {
return this.get('name') === 'red';
}).property('name')
});
App.IndexController = Ember.ArrayController.extend({
itemController: 'extendIndex'
});
Then, you could access the added properties in your template when iterating the controller:
{{#each controller}}
<li>{{name}} ({{isRestricted}})</li>
{{/each}}
http://emberjs.jsbin.com/gexos/1/edit
This case is documented in the Ember guide but I think, this specific case should documented as well.
Try this:
App.CensusStat = Ember.Object.extend({
targetPc: null,
starts: null,
isRestricted: function () {
var offTarget = this.get('targetPc');
return (offTarget &&
(Math.abs(offTarget) >=
Ember.I18n.t('ps.label.census.offtarget.restricted.percentage')));
}.property('targetPc')
});

Template not updating when controller property changes

Caveat: This is part of my first ember app.
I have an Ember.MutableArray on a controller. The corresponding view has an observer that attempts to rerender the template when the array changes. All the changes on the array (via user interaction) work fine. The template is just never updated. What am I doing wrong?
I'm using Ember 1.2.0 and Ember Data 1.0.0-beta.4+canary.7af6fcb0, though I guess the latter shouldn't matter for this.
Here is the code:
var ApplicationRoute = Ember.Route.extend({
renderTemplate: function() {
this._super();
var topicsController = this.controllerFor('topics');
var topicFilterController = this.controllerFor('topic_filter');
this.render('topics', {outlet: 'topics', controller: topicsController, into: 'application'});
this.render('topic_filter', {outlet: 'topic_filter', controller: topicFilterController, into: 'application'});
},
});
module.exports = ApplicationRoute;
var TopicFilterController = Ember.Controller.extend({
topicFilters: Ember.A([ ]),
areTopicFilters: function() {
console.log('topicFilters.length -> ' + this.topicFilters.length);
return this.topicFilters.length > 0;
}.property('topicFilters'),
getTopicFilters: function() {
console.log('getTopicFilters....');
return this.store.findByIds('topic', this.topicFilters);
}.property('topicFilters'),
actions: {
addTopicFilter: function(t) {
if(this.topicFilters.indexOf(parseInt(t)) == -1) {
this.topicFilters.pushObject(parseInt(t));
}
// this.topicFilters.add(parseInt(t));
console.log('topicFilters -> ' + JSON.stringify(this.topicFilters));
},
removeTopicFilter: function(t) {
this.topicFilters.removeObject(parseInt(t));
console.log('topicFilters -> ' + JSON.stringify(this.topicFilters));
}
}
});
module.exports = TopicFilterController;
var TopicFilterView = Ember.View.extend({
topicFiltersObserver: function() {
console.log('from view.... topicFilters has changed');
this.rerender();
}.observes('this.controller.topicFilters.[]')
});
module.exports = TopicFilterView;
// topic_filter.hbs
{{#if areTopicFilters}}
<strong>Topic filters:</strong>
{{#each getTopicFilters}}
<a {{bind-attr href='#'}} {{action 'removeTopicFilter' id}}>{{topic}}</a>
{{/each}}
{{/if}}
var TopicsController = Ember.ArrayController.extend({
needs: ['topicFilter'],
all_topics: function() {
return this.store.find('topic');
}.property('model', 'App.Topic.#each'),
actions: {
addTopicFilter: function(t) {
App.__container__.lookup('controller:topicFilter').send('addTopicFilter', t);
}
}
});
module.exports = TopicsController;
// topics.hbs
<ul class="list-group list-unstyled">
{{#each all_topics}}
<li class="clear list-group-item">
<span class="badge">{{entryCount}}</span>
<a {{bind-attr href="#"}} {{action 'addTopicFilter' id}}>{{topic}}</a>
</li>
{{/each}}
</ul>
your observes should just be controller.topicFilters.[]
And honestly this is a very inefficient way of doing this, rerendering your entire view because a single item changed on the array. If you show your template I can give you a much better way of handling this.
Here's an example, I've changed quite a few things, and guessed on some others since I don't know exactly how your app is.
http://emberjs.jsbin.com/uFIMekOJ/1/edit

Item specific actions

Against my controller, I have an item with an array of items which I want to display a list of and have an action to remove an item.
Have taken most of the code from a similar question item-specific actions in ember.js collection views. Majority of this works, the address display works and the items render, however the action does not seem to have the correct context and therefore does not perform the remove action.
Controller:
App.EditAddressesController = Em.ArrayController.extend({
temporaryUser: {
firstName: 'Ben',
lastName: 'MacGowan',
addresses: [{
number: '24',
city: 'London'
etc...
}, {
number: '23',
city: 'London'
etc...
}]
}
});
The temporaryUser is an EmberObject (based off a User model), and each item within the addresses array is another EmberObject (based off an Address model) - this has just been simplified for purposes of displaying code.
Parent view:
{{#each address in temporaryUser.addresses}}
{{#view App.AddressView addressBinding="this"}}
{{{address.display}}}
<a {{action deleteAddress target="view"}} class="delete">Delete</a>
{{/view}}
{{/each}}
App.AddressView:
App.AddressView = Ember.View.extend({
tagName: 'li',
address: null,
deleteAddress: function() {
var address = this.get('address'),
controller = this.get('controller'),
currentAddresses = controller.get('temporaryUser.addresses');
if(currentAddresses.length > 1) {
$.each(currentAddresses, function(i) {
if(currentAddresses[i] == address) {
currentAddresses.splice(i, 1);
}
});
}
else {
//Throw error saying user must have at least 1 address
}
}
});
Found out it was an issue with my each:
{{#each address in temporaryUser.addresses}}
should have been:
{{#each temporaryUser.addresses}}
And my AddressView should use removeObject (like the original code sample)
App.AddressView = Ember.View.extend({
tagName: 'li',
address: null,
deleteAddress: function() {
var address = this.get('address'),
controller = this.get('controller'),
currentAddresses = controller.get('temporaryUser.addresses');
if(currentAddresses.length > 1) {
currentAddresses.removeObject(address);
}
else {
//Throw error saying user must have at least 1 address
}
}
});
I think your code could become more readable if you approach it that way:
1- Modify your action helper to pass the address with it as context. I modified your Handlebars a bit to reflect the way i am approaching stuff like this and works for me. I think this is the "Ember way" of doing this kind of stuff, since a the properties in a template are always a lookup against the context property of the corresponding view:
{{#each address in temporaryUser.addresses}}
{{#view App.AddressView contextBinding="address"}}
{{{display}}}
<!-- this now refers to the context of your view which should be the address -->
<a {{action deleteAddress target="view" this}} class="delete">Delete</a>
{{/view}}
{{/each}}
2 - Modify the handler in your view to accept the address as a parameter:
App.AddressView = Ember.View.extend({
tagName: 'li',
address: null,
deleteAddress: function(address) {
var controller = this.get('controller'),
currentAddresses = controller.get('temporaryUser.addresses');
if(currentAddresses.length > 1) {
currentAddresses.removeObject(address);
}
else {
//Throw error saying user must have at least 1 address
}
}
});