I'm trying to work with a simple overlay component, and close this overlay if someone clicks outside of the overlay content:
<div class="overlay" {{action 'close' on='click'}}>
<div class="item">
<form {{action 'submit' on='submit'}}>
{{yield}}
{{#link-to closeRoute class="close"}}Close{{/link-to}}
</form>
</div>
</div>
The component looks like this:
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
submit: function() {
this.sendAction();
},
close: function(param) {
console.log(param); // -> undefined
console.log(this); // -> complete component object, no reference to the event?
// this.$("a.close").click();
}
}
});
This works like advertised, however, I need to determine the target of the click event, because also clicks on the item and form will trigger this click(close) action.
Question: How can I access the (jQuery) event object which has a target from within the close action inside the component?
I am using EmberCLI, and Ember 1.9
I have found this to provide the required result:
export default Ember.Component.extend({
classNames: ['overlay-block'],
didInsertElement: function() {
var self = this;
self.$().click(function(e) {
if (self.$(e.target).hasClass("overlay-block")) {
self.$("a.close").click();
}
});
}
});
This does not use an ember action like I expected. I'll leave the question open for a while to see if somebody comes up with an more 'Ember way' of doing this.
More Ember way
export default Ember.Component.extend({
classNames: ['overlay-block'],
click: function(e) {
if (this.$(e.target).hasClass("overlay-block")){
this.$("a.close").click();
}
}
});
Related
In my app I've defined this component:
// components/buy-functions.js
export default Ember.Mixin.create({
actions: {
openModal: function() {
$('#buyModal').openModal();
}
}
});
then in the route's template:
<h5>Buy form</h5>
{{#buy-functions}}
<div class="btn" {{action "openModal"}}>Buy</div>
{{/buy-functions}}
(the component does not have a template)
But when i click the button I get the error "nothing handled the "openModal" action...
Can someone explain what I'm doing wrong here?
You need to send your action to route
openModal: function(modalName) {
this.sendAction('openModal',modalName);
}
}
change your button to
<div class="btn" {{action "openModal" 'myModal'}}>Buy</div>
and then in your route
openModal: function(modalName) {
//do whatever you want
}
But another way would be:
let's change your component to
// components/buy-functions.js
export default Ember.Component.extend({
actions: {
openModal: function() {
$('#buyModal').openModal();
}
}
});
then create your component template
// tempaltes/components/buy-functions.hbs
<div class="btn" {{action "openModal"}}>Buy</div>
and then in your route template only use your component name
{{buy-functions}}
I wrote these codes on the fly. hope it works for you.
There is an add-on (ember-route-action-helper) that provides a helper (route-action) for just this very use case.
There's a blog about it.
I am new to ember. Have use liquid fire to slide between pages. But would like to add a transition to my ember modal.
I'd like fade in - like this - http://ember-animation.github.io/liquid-fire/#/modals (see bottom of the page).
My code:
app/templates/components/hello-modal.hbs:
<div>{{salutation}} {{person}}!</div>
<div>{{input value=message class="modal-input"}}</div>
<button {{action "gotIt"}} class="done">Thanks</button>
<button {{action "change"}} class="change">Change</button>
app/components/hello-modal.js:
export default Ember.Component.extend({
classNames: ['hello-modal'],
actions: {
gotIt: function() {
this.sendAction('dismiss');
},
change: function() {
this.sendAction('changeSalutation');
}
}
});
step 3 ( as you can see from the example in link says):
'Call modal() within your router map, at whichever scope you choose and wire up any actions:'
Not sure I understand this. But I added it to my cupcakes router:
router.js
Router.map(function() {
this.route('device');
this.route('cupcakes', function() {
this.modal('hello-modal', {
withParams: ['salutation', 'person'],
otherParams: {
modalMessage: "message"
},
actions: {
changeSalutation: "changeSalutation"
}
});
});
cupcakes.hbs
{{#link-to (query-params salutation="Guten tag" person="Stef") tagName="button"}}
Try It
{{/link-to}}
controllers/cupcakes.js:
export default Ember.Controller.extend({
queryParams: ['salutation', 'person'],
salutation: null,
person: null,
modalMessage: "bound text for modal",
});
Nothing happens when I click 'try it' on cupcakes.hbs And no error messages show in the console.
Did you add {{liquid-modal}} to your templates/application.hbs file?
My application.hbs looks like this:
// templates/application.hbs
{{outlet}}
{{liquid-modal}}
I have an ember Select that is bound to a property of the model in a modal dialog.
I want the model property to change only if the user clicks OK (and gets reverted if he clicks cancel).
How can I do that?
edit-session-modal.hbs:
{{#modal-dialog title='Change status (select empty to return to original status)' ok='save' close='closeModal'}}
<form {{action 'ok' on='submit' target=view}}>
<div class="form-group">
<label>Status</label>
{{view "select" content=sessionStatuses selection=model.status}}
</div>
</form>
{{/modal-dialog}}
controllers/edit-session-modal.js:
import Ember from 'ember';
export default Ember.Controller.extend({
actions: {
close: function() {
return this.send('closeModal');
}
},
sessionStatuses: ['', 'SUCCESS', 'FAILURE', 'RUNNING'],
selectedStatus: "model.status"
});
You can achieve it by not binding the model.status property to selection property of Ember.Select, but use some kind of a buffer.
Let's say that your template look like this:
{{view "select" content=sessionStatuses selection=userSelection}}
And your controller should have following actions:
actions: {
close: function() {
this.set("selection", undefined); # or any other that should be default
# everything else, like closing the modal
},
save: function() {
this.set("model.status", this.get("selection"));
# everything else, like saving the model
}
}
EDIT after #Boaz reference
In #Boaz example there is no route defined for that controller. The solution turned out to be creating view object with didInsertElement as follows:
didInsertElement: function() {
modelStatus = this.get('controller').get('model.editedStatus');
this.get('controller').set('selectedStatus', modelStatus);
}
I was wondering if someone could give me brief direction. I'm making an app that I want to be able to take notes from anywhere I'm at in the app (CRUD). I'm rendering my presentations in my application controller using {{render}} but I'm not sure how to put the full crud operations there as well. This is what I have so far:
-- Presentation Controller
import Ember from 'ember';
var PresentationController = Ember.ObjectController.extend({
actions: {
edit: function () {
this.transitionToRoute('presentation.edit');
},
save: function () {
var presentation = this.get('model');
// this will tell Ember-Data to save/persist the new record
presentation.save();
// then transition to the current user
this.transitionToRoute('presentation', presentation);
},
delete: function () {
// this tells Ember-Data to delete the current user
this.get('model').deleteRecord();
this.get('model').save();
// then transition to the users route
this.transitionToRoute('presentations');
}
}
});
export default PresentationController;
-- Presentations Controller
import Ember from 'ember';
var PresentationsController = Ember.ArrayController.extend({
actions: {
sendMessage: function ( message ) {
if ( message !== '') {
console.log( message );
}
}
}
});
export default PresentationsController;
-- Model
import DS from 'ember-data';
var Presentation = DS.Model.extend({
title: DS.attr('string'),
note: DS.attr('string')
});
-- Presentations Route
import Ember from 'ember';
var PresentationsRoute = Ember.Route.extend({
model: function() {
return this.store.find('presentation');
}
});
export default PresentationsRoute;
-- Presentation Route
import Ember from 'ember';
var PresentationRoute = Ember.Route.extend({
model: function (params) {
return this.store.find('presentation', params.id);
}
});
export default PresentationRoute;
-- Application Route
import Ember from 'ember';
export default Ember.Route.extend({
model: function () {
return this.store.find('category');
},
setupController: function (controller, model) {
this._super(controller, model);
controller.set('product', this.store.find('product'));
controller.set('presentation', this.store.find('presentation'));
}
});
-- Application HBS
<section class="main-section">
<div id="main-content">
{{#link-to "presentations.create" class="create-btn expand" tagName="button"}} Add presentation {{/link-to}}
{{render 'presentations' presentation}}
{{outlet}}
</div>
</section>
-- Presentations HBS
{{#each presentation in controller}}
{{#link-to 'presentation' presentation tagName='li'}}
{{presentation.title}}
{{/link-to}}
{{/each}}
{{outlet}}
-- Presentation HBS
{{outlet}}
<div class="user-profile">
<h2>{{title}}</h2>
<p>{{note}}</p>
<div class="btn-group">
<button {{action "edit" }}>Edit</button>
<button {{action "delete" }}>Delete</button>
</div>
</div>
Basically what you're describing is a modal of sorts. It'll be accessible no matter what page (route) you're viewing, and you will be able to perform actions within this modal (creating notes, editing notes, deleting notes, etc) without leaving or affecting the current page being displayed in the background. Essentially, what this means is that you should leave the router alone, since the router is what controls the current page, and you don't want to affect that. You're not going to want to have any {{#link-to}} or transitionTo or transitionToRoute calls, nor any presentation-related routes or outlets.
Instead, you're going to have to handle everything at the controller and view level. This is where components really come in handy, as they're great for encapsulation if you use them correctly. Inside of presentations.hbs, I'd use components to represent each of the presentations:
{{#each presentation in controller}}
{{individual-presentation presentationModelBinding="presentation"}}
{{/each}}
Note that you'll need a corresponding IndividualPresentationComponent object that extends Ember.Component. Going further, inside of individual-presentation.hbs, I'd have code similar to what you have now, but with allowances for various CRUD operations:
{{#if editing}}
{{input value=presentationModel.title}}
{{textarea value=presentationModel.note}}
{{else}}
<h2>{{title}}</h2>
<p>{{note}}</p>
{{/if}}
<div class="btn-group">
{{#if editing}}
<button {{action "save" }}>Save</button>
{{else}}
<button {{action "edit" }}>Edit</button>
{{/if}}
<button {{action "delete" }}>Delete</button>
</div>
Note that the context for a component's template is the component itself, not some other controller. Similarly, actions fired inside of a component's template are direct to the component's actions hash. So your IndividualPresentationComponent will need to look like this somewhat:
IndividualPresentationComponent = Ember.Component.extend({
classNames: ['user-profile'],
actions: {
save: function () {
this.sendAction('save', this.get('presentationModel'));
this.set('editing', false);
},
edit: function () {
this.set('editing', true);
},
delete: function () {
this.sendAction('delete', this.get('presentationModel'));
}
}
});
Notice I'm using sendAction here. This is how components communicate with the outside world. To get this to work, go back your presentations.hbs and intercept the actions like so:
{{#each presentation in controller}}
{{individual-presentation presentationModelBinding="presentation"
save="savePresentation"
delete="deletePresentation"}}
{{/each}}
Here you're basically saying that if the component sends the "save" action, you want to handle it with your controller's "savePresentation" action, and if the component sends the "delete" action, you want to handle it with your controller's "deletePresentation" action. So your presentations-controller.js will need to implement those actions:
var PresentationsController = Ember.ArrayController.extend({
actions: {
savePresentation: function (presentationModel) {
...
},
deletePresentation: function (presentationModel) {
...
},
}
});
And you can delete PresentationController, since all of its functionality is now handled directly by your IndividualPresentationComponent and your PresentationsController.
I just updated Ember and I am trying to convert an old app to the new router API.
In my template I have this:
<button {{ action "createNewApp" }} class="btn btn-primary">Create application</button>
And I put a createNewApp in my route:
App.CreateAppRoute = Ember.Route.extend({
renderTemplates: function() {
this.render({ outlet: 'content'});
},
createNewApp: function(){
console.log("It's clicked");
}
});
However, when I click on the button the function is never called. I have tried to change the target of the event to the controller but it's still not working. Is this a bug or am I doing something wrong?
I was able to handle the click using a function in the events property of the router (thanks sly7_7) or using a function in the controller:
Template:
<script type="text/x-handlebars" data-template-name="application">
<h1>Content Here:</h1>
{{outlet content}}
</script>
<script type="text/x-handlebars" data-template-name="content-view">
<h2>content</h2>
<button {{ action "createNewApp" }} class="btn btn-primary">Create application</button>
</script>
JS:
var App = Ember.Application.create();
App.CreateAppView = Ember.View.extend({
templateName: "content-view"
});
App.CreateAppController = Ember.Controller.extend({
createNewApp: function(){
console.log("It's clicked, controller");
}
});
App.Router.map(function(match) {
match('/').to('createApp');
});
App.CreateAppRoute = Ember.Route.extend({
renderTemplates: function() {
this.render({ outlet: 'content'});
},
events: {
createNewApp: function(){
console.log("It's clicked, router");
}
}
});
When the button is clicked, the console log message is "It's clicked, controller" and when the function in the controller is removed the action is handled by the router, and the console log message is "It's clicked, router". If no target is specified in the {{action}} Ember tries to find the event in the view, then the controller, then the route.
This is using a build of emberjs built today from source.
Original answer below:
The {{action}} in the new router is replaced with {{linkTo}}.
The emberjs.com guides are constantly being updated with new info about the new router. The links guide covers the {{linkTo}} helper, and the actions guide discusses using the {{action}} helper to handle events in templates.