Passing arguments to a Backbone.View's constructor - backbone.js

I create a Backbone View
App.MyView = Backbone.View.extend({...});
then instance it like
new App.MyView();
but if my object needs some initial values i
new App.MyView({name:"Beautiful"});
I then would access this options from MyView like that
App.MyView= Backbone.View.extend({
initialize: function(){
console.log(this.options.name);
}
});
This works good, however is not a self-explanatory object: i have to know before hand the need of passing in the initial values in a literal way ({this.name:"Beautiful"}) in order to make MyView work..
How can i pass in arguments like initialize(name) or something like that to make it clear that the values are needed in the View?

Javascript functions don't have a defined number of arguments, so basically, you can't.
Writing:
function(name) {...}
is simply a shortcut for:
function() {
var name = arguments[0];
...
}
You could eventually throw an error is your argument is undefined:
function(name) {
if(typeof name === 'undefined') throw 'Error: missing argument';
...
}
But yeah, other than that, you can't force the use of an argument.
Edit:
If you're problem is just clarity, you cannot do that either I fear.
Edit2:
Well, you could if you really want to, but I'd really avoid walking those paths.

Related

angular - fail to update array from service

I got a service that contain some contacts (name,phone). The controller has array that contain a reference to the array from the service so for every change on the array all gets updated.
Service:
app.service('ContactManagerService', function()
{
this.Contacts=[];
...
this.AddContact=function(Contact){...};
this.RemoveContact=function(Contact){...};
...
});
First question: Is this a good approach? Should every controller/directive need to have a direct reference to the original array from the service? I have read a lot about setting up some events from the service to the controllers when the array has been changed, but it sound stupid because the array on the controller will be change anyway (because its the same array) and the ng-repeat will be updated automatically.
Second problem: The service has a method that replace the array to new one:
this.ReplaceContacts=function(NewContacts)
{
this.Contacts=NewContacts;
});
The ng-repeat does not update because the controller still got the old reference to the old array. So a refresh need to be done.
I tried to replace the code to this one so the same array's reference wont change, but when the the code enter the foreach, this.Contacts array is undefined and the code stops. Why ?!
this.ReplaceContacts=function(NewContacts)
{
this.Contacts.splice(0, this.Contacts.length); //remove all contacts
NewContacts.forEach(function (contact, index)
{
this.Contacts.push(contact);//place the new ones
});
});
The controller code:
app.controller("myCtrl",
function ($scope,ContactManagerService)
{
$scope.Contacts = ContactManagerService.Contacts;
$scope.AddContact= function (Contact1) {
ContactManagerService.AddContact(Contact1);
}
$scope.RemoveContact = function (ContactID) {
ContactManagerService.RemoveContact(ContactID);
}
});
I hope everything is clear,
Thanks.
Because the callback function passed to forEach isn't bound to the service instance. So this, inside the callback, is not the service.
My advice: avoid this like the plague. It's an enormous source of bugs in JavaScript.
Use
var self = this;
at the top of the service, and use self instead of this everywhere.
Or bind the callback function to the service instance:
NewContacts.forEach(function (contact, index) {
...
}, this);
You can simply push elements to Contacts using Array.prototype.push()
The push() method adds one or more elements to the end of an array and returns the new length of the array.
this.ReplaceContacts=function(NewContacts){
this.Contacts.splice(0, this.Contacts.length); //remove all contacts
Array.prototype.push(this.Contacts, NewContacts);
});
As mentioned in previous anser, context of this in forEach loop is not what you think it is.
A simplification would be to use Array.prototype.concat():
var self = this;
self.ReplaceContacts = function (NewContacts) {
self.Contacts.splice(0, this.Contacts.length); //remove all contacts
self.Contacts.concat(NewContacts);
});

looping + rendering in backbone

render:function(output){
_.each(output, function(i){
p=i.name;
});
$(this.el).append(p);
}
here i am getting output in the form of json....i have 100 names...and i want to append it one after the another...in this case i get one name....
if i put like this...
render:function(output){
_.each(output, function(i){
p=i.name;
$(this.el).append(p);
});
}
inside the each loop nothing come...
do i need to make 100 variable like p here or is there any other way...
You need to provide the context, i.e. this. "this" is the reference to the current object you are working in. However, when you are calling a different function from within your current object, you are loosing the reference to your current object. Basically, this then refers to the the function. But you can easily fix this.
Either by adding this as an argument to the function call:
render:function(output){
_.each(output, function(i){
p=i.name;
$(this.el).append(p);
},this);
}
Or by providing this via a helper variable
render:function(output){
var self=this;
_.each(output, function(i){
p=i.name;
$(self.el).append(p);
});
}
I would go for option 1

