Recompute arrangedContent - javascript

I'm trying to reload a model and recompute arrangedContent. The model appears to be reloading but arrangedContent is not being recomputed. Sorting data is fine, it's adding and removing data that's causing the issue.
reloadModelData: function () {
this.get('target.router').refresh();
}
My template looks like:
{{#each project in arrangedContent}}
<tr>
<td>{{project.name}}</td>
...
</tr>
{{/each}}
Edit:
routes/projects/index.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function () {
return this.store.findAll('project');
}
});
controllers/projects/index.js
import Ember from 'ember';
export default Ember.Controller.extend(Ember.SortableMixin, {
queryParams: ['sortProperties', 'sortAscending'],
sortProperties: ['name'],
actions: {
...
reloadModelData: function () {
this.get('target.router').refresh();
}
}
});
A button is what is triggering reloadModelData
<button {{action 'reloadModelData'}}>Reload</button>

Your model hook is not being executed in your action. Why? Becouse you are executing #refresh() on targer.router, which is not a current route, but the Router.
So, how can you refresh the model?
There is a convention called data-down-action-up. It supports sending actions up, to the objects that are, let's say, parents for the data to change. Possible solution would be to let your reloadModelData bubble up to the route and handle this action in the route. Then, in that action you could fetch the data again and set them on the controller:
# controller code
reloadModelData: function() {
return true;
}
And in route:
# route code
reloadModelData: function() {
this.store.find('project').then((function(_this) {
return function(projects) {
_this.get('controller').set('model', projects);
};
})(this));
}
If the result from the find will be different than it was, the model related computed properties will for sure recompute.
Here is a working demo in JSBin that compares your and mine solution.

Related

Emberjs making 2 simultaneous requests

My router.js file is like this:
this.route('cards', function() {
this.route('all');
this.route('card', {path: '/:card_id'}, function() {
this.route('edit');
});
this.route('new');
});
In all.js, I've:
model() {
return this.store.findAll('card');
}
In cards.js, I've:
beforeModel() {
this.transitionTo('cards.all');
},
model() {
return this.store.findAll('card');
}
As you can see that I'm making 2 requests which, IMO, is not necessary. So, if I remove the call from cards.js, the new.js doesn't work properly.
When I create a new card from new.js, after creation, it should go to /cards/1 and show the proper data. But, when I remove that line from cards.js, after creation of a card, it goes to /cards/1 but the data is not saved.
Link to repo: https://github.com/ghoshnirmalya/hub-client

Ember form validation

