Two-way binding in custom component not working - ember.js

I've created a component like this:
templates/components/files-dropzone.hbs
<p>Awesome text</p>
<textarea id="drop-textarea" rows="10" cols="50">
{{value}}
</textarea>
components/files-dropzone.js
import Ember from 'ember';
export default Ember.Component.extend({
value: '',
valueChanged: function() {
console.log("yup") // is never triggered
}.observes('value'),
})
I'm using this component like this in another template:
<div class="large-7 columns padding-left-reset"
{{files-dropzone value=body}}
</div>
While the textarea contains the correct value for body when I load the page it doesn't bind it. I'm observing body and it doesn't change when I change the text inside the textarea.
EDIT: The value-attribute in the component itself doesn't change as well
What am I doing wrong?

I don't think that Ember knows that it should bind the {{value}} to the text area.
It should work, using the textarea helper:
{{textarea value=value id="drop-textarea" rows="10" cols="50"}}
Do you want to adapt the behaviour of the text area in some way?

Related

How to create action for my own component

I am creating one ember app.
Flow is like " page1 displays list of feeds item and clicking on any of the feed will take user to page2 showing details about that feed"
What i am doing:
i have one component named app-feed. Template is as below
<div onclick={{action 'click' feed}}>
{{#paper-card class="card-small" as |card|}}
<!-- --> {{card.image src=feed.imagePath class="small-feed-img" alt=feed.title}}<!---->
{{#card.header class="flex-box short-padding" as |header|}}
{{#header.avatar}}
<img class="profile-small" src="http://app.com/users/{{feed.userName}}.jpg" alt="{{feed.name}}" />
{{/header.avatar}}
<span class="tag-sm like-box">
{{feed.likes}} {{paper-icon "thumb_up" size="18"}}
{{feed.commentCount}}{{paper-icon "chat_bubble" size="18"}}
</span>
{{/card.header}}
{{#card.actions class="action-block"}}
{{#paper-button iconButton=true}}{{paper-icon "favorite" size="18"}}{{/paper-button}}
{{#paper-button iconButton=true}}{{paper-icon "share" size="18"}}{{/paper-button}}
{{#paper-button iconButton=true}}{{paper-icon "shopping_basket" size="18"}}{{/paper-button}}
{{/card.actions}}
{{/paper-card}}
</div>
component.js is as below
import Ember from 'ember';
export default Ember.Component.extend({
actions:{
click(feed){
console.log("Click event fired:"+feed.id); //Output is correct in console
this.sendAction("onClick", feed); //sending onClick Action
}
}
});
I'm populating list of this component in one of my route.
Template is as below
{{#app-sidenav user=model}}{{/app-sidenav}}
<div class="content">
<div class="row">
{{#each model as |item|}}
{{#app-feed-small onClick=(action "getDetail" item) class="col-xs-5" feed=item}} {{/app-feed-small}}
{{/each}}
</div>
</div>
route.js is as below
import Ember from 'ember';
export default Ember.Route.extend({
store: Ember.inject.service(),
model(){
//Populating module. Works just fine
} ,
actions:{
getDetails(feed){
console.log("Getting details of "+feed.id);
}
}
});
I have defined getDetails action as mentioned in my template.js of the route still i am getting below error
""Assertion Failed: An action named 'getDetail' was not found in (generated feed.index controller)""
feed.index is my route.
I used same method and modified paper-chip's source to get action corresponding to click on paper-chip's item which worked. But i am not able to do same in my own component.
Please let me know what is missing
Your problem is that in your second last code snippet, the one with your template. You refer to the action as getDetail but in route.js your last code snippet you declare the action as getDetails which is different to the code in your template. It's a common spelling error, one has an "s" st the end whereas the other doesn't.
The actions should be in controllers. And if controller bubbles up then the action in route be called.
For your case you don't need controller.
You can use ember-transition-helper
I assume you have in router.js :
this.route('feeds', function(){
this.route('edit', {path: '/:id'});
});
Now your template is going to be :
{#app-sidenav user=model}}{{/app-sidenav}}
<div class="content">
<div class="row">
{{#each model as |item|}}
{{#app-feed-small onClick=(transition-to "feeds.edit" item) class="col-xs-5" feed=item}} {{/app-feed-small}}
{{/each}}
</div>
</div>
sendAction is an ancient way to calling action inside controller/route.
The new style is to use closure action, which passes action as a value by creating a closure at the time of value passing.
Yes, you are correct. The action has been sendAction is able to bubble up from,
correspond controller -> correspond route -> upper route -> ... -> application route
However, closure action does NOT bubble.
Please refer to Ember component send action to route where #kumkanillam detailed explained how to call action inside route using different method and the differences between sendAction and closure action.
I have also made a sample project and write a simple explanation for it at,
https://github.com/li-xinyang/FE_Ember_Closure_Action

How to toggle a single item in an ember list in Ember

I have a component as follows
{{#md-collection content=model as |item|}}
<div class='collection-item'>
<img src="{{item.url}}" class="asset-thumbnail" />
<div class="asset-url">
{{item.url}}
</div>
<div class="secondary-content">
{{#copy-button
clipboardText=item.url
class="btn"
success="successfulCopy"
}}
{{fa-icon "chain" title="Copy to Clipboard"}} {{unless copied "Copy Link" "Copied"}}
{{/copy-button}}
{{confirmation-link
title="Delete"
action=(route-action "deleteAsset" item)
icon="trash"
message="Are you sure you want to delete this asset?"
confirmButtonText="Yes, Delete Asset"
confirmButtonColor="#FF6666"
classNames="btn delete"}}
</div>
</div>
{{/md-collection}}
and it has the controller:
import Ember from 'ember';
export default Ember.Component.extend({
copied:false,
actions:{
deleteAsset(asset){
this.attrs.deleteAsset(asset);
},
successfulCopy(btn){
console.log(this.$(btn));
this.$(btn).toggleProperty('copied', true);
Ember.run.later(()=>{
this.$(btn).toggleProperty('copied', false);
},500);
}
}
});
when I click the button with the text Copy Link, the component then toggles the copied property as it should, however, it is toggling the property for all of the items in the list changing all of their text. In the action successfulCopy I have a reference to the HTML of the button that was clicked. How would I toggle the copied property for just that one component to only toggle that button's text?
try this:
successfulCopy(btn){
this.set('item.copied', true)
}
{{fa-icon "chain" title="Copy to Clipboard"}} {{unless item.copied "Copy Link" "Copied"}}
main-component,
{{#copy-button
clipboardText=item.url
class="btn"
success="successfulCopy" as |copied|
}}
{{fa-icon "chain" title="Copy to Clipboard"}} {{unless copied "Copy Link" "Copied"}}
{{/copy-button}}
copy-button.hbs
copied property is available in copy-button component, so to access it main-component it should yield it.
{{yield copied}}
copy-button.js
successfulCopy function will toggle his own property copied. so you dont need to pass argument and you dont require jquery stuff, since you have already written logic based copied property. just toggling copied will do the rest.
import Ember from 'ember';
export default Ember.Component.extend({
init(){
this._super(...arguments);
this.set('copied',false);
}
actions:{
deleteAsset(asset){
this.get('deleteAsset')(asset);
},
successfulCopy(){
this.toggleProperty('copied');
}
}
});

Controlling component data across multiple routes

I have a mapping app that has a full-screen map with a sidebar for information. This app has two routes:
one route that should display a list of places with markers on the map, for example /places/
one route that should display a single place with that particular place's marker centered on the map, for example places/1/
My map is currently a Component that is in application.hbs, so it is "outside" of the route templates and persists across route changes. It looks something like:
<div class="page">
<aside class="sidebar">
{{outlet}}
</aside>
<div class="content">
{{places-map ... }}
</div>
</div>
and my routes looks something like:
Router.map(function() {
this.route('index', { path: '/' });
this.route('place', { path: "/place/:place_id" });
this.route('places');
});
So while I have all this set up and working (I can see a list of places and move a single particular place, in both cases with the map in the "background"), I can't understand how my routes can feed information to my component or simply how my routes can communicate with the component that is sitting "outside" of their context?
Is this a possible pattern with Ember and is there a way to achieve it?
Ditto on what #GerDner said about data-down-actions-up.
Starting from the top:
application/controller.js
import Ember from 'ember';
export default Ember.Controller.extend({
somethingDownFromController: null
});
application/route.js
import Ember from 'ember';
const {
set
} = Ember;
export default Ember.Route.extend({
actions: {
sendSomethingUp(something) {
set(this.controllerFor('application'), 'somethingDownFromController', something);
}
}
});
application/template.hbs
<div class="page">
<aside class="sidebar">
{{outlet}}
</aside>
<div class="content">
{{places-map
something=somethingDownFromController
}}
</div>
</div>
place/route.js
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return {
somethingFromNestedRoute: 'boooooyaaaaah'
}
}
});
place/template.hbs
<button {{action 'sendSomethingUp' model.somethingFromNestedRoute}}>
Send model up
</button>
You might not need to send anything up with the action you're bubbling here. If that's the case then you can just grab what you need from the application controller or route and pass it down into places-map.
places-map/template.hbs
Insert something from the outer context:
<div>{{something}}</div>
Here's an ember-twiddle. I made a few notes in the router.js file that might be useful depending on the exact needs of your application.
The data-down/actions-up Pattern is the answer.
http://www.samselikoff.com/blog/data-down-actions-up/
You hold the data on a toplevel component/controller and pass the data down to child components. Data changes are triggered via actions on the child component and handled by the toplevel component/controller via action bubbling. So you need only one component/controller which knows how to change the data and how to get data.

attributeBindings and classBindings not appending to <div> with component helper in Ember 2.1?

I have a widget dashboard app where the individual widgets are displayed using the component helper. A template ends up looking like this:
<div class="panel panel-default">
<div class="panel-body">
{{component model.displayComponent model=model}}
</div>
</div>
A Widget instance will return a path for the displayComponent like: widgets/category/specific-widget.
What I'm finding, in Ember 2.1, is that the attributeBindings and classBindings defined on the component are not being applied. And I think they were being applied back in Ember 1.13.
In other words, in 1.13 think a component defined like this:
// app/components/widgets/category/specific-widget.js
export default Ember.Component.extend({
model: null,
classNames: [ 'widget-content' ],
attributeBindings: [ 'style:style' ],
style: Ember.computed.readOnly('model.contentHeight')
});
I would get a <div id="ember..." style="height:300px" class="ember-view widget-content">...</div>. Now I simply get the default <div id="ember..." class="ember-view">...</div>.
Am I mistaken about the old behavior? If not, is there a way to make this work the old way?
P.S. I'm unable to find the documentation for the component helper anywhere. Is that on the ember.js site?
EDIT: developer error... I had my path to my widget templates named differently (plural) than my widget code. For example: my template was app/templates/components/widgets/categories/widget.hbs but my widget code was app/components/widgets/category/widget.js. Because Ember inserts a component code in there by default, there was no error thrown. If I had made the mistake in the other direction an error would have been thrown.

How to setup "style" attribute on Embers Handlebars {{input}} helper?

I try to use {{input style="width:100%"}} in my view's template but without any success.
Browser stubbornly renders <input> without style="width:100%;".
How can I achieve it?
For future reference, below is my resolution (for Ember-Cli) based on #damienc answer:
Component class
//app/components/forms/elements/style-input.js
import Ember from "ember";
export default Ember.TextField.extend({
attributeBindings: ['style'],
styleAttrib : null,
style: Ember.computed({
get: function () {
return Ember.String.htmlSafe(this.get('styleAttrib'));
},
set: function (key, newStyle) {
this.set('styleAttrib', newStyle);
return Ember.String.htmlSafe(newStyle);
}
})
});
And the template:
{{!app/templates/components/portlet-datatable/header-cell-filterable.hbs}}
<div class="ember-table-content-container">
<span class="ember-table-content">
{{forms/elements/style-input type="text"
placeholder=view.content.headerCellName style="width: 100%;"}}
</span>
</div>
This is not supported out-of-the-box by the input helper.
You could either add a class parameter that matches a CSS rule, or implement your own input helper to add style to your component's attributeBindings attribute.
This old cookbook entry achieves some specific behavior by extending the Ember.TextField class:
http://guides.emberjs.com/v1.10.0/cookbook/user_interface_and_interaction/focusing_a_textfield_after_its_been_inserted/