Toggle between child views with ember.js? - 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.

Related

Embjers store.createRecord or store.push not updating view

I'm trying to create a new record and push it into the store. I can persist it just fine (and i'll push to the store after successfully persisting, once i figure this out), but the view/template isn't being refreshed. I know persistence works because, if I refresh the entire page, the new record appears.
I also tried this.store.push(), and nothing happens. I've looked around and everyone seems to be using filter() but only on a child set of records in a parent controller...
Thanks in advance, i have feeling its something really basic.
route/projects
export default Ember.Route.extend({
model: function(){
return this.store.find('projects');
}
});
controllers/projects
export default Ember.ArrayController.extend({
actions: {
createProject: function() {
var title = this.get('newProjectTitle');
var description = this.get('newProjectDescription');
if (!title.trim()) { return; }
// Create the new Project model
var project = this.store.createRecord('project', {
title: title,
description: description
});
project.save();
this.set('newProjectTitle', '');
this.set('newProjectDescription', '');
}
}
});
projects.hbs
<div class="form-group">
{{input type="text" class="form-control" placeholder="title" value=newProjectTitle}}
{{input type="text" class="form-control" placeholder="description" value=newProjectDescription action="createProject"}}
</div>
<button class="btn btn-xs btn-success" {{action 'createProject'}}>Create this Project</button>
<ul class="list-group">
{{#each project in model}}
{{#link-to 'project' project}}
<li class="list-group-item">
{{project.id}} - {{project.title}}
</li>
{{/link-to}}
{{/each}}
</ul>
You are missing the Setup Controller method -
setupController: function(controller, model) {
controller.set('model', model);
}
Use above method in route/projects.js file.
your code will be like -
export default Ember.Route.extend({
model: function(){
return this.store.find('projects');
}
setupController: function(controller, model) {
controller.set('model', model);
}
});
you can also refer this link for more information -
http://guides.emberjs.com/v1.10.0/routing/setting-up-a-controller/
ended up going with.. which works.. but is this the best way to do this?
var model = this.get('model');
model.pushObject(project);
I also found that...
var posts = this.store.find('post'); //network request.
var posts = this.store.all('post'); //no network request, but does live reload
Ember.js Models:Finding Reords

How to display post's delete button for only post's author in Ember.js

Hello I've been stuck for days how to display a post's delete button only for the post's author in Ember.js (I'm using ember-cli to build this). I don't know where to put the logic of "When hovering a post (list), if the post's author is equal to currently logged in user, then display the delete button" I am lost. Please help me.
in template app/templates/posts.hbs
{{#each}}
<div class="eachPost">
{{#view 'posts'}}
<div class="postProfilePhoto">
{{#link-to 'users' }}
<img src="" alt="Profile Photo">
{{/link-to}}
</div>
<div class="eachPostContent">
<p class="postAuthor"><strong>{{user.id}}</strong></p>
<p class="postContent">{{body}}</p>
<span class="timePosted"><em>somtimes ago</em></span>
{{#if view.entered}}{{#if isAuthor}}
<a class="deletePost" {{action "removePost" this}}>Delete</a>
{{/if}}{{/if}}
</div>
{{/view}}
</div>
{{/each}}
in views app/views/posts.js
var Posts = Ember.View.extend({
classNames: ['eachPostContent'],
mouseEnter: function(event){
this.set('entered', true);
this.get('controller').send('isAuthor', this.get('post').user);
},
mouseLeave: function(){
this.set('entered', false);
}
});
export default Posts;
in controller app/controllers/posts.js
var PostsController = Ember.ArrayController.extend({
...
isAuthor: function(user){
if(this.get('session').user !== null && user === this.get('session').user){
return true;
} else {
return false;
console.log('You are not author');
}
}
}
});
export default PostsController;
SOLVED
in app/templates/posts.hbs
{{#each itemController="post"}}
<div class="eachPost">
created app/controllers/post.js
var PostController = Ember.ObjectController.extend({
isAuthor: function(){
return this.get('user') === this.get('session').user;
}.property('user')
});
export default PostController;
delete following code from app/views/posts.js
this.get('controller').send('isAuthor', this.get('post').user);
and deleted isAuthor function from app/controllers/posts.js
As mentioned above, you'll want to use an itemController
var PostsController = Ember.ArrayController.extend({
itemController:'post',
...
});
And then in the itemController you will create a computed property that checks the user id against the author id of the post
var PostController = Ember.ObjectController.extend({
isAuthor: function(){
//assuming the user property exists on an individual post model
return this.get('user') === this.get('session').user;
}.property('user')
})

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

Display crumble path with ember

I want to display a crumble path with Ember. How can I iterate through the current path?
In my opinion there are two approaches:
The ember-way
EDIT: see my answer below
I keep this question up-to-date with the current status of displaying breadcrumbs. You can browse through the revisions of this question to see the history.
There are a couple of goals here:
Listen on route change
Finding current route
displaying list of the current route
display working links to the steps in the route
Controller
App.ApplicationController = Ember.Controller.extend({
needs: ['breadcrumbs'],
currentPathDidChange: function() {
path = this.get('currentPath');
console.log('path changed to: ', path);
this.get('controllers.breadcrumbs').set('content',this.get('target.router.currentHandlerInfos'));
}.observes('currentPath')
});
App.BreadcrumbsController = Em.ArrayController.extend({});
Router
App.ApplicationRoute = Ember.Route.extend({
renderTemplate: function() {
this.render();
this.render('breadcrumbs', {
outlet: 'breadcrumbs',
into: 'application',
controller: this.controllerFor('breadcrumbs')
});
}
});
Template
{{! application template }}
<div class="clearfix" id="content">
{{outlet "breadcrumbs"}}
{{outlet}}
</div>
{{! breadcrumbs template }}
<ul class="breadcrumb">
{{#each link in content}}
<li>
<a {{bindAttr href="link.name"}}>{{link.name}}</a> <span class="divider">/</span>
</li>
{{/each}}
</ul>
The current problems to tackle are:
When I go to the URL: #/websites/8/pages/1 the output for the breadcrumbs is: (I removed all the script-tag placeholders
<ul class="breadcrumb">
<li>
application <span class="divider">/</span></li>
<li>
sites <span class="divider">/</span>
</li>
<li>
site <span class="divider">/</span>
</li>
<li>
pages <span class="divider">/</span>
</li>
<li>
page <span class="divider">/</span>
</li>
<li>
page.index <span class="divider">/</span>
</li>
</ul>
The URL's should be a valid route
The menu is now hardcoded with {{#linkTo}} to the routes, I tried to make that dynamic, like here but a transitionTo doesn't trigger the currentPath-observer
The other way
Most is the same as above, but there are a couple of differences. The breadcrumbs are made by looping over location.hash instead of getting it from the Router.
The ApplicationController becomes:
ApplicationController = Ember.Controller.extend({
needs: ['breadcrumbs'],
hashChangeOccured: function(context) {
var loc = context.split('/');
var path = [];
var prev;
loc.forEach(function(it) {
if (typeof prev === 'undefined') prev = it;
else prev += ('/'+it)
path.push(Em.Object.create({ href: prev, name: it }));
});
this.get('controllers.breadcrumbs').set('content',path)
}
});
ready : function() {
$(window).on('hashchange',function() {
Ember.Instrumentation.instrument("hash.changeOccured", location.hash);
});
$(window).trigger('hashchange');
}
We need to subscribe the custom handler in the ApplicationRoute
App.ApplicationRoute = Ember.Route.extend({
setupController: function(controller, model) {
Ember.Instrumentation.subscribe("hash.changeOccured", {
before: function(name, timestamp, payload) {
controller.send('hashChangeOccured', payload);
},
after: function() {}
});
}
});
So far the alternative approach is working best for me, but it's not a good way of doing it because when you configure your Router to use the history instead of location.hash this method won't work anymore.
Based on your current breadcrumb output I guess you have an error in your router.
The following command should return array with current breadcrumb:
App.get('Router.router.currentHandlerInfos');
Your router should be nested:
this.resource('page 1', function () {
this.resource('page 2');
});
You can use #linkTo instead of a tag in your breadcrumb, you will get active class for free.
I came up with a much simpler solution that I posted to the Ember discourse.
I found a (Ember-way) solution to display breadcrumbs. It is based on the router instead of my location.hash.
Infrastructure
First we need to make the infrastructure for the breadcrumbs before we add or remove items from the breadcrumbs array.
Menu
In my app.js I define a NavItem-object. This is a skeleton for all navigatable items. I use it to define my menu-items, but we are also going to use it for the breadcrumbs.
App.NavItem = Em.Object.extend({
displayText: '',
routeName: ''
});
// define toplevel menu-items
App.dashboardMenuItem = App.NavItem.create({
displayText: 'Dashboard',
routePath: 'dashboard',
routeName: 'dashboard'
});
App.sitesMenuItem = App.NavItem.create({
displayText: 'Websites',
routePath: 'sites.index',
routeName: 'sites'
});
Controllers
We need a BreadcrumbsController to keep the breadcrumbs in a central place
App.BreadcrumbsController = Em.ArrayController.extend({
content: []
});
My ApplicationController depends on the BreadcrumbsController
App.ApplicationController = Ember.Controller.extend({
needs: ['breadcrumbs']
});
The BreadcrumbsView is a subview of ApplicationView
Views
App.ApplicationView = Ember.View.extend({
BreadcrumbsView: Ember.View.extend({
templateName: 'breadcrumbs',
init: function() {
this._super();
this.set('controller', this.get('parentView.controller.controllers.breadcrumbs'));
},
gotoRoute: function(e) {
this.get('controller').transitionToRoute(e.routePath);
},
BreadcrumbItemView: Em.View.extend({
templateName:'breadcrumb-item',
tagName: 'li'
})
})
});
Templates
In my application-template I output the breadcrumbsview above the outlet
{{view view.BreadcrumbsView}}
{{outlet}}
I'm using Twitter Bootstrap so my markup for my breadcrumbs-template is
<ul class="breadcrumb">
{{#each item in controller.content}}
{{view view.BreadcrumbItemView itemBinding="item"}}
{{/each}}
</ul>
The breadcrumb-item-template
<a href="#" {{action gotoRoute item on="click" target="view.parentView"}}>
{{item.displayText}}
</a> <span class="divider">/</span>
Routing
We need to respond to the routing in our app to update the breadcrumbs.
When my SitesRoute (or any other toplevel route) is activated, we push the NavItem to the Breadcrumbs, but I also want to do that with the rest of my toplevel routes, so I first create a TopRoute
App.TopRoute = Em.Route.extend({
activate: function() {
this.controllerFor('menu').setActiveModule(this.get('routeName'));
var menuItem = app.menuItems.findProperty('routeName',this.get('routeName'));
this.controllerFor('breadcrumbs').get('content').pushObject(menuItem);
},
deactivate: function() {
var menuItem = app.menuItems.findProperty('routeName',this.get('routeName'));
this.controllerFor('breadcrumbs').get('content').removeObject(menuItem);
}
});
All my toproutes extend from this route, so the breadcrumbs are automatically updatet
App.SitesRoute = App.TopRoute.extend();
For deeper levels it works almost the same, all you have to do is use the activate and deactivate hooks to push/remove objects from the Breadcrumbs
App.SiteRoute = Em.Route.extend({
activate: function() {
var site = this.modelFor('site');
this.controllerFor('breadcrumbs').get('content').pushObject(app.NavItem.create({
displayText: site.get('name'),
routePath: 'site',
routeName: this.get('routeName')
}));
},
deactivate: function() {
var site = this.modelFor('site');
this.controllerFor('breadcrumbs').get('content').removeAt(1);
}
});

EmberJS Router App: Views vs Controllers

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" }}