Calling super class method in Backbone - javascript

var BaseView = Backbone.View.extend({
localizedTemplate : function (element) {
template = _.template(element.html());
return function (data) {
return template($.extend({}, data, resource));
};
}
});
var DerivedView = BaseView.extend({
initialize: function (options) {
this.model = options.model;
this.template = function () {
return this.localizedTemplate($("#someTemplate"));
};
},
render: function () {
var output = this.template(this.model.toJSON());
this.$el.append(output);
return this;
}
});
Why the above code is not working? why I am not able to call the someFunction in DerivedView? is there any way to achieve this?
I am using Backbone latest version.

When you do this:
this.template = function () {
return this.localizedTemplate($("#someTemplate"));
};
You're assigning a function to this.template. Note that localizedTemplate also returns a function:
return function (data) {
return template($.extend({}, data, resource));
};
That means that this.template is a function which returns a function and that second function is the one that wants this.model.toJSON() as an argument.
You're doing this:
var output = this.template(this.model.toJSON());
The function in this.template ignores its arguments and returns a function, that leaves you with this function:
function () {
return this.localizedTemplate($("#someTemplate"));
}
in output. You probably think output is a chunk of HTML at this point so you hand that to append:
this.$el.append(output);
But output is a function and what does append do when called with a function as its argument? jQuery calls that function like this:
function(index, html)
Type: Function()
A function that returns an HTML string, DOM element(s), or jQuery object to insert at the end of each element in the set of matched elements. Receives the index position of the element in the set and the old HTML value of the element as arguments. Within the function, this refers to the current element in the set.
So the output function will be called by jQuery's append and append will supply arguments that the compiled template function doesn't understand. The result is a big pile of confusion.
If you really want to do things like this then you'll want to call all the functions yourself so that you can get the right arguments to the right places:
var output = this.template()(this.model.toJSON());
// -----------------------^^
Demo: http://jsfiddle.net/ambiguous/YyJLR/
Or better, don't bother with all the extra wrappers at all. Say this in your view's initialize:
this.template = this.localizedTemplate($("#someTemplate"));
and then this in render:
var output = this.template(this.model.toJSON());
Demo: http://jsfiddle.net/ambiguous/BQjhS/
Also note that you don't need to this.model = options.model, the view constructor will do that for you:
There are several special options that, if passed, will be attached directly to the view: model, collection, el, id, className, tagName and attributes.

var DerivedView = BaseView.extend({
someVariable: function(someData) {
return this.someFunction(someData);
}
});

Related

Wrap static function (IIFE) into prototype

