access property of controller from within view - ember.js

I wanna do something like a progress bar, which will be controlled by ember. So in my eyes, there are two and a half ways to achieve this:
Have an observer in the controller, which sets the elements' width when triggered. Problem: AFAIK, one can't access DOM-elements from within the controller, i.e. like the way you'd do it in the view this.$('#progress').
Have an observer in the view, which observes the controller's property. Problem: I don't know, how to observe (and access) a controller's property.
(bind the controller's property via {{bindAttr}} to a freaky data-progress="42" attribute and adjust the elements width whenever the attribute's value has changed)

Option 2 is your best bet.
Problem: I don't know, how to observe (and access) a controller's property.
Ember will set a view's controller property when the view is created, you can use that to access the controller's property.
App = Ember.Application.create();
App.ApplicationController = Ember.Controller.extend({
percentComplete: '0'
});
App.ProgressView = Ember.View.extend({
percentChanged: function() {
percentString = (this.get('controller.percentComplete') + "%");
this.$('.bar').css('width', percentString);
}.observes('controller.percentComplete').on('didInsertElement')
});
I've posted a working example here: http://jsbin.com/hitacomu/1/edit

Related

Ember tabs with components

I am trying to write an Ember.Component to handle Bootstrap-style tabs. I implemented it as a reusable TabHolderComponent which takes an array as parameter and outputs one tab per item and then one content container per item (à la Bootstrap). So far it works well, but I also need to be able to refresh the content in the tabs with a button in the tab. Is there an elegant way of sending a refresh event/action to a child component?
I have several different solutions to this (moving the refresh button into the component, which would be less user-friendly, or binding a parent component refresh property that contains the name of the tabs that should be refreshed and that the child component observes), but none of them are elegant.
I also saw this suggestion, but don't know how to make that work for dynamically created tabs where I can't hardcode the tab name as a parent component property.
You can use the observable pattern (It's similar to what this post is saying).
{{my-thing parent=controller}}
App.MyThingComponent = Em.Component.extend({
setup: function(){
this.get('parent').register(this); // you should check to see if parent exists, I'm lazy
}.on('init'),
sayHello: function(){
console.log('hello');
}
});
And in the parent controller (Array, Object, or plain controller)
App.FooController = Em.Controller.extend({
observers: null,
setup: function(){
this.observers = [];
}.on('init'),
register: function(observer){
this.observers.pushObject(observer);
},
talkToChildren: function(){
this.observers.forEach(function(child){
child.sayHello();
});
}
});
Example: http://emberjs.jsbin.com/yoyod/1/edit

In Ember.js how to notify an ArrayController's corresponding itemController, when a property on the ArrayController changes

I have an EmailsController (ArrayController), which stores all the emails. I have an EmailController (ObjectController) that has a parameter that stores if the actual Email is selected or not. I am trying to implement a button in the emails template, that selects or deselects all the Emails. So somehow I need to notify the EmailController via an action of the EmailsController and change the EmailController's isChecked parameter.
I am trying to use the itemController, the needs, and the controllerBinding parameters, but nothing works.
Here are the controllers:
App.EmailsController = Ember.ArrayController.extend({
needs: ["Email"],
itemController: 'Email',
checkAll: true,
actions: {
checkAllEmails: function() {
this.toggleProperty("checkAll");
console.log(this.get("checkAll"));
}
}
});
App.EmailController = Ember.ObjectController.extend({
needs: ["Emails"],
controllerBinding: 'controllers.Emails',
isChecked: true,
checkAllChanged: function() {
//this should execute, but currently it does not
this.set("isChecked",this.get('controller.checkAll'));
}.property("controller")
});
Here is the corresponding jsFiddle: http://jsfiddle.net/JqZK2/4/
The goal would be to toggle the selection of the checkboxes via the Check All button.
Thanks!
Your mixing a few different mechanisms and your using a few wrong conventions. It's not always easy to find this stuff though, so don't fret.
Referencing Controllers
Even though controllers are created with an Uppercase format, the are stored in the lowercase format and your needs property should be:
needs: ['emails'],
You then access other controllers through the controllers property:
this.get('controllers.emails.checkAll')
Computed Properties
Computed properties can be used as a getter/setter for a variable and also as a way to alias other properties. For example, if you wanted the isChecked property on the Email controller to be directly linked to the value of the checkAll property of the Emails controller, you could do this:
isChecked: function() {
return this.get('controllers.emails.checkAll');
}.property('controllers.emails.checkAll')
Although computed properties can do much more, this basic form is really just a computed alias, and there is a utility function to make it easier:
isChecked: Ember.computed.alias('controllers.emails.checkAll')
Observables
An observable basically creates a method that will be called when the value it observes changes. A computed alias would cause all items to uncheck or check whenever you clicked on any one of them, since their isChecked property is linked directly to the checkAll property of the parent controller. Instead of your checkAllChanged method identifying as a property it should use observes:
checkAllChanged: function() {
this.set("isChecked",this.get('controllers.emails.checkAll'));
}.observes("controllers.emails.checkAll")
This way when the checkAll property changes on the parent controller, this method updates the isChecked properties of all items to its value, but if you uncheck or check an individual item, it doesn't affect the other items.
Bindings
Bindings are somewhat deprecated; from reading issues on the Ember github repository I believe the creators of Ember seem to favor using computed properties, aliases, and observables instead. That is not to say they don't work and if your goal was to avoid having to type out controllers.emails every time, you could create one like you did (I wouldn't call it controller though, cause thats really quite ambiguous):
emailsBinding: 'controllers.emails'
Using a computed alias instead:
emails: Ember.computed.alias('controllers.emails')
You could then change your observer to:
checkAllChanged: function() {
this.set("isChecked",this.get('emails.checkAll'));
}.observes("emails.checkAll")
Heres an updated version of your jsFiddle: http://jsfiddle.net/tMuQn/
You could just iterate through the emails, changing their properties from the parent controller. You don't need to specify needs or observe a variable.
App.EmailsController = Ember.ArrayController.extend({
itemController: 'email',
actions: {
checkAllEmails: function() {
this.forEach(function(email) {
email.toggleProperty("isChecked");
});
}
}
});
Also, you typically don't set initial values like you did with isChecked = true; I believe that's creating a static shared property on the prototype (not what you intended). Instead, set the property on init, or pass it in from your original json data.
See the code: http://jsfiddle.net/JqZK2/5/

