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.
Related
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?
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
I'm currently using Backbone + RequireJS.
In my application, I display a tree widget that is constructed with the same Model with nested Collections.
That is to say:
FooCollection
define(['backbone', 'models/foo'], function(Backbone, FooModel) {
var FooCollection = Backbone.Collection.extend({
model: FooModel
});
return FooCollection;
});
FooModel
define(['backbone', 'underscore'], function(Backbone, _) {
var FooModel = Backbone.Model.extend({
initialize : function() {
_.bindAll(this, 'adoptOne', 'adoptAll');
var self = this;
// Need to do it this way or RequireJS won't find it
require(['collections/foos'], function(FooCollection) {
self.foos = new FooCollection();
self.on('change:foos', function() {
self.foos.reset(self.get('foos'));
});
self.foos.on('reset', self.adoptAll);
self.foos.on('add', self.adoptOne);
self.foos.reset(self.get('foos');
});
},
adoptAll : function() {
this.foos.each(this.adoptOne);
},
adoptOne : function(foo) {
foo.parent = this;
}
});
return FooModel;
});
The above works. I don't get any errors and everything is constructed as expected.
However...
// In a view
this.foos = new FooCollection();
this.foos.fetch({
success : function(foos) {
var treeView = new TreeView();
treeView.render(foos); // Doesn't work!!
}
});
The above doesn't work because of a sync problem: the TreeView gets rendered before the nested collections have finished creating (either because it takes longer to run the code or because it takes time to load 'collections/foos'.
Either way, I can fix it with this:
setTimeout(function() {
treeView.render(foos);
}, 100);
But that, of course, it's just a hack. In a production environment it could take more than 100 miliseconds and the code wouldn't work.
So, I guess that what I should do is to trigger some sort of event that my view listens to. However, my question to y'all is the following: when do I know that the entire collection of foos have been constructed and where do I attach the listener?
Thanks in advance!!
Working on creating a dirt simply MVC framework for one of my own projects. Rather than using one that is public, I decided to create one since my needs are very unusual.
I've got my structure down for the Controllers and Views, however, I'm having some issues creating my model structure.
This is what I have for my model structure:
model.models = function(args){
init: function(){
this.on_init();
},
on_init: args.on_init || noop,
data: args.data || {},
};
So then, I would call this as a basic formula for all of the models I want to create. For example, I want to create employees, notifications and some other models using this as a basic blueprint, then make some basic adjustments.
I call:
model.employees = new model.models({
on_init: function(){
//something specific
},
data: {
//defaults
}
});
And we're all good up to this point, but here is where I'm having troubles. Now, when I want to create my end result, the model, I cannot create a new object from an object.. it must be a function.
The only thing I can think of is creating a return function for the second method, but that renders some issues in itself. I have done some research looking at other MVC code, but I was unable to wrap my head around it.
Any help would be very much appreciated!
is this what you want ?
model.models = function(args){
var noop = function(){};
var o = {};
var init = args.on_init || noop;
var data = args.data || {};
init();
//handle other initialization
//o.a = xx;
//o.b = xx;
//o.c = data.xxx;
//....
return o;
}
then you can use the new, and it can't appear syntax error
Did a lot of fiddling, came up with this:
var blueprint = function(args){
return {
data: args.data,
on_init: args.on_init,
create: function(args){
this.on_init();
return {
data: this.data,
whatever: function(){
console.log(args);
}
};
}
};
};
var notifs = new blueprint({
on_init: function(){
console.log('init');
},
data: {
test: 'test'
}
});
var res = notifs.create('test');
console.log(blueprint);
console.log(notifs);
console.log(res);
It comes out with a main function that works, the notifs function is customizable for each individual object type, then calling the create method will create the end method.
Boom!
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.