How to update view after Ember.Handlebars.compile? - ember.js

I'm using emberJS (without Rails) and have templates in different files in templates/*.hbs
I used ajax to query them and compile templates.
App.SetupTemplate = function(name) {
$.ajax( {url:"templates/"+name+".hbs", success: function(response) {
Ember.TEMPLATES[name] = Ember.Handlebars.compile(response);
} });
};
App.ProfileRoute = Ember.Route.extend({
setupController: function(controller) { App.SetupTemplate('profile');
},
});
If I hit ProfileRoute it compiles the template but not updated automatically in view.
But it works for the later clicks on same route as template is already compiled and view is rendered properly.
I don't know how to use transitionTo to the same route which will make it happen.
What should I do to automatically update view on first click itself ?
-Thanks.

You'd need to block the transition until the template has actually been downloaded
App.needsTemplate = function(name){
return Ember.isEmpty(Ember.TEMPLATES[name]);
};
App.setupTemplate = function(name) {
return new Ember.RSVP.Promise(function(resolve, reject){
if(!App.needsTemplate(name)){
resolve();
} else {
$.ajax( {url:"templates/"+name+".hbs"}).then(function(response){
Ember.TEMPLATES[name] = Ember.Handlebars.compile(response);
resolve();
});
}
});
};
App.IndexRoute = Ember.Route.extend({
model: function() {
return ['red', 'yellow', 'blue'];
},
afterModel: function(model, transition){
if(App.needsTemplate('index')){
transition.abort();
App.setupTemplate('index').then(function(){
transition.retry();
});
}
}
});
http://emberjs.jsbin.com/OxIDiVU/340/edit

Related

Ember, mixin to detect click outside of view/component

