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.
Related
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}}
I am trying to create a comment with this addComment action where I want to use the input text as the comment text and do a save to create the comment.
I couldn't connect the input box to comment.body because the position of this code does not have the comment model available.
I created a body field on the item model so I connect item.body to the text box and then use this as the comment.body when creating the comment which seems very wrong. Does anyone have any suggestions as to how to do this the correct way?
<form class="comments-list__add-comment add-comment" action="">
{{input type="text" class="add-comment__input" name="" value=item.body placeholder="Please add a comment"}}
<button {{action "addComment" item}} type="button" class="btn add-comment__submit" name="button">Add comment</button>
</form>
addComment(item){
const plan = item.get('plan');
const text = this.get('item.body');
const currentUserName = plan.get('appConfig.currentUser');
const currentUserId = plan.get('appConfig.currentUserId');
const itemid = item.id;
if(text.trim() !== ''){
let comment = this.get('item.store').createRecord('comment', {
body: text,
createdAt: new Date(),
commentableId: itemid,
commentableType: 'Plan',
unread: true,
commenterName: currentUserName,
commenterId: currentUserId
});
item.get('comments').pushObject(comment);
comment.save();
item.set('displayAddCommentForm', false);
this.set('item.body', '');
}
},
You can create a comment record in your route and assign it to a controller property. Then you can bind your template to the controller's comment property, like this:
route
export default Ember.Route.extend({
setupController(controller, model) {
this._super(...arguments);
controller.set('comment', this.store.createRecord('comment');
}
});
template
{{input value=comment.body}}
Then, in your Route's save method:
let comment= this.controller.get('comment');
// the remainder of your save should follow...
// At this point, comment.body should have the text entered by user
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.
If I define a checkbox as follows:
{{input type="checkbox" name="email" checked=controller.isEmailChecked}} Email
In the callback controller.isEmailedChecked, defined as:
isEmailChecked: function(key, value) {
...
}
How do I get the value of name ("email")?
My controller is responsible for displaying multiple checkboxes so I do not want to have to write lines like this:
{{input type="checkbox" name="x" checked=controller.isXChecked}} X
{{input type="checkbox" name="y" checked=controller.isYChecked}} Y
Only to be doing:
ixXChecked: function(key, value) {
// Set or Get
this.set('x', value);
this.get('x');
}
ixYChecked: function(key, value) {
// Set or Get
this.set('y', value);
this.get('y');
}
Can someone point me to the docs please.
Edit: Same applies to actions on Buttons:
<button {{action 'toggleSetting'}}>
</button>
How would I get the name of the element in the toggleSetting callback?
Thanks a lot,
Thanks albertjan but this is more simple:
{{view Ember.Checkbox checkedBinding=someModelProperty}}
It updates the someModelProperty when clicked, and it also sets the correct value initially.
You can pass a parameter to a action like this: {{action "post" "test"}} where the second test can also be a variable. {{action "post" mail}}
As for the checkboxes I'd solve that with an item controller:
{{#each thing in things itemController='thing'}}
{{input type="checkbox" checked=onChecked}}
{{/each}}
and your controller would looks something like:
App.ThingController = Ember.Controller.extend({
needs: ['parent'] //where parent is the name of your other controller.
action: {
isChecked: function() {
this.get('contollers.parent')
.send('checkboxChecked', this.get('model.name'));
}
}
});
Or you can add a component, lets call it named-checkbox. And that would look something like this:
Template (app/templates/components/named-checkbox.hbs):
{{input name=name type="checkbox" checked=onChecked}}
{{yield}}
Component (app/components/named-checkbox.js):
import Ember from 'ember';
import layout from '../templates/components/named-checkbox';
export default Ember.Component.extend({
layout: layout,
name: "",
action: {
onChecked: function() {
this.sendAction(this.get('action'), this.get('name'));
}
}
});
Then you can use it like this:
{{named-component name="email" action=isChecked}}
This will result in the isChecked action being called on the controller with the name as it's attribute:
actions: {
isChecked: function(name) {
this.set('name', !this.get('name'))
}
}
I'm trying to create a manually saved form with a moderate number of fields (let's say 20) in Ember.js (not using live bindings) and so far am confused about the correct way / best practice for doing so. I've found the following methods:
http://www.solitr.com/blog/2012/06/ember-input-field-with-save-button/
How to use one-way binding on emberjs?
https://stackoverflow.com/a/16473186/1248965
All of the above methods seem hacky to a degree; they either extend the text field or use a per-field observer, requiring you to list out each one. Is there some other way? Something like the 'unbound' helper, but allowing the auto-model updating magic / validation (via ember-data) on some action (like an 'unbound-until' or 'conditional-bind' or something)? I've gone through all the docs, SO, the github issues, the Ember forum, and the links above, and still feel like I must have missed something.
Basically, a way to say "do everything you would do with a normally bound form/fields, but only on a certain action, rather than in real time."
What you want is a "Buffered Proxy", where you temporarily store all changes to the model (you can catch those using setUnkownProperty) in a proxy object. Once you are happy with the changes, you'd copy all of the proxy data over into the actual object ("flush the data").
App.Heisenberg = {
firstName: 'Walter',
lastName: 'White',
};
App.IndexRoute = Ember.Route.extend({
model: function() {
return App.Heisenberg;
},
setupController: function(controller, model) {
controller.set('content', model);
}
});
App.IndexController = Ember.ObjectController.extend({
proxy: {},
setUnknownProperty: function(key, value) {
console.log("Set the unknown property: " + key + " to: " + value);
this.proxy[key] = value;
console.log(this.proxy);
},
flush: function() {
for(var key in this.proxy)
this.set('model.'+key, this.proxy[key]);
}
});
Template:
<script type="text/x-handlebars" data-template-name="index">
Saved Name: {{firstName}} {{lastName}}<br />
<br />
<form {{ action "saveEdit" on="submit" }}>
First Name: {{input type="text" valueBinding="firstName"}}<br />
Last Name: {{input type="text" valueBinding="lastName"}}<br />
<br />
<button {{ action "flush" }}>Flush</button>
</form>
</script>
This would make for a nice controller Mixin.
See this jsBin for a live example.
I found a workaround, but I'm not 100% happy with it:
In my "editing" template, I have:
<form {{ action "saveEdit" on="submit" }}>
Title: {{input type="text" value=title}}
<input type="submit" value="Save">
<button {{ action "cancelEdit" }}>Cancel</button>
</form>
Then in my associated controller, I do:
cancelEdit: function() {
var entry = this.get('model');
this.set('isEditing', false);
entry.rollback();
},
saveEdit: function() {
var entry = this.get('model');
this.set('isEditing', false);
entry.save().then(
function() {
console.log('Saved!');
}
I simply hide the fields where the "live updating" would show. I still would like to find a way to temporarily turn off the binding until I trigger my "saveEdit" action, since this still seems inelegant.