ember - combining data for two models into single result for power sort - ember.js

I'm getting data from two models in one of my routes using RSVP hash, and then trying to combine those results in my controller so that they can be used in a select for power sort. However something doesn't seem to be working.
My route looks like this:
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return Ember.RSVP.hash({
newBook : this.store.createRecord('book'),
authors : this.store.findAll('author'),
publishing_houses : this.store.findAll('publishing-house')
});
},
setupController(controller, model) {
this._super(...arguments);
Ember.set(controller, 'authors', model.authors);
Ember.set(controller, 'publishing_houses', model.publishing_houses);
},
actions: {
save() {
this.modelFor(this.routeName).get('newBook').save();
}
}
});
My template looks like this:
<form {{action "save" on="submit"}}>
{{input value=model.newBook.title placeholder="Title"}}<br>
{{input value=model.newBook.price placeholder="Price"}}<br>
{{#power-select class="select"
selected=model.newBook.author
options=model.authors
onchange=(action (mut model.newBook.author)) as |author|}}
{{author.name}}
{{/power-select}}
{{#power-select class="select"
selected=model.newBook.publisher
options=model.publishers
onchange=(action (mut model.newBook.publisher)) as |publisher|}}
{{publisher.name}}
{{/power-select}}
<input type="submit" value="Save">
</form>
and my controller, which I think is the problem looks like this:
import Ember from 'ember';
export default Ember.Controller.extend({
publishers: function() {
var authors = this.get("authors");
var publishingHouses = this.get("publishing_houses");
return authors.concat(publishingHouses);
}
});
I'm still figuring out how to use controllers. Am I accessing the model data correctly in the controller? Also is this the proper way to create a property to be used in a template?

