7

Upgrade from Ember <3.15 to >=3.15. How do I pass form values from a controller into a component?

I cannot begin to explain the number of diagnostic combinations attempted and their corresponding errors received. So, I figure it best to ask how it should be done correctly? Is Glimmer involved?

A simple example: pass a change password from old password to both a new and confirm password via a component to a controller. In the Component, I keep getting onsubmit() is not a function error.

Code example:

User Input Form

ChangePasswordForm.hbs

<div class="middle-box text-center loginscreen animated fadeInDown">
    <div>
        <h3>Change Password</h3>
        <form class="m-t" role="form" {{on "submit" this.changePassword}}>
            {{#each errors as |error|}}
                <div class="error-alert">{{error.detail}}</div>
            {{/each}}
            <div class="form-group">
            {{input type="password" class="form-control" placeholder="Old Password" value=oldPassword required="true"}}
            </div>
            <div class="form-group">
                {{input type="password" class="form-control" placeholder="New Password" value=newPassword required="true"}}
            </div>
            <div class="form-group">
                {{input type="password" class="form-control" placeholder="Confirm Password" value=confirmPassword required="true"}}
            </div>
            <div>
                <button type="submit" class="btn btn-primary block full-width m-b">Submit</button>
            </div>
        </form>
    </div>
</div>

Template Component

ChangePassword.hbs

<Clients::ChangePasswordForm @chgpwd={{this.model}} {{on "submit" this.changePassword}} @errors={{this.errors}} />

Component

ChangePasswordForm.js

import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';

export default class ChangePasswordForm extends Component {

    @tracked oldPassword;
    @tracked newPassword;
    @tracked confirmPassword;
    @tracked errors = [];

    @action
    changePassword(ev) {

        // Prevent the form's default action.
        ev.preventDefault();

        this.oldPassword = ev.oldPassword;
        this.newPassword = ev.newPassword;
        this.confirmPassword = ev.confirmPassword;

        // Call the form's onsubmit method and pass in the component's values.

        this.onsubmit({
            oldPassword: this.oldPassword,
            newPassword: this.newPassword,
            confirmPassword: this.confirmPassword
        });
    }
}

Controller

ChangePassword.js

import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';

export default class ChangePassword extends Controller {

    @service ajax 
    @service session

    @action
    changePassword(attrs) { 

        if(attrs.newPassword == attrs.oldPassword)
        {
            this.set('errors', [{
                detail: "The old password and new password are the same.  The password was not changed.",
                status: 1003,
                title: 'Change Password Failed'
            }]);
        }
        else if(attrs.newPassword != attrs.confirmPassword)
        {
            this.set('errors', [{
                detail: "The new password and confirm password must be the same value.  The password was not changed.",
                status: 1003,
                title: 'Change Password Failed'
            }]);
        }
        else
        {
            let token = this.get('session.data.authenticated.token');

            this.ajax.request(this.store.adapterFor('application').get('host') + "/clients/change-password", {
                method: 'POST',
                data: JSON.stringify({ 
                    data: {
                        attributes: {
                            "old-password" : attrs.oldPassword,
                            "new-password" : attrs.newPassword,
                            "confirm-password" : attrs.confirmPassword
                        },
                        type: 'change-passwords'
                    }
                }),
                headers: {
                    'Authorization': `Bearer ${token}`,
                    'Content-Type': 'application/vnd.api+json',
                    'Accept': 'application/vnd.api+json'
                }
            })
            .then(() => {

                // Transistion to the change-password-success route.
                this.transitionToRoute('clients.change-password-success');
            })
            .catch((ex) => {

                // Set the errors property to the errors held in the ex.payload.errors.  This will allow the errors to be shown in the UI.
                this.set('errors', ex.payload.errors);
            });
        }
    }
}

Model

ChangePassword.js

import Route from '@ember/routing/route';
import AbcAuthenticatedRouteMixin from '../../mixins/abc-authenticated-route-mixin';

export default Route.extend(AbcAuthenticatedRouteMixin, {
//export default class ChangePasswordRoute extends Route(AbcAuthenticatedRouteMixin, {

    model() {

        return {
            oldPassword: '',
            newPassword: '',
            confirmPassword: ''
        };
    },
})
johannchopin
  • 13,720
  • 10
  • 55
  • 101
J Weezy
  • 3,507
  • 3
  • 32
  • 88
  • 2
    How do you invoke the component and pass the `changePassword` action from the controller? – Gokul Kathirvel Mar 16 '20 at 04:29
  • @GokulKathirvel I have updated my answer - I was missing a few files. Thank you for catching that. – J Weezy Mar 17 '20 at 01:09
  • 1
    Everything seems fine for me.. can you able to reproduce using ember-twiddle.com – Gokul Kathirvel Mar 17 '20 at 06:46
  • What I am trying to say is that this works in Ember 3.12. But, 3.17 Octane introduces changes. What should this code look like in 3.17? – J Weezy Mar 17 '20 at 07:03
  • AFAIK, Octane is fully compatible with the classic Ember model/code. So, there should be not any issue with Octane upgrade. – Gokul Kathirvel Mar 17 '20 at 11:18
  • Why don't you upgrade to native classes and decorators at the same time? – Joshua Jenkins Mar 17 '20 at 23:07
  • I have updated the question to include native classes and decorators. This is what I have attempted so far, but it is not working. – J Weezy Mar 18 '20 at 03:23
  • Ok, from a brief look I may suggest 1) instead of `{{on "submit" this.changePassword}}` pass it like `@changePassword={{this.changePassword}}` so simply pass the action as a parameter and 2) then in your component call it not as `this.onsubmit` (I guess Ember is right here saying it's not defined) but rather as `this.args.changePassword`. If that helps I'd make that an answer. – Andrey Stukalin Mar 18 '20 at 04:12
  • @AndreyStukalin By changing to `@changePassword={{this.changePassword}}` then the component receives null data. Although, the `onsubmit is not a function` error goes away. But, now I don't have my data. – J Weezy Mar 18 '20 at 04:50
  • @JWeezy what do you mean by "null data"? The `@changePassword` function is null? – Andrey Stukalin Mar 18 '20 at 04:56
  • @AndreyStukalin in the component, the `changePassword(ev)` ev comes in undefined (null). For example, the breakpoint, `this.oldPassword = ev.oldPassword;` is undefined – J Weezy Mar 18 '20 at 04:58
  • Ok, and what if you change your `onsubmit=` to `{{on "submit"}}` like described here https://guides.emberjs.com/release/components/looping-through-lists/#toc_updating-lists? – Andrey Stukalin Mar 18 '20 at 05:19
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/209824/discussion-between-j-weezy-and-andrey-stukalin). – J Weezy Mar 18 '20 at 05:19
  • There is no onsubmit method of @glimmer/component – Gaurav Mar 25 '20 at 05:02
  • @gaurav Can you please show how this is supposed to be written in Glimmer/Octane? I can use this as a template to do the rest – J Weezy Mar 25 '20 at 05:04
  • It would help very much if we had a reproduction. Why not put something up in ember-twiddle.com? – Gaurav Mar 25 '20 at 05:05
  • @gaurav Do you want a working example from pre-Octane? I can copy and paste the above into Ember-Twiddle, but it won't work. – J Weezy Mar 25 '20 at 05:13
  • I didn't expect it to work, I expected you to make it so you show your code and the error you are getting. Then I could easily fix it for you. – Gaurav Mar 25 '20 at 05:53

1 Answers1

7

There is no onsubmit method in @glimmer/component, so you cannot call this.onsubmit inside an action in the component.

First, you need to pass the action created in your controller to your component. This can be done like this:

<ChangePasswordForm @chgpwd={{this.model}} @changePassword={{action 'changePassword'}} />

Remember, you cannot pass data up any more in a glimmer component, you need to use an action since everything is one way binding.

Second, you need to call this action inside your glimmer component:

this.args.changePassword({
  oldPassword: this.oldPassword,
  newPassword: this.newPassword,
  confirmPassword: this.confirmPassword
});

I've created an Ember Twiddle for you to show this example working.

J Weezy
  • 3,507
  • 3
  • 32
  • 88
Gaurav
  • 12,662
  • 2
  • 36
  • 34
  • I am now receiving an eslint no-action error. I have posted a new question here https://stackoverflow.com/questions/60880044/ember-octane-upgrade-how-to-handle-eslint-error-no-action Can you please advise? – J Weezy Mar 27 '20 at 04:35
  • Can you also show in the twiddle example of how to get the errors that may occur to come up on the page. For instance, if the old password and new password are the same we want that error to display on the page? – J Weezy Mar 29 '20 at 22:41