How to update attribute of an existing model?

I wanted to update the rank attribute of an existing model which I passed from another view. However, I get the error Uncaught TypeError: Object # has no method 'set'.
In the initialize part of the view, I have :
this.collection = new tgcollection({model : this.options.model });
I define a function updateModel intended to update the attribute value as:
updateModel: function(){
var val= $("#textbox_id").val();
console.log(val);
console.log(JSON.stringify(this.options.model));
JSON.stringify(this.options.model);
this.options.model.set({"rank": val});
this.render();
//
},
Where am I going wrong?
I can see the value and the model with its previous attribute values in the console.
The model:
define(['jquery','underscore', 'backbone', 'deepmodel'],
function($,_, Backbone) {
var model = Backbone.DeepModel.extend({
// Default attributes for the model.
defaults : {
id: null,
rank: null,
},
initialize: function(){
_.bindAll(this,"update");
this.bind('change : cost', this.update);
},
update: function(){
console.log(this.get("cost"));
},
// Remove this model from *localStorage*.
clear : function() {
this.destroy();
},
});
return model;
});
Just do
this.model.set({"rank": val});
instead of
this.options.model.set({"rank": val});
The model within a view is accessed via this.model not this.options.model
I love a good mystery. Here is my best guess based on what I see. The problem is probably even further back. Where you call:
this.collection = new tgcollection({model : this.options.model });
this.options.model is probably not what you think it is. It would be helpful to see the view BEFORE this view that is instantiating and passing in this.options.model. BTW, with models and collections passed into the view, you can always shorten it to this.model Model, Collection and a handful of others are special in that they get attached directly to the View once passed in.
I'm assuming that in your updateModel() the following SEEM to work:
console.log(JSON.stringify(this.options.model));
JSON.stringify(this.options.model);
The error is coming up on the set(), not the lines above. So the assumption is that you passed in a model. Or did you? My wild guess is that what this.options.model actually is, is just a json object of your model. This might explain why you "see" the model in your console when you stringify it, but then Backbone protests when you call set() on it.
Instead of JSON.stringify to test this.options.model try just console.log(this.options.model). Well, you don't have to test really. The fact that Backbone can't find set() on this object is a tell tale sign. If you're not seeing the complexity of a Backbone model in your console - it's not a model.
Also, for testing and debugging particularly models, I tend to use the model.toJSON() function as a quick check that it's a model and I'm seeing attributes I expect.
Let us know if you have more clues.

How to use Backbone.Marionette.ItemView with Mustache