I wrote a function that I thought I'd only need to process 1 object but it turns out I need more than 1. I will use a simple example:
var Helper = (function () {
return {
el: null
init: function(el) {
this.el = el;
}
doStuff: function(){
// Modify this.el in someway
}
};
}());
So then I'd just do Helper.init(el) on page load and then run Helper.doStuff() when I needed it.
Well now I have three elements who need this functionality.
My first idea was to just make it do Helper.init([el1,el2,el3]) and have it work on an array of elements but I may want to treat each element separately.
I am thinking the best approach is to probably turn the Helper IIFE into a "class" with prototype, but I am a bit crunch on time so I was looking for a way to make a wrapper to accomplish what I need.
I was thinking I can just take the function and not immediately execute it, and then somehow store that function into a prototyped function and utilize it that way.
Looking for ideas on how to best do this with minimal code change.
I am thinking the best approach is to probably turn the Helper IIFE into a "class" with prototype, but I am a bit crunch on time...
I wouldn't expect it to take very long.
I was thinking I can just take the function and not immediately execute it, and then somehow store that function into a prototyped function and utilize it that way.
Looking for ideas on how to best do this with minimal code change.
The class pattern is just one pattern in JavaScript, you can use that Helper just like it is as the prototype of other objects, which matches your "minimal changes" requirement. Just use Object.create:
var helper1 = Object.create(Helper);
helper1.init(el);
var helper2 = Object.create(Helper);
helper2.init(el2);
var helper3 = Object.create(Helper);
helper3.init(el3);
// ...
helper1.doStuff();
helper2.doStuff();
helper3.doStuff();
If you add return this; to the end of init, that can be more concise:
var helper1 = Object.create(Helper).init(el);
var helper2 = Object.create(Helper).init(el2);
var helper3 = Object.create(Helper).init(el3);
// ...
helper1.doStuff();
helper2.doStuff();
helper3.doStuff();
Live Example:
var Helper = (function () {
return {
el: null,
init: function(el) {
this.el = el;
return this;
},
doStuff: function(){
this.el.style.color = "green";
this.el.style.fontWeight = "bold";
}
};
}());
var helper1 = Object.create(Helper).init(document.getElementById("el1"));
var helper2 = Object.create(Helper).init(document.getElementById("el2"));
var helper3 = Object.create(Helper).init(document.getElementById("el3"));
// ...
setTimeout(function() {
helper1.doStuff();
}, 400);
setTimeout(function() {
helper2.doStuff();
}, 800);
setTimeout(function() {
helper3.doStuff();
}, 1200);
<div id="el1">el1</div>
<div id="el2">el2</div>
<div id="el3">el3</div>
You could even keep using Helper directly on the first el, further reducing code changes, though I wouldn't recommend it.
Alternately, wrap it in a function that returns it (and here I've also included that change to init):
function getHelper() {
var Helper = (function () {
return {
el: null,
init: function(el) {
this.el = el;
return this; // <============== Added
},
doStuff: function(){
// Modify this.el in someway
}
};
}());
return Helper;
}
Then for your three places you need it:
var helper1 = getHelper().init(el);
var helper2 = getHelper().init(el2);
var helper3 = getHelper().init(el2);
// ...
helper1.doStuff();
helper2.doStuff();
helper3.doStuff();
Side note: You don't need the IIFE there anyway unless you have things in it that aren't shown beyond the object initializer...
Just rewritten code:
function Helper (el) {
this.el = el;
}
Helper.prototype = {
doStuff: function(){
// Modify this.el in someway
}
};
var helper1 = new Helper(el1);
var helper2 = new Helper(el2);
var helper3 = new Helper(el3);
helper1.doStaff();
helper2.doStaff();
helper3.doStaff();
Another way, retrieves arguments from arguments object:
var Helper = (function () {
return {
el: null
init: function() {
this.el = Array.from(arguments)
}
doStuff: function(){
this.el.forEach(el => {
// Modify el in someway
});
}
};
}());

JavaScript return a default value

I want to make a library of functions like this (similar to what jquery is doing)
var myLib = function (idOfAnElement){
var myElement = document.getElementById(idOfAnElement);
return{
getHeight: function (){
return myElement.style.height;
},
getWidth: function (){
return myElement.style.width;
}
}
}
My problem is, that I don't know how to return
myElement
by default, if there is no other function called like
myLib('myId').getHeight; // Returns Height
myLib('myId') // Is supposed to return the HTML-Element with id = 'myId'
Create a privileged method returning the value of private myElement property itself
var myLib = function (idOfAnElement){
var myElement = document.getElementById(idOfAnElement);
return{
getHeight: function (){
return myElement.style.height;
},
getWidth: function (){
return myElement.style.width;
},
getElement: function() {
return myElement;
}
}
}
myLib('myId').getElement();
What you want can be achieved simply by adding the methods you want to the element object,
Javascript allows easy adding methods to existing objects, even the this pointer will point to the bound object.
var myLib = function (idOfAnElement){
var myElement = document.getElementById(idOfAnElement);
myElement.getHeight: function (){
return this.style.height;
}
myElement.getWidth: function (){
return this.style.width;
}
return myElement;
}
Note: While it works, I wouldn't recommend it.
You need to take care not to overwrite existing methods/fields and if multiple libraries will take the same approach a collision is likely.
And this is NOT what jQuery is doing: they create a wrapper object. To get the element from jQuery you need to use [0] for example $('#myEl')[0].

Access view property when calling render as a callback

I have to use the guid variable in the render() function, but I can pass it only to the constructor. I this code:
app.views.CompanyView = Backbone.View.extend({
el: '#company-view',
guid: '',
initialize: function (options) {
this.guid = options.guid;
},
render: function () {
var guid = this.guid;
}
});
I create my view like this:
app.currentView = new app.views.CompanyView({guid: guid});
Then I pass the render() function as a parameter to use it as a callback:
function call(callback){
callback();
}
call(app.currentView.render);
I tried this.guid, options and this.options too, but all of them were undefined. Is there a way to pass this variable to the render() function without using it's arguments or global variables? Here is a JsFiddle example.
When you call render through this:
function call(callback){
callback();
}
You're calling it as a plain function so this inside render will be window. Remember that this in JavaScript depends on how the function is called, not how it is defined (unless of course you're playing with bound functions).
You have some options:
Bind render to the view using _.bindAll, _.bind, $.proxy, Function.bind, ...
initialize: function() {
_.bindAll(this, 'render');
}
Demo: http://jsfiddle.net/ambiguous/GsUfY/
The more common approach these days is to pass a context with the function and then whoever calls the callback uses the appropriate context using call or apply:
function call(callback, context){
callback.apply(context);
}
Demo: http://jsfiddle.net/ambiguous/LnwPr/
Do it yourself by hand:
call(function() { v.render() });
This one usually takes the form of var _this = this; followed by an anonymous function that uses _this.some_method() instead of just passing this.some_method as a callback.
Demo: http://jsfiddle.net/ambiguous/K2Xj4/
I prefer the second option.
I see. When your render() is called by the callback function, the caller of the method is no longer the view itself, so the "this" inside your render will be the caller of the call function().
see this fiddle:
http://jsfiddle.net/cn8nN/2/
var CompanyView = Backbone.View.extend({
initialize: function (options) {
this.guid = options.guid;
},
render: function () {
console.log('hello');
console.log(this);
}
});
var v = new CompanyView({guid: 'the guid'});
function call(callbcak) {
callbcak();
}
call(v.render);
if you open the console, you will see "this " is actually the window.
to work around this, you want to bind the context to the view it self.
to do that, use _.bindAll();
initialize: function (options) {
_.bindAll(this, "render");
this.guid = options.guid;
}
jsfiddle: http://jsfiddle.net/cn8nN/3/

