Use ember-simple-validaiton to validate property on hasMany relation - ember.js

this question is about particular plugin - ember-cli-simple-validation.
Lets assume that there is two models
User = DS.Model.extend({
name: DS.attr()
emails: DS.hasMany('email')
})
Email = DS.Model.extend({
address: DS.attr()
emails: DS.belongsTo('user')
})
and user dynamically create emails:
<button {{action 'createEmail'}}>Add Email</button>
{{#each model.emails as |email index|}}
{{input value=email.address}}
{{/each}}
is it possible to use ember-simple-validaiton in this situation to validate presence of address on each of the emails?
I tried to go with validateEach but getting an Error while processing route: profile self.get(...).forEach is not a function TypeError: self.get(...).forEach is not a function error on the https://github.com/toranb/ember-cli-simple-validation/blob/master/addon/mixins/validate.js#L66
controller:
import Ember from 'ember';
import {ValidationMixin, validateEach} from "ember-cli-simple-validation/mixins/validate";
var isLegit = function(address) {
return address && address.length > 3;
};
export default Ember.Controller.extend(ValidationMixin, {
emailAddressValidation: validateEach("model.emails", isLegit),
actions: {
createChildren: function(type) {
this.store.createRecord(type, {profile: this.get('model')});
},
});
template:
{{#each model.emails as |email index|}}
{{input value=email.address}}
{{#validation-error-field submitted=submitted field="address" model=mode.emails index=index validation="basic"}}invalid address{{/validation-error-field}}
{{/each}}
model:
import Ember from 'ember';
import DS from 'ember-data';
export default DS.Model.extend{
address: DS.attr(''),
kind: DS.attr(''),
contactable: DS.belongsTo('contactable', { polymorphic: true, inverse: 'emails', async: false }),
kindIsPrimed: false,
kindChanged: Ember.observer('kind', function () {
this.set('kindIsPrimed', true);
}),
addressIsPrimed: false,
addressChanged: Ember.observer('address', function () {
this.set('addressIsPrimed', true);
})
});

On the surface (without running the code) you might alter your hbs from this
{{#each model.emails as |email index|}}
{{input value=email.address}}
{{#validation-error-field submitted=submitted field="address" model=mode.emails index=index validation="basic"}}invalid address{{/validation-error-field}}
{{/each}}
to instead ...
{{#each model.emails as |email index|}}
{{input value=email.address}}
{{#validation-error-field submitted=submitted field="address" model=email index=index validation="basic"}}invalid address{{/validation-error-field}}
{{/each}}
In the validateEach scenario (for array validation) you need to set the model and index on each iteration. The model should still be the individual model (not the array itself).
Another example that's tested in the addon for reference
https://github.com/toranb/ember-cli-simple-validation/blob/master/tests/dummy/app/templates/many.hbs

Related

EmberError: Nothing handled the action

I have been struggling with making my Ember Application work with Firebase.
I went through all the posts here on Stackoverflow about the similar matter but I did not find the answer to my problem. So here it is:
Whenever I try to put data into input fields and submit them with a button i get the console error:
EmberError
code : undefined
description : undefined
fileName : undefined
lineNumber : undefined
message :
"Nothing handled the action 'createBook'. If you did handle the action, this error can be caused by returning true from an action handler in a controller, causing the action to bubble."
My model:
import DS from 'ember-data';
export default DS.Model.extend({
title: DS.attr('string'),
author: DS.attr('string'),
picture: DS.attr('string'),
buyer: DS.attr('string'),
bought: DS.attr('boolean', { defaultValue: false }),
createdAt: DS.attr('date', { defaultValue() { return new Date(); } })
});
And my Controller:
import Ember from 'ember';
export default Ember.Controller.extend({
actions: {
createBook: function(){
var newBook = this.store.createRecord('book', {
title: this.get('title'),
author: this.get('author'),
picture: this.get('picture'),
buyer: this.get('buyer'),
bought: this.set(false),
createdAt: new Date().getTime()
});
newBook.save();
//reset values after create
this.setProperties({'title' : '',
'author' : '',
'picture' : '',
'buyer' : ''
});
}
}
});
The template:
{{outlet}}
<div style ="margin-left:130px;">
<h1> blabla </h1>
{{input type="text" value=title placeholder="Add Title"}}<br>
{{input type="text" value=author placeholder="Add author"}}<br>
{{input type="text" value=picture placeholder="Add picture"}}<br>
{{input type="text" value=buyer placeholder="buyer"}}<br>
</div>
<button class="btn btn-default" {{action "createBook" }}> Create</button>
{{#each model as |book|}}
<ul>
<li>{{book.title}}</li>
</ul>
{{/each}}
The connection between the Firebase and Ember is set up 100 % properly.
For now the rules on firebase have been set to true for both read and write.
The only problem is that it does not post the data to Firebase.
Thanks #Lux for your advice.
There were several things wrong with my code.
I created model and controller called book, but route called books.
I did not know that it will have an effect on my model and controller.
So i ended up with:
app/controllers/book.js
app/model/book.js
app/routes/book.js
app/templates/book.hbs
This was not enough. I also had to edit content of my controller
import Ember from 'ember';
export default Ember.Controller.extend({
actions: {
createBook: function(){
var newBook = this.store.createRecord('book', {
title: this.get('title'),
author: this.get('author'),
picture: this.get('picture'),
buyer: this.get('buyer')
});
newBook.save();
//reset values after create
this.setProperties({'title' : '',
'author' : '',
'picture' : '',
'buyer' : ''
});
}
}
});
As you can see I have removed the lines that were setting the default values of bought and createdAt. It was enough just to set them inside the model itself.

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).

ember firebase pass models to action handler

I have ember data models hooked with firebase, characters and spells. I can create new models and save them to firebase. Now I wanted to add spells to character. I defined that character has many spells:
export default DS.Model.extend({
chClass: DS.attr(),
chName: DS.attr(),
chImage: DS.attr(),
chSpells: DS.hasMany('spell', {async: true}),
});
In my hbs I listed spells in <select> element, there is also input fields and add button.
Add new character <br>
name {{input value=mchName }}<br>
class {{input value=mchClass }}<br>
image {{input value=mchImage }}<br>
<br>
Choose Spells:<br>
<select name="spellslist" multiple>
{{#each spells as |spell index|}}
<option value="{{index}}">{{spell.spName}}</option>
{{/each}}
</select>
<button {{action 'addChar' spells}}>add</button><br>
So when user types in character name, level and picks some spells I want to call addChar action function on add button and pass this data.
export default Ember.Controller.extend({
mchName:'',
mchClass:'',
mchImage:'',
store: Ember.inject.service(),
actions: {
addChar: function(spells) {
var newChar = this.store.createRecord('character');
newChar.set("chName", this.mchName);
newChar.set("chClass", this.mchClass);
newChar.set("chImage", this.mchImage);
newChar.get("chSpells").addObject(?????? how to get spell here ?????);
newChar.save();
I know how to pass string from inputs, but I dont know how to pass selected spells to this function, its killing me.
I'm assuming that you (as admin) are going to populate the spells table. Now ... assuming that a character can have many spells and a spell can have many characters, here's how one can approach this (note that I'm using a controller ... you should ideally be doing this in a component):
Character model is simplified:
//app/models/character.js
import DS from 'ember-data';
export default DS.Model.extend({
chName: DS.attr(),
chSpells: DS.hasMany('spell', {async: true})
});
Spells model is also simplified for this example:
//app/models/spell.js
import DS from 'ember-data';
export default DS.Model.extend({
spName: DS.attr(),
spellChar: DS.hasMany('character', {async: true})
});
We need an include helper for the multiline select. Review this article for details:
//app/helpers/include.js
import Ember from 'ember';
export function include(params/*, hash*/) {
const [items, value] = params;
return items.indexOf(value) > -1;
}
export default Ember.Helper.helper(include);
Here's the application route:
app/routes/application.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function(){
var spells = this.store.findAll('spell');
return spells;
}
});
And the application controller:
//app/controllers/application.js
import Ember from 'ember';
export default Ember.Controller.extend({
selectedSpellIds: [],
actions: {
selectSpell(event){
const selectedSpellIds = Ember.$(event.target).val();
this.set('selectedSpellIds', selectedSpellIds || []);
},
addChar: function(){
var charName = this.get('mchName');
var _this = this;
var spells = this.get('selectedSpellIds');
var spellObjArray = spells.map(function(spellId){
return _this.store.peekRecord('spell', spellId );
});
var charToSave = this.store.createRecord('character', {
chName: charName,
chSpells: spellObjArray
});
charToSave.save();
},
}
});
And the application template:
//app/templates/application.hbs
Add new character <br>
name {{input value=mchName }}<br>
<br>
Choose Spells:<br>
<select multiple onchange={{action "selectSpell"}}>
{{#each model as |spell|}}
<option value={{spell.id}} selected={{include selectedSpellIds spell.id}}>{{spell.spName}}</option>
{{/each}}
</select>
<button {{action 'addChar'}}>add</button><br>

Disable button in Ember with return value of function

I've been trying to create an Ember Component with the latest version on Ember. Here is the part of the component I'm having trouble with though:
//app/templates/components/page-r.hbs
<div class="pager-container">
{{#each range as |page|}}
<button type="button" disabled={{isCurrentPage(page)}}>{{page}}</button>
{{/each}}
</div>
//app/components/page-r.js
import Ember from 'ember';
const PageR = Ember.Component.extend({
settings: Ember.computed('params.[]', function() {
return this.get('params')[0];
}),
range: [4,5,6],
currentPage: 1,
isCurrentPage(pageIndex) {
return this.currentPage === pageIndex;
},
});
PageR.reopenClass({
positionalParams: 'settings'
});
export default PageR;
There is more to the component than that, but my question is this:
Is it possible to make the disabled attribute update based off of the result of an action like I'm trying to do? If so, how?
The above code doesn't even compile, but I've tried a number of variations and haven't gotten any of them to work. I can get the disabled property to work based off a pure boolean value, but can't figure out how to get this to execute the function and utilize the boolean value that is returned from it to fuel the disabled attribute. Any help would be greatly appreciated!
You need to use another component to create the computed property.
// app/templates/components/page-r.hbs
<div class="pager-container">
{{#each range as |page|}}
{{page-button page=page currentPage=currentPage}}
{{/each}}
</div>
// app/templates/components/page-button.hbs
<button type="button" disabled={{isCurrentPage}}>{{page}}</button>
// app/components/page-r.js
import Ember from 'ember';
const { computed } = Ember;
const PageR = Ember.Component.extend({
settings: computed('params.[]', function() {
return this.get('params')[0];
}),
range: [4,5,6],
currentPage: 1
});
PageR.reopenClass({
positionalParams: 'settings'
});
export default PageR;
// app/components/page-button.js
import Ember from "ember";
const { computed } = Ember;
export default Ember.Component.extend({
tagName: 'span',
isCurrentPage: computed('currentPage', 'page', function() {
return this.get('currentPage') === this.get('page');
}),
});

Ember computed property to return first X number of items from list

I have a one-to-many relationship (using Ember Data). All I want to do is list the first N number of items from that relationship in an overview (index) template. I'm trying to use the Array.slice method but it doesn't seem to return anything at all.
Here's what I have right now:
models/account.js
// Account model
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
notes: DS.hasMany('note', { async: true })
});
models/note.js
// Note model
import DS from 'ember-data';
export default DS.Model.extend({
body: DS.attr('string'),
date: DS.attr('number'), // unix timestamp
account: DS.belongsTo('account', { async: true })
});
controllers/account/index.js
// account/index controller
import Ember from 'ember';
export default Ember.ObjectController.extend({
firstNotes: function() {
return this.get('notes').slice(0,2);
}.property('notes')
});
templates/account/index.hbs
{{!-- this lists all the associated `Notes` --}}
{{#each notes}}
{{date}}<br>
{{body}}
{{/each}}
{{!-- this doesn't list anything!!?? --}}
{{#each firstNotes}}
{{date}}<br>
{{body}}
{{/each}}
I figured this out just as I was about to post it so I figured I'd answer it...
All I was missing was a #each in the computed property dependency. So it works as expected with this:
firstNotes: function() {
return this.get('notes').slice(0,2);
}.property('notes.#each')
Simple.