pushPayload isn't called - ember.js

I'm trying to modify the behaviour of pushPayload in the RESTSerializer. However the function is never called.
pushPayload: function(store, payload) {
console.log('pushPayload');
payload = this.normalizePayload(payload);
for (var prop in payload) {
var typeName = this.typeForRoot(prop),
type = store.modelFor(typeName),
typeSerializer = store.serializerFor(type);
/*jshint loopfunc:true*/
var normalizedArray = map.call(Ember.makeArray(payload[prop]), function(hash) {
return typeSerializer.normalize(type, hash, prop);
}, this);
store.pushMany(typeName, normalizedArray);
}
},
normalizePayload: function(payload) {
console.log('normalize');
return payload;
},
This will output only:
normalize
I really have no idea what's going on. I literraly copied from the master both methods. There is no typo and if normalizePayload is called pushPayload should be called also!

normalizePayload is called from both extractSingle (store.find('foo', 1);), store.extractArray (store.find('foo');) and pushPayload (store.pushPayload('foo', obj);).
You need to actually call pushPayload on the store with the type of the serializer defined. Additionally be careful, map is defined earlier in the document:
var map = Ember.ArrayPolyfills.map;
App.FooSerializer = DS.RESTSerializer.extend({
pushPayload: function(store, payload) {
console.log('pushPayload');
payload = this.normalizePayload(payload);
for (var prop in payload) {
var typeName = this.typeForRoot(prop),
type = store.modelFor(typeName),
typeSerializer = store.serializerFor(type);
/*jshint loopfunc:true*/
var normalizedArray = map.call(Ember.makeArray(payload[prop]), function(hash) {
return typeSerializer.normalize(type, hash, prop);
}, this);
store.pushMany(typeName, normalizedArray);
}
},
});
http://emberjs.jsbin.com/OxIDiVU/730/edit

Related

Ember - Within action, result is defined, returnvalue of same action logged in parent action is undefined? Why?

Quick and shortly I have following problem:
I have following two actions within a component in Ember:
createData: function(user) {
let collection = [];
for (let i = 0; i < user.posts.length; i++) {
let data = this.send('createSingleData',user.posts[i], user, 'post');
console.log(data);
collection.push(data);
}
return collection;
},
createSingleData: function(data, user, type) {
let entitySkeleton = {
name: data.place.name,
belongsTo: user.id,
position: {
data.place.location.longitude,
data.place.location.latitude
}
};
console.log(entitySkeleton);
return entitySkeleton;
}
the first log - within createSingleData, right before returning the logged value - writes the entitySkeleton as Object into the console - as expected.
However, the console.log(data) - within createData - writes 'undefined' to the console.
Is there any aspect of asynchrounosity I didn't respect?
P.S.:
I also logged any paramater within createSingleData, they are all set properly.
The variable collection also only gets pushed 'undefined'.
You cannot return the value from action, instead you can set property from the action.
how to return values from actions in emberjs
actions: {
PrintSomething: function() {
let obj = [{a: 'raj'}, {a: 'Prudvi'}, {a : 'thimappa'}]
console.log('before', obj);
this.send('returnSomething', obj);
console.log('after calling action', this.get('returnvalue'));
},
returnSomething: function(obj) {
obj.push({a: 'FSDFSDF'})
var data = obj;
this.set('returnvalue', data);
}
}

RESTSerializer extend not affecting Ember data objects