The following code works fine using Backbone.Marionette.ItemView but not Mustache.
Backbone.Marionette.ItemView - no Mustache
I would like to use the same code but loading the template varaible using Mustache.
Here is my code:
Backbone.Marionette.ItemView - with Mustache
Any idea why my code does not work and why?
Thanks
I'd like to update the answer here a bit as I was just struggling with this, and I was using this answer as a reference.
Here are my findings:
The answer here is a bit out of date with the current version of Mustache (which is understandable as it's pretty old)
Mustache.to_html is now deprecated, but still exists as a simple wrapper around Mustache.render for backwards compat. Check out this link.
Additionally, I found overriding Marionette.Renderer.render, as in the accepted answer above, completely bypasses the Marionette.TemplateCache layer which may not be the desired behavior.
Here's the source for the Marionette.Renderer.render method:
render: function(template, data){
if (!template) {
var error = new Error("Cannot render the template since it's false, null or undefined.");
error.name = "TemplateNotFoundError";
throw error;
}
var templateFunc;
if (typeof template === "function"){
templateFunc = template;
} else {
templateFunc = Marionette.TemplateCache.get(template);
}
return templateFunc(data);
}
Source
As you can see it accesses the Marionette.TemplateCache.get method and the above answer does nothing to maintain that functionality.
Now to get to my solve (note: the above answer is not wrong necessarily; this is just my approach to maintain the Marionette.TemplateCache layer):
As the comments suggest above, override compileTemplate instead:
Marionette.TemplateCache.prototype.compileTemplate = function(rawTemplate) {
// Mustache.parse will not return anything useful (returns an array)
// The render function from Marionette.Renderer.render expects a function
// so instead pass a partial of Mustache.render
// with rawTemplate as the initial parameter.
// Additionally Mustache.compile no longer exists so we must use parse.
Mustache.parse(rawTemplate);
return _.partial(Mustache.render, rawTemplate);
};
Here's a working JSFiddle as proof.
In the fiddle, I've also overridden Marionette.TemplateCache.loadTemplate to demonstrate that it's only called once. The body of the function only adds some debug output and then re-implements most of the original functionality (minus error handling).
Marionette assumes the use of UnderscoreJS templates by default. Simply replacing the template configuration for a view isn't enough. You also need to replace how the rendering process works.
In your simple example, you only need to override the Marionette.Renderer.render function to call Mustache, and then set the template of your views to the string template that you want:
Backbone.Marionette.Renderer.render = function(template, data){
return Mustache.to_html(template, data);
}
var rowTemplate = '{{ username }}{{ fullname }}';
// A Grid Row
var GridRow = Backbone.Marionette.ItemView.extend({
template: rowTemplate,
tagName: "tr"
});
Note that your JSFiddle still won't work even when you put this code in place, because the GridView is still using a jQuery selector/string as the template attribute. You'll need to replace this with the same type of template function to return mustache.
http://jsfiddle.net/derickbailey/d7qDz/

Parameterizing the name of the store in backbone-localStorage.js

The standard way to use the localStorage plugin for Backbone.js works like this:
App.WordList = Backbone.Collection.extend({
initialize : function(models, options){
},
localStorage : new Store('English')
}
But I want to make different, parallel wordlist collections in different languages. So, I want to be able to instantiate the name of the Store upon initialization of the collection. AFAICT, this works ok:
App.WordList = Backbone.Collection.extend({
initialize : function(models, options){
this.localStorage = new Store(options.language);
}
}
Then I can instantiate a WordList like:
english = new Wordlist([], {language: 'English'});
Or:
chinese = new Wordlist([], {language: 'Chinese'});
The thing is, I haven't really seen this done in any other examples and I'm wondering if anyone out there would have any "Eek! Don't do that, because..." sorts of reactions.
EDIT
I should add that I have already tried doing it this way:
App.WordList = Backbone.Collection.extend({
initialize : function(models, options){
},
localStorage : new Store(options.store)
}
And then:
chinese = new Wordlist([], {language: 'Chinese'});
But for some reason options.store is coming up undefined.
It's easier to explain myself as an answer, so I'll go ahead and give one.
In:
App.WordList = Backbone.Collection.extend({
initialize : function(models, options){
....
},
localStorage : new Store(options.store)
})
This is really little different from
var newInstanceConfig = {
initialize : function(models, options){
....
},
localStorage : new Store(options.store)
}
App.WordList = Backbone.Collection.extend(newInstanceConfig);
Think of it this way; there's nothing magical about the object being passed in to Backbone.Collection.extend(...). You're just passing in an ordinary object. The magic happens when Backbone.Collection.extend is invoked with that object as a parameter
Thus, the options parameter of the object method initialize is completely different that which is being passed in to new Store(...). The function being assigned initialize is defining the scope of options. Who knows where the one referred to in new Store(options.store) is defined. It could be window.options or it could be options defined in some other scope. If it's undefined, you're likely getting an error
That being said, I only see two or three strategic options (oh jeez, forgive the pun please!).
Whenever you're creating a new instance of the collection, either:
Pass in the language and let your Backbone collection create the new Store(..) where needed.
Pre-Create the Stores and either pass or give the specific Store want to that instance (either directly through its constructor or maybe you have your constructor "look-up" the appropriate pre-created Store).
And finally, I guess you could delegate the task of creating stores to another object and have it implement either options one or two. (Basically a Store Factory/Resource Manager kinda thing).
What you need to figure out is which one of those strategies should work for you. I have never used localStorage so, unfortunately, I can't help you in that regard. What I can do is ask, is there ever going to be multiple instances created from App.Wordlist where there might accidentally be created two of the same kind of Store?
In fact, I've got another question. where is this Store defined? Are you sure that's not defined somewhere in one of your other API libraries you're using? Perusing the localStorage docs I know about mentions something of a Storage constructor but nothing of a Store. So you might want to figure out that as well.
Edit #1: Nevermind, I see you mentioned where Store was defined.
I got around this by creating a method which allows you to configure the localStorage after instantiation:
var PageAssetCollection = Backbone.Collection.extend ({
initialize: <stuff>
model: <something>
...
setLocalStorage: function ( storageKey ) {
this.localStorage = new Backbone.LocalStorage(storageKey),
},
});
you can then set the localStorage after you have set up the collection:
fooPageAssets = new PageAssetCollection();
fooPageAssets.setLocalStorage('bar');

Resources