Within an EmberJS Application, it seems that Foundation's dropdown plugin interferes / stops you from using "actions" inside the content that is rendered in the dropdown.
For example, this should work fine, however the 'componentAction' action is never called. Any idea what could cause this?
<!-- application.hbs -->
{{my-dropdown}}
<!-- my-component.hbs -->
<a data-dropdown="drop2" aria-controls="drop2" aria-expanded="false">Has Content Dropdown</a>
<div id="drop2" data-dropdown-content class="f-dropdown content" aria-hidden="true" tabindex="-1">
<p {{action "componentAction"}}>Some text that people will think is awesome! Some text that people will think is awesome! Some text that people will think is awesome!</p>
</div>
// my-component.js
import Ember from 'ember';
import layout from './template';
export default Ember.Component.extend({
layout,
didInsertElement() {
this._super(...arguments);
Ember.run.scheduleOnce('afterRender', this, function() {
this.$().foundation('dropdown', 'reflow');
});
},
action: {
componentAction() {
console.log('component action fired');
}
}
});
The issue here was that I needed to pass 'document' into the jQuery selector.
Ember.run.scheduleOnce('afterRender', this, function() {
this.$(document).foundation('dropdown', 'reflow');
});
Related
I need assistance in executing action methods defined in ember components from outside. (even though ember follows DATA Down and Actions Up approach). My usecase is as follows
Application Template
<script type="text/x-handlebars-template" data-template-name="application">
<h2> Dialog Component </h2>
{{outlet}}
</script>
Index Template
<script type="text/x-handlebars-template" data-template-name="index">
<button {{action "showDialog1"}}>Open Dialog 1</button>
<button {{action "showDialog2"}}>Open Dialog 2</button>
{{#if openFirst}}
{{#modal-dialog name="dlg1" title="Modal 1" auto-open="true" onOpen=(action "handleDialogOpen" "dlg1") }} Content of the Dialog ...{{/modal-dialog}}
{{/if}}
{{#modal-dialog name="dlg2" title="Modal 2" onOpen=(action "handleDialogOpen" "dlg2")}} Content of the dialog ... {{/modal-dialog}}
</script>
Modal Dialog Template
<script type="text/x-handlebars-template" data-template-name="components/modal-dialog">
<div class="titlebar-title">
<span> {{title}} </span>
<a class="closeBtn" {{action "close"}}>X</a>
</div>
<div class="content">
{{yield}}
</div>
</script>
Index Controller
App.IndexController = Ember.Controller.extend({
openFirst : false,
actions : {
showDialog1 : function(){
this.toggleProperty("openFirst"); // open and close the first dialog when clicking the button.
},
showDialog2 : function(){
// want to trigger "open" action of modal-dialog component without using any conditionals(if-else) and without observing "auto-open" attribute
......
},
handleDialogOpen : function(dialogName){
if(dialogName === "dlg1"){
// do something.
}else if(dialogName === "dlg2"){
// do something
}
}
}
});
Modal Dialog Component
App.ModalDialogComponent = Ember.Component.extend({
tagName : 'div',
classNames : ['ui-dialog'],
attributeNames : ['title','name','auto-open'],
didInsertElement : function(){
if(this.get("auto-open")){
this.send("open");
}
},
actions : {
open : function(){
$(this.element).show();
this.onOpen()
},
close : function(){
$(this.element).hide();
}
}
});
Css Style Definition
ui-dialog{
display : none;
}
Is there any way to achieve this ? Kindly guide me.
Executing actions from controller inside component it not recommended approach. Why does your modal send Save and Cancel actions to the controller and router instead.
Ember has sendAction which you use to send action from component to controller or router.
Inside component you can define an action like this Ember 1.x
{{my-component action="doSomething"}}
which you can send to controller in router
MyComponent = Ember.Component.extend({
click: function() {
this.sendAction();
}
});
This will trigger doSomething from component to controller. In ember 2.x you can check them here, they are pretty much the same
https://guides.emberjs.com/v2.6.0/components/handling-events#toc_sending-actions
Lets say that you want to open modal. Inside component you would define a property called isOpen that will show hide modal content
{{my-modal isOpen=isOpen}}
There is a prop on controller that also has this field which you pass from controller to component. Inside your template you could say:
{{#if isOpen}}
{{my-modal ....this will trigger didInertElement on component that you can use to handle backdrop ...etc
{{/if}}
App Labs has a great example on how to do the modals which integrates with ember-cli
https://github.com/yapplabs/ember-modal-dialog
I'm learning EmberJS, I tried to search the docs and stuff but I couldn't get it right so far I've implemented the component and a action to respond on click event, for now it just print something in the console. I want to get the clicked element so I could change it's style and attributes. I'm using the scaffold generated by ember-cli version 0.2.7 Follows the code:
app/components/heart-like.js
import Ember from 'ember';
export default Ember.Component.extend({
actions:{
click: function (event) {
console.log(event); // undefined
console.log("Hello from component");
}
}
});
app/templates/components/heart-like.hbs
<div class="row">
<span {{action "click"}} class="pull-right" style="color: #B71C1C;"><i class="fa fa-2x fa-heart"></i></span>
</div>
Ember.EventDispatcher delegates some events to the corresponding Ember.View. Since Ember.Component is a subclass of Ember.View, you can capture click events just exposing a click function:
export default Ember.Component.extend({
click: function (event) {
console.log(event.target); // displays the clicked element
console.log("Hello from component");
}
});
Keep in mind that using directly these event hooks isn't a good practice because you're dealing directly with the DOM, and not expressing your intention. Depending of your use case it's a better alternative to just create a property in the component and change it via actions. For instance, to change the color of the icon when it's clicked:
component
export default Ember.Component.extend({
color: 'red',
actions:{
changeColor: function() {
this.set('color', 'black');
}
}
});
template
<div class="row">
<span {{action "changeColor"}} class="pull-right" style="color: {{color}};"><i class="fa fa-2x fa-heart"></i></span>
</div>
Live demo
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();
}
}
});
I have templates box/inbox.hbs and users/profile.hbs with {{outlet inbox}} in profile.hbs
In my profile.hbs I have {{#link-to 'box.inbox'}}this is a link{{/link-to}} which should render box/inbox.hbs into the outlet {{outlet inbox}} which I'm trying to do in my router.js with:
Router.BoxInboxRoute = Ember.Route.extend({
renderTemplate: function(){
this.render('box.inbox', {into: 'users.profile', outlet: 'inbox'});
}
});
but the link just redirects me to another page /box/inbox. How do I get it so that the inbox.hbs is rendered in the outlet on profile.hbs?
I think the link-to helper is the source of confusion. As far as I know, link-to will always change the URL and route, so you would never be able to achieve rendering the box.inbox route into a specific part of the users.profile template by clicking a link unless you use nested routes.
However, to achieve something like clicking on a link to show the inbox content, you could always load the box.inbox route hidden using the named outlet, then on click of a link, show the div containing the box.inbox contents. Here's an example.
UsersProfileRoute:
Router.UsersProfileRoute = Ember.Route.extend({
renderTemplate: function(){
this.render();
this.render('box.inbox', {into: 'users.profile', outlet: 'inbox'});
}
});
users/profile.hbs:
<a href='#' {{action 'showInboxAction'}}>click to see inbox</a>
<div {{bind-attr class=":inbox-style shouldShowInbox:displayed:hidden"}} >
{{outlet inbox}}
</div>
css:
.inbox-style.displayed {
display: block;
}
.inbox-style.hidden {
display: none;
}
and in the user profile controller have an action:
action:
{
showInboxAction: function()
{
this.set('shouldShowInbox', true);
}
}
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.