I'm having the following problem.
In my app I have a screen to make a new Site. But when I save the new site via an action on the controller, the languages-property isn't sent with the POST-request to the server.
The template for adding a new Site is this:
<form class="form-horizontal">
<div class="control-group">
<label class="control-label" for="name">Name</label>
<div class="controls">
{{view Ember.TextField valueBinding="name"}}
</div>
</div>
<div class="control-group">
<label class="control-label" for="languages">Languages</label>
<div class="controls">
{{view Ember.Select contentBinding="controllers.languages" selectionBinding="languages" optionValuePath="content.id" optionLabelPath="content.description" multiple="true"}}
</div>
</div>
<div class="form-actions">
<button {{ action "createSite" }} class="btn btn-primary">Save</button>
</div>
I defined my Store like this:
App.Store = DS.Store.extend({
revision : 12,
adapter : DS.RESTAdapter.extend({
namespace : 'rest'
})
});
This is my controller:
App.SitesNewController = Em.ObjectController.extend({
needs: ['languages'],
name: null,
languages: null,
createSite : function() {
var self = this;
var name = this.get('name');
var languages = this.get('languages');
// Create the new Site model
var s = App.Site.createRecord({
name : name
});
$.each(languages,function(i,lang) {
s.get('languages').addObject(lang);
});
this.get('store').commit();
}
});
This is the Site-model
App.Site = DS.Model.extend({
name : DS.attr('string'),
languages : DS.hasMany('App.Language')
});
Language-model:
App.Language = DS.Model.extend({
description : DS.attr('string')
});
The POST-request data sent to my server is this:
{
"site":{"name":"test"}
}
So I miss the language-property. Actually I expect a language_ids property with an array of id's.
When I edit my RESTAdapter-configuration like this:
DS.RESTAdapter.map('App.Site', {
languages: { embedded: 'always' }
});
Now the POST-request data is:
{
"site": {
"name":"test",
"languages":[{
"id":2,"description":"English"
},{
"id":3,"description":"Deutsch"
}]
}
}
The languages are know embedded in the request-data. This is no problem, at the backend I get the id before I save it. But know it expects the language-data to be embedded in the GET-responses also.
What is the way to send just the id's in the POST-data? I want it to be something like this:
{
"site": {
"name":"test",
"languages":[2,3]
}
}
This answer is largely derived from this other StackOverflow answer.
App.Store = DS.Store.extend({
revision : 12,
adapter : DS.RESTAdapter.extend({
namespace : 'rest',
serializer: DS.RESTSerializer.extend({
addHasMany: function (hash, record, key, relationship) {
var type = record.constructor,
name = relationship.key,
serializedHasMany = [],
manyArray, embeddedType;
embeddedType = this.embeddedType(type, name);
if (embeddedType !== 'always') { return; }
manyArray = record.get(name);
manyArray.forEach(function (record) {
serializedHasMany.push(record.get('id'));
}, this);
hash[this.singularize(key) + '_ids'] = serializedHasMany;
}
})
})
});
For reference, you might review the JSONSerializer, which the RESTSerializer mostly inherits from. The code for what addHasMany does can be found here.
Note that, for the above snippet, the only lines that really differ are the last several. Rather than serializing embedded records, ids are pushed to the hash under the singularized key (I would have used RESTSerializer#keyForHasMany if it didn't have its own check for embedded types.
Related
The following works. I can use my component to save new addresses. When the success promise is resolved, it transitions to the same route: _this.transitionToRoute('checkout.address.index')
The issue is, the form still contains the same values of the new address. I need to form to be cleared. How do I go about that?
// Controller
import Ember from 'ember';
export default Ember.Controller.extend({
actions: {
save: function(address) {
var _this = this;
this.store.createRecord('address', address.getProperties('address1', 'address2', 'city', 'postalCode')).save().then(function(){
_this.transitionToRoute('checkout.address.index');
}, function() {
// Need this promise, so we can render errors, if any, in the form
});
}
}
});
// Template
{{address-form action='save'}}
// Component object
import Ember from 'ember';
export default Ember.Component.extend({
address: function() {
return Ember.Object.create();
}.property(),
actions: {
save: function() {
this.sendAction('action', this.get('address'));
}
}
});
// Component template
<form {{action 'save' on='submit'}}>
<p>
<label>Address:
{{input value=address.address1 placeholder='11 Mars Street'}}
</label>
{{#each error in errors.address1}}
<br />{{error.message}}
{{/each}}
</p>
<p>
{{input value=address.address2 placeholder='Bel Air 1 Village'}}
{{#each error in errors.address2}}
<br />{{error.message}}
{{/each}}
</p>
<p>
<label>City:
{{input value=address.city placeholder='Makati'}}
</label>
{{#each error in errors.city}}
<br />{{error.message}}
{{/each}}
</p>
<p>
<label>Postal code:
{{input value=address.postalCode placeholder='1209'}}
</label>
{{#each error in errors.postalCode}}
<br />{{error.message}}
{{/each}}
</p>
<input type='submit' value='Next'/>
<button {{action 'cancel'}}>Cancel</button>
</form>
I'd suggest something like this (note, edited the code a little bit for readability):
export default Ember.Controller.extend({
actions: {
save: function(address, component) {
var controller = this;
var addressProperties = address.getProperties('address1', 'address2', 'city', 'postalCode');
var newAddress = controller.store.createRecord('address', addressProperties);
function onSuccess() {
controller.transitionToRoute('checkout.address.index');
component.reset());
}
function onFailure() {
// Need this promise, so we can render errors, if any, in the form
}
newAddress.save().then(onSuccess, onFailure);
}
}
});
// Component object
import Ember from 'ember';
export default Ember.Component.extend({
address: function() {
return Ember.Object.create();
}.property(),
reset: function() {
this.set('address', Ember.Object.create());
},
actions: {
save: function() {
var component = this;
component.sendAction('action', component.get('address'), component);
}
}
});
I need a little help about ember-data record creation.
My app is a basic one, with books and reviews, based on Code School Tutorial
The app is using the RESTadapter
There is a form on the book page to write and send a review
<div class="row">
<div class="col-sm-09">
<h4>Reviews</h4>
{{#each review in reviews}}
<div class="col-sm-3">
<p>{{review.text}}</p>
<p class="text-info">{{review.reviewedDate}}</p>
</div>
{{else}}
<p class="text-muted">No Reviews Yet. Be the first to write one!</p>
{{/each}}
</div>
<div class="col-sm-3">
<h4>Review</h4>
{{textarea valueBinding='review.text'}}
<br>
<button {{action 'createReview'}} class='btn-primary'>Review</button>
{{#if review.text}}
<br><h4>Preview</h4>
<p class="text-muted">{{review.text}}</p>
{{/if}}
</div>
My Controller
App.BookController = Ember.ObjectController.extend({
logoAvailable: 'images/instock.jpg',
logoUnavailable: 'images/unavailable.jpg',
logoAvailability: function () {
if (this.get('isAvailable'))
return this.logoAvailable;
else
return this.logoUnavailable;
}.property('isAvailable'),
review: function () {
return this.store.createRecord('review', {
book: this.get('model')
});
}.property('model'),
actions: {
createReview: function () {
var controller = this;
this.get('review').save().then(function (review) {
controller.set('text', '');
controller.get('model.reviews').addObject(review);
}, function (error) {
console.error(error);
controller.set('text', '');
review.unloadRecord();
});
}
}
});
My Models:
App.Book = DS.Model.extend({
title: DS.attr('string'),
isbn: DS.attr('string'),
summary: DS.attr('string'),
isAvailable: DS.attr('boolean'),
featured: DS.attr('boolean'),
author: DS.belongsTo('author', {async: true}),
reviews: DS.hasMany('review', {async: true}),
// computed properties
image: function () {
return 'images/books/' + this.get('id') + '.jpg';
}.property('id'),
isNotAvailable: function () {
return !this.get('isAvailable');
}.property('isAvailable')
});
App.Review = DS.Model.extend({
text: DS.attr('string'),
reviewedAt: DS.attr('date'),
book: DS.belongsTo('book'),
//Computed Properties
reviewedDate: function () {
return moment(this.get('reviewedAt')).format('LLL');
}.property('reviewedAt')
});
The controller creates a new "review" object for each opened book, and will eventualy save it when the button is pressed.
This is working, but
My uncommited record is shown in the Book reviews list, even before submiting it, (as if it was a live preview), and before the call of controller.get('model.reviews').addObject(review);
What's wrong with my code, and how can I only display commited records (sucessful save() call).
You can filter the reviews by the isNew flag.
in your BookController you could do something like this:
committedReviews: function () {
return this.get('model.reviews').filter(function(review) {
return !review.get('isNew');
});
}.property('model.reviews')
I am building a small educational app where the structure is as follows -
Exam hasMany Subjects and Subjects hasMany courses.
My model relationships -
App.Exam = DS.Model.extend({
name: DS.attr('string'),
description: DS.attr('string'),
subjects : DS.hasMany('subject',{ async: true }),
});
App.Subject = DS.Model.extend({
name: DS.attr('string'),
description:DS.attr('string'),
exam: DS.belongsTo('exam', { async: true })
});
Initially I display all the exams and on exam/1 I display all the subjects belonging to that exam.
I am having trouble in filtering the subjects via checkbox
Here is the Demo
Not able to figure out how to do it. Can someone suggest me how to approach this ?
Basically on click of physics checkbox only physics subject should be displayed in the view.
I am basically using the MultipleSelectionFilterComponent from my blog post mentioned in the comments. This component will take care of managing the selection of the different checkboxes and send a filter function to the controller. There you can use the function to filter the data. You can refer to my post for more details.
Here is the working demo.
The code looks like
App.ExamsExamRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('exam', params.exam_id).then(function (exam) {
console.log("found", exam);
return exam;
});
},
setupController: function(controller, model) {
this._super(controller, model);
controller.set('filteredContent', model.get('data.subjects'));
}
});
App.ExamsExamController = Em.ObjectController.extend({
filteredContent: [],
actions: {
filterBySubject: function(filterFn) {
this.set('filteredContent',
this.get('model.data.subjects').filter(filterFn));
}
}
});
<ul class="list-group">
{{#multiple-selection-filter filter-key-path="name"
action="filterBySubject"}}
{{#each subject in model.subjects}}
<li class="">
<label>
<input type="checkbox" class="item-checkbox" {{bind-attr
value=subject.name}}/>
{{subject.name}}
</label>
</li>
{{/each}}
{{/multiple-selection-filter}}
</ul>
<h3>Subjects Details - </h3>
{{#each subject in filteredContent}}
<div class="col-md-3 well">
{{subject.name}}
<br>{{subject.description}}
</div>
{{/each}}
Here is the code to the MultipleSelectionFilterComponent.
App.MultipleSelectionFilterComponent = Em.Component.extend({
selectedItems: [],
click: function(event) {
var el = Em.$(event.target);
var filterFn;
if(el.is('input[type=checkbox]')) {
if(el.is(':checked')) {
this.get('selectedItems').pushObject(el.val());
} else {
this.get('selectedItems').removeObject(el.val());
}
}
if(this.get('selectedItems.length')) {
filterFn = function(item) {
return this.get('selectedItems')
.contains(Em.get(item, this.get('filter-key-path')));
}.bind(this);
} else {
filterFn = function() {return true;};
}
this.sendAction('action', filterFn);
}
});
that is SIMPLY not possible in ember.js
yeahhh it sux
I am still learning Ember and have encountered a problem with keep consistent behavior when showing/hidding certain elements in the template. I have the following controller
import Ember from 'ember';
export default Ember.ArrayController.extend({
actions: {
newCalendar: function() {
this.set('showCalendarForm', true);
},
hideNewCalendar: function() {
this.set('showCalendarForm', false);
this.set('calendarName', '');
},
showCalendarForm: false,
createCalendar: function() {
var name = this.get('calendarName');
if (!name) { return; }
if (!name.trim()) { return; }
var calendar = this.store.createRecord('calendar', {
name: name
});
this.set('calendarName', '');
this.set('showCalendarForm', false);
calendar.save();
},
}
});
and a template
{{#if showCalendarForm}}
<div class="input-group">
{{input
class = 'form-control'
id = 'newCalendar'
type = 'text'
placeholder = 'New calendar'
value = calendarName
autofocus = 'autofocus'
focus-out = 'hideNewCalendar'
action = 'createCalendar'
}}
</div>
{{else}}
<button class="btn btn-sm btn-primary" {{action "newCalendar"}}>New</button>
{{/if}}
Problem is that the input field only gets autofocused the first time I click the button, and on subsequent clicks, the input gets displayed, but not autofocused. How can i fix this?
I am using EmberJs version 1.4.
When I click on one of the links I would expect the URL to include the id of the selected widget but nothing appears and when I look at the params parameter in the route model hook it has no properties and I would expect the id to be one of its properties so could someone help me to understand what am I missing?
In other words I would expect the URL to become awesome.html#/widgets/5 but it always is awesome.html#/widgets
Thank you!
This is my ember code:
window.Awesome = Ember.Application.create();
Awesome.Router.map(function() {
this.resource("awesome", {path: "/"}, function(){
this.route('login');
});
this.resource("widgets", function () {
this.resource('widget', { path: '/:widgetId' }, function () {
this.route('general', { path: 'info' });
this.route('configuration');
this.route('operations');
})
});
});
Awesome.WidgetsRoute = Awesome.AuthenticationRoute.extend({
model: function(){
//TODO: Call a service to get the model.
return { widgets: [{ widgetId: 1, widgetName: "Great Widget" }, { widgetId: 2, widgetName: "Fantastic Widget" }, { widgetId: 3, widgetName: "Brutal Widget" }] };
}
});
Awesome.WidgetIndexRoute = Awesome.AuthenticationRoute.extend({
model: function (params) {
var receivedWidgetId = params.widgetId;
return { widgetName: "Hardcoded Widget", widgetId: receivedWidgetId };
}
});
These are the templates:
<script type="text/x-handlebars" data-template-name="widgets">
<section class="span3 left-section">
<div class="btn-group-vertical btn-group-justified registration-actions-menu">
<button id="createNewWidget" class="btn btn-link">Create New Widget</button>
<button id="joinWidgetTeam" class="btn btn-link">Join Widget Team</button>
</div>
<div class="registered-widgets-menu">
<div class="btn-group-vertical">
{{#each widget in widgets}}
{{#link-to 'widget' widget class="btn btn-link"}}{{widget.widgetName}}{{/link-to}}
{{/each}}
</div>
</div>
</section>
<section class="span8">
{{outlet}}
</section>
</script>
<script type="text/x-handlebars" data-template-name="widget">
<div id="widgetOptions">
<!-- TODO: Change the anchors for handlebars link-to helpers. -->
<h1>{{widgetName}}</h1> <h5>{{widgetId}}</h5>
<ul id="widgetNavigation">
<li>Widget Info</li>
<li>Widget Configuration</li>
<li>Widget Operations</li>
</ul>
</div>
<div id="widgetContent">
<!-- TODO: Design some awesome widget content. -->
Some awesome widget content
</div>
</script>
I also have an authentication route from which the other routes inherit. Even though I don't believe it has something to do with the issue I'll include just in case I am wrong.
Awesome.AuthenticationRoute = Ember.Route.extend({
beforeModel: function(transition){
if(!Awesome.get('loggedUser')){
this.redirectToLogin(transition);
}
},
redirectToLogin: function(transition) {
var loginController = this.controllerFor('awesome.login');
loginController.set('attemptedTransition', transition);
this.transitionTo('awesome.login');
}
});
It looks like it's totally working to me, when you click on one of the widgets
http://emberjs.jsbin.com/mohex/1
Additionally it looks like you're mixing up the WidgetIndexRoute and WidgetRoute. The widget resource should be displayed like this (though this is unrelated to the issue you're describing)
Awesome.WidgetRoute = Awesome.AuthenticationRoute.extend({
model: function (params) {
var receivedWidgetId = params.widgetId;
return { widgetName: "Hardcoded Widget", widgetId: receivedWidgetId };
}
});