Ember Octane How to Get Error Messages to be Displayed? - ember.js

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: ''
};
}
}

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}}

Related

Unit Testing Login Vue Jest ValidationProvider

I am new to Jest and I am trying to mock the store, an action and to assert that the method was indeed called. Basically I want to check the login function.
I cannot query the button because I am retrieving only a part of the component and I don't know what I'm missing.
Where my Vue component looks like:
<ValidationObserver id="observer" v-slot="{ invalid }" :class="['w-full h-full']">
<form class="flex flex-col justify-around h-full" #submit.prevent="onSubmit">
<ValidationProvider v-slot="{ errors }" name="accessCode" :class="['w-full py-6']">
<sd-field :class="['sd-field_secondary', { ['sd-invalid ']: errors && errors.length > 0 }]">
<label for="email">{{ $t("form.access-code") }}</label>
<sd-input v-model="accessCode" type="accessCode" />
<span class="sd-error">{{ errors[0] }}</span>
</sd-field>
</ValidationProvider>
<div class="btn-group-y">
<button class="btn btn-fixed btn-blueDark" type="submit">
<span>{{ $t("account.login") }}</span>
</button>
<!-- <button class="btn btn-link" type=""> //TODO: temporary hide
<span>{{ $t("account.forgot_id") }}</span>
</button> -->
</div>
</form>
</ValidationObserver>
***MY TEST FILE***
import login from "../../pages/index/login";
import Vuex from "vuex";
import { mount, createLocalVue } from "#vue/test-utils";
const localVue = createLocalVue();
localVue.use(Vuex);
describe("Login form", () => {
it("calls the login action correctly", () => {
const loginMock = jest.fn(() => Promise.resolve());
const store = new Vuex.Store({
actions: {
onSubmit: loginMock,
},
});
const wrapper = mount(login, { localVue, store, stubs: { ValidationObserver: true } });
console.log(wrapper.html());
// wrapper.find("button").trigger("click");
// expect(loginMock).toHaveBeenCalled();
});
});
console.log(wrapper.html()) returns only a part of component
<div class="h-full w-full"><validationobserver id="observer"
class="w-full h-full"> </validationobserver>
</div>
With a warning also:
> console.error node_modules/vue/dist/vue.runtime.common.dev.js:621
> [Vue warn]: Unknown custom element: <ValidationObserver> - did you register the
> component correctly? For recursive components, make sure to provide the "name" option.
> found in
> ---> <Anonymous>
> <Root>
I would like to understand how this works, I have tried to stub and other ways found but with no success.
I can get rid of the warning by adding the stubs: { ValidationObserver: true } to mount but I actually need to access the elements inside.
Thank you!

EmberJS - Ember Simple Auth Oath2 'grant _type' not defined

