Computed property doesn't work in a service - ember.js

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.

Related

Ember Octane How to Get Error Messages to be Displayed?

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

How can I update component value in controller or route with ember?

My click-editable component like:
Template:
{{#if isEdit}}
<div class="input-group">
{{input type="text" value=editValue class="form-control"}}
<div class="input-group-btn">
<button type="button" class="btn no-margin-btn btn-info" {{action "updateValue"}}>{{fa-icon 'check'}}</button>
</div>
</div>
{{else}}
....
{{/if}}
And:
export default Ember.Component.extend({
tagName: "",
isEdit: false,
canEdit: true,
category: 'input',
editValue: Ember.computed.oneWay('value'),
actions:{
updateValue() {
this.sendAction('onUpdate', this.get('valueModel'), this.get('valueColumn'), this.get('editValue'), this.get('isEdit'));
}
}
});
Use in my template:
{{#each model.quotationWorkItems as |quotationWorkItem index|}}
{{click-editable valueModel=quotationWorkItem valueColumn='name' value=quotationWorkItem.name onUpdate=(action "updateInput")}}
{{/each}}
In the controller:
import Ember from 'ember';
export default Ember.Controller.extend({
....
actions: {
updateInput(updateModel, updateColumn, value, isEdit) {
updateModel.set(updateColumn, value);
updateModel.save().then(()=> {
this.get('model').reload();
this.set('isEdit', false);
}, ()=> {
alert('wrong');
});
}
}
})
Route:
import Ember from 'ember';
export default Ember.Route.extend({
...
model(params) {
return this.store.find('quotation', params.quotation_id);
},
setupController(controller, model) {
controller.set('model', model);
...
}
})
Quotation model:
import DS from 'ember-data';
export default DS.Model.extend({
quotationStocks: DS.hasMany('quotationStock'),
quotationWorkItems: DS.hasMany('quotationWorkItem'),
...
});
QuotationWorkItem model:
import DS from 'ember-data';
export default DS.Model.extend({
transactionType: DS.belongsTo('transactionType'),
quotation: DS.belongsTo('quotation'),
...
});
This code can update model value, but problem isEdit is the component value. When isEdit send to controller and set another value, it can not work. So I think can not change component value in ember controller?
this.set('isEdit', false);
The code can not work in controller. I'm using Ember 2.4.0.
You need to bind the controller isEdit property to the component isEdit property.
{{click-editable isEdit=isEdit valueModel=quotationWorkItem valueColumn='name' value=quotationWorkItem.name onUpdate=(action "updateInput")}}
This will overwrite the isEdit property in the component (data down).

How should I reload model from child controller in emberjs?

I am having problem while trying to update the model values, when PendingActionController.updateStage method is called I need it to update the related model & reflect the updated values. If I create another method in PendingController like ShowMessage it displays the alert.
Please explain What approach should I use?
For example, following is the code:
<script type="text/x-handlebars" id="pending/_actions">
<div class="content-actions">
<h2>Pending Actions</h2>
<ul>
{{#each pendingstages}}
<li>
{{#unless refreshingStage}}
{{render 'pendingAction' this}}
{{/unless}}
</li>
{{/each}}
</ul>
</div>
</script>
<script type="text/x-handlebars" id="pendingAction">
<div class="actionsBox">
<div class="actionsBar">
<div {{bindAttr class=":actionStatus completed:blue:green"}} {{action updateStage this}}> </div>
</div>
<div class="clear-both"></div>
</div>
</script>
PendingController:
App.PendingController = App.BaseObjectController.extend(App.ActionsControllerMixin, {
needs: ['application'],
postRender: function () {
//Some code here....
},
pendingstages: function(){
return App.PendingStage.find({Id: this.get('model.id')});
}.property('model.id', 'model.#stages.completed', 'refreshStage'),
ShowMessage: function(){
alert('Inside Sohw message.');
},
});
PendingActionController
App.PendingActionMixin = {
isEditing: false,
canDelete: true,
canEdit: true,
toggleIsEditing: function(){
this.toggleProperty('isEditing');
}
};
App.PendingActionController = App.BaseObjectController.extend(App.PendingActionMixin, {
needs: 'pending',
postRender: function(){
//some code here...
},
updateStage: function(stage){
var self = this;
this.get('controllers.pending').send('pendingstages');
},
});
EDIT (1):
Followignt are the versions of Ember & ember-data:
ember-1.0.0-master.js
ember-data-master.js: CURRENT_API_REVISION: 12
Problem can be solved by using store.fetch instead of store.find.
store.fetch always calls the API, whether that particular data exists in local ember-data store or not. Use it like this..
pendingstages: function(){
return App.PendingStage.fetch({Id: this.get('model.id')});
}.property('model.id', 'model.#stages.completed', 'refreshStage'),
See ember-data/store.js code. It is deprecated now. But you'll find new methods instead of this.

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.

Toggle between child views with ember.js?

I am trying to render a view that toggles between two of its children (or so I'd hope) and something is not exactly working. Here is my template:
{{#view App.LoginFormView isVisibleBinding="user.isNotAuthenticated" }}
Username: {{view Ember.TextField valueBinding="user.loginName"}} /
Password: {{view Ember.TextField valueBinding="user.userPassword" type="password"}}
<button class="btn" {{ action "login" }} {{bindAttr disabled="user.isNotValid"}}>Login</button>
{{/view}}
{{#view App.LoginInfoView isVisibleBinding="user.isAuthenticated" }}
You are logged in as {{user.loginName}}. Click <a {{action "logout"}}>here</a> to logout
{{/view}}
in app.js I have the following:
App.User = Ember.Object.extend({
loginName:'',
userPassword:'',
rememberMe:true,
isNotValid:function(){
return (this.get("loginName") == '') || (this.get("userPassword") == '');
}.property('loginName', 'userPassword'),
isAuthenticated:false,
isNotAuthenticated:function(){
return !this.isAuthenticated;
}.property('isAuthenticated')
});
App.AuthenticationController = Ember.Controller.extend({
login:function() {
alert("loginName:"+this.user.get('loginName')+";\n"+
"userPassword:"+this.user.get('userPassword')+";\n"+
"rememberMe:"+this.user.get('rememberMe')+";\n");
this.user.isAuthenticated = true;
},
user:App.User.create()
});
App.AuthenticationView = Ember.View.extend({
templateName: 'authentication',
userBinding:"App.AuthenticationController.user"
});
App.LoginFormController = Ember.Controller.extend({
userBinding:"App.AuthenticationController.user"
});
App.LoginFormView = Ember.View.extend();
App.LoginInfoController = Ember.Controller.extend({
userBinding:"App.AuthenticationController.user"
});
App.LoginInfoView = Ember.View.extend();
App.Router = Ember.Router.extend({
enableLogging:true,
root: Ember.Route.extend({
index: Ember.Route.extend({
route: '/',
connectOutlets: function(router){
router.get('applicationController').connectOutlet('authentication','authentication');
},
login:function(router){
router.get('authenticationController').login();
}
})
})
});
The problem I am having is that the view does not toggle on the change of isAuthenticated property. I was under impression that would happen automagically and yet it does not. Any ideas on how to make this work? Or am I missing something fundamental (ember.js newbie here, so be gentle :-))
Cheers,
Alex.
You can implement user authentication in the following way:
In your template (for example in _header.hbs templates which is a partial for application.hbs)
{{#if needAuth}}
// login form goes here
<button {{action submitLogin}}>login</button>
{{else}}
<small {{action logout}}>logout</small>
{{/if}}
In application controller:
submitLogin: function () {
// do login stuff
// if login success
that.set('needAuth', false);
// else
that.set('needAuth', true);
});
DOM will update automatically. In other partial templates you can use {{#if needAuth}} as well.