Creating alias to a dynamic property now that Ember.Binding is deprecated - ember.js

How can I add alias or observer to a property that's name I only know when the component is initialized?
I need this for a generic use component where the property name depends on what data was passed to the component. In previous Ember versions I could just create the binding on init:
binding = Ember.Binding.from("model.settings." + this.get('type')).to("setting");
binding.connect(this);
Then use the "setting" wherever it is needed and everything gets correctly updated when or if the property changes. The "type" property is passed to the component from the outside and is different for every instance of the component so I can't hard code the property name in the component itself.
Now Ember.Binding was deprecated in Ember 2.7 and will be removed in Ember 3.0.
I can't figure out how to achieve this without Ember.Binding. And no, there isn't a good way to pass the value from elsewhere or manage this without a binding as far as I can tell. The actual component is a bit more complicated than what I described above but the problem remains same.

You need to use defineProperty to create computed property for dynamic dependant key.. ember-twiddle
controllers/application.js
import Ember from 'ember';
export default Ember.Controller.extend({
appName: 'Ember Twiddle',
actions:{
changeType(){
this.set('model.settings.admin','changed-adminsettings');
}
}
});
templates/application.hbs
{{my-component model=model type='admin' }}
<button {{action 'changeType'}}> ChangeType</button>
my-component.hbs
{{setting}}
{{yield}}
my-component.js
import Ember from 'ember';
export default Ember.Component.extend({
init(){
this._super(...arguments);
var type= this.get('type');
Ember.defineProperty(this,'setting',Ember.computed('model.settings.' + type,function(){
return this.get('model.settings.'+type);
}));
}
});
Reference: https://github.com/emberjs/ember.js/issues/13912

Related

Adding component dynamically in Ember

using
Ember : 1.13.11,
Ember Data : 1.13.8,
ember-cli : 1.13.12
I want to add a component dynamically to webpage - this webpage is template of another component don't think that it will make any difference-. Here is my code snippet in which I try to add a component named LyricsEditorLine to <div> tag, somehow like this
agenda-alpha/components/lyrics-editor.js
import Ember from 'ember';
import LyricsEditorLine from 'agenda-alpha/components/lyrics-editor-line';
export default Ember.Component.extend({
afterRenderEvent:function(){
LyricsEditorLine.create().appendTo($("#in"));
},
init:function(){
Ember.run.scheduleOnce('afterRender', this, this.afterRenderEvent);
this._super();
}
});
agenda-alpha/templates/components/lyrics-editor.hbs
<div id='in'> </div>
every time this gives me
'Uncaught Error: Assertion Failed: You cannot append to an existing Ember.View. Consider using Ember.ContainerView instead'
Looked for ContainerViewhere found that it is deprecated
Most of the answers that I found are not using ember-cli and being a beginner makes it harder to understand
I want to be able to add components as much as the user needs
I think you probably want the {{component}} helper which allows to dynamically render a component.
{{component "componentName" param1=paramValue param2=anotherParamValue}}
Which means you can have (made up example)
{{component "lyrics-editor-line" line=line}}
One the best things is that componentName can be a bound property
{{component componentName line=line}}
And in your controller/component:
componentName: Ember.computed('prop1','prop2', function() {
if (this.get('prop1') === 'A') {
return 'my-component-a';
}
return 'default-component';
}),
line: computed('prop3', function() {
return this.get('prop2');
})
Also, you can have the component helper inside an each loop (example taken from the Ember documentation)
{{#each model as |post|}}
{{!-- either foo-component or bar-component --}}
{{component post.componentName post=post}}
{{/each}}

Getting element by ID in Ember

I am running two ember applications. One has the following component:
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'a',
click: function() {
Ember.$('#wrapper').toggleClass('toggled');
}
});
and the other one, has this one:
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'a',
click: function() {
this.$('#wrapper').toggleClass('toggled');
}
});
What I can't understand here is why in one application I select an element by ID using Ember.$('#wrapper') and in the other using this.$('#wrapper').
What is this about? Ember version?
UPDATE
I'm very puzzled, since both components are the same:
{{#show-menu}}
<i class="fa fa-bars"></i>`
{{/show-menu}}`
They are both hamburger menus used to hide a sidebar div, and the #wrapper is an external element.
Since in both cases the #wrapper are external elements, shouldn't just the first case work #Gaurav and #Kevin Jhangiani?
The difference is in the context of the jquery selector.
Ember.$()
is scoped to the entire document, ie, you can access any element on the page.
In contrast,
this.$()
is scoped to the current component or view, and thus you can only access dom elements that are children.
Generally, you should be using this.$ as it will be more performant (since the search space is only child elements). Ember.$ should be reserved for times when you absolutely need to access an element outside of the current context.
Ember.$('#wrapper') will find an element in the page with the id of wrapper.
this.$('#wrapper') will find an elment within the component with the id of wrapper.
If there is any chance that the component you are defining will ever occur more than once in the page, then you should use neither. Edit the appropriate template so that wrapper is a class, not an id. Then use:
this.$('.wrapper')
Since you are essentially just toggling a class, the more "Ember" way of doing this is having a conditional class on your wrapper and toggle a property on your component:
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'a',
classToggle: false,
click: function() {
this.toggleProperty('classToggle');
}
});
Then, on your DOM element you can have a conditional class:
<div id="wrapper" class="{{if toggleClass "toggled"}}">...</div>
or, if you are using an older version of Ember:
<div id="wrapper" {{bind-attr class="toggleClass:toggled"}}>...</div>
This is a bit more reusable as your component doesn't rely on a DOM element (which can get messy if you want to reuse this component ever).

