Unable to create a new instance of a Backbone View - javascript

I have a "master" view, a layout if you will, that loads other views through the loadView method. My problem is that these view classes perform some initialisation logic (in initialize) that can only be performed once (to do with templating). If, however, I try and instantiate these classes more than once, I get an error symptomatic of calling initialize on the same instance.
I have tried, in the console, instantiating them separately by loading the class and creating two new instances using var x = new MyViewClass(); but each time the first one instantiates and the second one fails because of this error caused by the templates already being initialised.
This really shouldn't be happening, but I cannot for the life of me see what is causing the problem.
The layout's loading code is below.
loadView: function(name, bootstrap_function) {
this.unloadView();
var _class = require('View/'+name), // Let's load the view file
pretty = name.replace('/', ''), // Prettify the name by removing slashes (should end up with some camelcased niceness)
bs_name = '__bootstrap'+pretty, // Generate the name of the bootstrap function
view = new _class(); // Pass the event aggregator in
// If there is a bootstrap function, bootstrap
if(typeOf(bootstrap_function) == 'function') { // Check if one has been passed in
bootstrap_function.call(this, view); // Bootstrap: function(AppView, LoadedView)
}
this._loaded = view; // Store the view in _loaded
// Now that we have a view to play with
// we should insert it into our container object
view.$el.appendTo(this.$container);
// And render!
view.render();
},
unloadView: function() {
if(this._loaded !== null) {
this._loaded.remove();
this._loaded.unbind();
this._loaded = null;
}
}
EDIT
The templating code that is having the errors is this:
processTemplates: function() {
if(this.templates === undefined) return this;
console.log(this.templates);
if(Object.getLength(this.templates) > 0) {
Object.each(this.templates, function(template, name) {
this.templates[name] = _.template(template);
}, this);
}
return this;
},
The console.log(this.templates) output shows that on the first initialisation, this.templates contains strings, as it should, but on second initialisation it shows template functions (which should only be the case after processTemplates() is called.
I wonder if it could have anything to do with the way my class is defined, for example:
define(
['backbone', 'View/Kords', 'text!Template/Pages/Landing.html', 'Collection/TenantTypes'],
function(Backbone, KordsView, landing_html, TenantTypesCollection) {
var LandingView = KordsView.extend({
tagName: 'div',
className: 'tiled-light',
templates: {
'main': landing_html
},
landing_html is defined like this in the class, but could there be a reference problem? _.template should not be affecting the value of landing_html within the scope, should it?
EDIT #2
It is not to do with the reference to landing_html. I tried just setting templates.main to a string in the class definition but I still got the errors as before.

Related

How to get the javascript file path at run-time

Requirement:
I want to find all the backbone views (src file path) used to develop an existing page. In our case each back-view will be maintained in a separate js file. In a nutshell I want to find the js files path (views which are extending Backbone view).
What I have tried:
I our case we have a wrapper view which is extending Backbone view, further all views will extends the wrapper view. So In the initialize method of wrapper-view I am generating error to get the call stack and further I am able to find the js-file-paths.
var WrapperView = Backbone.View.extend({
initialize: function() {
this.track();
},
track : function() {
try{
throw new Error("STACK");
}
catch(e){
this.$el.attr('view-url', e.stack.match(/\bhttps?:\/\/.*js*(?=(?:(?!http)[\s\S])*https?:\/\/\S*backbone-min\.js)/)[0]);
}
if(Object.observe){
Object.observe(this, function(changes){
var eleProp = _.filter(changes, function(prop){ return prop.name == "$el"; })[0];
if(!eleProp) return;
eleProp.object.$el.attr('view-url', j$(eleProp.oldValue).attr('view-url'));
}, ["update"])
}
}
});
var ChildView = WrapperView.extend({
initialize: function() {
WrapperView.prototype.initialize.apply(this);
this.track();
}
});
Backbone will invoke ------> ChildView.initialize --------> WrapperView.initialize();
As the ChildView.initialize function call is in call stack, I can get the file path.
What I want:
Though above solution is working, I don't want to include my track logic in the WrapperView, as it may not guarantee that all child views will call the WrapperView.prototype.initialize and I don't want to touch the framework src. So to fix this I included the track logic in Backbone.View (Js plugin) itself instead of WrapperView.
(function(){
var _viewExtend = Backbone.View.extend;
var newExtend = function (protoProps, classProps) {
var _init = protoProps.initialize;
var newInit = function(){
try{
throw new Error("STACK");
}
catch(e){
this.$el.attr('view-url', e.stack.match(/\bhttps?:\/\/.*js*(?=(?:(?!http)[\s\S])*https?:\/\/\S*backbone-min\.js)/)[0]);
}
if(Object.observe){
Object.observe(this, function(changes){
var eleProp = _.filter(changes, function(prop){ return prop.name == "$el"; })[0];
if(!eleProp) return;
eleProp.object.$el.attr('view-url', j$(eleProp.oldValue).attr('view-url'));
}, ["update"]);
}
return _init.apply(this, arguments);
};
protoProps.initialize = newInit;
return _viewExtend.call(this, protoProps, classProps);
};
Backbone.View.extend = newExtend;
})();
But the call stack not included the childview file path as the no call got made from child-view, so please help me to find a way get the things done.
Backbone will invoke ------> newInit (stack generated here) --------> Childview.initialize();
Note: If any other approach is there to achieve the requirement, please let me know.

Preventing Marionette CompositeView render until fetch complete

I'm having a problem where render is being called autimatically in my Marionette CompositeView which is correct, the problem is that I'm fetching collection data in the initialize and want this to be present when the render happens. At the moment I'm running this.render() inside the done method of the fetch which re-renders but this causes problems as now I have 2 views per model. Can anyone recommend how I can properly prevent this initial render or prevent the duplicate views. 1 entry will output view1 and view2.
JS CompositeView
initialize: function() {
var self = this;
this.teamsCollection = new TeamsCollection();
this.teamsCollection.fetch().done(function() {
self.render();
});
},
First of all, I don't believe there is a way to stop rendering outright, but you have a bunch ways around that.
Option 1: fetch data first, then create your view and pass data into it when it's done.
//before view is rendered, this is outside of your view code.
var teamsCollection = new TeamsCollection();
teamsCollection.fetch().done(function(results) {
var options = {res: results};
var myView = new CompositeView(options);
myView.setElement( /* your element here */ ).render();
});
Option 2:
// don't use render method, use your own
initialize: function() {
var self = this;
this.teamsCollection = new TeamsCollection();
this.teamsCollection.fetch().done(function() {
self.doRender();
});
},
render: function(){}, // do nothing
doRender: function(){
// override render here rather than using default
}
Option 3: (if using template)
// if you have a template, then you can simply pass in a blank one on initialization
// then when the fetch is complete, replace the template and call render again
initialize: function() {
var self = this;
this.template = "<div></div"; // or anything else really
this.teamsCollection = new TeamsCollection();
this.teamsCollection.fetch().done(function() {
self.template = /* my template */;
self.render();
});
},
In reality I need more info. How is the view created? is it a region? is it added dynamically on the fly? Do you use templates? Can you provide any more code?

this.own not defined in Dojo

I'm trying to create a singleton class in Dojo with a method called loadsth. There I want o run this.own from within a foreach loop. However, when I run this code it says
TypeError: this.own is not a function
I looked into the Dojo docs and scripts and there it says that the method "own" is part of dijit/Destroyable. But although it is included it doesn't work. I tried it at the positions //#1 and //#2. I actually need it at position //#2 but wanted to make sure that the foreach loop doesn't hide "this". So I tried //#1 but this doesn't work as well.
define([
"dojo/_base/declare",
"dijit/Destroyable"
], function (
declare,
destroyable
) {
var SingletonClass = declare("some.Class", null, {
_registerdPropertyGrids: [],
_resourceNode: "",
/*
* This method is used register a property grid plus
* provide the properties that should be shown in that
* grid.
*/
registerWidget: function(widgetName, propertyGridWidget) {
console.log(widgetName);
this._registerdPropertyGrids.push({widgetName, propertyGridWidget});
},
/*
* Sets the resource node for this widget.
*/
setResourceNode: function(resourceNode) {
this._resourceNode = resourceNode;
},
/*
* Loads sth.
*/
loadSth: function() {
console.log("load");
//var deferred = this.own(...)[0]; // #1
this._registerdPropertyGrids.forEach(function(element, index, array) {
console.log('_registerdPropertyGrids[' + index + '] = ' + element.widgetName);
var deferred = this.own(...)[0]; // #2
}, this);
}
});
if (!_instance) {
var _instance = new SingletonClass();
}
return _instance;
});
I suppose it has something todo with the implementation of the single class.
So my actual question is: Why does it say that this.own is not defined when I have dijit/Destroyable in the dependency list of "define"?
You have dijit/Destroyable listed as a dependency but you're not actually making your declared constructor extend from it, and as a result your own prototype doesn't have own on it.
Instead of declare("...", null, {, you want declare(destroyable, { (where destroyable is replacing null).
Notes:
The string argument to declare is deprecated, as it populates a global namespace, which is discouraged with AMD.
I would recommend renaming destroyable to Destroyable. Common convention is for constructors to start with a capital letter.

SPA and Knockout.js AddViewModel breakdown

I am having some issues trying to work out what is going ok with MVC SPA and Knockout.
When you create a new project some files are created for knockout.js as examples, but I am struggling to understand what is going on.
Primarily the issue is with the app.viewmodel.js and the function AddViewModel.
Here is some code which I will attempt to breakdown:
self.addViewModel = function (options) {
var viewItem = {},
navigator;
// Example options
//{
// name: "Home",
// bindingMemberName: "home",
// factory: HomeViewModel
//}
// Add view to AppViewModel.Views enum (for example, app.Views.Home).
self.Views[options.name] = viewItem; // Don't really get this, seems to add a blank object to app.Views.Home
// Add binding member to AppViewModel (for example, app.home);
self[options.bindingMemberName] = ko.computed(function () {
//if (self.view() !== viewItem) {
// console.log(self.view()); // returns {}
// console.log(viewItem); // returns {}
// return null; // should never hit this?
//}
return new options.factory(self, dataModel); // This adds our ViewModel to app.home, app.login, etc
});
// This checks to see if we have defined a navigatorFactory in our viewmodel (AddViewModel)
if (typeof (options.navigatorFactory) !== "undefined") {
navigator = options.navigatorFactory(self, dataModel);
} else {
navigator = function () {
console.log(viewItem);
self.view(viewItem);
};
}
// Add navigation member to AppViewModel (for example, app.NavigateToHome());
self["navigateTo" + options.name] = navigator;
};
ok, so let's start. First of all we declare 2 variables:
var viewItem = {},
navigator;
viewItem is set as a blank object and navigator is undefined.
The first thing we do, is set self.Views[options.name] to our viewItem, so in my understanding, this would mean:
self.Views.Home = {}
If we look at the declaration in app.viewmodel.js self.Views looks like this:
self.Views = {
Loading: {} // Other views are added dynamically by app.addViewModel(...).
};
So in here there is already a view called Loading. So I am confused as to what is actually happening here.
The next bit of code creates a function:
self[options.bindingMemberName] = ko.computed(function () {
return new options.factory(self, dataModel);
});
This is a lot easier to understand. It basically takes our ViewModel and adds it to a function under the name of self.home (or whatever the bindingMemberName of our ViewModel is.
This next piece is what confuses me:
if (typeof (options.navigatorFactory) !== "undefined") {
navigator = options.navigatorFactory(self, dataModel);
} else {
navigator = function () {
console.log(viewItem);
self.view(viewItem);
};
}
// Add navigation member to AppViewModel (for example, app.NavigateToHome());
self["navigateTo" + options.name] = navigator;
If I strip this down, it basically says if we define a navigatorFactory, then the navigator (which is currently undefined!) is equal to our navigatorFactory. That bit is easy.
It's the next bit I don't get.
It says, else, the navigator is a function that returns our self.view(viewItem) (remember that viewItem is just a blank object.
Then we set self["navigateTo" + options.name] = navigator.
So in english, this looks like it is saying, get our blank viewItem, assign it to self.view for every ViewModel we add. Then assign a function returning our self.view(viewItem) to our navigator variable (which is currently undefined) and assign this to our self.naviateToHome() (or whatever).
So to me, that looks like self.navigateToHome(), self.navigateToLogin(), self.navigateToTimbucktoo() would all return the same function with the same self.view.
So, can anyone explain to me what is actually happening?
Update 1
So, I have figured some things out. First things first, the navigator is setting the current view, so basically self.Views looks like this after all the models are added:
self.Views = {
Loading: { },
Home: { },
Login: { }
}
So even though self.view() returns an empty object, it isn't the same as the viewItem because it is stored with the name into self.Views.
So, the navigator is actually applying the viewItem to self.views.
I tested this out by changing the viewItem to this:
var viewItem = { options.name }
and sure enough, self.Views looked liked this:
self.Views = {
Loading: { },
Home: { name: "Home" },
Login: { name: "Login" }
}
so when we set self.view using our navigator, the function is called (app.home for example) and it runs the code to return our factory or null if it isn't the current view.

Custom JQuery Plugin Method error

I've been working on writing a custom jquery plugin for one of my web applications but I've been running into a strange error, I think it's due to my unfamiliarity with object-oriented programming.
The bug that I've been running into comes when I try to run the $(".list-group").updateList('template', 'some template') twice, the first time it works just fine, but the second time I run the same command, I get an object is not a function error. Here's the plugin code:
(function($){
defaultOptions = {
defaultId: 'selective_update_',
listSelector: 'li'
};
function UpdateList(item, options) {
this.options = $.extend(defaultOptions, options);
this.item = $(item);
this.init();
console.log(this.options);
}
UpdateList.prototype = {
init: function() {
console.log('initiation');
},
template: function(template) {
// this line is where the errors come
this.template = template;
},
update: function(newArray) {
//update code is here
// I can run this multiple times in a row without it breaking
}
}
// jQuery plugin interface
$.fn.updateList = function(opt) {
// slice arguments to leave only arguments after function name
var args = Array.prototype.slice.call(arguments, 1);
return this.each(function() {
var item = $(this), instance = item.data('UpdateList');
if(!instance) {
// create plugin instance and save it in data
item.data('UpdateList', new UpdateList(this, opt));
} else {
// if instance already created call method
if(typeof opt === 'string') {
instance[opt](args);
}
}
});
}
}(jQuery));
One thing I did notice when I went to access this.template - It was in an array so I had to call this.template[0] to get the string...I don't know why it's doing that, but I suspect it has to do with the error I'm getting. Maybe it can assign the string the first time, but not the next? Any help would be appreciated!
Thanks :)
this.template = template
Is in fact your problem, as you are overwriting the function that is set on the instance. You end up overwriting it to your args array as you pass that as your argument to the initial template function. It basically will do this:
this.template = ["some template"];
Thus the next time instance[opt](args) runs it will try to execute that array as if it were a function and hence get the not a function error.
JSFiddle

Categories

Resources