I'm trying to instantiate and render a view but SOMETIMES I need to pass an extra object to that view called keys.
this.$container.append(new app.MyExtraView({'model':model,'keys':keys}).render().el));
I'm expecting this.keys to be available from within app.MyExtraView but it isn't. How can I pass keys = {} to app.MyExtraView and access it inside app.MyExtraView like this.keys?
Interestingly the model object behaves in this manner 'automatically'.
Note that this keys object won't be needed every time.
thank you already
Related
The part I'm curious about is why use
value: function() { return {}; }
instead of
value: {}
Here's the sample code:
<script>
Polymer({
is: 'polymer-demo',
properties: {
data: {
type: Object,
notify: true,
value: function() { return {}; }
}
},
});
</script>
This is explained in the example:
When initializing a property to an object or array value, use a function to ensure that each element gets its own copy of the value, rather than having an object or array shared across all instances of the element.
Although you can use {}, this will be the same object shared by each element, so that if the value is mutated for one element, all other elements would see the same change applied to it. This is not what you want to happen. By using a function, the function will be called for each element, and each call will produce a new, separate object. Then a mutation will only apply to that single element without affecting the others.
I feel it is the same reason that vuejs requires their data component to be rendered as a function rather than the object that it is:
In the basic examples, we declare the data directly as a plain object.
This is because we are creating only a single instance with new Vue().
However, when defining a component, data must be declared as a
function that returns the initial data object. Why? Because there will
be many instances created using the same definition. If we still use a
plain object for data, that same object will be shared by reference
across all instance created! By providing a data function, every time
a new instance is created, we can simply call it to return a fresh
copy of the initial data.
Thus, making sure that the data being given to the component is always indicative of a fresh "load"
Hope this helps!
Usually this is done in cases where you want to show, that you can not only return a fixed value but calculate a dynamic value and return this instead but you were to lazy to return something more than {}. And sometimes the things you are declaring a used in some place by the framework and are there expected to be a function returning an object, and not just a fixed value or object.
You can find out by changing the code, executing it, and watching for errors or different behaviour.
I'm struggling a lot with Javascript prototyping; I don't seem to understand when I should be declaring an attribute in the prototype vs instantiation. Particularly, I don't understand, why in my code attached (a basic exercise in building a little Router class to treat a document more like an app than a page), attributes set at instantiation are persisting and thus accumulating in what I intend to be separate objects entirely.
Mostly this is a learning exercise, but I've scaled back some of the original code to help with contextual obfuscation*.
Code is here: http://codepen.io/anon/pen/xGNmWM
Basically, each time you click a link, the data-route attribute on the element should be picked up an event listener, a new Route object should be instantiated (and passed information about the intended route); finally the Router class should actually "launch" the Route, ie. make an ajax request or do some in-document stuff, whatever.
Right now, the Route.url.url attribute should be, in my obviously wrong understanding, created anew each time and then informed by passed data. Somehow, this attribute is persisting and thus accumulating passed information from each click.
I truly don't understand why.
**I have not removed anything that would effect my question; really it could be trimmed back even more but I realize the integrity of the question relies on a reasonable facsimile of the original code.
You have two problems.
By Value vs By Reference
In Javascript primitive types, as numbers, booleans and strings, are passed to another functions or set to another variable by value. That means that its value is copyed (cloned).
The object types, as objects, arrays and functions, are passed to another functions or set to another variable by reference. That means that variables of this type are just references to a content to memory. Then when you set an object to a variable, just its memory address is being copied.
When you pass the "route_data" its reference is copied. Then the Route constructor is working on same variable hold by Router.
If you clone your object before pass it the problem is solved.
...
var route_data = this.route_data[ route_name ];
route_data = $.extend(true, {}, route_data); // Clone object using jQuery
var route = new Route( route_name, route_data, request_obj);
...
Prototype
The Javascript has a prototypal inheritance, that means that each object points to its prototype that is another object.
var obj = { name: 'John' };
console.log(obj.__proto__);
All objects root prototype is Javascript Object by default. As noted by example above.
function Person(name) {
this.name = name;
}
Person.prototype = {
getName: function() {
return this.name;
}
}
var obj = new Person('John');
console(obj.getName());
console(obj.__proto__);
console(obj.__proto__.__proto__);
When you use new a new empty object is created and binded as this to specified function. Additionally its object prototype will point to the object specified on prototype of called function.
On get operations the Javascript engine will search on entire prototype chain until specified field is found.
But on set operations if the specified field does not exist on current object, a new field will be created.
When you define the url field on Route this should be static, because when you try to change it a new field is created.
If you verify your Route instance you will note that you have created duplicated url fields. One on object itself and another on prototype.
I would have really appreciated a minimal code example posted here on SO rather than codepen. It would have saved me some time reading your code (you're not paying me for this after all).
Anyway, this is problematic:
Route.prototype = {
// etc..
url : {url: false, type: "get", defer: false}
// etc..
}
Basically, what you're doing is this:
var global_shared_object = {url: false, type: "get", defer: false};
Route.prototype.url = global_shared_object;
Do you see the problem? Now when you do:
var route1 = new Route();
var route2 = new Route();
Both the .url property of route1 and route2 point to the same object. So modifying route1.url.url will also modify route2.url.url.
It should be noted that route1.url and route2.url are different variables. Or rather different pointers. Modifying route1.url will not modify route2.url. However, the way they've been initialized makes them both point to the same object so modifying that object can be done from either pointer.
The key to making this work is to create a new object for .url every time you create a new object. That can be done either in the constructor or in your case your .init() method:
Route = function (name, route_data, request_obj) {
this.url = {url: false, type: "get", defer: false};
this.init(name, route_data, request_obj);
}
Implied Lesson:
The lesson to take from this is that the object literal syntax is actually an object constructor. Not just a syntax.
// this:
{};
// is the same as this:
new Object();
So every time you see an object literal, in your mind you should be thinking new Object() then see if it makes sense to call it just once or do you need a new instance of it every time.
I have a viewModel that looks something like this when simplified:
var viewModel = function(){
var self = this;
self.selectedObject = ko.observable({});
self.getUnit = function(){
//get the selected object from the server side as json
self.selectedObject(ko.mapping.fromJS(data,mapping));
};
self.addObjectMember = function(){
self.selectedObject().objectMembers.push(new ObjectMemberViewModel(null, self.selectedObject()));
self.save = function(){
var data = ko.mapping.toJS(self.selectedObject);
//ship data to server
}
The data received from the server contains an array called objectMembers that has some properties inside it. The properties might differ between different objects.
My mapping object look like this:
var mapping = {
'objectMembers': {
create: function(options){
return new ObjectMemberViewModel(options.data, options.parent);
}
}
};
To see my problem, I'll give an example:
The user loads the page, and then fetches an object. That object includes two elements inside the objectMembers array. Then those are mapped using the ko.mapping.fromJS and everything works just fine. I can modify my viewModel using my model and the viewModel is updated. Then the user clicks a button that triggers the addObjectMember function, adding a third entry to the observableArray. I can interact with this also, and any changes done to my model can be seen in the viewModel as expected.
The problem comes when I click save. If I debug the save method, and check the contents of self.selectedObject, I can see that it contains what I want it to, but the object that is mapped back into the variable data has the last element in the objectsMembers array as an empty object ({}). The two other object look as I want them to.
I think I know why. The two first object have their __ko_mapping__.mappedProperties containing all the properties they had when it got mapped initially. The last one however has an empty __ko_mapping__.mappedProperties, and therefore I guess that no properties are mapped back.
So I need to do one of these things I guess:
When adding the object to the array on addObjectMember I need to get the __ko_mapping__.mappedProperties set so that it gets mapped back when I save.
When mapping back, I include all the properties on the mapped object regardless of their presence inside the __ko_mapping__.mappedProperties.
I have no clue how to do any of them that does not feel like a dirty hack, so any help here would be appreciated.
I ended up using ko.toJS instead of ko.mapping.toJS. ko.toJS does not care about __ko_mapping__, and maps everything (including __ko_mapping__).
That means it maps a bit more than I need, but other than that it works just fine.
I am trying to bind an Ember object to a property in an ArrayController. The properties of this object will be used for all objects added to the ArrayController in calculations.
Here's a simple example of what I'm seeing: http://jsfiddle.net/EjDFS/2/
As you can see, the object that I am trying to bind is undefined within the array controller. I do not want to bind this object directly to the objects that are added to the array controller.
Thanks for taking the time to look at this - much appreciated!
Its undefined because when it fires, it hasn't completed its run loop.
and as for App.ObjectOne.param3, you need to use this.get('param1') instead of param1, the same with param2
http://jsfiddle.net/EjDFS/4/
When is it appropriate to pass a collection to a backbone model constructor? as in
var someColl = new SomeCollection();
var m = new SomeModel({}, {collection: someColl});
the model constructor queries the 2nd arg for a collection property and if it exists, it is assigned to the model collection property. Otherwise these items are attached the constructor function I believe. Are there any examples of when and why you would do these things?
var someColl = new SomeCollection()
var m = someColl.create({ foo: bar })
# m.collection == someColl
If you call create on the collection you will receive the newly created model (which was also added to collection). From then on you can reference the collection whenever you need it with m.collection
I think referencing a collection on the model makes the most sense in this scenario. You're adding a new model to a collection, and since it intrinsically belongs within it, it makes sense to reference it with .collection.
I've sometimes found it useful to pass the parent view a model gets instantiated in. Albeit that's very rare, you can easily do something like new TabModel(null, {tabGroupView: this}); . I wouldn't say it's a pattern I would use much, but it's definitely handy to pass options around. That's what I like about Backbone, you're more free to do whatever you like.