Rendering a form inside a bootstrap modal in Ember - ember.js

There are plenty of questions already that ask about modals in Ember (like this one and this one). Even the cookbook has an article that explains how to use modals, but none of these cover form submissions that require validation from the server within the modal (ie, username already taken).
Following along with the cookbook, I have a named outlet in my application template.
{{outlet}}
{{outlet modal}}
And an action that triggers rendering a template inside the modal outlet (using bootstrap).
App.ApplicationRoute = Ember.Route.extend
actions:
openModal: (template, model) ->
#controllerFor(template).set('model', model)
#render template, into: 'application', outlet: 'modal'
closeModal: ->
#render '', into: 'application', outlet: 'modal'
Obviously I'm calling this action from within a link.
<a href="#" {{action openModal "person.edit" model}}>Edit</a>
Where model is the model of the current route.
In the PesonEditView I hook into the didInsertElement function to enable the bootstrap modal. Inside this hook, I also listen to the hidden.bs.modal event fired by bootstrap when the close button is clicked to clear out the modal outlet.
App.PersonEditView = Ember.View.extend
didInsertElement: ->
#$('.modal').modal('show').on 'hidden.bs.modal', =>
#get('controller').send('closeModal')
My question is, how can I define a save action that will close the modal (using bootstraps animations) after it has validated on the server? The sequence of events that I see are required are, 1) save gets triggered on controller, 2) if successful save, close the modal (which would require calling #$('.modal').modal('hide') from the view).
I'm not sure what Ember experts would suggest here, since it seems as though the view and controller will need to communicate very closely.
Thanks!
EDIT
In response to edpaez's comment, I have tried resolving the promise returned by save from within the view. For example, in my template
<button type="button" class="btn btn-primary" {{action "save" target="view"}}>Save</button>
The view
App.PersonEditView = Ember.View.extend
actions:
save: ->
#get('controller').send('save').then(#closeModal.bind(#))
closeModal: ->
#$('.modal').modal('hide')
# the rest omitted for brevity
And the controller
App.PersonEditController = Ember.ObjectController.extend
actions:
save: ->
#get('model').save()
I get the following error
Uncaught TypeError: Cannot call method 'then' of undefined

Try targeting the save action to the view:
<button type="button" class="btn btn-primary" {{action "save" target="view"}}>Save</button>
and call a save method in the controller. That method would return a promise that would resolve when the save method from the model resolves.
App.PersonEditView = Ember.View.extend
actions:
save: ->
#get('controller').save().then(#closeModal.bind(#))
closeModal: ->
#$('.modal').modal('hide')
App.PersonEditController = Ember.ObjectController.extend
save: ->
#get('model').save()
This way, the controller abstracts the model-saving logic and the view gets notified when the model saves so it can behave as expected.
Make sure you call the save method in the controller instead of sending an action. the send method returns nothing.
Hope it helps!

Related

Send actions to the Application controller in EmberJS

I'm trying to toggle a global property on the application controller, by clicking a button on one of the templates. I've read some stuff on action bubbling but can't it to work.
Here's the property and action on the Application controller
export default Ember.Controller.extend({
isAuthenticated: true,
actions: {
logIn: function(){
this.toggleProperty('isAuthenticated');
}
}
});
And here's the action with a login.hbs template file (I'll turn this into a proper button soon)
<span {{action 'logIn'}}>
{{#link-to 'boards' class="btn-l bg-blue white db mtl link bn w-100"}}Login{{/link-to}}
</span>
How could I ensure the action toggles the property on the Application Controller?
In your login controller,you need to inject application controller.
import Ember from 'ember';
export default Ember.Controller.extend({
appCont:Ember.inject.controller('application')
});
and in login.hbs you need to specify which target will receive the method call.
<button {{action 'logIn' target=appCont}}> Login </button>
In this <button {{action 'logIn'}}> Login </button> , context of a template is login controller, actions used this way will bubble to login route when the login controller does not implement the specified action. Once an action hits a login route, it will bubble through the route hierarchy.
Reference: http://emberjs.com/api/classes/Ember.Templates.helpers.html#toc_specifying-a-target
EDIT: If you want to call functions available in Controller inside Component then you need to pass actions toComponent`
Login.hbs
{{component-a logIn=(action 'logIn') }}
component-a.hbs
<button {{action (action logIn)}}> LogIn</button>

Emberjs: Accessing parent route model which is a Promise (master-detail)

I've got master-detail page layout as on image. I access this page through #/masters/:master_id app url.
Routes a defined as follows:
App.Router.map(function() {
this.resource('masters', { path: '/masters' }, function() {
this.route('detail', { path: '/:master_id' });
});
});
App.MastersRoute = Ember.Route.extend({
model: function() {
return App.DataStore.getData('/api/masters'); //returns Promise!
},
setupController: function(controller, model) {
controller.set("content", model);
}
});
App.MastersDetailRoute = Ember.Route.extend({
model: function(params) {
return this.modelFor("masters").find(function(item) {
return item.get("id") == params.master_id;
});
}
});
Templates:
<script type="text/x-handlebars-template" data-template-name="masters">
<div id="masters-grid">
{{#each master in model}}
<div {{action "show" master}}>
{{master.name}}
</div>
{{/each}}
</div>
<div id="detail">
{{outlet}}
</div>
</script>
<script type="text/x-handlebars-template" data-template-name="masters/detail">
{{model.name}} <br />
{{model.age}} <br />
{{model.address}} <br />
</script>
When clicking through masters in the grid I want to show their details in Detail outlet and I do not want to reload all masters from API when changing the master selection.
I have a problem with MastersDetailRoute's model, because this.modelFor("masters") returns undefined. I think, it is caused by returning Promise in model hook. Any idea or workaround how to access one item from Masters model or controller in "child route" model hook?
I see a few things here.
when defining routes that have the same url as the route name theres no need to specify the path
the detail route should also be a resource as it is a route backed by a model
In the Masters route returning a promise is correct and supported natively by ember. The route wont be resolved until the promise is.
setup controller isn't required
its usually best to do the required api call to fetch the individual record in the detail route. This will only be used when loading the page for the first time (if f5 ing or coming from a bookmark)
in your masters template you can use id instead of typing data-template-name or better still look into use ember-cli/brocolli or grunt to precompile your templates
to prevent ember refetching your model when selecting a row use the handlebars helper link-to
{{#link-to 'masterDetail' master}}
{{master.name}}
{{/link-to}}
just to clarify, using link-to in this way passes the object specified in the second parameter as the model to the specified route (first parameter). In your case master will now be set as the model to the master detail route.
in masters detail theres no need to type "model" the default context (i.e. the value of "this") in your template is the controller, then if the property is not found on the controller it looks for it in the model.
Hope this helps

Evaluating controller property everytime the template is rendered in Ember

I have a template called sample and I am changing the property show in the controller based on a button press. Now I want the controller to reset the property every time the template is rendered. Currently even if I go to 'next' template and come back, the property show remains true if the button on sample was pressed. I want to change this behaviour and the property show should be false by default. I know I can do this by defining a view and using the didInsertElement hook but is that the only way to do this?? Ember.js website says that Views in Ember.js are typically only created for the following reasons:
When you need sophisticated handling of user events
When you want to create a re-usable component
and I am doing none of the above. Here is some sample code:
<script type="text/x-handlebars" data-template-name="sample">
{{#if show}}
Showing stuff
{{/if}}
<button {{action changeShow}}>Change</button>
{{#link-to 'next'}} Next {{/link-to}}
</script>
<script type="text/x-handlebars" data-template-name="next">
Hello
{{#link-to 'sample'}}Back{{/link-to}}
</script>
App.SampleController=Ember.Controllers.Extend{(
show:false,
actions:{
changeShow:function(){
this.controllerFor('sample').set('show',true);
}
}
)};
you can use didTransition action which will trigger automatically once the transition happened. didTransition action
App.SampleController=Ember.Controllers.Extend{(
show:false,
actions:{
didTransition:function(){
this.controllerFor('sample').set('show',false);
},
changeShow:function(){
this.controllerFor('sample').set('show',true);
}
}
)};
You can use the renderTemplate hook for the route you're doing this in, and change the controller variable in there.
http://emberjs.com/api/classes/Ember.Route.html#method_renderTemplate
I'd do something like this:
App.PostsRoute = Ember.Route.extend({
renderTemplate: function(controller, model) {
var favController = this.controllerFor('favoritePost');
favController.set("toggle", false)
this._super()
}
});

Ember View not finding the controller/action

I'm trying to call my controller's action from my view with Ember, but it says:
Uncaught TypeError: Cannot call method 'send' of null
I just can't find the right way to work with views in ember.
My view layout has a call like:
<button type="button" {{action modalConfirmation target="view"}} class="btn btn-primary">Save changes</button>
And my View class tries to call the controller in this fashion:
this.get('controller').modalConfirmation();
My Controller has something like this:
ProjEmber.BananasIndexController = Ember.ArrayController.extend({
actions: {
showModal: function() {
modalinaView.title = "My Title";
modalinaView.templateName = "any_template_you_wish";
modalinaView.append();
},
modalConfirmation: function() {
console.debug('Action modalConfirmation');
}
}
});
OBS: it works if I append my view using the helper like this:
{{#view ProjEmber.ModalinaView title='A title'}}
A not so good application of a modal view. Just for the sake of illustration.
{{/view}}
You can see the full source on Github, especifically this part of the commit:
https://github.com/lucaspottersky/ember-lab/commit/4862426b39adc0bbcce0b4cc3fd0099439f8dd55#commitcomment-4421854
There is a good likelihood it's failing to be appended within the body, or the scope of your ember app which would be why the events aren't propagating to your actions hash.
You might try appendTo('body')
You shouldn't access view like this
var modalinaView = this.container.lookup('view:modalina');
This PR can give you more insights.
You are doing the same as Stefanpenner has done in this commit.
And this is Wycats reply.
Alternatively, This answer may help you in instantiating modals

toastr and ember.js

Is the popup library toastr not going to work with Ember because of direct dom manipulation that ember doesn't like?
Are there any other libraries like this one that work nicely with ember?
Edit
Even through the working example posted below I could not get this to work locally. I finally used Pine Notify which worked straight away.
This works fine in Ember, you just have to handle the event in the right place. The "right place" depends on your implementation. If you want this to be fired from a button within your view, you'll need to use the {{action}} helper passing the action name. Example:
<script type="text/x-handlebars" >
<button class="btn btn-info" {{action showInfo}}>Info</button>
</script>
In the template above, I'm saying that the button should fire the showInfo event, so the Controller responsible for this view should have a function with the same name:
App.ApplicationController = Em.ArrayController.extend({
showInfo: function() {
toastr.info('This is some sample information');
}
});
You can also have the view handle the event; the code below defines a click event, so if you click anywhere in the view, it would run your function:
App.OtherView = Em.View.extend({
click: function(e) {
toastr.error('This is some sample error');
}
});
and in your Handlebars template, you don't have do tell the action since you are already saying in the view class that you want to handle the click event for that view, so you can simple render the view and style it:
{{#view App.OtherView class="btn btn-danger"}}
Error
{{/view}}
Here's a sample in JSFiddle: http://jsfiddle.net/schawaska/YZwDh/
I recommend that you read the Ember Guide about the {{action}} helper