I've been following the Ember Simple Auth walkthrough available here. I have added the various code snippets as instructed but when I submit my login form I receive a 'grant_type' not defined error.
Here is the current setup:
// Login Form
<form {{action 'authenticate' on='submit'}}>
<label for="identification">Login</label> {{input value=identification placeholder='Enter Login' class='form-control'}}
<br>
<label for="password">Password</label> {{input value=password placeholder='Enter Password' class='form-control' type='password'}}
<br>
<button type="submit" class="btn btn-default">Login</button>
</form>
{{#if errorMessage}}
<p>
<strong>Login failed: </strong>
<code>{{errorMessage}}</code>
</p>
{{/if}}
//index.js controller
import Controller from '#ember/controller';
export default Controller.extend({
session: Ember.inject.service('session'),
actions: {
invalidateSession() {
this.get('session').invalidate();
},
authenticate() {
let {identification, password } = this.getProperties('identification', 'password');
this.get('session').authenticate('authenticator:oath2', identification, password).catch((reason) => {this.set('errorMessage', reason.error)
})
}
}
});
//application route
import Route from '#ember/routing/route';
import ApplicationRouteMixin from 'ember-simple-auth/mixins/application-route-mixin';
export default Ember.Route.extend(ApplicationRouteMixin);
// authenticators/oath.js
import OAuth2PasswordGrantAuthenticator from 'ember-simple-auth/authenticators/oauth2-password-grant';
export default OAuth2PasswordGrantAuthenticator.extend({
serverTokenEndpoint: 'http://server:port/api/token',
});
//api endpoint
var tokenRouter = express.Router();
tokenRouter.post('/api/token', function(req, res) {
if (req.body.grant_type === 'password') {
if (req.body.username === 'letme' && req.body.password === 'in') {
res.status(200).send('{"access_token": "secret token!"}');
} else {
res.status(400).send('{ "error": invalid_grant_type" }')
}
} else {
res.status(400).send(' { "error": "unsupported_grant_type" }')
}
})
app.use('/', tokenRouter)
The request is successfully sent to my endpoint and the 500 error is generated with the message that grant_type is not defined. Looking at the request, it doesn't look like the username or password are being sent either.
As far as I can tell my code is identical to the code from the documentation and supplementary video but I am obviously missing something.
I figured this out in the end.
After checking the Form Data in the Network tab of the Chrome debug tools it was clear that the grant_type was being sent correctly.
My issue was a missing bit of body-parser middleware:
app.use(bodyParser.urlencoded({ extended: false }))

how emberjs get form values in a object?

templates/module.hbs
<form class="" method="post" {{ action "step1" on="submit"}}>
{{input type="email" value=email}}
{{input type="checkbox" checked=permission}}
{{input type="submit" value="next"}}
</form>
how can i reach email and checkbox value in a object (like model.email and model checkbox ) in Route
routes/module.js
export default Ember.Route.extend({
model() {
return this.store.createRecord('wizard');
},
actions: {
step1(){
alert(this.controller.get('model.email')); // returns undefined
// get form values like model.email model.checkbox
},
}
models/wizard.js
export default DS.Model.extend({
email: DS.attr('string'),
permission: DS.attr('boolean')
});
Update: [[ alerts returns undefined ]]
First you will have to create model. let's say you are working on model user
//routes/module.js
export default Ember.Route.extend({
model() {
return this.store.createRecord('wizard');
},
actions: {
step1(){
this.controller.get('model.email')// you will get email value here.
// get form values like model.email model.checkbox
}
}
then in template you have to use in the same format
//templates/module.hbs
<form class="" method="post" {{ action "step1" on="submit"}}>
{{input type="email" value=model.email}}
{{input type="checkbox" checked=permission}}
{{input type="submit" value="next"}}
</form>
In case you do not want to use a model for the simplest request, you can use jQuery's serialize method.
btnQueryClicked() {
const $form = $('.bar-query-query');
const params = $form.serializeArray();
// convert parameters to dictionary
const paramsDict = {};
params.forEach((param) => {
paramsDict[param['name']] = param['value'];
});
$.post('/query', paramsDict)
.done((data) => {
console.log(data);
});
},
Code above is what I use to make a simplest query request for data display purpose only. (It is not that elegent but you get the idea)
It is too heavy to me to create a model only for a simple request which does not need to be persistent anyway.

Computed property doesn't work in a service

I'm using EmberJS 1.13. I want to show username (on application.hbs) when an user is logged in.
templats/application.hbs
<div id="main-frame" class="container-fluid">
<div id="page-header">
<img src="/assets/logo-main.png">
{{#if isLoggedIn}}
<span>{{currentUser.displayName}}</span>
{{else}}
<a id="loginButton" class="text-button" data-toggle="modal" data-target="#loginWindow">Войти</a>
{{/if}}
</div>
<div id="content-frame" class="container">
<ul class="nav nav-pills">
<li>{{#link-to 'builder'}}Конструктор{{/link-to}}</li>
<li>Мой бар</li>
<li>Админ-панель</li>
</ul>
<hr>
<hr>
{{outlet}}
</div>
</div>
{{login-window authSuccess="authSuccess"}}
{{signup-window}}
routes/application.js
import Ember from 'ember';
import ApplicationRouteMixin from 'ember-simple-auth/mixins/application-route-mixin';
export default Ember.Route.extend(ApplicationRouteMixin, {
currentUser: Ember.inject.service('current-user'),
isLoggedIn: Ember.computed.bool('currentUser.isAuthenticated'),
actions: {
authSuccess: function(userInfo) {
this.get('currentUser').setUser(userInfo);
}
}
});
services/current-user.js
import Ember from 'ember';
export default Ember.Service.extend({
session: Ember.inject.service("session"),
username: "",
password: "",
displayName: "",
accessLevel: -1,
isLoggedIn: false,
isAuthenticated: function () {
return this.get("isLoggedIn") && this.get('session').isAuthenticated;
}.property('isLoggedIn'),
setUser: function(userInfo) {
this.set("username", userInfo.username);
this.set("password", userInfo.password);
this.set("displayName", userInfo.displayName);
this.set("accessLevel", userInfo.accessLevel);
this.set("isLoggedIn", true);
}
});
I have next behavior: currentUser.setUser() called, currentUser.isLoggedIn setted, but currentUser.isAuthenticated doesn't recalculates and no username in html. What I do wrong?
UPDATE:
I also tried different ways implement isLoggedIn property:
isAuthenticatedObserver: Ember.on('init', Ember.observer('isLoggedIn', function () {
this.set('isAuthenticated', this.get("isLoggedIn") && this.get('session').isAuthenticated);
}))
////
isLoggedIn: function() {
return this.get('currentUser').isAuthenticated;
}.observes('currentUser.isAuthenticated')
////
isLoggedIn: function() {
return this.get('currentUser').isAuthenticated;
}.property('currentUser.isAuthenticated')
But nothing changed.
I got answer at ember forum. So all properties that was used in template must be in controller but not in route.

ember-data stores a string instead of a number

In my ember app I want to reuse a model attribute as soon as the form is submitted. But the store seems to keep it as string unless I reload the whole route. I am using this and the following components:
Ember : 1.12.0
Ember Data : 1.0.0-beta.18
jQuery : 1.11.3
/app/models/purchase.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
amount: DS.attr('number'),
createdAt: DS.attr('date', {
defaultValue: function() { return new Date(); }
}),
.. other callback and associations..
});
/app/controllers/ledger/purchases/new.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function () {
return {
newPurchase: this.store.createRecord('purchase', {
name: null,
amount: null,
player: null
})
}
}
});
/app/templates/ledger/purchases/new.hbs
<div class="row">
<div class="col-xs-12">
<h4>New purchase</h4>
<form>
<div class="form-group">
<label for="name" class="sr-only control-label">name</label>
{{input id='name' type="text" value=newPurchase.name placeholder="What" class="form-control"}}
</div>
<div class="form-group">
<label for="amount" class="sr-only control-label">amount</label>
{{input id='amount' type='number' value=newPurchase.amount placeholder="How much" class="form-control"}}
</div>
<div class="form-group">
<button type="submit" class="btn btn-success" {{action "create"}}>create</button>
{{#link-to 'ledger.purchases' tagName="button" class="btn btn-link" }}cancel{{/link-to}}
</div>
</form>
</div>
</div>
/app/controllers/ledger/purchases/new.js
import Ember from 'ember';
export default Ember.Controller.extend({
newPurchase: Ember.computed.alias('model.newPurchase'),
actions: {
create: function() {
var np = this.get('newPurchase');
console.log(Ember.typeOf(np.get('amount')));
........
save np etc...
}
}
});
the console log call clearly shows that the type is a string. The ember inspector shows the same. However data are correctly saved to the backend because after reloading everything is fine. But I need the amount as a number as soon as it is submitted because I use it to make and show the sum of all purchases.
Okay, I think I know what's going on. Setting input type to number won't help here. Value is still recognized as string. Usually when you submit form, backend anyway returns this value formatted as a number and problem's gone. You can see this even when you mock your data with a number, without a backend.
My solution would be to use a computed property for input component. Model:
export default DS.Model.extend({
name: DS.attr('string'),
amount: DS.attr('number'),
createdAt: DS.attr('date', {
defaultValue: function() { return new Date(); }
}),
amountAsNum: Ember.computed('amount', {
get: function () {
return parseFloat(this.get('amount'));
},
set: function (key, value) {
var valueToSet = parseFloat(value);
this.set('amount', valueToSet);
return valueToSet;
}
}),
.. other callback and associations..
});
Template:
{{input id='amount' type='number' value=newPurchase.amountAsNum placeholder="How much" class="form-control"}}
Now, you can check typeof(amount) before save and it'll give you number. Demo on JS Bin.