I'm kind of new to Ember and I'm on something that should be quite simple. Just trying to validate a form actually.
Using ember-forms and ember-validations.
Here is the part of the structure, I'll be quite exhaustive so you can get to the point but there is really not a lot of code:
/app
/controllers
/admin
/create-user.js
/models
/admin
/create-user.js
/routes
/admin
/create-user.js
/templates
/admin
/create-user.js
First, I'm not sure it's the good structure, especially about the model.
The model:
import DS from 'ember-data';
import EmberValidations from 'ember-validations';
export default DS.Model.extend(EmberValidations, {
entity: DS.attr()
}).reopen({
validations: {
entity: {
presence: true,
length: { minimum: 5 }
}
}
});
The controller:
import Ember from 'ember';
export default Ember.Controller.extend({
actions: {
createUser() {
console.log("create");
}
}
});
The route:
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.createRecord('admin/create-user');
}
});
The template:
<h3>Create User</h3>
<div class="row">
<div class="col-sm-6">
{{#em-form action="createUser" model=admin/create-user}}
{{em-input
label="Name / Entity"
property="entity"
placeholder="The name of the user or the entity"}}
{{/em-form}}
</div>
</div>
I know I'm missing something and I'm pretty sure it has to do with the model (I tried a lot of thing such as model=admin/create-user in the template).
EDIT: there's no error or whatever in the console, the validation is just not called.
Thx for your help!
The first thing that jumps out at me is the fact that you never, anywhere in your code, check to see if the data is valid via:
// Somewhere in your controller
this.get('isValid');
The second is that you're validations are defined on the controller rather than the model. That works great if your controller extends ObjectController (which is now deprecated) which proxies properties automatically to the model.
If you're extending Controller and you want to validate the model, you need to define them a bit differently:
validations: {
'model.entity': {
presence: true,
length: { minimum: 5 }
}
}
The third is that you're never actually passing an instance of your model to the controller via the route (even though validations should still work):
export default Ember.Route.extend({
model: function() {
// assuming you're using Ember Data
// find the model with id of 1
return this.store.find('admin/create-user', 1);
}
});

How to filter a local hasMany record set in Ember.js

I am having issues making the dom update when filtering a ul of hasMany objects. Here is what I'm trying to accomplish:
When a user clicks on one of the links it should filter down a div containing the respective elements. Here is my current code:
Route
import Ember from 'ember';
import AuthenticatedRouteMixin from 'simple-auth/mixins/authenticated-route-mixin';
export default Ember.Route.extend(AuthenticatedRouteMixin,{
model: function (params) {
return this.store.find('recipient', params.recipient_id);
},
setupController: function (controller, model) {
this._super(controller, model);
var categories = model.get('offers').map(function(offer){
var category = Ember.Object.create();
category.name = offer.get('company_industry');
category.count = model.get('offers').filterBy('company_industry', offer.get('company_industry')).get('length');
return category;
});
controller.set('categories', categories);
}
});
This sets categories to an Ember object liks so:
{name: 'Home And Beauty', count: 1}
Component: filterable-offers.js
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
filter: function(categoryName) {
return this.get('model').get('offers').filterBy("company_industry", categoryName);
}
}
});
filterable-offers.hbs
<ul>
{{#each categories as |category|}}
<li>
<a {{action "filter" category.name}}>{{category.name}} ({{category.count}})</a>
</li>
{{/each}}
</ul>
recipient.hbs
<div class="row">
<div class="col-sm-4">
{{filterable-offers categories=categories model=model}}
</div>
<div class="col-sm-8">
{{#each model.offers as |offer|}}
<div class="offer-card">
{{offer.text}}
</div>
{{/each}}
</div>
I know I'm missing some simple like observing something here, but when I click the link to filter down the elements nothing is updated in the dom. The filter function does return the result set I want but it isn't being observed by the model.offers call in the main recipient template.
Your problem is that you're returning your filtered result from your action handler. The action handler doesn't use a return value (for the most part). Instead, you need to place your filtered items somewhere where your template can access them. Since your filter selector is in a different component, this is what I would do (if you have questions about any part of this, just ask):
In your component, re-send the filter action so it bubbles to the controller:
actions: {
filter(categoryName) {
this.sendAction('filterByCategory', categoryName);
}
}
Make sure your controller can receive the action by subscribing to it in the template:
{{filterable-offers categories=categories filterByCategory='filterByCategory'}}
Add a handler for the filterByCategory action in your controller:
actions: {
filterByCategory(categoryName) {
// We'll use this in the next step
this.set('filterCategory', categoryName);
}
}
Set up a computed property that will automatically filter your items based on the filter category. We have our filterCategory property on the controller, so let's use that:
filteredOffers: Ember.computed('model.offers.#each.company_industry', 'filterCategory', {
get() {
const offers = this.get('model.offers');
const filterCategory = this.get('filterCategory');
// If there's no category to filter by, return all of the offers
if (filterCategory) {
return offers.filterBy('company_industry', filterCategory);
} else {
return offers;
}
}
})
Finally, instead of using model.offers in your template, use filteredOffers
{{#each filteredOffers as |offer|}} ... {{/each}}
That answer might be a tad more in-depth than you want, but hopefully it helps. The major sticking point you were having is that you needed some way to tell your controller to use a filtered set of offers instead of the original set. This is just one of many ways to accomplish that.

Dynamic segment value

Just getting started with Ember and have a question about the ember way to handle a common pattern.
I have a have the following router.js:
export default Router.map(function() {
this.resource('posts', function(){
this.route('post', { path: "/:title" });
this.route('new');
});
});
I'm wondering how to use the value of the post title as the dynamic segment so that post urls show up as /posts/my-post-title-here
I'm confused as to which model this is being looked up on or if there is an "ember way" to handle this common patter (besides using the posts_id for the dynamic segment).
All my posts are defined in routes/posts.js, so I thought I simply needed to lookup the values in this route inside of my routes/post.js route, like this:
export default Ember.Route.extend({
model: function(params) {
var posts = this.modelFor('posts')
return posts.findBy('title', params.title);
}
});
I'm seeing the /posts/:title route in my Ember inspector, but in the browser, the links are all undefined ( /posts/undefined ).
{{#each model as |post|}}
{{#link-to "posts.post" model }}
<li>{{post.title}}</li>
{{/link-to}}
{{/each}}
I'd love any advice about the proper way to handle this situation or explanations about how Ember looks up values for nested routes.
You need to setup a serializer on your routes/post.js, like this:
// routes/post.js
export default Ember.Route.extend({
model: function(params) {
var posts = this.modelFor('posts')
return posts.findBy('title', params.title);
},
serialize: function(model) {
return { post_slug: model.get('title') };
}
});
See Dynamic Segments

EmberJS: How to load multiple models on the same route?

While I am not new to web development, I am quite new to to client-side MVC frameworks. I did some research and decided to give it a go with EmberJS. I went through the TodoMVC guide and it made sense to me...
I have setup a very basic app; index route, two models and one template. I have a server-side php script running that returns some db rows.
One thing that is very confusing me is how to load multiple models on the same route. I have read some information about using a setupController but I am still unclear. In my template I have two tables that I am trying to load with unrelated db rows. In a more traditional web app I would have just issued to sql statements and looped over them to fill the rows. I am having difficulty translating this concept to EmberJS.
How do I load multiple models of unrelated data on the same route?
I am using the latest Ember and Ember Data libs.
Update
although the first answer gives a method for handling it, the second answer explains when it's appropriate and the different methods for when it isn't appropriate.
BEWARE:
You want to be careful about whether or not returning multiple models in your model hook is appropriate. Ask yourself this simple question:
Does my route load dynamic data based on the url using a slug :id? i.e.
this.resource('foo', {path: ':id'});
If you answered yes
Do not attempt to load multiple models from the model hook in that route!!! The reason lies in the way Ember handles linking to routes. If you provide a model when linking to that route ({{link-to 'foo' model}}, transitionTo('foo', model)) it will skip the model hook and use the supplied model. This is probably problematic since you expected multiple models, but only one model would be delivered. Here's an alternative:
Do it in setupController/afterModel
App.IndexRoute = Ember.Route.extend({
model: function(params) {
return $.getJSON('/books/' + params.id);
},
setupController: function(controller, model){
this._super(controller,model);
controller.set('model2', {bird:'is the word'});
}
});
Example: http://emberjs.jsbin.com/cibujahuju/1/edit
If you need it to block the transition (like the model hook does) return a promise from the afterModel hook. You will need to manually keep track of the results from that hook and hook them up to your controller.
App.IndexRoute = Ember.Route.extend({
model: function(params) {
return $.getJSON('/books/' + params.id);
},
afterModel: function(){
var self = this;
return $.getJSON('/authors').then(function(result){
self.set('authors', result);
});
},
setupController: function(controller, model){
this._super(controller,model);
controller.set('authors', this.get('authors'));
}
});
Example: http://emberjs.jsbin.com/diqotehomu/1/edit
If you answered no
Go ahead, let's return multiple models from the route's model hook:
App.IndexRoute = Ember.Route.extend({
model: function() {
return {
model1: ['red', 'yellow', 'blue'],
model2: ['green', 'purple', 'white']
};
}
});
Example: http://emberjs.jsbin.com/tuvozuwa/1/edit
If it's something that needs to be waited on (such as a call to the server, some sort of promise)
App.IndexRoute = Ember.Route.extend({
model: function() {
return Ember.RSVP.hash({
model1: promise1,
model2: promise2
});
}
});
Example: http://emberjs.jsbin.com/xucepamezu/1/edit
In the case of Ember Data
App.IndexRoute = Ember.Route.extend({
var store = this.store;
model: function() {
return Ember.RSVP.hash({
cats: store.find('cat'),
dogs: store.find('dog')
});
}
});
Example: http://emberjs.jsbin.com/pekohijaku/1/edit
If one is a promise, and the other isn't, it's all good, RSVP will gladly just use that value
App.IndexRoute = Ember.Route.extend({
var store = this.store;
model: function() {
return Ember.RSVP.hash({
cats: store.find('cat'),
dogs: ['pluto', 'mickey']
});
}
});
Example: http://emberjs.jsbin.com/coxexubuwi/1/edit
Mix and match and have fun!
App.IndexRoute = Ember.Route.extend({
var store = this.store;
model: function() {
return Ember.RSVP.hash({
cats: store.find('cat'),
dogs: Ember.RSVP.Promise.cast(['pluto', 'mickey']),
weather: $.getJSON('weather')
});
},
setupController: function(controller, model){
this._super(controller, model);
controller.set('favoritePuppy', model.dogs[0]);
}
});
Example: http://emberjs.jsbin.com/joraruxuca/1/edit
NOTE: for Ember 3.16+ apps, here is the same code, but with updated syntax / patterns: https://stackoverflow.com/a/62500918/356849
The below is for Ember < 3.16, even though the code would work as 3.16+ as fully backwards compatible, but it's not always fun to write older code.
You can use the Ember.RSVP.hash to load several models:
app/routes/index.js
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return Ember.RSVP.hash({
people: this.store.findAll('person'),
companies: this.store.findAll('company')
});
},
setupController(controller, model) {
this._super(...arguments);
Ember.set(controller, 'people', model.people);
Ember.set(controller, 'companies', model.companies);
}
});
And in your template you can refer to people and companies to get the loaded data:
app/templates/index.js
<h2>People:</h2>
<ul>
{{#each people as |person|}}
<li>{{person.name}}</li>
{{/each}}
</ul>
<h2>Companies:</h2>
<ul>
{{#each companies as |company|}}
<li>{{company.name}}</li>
{{/each}}
</ul>
This is a Twiddle with this sample: https://ember-twiddle.com/c88ce3440ab6201b8d58
Taking the accepted answer, and updating it for Ember 3.16+
app/routes/index.js
import Route from '#ember/routing/route';
import { inject as service } from '#ember/service';
export default class IndexRoute extends Route {
#service store;
async model() {
let [people, companies] = await Promise.all([
this.store.findAll('person'),
this.store.findAll('company'),
]);
return { people, companies };
}
}
Note, it's recommended to not use setupController to setup aliases, as it obfuscates where data is coming from and how it flows from route to template.
So in your template, you can do:
<h2>People:</h2>
<ul>
{{#each #model.people as |person|}}
<li>{{person.name}}</li>
{{/each}}
</ul>
<h2>Companies:</h2>
<ul>
{{#each #model.companies as |company|}}
<li>{{company.name}}</li>
{{/each}}
</ul>
I use something like the answer that Marcio provided but it looks something like this:
var products = Ember.$.ajax({
url: api + 'companies/' + id +'/products',
dataType: 'jsonp',
type: 'POST'
}).then(function(data) {
return data;
});
var clients = Ember.$.ajax({
url: api + 'clients',
dataType: 'jsonp',
type: 'POST'
}).then(function(data) {
return data;
});
var updates = Ember.$.ajax({
url: api + 'companies/' + id + '/updates',
dataType: 'jsonp',
type: 'POST'
}).then(function(data) {
return data;
});
var promises = {
products: products,
clients: clients,
updates: updates
};
return Ember.RSVP.hash(promises).then(function(data) {
return data;
});
If you use Ember Data, it gets even simpler for unrelated models:
import Ember from 'ember';
import DS from 'ember-data';
export default Ember.Route.extend({
setupController: function(controller, model) {
this._super(controller,model);
var model2 = DS.PromiseArray.create({
promise: this.store.find('model2')
});
model2.then(function() {
controller.set('model2', model2)
});
}
});
If you only want to retrieve an object's property for model2, use DS.PromiseObject instead of DS.PromiseArray:
import Ember from 'ember';
import DS from 'ember-data';
export default Ember.Route.extend({
setupController: function(controller, model) {
this._super(controller,model);
var model2 = DS.PromiseObject.create({
promise: this.store.find('model2')
});
model2.then(function() {
controller.set('model2', model2.get('value'))
});
}
});
The latest version of JSON-API as implemented in Ember Data v1.13 supports bundling of different resources in the same request very well, if you don't mind modifying your API endpoints.
In my case, I have a session endpoint. The session relates to a user record, and the user record relates to various models that I always want loaded at all times. It's pretty nice for it all to come in with the one request.
One caveat per the spec is that all of the entities you return should be linked somehow to the primary entity being received. I believe that ember-data will only traverse the explicit relationships when normalizing the JSON.
For other cases, I'm now electing to defer loading of additional models until the page is already loaded, i.e. for separate panels of data or whatever, so at least the page is rendered as quickly as possible. Doing this there's some loss/change with the "automatic" error loading state to be considered.

Categories