Issue with creating the instance of the model in EmberJS - javascript

I'm writing simple app in EmberJS and trying to do some simple objects creation in the console. I'm trying to create PageModel and then retrieve it from the store (I use localstorage). Unfortunately it doesn't succeeded.
// creates the PageModel instance
page = store.createRecord(App.PageModel)
Class {id: "i1i67", store: Class, container: Container, _changesToSync: Object, _deferredTriggers: Array[0]…}
// tries to retrieve the object from the storage
store.find('page', page.get('id'))
// but doesn't work
Class {__ember1413471577603: null, __nextSuper: undefined, __ember_meta__: Object, constructor: function, _super: function…}
Can anyone is able to help with ?

If your model is called PageModel, then I'd expect the store to call it page_model. It is unusual to call your model ThingModel- more common is Thing. However, this works too:
store.find(App.PageModel, page.get('id'))

Related

Ember.js: How to access a model's attributes inside Javascript code

In Ember.js while using ember-data, I can access a model's attributes like this when in a .hbs template:
{{#each model as |person|}}
Welcome, {{person.firstName}} {{person.lastName}}!
{{/each}}
I tried accessing these values in Javascript code (be it inside a controller or a component) using the following methods:
// Method 1
if (person.firstName === 'Jack') {
// Do something when the first name is Jack
}
// Method 2
if (person.get('firstName') === 'Jack') {
// Do something when the first name is Jack
}
But none of these work to get any attributes of the current model. The only value I can get this way is the id of the current model instance.
I have looked far and wide for a solution to this problem and found nothing, so I ask this question:
Is it possible to access the attributes of a model instance inside Javascript code while using Ember.js and ember-data? If so, how can this be done? If not, why can't I do that?
For reference, here is my current Ember.js setup:
DEBUG: Ember : 2.5.1
DEBUG: Ember Data : 2.5.3
DEBUG: jQuery : 2.2.4
DEBUG: Ember Simple Auth : 1.1.0
When you have an object that you're passing into a component, it becomes a property of the component. So you need to get that object via the component's property before accessing any properties on the model object itself.
Assuming you're passing the object into a component like this:
{{person-profile person=person}}
Either of these should work:
// Method 3
if (this.get('person').get('firstName') === 'Jack') {
// Do something when the first name is Jack
}
// Method 4
if (this.get('person.firstName') === 'Jack') {
// Do something when the first name is Jack
}

model not an instance of a model, but has attributes of a particular instance

The best way to think of the problem is to consider it in terms of the Backbone TodoMVC app (even though that's not what I'm doing, it's similar in that it has one main view and many list views). If you declare a custom destroy method on the Todo model (such as one I have below) and click on a view to delete it, then in the method that's triggered (where this.model.destroy is called), you can't invoke the custom destroy method on the model, because this.model is not an instance of the model. Somehow calling this.model.destroy
clear: function () { //in the todo app, this method is called clear
this.model.destroy();
}
works to destroy the model but not if you're trying to invoke a custom destroy method.
Below, I explain the same in terms of my app.
I have a Backbone application that uses server side storage but am unable to send a delete request to the server. Using another SO answer, I created a custom destroy method on the model (FunkyModel shown below) and I try to call that custom destroy method in a deleteModel method in the view but to no avail.
However, it's not calling the custom destroy method on FunkyModel.When I inspect this.model, it doesn't say it's an instance of FunkyModel, rather it only says Backbone.Model and then lists the model's properties. So obviously it's not an instance of FunkyModel and therefore not a surprise that it can't call the custom destroy method on FunkyModel, but at the same time under the attributes property, it has all the attributes of an instance of FunkyModel.
Question: how can a model have attributes that are unique to an instance of a model but not be an instance of that model, but rather simply Backbone.Model
Further Info:
The application is structured like the BackboneMVC Todo App in that there is one main view and then it attaches more views as list items. When I click on a delete symbol on one of the views, it triggers an event that calls the deleteModel method, in which I call
deleteModel(e){
this.model.destroy();
}
So naturally, depending on which list view I click on, the attributes of this.model will be different, but in no case is it ever an instance of the model. It's simply a Backbone.Model and its attributes have the properties of a particular instance. I'm not sure how a model that's not an instance can have properties in its attributes that are from a particular instance.
model
export class FunkyModel extends Model{
defaults(){
return {
name: '',
type: ''
}
}
addToCollection(){
collection.add(this);
this.save();
}
destroy(options){
var opts = _.extend({url: '/api/'}, options || {});
console.log("never getting run");
return Backbone.Model.prototype.destroy.call(this, opts);
}
}
view
deleteModel(e){
this.model.destroy({success: function(model, response, xhr){
console.log(model,xhr, options, "success callback") //xhr is undefined
},
error: function(model, xhr,options){
console.log(model,xhr,options, "error callback")
}
}
Further Info
When the model's created, I do
this.model = new FunkyModel();
this.model.set({"type": "funky"})
When the model gets saved, it gets added to the collection (code shown above in original OP). I'm wondering if when I call this.model.destroy(), it's only destroying it in terms of removing it from the collection (hence the success callback is fired).

Creating and updating nested objects in Ember

I got nested JSON data from the server like this:
{
name: "Alice",
profile: {
something: "abc"
}
}
and I have the following model:
App.User = Ember.Object.extend({
name: null,
profile: Ember.Object.extend({
something: null
})
})
If I simply do App.User.create(attrs) or user.setProperties(attrs), the profile object gets overwritten by plain JS object, so currently I'm doing this:
var profileAttr = attrs.profile;
delete attrs.profile
user.setProperties(attrs); // or user = App.User.create(attrs);
user.get('profile').setProperties(profileAttrs);
It works, but I've got it in a few places and in the real code I've got more than one nested object, so I was wondering if it's ok to override User#create and User#setProperties methods to do it automatically. Maybe there's some better way?
Based on your comment, you want the automatic merging behaviour you get with models (the sort of thing you get with .extend()). In that case, you could try registering a custom transformer, something like:
App.ObjectTransform = DS.Transform.extend({
deserialize: function(json){
return Ember.Object.create(json);
}
});
App.User = DS.Model.extend({
profile: DS.attr('object')
});
See: https://github.com/emberjs/data/blob/master/TRANSITION.md#json-transforms
If you are doing your server requests without an adapter you can use the model class method load() with either an array of json objects or a single object. This will refresh any known records already cached and stash away the JSON for future primary key based lookups. You can also call load() on a model instance with a JSON hash as well but it will only update that single model instance.
Its unclear why you are not using an adapter, you can extend one of the Ember Model adapters and override the the record loading there, eg. extend from the RESTAdapter and do any required transform on the JSON if required by overriding _loadRecordFromData
You can also override your models load function to transform data received if required as well. The Ember Model source is fairly easy to read so its not hard to extend to your requirements.

Prototype / class / public attributes in JavaScript

I have a constructor for a model (similar to Backbone model) which uses an instance of a store (e.g. MongoDB / Redis) which is passed to the model factory. Now inside the constructor for the model, I do
this.store = options.store;
Store is now available as this.store once I construct an instance with var model = new Model().
Then I faced a situation when I my Model has "public" methods, e.g. Model.find() which will work without instantiating the model. But now because it's not been instantiated, functions inside Model can not access store by this.store.
What I did, is I started also adding store to the constructor itself Model.store = options.store which fixes the problem. But now store is exposed to anything that uses Model constructor and this is not desirable.
I am guessing I am doing something wrong. So I would appreciate some help.
If I get what you're saying correctly, then I think what you want is to allow for a "static" find() method on Model without exposing the store variable. Javascript doesn't really have a formal private scope for classes, but you can get what you want using closures. Here's a fiddle:
http://jsfiddle.net/52FPy/
EDIT:
An updated fiddle to demonstrate various ways to expose/hide info using closures:
http://jsfiddle.net/52FPy/2/
Briefly:
var Model = function(){
var store = 'this is a store';
var Model = function(){
}
Model.find = function(){
return store;
}
return Model;
}()
This will "hide" the store variable in the way you want.

Backbone.js and localStorage plugin relation between collection and model -- based on official 'todo' example

Here's the official sample app:
http://documentcloud.github.com/backbone/docs/todos.html
I am confused about the relationship between the collection and its property localStorage = new Store(..)
Shouldn't this be in the model because you can't do a collection.save() anyway?
In addition, I tried implementing something like it, and it doesn't work
var Person = Backbone.Model.extend({
defaults: {
name:'no-name',
age:0
}
});
var Persons = Backbone.Collection.extend({
model: Person,
localStorage: new Store('Persons'),
initialize: function(){
console.log('collection initialized');
}
});
window.people = new Persons();
var p1 = new Person({name:'JC',age:24});
p1.save({text:'hello'}); //<--- Uncaught TypeError: Cannot read property 'localStorage' of undefined
Can anyone help me figure this out?
It's actually the .create() function of a collection that allows the collection to "save" to localStorage.
source-code of todo sample:
createOnEnter: function(e) {
var text = this.input.val();
if (!text || e.keyCode != 13) return;
Todos.create({text: text});
this.input.val('');
},
This then allows the model instance to manipulate it using the .save({attr:value}) function.
Calling modelInstance.save() without a defined localStorage property in the model's constructor function will cause the error:
Uncaught TypeError: Cannot read property 'localStorage' of undefined
However, being that the model is now saved in the localStorage through the collectionInstance.create() method, modelInstance.save({attr:value}) can now be used to modify it.
So, in conclusion, Models only has the save() function which allows persistence, but the Collection object has the create() function that allows persistence.
In order to use these, REST urls within the collection and model must be properly set up or the localStorage plugin must be instantiated within the Constructor Function of either (depending on setup)
I had a similar problem in that I wanted to simply 'save' a collection that I had loaded from LocalStorage. I wrote a save() method on my Collections that simply looped through each model and called model.save().
MyCollection.save = ->
#each (model) ->
model.save()
There's a big drawback to this, however, with regards to Backbone.LocalStorage. You loose all the great benefits of using Collection.set({ models... }); to update your collection (pulling in an online update, or something) with all the add/merge/delete goodness. Removing the model from your collection at runtime doesn't delete it from local storage, and manually identifying unmatched models and destroying them somewhat defeats the purpose of Backbone.Collection.set();
One solution I found was to augment Backbone such that Backbone.Collection.set() uses destroy() instead of remove() on the models it finds to be missing. (see line 705 of BB 1.0.0)
Another solution, which I wound up going with, was to have all models listen for their own 'remove' event and call their own 'destroy' method when it happens. This allows Backbone.Collection.set()'s removals to become permanent.
class Model extends Backbone.Model
initialize: ->
#on 'remove', #destroy
Both of these mean you can't "remove" a model without destroying it permanently, which was fine for me. If you want, you could probably create some special conditions under which this occurs, and manage it that way.

Categories