I'm at my wit's end trying to accomplish what should be very straightforward behavior: I have an Ember table component (from Addepar), I would like to have buttons inside that table that trigger a modal dialog.
Since I'm new to Ember, I started with the Ember table starter kit jsbin available here: http://jsbin.com/fasudiki/9/edit
I added a custom cell view so I can use my own template:
columns: function() {
var firstColumn;
firstColumn = Ember.Table.ColumnDefinition.create({
columnWidth: 350,
textAlign: 'text-align-right',
headerCellName: 'Column 1',
tableCellViewClass: 'App.EmberTableMyCustomCell',
getCellContent: function(row) {
return row.get('myRandomValue').toFixed(2);
}
});
return [firstColumn];
}.property(),
with
App.EmberTableMyCustomCell = Ember.Table.TableCell.extend({
templateName: 'custom-table-cell',
classNames: 'custom-table-cell'
});
and
<script type="text/x-handlebars" data-template-name="custom-table-cell">
<span class="ember-table-content">
{{ view.cellContent}}
<button {{action 'openModal' 'modal'}}>This one doesn't</button>
<button {{action 'myCellAction' 'modal'}}>This one doesn't either</button>
</span>
</script>
I then tried following the official Ember guide for modal dialogs: http://emberjs.com/guides/cookbook/user_interface_and_interaction/using_modal_dialogs/
In Ember terminology, I'd like to be able to trigger an action on the Index route from within the ember-table component.
I tried triggering the action directly from the template which didn't work:
<button {{action 'openModal' 'modal'}} >Open modal</button>
I then tried what is suggested in the "Sending Actions From Components To Your Application" guide:
http://emberjs.com/guides/components/sending-actions-from-components-to-your-application/
by creating an 'actions' map on the App.EmberTableMyCustomCell view and then using both
this.send('openModal', 'modal');
and
this.sendAction('openModal', 'modal');
Again, no success.
I then tried what's recommended in this SO question:
Ember component sendAction() not working
by setting the action name in a custom attribute on my ember-table and using it at a parameter for triggerAction(...) using:
<div class="table-container">
{{table-component
hasFooter=false
columnsBinding="columns"
contentBinding="content"
myCustomActionName="openModal"
}}
</div>
and
actions : {
myCellAction : function () {
this.triggerAction('myCustomActionName', 'modal');
}
}
Again, no success.
Any ideas what I'm doing wrong?
I have put the code in jsbin: http://jsbin.com/yovikaviseve/2/edit
I believe that (unfortunately) your action in App.EmberTableMyCustomCell is not being called. I'm not sure if it's the best solution, but I was able to work around the problem by extending Ember.Table.EmberTableComponent and defining my action there. Once the action was called there, I was able to use the method from your linked SO post to propagate the action to the ApplicationController.
I actually sent the primary action instead to make things a bit simpler, as described here: http://emberjs.com/guides/components/sending-actions-from-components-to-your-application/.
Here's the working example: http://emberjs.jsbin.com/yemebu/3/edit.
Thanks for including a JS Bin - made it much easier for me to take a look. Let me know if you have more questions or if this approach doesn't work for you!
Related
I try to create a custom component for my application with emberJS, I have followed the quickstart tutoriel and now I try to create a upload button as a component.
It seems I don't have code issues but my component don't render on my main page. I have use the ember component generator to create it
here my upload-button.hbs :
<button type="button">{{#buttonText}}</button>
now my upload-button.js :
import Component from '#ember/component';
export default Component.extend({
actions: {
showExplorer(){
alert()
}
}
});
for the moment I simply put a alert() method in showExplorer()
and now my main page, application.hbs :
<upload-button #buttonText="Uploader un fichier" {{action "showExplorer"}}/>
{{outlet}}
I expect to see my button but I just have a blank page with no code error.
I suspect that's a really stupid mistake but I can't find it.
Glad you decided to try out Ember.js :) Your upload-button.hbs and upload-button.js file looks good. However, there are a couple of issues here.
The component, when invoked using the angle-bracket syntax (<... />), the name should be CamelCased to distinguish between HTML tags and Ember components. Hence, we need to invoke the upload-button component as <UploadButton />.
You defined your action, showExplorer, inside the component (upload-button.js) file, but, referenced in the application.hbs file. The data in the backing component file can only be accessed inside the component's .hbs file because of the isolated nature of the component architecture. Also, we can only attach an action using {{action}} modifier to a DOM element and not to the component itself. So,
we need to remove the action binding from application.hbs file,
{{!-- application.hbs --}}
<UploadButton #buttonText="Uploader un fichier"/>
and add the same inside the upload-button.hbs file:
{{!-- upload-button.hbs --}}
<button type="button" {{action "showExplorer"}}>{{#buttonText}}</button>
A working model can be found in this CodeSandbox
Hope you find learning Ember, a fun process!
I have an ember application which works fine. But user's interaction does some DOM insertion like below...
$(.test).append(<a {{action "getData"}}>Get Data</a>);
The problem is that Ember seems do not recognize that an action "getData" has been added to the DOM. Nothing is happening when I click the element. Any thoughts on this?
Another way I am trying to do is:
//create the element
$(.test).append(<a id="idnum">Get Data</a>);
//make click listener
$('#idnum').click(function(){
console.log("get data");
}
my question is where should i place the code inside the component so the it can listen on the click event. Thanks.
You should do it in Ember way. Try handlebars {{#if}} helper to render an element dynamically.
{{#if canGetData}}
<a {{action "getData"}}>Get Data</a>
{{/if}}
Here you can set the value of the canGetData to true in the controller based on the users action.
The first example can't work because ember does not analythe the Handlebars elements in the DOM, but rather parses your Handlebars template with HTMLBars, which is a full HTML parser, and then renders it manually by inserting elements, not text into the DOM.
However the second example is the way to go if you have to rely on external code that does manual DOM manipulation. And it does work. Checkout this twiddle.
This does work:
this.$('.target').append('<a id="idnum">Get Data</a>');
this.$('#idnum').on('click', () => {
alert('clicked');
});
Just make sure that the DOM is ready. So do it in the didInsertElement hook or after the user clicked a button or so.
Like Lux suggested avoid DOM manipulation. I prefer the following approach,
if it is dynamic then you can consider wrapping DOM element as a new component and use component helper.
find sample twiddle
In application.js
export default Ember.Controller.extend({
appName: 'Ember Twiddle',
linksArray:[ Ember.Object.create({value:'Text to display',routename:'home'}),
Ember.Object.create({value:'Text to display2',routename:'home'})],
actions:{
addItem(){
this.get('linksArray').pushObject(Ember.Object.create({value:'AddedDynamically',routename:'home'}));
}
}
});
in Application.hbs
<h1>Welcome to {{appName}}</h1>
<br>
{{#each linksArray as |item|}}
{{component 'link-to' item.value item.route }}
{{/each}}
<button {{action 'addItem'}}>Add Item</button>
<br>
{{outlet}}
<br>
<br>
I have a component that is wrapping content defined by another template. I want an action on the template to trigger a method in my surrounding component. Is this possible?
Here is what I have for my template. Note this is shortened for brevity.
{{#drop-down}}
<div class="menu-selector clickable" {{action "toggleDropdown"}}>
</div>
{{/drop-down}}
This is my component:
DropDownComponent = Ember.Component.extend
showDropdown: false
actions:
toggleDropdown: ->
#toggleProperty 'showDropdown'
`export default DropDownComponent`
I can verify that everything else in my component is working. If I put the action in my component that loads this template, it works fine. But that's not where I want it.
So you would like to send an action to particular components. Take a notice that
A component is a custom HTML tag whose behavior you implement using JavaScript and whose appearance you describe using Handlebars templates. They allow you to create reusable controls that can simplify your application's templates.
and
An Ember.Component is a view that is completely isolated.
You are probably using wrong tool here. You should use instead custom view.
App.DropdownView = Ember.View.extend
showDropdown: false
elemendId: 'dropdown'
actions:
toggleDropdown: ->
#toggleProperty 'showDropdown'
return;
{{#view 'dropdown'}}
<div>
<div class="menu-selector clickable" {{action "toggleDropdown"}}>
</div>
{{/view}}
Then you can send an action to view by
Ember.View.views.dropdown.send('toggleDropdown');
Demo: http://jsbin.com/zoqiluluco/1/
Alright, newbie Knockout question here.
In this example: http://learn.knockoutjs.com/WebmailExampleStandalone.html#Inbox
How does the mail detail view replace the folder list view?
That is, what feature causes the divs to be toggled? In inspecting the dom, I see that what happens is the div's are actually rendered empty when not displayed.
Can someone enlighten me? I know this is rather basic, but its the last piece I need to click into place for my understanding.
Just to be 100% clear: when you click on a row in the folder list, what causes the folder view to be emptied and the mail detail to display? Is it the with binding? (That doesn't seem right.)
You are on the right track with the with binding: in this example the views are changed using the with binding relaying on this feature of the binding:
The with binding will dynamically add or remove descendant elements depending on whether the associated value is null/undefined or not
So in the viewmodel code you will see something like this:
this.get('#:folder', function () {
self.chosenFolderId(this.params.folder);
self.chosenMailData(null);
$.get("/mail", { folder: this.params.folder }, self.chosenFolderData);
});
this.get('#:folder/:mailId', function () {
self.chosenFolderId(this.params.folder);
self.chosenFolderData(null);
$.get("/mail", { mailId: this.params.mailId }, self.chosenMailData);
});
So the functions which are "chaining" the view nulls out one of the properties while filling in the other which toggles the views defined as:
<!-- Chosen mail -->
<div class="viewMail" data-bind="with: chosenMailData">
...
<div/>
<!-- Mails grid -->
<table class="mails" data-bind="with: chosenFolderData">
</table>
This is not the nicest solution but don't forget that Knockout is a Databind/MVVM library and not a full blown SPA framework so it does not have concepts for layouting and higher level view composition.
However this could be made nicer with using the template binding:
<div id="mainView" data-bind="{template: {name: templateName, data: activeView}}">
</div>
And turning the views into templates:
<script type="text/html" id="ChosenMail">
<div class="viewMail">
...
<div/>
</script>
<script type="text/html" id="MailsGrid">
<table class="mails">
...
</table>
</script>
And in the routing only set the activeView property and lookup the corresponding template name for it:
this.get('#:folder', function () {
$.get("/mail", { folder: this.params.folder }, function(data) {
self.activeView(data);
self.templateName('ChosenMail');
});;
});
this.get('#:folder/:mailId', function () {
$.get("/mail", { mailId: this.params.mailId }, function(data) {
self.activeView(data);
self.templateName('MailsGrid');
});
});
But because this is quite much manual and error prone work I would use something like Durandal.js which is a real SPA framework and it was designed for this kind scenarios.
That is just a Demo on a light weight SPA scenario, with binding is just a inline template binding. Not very useful for a dynamic SPA. Like Nemesv suggests use the template binding.
The problem with the template binding is that its very verbose to use, I have addressed this in my Binding convention library (One of many features)
Instead of doing
<div id="mainView" data-bind="{template: {name: templateName, data: activeView}}">
</div>
You do
<div id="mainView" data-name="activeView">
</div>
My library will do the rest, check out the wiki on templates here
https://github.com/AndersMalmgren/Knockout.BindingConventions/wiki/Template-convention
And a little fiddle
http://jsfiddle.net/xJL7u/11/
I need to combine linkTo and action helpers in Ember.js. My code is:
{{#link-to 'index'}}<span {{action 'clear'}}>Clear</span>{{/link-to}}
But I would like to make this something like this:
{{#link-to 'index' {{action 'clear'}} }}Clear{{/link-to}}
And also:
<li>
{{#link-to 'support'}}
<span {{action 'myAction' 'support'}}>Support</span>
{{/link-to}}
</li>
To:
<li>
{{#link-to 'support' {{action 'myAction' 'support'}} }}Support{{/link-to}}
</li>
How can I achieve this?
Solution
Check my answer for Ember 2.0 compatible, OK for SEO solution.
Ember Link Action addon
This is OK for SEO solution!
Install addon
ember install ember-link-action
Usage
You can pass closure action as invokeAction param to {{link-to}} component:
{{#link-to 'other-route' invokeAction=(action 'testAction')}}
Link to another route
{{/link-to}}
To pass parameters to action you can use:
{{#link-to 'other-route' invokeAction=(action 'testAction' param1 param2)}}
Link to another route
{{/link-to}}
Compatibility
Automated test suite confirms that addon works with 1.13 up to latest Ember 3 releases.
It works with a release, beta and canary versions of Ember.
Addon GitHub repository. Contributions are welcome.
Update: See Michael Lang's comment below for Ember 1.8.1+
The problem with Myslik's answer (not using link-to at all but instead using an action and then transitionToRoute) is that it's useless for SEO, search engine bots will see nothing.
If you want what your link is pointing to to be indexed, it's easiest to have a good old <a href=x> in there. It's best to use link-to so that your link URLs are kept in sync with your route URLs. The solution I use gives both an action to do the work and a handy link-to to index the pages.
I override some functionality of Ember.LinkView:
Ember.LinkView.reopen({
action: null,
_invoke: function(event){
var action = this.get('action');
if(action) {
// There was an action specified (in handlebars) so take custom action
event.preventDefault(); // prevent the browser from following the link as normal
if (this.bubbles === false) { event.stopPropagation(); }
// trigger the action on the controller
this.get('controller').send(action, this.get('actionParam'));
return false;
}
// no action to take, handle the link-to normally
return this._super(event);
}
});
Then I can specify which action to take and what to pass the action in Handlebars:
<span {{action 'view' this}}>
{{#link-to 'post' action='view' actionParam=this}}
Post Title: {{title}}
{{/link-to}}
</span>
In the controller:
App.PostsIndexController = Ember.ArrayController.extend({
actions: {
view: function(post){
this.transitionToRoute('post', post);
}
}
}
This way, when I cache a rendered copy of the page and serve that to an indexing bot, the bot will see a real link with an URL and follow it.
(note also that transitionTo is now deprecated in favour of transitionToRoute)
None of these combinations will work in Ember.js, but you do not need to combine these two helpers. Why don't you just use action helper and let it bubble to controller or route? There you can use transitionToRoute in controller or transitionTo in route.
For example in controller you could have code like this:
App.PostsController = Ember.ArrayController.extend({
clear: function () {
// implement your action here
this.transitionToRoute('index');
}
});
This works fine in 1.6.0-beta.5:
<span {{action "someAction"}}>
{{#link-to "some.route"}}
Click Me
{{/link-to}}
</span>
The link will happen and then the click will bubble up to the action handler. It's documented (albeit indirectly) here.
Edit: corrected syntax in opening link tag
I like Cereal Killer's approach for its simplicity, but unfortunately it exhibits a problem for me. When the browser navigates to another route, it restarts the Ember application.
As of Ember 2.6, the following simple approach does the trick:
<span {{action 'closeNavigationMenu'}}>
{{#link-to 'home' preventDefault=false}}
Go Home
{{/link-to}}
</span>
This achieves the following:
navigates to route 'home'
action 'closeNavigationMenu' is invoked
on mouseover, browser displays link that will be followed (for SEO and better UX)
browser navigation does not result in reboot of Ember app
Having the same problem, i found this simple solution:
{{#linkTo eng.rent class="external-button"}}<div class="internal-button" {{action "updateLangPath"}} >X</div>{{/linkTo}}
then, managing the css classes external-button and internal-button in the stylesheet, i made sure that the "internal-button" was covering the whole "external-button" area; in this way it is not possible to click on the external-button without clicking on the internal-button.
It works well for me; hope it can help...
This is how I solved this in our demo application for the O'Reilly Ember.js book: https://github.com/emberjsbook.
You can see the complete source here: https://github.com/emberjsbook/rocknrollcall
In the view:
{{#if artistsIsChecked}}
{{#if artists.length}}
<h3>Artists</h3>
<ul class="search-results artists">
{{#each artists}}
<li><a {{action 'viewedArtist' this.enid }}>{{name}}</a></li>
{{/each}}
</ul>
{{/if}}
{{/if}}
And the controller:
App.SearchResultsController = Em.ObjectController.extend({
actions: {
viewedArtist: function(enid) {
this.transitionToRoute('artist', enid);
},
viewedSong: function(sid) {
this.transitionToRoute('song', sid);
}
},
needs: ['artists', 'songs'],
artistsIsChecked: true,
songsIsChecked: true,
artists: [],
songs: []
});
This is what it would look like after Ember 3.11.0 with the on modifier.
<LinkTo
{{on "click" this.recordMetrics}}
#route="post.see-all"
#model={{#model}}
#bubbles={{false}}>
Link Me
</LinkTo>
bubbles shown just to illustrate the LinkTo API.
https://blog.emberjs.com/ember-3-11-released/
Ember's link-to tags use routes to open new views, so you can perform whatever functionality you wanted to put in the link's 'action' attribute in the setupController method of the target route instead of in a controller action.
See here in the Ember guide:
http://emberjs.com/guides/routing/setting-up-a-controller/
This only works if you want to perform the action every time the route is accessed, however, as opposed to with specific links.
Make sure to include the controller.set('model', model); line along with whatever else you put in there.