EmberJS Router App: Views vs Controllers - ember.js

I'm creating a router-based EmberJS app (strongly modelled on the excellent router guide). However, I'm quite muddled over what belongs in a view vs in a controller.
I totally get that {{action showFoo}} often indicates a state change and that the Router is the state machine for my app. But some of my actions don't fall into that category.
Here's an example from my actual code (html simplified but mustaches intact). I want to have a login form that works via ajax (i.e. the html form doesn't post directly to the server, it tells my ember app to attempt a login via json).
<form>
Email Name: {{view Ember.TextField valueBinding="email"}}
Password: {{view Ember.TextField valueBinding="password"}}
<button type="submit" {{ action logIn target="this" }}>Sign in</button>
</form>
The valueBindings are fields in my loginController but the logIn handler is in my view (because I couldn't figure out how to tell the template to call the controller). I feel like this is a weird distribution & I'm not sure what the right Ember approach is to this.
I don't think the router should be handling the action because requesting a login attempt isn't really a state change. The loginController feels like the right place to try the login. After a login response is received then that controller could trigger the state change.

I don't think the router should be handling the action because requesting a login attempt isn't really a state change.
I think that's exactly the case: attempting a login should transition to an authenticating state, where for example another click to "login" is ignored.
So IMHO this should be handled by the router. I'm thinking about something like this, see http://jsfiddle.net/pangratz666/97Uyh/:
Handlebars:
<script type="text/x-handlebars" >
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="login" >
<p class="info">{{message}}</p>
Login to view the admin area <br/>
Email: {{view Ember.TextField valueBinding="email" }} <br/>
Password: {{view Ember.TextField valueBinding="password" }} <br/>
<button {{action login}} >Login</button>
</script>
<script type="text/x-handlebars" data-template-name="authenticating" >
Communicating with server ...
</script>
<script type="text/x-handlebars" data-template-name="admin" >
Hello admin!
</script>
​
JavaScript:
App = Ember.Application.create();
App.ApplicationController = Ember.Controller.extend({
login: function() {
// reset message
this.set('message', null);
// get data from login form
var loginProps = this.getProperties('email', 'password');
// simulate communication with server
Ember.run.later(this, function() {
if (loginProps.password === 'admin') {
this.set('isAuthenticated', true);
this.get('target').send('isAuthenticated');
} else {
this.set('message', 'Invalid username or password');
this.set('isAuthenticated', false);
this.get('target').send('isNotAuthenticated');
}
}, 1000);
// inform target that authentication is in progress
this.get('target').send('authenticationInProgress');
},
logout: function() {
this.set('isAuthenticated', false);
}
});
App.ApplicationView = Ember.View.extend({
templateName: 'application'
});
App.LoginView = Ember.View.extend({
templateName: 'login'
});
App.AdminView = Ember.View.extend({
templateName: 'admin'
});
App.AuthenticatingView = Ember.View.extend({
templateName: 'authenticating'
});
App.Router = Ember.Router.extend({
root: Ember.Route.extend({
index: Ember.Route.extend({
route: '/',
loggedOut: Ember.Route.extend({
route: '/',
connectOutlets: function(router) {
router.get('applicationController').connectOutlet('login');
},
login: function(router) {
router.get('applicationController').login();
},
authenticationInProgress: function(router) {
router.transitionTo('authenticating');
}
}),
authenticating: Ember.State.extend({
enter: function(router) {
router.get('applicationController').connectOutlet('authenticating');
},
isAuthenticated: function(router) {
router.transitionTo('loggedIn');
},
isNotAuthenticated: function(router) {
router.transitionTo('loggedOut');
}
}),
loggedIn: Ember.Route.extend({
route: '/admin',
connectOutlets: function(router) {
if (!router.get('applicationController.isAuthenticated')) {
router.transitionTo('loggedOut');
}
router.get('applicationController').connectOutlet('admin');
},
logout: function(router) {
router.get('applicationController').logout();
}
})
})
})
});​

You can use the controller for this, the template your using have access to the controller.
<script type="text/x-handlebars" data-template-name="loginTemplate">
{{#if controller.login}}
Logged in!
{{else}}
Login failed
{{/if}}
</script>
This fiddle shows a small app, which does that: fiddle
Then after login has occured you can make an actioncall to your router, or show the user that login failed.

I have just made it done by change the codes as:
{{ action logIn target="controller" }}

Related

Why does Action event on a button does not call the controller function

I have an index.html file which has a button with an action handler.
<button {{action 'modules'}} > press </button>
But nothing happens when I press the button.
please look at the code at jsbin:
http://jsbin.com/OREMORe/1/edit
Feel free to correct any other inconsistencies.
Appreciate any help. Thx.
You'd do it like this. Please note that there were a few things wrong with your code:
1) You handlebars code with your button in it was not wrapped in a handlebars script tag
2) You're 'modules' template was in the application template
3) You did not include valid links to the necessary libraries
4) There was not rootElement specified for the Application
5) You tried to access ModulesController in the ApplicationRoute. I inserted a redirect to 'modules'
6) You tried to access module in your {{#each}} loop, but that does not exists in your model. What you want is content.
<script type="text/x-handlebars" data-template-name="application">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="modules">
<ul>
{{#each content}}
<li>{{this}}</li>
{{/each}}
<button {{action 'modules'}} > press </button>
</ul>
</script>
And:
var Test1 = Ember.Application.create({
rootElement: 'body',
ready: function() {
console.log("App ready1");
}
});
Test1.IndexRoute = Ember.Route.extend({
redirect: function() {
this.transitionTo('modules');
}
});
Test1.Router.map( function() {
this.route('modules');
});
Test1.ModulesController = Ember.Controller.extend({
actions: {
modules: function() {
console.log('clicked the button');
/* this.transitionToRoute('modules');*/
}
}
});
Test1.ModulesRoute = Ember.Route.extend({
model: function() {
return ['item1', 'item2'];
}
});

Error while using simple login in ember?

I am a newbie to Ember and as a part of learning i was trying to set up the login form from one tutorial but it doesnot seem to be working.
Error: Invalid Path---Thats the Error i am getting in login method
I want to see if the password is okay then connect login template else kick the user to '/'.?
Html file
<script type="text/x-handlebars" data-template-name="login">
{{message}} <br>
E-Mail {{view Ember.TextField target="controller" valueBinding= controller.email}} <br />
Password {{view Ember.TextField target="controller" valueBinding= controller.password}} <br />
<button {{action login}}>Login</button>
</script>
<script type="text/x-handlebars" data-template-name="loggedin">
Welcome to Our Site !
</script>
JS File
App.LoginController = Ember.ObjectController.extend({
email :null,
password :null,
message :null,
login : function() {
var loginCreds = this.getProperties('email','password');
if (loginCreds.password === 'admin') {
this.set('isAuthenticated',true);
console.log(isAuthenticated);
this.get('target').send('isAuthenticated');
}
else{
this.set('message','Invalid password');
this.set('isAuthenticated',false);
console.log(isAuthenticated);
this.get('target').send('isAuthenticated');
}
}
});
App.LoginRoute = Ember.Route.extend({
isAuthenticated : function(router) {
router.transitionTo('loggedin');
},
connectOutlets: function(router) {
if (!router.get('loginController.isAuthenticated')) {
router.transitionTo('/');
}
router.get('loginController').connectOutlet('loggedin');
},
});
i think problem may be u didnt map the path u are giving in the browser.You should map the path like this
App.Router.map(function () {
this.route('login');
});
then in the url u can add #login

Toggle between child views with ember.js?

I am trying to render a view that toggles between two of its children (or so I'd hope) and something is not exactly working. Here is my template:
{{#view App.LoginFormView isVisibleBinding="user.isNotAuthenticated" }}
Username: {{view Ember.TextField valueBinding="user.loginName"}} /
Password: {{view Ember.TextField valueBinding="user.userPassword" type="password"}}
<button class="btn" {{ action "login" }} {{bindAttr disabled="user.isNotValid"}}>Login</button>
{{/view}}
{{#view App.LoginInfoView isVisibleBinding="user.isAuthenticated" }}
You are logged in as {{user.loginName}}. Click <a {{action "logout"}}>here</a> to logout
{{/view}}
in app.js I have the following:
App.User = Ember.Object.extend({
loginName:'',
userPassword:'',
rememberMe:true,
isNotValid:function(){
return (this.get("loginName") == '') || (this.get("userPassword") == '');
}.property('loginName', 'userPassword'),
isAuthenticated:false,
isNotAuthenticated:function(){
return !this.isAuthenticated;
}.property('isAuthenticated')
});
App.AuthenticationController = Ember.Controller.extend({
login:function() {
alert("loginName:"+this.user.get('loginName')+";\n"+
"userPassword:"+this.user.get('userPassword')+";\n"+
"rememberMe:"+this.user.get('rememberMe')+";\n");
this.user.isAuthenticated = true;
},
user:App.User.create()
});
App.AuthenticationView = Ember.View.extend({
templateName: 'authentication',
userBinding:"App.AuthenticationController.user"
});
App.LoginFormController = Ember.Controller.extend({
userBinding:"App.AuthenticationController.user"
});
App.LoginFormView = Ember.View.extend();
App.LoginInfoController = Ember.Controller.extend({
userBinding:"App.AuthenticationController.user"
});
App.LoginInfoView = Ember.View.extend();
App.Router = Ember.Router.extend({
enableLogging:true,
root: Ember.Route.extend({
index: Ember.Route.extend({
route: '/',
connectOutlets: function(router){
router.get('applicationController').connectOutlet('authentication','authentication');
},
login:function(router){
router.get('authenticationController').login();
}
})
})
});
The problem I am having is that the view does not toggle on the change of isAuthenticated property. I was under impression that would happen automagically and yet it does not. Any ideas on how to make this work? Or am I missing something fundamental (ember.js newbie here, so be gentle :-))
Cheers,
Alex.
You can implement user authentication in the following way:
In your template (for example in _header.hbs templates which is a partial for application.hbs)
{{#if needAuth}}
// login form goes here
<button {{action submitLogin}}>login</button>
{{else}}
<small {{action logout}}>logout</small>
{{/if}}
In application controller:
submitLogin: function () {
// do login stuff
// if login success
that.set('needAuth', false);
// else
that.set('needAuth', true);
});
DOM will update automatically. In other partial templates you can use {{#if needAuth}} as well.

Ember: How to get Router and Controller work together?

For my login/logout scenario I implemented a conditional routing in ember:
App.Router = Ember.Router.extend({
//needs controller handling
//goLoggedIn: Ember.Route.transitionTo('loggedIn'),
goLoggedOut: Ember.Route.transitionTo('loggedOut'),
root: Ember.Route.extend({
index: Ember.Route.extend({
route: '/',
enter: function(router) {
var logged = getLoginState();
Ember.run.next(function() {
if (logged) {
router.transitionTo('loggedIn');
} else {
router.transitionTo('loggedOut');
}
});
}
}),
loggedIn: Ember.Route.extend({
connectOutlets: function(router, context){
...
}
}),
loggedOut: Ember.Route.extend({
connectOutlets: function(router, context){
...
}
})
...
My index.html says for loggedin view
<!-- Template for out -->
<script type="text/x-handlebars" data-template-name="out">
<hr /><br />
<h1>Logged Out</h1>
<span>Login with "test/test"</span><br /><br />
<label>Username: </label>{{view Ember.TextField valueBinding="App.OutController.username"}}<br />
<label>Password: </label>{{view Ember.TextField valueBinding="App.OutController.password" type="password"}}<br />
{{#if App.loginController.isError}}
<span class="login-error">Error: Invalid username or password.</span><br />
{{/if}}
<br /><button {{action goLoggedIn href=true}}>Login</button>
</script>
Now I am delegating this action simply to my Router. I know that I can delegate this to my controller as well with:
action login target="controller"
but after that, how to do the transitionTo function in my Router? Because I know that this shouldnt be done in my controller. So how to pass it to my Router?
Probably I am wrong and I have to let {{action goLoggedIn href=true}}. Then my Router delegates this with a function to my controller and I get a response. Instead of having goLoggedIn: Ember.Route.transitionTo('loggedIn') I need something like App.LoginController.doLogin and afterwards goLoggedIn: Ember.Route.transitionTo('loggedIn'). When this is the case how to implement it?
EdiT:
Like this?
App.Router = Ember.Router.extend({
//needs controller handling
goLoggedIn: Ember.Route.transitionTo('loggedIn'),
goLoggedOut: Ember.Route.transitionTo('loggedOut'),
root: Ember.Route.extend({
index: Ember.Route.extend({
route: '/',
enter: function(router) {
var logged = getLoginState();
Ember.run.next(function() {
if (logged) {
router.transitionTo('loggedIn');
} else {
router.transitionTo('loggedOut');
}
});
}
}),
loggedIn: Ember.Route.extend({
connectOutlets: function(router, context){
...
}
}),
loggedOut: Ember.Route.extend({
connectOutlets: function(router, context){
...
},
goLoggedIn: function(router, evt) {
router.get('inController').tryLogin()
router.transitionTo('loggedIn')
}
})
...
I get:Cannot call method 'split' of undefined
Edit 2:
It is working now. I had to remove href=true. Thanks
In a typical Ember app, the target property of each controller is set to the Router instance. If you want to send an action to the controller and then on to the router, you can do so within the controller method by saying this.get('target').send('goLoggedIn', optionalEvt)
I would recommend that you let the action be sent to the router directly. You can have a function handle the action that can call a method on one or more controllers before transitioning. e.g.:
...
{{action log href=true}}
...
logIn: function(router, evt) {
router.get('userController').prepareForLogin()
router.transitionTo('loggedIn')
}

How to access a specific context in ember 1.0 pre with action helper

In my ember view I want to get the person during this each and have it passed to the action but currently I only get a jquery event in the router (curious if this is bound to the context for free in pre 1.0 now)
template
<script type="text/x-handlebars" data-template-name="person">
{{#each person in controller}}
<li>
{{person.username}}
<input type="submit" value="delete" {{action removePerson person}}/>
</li>
{{/each}}
</script>
router w/ the method I was hoping to invoke w/ the person context
Router: Ember.Router.create({
root: Ember.Route.extend({
index: Em.Route.extend({
route: '/',
removePerson: function(router, context) {
router.get('personController').removePerson(context);
},
controller in more detail
PersonController: Ember.ArrayController.extend({
content: [],
addPerson: function (username) {
var person = PersonApp.Person.create({
username: username
});
this.pushObject(person);
},
removePerson: function (person) {
this.removeObject(person);
}
}),
The second variable passed to the router action handler is actually the event. The context is a variable of this event. Rewrite it like so:
Router: Ember.Router.create({
root: Ember.Route.extend({
index: Em.Route.extend({
route: '/',
removePerson: function(router, event) {
router.get('personController').removePerson(event.context);
},