I'm writing some Octane-style components in Ember v3.13, together with the {{did-insert}} ember-render-modifier. However, when the function tied to did-insert is called, I get TypeError: this is undefined. What am I doing wrong?
Here's my component template:
<div class="cardhost-monaco-container" {{did-insert this.renderEditor}}></div>
And here's the component's JavaScript class:
import Component from '#glimmer/component';
export default class CodeEditor extends Component {
renderEditor(el) {
console.log(this.args.code)
}
}
Methods that are used as actions in templates need to be decorated with #action to have correct this-context:
import Component from '#glimmer/component';
import { action } from '#ember/object';
export default class CodeEditor extends Component {
#action
renderEditor(el) {
console.log(this.args.code)
}
}
The action decorator binds the component context to the method. This is described in more detail in the API docs for action.
Related
Created a new ember octane app (3.15) and using ember-bootstrap with it. I am using a modal like so
<BsModalSimple
#open={{true}}
#title="Create new podcast"
#closeTitle="Cancel"
#submitTitle="Create"
#size="lg"
#closeButton={{false}}
#fade={{false}}
#backdrop={{true}}
#backdropClose={{false}}
#renderInPlace={{false}}
#onHidden={{action 'closeModal'}}
#position="center">
</BsModalSimple>
This works but I get an error which says
Do not use action as {{action ...}}. Instead, use the on modifier
and fn helper.
What's the right way to use an action in this scenario? I've tried this but it didn't work
{{on 'hidden' this.closeModal}}
In classic Ember model (pre-octane), the {{action}} helper was used to bind the proper this context to the action/method that is being passed as a closure action. So, when the closure action is called inside any class, the action will have the caller's this context not the callee.
To be more predictable and explicit, this context binding was moved as a decorator, #action which should be used to decorate your closeModal method instead of using the {{action}} helper in the template. So, your code can be:
<!-- application.hbs -->
<BsModalSimple
#open={{true}}
#title="Create new podcast"
#closeTitle="Cancel"
#submitTitle="Create"
#size="lg"
#closeButton={{false}}
#fade={{false}}
#backdrop={{true}}
#backdropClose={{false}}
#renderInPlace={{false}}
#onHidden={{this.closeModal}}
#position="center">
</BsModalSimple>
// controllers/application.js
import Controller from "#ember/controller";
import { action } from "#ember/object";
export default class ApplicationController extends Controller {
#action
closeModal() {
// your implementation
}
}
Please note that the error was thrown by the linter (ember-template-lint) and the error message can be more explicit to use the #action decorator.
I am working with Ember Octane version, I want to invoke an action in the Route from the child component. The pseudo code is as follows.
**Route**
export default class SomeRouter extends Route {
model() {
return data;
}
#action
refreshRoute() {
this.refresh();
}
}
**SomerRouter.hbs**
<ChildComponent> //Using child component here
**ChildComponent**
export default class ChildComponent extends Component {
#action
revert() {
//How do I invoke the "refreshRoute" on the SomeRouter from here?
}
}
In the revert method of the above child component, "this" refers to the component itself but in the previous version of the ember "this" refers to the router where I could simply call this.refresh(). So how do I achieve this in Ember Octane. Would really appreciate any help.
You dont. This is actually one of the things that are still a bit inconsistent even with octane. Because the bound context of the route template is the Controller, not the route. So you can not access the action with {{this.refreshRoute}}.
To call an action on the Route your best way is to uzilize send. But to do this you need a Controller and define a different action on the Controller:
controllers/some.js:
export default class SomeController extends Controller {
#action
refreshRouteFromController() {
this.send('refreshRoute');
}
}
Now this function you can use from your template:
<ChildComponent #refresh={{this.refreshRouteFromController}}>
And then use it from your component:
revert() {
this.args.refresh();
}
Or directly from a button:
<button {{on "click #refresh}}>...</button>
Vue Test Utils has an API method called shallowMount() that:
...creates a Wrapper that contains the mounted and rendered Vue component, but with stubbed child components.
I've searched the Vue Test Utils documentation website but failed to find a good explanation of how these stubbed child components behave.
What exactly are these stubbed child components?
Which parts of the Vue component lifecycle do they go through?
Is there a way to pre-program their behavior?
What exactly are stubbed child components?
A stubbed child component is a replacement for a child component rendered by the component under test.
Imagine you have a ParentComponent component that renders a ChildComponent:
const ParentComponent = {
template: `
<div>
<button />
<child-component />
</div>
`,
components: {
ChildComponent
}
}
ChildComponent renders a globally registered component and calls an injected instance method when it's mounted:
const ChildComponent = {
template: `<span><another-component /></span>`,
mounted() {
this.$injectedMethod()
}
}
If you use shallowMount to mount the ParentComponent, Vue Test Utils will render a stub of ChildComponent in place of than the original ChildComponent. The stub component does not render the ChildComponent template, and it doesn't have the mounted lifecycle method.
If you called html on the ParentComponent wrapper, you would see the following output:
const wrapper = shallowMount(ParentComponent)
wrapper.html() // <div><button /><child-component-stub /></div>
The stub looks a bit like this:
const Stub = {
props: originalComonent.props,
render(h) {
return h(tagName, this.$options._renderChildren)
}
}
Because the stub component is created with information from the original component, you can use the original component as a selector:
const wrapper = shallowMount(ParentComponent)
wrapper.find(ChildComponent).props()
Vue is unaware that it's rendering a stubbed component. Vue Test Utils sets it so that when Vue attempts to resolve the component, it will resolve with the stubbed component rather than the original.
Which parts of the Vue component lifecycle do they go through?
Stubs go through all parts of the Vue lifecycle.
Is there a way to pre-program their behavior?
Yes, you can create a custom stub and pass it using the stubs mounting option:
const MyStub = {
template: '<div />',
methods: {
someMethod() {}
}
}
mount(TestComponent, {
stubs: {
'my-stub': MyStub
}
})
You can find more information about stubbed components in this unofficial testing guide for Vue.
https://lmiller1990.github.io/vue-testing-handbook/#what-is-this-guide
In short:
A stub is simply a piece of code that stands in for another.
The Vue Test Utils information also has some information about shallow mount:
https://vue-test-utils.vuejs.org/guides/#common-tips
The Vue Test Utils is lacking quite a bit of context though.
This is Ember.js 2.11. If I have a Component:
app/components/a/component.js
export default Ember.Component.extend({
actions: {
foo() { ... }
}
});
and another component which inherits from it:
app/components/b/component.js
import A from 'components/a';
export default Ember.Component.extend(A, {
...
});
How do I override foo in B? Defining my own actions object does not appear to work.
By default actions hash is mergedProperties that's the reason for it's not overriding. You can see it here.
You can see merged properties explanation here
Defines the properties that will be merged from the superclass (instead of overridden).
You can just extend like the below,
import A from 'components/a';
export default A.extend({
...
actions: {
foo() { ... }
}
});
You can define foo actions in component-b. if you want to call foo action of component a from component-b you can call this._super(...arguments).
Sample twiddle
I have a template that includes a background image for it's items:
{{#each model as |item|}}
<div style="background-image: url('img/backgrounds/{{item.imageBackground}}');">
{{image.title}}
</div>
{{/each}}
This of course is no good, as binding to style-attribute is deprecated.
So I made a computed property on my controller that serves a htmlSafe string to bind, which is working as intended.
Since I need this - and images bound to a special link - in several templates I made 2 helpers that I want/tried to combine:
The first helper is working perfectly in several other templates (generates a params-string/link to a php-file that serves the desired image)
// helpers/imagelink.js
export default Ember.Helper.extend({
empty: "img/dummies/blank.png",
compute(params, hash) {
if(params[0]) {
let paramString = 'file='+params[0]+'&itemType='+hash.item+'&type='+hash.type;
return ENV.ajaxPrefix + ENV.apiNamespace + '/getimage?'+paramString;
} else {
// display dummy
return this.get('empty');
}
}
});
Now I wanted to make a second helper that somehow encapsulates the first helper and adds the needed 'style' string to the link:
// helpers/imagebackgoundstyle.js
import Ember from 'ember';
import { imagelink } from 'my-app-name/helpers/imagelink';
export default Ember.Helper.extend({
compute(params, hash) {
// ERROR HERE
let link = imagelink(params, hash);
return Ember.String.htmlSafe("background-image: url('"+link+"');");
}
});
calling that seceond helper like this:
<div style={{imagebackgroundstyle workgroup.imageBackground item='workgroup' type='imageBackground'}}>
The error I get here is imagelink.imagelink is not a function.
I've tried several variations, even odd stuff like imagelink.compute(params, hash), ...
Clearly I'm doing something wrong when importing the helper, but I just can't get around what....?
I've tried/viewed
Ember js use handlebars helper inside a controller?
and
Calling a handlebars block helper from another helper
and several more....
Didn't solve/are outdated.
I believe your is not a function errors are all related to your import syntax:
import { imagelink } from 'my-app-name/helpers/imagelink';
You are trying to import something that doesn't exist, as the imagelink helper is exported as default. So you'll have to use:
import imagelink from 'my-app-name/helpers/imagelink';
But you'll run into another problem with your code, so I would recommend changing it to this:
import Ember from 'ember'
import ImageLink from './image-link'
export default ImageLink.extend({
compute(params, hash) {
const val = this._super(params, hash)
return val + '2'
}
})
What you're doing here, is just extending the other helpers, calling it's compute function by using this._super(), and using the return value from that in your new helper.
Here is a twiddle with a working example.