In Ember.js how do you extend a component and trigger an action on did-insert? - ember.js

Our project is currently using Ember 3.12 and we are trying to upgrade to using Ember 3.20, but we are having an issue with extending an ember-power-select component (which now uses Glimmer components). In our extended component we need to call a method when the component is inserted and have access to component element, which we did using didInsertElement in Ember 3.12, but we now need to use a did-insert modifier. However, when we create a template file of our own which contains an element which triggers the did-insert modifier the power-select element is not displayed (because our template file has replaced it). I would rather not copy the entire contents of the power-select.hbs file into our own file and wrap it in a div that contains the did-insert modifier so that we can get access the component element in the action. Is there a pattern for this situation? Like templates can now be extended or there is another way to trigger an action when the component is inserted (and still get access to the component's element)?

I would recommend to not extend a component in Ember Octance by extending from it's JavaScript class. Instead invoke the component in the template of the wrapping component:
{{! app/component/wrapper-around-ember-power-select.hbs }}
<PowerSelect
{{! passthrough all arguments you want to support }}
#selected={{#selected}}
#options={{#options}}
#onChange={{#onChange}}
{{! register your own modifier }}
{{did-insert this.onEmberPowerSelectInsertIntoDom}}
{{! set some HTML attributes }}
class="foo"
/>
// app/component/wrapper-around-ember-power-select.js
import Component from '#glimmer/component';
import { action } from '#ember/object';
export default class WrapperAroundEmberPowerSelectComponent extends Component {
#action
onEmberPowerSelectInsertIntoDom(element) {
// do something with the element
}
}
Extending the component in this way has the benefit of only using its public API.

Related

Adding a class dynamically to hide only yield part of the ember component without wrapping

Is there any way to add a class(In my case it is hide class) dynamically to hide(display : none) only yield part of the ember component without wrapping the yield part of the component with tags (div,span or anyother) in Ember JS?
My Case :
{{#if isLoading}}
<p>Loading....!</p>
{{/if}}
<div class="{{isLoading "hide"}}">
{{yield}}
</div>
Here I want to hide the yield part without wrapping it up by a div tag
Note : I can't use If Else statement as it will destroy the current instance of component every time isLoading property changes. This is a component to show loading. Any other ways to use a component for loading is appreciated
You can't add a class without specifying an element or a tag. If you want to add class name to the yield part, then wrapping it in a container is the only way.

Component doesn't use template

I want to extend Ember's textarea component.
Therefore I created a file drop-files-textarea.js in app/components with this content:
import Ember from 'ember';
export default Ember.TextArea.extend({
// here comes the new behaviour
})
Now I'd like to add something to it's template. Therefore I created a file called drop-files-textarea.js in app/templates/components with this content:
{{yield}}
<p>added text</p>
This template is never used though.
The Ember Inspector shows me that it uses the inline-template.
What am I doing wrong?
The template is used, but you won't see anything since the TextArea component uses tagName: 'textarea' to wrap the component in a textarea html element, so you get:
<textarea>
<p>added text</p>
aaaa
</textarea>
You may want to create a new component using the textarea component inside or use the value property to set the content of the textarea depending on your use case.
You could also overwrite the tagName, but then you don't get a text area...
export default Ember.TextArea.extend({
value: 'Content of the text area'
})

How to get the outer parent controller from inside an ember component?

Currently I'm passing a custom keyword into the component like so (as I loop over each model in my array controller)
{{#each thing in controller}}
{{my-thing foo=controller}}
{{/each}}
Then inside my component I can add a custom attributeBindings and bind to "foo" but I'd like to think I can get access to the parent controller (from inside the component itself)
How else can I get this from inside the component in ember 1.8+ ?
Check out targetObject:
If the component is currently inserted into the DOM of a parent view, this property will point to the controller of the parent view.
In the Component, you can use this.get('targetObject'); to get the Controller of the parent view.

Access parent context from ember component block

I'm trying to work out how blocks work with ember components. With the following I'd expect project.name to be rendered for each loop.
// components/block-test.js
export default Ember.Component.extend({});
// index.hbs
{{#each project in projects}}
{{#block-test}}
{{project.name}}
{{/block-test}}
{{/each}}
But when I use this pattern project.name is not rendered. Is this the expected behaviour and if so how could I change it to get the above code to work?
component's are intentionally isolated, pass in anything you want to use (you don't need yield if you are passing it in)
{{#block-test project=project}}
--{{project.name}}--
{{/block-test}}
No Component template
--apple--
--dog--
--piano--
With yield
Component template
--{{yield}}--
Using Component
{{#block-test}}
{{project.name}}
{{/block-test}}
--apple--
--dog--
--piano--

Ember this.get('controller') returns a parent controller

I have a graphHolderController that renders this template:
{{#each controller.graphs}}
{{view sidePanelView}}
{{view graph}}
{{/each}}
In the SidePanelView, I want to call a method on SidePanelController, but I find that in SidePanelView, this.get('controller') returns an instance of the graphHolderController. How can I call a method on the SidePanelController from the SidePanelView?
The problem is that {{view}} helper does render the view you passed as first parameter, but binds the same controller as in the parent view.
Move your method to graphHolderController and send the model as parameter for that action if you need to know which graph the action comes from.
If you want a view to be managed by its own separate controller, I'd recommend using the render helper: {{render sidePanel}}. This will render the side_panel template, backed by the sidePanelView and a singleton sidePanelController.
If the sidePanelController needs to manage state for a particular model, pass that model in as a context: {{render sidePanel this}} (where this represents a model). This will generate a new non-singleton sidePanelController to manage just this model. Since you're rendering within an {{each}} block, I'd guess you want to take this approach.