Getting error after migrating to Ember octane - ember.js

I am using Ember 3.16.3 and i am getting this error on the following code :
Error: Assertion Failed: You must pass a function as the second
argument to the on modifier
//login.hbs
<form {{on "submit" this.login}}>
<Input type="email" placeholder="email" #value={{this.email}} />
<button type="submit">login</button>
</form>
.
//login.js
import Route from '#ember/routing/route';
import { tracked } from '#glimmer/tracking';
import { action } from '#ember/object';
export default class LoginRoute extends Route {
#tracked email = '';
#action
login(event) {
event.preventDefault();
// do some operations ...
}
}

As specified in the error, the on modifier should receive a valid function to execute. As mentioned in the guides,
If you add the {{action}} helper to any HTML DOM element, when a user clicks the element, the named event will be sent to the template's corresponding component or controller.
This holds good for on modifier or any values that are used in the template. You can think of routes to be a part where we fetch data for the corresponding page. Any other backing property or computation has to be defined inside a controller or component to be used in the template.
Hence, moving your login action to a Controller will solve this issue. Additionally, you need to move email to the controller as well, or you won't see updates to it work correctly.

Related

How to get Ember to detect a checkbox on change along with a glimmer tracked?

In ember js version 3.2.6, how do we get a checkbox to do extra logic on value changed?
Example, I have a checkbox (a toggle true/false) for updateServer.
The existing code is using a glimmer #tracked and this work fine for showing some instant UI modification as can be seen on the application.hbs.
Value of update server:
{{updateServer}}
But then I need to add some logic on value change, see onCheckboxChange. That function is called, but it seem the value of updateServer is not the one after click. It's the older one, before click. See picture.
How do I get the latest value of updateServer?
application.js
import Controller from '#ember/controller';
import { tracked } from '#glimmer/tracking';
import { action } from '#ember/object';
export default class ApplicationController extends Controller {
#tracked updateServer = false;
#action
onCheckboxChange() {
//some extra logic
console.log(`at controller, updateServer is: ${this.updateServer}`);
}
}
application.hbs
{{page-title 'EmberSimple'}}
<label>
<Input #type='checkbox' #checked={{this.updateServer}} {{on 'change' onCheckboxChange}}/>
Update Server</label>
<br />
Value of update server:
{{updateServer}}
<br />
{{outlet}}
One simple solution is to just not use <Input but a primitive <input. This could look like this:
<input type="checkbox" checked={{this.updateServer}} {{on "click" this.onCheckboxChange}} />
However then you manually need to assign the new value to the tracked property:
#action
onCheckboxChange(event) {
this.updateServer = event.target.checked;
console.log(`at controller, updateServer is: ${this.updateServer}`);
}

Ember component call an action in a route or controller

