I have a simple form in Ember which submits to a server and returns a response. On a failed response, I want to re-focus on the input field. Is there a way to access the field in my controller using the value binding?
Here's my jsbin as an example:
http://jsbin.com/umodir/1/edit
http://jsbin.com/efatut/2/edit
Your server should return something that sets a property on your controller that you can observe. I made it pretty simple and called it "error".
var App = Ember.Application.create();
App.ApplicationController = Ember.ObjectController.extend({
error: false,
submit: function() {
alert('I want to set a focus on the email input field now...');
this.set('error', true);
}
});
App.ApplicationView = Ember.View.Extend({
focusEmail: function() {
if (this.get('controller.error')) {
this.$('input[type=text]').focus();
}
}.observes('controller.error')
});
If you wanted to get really fancy you could use an Ember.Component like {{eb-input focuson="error"}} that would automatically focus when the controller's "error" property changed.
Related
I'm trying to load the current user into the data store but am having some difficulty. The server uses PassportJS and visiting /api/users/me returns a JSON object similar to this:
{"user":{"_id":"53a4d9c4b18d19631d766980","email":"ashtonwar#gmail.com",
"last_name":"War","first_name":"Ashton","location":"Reading, England",
"birthday":"12/24/1993","gender":"male","fb_id":"615454635195582","__v":0}}
My store is just defined by App.store = DS.Store.create();
The controller to retrieve the current user is:
App.UsersCurrentController = Ember.ObjectController.extend({
content: null,
retrieveCurrentUser: function() {
var controller = this;
Ember.$.getJSON('api/users/me', function(data) {
App.store.createRecord('user', data.user);
var currentUser = App.store.find(data.user._id);
controller.set('content', currentUser);
});
}.call()
});
It is called by my application controller:
App.ApplicationController = Ember.Controller.extend({
needs: "UsersCurrent",
user: Ember.computed.alias("controllers.UsersCurrent")
});
I suspect the line App.store.createRecord('user', data.user); is causing issues but I don't have any idea how to fix it.
The console logs TypeError: this.container is undefined while the Ember debugger shows every promise is fulfilled and the users.current controller has no content. Thankyou for any help you can provide.
Are you defining the store on the App namespace, because Ember Data doesn't do that by default. Either way, you're failing to define the type you want to find after you create the record.
var currentUser = controller.store.find('user', data.user._id);
createRecord returns the record, so there is no point in finding it afterward
var currentUser = controller.store.createRecord('user', data.user);
Also in your example, you are trying to call the function immediately on the type, and not on the instance. You should add that as a method to run on init.
App.UsersCurrentController = Ember.ObjectController.extend({
retrieveCurrentUser: function() {
console.log('hello')
var controller = this;
Ember.$.getJSON('api/users/me', function(data) {
var user = controller.store.createRecord('user', data.user);
controller.set('model', user);
});
}.on('init')
});
http://emberjs.jsbin.com/OxIDiVU/693/edit
I'm trying to develop a small app using EmberJS and HighchartJS, and I have some problem to re-render the the HighChartJS after the Model property has changed. This is http://www.loancomparison.com.s3-website-us-east-1.amazonaws.com/
App.Loan = DS.Model.extend({
name : DS.attr('string'),
principal : DS.attr('number'),
interest_rate : DS.attr('number'),
months_to_pay : DS.attr('number')
});
App.LoansView = Ember.View.extend({
templateName: "loans",
loansChanged: function() {
//this.rerender();
Ember.run.scheduleOnce('afterRender', this, 'propertyChanged');
}.observes('controller.#each.principal', 'controller.#each.name', 'controller.#each.interest_rate', 'controller.#each.months_to_pay'),
propertyChanged : function() {
console.log("property changed");
this.loadHighChart(); // This will load the highchart function.
},
});
What I want is to notify the view whenever the model property finishes their change. However, when using observes, it notify the view when the model starts to change. This causes the scheduleOnce to run at the initial state of model property change only.
Edit: Resolved
The solution for this turned out to be very simple that I just need to create a "modified" property under the model loan. Then whenever the edit is made, I update this model. Now the view just need to observe the change of this "modified" property.
You'll want to hook into the save action within your LoanController: https://github.com/pmkhoa/loan-comparison/blob/master/source/assets/js/app/controllers/loanController.js#L7
You can communicate really easily between views and controllers by using Ember.Evented:
App.LoanController = Ember.ObjectController.extend(Ember.Evented, { <--PASS IN EMBER.EVENTED
//...
save: function () {
this.set('isEditing', false);
this.get('model').save().then(function () {
this.trigger('highChartReload');
}.bind(this));
},
//...
});
Notice that I've passed Ember.Evented into the controller (just like you would with any mixin...) and I've added a trigger to the save action.
Now, we'll want to listen for that event within the view: https://github.com/pmkhoa/loan-comparison/blob/master/source/assets/js/app/views/loansView.js#L3
App.LoansView = Ember.View.extend({
//...
didInsertElement: function () {
this.get('controller.controllers.loan').on('highChartReload', $.proxy(this.loadHighChart, this));
},
//...
});
Now the view will listen to LoanController for the event to trigger, then fire off the loadHighChart method.
The last thing to do will be to tell the LoansController to need 'loan':
App.LoansController = Ember.ArrayController.extend({
needs: ['loan'],
//...
});
That should do it. Hope that helps!
Before starting this question I found something similar to what I am looking for: How can I access an Ember input as a jQuery element?
The problem with that approach is focusEmail() method in the View will only get called the first time, when Controller's error property changes from false to true. If I submit the form again, the error is still true, so in a sense it hasn't changed, and thus View's focusEmail() is not fired.
What can I do here to focus an input element all the time, not just the first time when error changes from false to true?
My code
// login_view.js
var LoginView = Ember.View.extend({
focusPassword: function() {
if (this.get('controller.error')) {
this.$('#password').focus();
}
}.observes('controller.error')
});
// login_controller.js
var LoginController = Ember.Controller.extend(Ember.SimpleAuth.LoginControllerMixin, {
error: false,
actions: {
loginFailed: function(xhr) {
this.set('error', true);
this.set('errorMessage', xhr.responseText);
}
}
});
Note: The loginFailed is fired whenever non-200 response is sent from the server. (I am using ember-simple-auth library)
UPDATE: I was able to get it working using submit method in LoginView. In addition to what I have above:
submit: function() {
this.get('controller').send('error', false);
},
And then in controller:
error: function(value) {
this.set('error', value);
}
If someone has a better approach, simpler, and easier, post it below and and I will accept it as the answer.
Have you tried to explicitly mark the error property as changed, with: notifyPropertyChange?
http://emberjs.com/api/classes/Ember.Object.html#method_notifyPropertyChange
Personally, I like your last solution more and if I had to implement it, I would have done the same thing.
I understand that Ember.Application now has deferReadiness that lets me wait for the return of an AJAX call before initializing the app. However, in the example in the api docs, they put the value into a global variable in the App:
App = Ember.Application.create();
App.deferReadiness();
jQuery.getJSON("/auth-token", function(token) {
App.token = token;
App.advanceReadiness();
});
Rather than introducing a global variable for the token, I want to place the returned value into my ApplicationController. However, I can't seem to find how to get a handle to a controller at this point, i.e. in the jQuery callback.
You can reopen your controller in the $.getJSON callback to set the response value in the token property. Assuming you have an endpoint ~/auth-token returning a JSON with a single attribute key, you can do something like this:
window.App = Ember.Application.create();
App.ApplicationController = Em.Controller.extend({
token: ''
});
App.deferReadiness();
$.getJSON("/auth-token", function(token) {
console.log(token.key);
App.ApplicationController.reopen({
token: token.key
});
App.advanceReadiness();
});
(see fiddle)
hi i have the following route:
MB3.PlaylistRoute = Ember.Route.extend({
model: function(params) {
return MB3.Playlist.find(params.playlist_id);
}
});
The playlist has a hasMany realtion with tracks. in the playlist view i want do do some logic with an attribute of the first track of the playlist.
so i added this code:
MB3.PlaylistView = Ember.View.extend({
didInsertElement: function() {
console.log(this.get("controller.tracks").objectAt(0).get("title"));
}
});
The problem is title is undefined (i think because it is not yet loaded. the second thing i tried is waiting for the didLoad event:
MB3.PlaylistView = Ember.View.extend({
didInsertElement: function() {
var self=this;
this.get("controller.tracks").on("didLoad", function() {
console.log(self.get("controller.tracks").objectAt(0).get("title"));
});
}
});
but this logges null as well. How do i accomplish that?
Like Adrien said in the comments, it seems you are running into issue 587. That said, I don't think you actually need the "didLoad" callback in this case. Instead, try using a computed property to get the video_id or track title. For example:
MB3.PlaylistView = Ember.View.extend({
firstTrackTitle: function() {
return this.get('controller.tracks.firstObject.title');
}.property('controller.tracks.firstObject.title')
});
Then in your template, embed the player if this property is defined:
{{#if view.firstTrackTitle}}
embed code here
{{/if}}
FWIW I would put this logic in controller instead of view, but same idea.