Computed property in controller based on route's model in EmberJS - javascript

I want to implement computed property in controller that changes when data in route's model changed.
Route:
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return new Ember.RSVP.hash({
ingredients: this.store.findAll('ingredient'),
recipes: this.store.peekAll('recipe')
});
},
setupController: function(controller, modelHash) {
controller.setProperties(modelHash);
}
});
Controller:
import Ember from 'ember';
export default Ember.Controller.extend({
pageNumber: 0,
pageSize: 16,
pages: function() {
var pages = [];
if (this.model != null) {
var content = this.model.recipes;
while (content.length > 0) {
pages.push(content.splice(0, this.get("pageSize")));
}
}
return pages;
}.property('model.recipes.#each', 'pageSize'),
recipesOnPage: function() {
return this.get('pages')[this.get('pageNumber')];
}.property('pages', 'pageNumber')
});
This code produce no error, but doesn't work - "pages" always empty. And "pages" property doesn't recomputed on model changing. What am I doing wrong? And how to achieve desired result?
P.S. Ember version - 1.13.

Since you have modified setupController hook, your controller has properties ingredients and recipes, but has no model property.
So your computed property should be:
pages: function() {
// avoid using model here
// use this.get('recipes') instead of this.model.recipes
}.property('recipes.[]', 'pageSize')
SetupController hook guides link.

Please try:
import Ember from 'ember';
export default Ember.Controller.extend({
pageNumber: 0,
pageSize: 16,
pages: function() {
var pages = [];
var model = this.get('model');
if (model != null) {
var content = model.get('recipes');
while (content.length > 0) {
pages.push(content.splice(0, this.get("pageSize")));
}
}
return pages;
}.property('model.recipes.#each', 'pageSize'),
recipesOnPage: function() {
return this.get('pages')[this.get('pageNumber')];
}.property('pages', 'pageNumber')
});

Related

How to connect to Graphql server using Ember-Apollo?

