0
votes

This question is related to Ember Octane Upgrade How to pass values from component to controller

How do I get Ember Octane to display on the webpage? For instance, if the old password and new password are the same we want that error to display on the page.

Ember-Twiddle here

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 this.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={{this.oldPassword}} required="true" />
            </div>
            <div class="form-group">
                <Input @type="password" class="form-control" placeholder="New Password" @value={{this.newPassword}} required="true" />
            </div>
            <div class="form-group">
                <Input @type="password" class="form-control" placeholder="Confirm Password" @value={{this.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}} @changePassword={{action '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
    changeOldPassword(ev) {
        this.oldPassword = ev.target.value;
    }
    @action
    changeNewPassword(ev) {
        this.newPassword = ev.target.value;
    }
    @action
    changeConfirmPassword(ev) {
        this.confirmPassword = ev.target.value;
    }

    @action
    changePassword(ev) {

        ev.preventDefault();

        this.args.changePassword({
            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)
        {
shown in the UI.
            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(() => {

                this.transitionToRoute('clients.change-password-success');
            })
            .catch((ex) => {

                this.set('errors', ex.payload.errors);
            });
        }
    }
}

Model

ChangePassword.js

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

export default class ChangePasswordRoute extends Route.extend(AbcAuthenticatedRouteMixin) {

    model() {

        // Return a new model.
        return {
            oldPassword: '',
            newPassword: '',
            confirmPassword: ''
        };
    }
}
1
There is a mismatch between the snippet mentioned here and your twiddle. It would be easier to help if you post the updated twiddle with the exact issue you are facing with Octane.Gokul Kathirvel
Please update this question so that it's clear what's not working. One thing I already noticed: the {{on "submit" this.changePassword}} binding you setup here Clients::ChangePasswordForm will never work. Pass the action with @onsubmit={{action 'changePassword'}}mistahenry
@mistahenry With apologies, I have corrected the code. The ember-twiddle is actually provided by the individual who answered the last question. Unfortunately, I do not have the ability to change that twiddle. My code posted above is the effective implementation of what is in ember-twiddle.J Weezy

1 Answers

2
votes

In your form component, you reference the errors like

{{#each this.errors as |error|}}
  <div class="error-alert">{{error.detail}}</div>
{{/each}}

From class components -> glimmer components, there's been a fundamental shift in the way you access the component's arguments vs the component's own values (for the better!)

In class components, arguments are assigned directly to the class instance. This has caused a lot of issues over the years, from methods and actions being overwritten, to unclear code where the difference between internal class values and arguments is hard to reason about.

New components solve this by placing all arguments in an object available as the args property.

When referencing an argument to a component in javascript, you use: this.args.someArg. In the template, you use the shorthand @someArg. These are known as "named arguments" (feel free to read the rfc for more info). When you, as you did here, use this.errors in your template, you are looking for a local component property errors.

Just to emphasize, this does not work because errors is passed to Clients::ChangePasswordForm via @errors here:

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

and must be @errors in the template

{{#each @errors as |error|}}
  <div class="error-alert">{{error.detail}}</div>
{{/each}}