Update UI for a particular model value in Ember - ember.js

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')
});

Related

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!

How to pass arguments to a controller init method?

How can I pass external values into a controller. In the below code I would like to pass in values filtertype and filterterm from PostsController into PostsDynamicController. What is a way do achieve this?
I have a template like this
<script type="text/x-handlebars" id="posts">
{{view Ember.Select
contentBinding="App.names.content"
valueBinding="App.names.selected"
}}
{{view Ember.TextField valueBinding="filterterm" }}
<button {{action "submit"}} > Submit</button>
{{outlet}}
</script>
Part of my App.js is this:
App.PostsController = Ember.ObjectController.extend({
content: [],
filterterm: "",
submit: function () {
var filtertype = App.names.selected;
var filterterm = this.get('filterterm');
this.transitionToRoute("posts.dynamicfinder");
}
});
App.PostsDynamicController = Ember.ObjectController.extend({
init: function () {
//want access to filtertype and filterterm here so that I can pass them in find. i.e.
//App.Request.find(filtertype: filterterm);
this.set('model', App.Request.find(..);
}
});
You cannot pass args to the controller's init() function.
To pass external values into a controller you should use bindings. Specifically the controller's needs property. See the ember guide dependencies-between-controllers
So for example:
// Change handlebars template to valueBinding="filtertype" instead of valueBinding="App.names.selected"
// Also these should be ArrayControllers not ObjectControllers
App.PostsController = Ember.ArrayController.extend({
filterterm: null,
filtertype: null,
submit: function () {
this.transitionToRoute("posts.dynamicfinder");
}
});
App.PostsDynamicController = Ember.ArrayController.extend({
needs: ['posts'],
termBinding: 'controllers.posts.filterterm',
typeBinding: 'controllers.posts.filtertype',
filteredPosts: function() {
var filtertype = this.get('type');
var filterterm = this.get('term');
// ...
}.property('term', 'type')
}
});

differentiate polymorphic association type in views [duplicate]