Calling Controller method within view Ember.js

I have a controller in Ember like so:
App.TasksController = Ember.ArrayController.extend({
search: function(term){ ... }
})
And I have the relative view, with a custom text field, as such:
App.TasksView = Ember.View.extend({
searchField: Ember.TextField.extend({
keyUp: function(){ this.get('controller').search() }
})
})
I however get an error saying that there is no such method.
I was wondering:
How can I correctly call the method defined in the controller from the view?
How can I debug which is the currently active controller? If I console.log(this.get('controller')) I get an object, but it is very confusing to navigate and to understand exactly which controller is that.
the scope of this on the text field isn't the same scope as the tasksview, so it doesn't have access to the controller.
Honestly a better way to handle this is to bind the textfield value to something and add an observer to it. When it changes fire off a search (or probably better would be to debounce it so you aren't firing off requests every single character they type)
http://emberjs.jsbin.com/IRAXinoP/3/edit

Bind to the number of objects

I'm trying to understand how I would create a binding or computed property that is the number of objects I have. I can get the number (I think) via:
App.MyObject.all().get("length")
When I create a controller property with that inside a function it doesn't update as more objects are downloaded.
numOfMyObjects: function(){
return App.MyObject.all().get("length");
}.property()
Right now I have this in my ApplicationController, but it just shows 0.
I'd like to know how to do this for all of the objects and then also for a filtered set of objects.
Thanks.
You need to tell Ember on which properties it should observe to fire the numOfMyObjects method. For example:
numOfMyObjects: function(){
return App.MyObject.all().get("length");
}.property('myArray.length');
However, this won't work in your case because you've got App.MyObject in your controller itself, instead you want to be instructing the appropriate route which model(s) the controller should represent.
This way you won't actually need to create a computed property, because you'll have access to the model in your Handlebars.
Please see the JSFiddle I've put together as an example: http://jsfiddle.net/ESkkb/
The main part of the code lies in the IndexRoute:
App.IndexRoute = Ember.Route.extend({
model: function() {
return App.Cat.find();
}
});
We're telling the IndexController that it should represent all of the cats. And then once we've done that, we can display the cats in our view, or in our case, the number of cats:
Count: {{length}}

How Can The Current Route Be Observed for Changes?

I want to update the <title> tag for the page whenever the location (route) changes. I'm particularly interested in observing App.Router's current route changing - I then want to access the View associated with that route and update the <title> tag from the observer based on a property of the View.
For example, if going to /about
What do I set the Observer to observe? I'm having trouble finding a currentState property or equivalent to observe.
Within the Observer, how can I access the App.AboutView associated with the route?
I'm using 1.0.0pre4
My goal is to have a title property and an authorization property on each View that the Observer on currentPath will work with. I want to use the title property to update the document.title, and the authorization property object to check wither my current user can access the route.
Now that I say this and with #Michael Grassotti's answer, perhaps these properties belong on the Controller and not the View. The goal is to gain access to these properties associated with the current Route's context to modify the document.title and check whether or not my App.CurrentUserController (that stores a User object model for the logged in user) is authorized to acess the current route.
What do I set the Observer to observe? I'm having trouble finding a currentState property or equivalent to observe.
You can observe the ApplicationController.currentPath. For example:
App.ApplicationController = Ember.Controller.extend({
currentPathDidChange: function() {
path = this.get('currentPath');
console.log('path changed to: ', path);
window.document.title = path;
}.observes('currentPath')
});
Within the Observer, how can I access the App.AboutView associated with the route?
Trying to access App.AboutView from this observer would be tricky and probably not a good idea. I'm not clear on why a view's property would be useful in this scenario. Can you elaborate?
So the properties I'm trying to work with belong on the Controller for the route, not the View.
Given some contactUs route, it's Controller might look like
App.ContactUsController = App.AppController.extend({
title: "Contact Us"
});
My App.ApplicationController can now look like
App.ApplicationController = Ember.Controller.extend({
updateTitle: function() {
window.document.title = this.controllerFor(this.currentPath).get('title');
}.observes('currentPath')
});
So the API methods/properties I was looking for are
App.ApplicationController.get('currentPath') (currently appears to be undocumented on the site)
controllerFor (also seems undocumented, but is found within a reopen implementation for Ember.ControllerMixin)