In setupController hook, you don't need to explicitly set authors and publishing_houses since it will be set by default through super function call.
In your controller, you can try accessing it like this.get("model.authors") and in the same way for other properties publishing_houses, newBook
To access RSVP return model from route, you should access it directly without get function.
`this.modelFor(this.routeName).newBook.save()
For concatenation and other stuff you refer http://emberjs.com/api/classes/Ember.Enumerable.html
reduce might suite your needs.

Related

How can I get the Id from the URL in an Ember Route?

I have a two panel display where I show a list of items on the left, then detail about a selected item on the right (using nested route).
My route looks like this:
Router.map(function() {
this.route('authenticated', {path: '/'}, function() {
this.route('bakery', function() {
this.route('cakes', function() {
this.route('detail', { path: '/:id' });
});
});
});
});
My URL looks like
http://localhost:3333/bakery/cakes/e34b3ce3
When an item is selected, it is set to "active" (temporary property on the model - default is false) and highlighted via an action on the bakery/cakes route. The detail is then shown on the right.
If I refresh the page, the item is no longer highlighted - but the detail is still shown.
Ideally I'd like to use the afterModel() hook in the bakery/cakes route to set that item back to active again, but I've been unable to get the Id to be able to do this.
I've tried the following:
Accepted answer from here
This question doesn't help me as the model will have reloaded and my "active" property will be false so I can't just select where active = true.
I'm using ember 2.5.0. Thanks.
I wonder if it'd be better to architect your structure a bit differently (from what I assume you're doing).
First, load all of the cakes on the authenticated.bakery.cakes route;
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return this.store.findAll('cakes');
}
});
Secondly, show your "full width" cakes list on the authenticated.bakery.cakes.index template (the cake models will be inherited);
<div class="full width cake list">
{{#each model as |cake|}}
{{#link-to "authenticated.bakery.cakes.detail" cake.id}}
{{!-- cake photo --}}
{{cake.name}}
{{!-- other cake details... --}}
{{/link-to}}
{{/each}}
</div>
Next, on your authenticated.bakery.cakes.detail route, load the specific cake along with the list of cakes;
import Ember from 'ember';
export default Ember.Route.extend({
model(params) {
let cakes= this.modelFor('authenticated.bakery.cakes');
return Ember.RSVP.hash({
cakes: cakes,
cake: cakes.findBy('id', params.id)
});
}
});
Finally on the authenticated.bakery.cakes.detail template, show the condensed/narrow list of cakes along with the specific cake details. And using {{link-to}}, the 'active' class will automatically be applied;
<div class="narrow width cake list">
{{#each model.cakes as |cake|}}
{{#link-to "authenticated.bakery.cakes.detail" cake.id}}
{{cake.name}}
{{/link-to}}
{{/each}}
</div>
<div class="cake details">
{{model.cake.name}}
</div>
As another option, change your model active flag on the proper route hooks should work. (I think anyway, haven't done this myself.) On your authenticated.bakery.cakes.detail route;
import Ember from 'ember';
export default Ember.Route.extend({
model(params) {
return this.store.findRecord('cakes', params.id);
},
afterModel(cake) {
cake.set('active', true);
},
actions: {
willTransition() {
this.get('controller.model').set('active', false);
}
}
});

How does one access model data in a router/controller?

Bear with me please, I'm new.
Been breaking my head over this problem and sort of here as last resort. It's about how to access a model's data when that route loads. For instance, when /meals/2 loads, I want a function to run that sets the background of the document using that model's background-image string property. Or when /meals loads, the a function that uses a property of the collection's first item.
Any help on 'the ember way' to do this would be much appreciated.
Menu.hbs
{{#each meal in model}}
<span {{action 'mealSelected' meal.image_large}}>
{{#link-to 'menu.meal' meal tagName="li" class="meal-block" href="view.href"}}
[...]
{{/link-to}}
</span>
{{/each}}
<div id="meal-info-wrapper">
{{outlet}}
</div>
Model:
export default DS.Model.extend({
name: DS.attr('string'),
image: DS.attr('string')
});
Router.js
export default Router.map(function() {
this.route('about');
this.route('menu', { path: '/' }, function() {
this.route('meal', { path: '/meal/:id/:slug' });
});
});
routes/menu.js
export default Ember.Route.extend({
model: function() {
return this.store.find('menu');
},
afterModel: function() {
Ember.$(document).anystretch('temp-images/bg-1.png');
}
});
What I want to do in routes/menu.js for instance would be to have that image url be supplied by the model.
afterModel will run only once the model has been resolved, and the model is passed as an argument. So, based on my understanding of your app, you can adjust your routes/menu example to:
export default Ember.Route.extend({
model: function() {
return this.store.find('menu');
},
afterModel: function(model) {
Ember.$(document).anystretch(model.get('firstObject.image'));
}
});
Correct me if I misunderstood something, what you want to do is:
Change the background image of a DOM element based on a property found
in each Model's record.
Model loading is an async operation, you want to do the image swaping once you are sure the data is loaded. You used the afterModel hook to guarantee that, but that is not enough.
You want to modify the DOM inside your template, but you need to make sure that the template has been rendered. So, the DOM manipulation logic, instead of placing it in afterModel, it belongs to the didInsertElement event that Views have.
I suggest you use a component (its a view too), something like:
// your template
{{#each meal in model}}
{{meal-component content=meal}}
{{/each}}
// the meal-component
didInsertElement: function() {
var imgURLProperty = this.get('content.imgURLProperty');
Ember.$(document).anystretch(imgURLProperty);
}
Of course, you can't copy paste any of that. It just shows you the main mechanic of how you can modify a template based on the properties of a model.

How to find and delete a specific record in Ember

I am totally newbie in Ember. I would like to find a record in todos array then delete it. I know I have to pass a parameter to function to find it first then delete it, but I had no luck. Here are related files:
todos.hbs:
{{#each todo in model}}
<li>
<label>{{#link-to "todos.show" list}}{{todo.title}}{{/link-to}}</label>
<button type="button" class="destroy" {{action "deleteTodo"}}></button>
</li>
{{/each}}
I put deleteTodo action in toods routes.
todos routes :
export default Ember.Route.extend({
model: function() {
return this.store.find('todo');
},
deleteTodo: function() {
// Function itself
});
Thanks
Move the deleteTodo method into an action on your routes controller instead of on the Route. Then pass your current todo into your controllers deleteTodo method:
<button type="button" class="destroy" {{action "deleteTodo" todo}}></button>
Looks like your are using ember-cli so the controller would look something like
import Ember from 'ember';
export default Ember.Controller.extend({
actions: {
deleteTodo: function(todo){
todo.destroyRecord();
}
}
});

Global CRUD Ember.js

I was wondering if someone could give me brief direction. I'm making an app that I want to be able to take notes from anywhere I'm at in the app (CRUD). I'm rendering my presentations in my application controller using {{render}} but I'm not sure how to put the full crud operations there as well. This is what I have so far:
-- Presentation Controller
import Ember from 'ember';
var PresentationController = Ember.ObjectController.extend({
actions: {
edit: function () {
this.transitionToRoute('presentation.edit');
},
save: function () {
var presentation = this.get('model');
// this will tell Ember-Data to save/persist the new record
presentation.save();
// then transition to the current user
this.transitionToRoute('presentation', presentation);
},
delete: function () {
// this tells Ember-Data to delete the current user
this.get('model').deleteRecord();
this.get('model').save();
// then transition to the users route
this.transitionToRoute('presentations');
}
}
});
export default PresentationController;
-- Presentations Controller
import Ember from 'ember';
var PresentationsController = Ember.ArrayController.extend({
actions: {
sendMessage: function ( message ) {
if ( message !== '') {
console.log( message );
}
}
}
});
export default PresentationsController;
-- Model
import DS from 'ember-data';
var Presentation = DS.Model.extend({
title: DS.attr('string'),
note: DS.attr('string')
});
-- Presentations Route
import Ember from 'ember';
var PresentationsRoute = Ember.Route.extend({
model: function() {
return this.store.find('presentation');
}
});
export default PresentationsRoute;
-- Presentation Route
import Ember from 'ember';
var PresentationRoute = Ember.Route.extend({
model: function (params) {
return this.store.find('presentation', params.id);
}
});
export default PresentationRoute;
-- Application Route
import Ember from 'ember';
export default Ember.Route.extend({
model: function () {
return this.store.find('category');
},
setupController: function (controller, model) {
this._super(controller, model);
controller.set('product', this.store.find('product'));
controller.set('presentation', this.store.find('presentation'));
}
});
-- Application HBS
<section class="main-section">
<div id="main-content">
{{#link-to "presentations.create" class="create-btn expand" tagName="button"}} Add presentation {{/link-to}}
{{render 'presentations' presentation}}
{{outlet}}
</div>
</section>
-- Presentations HBS
{{#each presentation in controller}}
{{#link-to 'presentation' presentation tagName='li'}}
{{presentation.title}}
{{/link-to}}
{{/each}}
{{outlet}}
-- Presentation HBS
{{outlet}}
<div class="user-profile">
<h2>{{title}}</h2>
<p>{{note}}</p>
<div class="btn-group">
<button {{action "edit" }}>Edit</button>
<button {{action "delete" }}>Delete</button>
</div>
</div>
Basically what you're describing is a modal of sorts. It'll be accessible no matter what page (route) you're viewing, and you will be able to perform actions within this modal (creating notes, editing notes, deleting notes, etc) without leaving or affecting the current page being displayed in the background. Essentially, what this means is that you should leave the router alone, since the router is what controls the current page, and you don't want to affect that. You're not going to want to have any {{#link-to}} or transitionTo or transitionToRoute calls, nor any presentation-related routes or outlets.
Instead, you're going to have to handle everything at the controller and view level. This is where components really come in handy, as they're great for encapsulation if you use them correctly. Inside of presentations.hbs, I'd use components to represent each of the presentations:
{{#each presentation in controller}}
{{individual-presentation presentationModelBinding="presentation"}}
{{/each}}
Note that you'll need a corresponding IndividualPresentationComponent object that extends Ember.Component. Going further, inside of individual-presentation.hbs, I'd have code similar to what you have now, but with allowances for various CRUD operations:
{{#if editing}}
{{input value=presentationModel.title}}
{{textarea value=presentationModel.note}}
{{else}}
<h2>{{title}}</h2>
<p>{{note}}</p>
{{/if}}
<div class="btn-group">
{{#if editing}}
<button {{action "save" }}>Save</button>
{{else}}
<button {{action "edit" }}>Edit</button>
{{/if}}
<button {{action "delete" }}>Delete</button>
</div>
Note that the context for a component's template is the component itself, not some other controller. Similarly, actions fired inside of a component's template are direct to the component's actions hash. So your IndividualPresentationComponent will need to look like this somewhat:
IndividualPresentationComponent = Ember.Component.extend({
classNames: ['user-profile'],
actions: {
save: function () {
this.sendAction('save', this.get('presentationModel'));
this.set('editing', false);
},
edit: function () {
this.set('editing', true);
},
delete: function () {
this.sendAction('delete', this.get('presentationModel'));
}
}
});
Notice I'm using sendAction here. This is how components communicate with the outside world. To get this to work, go back your presentations.hbs and intercept the actions like so:
{{#each presentation in controller}}
{{individual-presentation presentationModelBinding="presentation"
save="savePresentation"
delete="deletePresentation"}}
{{/each}}
Here you're basically saying that if the component sends the "save" action, you want to handle it with your controller's "savePresentation" action, and if the component sends the "delete" action, you want to handle it with your controller's "deletePresentation" action. So your presentations-controller.js will need to implement those actions:
var PresentationsController = Ember.ArrayController.extend({
actions: {
savePresentation: function (presentationModel) {
...
},
deletePresentation: function (presentationModel) {
...
},
}
});
And you can delete PresentationController, since all of its functionality is now handled directly by your IndividualPresentationComponent and your PresentationsController.

Creating a new record not pulling data from template fields

I am attempting to create a new record, however none of the data from the fields is being passed automatically, as I expected Ember to (from what I've read).
My template:
<form {{action save content on="submit"}}>
{{input value=name}}
<button type="submit"}}>Next</a>
From what I've read content is an alias for model and interchanging these makes no difference.
My route:
App.CampaignsNewRoute = Ember.Route.extend({
actions: {
save: function(campaign) {
console.log(campaign.name);
}
},
model: function(controller) {
return this.store.createRecord('campaign');
}
});
And my controller:
App.CampaignsNewController = Ember.ObjectController.extend({
pageTitle: 'New Campaign Setup'
});
When I hit 'Next' it logs undefined. Logging just the campaign shows it's an Ember model, but without the name attribute. name is defined on the campaign model. Setting the input to {{input value=content.name}} places the name attribute within the model returned, but it's still undefined. Am I missing anything in this process? The EmberJS site doesn't show how to do this, from what I can find.
--
As a side note: I was originally using App.CampaignsNewController = Ember.Controller.extend as my model was returning a hash of promises, one of which is an array and Ember didn't like me using either array or object controller. I simplified it to the above to verify it wasn't that which was causing the issue. So any solution taking this into account would be wonderful.
Edit: I can access the template fields by doing this.get('controller').get('name') but surely that is not necessary? Changing my controller to a Ember.Controller.extend also stops that from working, would love to know why. Clarification on best practice here would still be wonderful!
Edit2: this.get('controller.content').get('name') works if the controller is simply an Ember.Controller as opposed to Ember.ObjectController and the template has {{input value=content.name}}. I'll work with but hopefully someone can clarify this is the correct way.
ObjectController is the way to go here. You would have it backed by one particular model, your new model, and you would add additional properties to the controller for use in the template.
Code
App.IndexRoute = Ember.Route.extend({
actions: {
save: function(campaign) {
console.log(campaign.get('color'));
}
},
model: function() {
return Ember.RSVP.hash({
record: this.store.createRecord('color'),
all: this.store.find('color')
});
},
setupController: function(controller, model){
this._super(controller, model.record);
controller.set('allColors', model.all);
}
});
App.IndexController = Em.ObjectController.extend({
});
Template
In the template any time you want to access anything on the model backing the template, you can just access it as if the model is the current scope.
{{name}}
if you want to access any of the properties that exist on the controller you would use the property name that it is on the controller.
{{allColors.length}}
Here's an example:
<form {{action save model on="submit"}}>
Color:{{input value=color}}<br/>
<button type="submit">Next</button>
</form>
<ul>
{{#each item in allColors}}
{{#unless item.isNew}}
<li>{{item.color}}</li>
{{/unless}}
{{/each}}
</ul>
One last tip, always use getters and setters ;)
Ember Data hides the properties, they don't live right on the object, so campaign.name will return undefined forever and ever. If you do campaign.get('name') you'll get a real response.
With the example: http://emberjs.jsbin.com/OxIDiVU/792/edit