I'm writing a Mixin to handle when user clicks outside of a view/component.
This is the mixin:
App.ClickElsewhereMixin = Ember.Mixin.create({
onClickElsewhere: Ember.K,
didRender: function() {
this._super.apply(this, arguments);
return $(document).on('click', this.get('onClickElsewhere'));
},
willDestroyElement: function() {
this._super.apply(this, arguments);
$(document).off('click', this.get('onClickElsewhere'));
},
});
I use it in my component:
onClickElsewhere: function() {
this.send('exitEditMode');
},
But when I run it, I get:
TypeError: this.send is not a function
How can I keep the this context?
Solution:
just to make it easier for the reader, here the working Mixin:
App.ClickElsewhereMixin = Ember.Mixin.create({
onClickElsewhere: Ember.K,
setupListener: Ember.on('didRender', function() {
// Set an event that will be fired when user clicks outside of the component/view
return $(document).on('click', $.proxy(this.get('onClickElsewhere'), this));
}),
removeListener: Ember.on('willDestroyElement', function() {
// Clean the previously defined event to keep events stack clean
return $(document).off('click', $.proxy(this.get('onClickElsewhere'), this));
}),
});
The current answer doesn't check whether the click was actually outside of the element – a click on the component will also trigger the callback.
Here's an updated version:
export default Ember.Mixin.create({
onOutsideClick: Ember.K,
handleOutsideClick: function(event) {
let $element = this.$();
let $target = $(event.target);
if (!$target.closest($element).length) {
this.onOutsideClick();
}
},
setupOutsideClickListener: Ember.on('didInsertElement', function() {
let clickHandler = this.get('handleOutsideClick').bind(this);
return Ember.$(document).on('click', clickHandler);
}),
removeOutsideClickListener: Ember.on('willDestroyElement', function() {
let clickHandler = this.get('handleOutsideClick').bind(this);
return Ember.$(document).off('click', clickHandler);
})
});
Greg answer have a mistake, that makes removing the clickHandler event not working. Which means that your clickevent will fire even if you destroy the component.
Here is proper version
import Ember from 'ember';
export default Ember.Mixin.create({
onOutsideClick: Ember.K,
handleOutsideClick: function(event) {
let $element = this.$();
let $target = $(event.target);
if (!$target.closest($element).length) {
this.onOutsideClick();
}
},
setupOutsideClickListener: Ember.on('didInsertElement', function() {
let clickHandler = this.get('handleOutsideClick').bind(this);
return Ember.$(document).on('click', clickHandler);
}),
removeOutsideClickListener: Ember.on('willDestroyElement', function() {
let clickHandler = this.get('handleOutsideClick').bind(this);
return Ember.$(document).off('click', Ember.run.cancel(this, clickHandler));
})
});
The ember way of doing it is Ember.run.bind. This takes care of binding and the run loop.
App.ClickElsewhereMixin = Ember.Mixin.create({
onClickElsewhere: Ember.K,
setupListener: Ember.on('didRender', function() {
this.set('clickHandler', Ember.run.bind(this, this.onClickElsewhere));
Ember.$(document).click(this.get('clickHandler'));
}),
removeListener: Ember.on('willDestroyElement', function() {
Ember.$(document).off('click', this.get('clickHandler'));
}),
});
You have two options:
Use a closure
Use bind
Closure
App.ClickElsewhereMixin = Ember.Mixin.create({
onClickElsewhere: Ember.K,
didRender: function() {
this._super.apply(this, arguments);
return $(document).on('click', function(this){ return this.get('onClickElsewhere'); }(this));
},
willDestroyElement: function() {
this._super.apply(this, arguments);
$(document).off('click', function(this){ return this.get('onClickElsewhere'); }(this));
},
});
Bind
App.ClickElsewhereMixin = Ember.Mixin.create({
onClickElsewhere: Ember.K,
didRender: function() {
this._super.apply(this, arguments);
return $(document).on('click', this.get('onClickElsewhere').bind(this));
},
willDestroyElement: function() {
this._super.apply(this, arguments);
$(document).off('click', this.get('onClickElsewhere').bind(this));
},
});
However, not all browsers support bind yet.
Also, I think you need to use sendAction instead of send in the component (http://guides.emberjs.com/v1.10.0/components/sending-actions-from-components-to-your-application/)
Edit:
jQuery.proxy uses call/apply underneath the covers. See this post for a discussion of call/apply vs bind.
You can use the lib ember-click-outside. Worked for me.

Ember + Ember Data Route error handling issue

In soume routes in my app error action is never triggered and I can't figure out why. On some Routes error action works fine.
This is application route:
Simitu.ApplicationRoute = Ember.Route.extend({
init: function() {
this._super();
Simitu.AuthManager = Simitu.AuthManager.create();
},
model: function() {
if (Simitu.AuthManager.get('session.user'))
return this.store.find('admin', Simitu.AuthManager.get('session.user'));
},
actions: {
error: function(reason, transition) {
if (reason.status === 401) {
Simitu.AuthManager.reset();
this.transitionTo('login');
}
}
}
});
On this route Error is never triggered:
Simitu.PlacesIndexRoute = Ember.Route.extend({
model: function() {
var self = this;
// force adapter request
this.store.find('place');
return this.store.filter('place', function(record) {
// return just places that belongs to this client / application
return record.get('client_id') === self.modelFor('client');
});
},
actions: {
createNew: function() {
var place = this.store.createRecord('place');
// tree structure in places is not implemented yet
//parent = this.store.find('place', params.place_id);
place.set('client_id', this.modelFor('client'));
// open place
this.transitionTo('place', place);
},
error: function(error, transition) {
return true;
}
}
});
And on this Route everything works just fine:
Simitu.ClientsRoute = Ember.Route.extend({
model: function() {
return this.store.find('client');
},
actions: {
error: function() {
return true;
}
}
});
Have anybody some ide why?
The error action is fired on the resource, not an individual route.
http://emberjs.jsbin.com/cayidiwa/1/edit
This is how my router looks like. Maybe it breaks because of the nesting or filter logic in models. I fixed it in beforeModel hook in routes but still have not clue what is wrong with my first solution.
Simitu.Router.map(function () {
this.resource('login');
this.resource('clients');
this.resource('client', { path: 'clients/:client_id'}, function() {
this.resource('places', function() {
this.resource('place', { path: ':place_id' });
});
this.resource('placecategories',{ path: 'places-categories' }, function() {
this.route('new');
});
});
});
I move some of auth handling logic to beforeModel hook.
Simitu.AuthRoute = Ember.Route.extend({
beforeModel: function(transition) {
if (!Simitu.AuthManager.isAutenticated()) {
this.redirectToLogin(transition);
}
},
redirectToLogin: function(transition) {
this.transitionTo('login');
},
actions: {
error: function(reason, transition) {
if (reason.status === 401) {
Simitu.AuthManager.reset();
this.redirectToLogin(transoition);
}
}
}
});

ArrayController Not Updating When Deleting Records

using ember-data 1.0.0-beta.5 and have the following routes and router
App.Router.map(function () {
this.resource('users', function () {
this.route('new');
});
this.resource('user', { path: '/user/:id' }, function () {
this.route('edit');
});
});
App.UsersIndexRoute = Ember.Route.extend({
model: function(){
return this.store.findAll('user');
},
setupController: function (controller, data) {
this._super(controller, data);
}
});
App.UserEditRoute = Ember.Route.extend({
model: function() {
return this.store.find('user', this.modelFor("user").id);
},
setupController: function (controller, data) {
this._super(controller, data);
},
actions: {
delete: function(){
var router = this;
var model = this.currentModel;
model.destroyRecord().then(function(){
router.transitionTo('users');
});
}
}
});
However when i transition back to the users route the ArrayController still has the deleted object in it. Any ideas as to why this is or how to wait until it is removed before transitioning?
kind of a work around but solves the problem.
in the UsersIndexContoller add a array to store the deleted ids
App.UsersIndexController = Ember.ArrayController.extend({
deleted: []
});
Then just append the id of the deleted model to this array and filter in the setupController method.
App.UsersEditRoute = Ember.Route.extend({
delete: function(){
var router = this;
var model = this.currentModel;
var usersController = this.controllerFor('users.index');
model.destroyRecord().then(function(){
usersController.get('deleted').push(model.id);
router.transitionTo('users');
});
}
}
});
App.UsersIndexRoute = Ember.Route.extend({
setupController: function (controller, data) {
var deleted = this.controller.get('deleted');
var filtered = data.filter(function(user){
return !deleted.contains(user.id);
});
data.set('content', filtered);
this._super(controller, data);
}
});
In your case since you want to refetch all of the users and your server lies to you about deleting the record (or it happens later), you might contemplate transitioning then deleting.
delete: function(){
var model = this.currentModel;
this.transitionTo('users').then(function(){
model.destroyRecord();
});
}