OOP jQuery Plugin elem undefined in one place and not in others

How is my element undefined if I clearly define it here. It works in all the other methods but with exception of setUpCalendar(). I've included the "path" the code goes through before reaching the problematic part.
var Calendar = {
init: function(options, elem) {
this.options = $.extend( {}, this.options, options );
this.elem = $(elem); //Clearly defined here and works elsewhere
this.getFeed();
return this;
},
getFeed: function() {
var self = Calendar;
$.jGFeed(this.options.feedUrl, function (feeds) {
if (!feeds) {
return false;
}
$.extend(self.entries, feeds.entries);
self.parseEntries();
}, 10);
},
parseEntries: function() {
//Rename to fit plugin requirements
for (var i = 0; i < Calendar.entries.length; i++) {
var entry = Calendar.entries[i];
entry["allDay"] = false;
//Rename
entry["url"] = entry["link"];
delete entry["link"];
};
this.setUpCalendar();
},
setUpCalendar: function() {
Calendar.elem.fullCalendar({ //It's telling me Calendar.elem is undefined here
editable: false,
weekends: false,
events: Calendar.entries //funny story, here the reference works
});
}
};
UPDATE:
$.fn.ksdCalendar = function( options ) {
if (this.length) {
return this.each(function() {
var myCalendar = Object.create(Calendar);
myCalendar.init(options, this);
$.data(this, 'ksdCalendar', myCalendar);
});
}
};
$("#calendar").ksdCalendar({
feedUrl: "http://www.kent.k12.wa.us/site/RSS.aspx?DomainID=275&ModuleInstanceID=4937&PageID=4334"
});
So the problem here is that you're mixing your this references and Calendar references. When you call this.getFeed() you're operating on a newly created Calendar object. Within getFeed though, however, you're setting var self = Calendar and subsequently using self to set the values for entries and call parseEntries.
What you've done here is create a reference (with self) to the global Calendar object literal. That object is not the object that you've initialized via Object.create (and it's not the one you called init on).
What this all boils down to is, you've initialized myCalendar and set up the elem that it's attached to, but in your subsequent method calls you set entries on the global Calendar object literal. Inside your parseEntries method, all of this becomes apparent because the current scope is inside that of the global Calendar object literal and not the instance of the Calendar object created and returned by Object.create.
To fix it, do var self = this; instead of var self = Calendar;. Within parseEntries reference this.entries instead of Calendar.entries, and in setUpCalendar reference this.entries and this.elem instead of Calendar.entries and Calendar.elem repsectively.
In init() you're adding elem to this, that is myCalendar. In setupCalendar() you're trying to retrieve it from Calendar which doesn't seem to have the elem property (unless it's in a different portion of the code) even tho' it's the constructor function prototype for myCalendar.

Assigning scope amongst jQuery.getJSON and a JS.Class

I'm trying to assign some JSON data to a property of a JS.Class instance.
var MyClass = new JS.Class({
initialize: function(uuid) {
this.uuid = uuid;
},
write: function() {
$.getJSON(url+"?callback=?", {}, function(data) {
Assign(data);
});
function Assign(data) { this.content = data; };
}
});
var m = new MyClass("uuid_goes_here");
m.write();
The JSON is received asynchronously, which is why there's a function call within the $.getJSON callback.
The problem I have now is that the this.content within the Assign function is not within the scope of the instance method named write. So whereas this.uuid returns correctly, this.content remains undefined (as you would expect).
Any ideas on how to correct this? I've tried using a global variable as a workaround but the async call doesn't allow for that (plus it's a crappy solution).
Some points to note, in case they matter: I have to use JSONP, so the "?callback=?" has to stay, and I'd like to keep it async.
I would usually go for either czarchaic's version, or replace Accept with a bound method from the object. What you have to bear in mind is that calling Accept() like that (as a function call rather than a method call) will bind this to the global object, i.e. window. I'd try this:
var MyClass = new JS.Class({
initialize: function(uuid) {
this.uuid = uuid;
},
write: function() {
$.getJSON(url+"?callback=?", {}, this.method('setContent'));
},
setContent: function(data) {
this.content = data;
}
});
See http://jsclass.jcoglan.com/binding.html for more info.
You should cache the current instance in the write method and update it after ajax.
write: function() {
var self=this;
$.getJSON(url+"?callback=?", {}, function(data) {
self.data=data;
});
}

Categories

Resources