JSON from Sails responses don't have root elements. Ember data requires that the JSON be wrapped in a root element with the same name as the object. The plan is to extend RESTSerializer to munge the JSON on the client side, because I don't have similar control over the server that I'm getting this data from. I'm not sure what I'm doing wrong, but it doesn't seem like my Accounts model is using my AccountSerializer...
This is my Serializer:
var AccountSerializer = DS.RESTSerializer.extend({
/**
The current ID index of generated IDs
#property
#private
*/
_generatedIds: 0,
/**
Sideload a JSON object to the payload
#method sideloadItem
#param {Object} payload JSON object representing the payload
#param {subclass of DS.Model} type The DS.Model class of the item to be sideloaded
#param {Object} item JSON object representing the record to sideload to the payload
*/
sideloadItem: function(payload, type, item){
var sideloadKey = type.typeKey.pluralize(), // The key for the sideload array
sideloadArr = payload[sideloadKey] || [], // The sideload array for this item
primaryKey = Ember.get(this, 'primaryKey'), // the key to this record's ID
id = item[primaryKey];
// Missing an ID, generate one
if (typeof id == 'undefined') {
id = 'generated-'+ (++this._generatedIds);
item[primaryKey] = id;
}
// Don't add if already side loaded
if (sideloadArr.findBy("id", id) != undefined){
return payload;
}
// Add to sideloaded array
sideloadArr.push(item);
payload[sideloadKey] = sideloadArr;
return payload;
},
/**
Extract relationships from the payload and sideload them. This function recursively
walks down the JSON tree
#method sideloadItem
#param {Object} payload JSON object representing the payload
#paraam {Object} recordJSON JSON object representing the current record in the payload to look for relationships
#param {Object} recordType The DS.Model class of the record object
*/
extractRelationships: function(payload, recordJSON, recordType){
// Loop through each relationship in this record type
recordType.eachRelationship(function(key, relationship) {
var related = recordJSON[key], // The record at this relationship
type = relationship.type; // belongsTo or hasMany
if (typeof related === "object" && related !== null){
// One-to-one
if (relationship.kind == "belongsTo") {
// TODO: figure out when we need to only sideload 1 item we don't need to pluralize
// Sideload the object to the payload
this.sideloadItem(payload, type, related);
// Replace object with ID
recordJSON[key] = related.id;
// Find relationships in this record
this.extractRelationships(payload, related, type);
}
// Many
else if (relationship.kind == "hasMany") {
// Loop through each object
related.forEach(function(item, index){
// Sideload the object to the payload
this.sideloadItem(payload, type, item);
// Replace object with ID
related[index] = item.id;
// Find relationships in this record
this.extractRelationships(payload, item, type);
}, this);
}
}
}, this);
return payload;
},
/**
Overrided method
*/
extractArray: function(store, type, payload, id, requestType) {
var typeKey = type.typeKey,
typeKeyPlural = typeKey.pluralize(),
newPayload = {};
newPayload[typeKeyPlural] = payload;
payload = newPayload;
console.log(payload);
// Many items (findMany, findAll)
if (typeof payload[typeKeyPlural] != "undefined"){
payload[typeKeyPlural].forEach(function(item, index){
this.extractRelationships(payload, item, type);
}, this);
}
for(var key in payload) {
if(key === typeKeyPlural) {
for(var i =0; i < payload[key].length; i++) {
if(typeof payload[key][i] !== 'object') {
delete payload[key][i];
}
}
}
}
return this._super(store, type, payload, id, requestType);
},
extractSingle: function (store, type, payload, id, requestType) {
console.log('what is happening');
var typeKey = type.typeKey,
typeKeyPlural = typeKey.pluralize(),
newPayload = {};
if(typeof payload[typeKey] !== "object") {
newPayload[typeKey] = payload;
payload = newPayload;
if(payload[typeKey] instanceof Array) {
payload[typeKey] = payload[typeKey][0];
}
}
if (typeof payload[typeKey] === "object"){
this.extractRelationships(payload, payload[typeKey], type);
delete payload[typeKeyPlural];
}
console.log(payload);
return this._super(store, type, payload, id, requestType);
}
});
export default AccountSerializer;
In my adapters/account.js I have the following:
import DS from "ember-data";
var AccountAdapter = DS.RESTAdapter.extend({
namespace: 'api/v1',
host: 'http://localhost:5000',
pathForType: function(type) {
return type + '.json';
},
serializer: AccountSerializer
});
export default AccountAdapter;
I'm not 100% sure where you got the serializer property from, but I'm not sure that actually exists. There's a defaultSerializer property on the adapter which would work, but that comes with a bit of weird precedence rules. If I were you, I would declare the serializer in the Ember CLI way by putting it in serializers/account.js and remove the serializer property from your adapter.

How to properly setup a store that acts as a single pointer across your web app

