I am having some trouble getting the state manager to toggle my views. I have a state machine, which will initially display the login screen and once the user has been authenticated the state machine will transition to the authorized state and display the workspace or dashboard. The problem is that when i load the page I don't see the login screen so i suspect i am missing something. I am using emberjs 0.9.7.1
Here is the div where i want the screens added
<body>
<div id="main-container" class="container">
</div>
</body>
This is the core html snippet of login_view.handlerbars file (there is more but i have omitted it fror brevity sake). I can see this compiled and stored in Ember.TEMPLATES['login_view'].
<form class="form-inline">
<fieldset>
<input type="text" name="email">
<input type="password" name="password">
<button id="sign-in-button" class="btn btn-primary">Sign In</button>
</fieldset>
</form>
Here is the associated view javascript file
App.LoginView = Ember.View.extend({
templateName: 'login_view'
});
Finally, here is my state machine. I see the message "Entering unauthorized state" in the console but i don't see the login html embedded within the specified div.
App.sessionStates = Ember.StateManager.create({
rootElement: '#main-container',
initialState: 'unauthorized',
unauthorized: Ember.ViewState.create({
viewClass: App.LoginView,
enter: function(stateManager, transition) {
console.log("Entering unauthorized state");
},
exit: function(stateManager, transition) {
console.log("Exiting unauthorized state");
}
}),
authorized: Ember.ViewState.create({
view: App.WorkspaceView
})
})
cheers
Actually the docs are wrong and a bit confusing.
They tell you that you have to call this._super() if you overwrite those methods, but they forgot to specify that you have to pass the arguments as well.
I got it running with this code:
enter: function(manager, transition) {
this._super(manager, transition);
}
or if you don't care about the arguments:
enter: function() {
this._super.apply(this, arguments);
}
I think it should be 'view' instead of 'viewClass'
unauthorized: Ember.ViewState.create({
viewClass: App.LoginView, // the key should be 'view'
UPDATE:
Here is a quick example fiddle showing how to use the StateManager. http://jsfiddle.net/lifeinafolder/GNr4M/
If you want to use 'enter' & 'exit' states, be sure to call super(). Ref: http://ember-docs.herokuapp.com/#doc=Ember.StateManager&src=false
Related
I want to navigate to another page after logging in with facebook. Right now I am able to log in using Facebook and stay on the same page, but i want to navigate to another page.
My .html file is
<button ion-button full (click)="loginWithFB()">Login!</button>
<ion-card *ngIf="userData; else facebookLogin">
<ion-card-header>{{ userData.username }}</ion-card-header>
<img [src]="userData.picture" />
<ion-card-content>
<p>Email: {{ userData.email }}</p>
<p>First Name: {{ userData.first_name }}</p>
</ion-card-content>
</ion-card>
My .ts file is
loginWithFB() {
this.facebook.login(['email', 'public_profile']).then((response: FacebookLoginResponse) => {
this.facebook.api('me?fields=id,name,email,first_name,picture.width(720).height(720).as(picture_large)', []).then(profile => {
this.userData = {email: profile['email'], first_name: profile['first_name'], picture: profile['picture_large']['data']['url'], username: profile['name']}
});
});
}
You just need to push the page using navController in the promise returned after your facebook login call:
this.facebook.api('me?fields=id,name,email,first_name,picture.width(720).height(720).as(picture_large)', []).then(profile => {
this.userData = {email: profile['email'], first_name: profile['first_name'], picture: profile['picture_large']['data']['url'], username: profile['name']};
this.navCtrl.push(PageName, PageParametersIfAny);
});
There's two ways of pushing a page:
Importing the page directly from it's file if you haven't lazy loaded your application.
Just passing the string containing the page name if it's lazy loaded.
Like this if not lazy loaded:
import { MyPage } from 'path/to/my/page/folder';
...
this.navCtrl.push(MyPage);
or this if lazy loaded:
this.navCtrl.push('MyPage'); // There's no import
Hope this helps.
I've been following the Ember Simple Auth walkthrough available here. I have added the various code snippets as instructed but when I submit my login form I receive a 'grant_type' not defined error.
Here is the current setup:
// Login Form
<form {{action 'authenticate' on='submit'}}>
<label for="identification">Login</label> {{input value=identification placeholder='Enter Login' class='form-control'}}
<br>
<label for="password">Password</label> {{input value=password placeholder='Enter Password' class='form-control' type='password'}}
<br>
<button type="submit" class="btn btn-default">Login</button>
</form>
{{#if errorMessage}}
<p>
<strong>Login failed: </strong>
<code>{{errorMessage}}</code>
</p>
{{/if}}
//index.js controller
import Controller from '#ember/controller';
export default Controller.extend({
session: Ember.inject.service('session'),
actions: {
invalidateSession() {
this.get('session').invalidate();
},
authenticate() {
let {identification, password } = this.getProperties('identification', 'password');
this.get('session').authenticate('authenticator:oath2', identification, password).catch((reason) => {this.set('errorMessage', reason.error)
})
}
}
});
//application route
import Route from '#ember/routing/route';
import ApplicationRouteMixin from 'ember-simple-auth/mixins/application-route-mixin';
export default Ember.Route.extend(ApplicationRouteMixin);
// authenticators/oath.js
import OAuth2PasswordGrantAuthenticator from 'ember-simple-auth/authenticators/oauth2-password-grant';
export default OAuth2PasswordGrantAuthenticator.extend({
serverTokenEndpoint: 'http://server:port/api/token',
});
//api endpoint
var tokenRouter = express.Router();
tokenRouter.post('/api/token', function(req, res) {
if (req.body.grant_type === 'password') {
if (req.body.username === 'letme' && req.body.password === 'in') {
res.status(200).send('{"access_token": "secret token!"}');
} else {
res.status(400).send('{ "error": invalid_grant_type" }')
}
} else {
res.status(400).send(' { "error": "unsupported_grant_type" }')
}
})
app.use('/', tokenRouter)
The request is successfully sent to my endpoint and the 500 error is generated with the message that grant_type is not defined. Looking at the request, it doesn't look like the username or password are being sent either.
As far as I can tell my code is identical to the code from the documentation and supplementary video but I am obviously missing something.
I figured this out in the end.
After checking the Form Data in the Network tab of the Chrome debug tools it was clear that the grant_type was being sent correctly.
My issue was a missing bit of body-parser middleware:
app.use(bodyParser.urlencoded({ extended: false }))
I'm trying to create a manually saved form with a moderate number of fields (let's say 20) in Ember.js (not using live bindings) and so far am confused about the correct way / best practice for doing so. I've found the following methods:
http://www.solitr.com/blog/2012/06/ember-input-field-with-save-button/
How to use one-way binding on emberjs?
https://stackoverflow.com/a/16473186/1248965
All of the above methods seem hacky to a degree; they either extend the text field or use a per-field observer, requiring you to list out each one. Is there some other way? Something like the 'unbound' helper, but allowing the auto-model updating magic / validation (via ember-data) on some action (like an 'unbound-until' or 'conditional-bind' or something)? I've gone through all the docs, SO, the github issues, the Ember forum, and the links above, and still feel like I must have missed something.
Basically, a way to say "do everything you would do with a normally bound form/fields, but only on a certain action, rather than in real time."
What you want is a "Buffered Proxy", where you temporarily store all changes to the model (you can catch those using setUnkownProperty) in a proxy object. Once you are happy with the changes, you'd copy all of the proxy data over into the actual object ("flush the data").
App.Heisenberg = {
firstName: 'Walter',
lastName: 'White',
};
App.IndexRoute = Ember.Route.extend({
model: function() {
return App.Heisenberg;
},
setupController: function(controller, model) {
controller.set('content', model);
}
});
App.IndexController = Ember.ObjectController.extend({
proxy: {},
setUnknownProperty: function(key, value) {
console.log("Set the unknown property: " + key + " to: " + value);
this.proxy[key] = value;
console.log(this.proxy);
},
flush: function() {
for(var key in this.proxy)
this.set('model.'+key, this.proxy[key]);
}
});
Template:
<script type="text/x-handlebars" data-template-name="index">
Saved Name: {{firstName}} {{lastName}}<br />
<br />
<form {{ action "saveEdit" on="submit" }}>
First Name: {{input type="text" valueBinding="firstName"}}<br />
Last Name: {{input type="text" valueBinding="lastName"}}<br />
<br />
<button {{ action "flush" }}>Flush</button>
</form>
</script>
This would make for a nice controller Mixin.
See this jsBin for a live example.
I found a workaround, but I'm not 100% happy with it:
In my "editing" template, I have:
<form {{ action "saveEdit" on="submit" }}>
Title: {{input type="text" value=title}}
<input type="submit" value="Save">
<button {{ action "cancelEdit" }}>Cancel</button>
</form>
Then in my associated controller, I do:
cancelEdit: function() {
var entry = this.get('model');
this.set('isEditing', false);
entry.rollback();
},
saveEdit: function() {
var entry = this.get('model');
this.set('isEditing', false);
entry.save().then(
function() {
console.log('Saved!');
}
I simply hide the fields where the "live updating" would show. I still would like to find a way to temporarily turn off the binding until I trigger my "saveEdit" action, since this still seems inelegant.
In my application I display a list of accounts like so:
<script type="text/x-handlebars" data-template-name="accounts">
{{#each account in controller}}
{{#linkTo "account" account class="item-account"}}
<div>
<p>{{account.name}}</p>
<p>#{{account.username}}</p>
<i class="settings" {{ action "openPanel" account }}></i>
</div>
{{/linkTo}}
{{/each}}
</script>
Each account has a button which allows users to open a settings panel containing settings just for that account. as you can see in this quick screencast:
http://screencast.com/t/tDlyMud7Yb7e
I'm currently triggering the opening of the panel from within a method located on the AccountsController:
Social.AccountsController = Ember.ArrayController.extend({
openPanel: function(account){
console.log('trigger the panel');
}
});
But I feel that it's more appropriate to open the panel from within a View that I've defined for this purpose. This would give me access to the View so that I can perform manipulations on the DOM contained within it.
Social.MainPanelView = Ember.View.extend({
id: 'panel-account-settings',
classNames: ['panel', 'closed'],
templateName: 'mainPanel',
openPanel: function(){
console.log('opening the panel');
}
});
<script type="text/x-handlebars" data-template-name="mainPanel">
<div id="panel-account-settings" class="panel closed">
<div class="panel-inner">
<i class="icon-cancel"></i>close
<h3>Account Settings</h3>
Disconnect Account
</div>
</div>
</script>
The problem I'm encountering is that I don't see how I can trigger a method on the Social.MainPanelView from the context of the AccountsController. Is there a better solution?
UPDATE 1
I've worked up a Fiddle to illustrate what I'm talking about:
http://jsfiddle.net/UCN6m/
You can see that when you click the button it calls the showPanel method found on App.IndexController. But I want to be able to call the showPanel method found on App.SomeView instead.
Update:
Approach One:
Simplest of all
Social.AccountsController = Ember.ArrayController.extend({
openPanel: function(account){
/* we can get the instance of a view, given it's id using Ember.View.views Hash
once we get the view instance we can call the required method as follows
*/
Ember.View.views['panel-account-settings'].openPanel();
}
});
Fiddle
Approach Two:(Associating a controller, Much Cleaner)
Using the Handlebars render helper: what this helper does is it associates a controller to the view to be displayed, so that we can handle all our logic related to the view in this controller, The difference is
{{partial "myPartial"}}
just renders the view, while
{{render "myPartial"}}
associates App.MyPartialController for the rendered view besides rendering the view, Fiddle
now you can update your code as follows
application.handlebars(The place you want to render the view)
{{render "mainPanel"}}
accounts controller
Social.AccountsController = Ember.ArrayController.extend({
openPanel: function(account){
this.controllerFor("mainPanel").openPanel();
}
});
main panel view
Social.MainPanelView = Ember.View.extend({
id: 'panel-account-settings',
classNames: ['panel', 'closed']
});
main panel controller
Social.MainPanelController = Ember.Controller.extend({
openPanel: function(){
console.log('opening the panel');
}
})
Approach Three:
This one is the manual way of accomplishing Approach Two
Social.MainPanelView = Ember.View.extend({
id: 'panel-account-settings',
controllerBinding: 'Social.MainPanelController',
classNames: ['panel', 'closed'],
templateName: 'mainPanel'
});
Social.MainPanelController = Ember.Controller.extend({
openPanel: function(){
console.log('opening the panel');
}
})
use this.controllerFor("mainPanel").openPanel()
You need to use the action helper rather than directly coding the links. The action helper targets the controller by default, but you can change it to target the view instead:
<a {{action openPanel target="view"}}></a>
Your second link should be a linkTo a route, since you are specifying a link to another resource. The whole snippet, revised:
Social.MainPanelView = Ember.View.extend({
id: 'panel-account-settings',
classNames: ['panel', 'closed'],
templateName: 'mainPanel',
openPanel: function(){
console.log('opening the panel');
}
});
<script type="text/x-handlebars" data-template-name="mainPanel">
<div id="panel-account-settings" class="panel closed">
<div class="panel-inner">
<a {{action openPanel target="view"} class="button button-close"><i class="icon-cancel"></a></i>
<h3>Account Settings</h3>
{{#linkTo "connections"}}Disconnect Account{{/linkTo}}
</div>
</div>
</script>
I'm using the latest pre 1.0 of ember.js and wanted to get away from using the deprecated button for simple forms.
I have something that works but I don't feel like this is the correct way to wire up a view that has both a text input and a button that needs access to that text.
Here is the basic view
{{#view PersonApp.AddPersonView}}
{{view Ember.TextField valueBinding="username"}}
{{#with this as username}}
<input type="submit" value="add" {{action addPerson username}}/>
{{/with}}
{{/view}}
Here is the view
PersonApp.AddPersonView = Ember.View.extend({
username: null,
addPerson: function(event) {
var username = event.context.username;
if (username) {
PersonApp.personController.addPerson(username);
this.set('username', ''); //this does not currently work
}
}
});
The only other issue I'm having is that I don't have access to username the usual way. ie - this.get('username') but in addition I can't clear the textbox value (even though it's shown above).
I'm looking to build a modern version of this gist (previous version of ember) https://gist.github.com/1477225
I see three issues here (perhaps there are more). First, username will not be a field in the event.context, but will actually be the event context. Secondly, I believe you need to specify view.username in the valueBinding, otherwise the controller is the default home of the property (I believe). Then, to set it to initial state you need to set it to null. Third, the target of your action will be the router, so you need to specify the view as the target.
This should work:
{{#view PersonApp.AddPersonView}}
{{view Ember.TextField valueBinding="view.username"}}
{{#with this as username}}
<input type="submit" value="add" {{action addPerson username target="this"}}/>
{{/with}}
{{/view}}
PersonApp.AddPersonView = Ember.View.extend({
username: null
addPerson: function(event) {
var username = event.context;
if (username) {
this.get('controller').addPerson(username);
this.set('username', null);
}
}
});
Also, a better way of creating a new person would be to create a blank person model, bind the controller and view to that, and then save the record, afterwards setting the binding back to null.
You can do the validation and then pass data right now, even with Gidrius' code. The only thing you need to do is write the validation code in the submit handling method. Or, 'cause we`re talking client-side validation anyway, you can do it on field value change or blur, which will give the user almost instant feedback on what he is doing.
I still couldn't get something like this.get('username') to work but I ended up with the following
{{#view PersonApp.AddPersonForm}}
{{view Ember.TextField valueBinding="username"}}
<input type="submit" value="add" {{action addPerson this}}/>
{{/view}}
PersonApp.AddPersonForm = Ember.View.extend({
addPerson: function(event) {
var username = event.context.username;
if (username) {
PersonApp.personController.addPerson(username);
event.context.set('username', '');
}
}
});
probably a bit too late, but might be helpful to someone else.
Usually form field value will be bind to controller or model, so all you need is to have is a submit function in the controller so whenever function will be called you will have access to the fields via bindings.
Here is how it all could look like, assuming you are using latest pre.4 ember
Updated
// DOM part
<form {{action submitForm on="submit"}}>
{{view Ember.TextField valueBinding="username"}}
<button type="submit">add</button>
</form>
And here is a controller
PersonApp.PersonController = Ember.ArrayController({
username: '',
submitForm: function() {
var u = this.get('username'); // saving value to variable
this.set('username',''); // sets username to ''
console.log(u); // will output saved username
}
});