Fullcalendar Custom Views via JS classes destroy throws errors - angularjs

I have implemented Fullcalendar Custom View by referring document Custom Views via JS classes. It seems to worki fine, but the destroy method throws an error: View.prototype.destroy.apply is not function.
I have used the below code:
var FC = $.fullCalendar;
var View = FC.View;
var BlankView;
BlankView = View.extend({
initialize: function() {
// View.prototype.initialize.apply(this, arguments);
},
setHeight: function(height, isAuto) {
},
render: function () {
this.title = this.title || 'TestView';
if (this.el[0].children.length === 0) this.el.addClass('fc-basic-view').html(this.renderHtml());
},
destroyEvents: function() {
// responsible for undoing everything in renderEvents
},
destroy: function() {
View.prototype.destroy.apply(this, arguments);
},
renderHtml: function () {
this.el.html('<div></div>');
},
});
FC.views.blank = BlankView;
Any clues to achieve the destroy method in custom views?

Related

How can a Router talk to a View other than using a global variable?

I can not understand why in my Backbone app (Todo app) after I reload a page (CTRL+F5) a filterTodos method does not get called. When I simply click on links to filter Todos ("Active", "Completed") - it does get called.
You can see this feature in links below. No matter how many times you click Refresh in Browser - correct filtered results are displayed:
http://todomvc.com/architecture-examples/backbone/#/completed
http://todomvc.com/architecture-examples/backbone/#/active
I have a theory that it's because I am triggering a filter event from Router too early - a TodosView is not initialized yet and therefore it does not listenTo filter event yet.
But how a Router can inform a View to re-render itself (based on filter) if this View does not exist yet? Can't it be achieved via triggering some event in Router as I do? One possible option is to have a global variable app.FilterState.
Is there any other methods of communications between a Router and a non-constructed yet View?
For app.FilterState I will set its state in Router and then check it in View and call filterTodos function manually like so and it will work:
views/todos.js
render: function() {
app.Todos.each(function(todo) {
this.renderTodo(todo);
}, this);
if (app.FilterState !== 'all') { // <--- ADDED CODE
this.filterTodos(app.FilterState);
}
return this;
}
Existing source code:
routers/router.js
var app = app || {};
var Router = Backbone.Router.extend({
routes: {
'all': 'all',
'active': 'active',
'completed': 'completed'
},
all: function() {
console.log('all');
app.Todos.trigger('filter', 'all');
},
active: function() {
console.log('active');
app.Todos.trigger('filter', 'active');
},
completed: function() {
console.log('completed');
app.Todos.trigger('filter', 'completed');
}
});
app.Router = new Router();
Backbone.history.start();
views/todos.js
var app = app || {};
app.TodosView = Backbone.View.extend({
el: '#todo-list',
initialize: function(todos) {
console.log('initialize begin');
app.Todos.reset(todos);
this.listenTo(app.Todos, 'add', this.addOneTodo);
this.listenTo(app.Todos, 'filter', this.filterTodos);
this.render();
console.log('initialize end');
},
render: function() {
app.Todos.each(function(todo) {
this.renderTodo(todo);
}, this);
return this;
},
renderTodo: function(todo) {
var todoView = new app.TodoView({model: todo});
this.$el.append(todoView.render().el);
},
addOneTodo: function(todo) {
this.renderTodo(todo);
},
filterTodos: function(filterType) {
console.log('filter'); // <--- CODE DOES NOT REACH THIS LINE WHEN CALLED ON BROWSER'S REFRESH (F5)
var active = app.Todos.active();
var completed = app.Todos.completed();
if (filterType === 'active') {
// hide remaining
_.each(completed, function(todo) {
todo.trigger('hide');
});
//show active
_.each(active, function(todo) {
todo.trigger('show');
});
}
else if (filterType === 'completed') {
_.each(completed, function(todo) {
todo.trigger('show');
});
//show active
_.each(active, function(todo) {
todo.trigger('hide');
});
}
else if (filterType === 'all') {
app.Todos.each(function(todo) {
todo.trigger('show');
});
}
}
});
Have you considered using Backbone Marionette? It comes with a built in pub sub communication system built in that makes it super easy to do this. Overall it gives you a great organization/modularization of your code by utilizing the pub sub system.