How to programatically add component via controller action in ember 2.x

I am being faced with the same problem on
How to programatically add component via controller action
However since I am using ember cli, I am unable to do so.
Here is my source code
import Ember from "ember";
export default Ember.Component.extend({
actions : {
remove : function(){
this.remove();
},
add : function()
{
Ember.AuthorNameComponent.create().appendTo($('#authors'));
}
},
});
When I try to run this code, I get undefined error. Also name of component is author-name.
Any help, how can I create component via programmatically?
You need to import the component, then you don't need the Ember Global.
import AuthorNameComponent from '../components/author-name-component'
Another way is to have an array of items and base the list of AuthorNameComponent from that.
{{#each items as |item|}}
{{author-name name=item.name}}
{{/each}}

Ember customize id attribute of component

Hi is there a way to customize the id of a component (i know it can be done for views ...but views have be deprecated since ember 1.13):
E.g. the following worked for the view:
export default Ember.View.extend({
classNames: ['music-js', 'vjs-default-skin', 'center-x','center-block'],
attributeBindings: ['id'],
id: 'musicOne',
However when I attempt to use id binding for the component i get the exception in the console logs:
export default Ember.Component.extend({
classNames: ['music-js', 'vjs-default-skin', 'center-x','center-block'],
attributeBindings: ['id'],
id: 'musicOne',
Uncaught TypeError: The element or ID supplied is not valid.
2 ways:
In the component itself:
export default Ember.Component.extend({
elementId: 'the-id'
});
Or specifying it in the component call itself:
{{my-component id="the-id"}}
I think the reason for NOT being able to do this is that the component is automatically assigned an ID by the Ember framework itself. You can see that if you inspect the HTML when you run your app:
<div id="ember428" class="ember-view">
But you can get a handle on that auto-generated ID and pass that to the JQuery plugin, instead of creating your own ID as per Mikko's answer.
See this to learn how to do that.
I think this is the preferred way, since components should be 'isolated' from external dependencies. By having to pass in the ID from the template defeats that (as per Mikko's suggestion) - since any consumer of a component would have to know what ID to pass in for the component to work.
However, Mikko has now edited his answer, so setting your own ID inside the component, also satisfies the 'isolation' requirement (ie. using elementID: 'the-id')

transitionToRoute('route') From Inside Component

How do I transition to a route pragmatically from inside a component action?
I tried to use #get('controller').transitionToRoute('images'), but the controller refers to the component itself. I understand that components should be self contained, so should I be using a view instead to interact with controllers/routes better?
Example
App.ImageEditorComponent = Ember.Component.extend
...
actions:
delete: ->
App.Files.removeObject(object)
#transitionToRoute('images') # This throws an exception
...
You could pass the controller in via a binding and then access it inside your component like so:
{{image-editor currentControllerBinding="controller"}}
App.ImageEditorComponent = Ember.Component.extend
...
actions:
delete: ->
App.Files.removeObject(object)
#get('currentController').transitionToRoute('images')
...
Create action on parent controller.
export default Ember.Controller.extend({
actions: {
transInController() {
this.transitionToRoute('home')
}
}
});
Specify this action on component call.
{{some-component transInComponent=(action "transInController")}}
AFTER v3.4.0 (August 27, 2018)
some-component.js
export default Component.extend({
actions: {
componentAction1() {
this.transInComponent();
}
}
});
OR simpler in some-component.hbs
<button onclick={{#transInComponent}}>GO HOME</button>
BEFORE v3.4.0
Ember.component.sendAction
"Send Action" from component up to controller
export default Ember.Component.extend({
actions: {
componentAction1() {
this.sendAction('transInComponent');
}
}
});
A component is supposed to be isolated from its context, so while you could pass in a reference to the controller, that's probably outside the scope of what a component is for. You might want to just stick with using a view with its own controller instead. Check out Views Over Components - An Ember Refactoring Story.
From Ember.js, Sending Actions from Components to Your Application, there's discussion about sending actions from a component up the route hierarchy.
A lot of things changed in Ember since the original post. So maybe today the best option would be to pass down to the component a route action that takes care of the transition (maybe using the fancy addon ember-cli-route-action.
Otherwise you can create an initializer with ember g initializer router and inside put in there a code like this one
export function initialize (application) {
application.inject('route', 'router', 'router:main')
application.inject('component', 'router', 'router:main')
}
export default {
name: 'router',
initialize
}
This way you can access the router in your component with this.get('router') and, for instance, perform a transition
this.get('router').transitionTo('images')
At component.HBS component file make a
{{#link-to "routeDestinationYouWant" }}
For Example:
<section class="component">
{{#link-to "menu.shops.category.content" "param"}}
<figure id="{{Id}}" class="list-block yellow-bg text-center" {{action "showList" "parameter"}}>
<section class="list-block-icon white-text my-icons {{WebFont}}"></section>
<figcaption class="list-block-title black-text">{{{Title}}}</figcaption>
</figure>
{{/link-to}}
</section>
I've written this answer for another similar question.
If you want to use the router only in a specific component or service or controller, you may try this:
Initialize an attribute with the private service -routing. The - because it's not a public API yet.
router: service('-routing'),
And then inside any action method or other function inside the service or component:
this.get('router').transitionTo(routeName, optionalParams);
Note: It'll be transitionToRoute in a controller.
Link to question: Ember transitionToRoute cleanly in a component without sendAction
Link to answer: https://stackoverflow.com/a/41972854/2968465