I have an application where i wish to create a user profile widget. The widget will sit in the navbar in the application template. the widget will look like a login button that pops up a modal login dialog or a dropdown menu that contains links to various user related functions/routes.
the logic for the widget is so far this:
{{#if 'user.loggedIn'}}
<span class="glyphicon glyphicon-user"/> {{user.username}}
<ul class="dropdown-menu">
<li>{{#link-to "profile" user}}Profile{{/link-to}}</li>
<li>{{#link-to "logoff" user}}Logout{{/link-to}}</li>
</ul>
{{else}}
<button class="btn btn-primary navbar-btn" data-target="#loginModal" data-toggle="modal">Login</button>
{{/if}}
I wish to have a controller handle the login functions and calling the modal prompt:
App.UserController = Em.ObjectController.extend({
loggedIn: Em.computed.readOnly('model.loggedIn'),
username: '',
password: '',
showLoginModal: function(){
Em.$("#loginModal").modal();
},
hideLoginModal: function() {
Em.$("#loginModal").modal('hide');
},
login: function() {
var username = this.get('username'),
success = true; //attempt a login
console.log('logging in as' + username);
if(success) {
this.send('hideLoginModal');
this.set('user.loggedIn', true);
}
return false;
}
});
So my question is, what is the best way to get this component/view/partial into the application template using the UserController for the widget?
I'm very new to ember so please be nice :).
I would use a component for the view, and handle the login logic in the ApplicationController and the UserModel.
But actually, there are multiple ways to solve that problem! U could also do it directly in the applicationTemplate.
But: U should not use a normal view or partitial! Mostly u should use a component instead!
The render helper IS an option, if u need the View/Controller pattern, and if its not clearly seperated!
Generall:
If u can seperate it, you should. And then u should use a component.
Related
We have a requirement of opening a modal dialog containing a route or a component. We are looking for some modal components, and saw ember-bootstrap's modal is useful.
So,
How can we open any route as a modal dialog ? (If parent route decides a route to be open in a modal, the child route should be open in modal.)
Can we create a service, to pop up a modal dialog? Such as: ModalDialogService.popup(title, bodyComponent, commitHandler, cancelHandler); or ModalDialogService.popup(title, routeName, commitHandler, cancelHandler); And how can we do this without violating the Data Down Action Up principle?
Is there any guide, document, tutorial or npm package for implementing modals in ember.js?
UPDATED:
What I need is to open any of the current routes in a modal. For example, in a given route hierarchy:
-module1
|-module1.query
|-module1.add
|-module1.update
|-module1.delete
Currently module1.query has transitions to others. But I want to give an option to the module developers to open any of the add, update, delete routes in a modal. So that query route doesn't lose its state, when an add operation finished.
Also we have some services used by components. At some conditions, services need to display a modal that has a component.
You should be able to use a service and component similar to one below to achieve what you want.
Have a look at the twiddle for a demo of how this works exactly, and the code below for quick reference
Your route template could look something like this.
// templates/hasmodal.hbs
{{#bs-modal}}
Modal Content
{{/bs-modal}}
Your route hooks, with service injected
// routes/hasmodal.js
export default Ember.Route.extend({
modalNavigation: Ember.inject.service(),
activate(){
console.log('openingModal')
this.get('modalNavigation').openModal()
},
deactivate(){
console.log('closingModal')
this.get('modalNavigation').openModal()
},
actions: {
onClose(){
console.log('we want to close route')
}
}
})
Your bs-modal or relevant component
//components/bs-modal.js
export default Ember.Component.extend({
modalNavigation: Ember.inject.service(),
isOpen: Ember.computed.alias('modalNavigation.modalOpen'),
classNameBindings: ['isOpen:modalDialog:notOpen'],
actions: {
close(){
this.get('modalNavigation').closeModal()
}
}
})
The bs-modal component template
// templates/components/bs-modal
<div>
{{yield}}
</div>
<button class='close' {{action 'close'}}>Close Me</button>
Your Modal Service to manage state
// services/modal-navigation.js
export default Ember.Service.extend({
modalOpen: false,
openModal(){
this.set('modalOpen',true)
},
closeModal(){
this.set('modalOpen',false)
}
})
UPDATE:
updated twiddle
It basically nests routes that contain a modal underneath a route you want to preserve the state of and show behind the modal.
// router.js [truncated]
Router.map(function() {
this.route('module1',function(){
this.route('query',function(){
this.route('add')
this.route('update', { path: '/update/:item_id' })
this.route('delete', { path: '/delete/:item_id' })
})
})
// templates/modules1/query.hbs
Queried List {{link-to 'add item' 'module1.query.add'}}<br/>
<ul>
{{#each model as |item|}}
<li>
{{item.id}}-{{item.title}}
{{link-to 'u' 'module1.query.update' item}}
{{link-to 'd' 'module1.query.delete' item}}
</li>
{{/each}}
</ul>
{{outlet}}
// templates/module1/query/add.hbs
{{#modal-component isOpen=true onClose=(action "routeClosed")}}
<div>
Title:{{input value=model.title}}
</div>
<button {{action 'save'}}>Save</button>
{{/modal-component}}
Where all the other sub components follow the same modal wrapper principle
When I close a bootstrap modal, it doesn't send the action it should (in application js:)
application.hbs:
<li><a {{action "showSignInModal"}}>Sign In</a></li>
{{outlet}}
{{outlet 'modal'}}
bootstrap-modal.js:
this.$('.modal').modal().on('hidden.bs.modal', function() {
alert("Closed");
this.sendAction('removeModal');
}.bind(this));
routes/application.js:
export default Ember.Route.extend({
actions: {
showSignInModal: function() {
this.render('components.signin-modal', {
into: 'application',
outlet: 'modal'
});
},
removeModal: function(){
alert("Working")
}
//...
}
})
signin-modal.hbs:
{{#bootstrap-modal title="Sign In" ok='signin' okText="Signin"}}
<p>
Please sign in. Thanks!
</p>
{{/bootstrap-modal}}
The "closed" alert shows, but the "working" alert doesn't.
(The signin modal is a component, with no actions defined, and is just a bootstrap-modal)
You are not passing your action name properly.
You need to be aware that the sendAction method will fail silently if it can't find the action name.
Make sure inside your template that contains the modal component, you pass a property with the action name you want to call:
{{#bootstrap-modal title="Sign In" ok='signin' okText="Signin" removeModal="removeModal"}}
You can read more about Passing Actions to Components
Actions won't propagate like event bubbing
Wrap your bootstrap modal into a component and give it the removeModal action from the signin-modal to call.
Trying to execute this code within a component's template
// components/my-component.js
{{#if session.isAuthenticated}}
<a {{action 'invalidateSession'}}>Sign out</a>
{{else}}
{{#link-to 'signin'}}Sign in{{/link-to}}
{{/if}}
However, when clicking "Sign out" button I get this error
Error: <...#component:my-component::ember381> had no action handler for: invalidateSession
How do I make "invalidateSession" available from a component?
You can just implement your own invalidateSession action:
actions: {
invalidateSession: function() {
this.get('session').invalidate();
}
}
or simply forward the action from the component to the route:
{{my-component invalidateSession='invalidateSession'}}
You need to add the Simple Auth ApplicationRouteMixin to your ApplicationRoute. For example if you are using ES6 modules do
import ApplicationRouteMixin from 'simple-auth/mixins/application-route-mixin';
ApplicationRoute = Ember.Route.extend ApplicationRouteMixin,
The action should bubble up to the application route, where it will be handled.
See the documentation here
I'm trying to figure out how to implement the load more functionality like Telescope. This is what I have originally:
// Iron Router
Router.route('/posts', {
name: 'posts.index',
waitOn: function () {
return Meteor.subscribe('posts', Session.get('postsLimit');
},
data: function () {
return { posts: Posts.find({}, { sort: { createdAt: -1 } }) };
},
action: function () {
this.render();
}
});
// client/views/posts_list.html
<ul>
{{#each posts}}
<li>{{ title }}</li>
{{/each}}
</ul>
<a href"#" class="load-more">Load more</a>
// client/views/posts_list.js
var POSTS_INCREMENT = 3;
Session.setDefault('postsLimit', POSTS_INCREMENT);
Template.PostsIndex.events({
'click .load-more': function (e, tmpl) {
Session.set('postsLimit', Session.get('postsLimit') + POSTS_INCREMENT);
return false;
}
}
});
It makes sense that Meteor will rerender the list when the postsLimit changes. I'm just curious how Telescope did it without re-rendering the list and only render the new posts. From what I see from the code, instead of storing the limit in the Session, the author uses the route top/:limit? and instead of using waitOn, they use onBeforeAction. It's hard to pinpoint which part of the code helps prevent re-rendering the list. Could someone please help explain in detail how they did it?
The part that triggers the re-rendering is actually waitOn. By using waitOn, you're telling Iron Router to redirect you to the loading template while you wait, which is what triggers the re-rendering and sticks you back up at the top of the page.
This is waitOn's job, and it works great when going from page to page, but is obviously not ideal when re-rendering the same page.
By the way, note that the new subscriptions option can also trigger the same behavior (if you've set a global loading template).
So this is why we're using onBeforeAction in this specific case. This pattern is explained in more details in Discover Meteor, by the way.
Don't know if this is helpful but to load more posts all you have to do is add {{> UI.dynamic template=postsLoadMore}} in the postList template.
<template name="posts_list">
{{> UI.dynamic template=postsListIncoming data=incoming}}
<div class="posts-wrapper grid grid-module">
<div class="posts list">
{{#each posts}}
{{> UI.dynamic template=post_item}}
{{/each}}
</div>
</div>
{{> UI.dynamic template=postsLoadMore}}
</template>
I'm trying out ember js on a personal project. I'm struggling with understanding what part plays the different components of Ember.
I'm quite familiar with Rails, and I've used backbone to some extent (never used everything related to routing), and get how the two of them work, and what kind of objects plays which part of MVC.
With Ember, things are not that clear and seems a bit less easy. I've read the guides and a few posts, but I'm still struggling to get going. I get the model part, which is fairly similar to its rails and backbone counterpart. I get templates, handlebars, that stuff too. The router is similar to rails router.
Then there is Controllers, Routes, and Views. From what I understood, Views represent a portion of the ui, and handles user interaction. It's the controller/routes roles that I don't get very much.
To be more concrete: I want my app to have a nav bar (with links to page sections), and a "user connexion widget", displaying user data if he's connected, allow him to do so if not, log in via facebook etc.
The navbar seems appropriate for a View, however I need to keep the state somewhere (to highlight the current page), which seems to be the controller role. And the user widget seems appropriate for a controller, but there's only one controller per route, so how to do this ?
Thanks a lot for your time, I hope I was clear enough!
:)
Controller stores the state of your application. Router manages different states of your application. You're correct, there's only one controller per route, but you can access other controllers as well.
Assuming this your html:
<script type="text/x-handlebars" data-template-name="application">
<div class="main-content">
{{outlet}}
</div>
</script>
<script type="text/x-handlebars" data-template-name="index">
<div class="nav-bar">
<div class="user">
{{#if isLoggedIn}}
<div class="user-name">{{userName}}</div>
{{else}}
<button {{action "login" target="App.UserController"}}>
Login
</button>
{{/if}}
</div>
</div>
<div class="content">some other amazing content here</div>
</script>
and JS:
var App = Ember.Application.create({
LOG_TRANSITIONS: true,
rootElement: '#ember-app'
});
App.Router.map(function () {
this.route('user');
});
App.IndexRoute = Ember.Route.extend({
setupController: function(controller, model) {
this.controller.set('isLoggedIn', this.controllerFor('user').get('isLoggedIn'));
}
});
App.UserController = Ember.ObjectController.extend({
isLoggedIn: false,
userName: 'emberjs',
login: function () {
this.set('isLoggedIn', true);
}
});
App.NavbarView = Ember.View.create({
loggedInBinding: Ember.Binding.oneWay('App.UserController.isLoggedIn')
});
If you need to access another controller from your route, you can do this:
App.IndexRoute = Ember.Route.extend(function () {
setupController: function () {
var userController = this.controllerFor('user');
console.log('this is a user controller instance', userController);
}
});