I have a home grown store that has a simple identityMap. When I return an array of models from this and bind it to a controllers "model" it reflects what you'd expect
the first time you hit a route it reflects this in the template as
you'd expect
But later if I get this same store instance (it's a singleton) and push an object into the identityMap it doesn't automatically update the previous template
The store itself is super basic (no relationships/ just push objects and get by id)
function buildRecord(type, data, store) {
var containerKey = 'model:' + type;
var factory = store.container.lookupFactory(containerKey);
var record = factory.create(data);
var id = data.id;
identityMapForType(type, store)[id] = record;
return record;
}
function identityMapForType(type, store) {
var typeIdentityMap = store.get('identityMap');
var idIdentityMap = typeIdentityMap[type] || {};
typeIdentityMap[type] = idIdentityMap;
return idIdentityMap;
}
var Store = Ember.Object.extend({
init: function() {
this.set('identityMap', {});
},
push: function(type, data) {
var record = this.getById(type, data.id);
if (record) {
record.setProperties(data);
} else {
record = buildRecord(type, data, this);
}
return record;
},
getById: function(type, id) {
var identityMap = identityMapForType(type, this);
return identityMap[id] || null;
}
getEverything: function(type) {
var identityMap = identityMapForType(type, this);
var keys = Object.keys(identityMap);
var values = [];
for (var i = 0; i < keys.length; i++)
{
var val = identityMap[keys[i]];
values.push(val);
}
return values;
}
});
Ember.onLoad('Ember.Application', function(Application) {
Application.initializer({
name: "store",
initialize: function(container, application) {
application.register('store:main', Store);
application.inject('controller', 'store', 'store:main');
application.inject('route', 'store', 'store:main');
}
});
});
In my model hook (in the find all route lets say) I simply query for each item and push them into the store
//inside my model find method lets say ...
find: function(store) {
var url = "/api/foo";
$.getJSON(url, function(response) {
response.forEach(function(data) {
var model = store.push("foo", data);
}
}
return store.getEverything("foo");
}
So I assumed my controllers' model was this bound array (using a single pointer in memory for this array of models)
Yet when I do this inside a controller submit action it won't re-render that prev view (to show the new item that was added to that store's array)
actions: {
submit: function() {
var foo = {}; // assume this is a real json response or js object
var store = this.get("store");
store.push("foo", foo);
}
}
Because of this today, I'm forced to get the parent controller and "set" / "push" this new object to it's content/model property :(
Anyone know what I'm doing wrong here?
I like homegrown solutions, they generally are easier to work with and meld around what you're working on.
So I'm actually surprised this part is working:
//inside my model find method lets say ...
find: function(store) {
var url = "/api/foo";
$.getJSON(url, function(response) {
response.forEach(function(data) {
var model = store.push("foo", data);
}
}
return store.getEverything("foo");
}
If I read through it I see you make an ajax call, and then return store.getEverything immediately after (without a guarantee that the ajax call has completed). Then inside of getEverything you create a new array called values then iterate the identity map linking up all of the currently available records and return that. At this point your store is unaware of this array going forward. So any changes to your store wouldn't get pushed out to the array, they might make it into the identity map, but it isn't feeding the getEverything array.
There are a couple of solutions, one would be to keep track of your everything array. That collection would be super cheap to build, more expensive to search, so keeping the identity map as well would be super beneficial. You could follow your same pattern, but one collection would be the map, whereas the other would be an array of everything.
Modified Build Record
function buildRecord(type, data, store) {
var containerKey = 'model:' + type;
var factory = store.container.lookupFactory(containerKey);
var record = factory.create(data);
var id = data.id;
identityMapForType(type, store)[id] = record;
everythingArrayForType(type, this).pushObject(record);
return record;
}
Copy paste, possibly could be refactored
function everythingArrayForType(type, store) {
var everythingArrays = store.get('everythingArrays');
var arr = everythingArrays[type] || [];
everythingArrays[type] = arr;
return arr;
}
Slightly modified Store
var Store = Ember.Object.extend({
init: function() {
this.set('identityMap', {});
this.set('everythingArrays', {});
},
push: function(type, data) {
var record = this.getById(type, data.id);
if (record) {
record.setProperties(data);
} else {
record = buildRecord(type, data, this);
}
return record;
},
getById: function(type, id) {
var identityMap = identityMapForType(type, this);
return identityMap[id] || null;
}
getEverything: function(type) {
return everythingArrayForType(type, this);
}
});

bound {{if}} statement in ember not reevaluated

The following bound {{if}} statement is not reevaluated when the object 'model' it is bound to changes. This is contrary to my expectation and what I thought was the purpose of binding an if statement to an object. Am I doing something wrong?
if-data.js:
Handlebars.registerHelper("ifData", function(property, fn)
{
var context = (fn.contexts && fn.contexts[0]) || this;
var args = [property];
var canAction = function(can_args)
{
alert('I was called for '+can_args[0].get('id'));
return true;
};
// Resolve actual values for all params to pass to the conditional callback
var normalizer = function() {
return Ember.Handlebars.resolveParams(context, args, fn);
};
return Ember.Handlebars.bind.call(context, 'content', fn, true, canAction, normalizer, args);
});
show.hbs:
{{#ifData model }}
<h1> Showme </h1>
{{/ifData}}
When testing I am updating the bound obejct using this in the console:
App.__container__.lookup('store:main').find('post',1).then(function(model){
model.set('title','mydream20');
model.save();
});

Ember Data: How to make AJAX calls in Ember-Objects (has no method 'find' )

I'm trying to make an AJAX call to my API over Ember Data (1.0.0 Beta 4), but I don't know how to access the model outside the router. The documentation provides such examples only:
App.PostRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('post', params.post_id);
}
});
My code:
var AuthManager = Ember.Object.extend({
authenticate: function(accessToken, userId) {
var user = this.store.find('user', userId);
/* ... */
},
/* ... */
});
Now I get has no method 'find':
Uncaught TypeError: Object function () {
if (!wasApplied) {
Class.proto(); // prepare prototype...
}
o_defineProperty(this, GUID_KEY, undefinedDescriptor);
o_defineProperty(this, '_super', undefinedDescriptor);
var m = meta(this), proto = m.proto;
m.proto = this;
if (initMixins) {
// capture locally so we can clear the closed over variable
var mixins = initMixins;
initMixins = null;
this.reopen.apply(this, mixins);
}
if (initProperties) {
// capture locally so we can clear the closed over variable
var props = initProperties;
initProperties = null;
var concatenatedProperties = this.concatenatedProperties;
for (var i = 0, l = props.length; i < l; i++) {
var properties = props[i];
Ember.assert("Ember.Object.create no longer supports mixing in other definitions, use createWithMixins instead.", !(properties instanceof Ember.Mixin));
if (typeof properties !== 'object' && properties !== undefined) {
throw new Ember.Error("Ember.Object.create only accepts objects.");
}
if (!properties) { continue; }
var keyNames = Ember.keys(properties);
for (var j = 0, ll = keyNames.length; j < ll; j++) {
var keyName = keyNames[j];
if (!properties.hasOwnProperty(keyName)) { continue; }
var value = properties[keyName],
IS_BINDING = Ember.IS_BINDING;
if (IS_BINDING.test(keyName)) {
var bindings = m.bindings;
if (!bindings) {
bindings = m.bindings = {};
} else if (!m.hasOwnProperty('bindings')) {
bindings = m.bindings = o_create(m.bindings);
}
bindings[keyName] = value;
}
var desc = m.descs[keyName];
Ember.assert("Ember.Object.create no longer supports defining computed properties.", !(value instanceof Ember.ComputedProperty));
Ember.assert("Ember.Object.create no longer supports defining methods that call _super.", !(typeof value === 'function' && value.toString().indexOf('._super') !== -1));
Ember.assert("`actions` must be provided at extend time, not at create time, when Ember.ActionHandler is used (i.e. views, controllers & routes).", !((keyName === 'actions') && Ember.ActionHandler.detect(this)));
if (concatenatedProperties && indexOf(concatenatedProperties, keyName) >= 0) {
var baseValue = this[keyName];
if (baseValue) {
if ('function' === typeof baseValue.concat) {
value = baseValue.concat(value);
} else {
value = Ember.makeArray(baseValue).concat(value);
}
} else {
value = Ember.makeArray(value);
}
}
if (desc) {
desc.set(this, keyName, value);
} else {
if (typeof this.setUnknownProperty === 'function' && !(keyName in this)) {
this.setUnknownProperty(keyName, value);
} else if (MANDATORY_SETTER) {
Ember.defineProperty(this, keyName, null, value); // setup mandatory setter
} else {
this[keyName] = value;
}
}
}
}
}
finishPartial(this, m);
this.init.apply(this, arguments);
m.proto = proto;
finishChains(this);
sendEvent(this, "init");
} has no method 'find'
In Ember Data < 0.14 methods like App.User.find(id) were present but it's deprecated
You can use the dependency injection to inject a store in the AuthManager:
Ember.Application.initializer({
name: "inject store in auth manager",
initialize: function(container, application) {
// register the AuthManager in the container
container.register('authManager:main', App.AuthManager);
// inject the store in the AuthManager
container.injection('authManager', 'store', 'store:main');
// inject the AuthManager in the route
container.injection('route', 'authManager', 'authManager:main');
// inject in the controller
// container.injection('controller', 'authManager', 'authManager:main');
}
});
And in the route you will able to do:
App.IndexRoute = Ember.Route.extend({
model: function() {
this.authManager.authenticate('token', 'userId');
return [];
}
});
See this in action http://jsfiddle.net/marciojunior/3dYnG/