Correct way to use actions with ember-bootstrap - ember.js

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.

Related

How to invoke an action in the current router from the child component in Ember Octane?

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>

Why do I get 'this is undefined' in my Octane component methods?

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.

Ember tooltip pass variable

I am building an Ember tooltip module to create dynamic content on hover.
<div class="custom-tool-wrapper">
{{#custom-tool-tipster
side="right"
content=(or getContent question.id)
contentAsHTML=true
class="tool-tipster-field"}}
Preview
{{/custom-tool-tipster}}
</div>
in the ember controller - the function doesn't return the variable "question.id" --- it comes back as 0 always - when it should be a string "q-1"
export default Ember.Component.extend({
getContent(tips){
console.log("tips1")
console.log("question", tips);
},
});
I think what you're actually trying to achieve is best done via computed property on the question model object (your question is still really vague).
content: computed('id', function(){
//this.tips is a part of the model object
//compute and return whatever the content is
return "content";
}
and then just say:
{{#custom-tool-tipster
side="right"
content=model.content
contentAsHTML=true
class="tool-tipster-field"}}
Preview
{{/custom-tool-tipster}}
If you needed to actually invoke a function (which it's rare to think of an instance where the computed property isn't a better solution whenever state is involved), you would use a custom handlebars helper.
(or a b) is (a || b) and isn't function invocation like you're attempting if you're using the ember truth helpers lib for the or helper. It looks like you're trying to accomplish what ember-invoke allows
import Ember from 'ember';
import { helper } from '#ember/component/helper';
export function invokeFunction([context, method, ...rest]) {
if (typeof context[method] !== 'function') {
throw new Error(`Method '${method}' is not defined or cannot be invoked.`);
}
return Ember.get(context,method).apply(context, rest);
}
export default helper(invokeFunction);
which can be used like content=(invoke this "getContent" question.id) to invoke and return the value of a function on the passed in context object (the controller if this in the case of a route's template). Let me be clear, I think this invoke approach is a terrible idea and really gets rid of your separation of concerns and I'm not advocating that you do it. Templates shouldn't contain your logic and definitely shouldn't be calling arbitrary functions on the controller when you have such a nice facility like computed properties.

Ember CLI: custom input helper

I'm trying to extend Ember's TextField with UrlField so that if someone forgets to include http://, it does it for them.
Here's my View:
views/input-url.js
import Ember from 'ember';
export default Ember.TextField.extend({
type: 'url',
didInsertElement: function() {
this._super.apply(this, arguments);
this.formatValue();
},
onValueChange: function() {
this.formatValue();
}.observes('value'),
formatValue: function() {
var pattern = /^https{0,1}:\/\/[A-Za-z0-9]+\.[A-Za-z0-9]+/g;
if (pattern.test(this.get('value')))
return;
if (!pattern.test('http://' + this.get('value')))
return;
this.set('value', 'http://' + this.get('value'));
}
});
If I use it in my template like this, it works fine:
{{view "input-url" value=url}}
I prefer to use custom view helpers, so I created this (following the guide at the bottom of this page: http://guides.emberjs.com/v1.11.0/templates/writing-helpers/):
helpers/input-url.js
import Ember from 'ember';
import InputUrl from '../views/input-url';
export default Ember.Handlebars.makeBoundHelper(InputUrl);
Now trying to render this in my template doesn't work:
{{input-url value=url}}
I've also tried different permutations of this, including what's shown in the guide Ember.Handlebars.makeBoundHelper('input-url', InputUrl); (which throws an error), but I can't seem to get my input field to show up. What am I doing wrong?
Not sure what you are doing wrong with your view helper, but there is a much simpler solution: take advantage of the fact that Ember.Textfield is a component. http://emberjs.com/api/classes/Ember.TextField.html
Simply move views/input-url.js to components/input-url.js and get rid of your view helper.
Then {{input-url value=url}} should work automatically.
If you want do to this using a helper, you cannot extend Ember.TextField because extends Ember.Component and is not a Handlebars helper.
The way to do this using a helper would actually be simpler. Since you are using Ember-CLI, you can create a helper called "input-url" with the command ember g helper input-url and the only code you would need is the code within your formatValue() function:
helpers/input-url.js
// define patter globally so it's not recreated each time the function is called
var pattern = /^https{0,1}:\/\/[A-Za-z0-9]+\.[A-Za-z0-9]+/g;
export function inputUrl(value) {
if (pattern.test(value)) {
return value;
}
if (!pattern.test('http://' + value)) {
return value;
}
return 'http://' + value;
};
export default Ember.Handlebars.makeBoundHelper(inputUrl);
And you can use it like:
{{input-url PASS_YOUR_URL_HERE}}
Where the value you pass will be the value of the value variable within the helper.
You could also create a component, as #Gaurav suggested, using the exact code you have above, just in components/input-url.js instead and delete the helper cause it is not necessary anymore. You also have to edit the corresponding template of the component if you want it to display the value with a single handlebars expression:
templates/components/input-url.hbs
{{value}}
The usage with a component would be:
{{input-url value=PASS_YOUR_URL_HERE}}

How do i get ApplicationController?

How can I get an ApplicationController inside my HomeController?
HomeController=Ember.Controller.Extend({
init:function {
// Here I want to get My application controller. Is there any way to get??
}
})
How can I get an ApplicationController inside my HomeController?
To specify dependencies (or needs) between controllers, use the needs property:
HomeController=Ember.Controller.Extend({
needs: ['application']
})
Now Ember will make the application controller accessible from inside your HomeController as controllers.application. You can use it like any other HomeController, it is even accessible from your templates:
<!-- inside `home` template -->
{{controllers.application}}
See http://darthdeus.github.com/blog/2013/01/27/controllers-needs-explained/ for more detail on controller-needs.
BTW: It's pretty rare for an ember controller to use a custom init fx. Consider moving any initialization logic to setupController hook on the route instead.
App.HomeController = Ember.Controller.extend({
init: function () {
var applicationController = this.controllerFor('application');
}
});