I have a list of items I'm trying to display with Ember. For each of these items, I'd like to be able to dynamically select the view type to use to display it based on a "message_type" field in each model.
I currently have something like this, which totally sucks and is not scalable:
{{#each message in controller}}
{{#if message.isImage}}
{{view App.ImageMessageView}}
{{/if}}
....
{{#if message.isVideo}}
{{view App.VideoMessageView}}
{{/if}}
{{/each}}
How can you dynamically select a view based on a model's field in Ember?
Here is a similar question that showed 2 ways to do this: Collection of objects of multiple models as the iterable content in a template in Ember.js
rendering items based on a property or their type
I know of two ways to do this:
add a boolean property to each object and use a handlebars {{#if}} to check that property and render the correct view
extend Ember.View and use a computed property to switch which template is rendered based on which type of object is being rendered (based on Select view template by model type/object value using Ember.js)
Method 1
JS:
App.Post = Ember.Object.extend({
isPost: true
});
App.Bookmark = Ember.Object.extend({
isBookmark: true
});
App.Photo = Ember.Object.extend({
isPhoto: true
});
template:
<ul>
{{#each item in controller.stream}}
{{#if item.isPost}}
<li>post: {{item.name}} {{item.publishtime}}</li>
{{/if}}
{{#if item.isBookmark}}
<li>bookmark: {{item.name}} {{item.publishtime}}</li>
{{/if}}
{{#if item.isPhoto}}
<li>photo: {{item.name}} {{item.publishtime}}</li>
{{/if}}
{{/each}}
</ul>
Method 2
JS:
App.StreamItemView = Ember.View.extend({
tagName: "li",
templateName: function() {
var content = this.get('content');
if (content instanceof App.Post) {
return "StreamItemPost";
} else if (content instanceof App.Bookmark) {
return "StreamItemBookmark";
} else if (content instanceof App.Photo) {
return "StreamItemPhoto";
}
}.property(),
_templateChanged: function() {
this.rerender();
}.observes('templateName')
})
template:
<ul>
{{#each item in controller.streamSorted}}
{{view App.StreamItemView contentBinding=item}}
{{/each}}
</ul>
JSBin example - the unsorted list is rendered with method 1, and the sorted list is rendered with method 2
It would probably need some more thought, but here is what I have come up with quickly:
var get = Ember.get,
isGlobalPath = Ember.isGlobalPath,
normalizePath = Ember.Handlebars.normalizePath;
var getProp = function (context, property, options) {
if (isGlobalPath(property)) {
return get(property);
} else {
var path = normalizePath(context, property, options.data);
return get(path.root, path.path);
}
};
Ember.Handlebars.registerHelper('detect', function (definition, instance, options) {
Ember.assert("You must pass exactly two argument to the detect helper", arguments.length === 3);
Ember.assert("You must pass a block to the detect helper", options.fn && options.fn !== Handlebars.VM.noop);
var path = '_detect_' + definition.replace('.', '_').toLowerCase();
context = (options.contexts && options.contexts[0]) || this;
definition = getProp(context, definition, options);
instance = getProp(context, instance, options);
context.set(path, definition.detectInstance(instance));
return Ember.Handlebars.helpers.boundIf.call(options.contexts[0], path, options);
});
Then you can use a helper like this:
{{#detect App.Definition instance}}
DETECTED
{{else}}
NOT DETECTED
{{/detect}}

Selecting view type based on model

I have a list of items I'm trying to display with Ember. For each of these items, I'd like to be able to dynamically select the view type to use to display it based on a "message_type" field in each model.
I currently have something like this, which totally sucks and is not scalable:
{{#each message in controller}}
{{#if message.isImage}}
{{view App.ImageMessageView}}
{{/if}}
....
{{#if message.isVideo}}
{{view App.VideoMessageView}}
{{/if}}
{{/each}}
How can you dynamically select a view based on a model's field in Ember?
Here is a similar question that showed 2 ways to do this: Collection of objects of multiple models as the iterable content in a template in Ember.js
rendering items based on a property or their type
I know of two ways to do this:
add a boolean property to each object and use a handlebars {{#if}} to check that property and render the correct view
extend Ember.View and use a computed property to switch which template is rendered based on which type of object is being rendered (based on Select view template by model type/object value using Ember.js)
Method 1
JS:
App.Post = Ember.Object.extend({
isPost: true
});
App.Bookmark = Ember.Object.extend({
isBookmark: true
});
App.Photo = Ember.Object.extend({
isPhoto: true
});
template:
<ul>
{{#each item in controller.stream}}
{{#if item.isPost}}
<li>post: {{item.name}} {{item.publishtime}}</li>
{{/if}}
{{#if item.isBookmark}}
<li>bookmark: {{item.name}} {{item.publishtime}}</li>
{{/if}}
{{#if item.isPhoto}}
<li>photo: {{item.name}} {{item.publishtime}}</li>
{{/if}}
{{/each}}
</ul>
Method 2
JS:
App.StreamItemView = Ember.View.extend({
tagName: "li",
templateName: function() {
var content = this.get('content');
if (content instanceof App.Post) {
return "StreamItemPost";
} else if (content instanceof App.Bookmark) {
return "StreamItemBookmark";
} else if (content instanceof App.Photo) {
return "StreamItemPhoto";
}
}.property(),
_templateChanged: function() {
this.rerender();
}.observes('templateName')
})
template:
<ul>
{{#each item in controller.streamSorted}}
{{view App.StreamItemView contentBinding=item}}
{{/each}}
</ul>
JSBin example - the unsorted list is rendered with method 1, and the sorted list is rendered with method 2
It would probably need some more thought, but here is what I have come up with quickly:
var get = Ember.get,
isGlobalPath = Ember.isGlobalPath,
normalizePath = Ember.Handlebars.normalizePath;
var getProp = function (context, property, options) {
if (isGlobalPath(property)) {
return get(property);
} else {
var path = normalizePath(context, property, options.data);
return get(path.root, path.path);
}
};
Ember.Handlebars.registerHelper('detect', function (definition, instance, options) {
Ember.assert("You must pass exactly two argument to the detect helper", arguments.length === 3);
Ember.assert("You must pass a block to the detect helper", options.fn && options.fn !== Handlebars.VM.noop);
var path = '_detect_' + definition.replace('.', '_').toLowerCase();
context = (options.contexts && options.contexts[0]) || this;
definition = getProp(context, definition, options);
instance = getProp(context, instance, options);
context.set(path, definition.detectInstance(instance));
return Ember.Handlebars.helpers.boundIf.call(options.contexts[0], path, options);
});
Then you can use a helper like this:
{{#detect App.Definition instance}}
DETECTED
{{else}}
NOT DETECTED
{{/detect}}

Collection of objects of multiple models as the iterable content in a template in Ember.js

I am trying to build a blog application with Ember. I have models for different types of post - article, bookmark, photo. I want to display a stream of the content created by the user for which I would need a collection of objects of all these models arranged in descending order of common attribute that they all have 'publishtime'. How to do this?
I tried something like
App.StreamRoute = Ember.Route.extend({
model: function() {
stream = App.Post.find();
stream.addObjects(App.Bookmark.find());
stream.addObjects(App.Photo.find());
return stream;
}
}
where the resource name is stream
But it doesn't work. I am using the latest released Ember 1.0.0 rc 2 and handlebars 1.0.0 rc 3 with jQuery 1.9.1 and ember-data.
Probably the way I am trying to achieve this whole thing is wrong. The problem is even if I am able to use the collection of objects of multiple models to iterate in the template, I would still need to distinguish between the type of each object to display its properties apart from the common property of 'publishtime'.
You can use a computed property to combine the various arrays and then use Javascript's built in sorting to sort the combined result.
Combining the arrays and sorting them
computed property to combine the multiple arrays:
stream: function() {
var post = this.get('post'),
bookmark = this.get('bookmark'),
photo = this.get('photo');
var stream = [];
stream.pushObjects(post);
stream.pushObjects(bookmark);
stream.pushObjects(photo);
return stream;
}.property('post.#each', 'bookmark.#each', 'photo.#each'),
example of sorting the resulting computed property containing all items:
//https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/sort
streamSorted: function() {
var streamCopy = this.get('stream').slice(); // copy so the original doesn't change when sorting
return streamCopy.sort(function(a,b){
return a.get('publishtime') - b.get('publishtime');
});
}.property('stream.#each.publishtime')
});
rendering items based on a property or their type
I know of two ways to do this:
add a boolean property to each object and use a handlebars {{#if}} to check that property and render the correct view
extend Ember.View and use a computed property to switch which template is rendered based on which type of object is being rendered (based on Select view template by model type/object value using Ember.js)
Method 1
JS:
App.Post = Ember.Object.extend({
isPost: true
});
App.Bookmark = Ember.Object.extend({
isBookmark: true
});
App.Photo = Ember.Object.extend({
isPhoto: true
});
template:
<ul>
{{#each item in controller.stream}}
{{#if item.isPost}}
<li>post: {{item.name}} {{item.publishtime}}</li>
{{/if}}
{{#if item.isBookmark}}
<li>bookmark: {{item.name}} {{item.publishtime}}</li>
{{/if}}
{{#if item.isPhoto}}
<li>photo: {{item.name}} {{item.publishtime}}</li>
{{/if}}
{{/each}}
</ul>
Method 2
JS:
App.StreamItemView = Ember.View.extend({
tagName: "li",
templateName: function() {
var content = this.get('content');
if (content instanceof App.Post) {
return "StreamItemPost";
} else if (content instanceof App.Bookmark) {
return "StreamItemBookmark";
} else if (content instanceof App.Photo) {
return "StreamItemPhoto";
}
}.property(),
_templateChanged: function() {
this.rerender();
}.observes('templateName')
})
template:
<ul>
{{#each item in controller.streamSorted}}
{{view App.StreamItemView contentBinding=item}}
{{/each}}
</ul>
JSBin example - the unsorted list is rendered with method 1, and the sorted list is rendered with method 2
It's a little complicated than that, but #twinturbo's example shows nicely how to aggregate separate models into a single array.
Code showing the aggregate array proxy:
App.AggregateArrayProxy = Ember.ArrayProxy.extend({
init: function() {
this.set('content', Ember.A());
this.set('map', Ember.Map.create());
},
destroy: function() {
this.get('map').forEach(function(array, proxy) {
proxy.destroy();
});
this.super.apply(this, arguments);
},
add: function(array) {
var aggregate = this;
var proxy = Ember.ArrayProxy.create({
content: array,
contentArrayDidChange: function(array, idx, removedCount, addedCount) {
var addedObjects = array.slice(idx, idx + addedCount);
addedObjects.forEach(function(item) {
aggregate.pushObject(item);
});
},
contentArrayWillChange: function(array, idx, removedCount, addedCount) {
var removedObjects = array.slice(idx, idx + removedCount);
removedObjects.forEach(function(item) {
aggregate.removeObject(item);
});
}
});
this.get('map').set(array, proxy);
},
remove: function(array) {
var aggregate = this;
array.forEach(function(item) {
aggregate.removeObject(item);
});
this.get('map').remove(array);
}
});