Here's a JSBin demonstrating an issue I'm experiencing. This is the code for it.
I've run into a situation where if I navigate to a route in Ember.js after the application has been loaded (e.g. using transitionTo), everything works. But if I try to navigate to the route directly (by entering the url or refreshing the page once I'm already on the route) it doesn't render correctly.
I'm using renderTemplate to place the template into a parent route. But the parent route hasn't been rendered yet when visiting the route directly, so it fails with the following error in the console:
Cannot read property 'connectOutlet' of undefined
How can I ensure that the parent route's template is in place to avoid this error?
When you render a template into another template programmatically the other template must have already been rendered. You can schedule it to render after it's finished rendering.
App.BarRoute = Ember.Route.extend({
renderTemplate: function() {
var self = this;
Em.run.schedule('afterRender', function(){
self.render('bar', {into: 'baz'});
});
}
});
Example: http://jsbin.com/qilem/1#/bar
Really you're fighting the ember router heavily here. The application template should have an {{outlet}} defined inside of it, but when you use render you've created a nested scope where the outlet no longer exists in the application template. You can either use partial or pull the {{outlet}} out of the nested template.
Example Partial: http://jsbin.com/qilem/2#/bar
Example Render: http://jsbin.com/qilem/3#/bar
Related
This is very basic question but i am not finding it any where.
As per my understanding before rendering template correspondin route or component.js's beforeModel() model() etc functions gets called.
What i want to do:
I want to show image of logged in user on my sidenav. User's data is stored in local storage.
My problems here
I am hoping that setting a variable in model and returning the same will solve my problem, but my model method is not being called at all.
My Code:
Template:
{{#paper-sidenav
class="md-whiteframe-z2"
name="right"
open=leftSideBarOpen2
lockedOpen=leftSideBarLockedOpen
position="right"
onToggle=(action (mut leftSideBarOpen2))}}
{{#paper-toolbar as |toolbar|}}
{{#paper-toolbar-tools}}
<img src="http://example.com/users/{{model.username}}.jpg" />
{{/paper-toolbar-tools}}
{{/paper-toolbar}}
{{#paper-content padding=true}}
Çup?
{{/paper-content}}
{{/paper-sidenav}}
Component.js
import Ember from 'ember';
export default Ember.Component.extend({
beforeModel(){
},
model(){
let user = localStorage.get('user');
console.log(user.username);
return user;
},
actions:{
toggle(propName) {
this.toggleProperty(propName);
}
}
});
In console i am getting error "GET http://example.com/users/.jpg 404 (Not Found)", which certainly says that user.username in my template evaluates to null. i tried debugging my model method in chrome's dev tool and found that model is not getting called
Could you please let me know where i'm mistaking?
Ember component does not have model() and afterModel() hooks, what you need to do, is first access the local storage data in afterModel() hook in route and pass the resolved model to component.
Main Route
export default Ember.Route.extend({
model(){
//load data
},
afterModel(model){
//access local store and set to model
model.set('users',data);
}
});
Main route hbs
{{component componetModel=model}}
inside component
import Ember from 'ember';
export default Ember.Component.extend({
init(){
this.set('users', componetModel.users);
}
});
component hbs
{{#each user in users}}
{{user.name}}
{{/each}}
You really have to read the documentation about component. It clearly state what are the lifecycle hooks for each stage,
init
update
delete
https://guides.emberjs.com/v2.11.0/components/the-component-lifecycle/
The only thing the template have access to from route is model so that you can use model directly inside the corresponding template OR pass it to the component(s).
If you need other property for your component you can either set them inside route's setupController(controller, model) hook OR create a controller for that route.
Illustration below is a sample for one specific route,
NOTE: Dashed line represents the runtime generate controller
See image above each "layer" only have access the property OR action direct inside the upper layer (special case is route auto inject model into runtime generated controller).
You do NOT need to specifically define your own controller because if the route cannot find one it will generate one at runtime.
The component is completed isolated from literally anything (unless you inject anything into it). The only way to work with the component is to pass data and action (usually used to handle events) to it.
Summary
(1) The component can only access the data you specifically passed to it (see code below).
{{component-name
internalName=externalName
}}
(2) The template can only access the data that is available inside the controller, either auto-generated one or via ember g controller controller-name. (model property is an exception)
(3) If you want to your template to have route data, using setupController hook.
In my Backbone app (Rails backend), I am using [Handlebars] Javascript templates (JST's) for rendering my Backbone views. Whenever I do a browser refresh while on one of those URL templates, it jumps back to the root URL. I want it to stay on that current template after page-refresh. I want to do something like this...
getPage: function() {
$(window).on('beforeunload', function(){
var fragment = Backbone.history.fragment;
});
if (fragment === "this_current_template") {
return App.getCurrentTemplatePage(); // render it
} else if (fragment === "") {
return App.getFrontMainPage(); // render it
}
}
getPage is a method in my backbone App object. My fragment variable can't be recognized outside of the $(window) event handler. I can't seem to find a way to persist my previous Backbone.history.fragment in a variable, even if I make fragment global. It's all swept away after refresh. I know about Backbone.history.loadUrl(Backbone.history.fragment);, but I just can't get that to work either. It only seems to work prior to browser refresh.
Is it something I'll have to change in the root route of my backend code? or is there a way to persist the current page using Backbone / JS on page reload? Any help on this would be much appreciated!
First of all you need to create a Router. According to Backbone docs:
var Router = Backbone.Router.extend({
routes: {
"main": "main"
},
main: function() {
// your code here
}
});
After that you only need to init Backbone.history:
Backbone.history.start({});
After that Backbone automatically will try to find appropriate route for your hashbang and fire the appropriate action. In given example if your initial hashbang will be #main then Router.main action will be executed.
Inside an application we allow users to create new records, related to an existing record. To achieve this, we use actions something like this:
createUser() {
var route = this;
var model = this.store.createRecord('user', {
client: route.modelFor('client'),
});
route.transitionTo('user.update', model);
},
The user.update route renders a user-form component, using the model that was passed in the transition. The same route is also used to update existing users.
The issue with this approach is as follows; when refreshing the page, the page errors because the route fails to find the respective record when querying the store (at this point, the URL is /users/null/update). Ideally I'd pass the client (or client.id) argument in the URL so that:
The page can be reloaded without issue.
The client associated with the user is set correctly.
How can I achieve this in Ember.js? I know that this can easily be done using nested routes (by nesting the user.update route inside a client route), but this doesn't make sense visually.
The relevant parts of the router are as follows:
this.route('clients');
this.route('client', {path: 'clients/:id'}, function() {
this.route('users');
});
this.route('user', {path: 'users/:id'}, function() {
this.route('update');
});
All I do in the user/update.hbs template is {{user-form user=model}}
The problem is that the model you just created has no id at that point because it is not saved, ember can´t route to a model without an id, if possible save the model before you try to transition to the route, if you don´t want to save the model because the user can cancel the action check this thread where a user had the same problem (if I understand you problem correctly), I provided a solution for that problem that I´m using in my own project
https://stackoverflow.com/a/33107273/2214998
I want to insert a component into controller template without using the handlebars helper (component "component-name"... or component-name). Or through a controller in an outlet (or as long as the solution works for a component that wants to insert another component, then it's fine, I don't think outlets work in components).
In other words:
App.IndexController = Ember.Controller.extend({
actions: {
insertComponent: function() {
var component = this.container.lookup("component:my-inserted", { singleton: false });
component.set("layoutName", "components/my-inserted");
// to be like handlebars-inserted component, what do i do here?
}
}
});
You can use test with this: http://emberjs.jsbin.com/popozanare/4/edit?html,js,output
Why?
Thinking of a way of to have clean modal syntax, such as the "openModal" syntax described in the Ember Cookbook: http://guides.emberjs.com/v1.10.0/cookbook/user_interface_and_interaction/using_modal_dialogs/.
The problem is that the source context is lost, as the modal is within the ApplicationRoute. I want the same syntax when calling a modal, but keeping the hierarchy. You can keep the hierarchy using https://github.com/yapplabs/ember-modal-dialog, which requires a mapping of variables... which i don't like either (but will likely implement if I have no other choice).
TD;LR: Want to open modal within the controller/component (context) that called it without scaffolding in the controller/component that called it (mapping variables, etc).
Edit:
On second thought, using a container view might be cleaner than mapping variables, found in this solution: http://jsbin.com/hahohi/1/edit?html,js,output. Still needs scaffolding though. Thanks #user3568719.
That cookbook is a bit outdated, but if you are looking for a "clean" way to handling modals in your app I would suggest named outlets.
Add it to your application or auth template {{outlet "modal"}} and when you want to bring up the modal you can catch the action on the corresponding route and then render into that named outlet like so:
this.render('your-desired-modal-template', {
into: 'auth',
outlet: 'modal'
});
And when you want to dismiss it simply disconnectOutlet like so:
this.disconnectOutlet({
outlet: 'modal',
parentView: 'auth'
});
This is the way we've been going about it, I m open to suggestions/better methods.
I currently render my templates manually like so:
App.IndexRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('index');
this.render('nav', {
outlet: 'nav',
into : 'index'
});
this.render('welcome', {
outlet: 'welcome',
into : 'index'
});
}
});
Now this works fine, but its been brought to my attention that best ember practices avoid manually rendered templates.
So I can understand that my index template should and would be rendered automatically dude to Ember awesomeness. However, how do I control the rendering of my nav and welcome templates inside of my index template? Is that something I handle in the router, controller? Or should I do it with handlebar partials?
Thanks.
in the template where you have your outlets replace it with template renders
instead of
{{outlet nav}}
use
{{render 'nav'}}
and you can completely remove the render section for nav. After you've done the same for welcome you can delete the renderTemplate hook altogether.
Additionally, there is nothing wrong with using the renderTemplate hook, it's a very accepted practice, granted in your case unnecessary.
read more about the render helper here http://emberjs.com/guides/templates/rendering-with-helpers/#toc_the-code-render-code-helper