I have a component the main purpose of which is to display a row of items.
Every row has a delete button to make it possible to delete a row. How is possible to pass an action from a template to the component which will trigger an action in a router ?
Here is the template using the component:
#templates/holiday-hours.hbs
{{#each model as |holidayHour|}}
{{holiday-hour holiday=holidayHour shouldDisplayDeleteIcon=true}}
{{/each}}
Here is the component template:
# templates/components/holiday-hour.hbs
...
div class="col-sm-1">
{{#if shouldDisplayDeleteIcon}}
<button type="button" class="btn btn-danger btn-sm mt-1" {{action 'deleteHoliday' holiday}}>
<span class="oi oi-trash"></span>
</button>
{{/if}}
</div>
I'm using the same component to display a row and to create a new item (holiday-hour).
I'm using ember 3.1.2
Thank you
You have to send the actions up from the component to the route. The main way to do this is by adding actions to your component that "send" the action to the parent. Once the action is sent you have to tell the component what action on the route to trigger by passing in the action as a parameter. Below is an example of how to do this.
Component js
# components/holiday-hour.js
...
actions: {
deleteHoliday(){
this.sendAction('deleteHoliday');
}
}
Template for route
#templates/holiday-hours.hbs
...
{{#each model as |holidayHour|}}
{{holiday-hour holiday=holidayHour shouldDisplayDeleteIcon=true deleteHoliday='deleteHoliday'}}
{{/each}}
Route js
#routes/holiday-hours.js
...
actions: {
deleteHoliday(){
//code to delete holiday
}
}
I will try to give a general answer because your question is not giving enough/all info regarding the route actions etc. Long answer short, using closure functions. Assuming this is your route js file routes/holiday-hours.js
import Route from '#ember/routing/route';
export default Route.extend({
model(){ /*... some code */ },
setupController(controller){
this._super(controller);
controller.set('actions', {
passToComponent: function(param) { //.... function logic }
})
}
});
Note: in the above snippet, I'm using setupController to create actions. Alternatively, you can put the actions inside a controller file otherwise actions directly inside the route will throw an error.
So I want the action passToComponent to be called from the component. This is what you do to make it accessible inside the component.
{{#each model as |holidayHour|}} {{holiday-hour holiday=holidayHour shouldDisplayDeleteIcon=true callAction=(action 'passToComponent')} {{/each}}
Now we have passed the action to the component and here's how to call it from the component. Note: I have added a param just to show that it can take a param when called within the component.
import Component from '#ember/component';
export default Component.extend({
actions: {
deleteHoliday: ()=> {
this.get('callAction')() /*Pass in any params in the brackets*/
}
}
});
You will also see demonstrations using sendAction which is rather old and acts more of an event bus that is not very efficient. Read more from this article

Ember2.8: Sending an action from a component to the controller

Reading up on the documentation for Ember, I was under the impression that when an action is triggered by a component, it will go up the hierarchy until it hits an action with that name. But here's what's happening right now. I have a game-card component written like so:
game-card.hbs
<div class="flipper">
<div class="front"></div>
<div class="back">
<img {{action "proveImAlive"}} src={{symbol}} />
</div>
</div>
game-card.js
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['flip-container'],
actions: {
//blank for now because testing for bubbling up
}
});
Now according to what I've read, since game-card.js does not have a 'proveImAlive' action, it will try to bubble up the hierarchy i.e. the controller for the particular route.
play.js (the route /play)
import Ember from 'ember';
export default Ember.Controller.extend({
actions: {
proveImAlive() {
console.log('Im aliiiiveeee');
}
}
});
But when I finally run my application, I get this error:
Uncaught Error: Assertion Failed: <testground#component:game-card::ember483> had no action handler for: proveImAlive
Now my question is twofold:
Why is this error happening?
I want some of my component's actions to bubble up to the route's controller. For example, when a game-card is clicked, i'd like to send the id value (to be implemented) of that card up to the controller so it can store it on an array.
game-card is clicked --> sends value of 1 --> arrayinController.push(1)
How can I achieve this?
First, I'd like to point out that you linked to the documentation of Ember v1.10.0. You should consult the documentation for the version of Ember you are utilizing, which you mention is v2.8.0.
Now according to what I've read, since game-card.js does not have a 'proveImAlive' action, it will try to bubble up the hierarchy i.e. the controller for the particular route.
This isn't quite what happens because components are isolated, so there is no implicit bubbling. When the Guides say "actions sent from components first go to the template's controller" and "it will bubble to the template's route, and then up the route hierarchy" they mean that you have to explicitly send an action up from the Component. If the component is nested inside another component, you have to do this for each layer, until you reach the Controller.
Why is this error happening?
You need to bind the action in the template: {{game-card proveImAlive="proveImAlive"}}
i'd like to send the id value (to be implemented) of that card up to the controller so it can store it on an array.
I am going to be using closure actions for this part of the answer. As mentioned by #kumkanillam, they have better ergonomics, and they are the current proposed way to use actions if you consult the Guides.
I have prepared a Twiddle for you.
a) Initialize array in the controller
export default Ember.Controller.extend({
appName: 'Ember Twiddle',
gameCards: null,
init() {
this.set('gameCards', []);
}
}
b) Implement the action that pushed to the array
export default Ember.Controller.extend({
appName: 'Ember Twiddle',
gameCards: null,
init() {
this.set('gameCards', []);
},
actions: {
proveImAlive(cardNo) {
this.get('gameCards').pushObject(cardNo);
console.log('Im aliiiiveeee - cardNo', cardNo);
}
}
});
c) Bind the closure action down
{{game-card proveImAlive=(action 'proveImAlive')}}
d) Trigger the action passing the arguments
<div class="flipper">
<div class="front"></div>
<div class="back">
<button {{action proveImAlive 1}}> ProveIamAlive</button>
</div>
</div>
You need to explicitly set the action handler:
{{component-name fooAction=fooHandler}}
This is required because it helps keep components modular and reusable. Implicit links could result in a component triggering unintended behavior.
Your code should work, only if you have included game-card component into play.hbs. I doubt the controller for the particular route is not play in your case.
Here is the working-twiddle
Instead of bubbling actions, use closure actions. For better understanding you can go through the below links,
https://dockyard.com/blog/2015/10/29/ember-best-practice-stop-bubbling-and-use-closure-actions
http://miguelcamba.com/blog/2016/01/24/ember-closure-actions-in-depth/
https://emberigniter.com/send-action-does-not-fire/

Ember.js this.set() not working from inside action

Folks, I'm having some beginner problem and would love some assistance. This is in ember 2.6 if it makes any difference. Basically, I'm trying to change some text on the screen when the button is pressed inside a component. Here's the handlebars and component js code:
<h2>Stupid Button</h2>
<div>
<form id="sign_in_form" {{action 'triggerTheAction' on='submit'}}>
<button id="signin" type="submit">FIRE!</button>
{{someTextThatShouldChange}}
</form>
</div>
And the component's js code:
export default Ember.Component.extend({
someTextThatShouldChange: 'Initial Value',
actions: {
triggerTheAction: (email, password) => {
alert('alerts just fine');
this.set('someTextThatShouldChange', 'New value for text!');
}
}
});
The error I get in console:
TypeError: _this.set is not a function
Really appreciate the help. I have a feeling I'm just not understanding something fundamental about Ember.
You are using an arrow function to define the action handler. This means that it's binding this to the outer scope, which in your case is the ES6 module. By the specification, a module's this is undefined.

How to get reference of View in Controller

I am using Ember 1.0pre and following Ember suggested application structure (Using Router).
For form validation, I want to call $('form').valid() method on Button click.
So I have following method in my view
validate: function(){
return this.$('form').valid()
}
Action in template file:
<button type="submit" class="btn" {{action doSaveSettings this}}>Save Changes</button>
and doSaveSettings method is in Controller.
How can I get instance of view in controller, for calling validate method?
EDIT:
In controller, this.view is null. I have put {{debugger}} in template and this refers to
<App.XyzController:ember1062> and this.view is null.
The default target of actions have been changed from the view to the router in ember 0.9.8.1 (I believe). To set the target to the view you need to override it like this
<button type="submit" class="btn" {{action doSaveSettings target="view"}}>Save Changes</button>
edit: Your controllers should not know about the view.
In ember the purpose of a view is only i repeat is only to handle events or to create reusable components
i would not suggest this since there is always a reason why views are not meant to be accessed from controller and its good to follow it, however if you really do want to use it you could do the below two ways:
I dont know if u r using ember-cli or ember but the logic is same. The answer however is for ember-cli
//Inside appname/controller/your-conroller.js
import reqdView from 'appname/views/your-view';
//Lets assume u want to call a function called validate inside view
//Add this statement inside the controller to run the validate function
reqdView.prototype.validate();
OR
var reqdViewInst = new reqdView();
reqdViewInst.validate();
If you want to validate the view then do the validation inside didInsertElement
export default Ember.View.extend({
didInsertElement:function()
{
this.validate();
},
validate:function()
{
//do your validation
}
});
OR
export default Ember.View.extend({
eventManager: Ember.Object.create({
didInsertElement:function(event, view)
{
view.validate();
}
}),
validate:function()
{
//do your validation
}
});