inherit backbone view declared in different .js file

Expanding my original question located here
If my Userview is in Userview.js file and I want to inherit from that class in AdminView.js file, how would I go about it.
I tried this, but would not fit my need as I don't have a class.
UPDATE 1:
define([
'modules/userdetail'
],
function(UserView) {
var adminView
adminView.Views.Content = UserView.Views.Content.extend({
initialize: function() {
//looking to override the fn that is declared in UserView
console.log("AAA");
},
});
}
UPDATE 2:
So digging deep, the User Detail is
define(
[ 'modules/baseClass'],
function(BaseClass) {
// Create a new module
//Create Model
//Create View
UserDetails.Views.Content = Backbone.View
.extend({
template :
initialize : function() {
this.model = new UserDetails.Model();
},
events : {
},
render : function(LayOut) {
return LayOut(this).render().then(this.pageReady);
},
pageReady : function() {
},
});
UserDetails.activate = function() {
app.router.navigate('UserDetails', true);
};
UserDetails.configureRouting = function() {
app.router.route('UserDetails', 'UserDetails',
function() {
layoutmanager.setView('#content',
new UserDetails.Views.Content())
.render();
});
};
return UserDetails;
});
ADMIN:
define([
'modules/baseclass',
'modules/UserDetail'
],
function(BaseClass, UserDetails) {
UserDetail.Views.Content = UserDetail.Views.Content.extend({
render:function(){
console.log("rendering");
UserDetail.Views.Content.prototype.render();
}
});
//create admin model
//admin view
AdminView.Views.Content = Backbone.View.extend({
template: "admin-template",
events: {
},
initialize: function() {
this.model = new AdminModel.Model();
},
render: function(manage) {
return manage(this).render().then(this.pageReady);
},
pageReady: function() {
});
},
AdminView.activate = function() {
app.router.navigate('adminview', true);
};
AdminView.configureRouting = function() {
app.router.route('adminview', 'adminview', function() {
layoutmanager.setView('#content', new AdminView.Views.Content()).render();
layoutmanager.setView('#userDetials', new UserDetials.Views.Content()).render();
});
};
if(app.router && app.router.route) {
AdminView.configureRouting();
}
return AdminView;
});
Now if I have to call the render of the userdetails from admin view, the render method fails as the param is undefined.
I am not well versed with where the para in render is defined as I looked through my code and have not found anything
Either include the script tag for Userview.js before the script tag for AdminView.js, or using a module system like requirejs or browserify where you can specify the two modules as dependencies.

Backbone inheritance, merging the render() function

So I have this current situation:
app.Ui.ModalView = Backbone.View.extend({
events: {
},
initialize: function() {
},
render: function() {
var that = this;
var model = this.model.toJSON();
that.$el.html(that.template(_.extend(this.params || {}, {
model: model,
})));
return this;
}
});
and then the inherited view:
app.Views.childView = kf.Ui.ModalView.extend({
template: JST["templates/app/blah/blah-edit.html"],
events: {
},
initialize: function() {
var that = this;
this.events = _.extend({}, app.Ui.ModalView.prototype.events, this.events);
app.Ui.ModalView.prototype.initialize.apply(this, arguments);
},
render: function(){
// add extra logic in this render function, to run as well as the inherited render function?
}
});
So, I don't want to override the parent's render(), but to add extra functionality to it, how would I go about doing that?
Two ways to achieve this: Either you can add explicit support for overriding the behaviour by creating a "render hook" in the base class, or you'll have to call the overridden base method from the superclass method:
Render hook in base class:
app.Ui.ModalView = Backbone.View.extend({
render: function() {
//if this instance (superclass) defines an `onRender` method, call it
if(this.onRender) this.onRender();
//...other view code
}
}
app.Views.childView = kf.Ui.ModalView.extend({
onRender: function() {
//your custom code here
}
});
Call base class method from super class:
app.Views.childView = kf.Ui.ModalView.extend({
render: function() {
//your custom code here
//call the base class `render` method
kf.Ui.ModalView.prototype.render.apply(this, arguments);
}
});

How to unbind all the socket.io events from my backbone view?

I have a page which include two backbone views (views related to two template). I am changing content of one views based on clicking event on different items on another view. For this, Every time I click on any items in one view I just create a instance of another view which include some socket.io events. At the first time It's work well but everytime I click on item on first view it just create the instance of 2nd one so that all the socket.io events is binding. Except first click every time I click on items on first view and call an socket.io events, it fired more than one time based on how many click I have done to different items.
I know that every time I click an items it create an instance of a view with socket.io event bind. But I can not get the way to unbind the previous socket.io events.
I have tried to use this reference:
Backbone.js View removing and unbinding
But it is not working in my case. May be I did not use it in proper way.
Can anyone please give me a solution or way to unbind all the socket.io events binded before?
Here is my Clicking event from where I am creating a new instance of another view where all the socket.io events binds.
LoadQueueDetails: function (e) {
e.preventDefault();
var queues = new Queues();
queues.fetch({
data: $.param({ Code: this.model.get("QueueCode") }),
success: function () {
$("#grid21").html(new SearchResultListView({ collection: queues }).el);
},
error: function (queues) {
alert('error found in fetch queue details');
}
});
}
And here is my actual view where I bind all the socket.io events.
window.SearchResultListView = Backbone.View.extend({
initialize: function () {
this.collection.on('change', this.render, this);
this.render();
},
render: function () {
var Queues = this.collection;
var len = Queues.length;
$(this.el).html(this.template());
for (var i = 0; i < len; i++) {
$('.QueueListItem', this.el).append(new SearchResultListItemView({ model: Queues.models[i]}).render().el);
}
return this;
}
});
window.SearchResultListItemView = MainView.extend({
tagName: "tr",
initialize: function () {
this.__initialize();
var user;
if ($.super_cookie().check("user_cookie")) {
this.user = $.super_cookie().read_JSON("user_cookie");
}
this.model.bind("change", this.render, this);
this.model.on("destroy", this.close, this);
socket.emit('adduser', this.user.UserName, this.model.get("Code"));
},
events: {
"click a": "JoinQueue"
},
onClose: function(){
this.model.unbind("change", this.render);
},
close: function () {
this.remove();
this.unbind();
this.model.unbind("change", this.render);
},
socket_events: {
"updatechat": "updatechat",
"changeroom": "changedroom"
},
changedroom: function (username, data) {
alert(data);
socket.emit('switchRoom', data);
},
updatechat: function (username, data) {
alert(username);
alert(data);
},
JoinQueue: function (e) {
e.preventDefault();
if ($.super_cookie().check("user_cookie")) {
user = $.super_cookie().read_JSON("user_cookie");
}
socket.emit('sendchat', "new user");
},
render: function () {
var data = this.model.toJSON();
_.extend(data, this.attributes);
$(this.el).html(this.template(data));
return this;
}
});
window.Queue = Backbone.Model.extend({
urlRoot: "/queue",
initialize: function () {
},
defaults: {
_id:null,
Code: null,
ServiceEntityId: null,
ServiceEntityName:null,
Name: null,
NoOfWaiting: null,
ExpectedTimeOfService: null,
Status: null,
SmsCode: null
}
});
window.Queues = Backbone.Collection.extend({
model: Queue,
url: "/queue",
initialize: function () {
}
});
Backbone.View.prototype.close = function () {
this.remove();
this.unbind();
if (this.onClose) {
this.onClose();
}
}
And this is my main view to bind socket.io event in searchResultItemview.
var MainView = Backbone.View.extend({
initialize: function () {
this.__initialize();
},
__initialize: function () {
if (this.socket_events && _.size(this.socket_events) > 0) {
this.delegateSocketEvents(this.socket_events);
}
},
delegateSocketEvents: function (events) {
for (var key in events) {
var method = events[key];
if (!_.isFunction(method)) {
method = this[events[key]];
}
if (!method) {
throw new Error('Method "' + events[key] + '" does not exist');
}
method = _.bind(method, this);
socket.on(key, method);
};
}
});
For extra information:
1. I am opening socket connection globally. Like this :
var socket = io.connect('http://localhost:3000');
I am waiting for any kind of advice or solution to get out of this problem. Please feel free to ask any kind of inquiries.
Basically you have to do socket.removeListener for every socket.on when you close your View.
You can update your MainView and add a close method.
This is how it looks in my code (CoffeeScript)
close: ->
self = #
_.each #socket_events, (method, key) ->
method = self[self.socket_events[key]]
socket.removeListener key, method

Backbone view not rendering correctly on subsequent calls to route handler

I'm having a problem with rendering a backbone.js view successfully from a route handler (browser application).
My javascript module is currently setup like this:
$(function () { // DOM ready
myModule.Init();
});
var myModule = (function () {
// Models
var DonorCorpModel = Backbone.Model.extend({ });
// Collections
var DonorCorpsCollection = Backbone.Collection.extend({ model : DonorCorpModel });
// Views
var DonorCorpsListView = Backbone.View.extend({
initialize : function () {
_.bindAll(this, 'render');
this.template = Handlebars.compile($('#pre-sort-actions-template').html())
this.collection.bind('reset', this.render);
},
render : function () {
$(this.el).html(this.template({}));
this.collection.each(function (donorCorp) {
var donorCorpBinView = new DonorCorpBinView({
model : donorCorp,
list : this.collection
});
this.$('.donor-corp-bins').append(donorCorpBinView.render().el);
});
return this;
}
});
var DonorCorpBinView = Backbone.View.extend({
tagName : 'li',
className : 'donor-corp-bin',
initialize : function () {
_.bindAll(this, 'render');
this.model.bind('change', this.render);
this.template = Handlebars.compile($('#pre-sort-actions-donor-corp-bin-view-template').html());
},
render : function () {
var content = this.template(this.model.toJSON());
$(this.el).html(content);
return this;
}
})
// Routing
var App = Backbone.Router.extend({
routes : {
'' : 'home',
'pre-sort' : 'preSort'
},
initialize : function () {
// ...
},
home : function () {
// ...
},
preSort : function () {
if (donorCorps.length < 1) {
donorCorps.url = 'http://my/api/donor-corps';
donorCorps.fetch();
}
var donorCorpsList = new DonorCorpsListView({ collection : donorCorps }).render().el;
$('#document-action-panel').empty().append(donorCorpsList);
// ...
}
});
// Private members
var app;
var donorCorps = new DonorCorpsCollection();
// Public operations
return {
Init: function () { return init(); }
};
// Private operations
function init () {
app = new App();
Backbone.history.start({ root: '/myApp/', pushState: true });
docr.navigate('/', { trigger: true, replace: true});
}
}(myModule || {}));
Everything runs just fine when I run the app...it navigates to the home view as expected. I have links setup with handlers to navigate to the different routes appropriately, and when I run app.navigate('pre-sort', { trigger: true, replace: true}) it runs just fine, and renders the expected items in the list as expected.
The problem comes when I try to go back to the home view, and then back to the pre-sort view again...the items in the list don't get displayed on the screen as I am expecting them to. I'm just getting the empty pre-sort-actions-template rendered with no child views appended to it. I've stepped through the code, and can see that the collection is populated and the items are there...but for some reason, my code isn't rendering them to the view properly, and I can't seem to figure out why or what I'm doing wrong. Any ideas?? I'm pretty new to backbone, so I'm sure this code isn't written totally right...constructive feedback is welcome. Thanks.
how is the code rendering it to the view after going back home, then to pre-sort again? could you provide some details on that? duplicate items? empty view?
Also, I like to keep an index of my sub-views when I render them so the parent view can always access them regardless of scope. I found a really nice technique for this here: Backbone.js Tips : Lessons from the trenches
A quick overview of the pattern I'm referring to is as follows:
var ParentView = Backbone.View.extend({
initialize: function () {
this._viewPointers = {};
},
render: function (item) {
this._viewPointers[item.cid] = new SubView ({
model: item
});
var template = $(this._viewPointers[item.cid].render().el);
this.$el.append(template);
}
});
var SubView = Backbone.View.extend({
initialize: function () {
this.model.on("change", this.render, this);
},
render: function () {
this.$el.html( _.template($('#templateId').html(), this.model.attributes) );
return this;
}
});
I realize this answer is rather "broad," but it will be easier to answer with more specifics if I can understand the exact issue with the rendering. Hope its of some help regardless :)

Resources