I am working with a full stack GraqlQL based application. The server is working fine and now I need to try out the first queries and mutations on the client side. For some reason, the "monitoring" route, and everything that follows it, is not displayed. Below I will show the files that I have edited or created.
items.graphql:
query {
items {
_id
name
}
}
environment.js:
'use strict';
module.exports = function(environment) {
let ENV = {
apollo: {
apiURL: 'http://localhost:5000/graphql'
},
modulePrefix: 'client',
environment,
rootURL: '/',
locationType: 'auto',
EmberENV: {
FEATURES: {
//
},
EXTEND_PROTOTYPES: {
Date: false
}
},
APP: {
//
}
};
if (environment === 'development') {
//
}
if (environment === 'test') {
ENV.locationType = 'none';
ENV.APP.LOG_ACTIVE_GENERATION = false;
ENV.APP.LOG_VIEW_LOOKUPS = false;
ENV.APP.rootElement = '#ember-testing';
ENV.APP.autoboot = false;
}
if (environment === 'production') {
//
}
return ENV;
};
monitoring.js (route):
import Route from '#ember/routing/route';
import { queryManager } from 'ember-apollo-client';
import query from 'client/gql/items.graphql';
export default Route.extend({
apollo: queryManager(),
model() {
return this.apollo.watchQuery({ query }, 'items');
}
});
monitoring.hbs:
<h3>Monitoring</h3>
<div>
{{#each model as |item|}}
<h3>{{item.name}}</h3>
{{/each}}
</div>
{{outlet}}
Thank you for attention!
I see this error:
Uncaught (in promise) Error: fetch is not defined - maybe your browser targets are not covering everything you need?
The solution is to fix two things.
First is to put this in ember-cli-build.js:
'ember-fetch': {
preferNative: true
}
And fix the route file:
import Route from '#ember/routing/route';
import { queryManager } from 'ember-apollo-client';
import query from 'client/gql/queries/items.graphql';
export default Route.extend({
apollo: queryManager(),
async model() {
let queryResults = await this.apollo.watchQuery({ query }, 'items');
return Object.values(queryResults);
}
});

Ember display model which has relationship in one component

I want to expose my models to one component and show in one table. I want this to be modular in a way that I can use this component to other models.
The way I have done the attributes which have certain relationship do not show up. The problem that I found out is because by the time I grab it, the promise has not been resolved and I am not grabbing the attributes using {{ember-data}}. I cant figure out one way of doing that... I'm really new to ember and its' been a problem for me...
UPDATE
Before reading all the description below, the thing is that if I can convert the model to one array it would be enough, I guess. So, I could do something like this:
{#each model as |item|}}
<tr>
{{#each item as |column index|}}
<td>{{column.[index]}} </td>
{{/each}}
</tr>
{{/each}}
END OF UPDATE
I have the following two models
// #models/inventory-summary.js
export default DS.Model.extend({
name: DS.attr('string'),
total: DS.attr('number'),
projects: DS.hasMany('inventoryProject'), //I WANT TO SHOW THIS GUY HERE
});
// #models/inventory-project.js
export default DS.Model.extend({
name: DS.attr('string'), // IN PARTICULAR THIS ONE
summary: DS.hasMany('inventorySummary'),
});
Templates:
// #templates/inventory-summary.js
// here I'm sending the model to the component and mapping the attribute to something readable
{{model-table model=inventorySearchResult columnMap=columnMap}}
// #templates/components/model-table.hbs
// here is where I show the value of the model
{{#each model_table.values as |item|}}
{{getItemAt item index}}
{{/each}}
My helper
export function getItemAt(params/*, hash*/) {
return params[0][params[1]];
}
And in my route I'm doing:
// #routes/inventory-summary.js
model(params) {
let query_params = {page_size: 100};
if (params.page !== undefined && params.page !== null) {
query_params['page'] = params.page;
}
return Ember.RSVP.hash({
inventoryProject: this.get('store').findAll('inventoryProject'),
inventorySummary: this.get('store').query('inventorySummary', query_params),
});
},
setupController(controller, models) {
this._super(...arguments);
controller.set('projects', models.inventoryProject);
controller.set('inventorySearchResult', models.inventorySummary);
let columnMap = [
['name', 'Name ID',],
['total', 'Total'],
['projects', 'Project', {'property': 'name', 'has_many': true}]
];
controller.set('columnMap', columnMap);
},
Finally this is the part of the code which is really messed up which is where I'm passing to the template the values I'm trying to show
// #components/model-table.js
getValueForColumn(values, params) {
if (values.length > 0) {
if (typeof values[0] === "object") {
return this.getResolved(values, params);
} else {
return values;
}
}
return values;
},
getResolved(promise, params) {
let result = [];
promise.forEach( (data) => {
data.then((resolved) => {
let tmp = "";
resolved.forEach((item) => {
console.log(item.get(property)); // THIS GUY SHOWS UP LATER
tmp += item.get(property) + ", ";
});
result.push(tmp);
});
});
return result; // THIS GUY RETURN AS EMPTY!
},
didReceiveAttrs() {
this._super(...arguments);
let model = this.get('model');
let columnMap = this.get('columnMap');
for (var i = 0; i < columnMap.length; i++) {
attributes.push(columnMap[i][0]);
columns.push({'name': columnMap[i][1], 'checked': true});
values.push(this.getValueForColumn(model.getEach(columnMap[i][0]), columnMap[i][2])); //WRONG HERE
}
this.set('model_table', {});
let model_table = this.get('model_table');
Ember.set(model_table, 'values', values);
},
I am able to show in the template if I start doing a bunch of if's in the {{template}}, because I believe the template does some kind of binding I am not doing and it resolves later, but it was really ugly and nasty. I wanted to do something cleaner... that's why I'm posting here.
move your api calls to afterModel.
and
try something like this where you wait for promise to resolve and set it.
afterModel: function () {
var rsvp = Ember.RSVP.hash({
obj1: Ember.$.getJSON("/api/obj1"),
obj2: Ember.$.getJSON("/api/obj2"),
});
var ret = rsvp.then(function (resolve) {
console.log("Something" + resolve.obj1);
self.set('obj2', resolve.obj2);
});
return ret;
},
setupController: function (controller, model) {
this._super(controller, model);
controller.set('obj1', this.get('obj1'));
controller.set('obj2, this.get('obj2'));
}

Observing changes to an ItemController in Array Controller

I've been learning ember by recreating the TodoMVC in EmberCli. Ive recreated all the functionality, but I ran into an issue and I was hoping someone could shed some light on the situation.
It seems that my Todos ArrayController will observe and fire functions when properties in my model change but not when values in my Todo ObjectController change.
I moved isEditing into the Model so that when I call editTodo canToggle fires. But I would prefer to store that value in my controller and not the model.
I set up a test with with a propTest boolean. on a button click I fire propTogglebut todoPropToggle doesn't respond to the change. The only time that it does ever fire is on initialization.
Any insight would be super helpful.
TODOS CONTROLLER
import Ember from 'ember';
export default Ember.ArrayController.extend({
actions: {
createTodo: function() {
var title = this.get('newTitle');
if (!title.trim()) {
return;
}
var todo = this.store.createRecord('todo', {
title: title,
isCompleted: false,
isEditing:false
});
this.set('newTitle', '');
todo.save();
}
},
canToggle: function() {
var isEditing = this.isAny('isEditing');
return this.get('length') && !isEditing;
}.property('length','#each.isEditing'),
todoPropToggle: function() {
var hasPropTest = this.isAny('propTest');
return hasPropTest;
}.property('#each.propTest')
});
TODO CONTROLLER
import Ember from 'ember';
export default Ember.ObjectController.extend({
actions: {
editTodo: function() {
var todo = this.get('model');
todo.set('isEditing', true);
},
removeTodo: function() {
var todo = this.get('model');
todo.deleteRecord();
todo.save();
},
acceptChanges: function() {
var todo = this.get('model');
todo.set('isEditing', false);
if (Ember.isEmpty(this.get('model.title'))) {
this.send('removeTodo');
}
else {
this.get('model').save();
}
},
propToggle:function(){
this.set('propTest',!this.get('propTest'));
}
},
propTest:true,
isCompleted: function(key, value) {
var model = this.get('model');
if (value === undefined) {
return model.get('isCompleted');
}
else {
model.set('isCompleted', value);
model.save();
return value;
}
}.property('model.isCompleted')
});
How about an alternative approach? We could toggle 'canToggle' in arrayController directly from the object controller by either using parentController or specifying needs. Avoids having to observer all the itemControllers which should be more efficient too.
TODOS CONTROLLER:
import Ember from 'ember';
export default
Ember.ArrayController.extend({
/**
* references the todo model that is currently being edited
*/
editingTodo: null,
canToggle: Ember.computed.notEmpty('editingTodo'),
actions: {
createTodo: function () {
var title = this.get('newTitle');
if (!title.trim()) {
return;
}
var todo = this.store.createRecord('todo', {
title: title,
isCompleted: false,
isEditing: false
});
this.set('newTitle', '');
todo.save();
}
}
});
TODO CONTROLLER
import Ember from 'ember';
export default
Ember.ObjectController.extend({
needs:['todos']
todos : Ember.computed.alias('controllers.todos'),
actions: {
editTodo: function () {
this.set('todos.editingTodo', this.get('model'));
},
removeTodo: function () {
var todo = this.get('model');
if (this.get('todos.editingTodo') === todo) {
this.set('todos.editingTodo', null);
}
todo.deleteRecord();
todo.save();
},
acceptChanges: function () {
this.set('todos.editingTodo', null);
if (Ember.isEmpty(this.get('model.title'))) {
this.send('removeTodo');
}
else {
this.get('model').save();
}
}
},
isCompleted: function (key, value) {
var model = this.get('model');
if (value === undefined) {
return model.get('isCompleted');
}
else {
model.set('isCompleted', value);
model.save();
return value;
}
}.property('model.isCompleted')
});
I have not tested it, but I hope you get the jist of the solution I am proposing.
propTest in this scenario here looks like it's a computed property, and not an observable. (edit: not that computed properties don't have underlying observables powering them, but they're different in usage enough that I like to keep them separate) It'll fire when the underlying propTest fires, but if there's no change, ember will no-op that out in the run loop. If you'd rather this be a raw observable, use the observes() syntax. #each will work here, but I like being explicit and have an observable update what it needs to rather than using the computed property, unless I need direct access to that property to bind with in a template.
The "only firing on execution" stems from the initial bindings from the computed property getting created. If it never fires after that, the underlying bindings you're using for the computed property #each.propTest must be incorrect, otherwise that would indeed fire.
I think you may also be confused to the purpose of an ObjectController. An Ember.Object can do all of the observable business that a controller can, short of having a 'model' or 'content' property backing it. It looks like you may want to go with a straight up object rather than a controller here for the todo, since ember doesn't really have a 'model' type. I'd then put the objects themselves as part of the content of the ArrayController, at which point #each would be able to iterate on them as you'd expect.
The ObjectController sits at the same level of usage as an ArrayController. You can certainly nest them as you've done here, but my spidey-sense is tingling that it's the wrong thing to do given the application. You probably don't need to have a backing controller for each todo object, you just need the todo object itself.

Ember raw JSON transform doesn't work as excepted?

I'm using the default RESTAdapter with the ActiveModelAdapter, and I want to include a JSON object in a particular model.
eg:
App.Game = DS.Model.extend(
name: attr('string')
options: attr('raw') # This should be a JSON object
)
After reading ember-data/TRANSITION.md.
I've used the same transformer from the example:
App.RawTransform = DS.Transform.extend({
deserialize: function(serialized) {
return serialized;
},
serialize: function(deserialized) {
return deserialized;
}
});
When I've tried to create an Game instance model and save it, the options attribute in the POST data was "null"(string type).
App.GamesController = Ember.ObjectController.extend(
actions:
add_new: ->
game = this.get('model')
game.set('options', {max_time: 15, max_rounds: 5})
game.save()
)
What am I missing here?
Probably you need to register your transform:
App = Ember.Application.create();
App.RawTransform = DS.Transform.extend({
deserialize: function(serialized) {
return serialized;
},
serialize: function(deserialized) {
return deserialized;
}
});
App.initializer({
name: "raw-transform",
initialize: function(container, application) {
application.register('transform:raw', App.RawTransform);
}
});
I hope it helps

backbone event listener on singleton object

I am trying to create a singleton object as a model for a view on backbone, and I want to re-render the view whenever this singleton object is being changed and updated. I am not sure the following code would be the right way of doing it or not
Model
define(function(require) {
var Singleton = require("modules/Singleton");
var Singleton = null;
var SingletonHolder = {
init: function() {
Singleton = new Singleton();
return Singleton.fetch();
},
current: function() {
return Singleton;
},
refresh: function() {
return Singleton.fetch();
}
};
return SingletonHolder;
});
Controller
var currentObj = SingletonHolder.current();
var tempView = new TempView({
model: currentObj
});
View
var TempView = Backbone.View.extend({
initialize: function() {
this.listenTo(this.model, "change", this.render);
}
render: function() {
...
}
});
For some reasons it doesn't work. Did i miss anything ? I also tried to call Singleton.refresh which it goes to the server side and fetches the latest data from the database, but it doesn't detect the changes and re-render the view.
You don't have to define a Singleton if you already use requirejs.
Singleton:
define(['models/foo'], function(FooModel){
return new FooModel;
})
Controller:
define(['foosingleton', 'tempview'], function(fooSingleton, TempView){
var tempView = new TempView({
model: fooSingleton
});
});
Here is a similar question: Is it a bad practice to use the requireJS module as a singleton?

Categories