Ember.js - Duplicate record in Store that clears after refresh

I have a basic Rest API and when I visit my newPost page, there is a form I can fill in with 'title' and 'text' fields as well as a submit button. After I create the record, I save it with the controller and transitionToRoute('posts').
When I view the 'posts' page, the input I just entered is displayed twice. I checked the Ember inspector and it looks like the record is put into the data store twice, once as I click the create button and one after the page refreshes. When I refresh the browser again, the duplicate is removed.
How do I get rid of the duplicate record in the store? Cheers for any suggestions.
EDIT: I believe I have solved my issue by using a filter to remove posts that hadn't been persisted yet.
Updated Routes:
App.PostsRoute = Ember.Route.extend({
model: function() {
return this.store.find('post');
},
setupController: function() {
var posts = this.store.filter('post', function (post) {
return !post.get('isDirty');
});
this.controllerFor('posts').set('model', posts);
}
});
App.NewPostRoute = Ember.Route.extend({
model: function(){
return this.store.createRecord('post');
}
});
Routes:
/* Routes */
App.Router.map(function() {
this.resource('posts');
this.resource('newPost');
});
App.PostsRoute = Ember.Route.extend({
model: function() {
return this.store.find('post');
},
setupController: function() {
var posts = this.store.filter('post', function (post) {
return !post.get('isDirty');
});
this.controllerFor('posts').set('model', posts);
}
});
App.NewPostRoute = Ember.Route.extend({
model: function(){
return this.store.createRecord('post');
}
});
NewPostController:
actions:
createPost: function() {
...
var post = this.get('store').createRecord('post', {
title: title,
text: text
});
this.set('title', '');
this.set('text', '');
post.save();
this.transitionToRoute('posts');
...
newRecord: function() {
this.set('content', App.Post);
}
Thanks!

Why don't nested resources in Ember.js preserve the params hash?

Given the following Ember.js application (using Ember 1.0.0.rc.6.1 and Ember Data 0.13):
App = Ember.Application.create({ LOG_TRANSITIONS: true });
App.Store = DS.Store.extend();
App.Router.map(function() {
this.resource('promotions', function() {
this.resource('promotion', { path: '/:promotion_id' }, function() {
this.resource('entrants', function() {
this.resource('entrant', { path: '/:entrant_id' });
});
});
});
});
App.PromotionRoute = Ember.Route.extend({
model: function() {
return { id: 1, name: 'My Promotion' };
}
});
App.EntrantsIndexRoute = Ember.Route.extend({
model: function(params) {
console.warn('EntrantsIndexRoute', '\nparams:', params, '\nparams.promotion_id:', params.promotion_id, '\narguments:', arguments);
console.log('params should be:', { promotion_id: 1 });
console.log('The queried URL should be:', '/entrants?promotion_id=1');
return App.Entrant.find({promotion_id: params.promotion_id});
}
});
App.Entrant = DS.Model.extend({
name: DS.attr('string')
});
If you enter the url #/promotions/1/entrants, which should be a nested resource, the params is an empty object. How can I access promotion_id there? JSFiddle here, take a look at the console after clicking on "Click me": http://jsfiddle.net/Kerrick/4GufZ/
While you can't access the dynamic segments of the parent route, you still can retrieve the model for the parent route and get its ID, like this:
App.EntrantsIndexRoute = Ember.Route.extend({
model: function() {
var promotion_id = this.modelFor('promotion').id;
return App.Entrant.find({ promotion_id: promotion_id });
}
});
Or, if there is a has-many relation between promotion and entrants, you even might do:
App.EntrantsIndexRoute = Ember.Route.extend({
model: function() {
return this.modelFor('promotion').get('entrants');
}
});
Try this code:
App.EntrantsIndexRoute = Ember.Route.extend({
model: function() {
var promotion_id = this.modelFor('promotion').query.promotion_id;
return App.Entrant.find({ promotion_id: promotion_id });
}
});