Backbone View firing event twice - javascript

I have a backbone view that is initialized via route, but i when i navigate to another route and return to the previous one again via link, the events in the view get fired twice
Heres my router
define(['underscore','backbone','views/projects/view_project',
'views/projects/project_tasks','views/projects/project_milestones',
'views/projects/project_tasklists','views/projects/project_documents'
],
function( _,Backbone,ProjectTasks,ProjectMilestones,
ProjectTasklists,ProjectDocuments) {
var ProjectRouter = Backbone.Router.extend({
initialize: function(projects) {
if(projects) {
this.projects = projects;
}
},
//url routes mapped to methods
routes: {
"project/:id":"get_project",
"project/:id/milestones":"get_project_milestones",
"project/:id/tasks":"get_project_tasks",
"project/:id/tasklists":"get_project_tasklists",
"project/:id/documents":"get_project_documents"
},
get_project: function(id) {
UberERP.UI.loadpage("#project-view");
var project_view = new ProjectView(this.projects,id);
},
get_project_tasks: function(id) {
UberERP.UI.loadpage("#project-tasks-view");
var project_tasks_view = new ProjectTasks(id,this.projects);
},
get_project_tasklists: function(id) {
UberERP.UI.loadpage("#project-tasklist-view");
var project_tasks_view = new ProjectTasklists(id,this.projects);
},
get_project_milestones: function(id) {
UberERP.UI.loadpage("#project-milestones-view");
var project_milestones_view = new ProjectMilestones(id,this.projects);
},
get_project_documents: function(id) {
UberERP.UI.loadpage("#project-documents-view");
var project_documents_view = new ProjectDocuments(id,this.projects);
}
});
return ProjectRouter;
});
and a snipper from the view
events: {
"click input[name=task]":"select_task",
"click a.remove-icon":"remove_task",
"click td.view-task":"view_task",
"click #project-tasks-table .sort-by-status":"sort_by_status",
"click #project-tasks-table .group-filter-btn":"sort_by_task_list"
},
select_task: function( event ) {
var el = $(event.currentTarget);
row = el.parent('td').parent('tr');
console.log(el.val());
if(row.hasClass('active')) {
row.removeClass('active');
}
else {
row.addClass('active');
}
}
I have a line in the select_task method that logs the value of the clicked input element.
When the view is initially called it works properly and logs to the console. But after navigating to another route and returning back, the value of the input element is logged twice when clicked. What could be wrong?

I think you just find your self in the middle of a Backbone ghost View issue.
Try to remove and unbind all your Views when you are moving from one route to another.

Related

Marionette CollectionView not re-rendering after collection.fetch

I have an 'email' style app that displays messages grouped by the date. When the app loads, a shallow collection of messages are fetched and loaded into a backbone collection. Each model in the collection represents a list of messages within a grouping. The MessageGroup represents a group of messages and the MessagesView displays the groups of messages.
This all works well until the collection is fetched again like after a filter is applied, only the group headers are displayed, not the messages inside. I've tried triggering an event that the MessagesView can listen for, then re-render itself but I get an error: listening.obj.off is not a function.
var MessageModel = Backbone.Model.extend({});
var MessageCollection = Backbone.Collection.extend({
model: MessageModel
});
var GroupModel = Backbone.Model.extend({});
var GroupCollection = Backbone.Collection.extend({
model: GroupModel,
url: '/messages/recipient',
parse: function (response) {
// Create a grouped JSON to render nested views with
var messageArray = [];
var groupedlist = _.groupBy(response.messages, function(model) {
return model.publishDate;
});
_.forEach(groupedlist, function(n, key) {
var grouping = {};
grouping.group = key;
grouping.list = n;
messageArray.push(grouping);
});
return messageArray;
},
fetchMessages: function() {
this.fetch({
data: filtermodel.toJSON(),
success: function() {
var messagecollection = new MessageCollection();
// Loop through each grouping and set sub-collections
groupcollection.each(function(group) {
var list = group.get('list');
messagecollection.reset(list);
group.set('list', messagecollection);
});
}
});
}
});
// Model to track applied filters
var FilterModel = Backbone.Model.extend({
defaults: {
folder: 0
}
});
// ------------------------ VIEWS ------------- //
// View for a single Message
var MessageView = Backbone.Marionette.ItemView.extend({
template: require('../../../templates/activities/message-item.ejs'),
events: { 'click li.item': 'getMessageDetail' },
getMessageDetail: function(e){
this.triggerMethod('showDetail', this.model);
//initMessageDetail(this.model);
}
});
// Grouped container view for a list of Messages within a group
var MessageGroup = Backbone.Marionette.CompositeView.extend({
template: require('../../../templates/activities/message-list.ejs'),
className: "list-view-group-container",
childView: MessageView,
childViewContainer: "ul.viewcontainer",
initialize: function() {
this.collection = this.model.get('list');
}
});
// Top level view for all grouped messages
var MessagesView = Backbone.Marionette.CollectionView.extend({
childView: MessageGroup,
initialize: function() {
this.collection.on('change', this.log, this);
},
log: function() {
console.log('triggered log');
}
});
// View for selected message detail
var MessageDetailView = Backbone.Marionette.ItemView.extend({
template: require('../../../templates/activities/message-detail.ejs'),
className: "message-content-wrapper"
});
// View for filter selection bar
var MessageFilterView = Backbone.Marionette.ItemView.extend({
template: require('../../../templates/activities/message-filter-bar.ejs'),
events: {
'click #search-btn': function() {
filtermodel.set('search', $('#search-input').val());
groupcollection.fetchMessages();
}
}
});
var filtermodel = new FilterModel();
var groupcollection = new GroupCollection();
// Fetch messages first run
groupcollection.fetchMessages();
// LayoutView to display in center panel of application
module.exports = ViewMessages = Marionette.LayoutView.extend({
template: require('../../../templates/activities/viewmessages.ejs'),
className: 'content full-height',
regions: {
'messagelistregion': '#messageList',
'messagedetailregion': '.message-detail',
'messagefilterregion': '.filter-bar'
},
childEvents: { 'showDetail': 'onMessageSelected' },
onMessageSelected: function (childView, childViewModel) {
var that = this;
var detailModel = childViewModel.clone();
var messageDetailView = new MessageDetailView({model:detailModel});
that.messagedetailregion.show(messageDetailView);
},
onShow: function(){
var that = this;
var messagesview = new MessagesView({
collection: groupcollection
});
var messageFilterView = new MessageFilterView();
that.messagelistregion.show(messagesview);
$("#messageList").ioslist();
that.messagefilterregion.show(messageFilterView);
this.messagedetailregion.on('show', function() {
console.log('message detail region shown:' + that.messagedetailregion.currentView);
})
}
});
I'm thinking its because the work that is done to build out the groupings of messages inside the success callback doesn't finish before the reset event is triggered and the view is refreshed. How can I get the MessagesView to update after subsequent fetches?
UPDATE:
I moved the post-success logic of grouping the collection into its hierarchical tree/leaf structure to a custom event (fetchSuccess) in the top level collectionview (MessagesView):
var MessagesView = Backbone.Marionette.CollectionView.extend({
childView: MessageGroup,
initialize: function() {
this.collection.on('fetch:success', this.fetchSuccess, this);
},
fetchSuccess: function() {
var messagecollection = new MessageCollection();
groupcollection.each(function(group) {
var list = group.get('list');
messagecollection.reset(list);
group.set('list', messagecollection);
});
}
});
It is being triggered in the success callback of the fetch. I'm pretty sure this is a good way of rendering the collection, but I cant seem to get around the error in Marionette:
**Uncaught TypeError: listening.obj.off is not a function**
Anyone have any ideas why this collectionview will not re-render??
I was able to determine that the creation of the models in the collection occurred after the reset event, but before the structure of the nested models was built out:
success: function() {
var messagecollection = new MessageCollection();
// Loop through each grouping and set sub-collections
groupcollection.each(function(group) {
var list = group.get('list');
messagecollection.reset(list);
group.set('list', messagecollection);
});
};
After any filter event, grouping, sorting etc, the collection structure needs to be modified into this nested hierarchy each time. The view was picking up the reset event before the structure was built out so the child views had no data to render. I fixed this by cloning the original collection after the changes and having the views render the cloned collection:
groupcollection.fetch({
reset: true,
data: filtermodel.toJSON(),
success: function() {
groupcollection.each(function(group) {
var list = group.get('list');
var messagecollection = new MessageCollection(list);
group.set('list', messagecollection);
});
filteredcollection.reset(groupcollection.toJSON());
}
});

Backbone Marionette Layout Region not closing when returning to module

I have a Backbone Marionette application whose layout's regions are not working properly. My app is structured using Require modules and some of these modules' regions are failing to close themselves when the module has been returned to a second time. The first time through the regions are closing as expected but upon return the layout object no longer contains the region objects it did during the first visit: I am using the browser debugger to ascertain this difference.
Here is my Module code:-
define(["marionette", "shell/shellapp", "interaction/videoreveal/model", "interaction/videoreveal/controller", "utils/loadingview", "utils/imagepreloader"], function(Marionette, shellApp, Model, Controller, LoadingView, imagePreloader){
var currentModuleModel = shellApp.model.get("currentInteractionModel"); // get module name from menu model
var Module = shellApp.module(currentModuleModel.get("moduleName")); // set application module name from menu model
Module.init = function() { // init function called by shell
//trace("Module.init()");
Module.model = new Model(shellApp.interactionModuleData); // pass in loaded data
Module.controller = new Controller({model: Module.model, mainRegion:shellApp.mainRegion}); // pass in loaded data and region for layout
Module.initMain();
};
Module.initMain = function() {
trace("Module.initMain()");
shellApp.mainRegion.show(new LoadingView());
// do some preloading
var imageURLs = this.model.get('imagesToLoad');
imagePreloader.preload(imageURLs, this.show, this);
};
Module.show = function() {
Module.controller.show();
};
Module.addInitializer(function(){
//trace("Module.addInitializer()");
});
Module.addFinalizer(function(){
//trace("Module.addFinalizer()");
});
return Module;
});
Here is the Controller class which is handling the Layout and Views:-
define(["marionette", "shell/vent", "shell/shellapp", "interaction/videoreveal/layout", "interaction/videoreveal/views/mainview", "ui/feedbackview", "ui/videoview"], function(Marionette, vent, shellApp, Layout, MainView, FeedbackView, VideoView){
return Marionette.Controller.extend({
initialize: function(options){
trace("controller.initialize()");
// store a region that will be used to show the stuff rendered by this component
this.mainRegion = options.mainRegion;
this.model = options.model;
this.model.on("model:updated", this.onModelUpdate, this);
this.layout = new Layout();
this.layout.render();
this.mainView = new MainView({model:this.model, controller:this});
this.feedbackView = new FeedbackView({feedbackBoxID:"vrFeedbackBox"});
this.videoView = new VideoView({videoContainerID:"vrVideoPlayer"});
vent.on("feedbackview:buttonclicked", this.onFeedbackClick, this);
vent.on("videoview:buttonclicked", this.onVideoClick, this);
},
// call the "show" method to get this thing on screen
show: function(){
// get the layout and show it
this.mainRegion.show(this.layout);
this.model.initInteraction();
},
initFeedback: function (index) {
this.model.set("currentItem", this.model.itemCollection.models[index]);
this.model.set("itemIndex", index);
this.model.initFeedback();
},
initVideo: function (index) {
this.model.set("currentItem", this.model.itemCollection.models[index]);
this.model.set("itemIndex", index);
this.model.initVideo();
},
finalizer: function() {
this.layout.close();
},
// events
onFeedbackClick: function(e) {
this.layout.overlayRegion.close();
},
onVideoClick: function(e) {
this.layout.overlayRegion.close();
},
onFinishClick: function() {
this.model.endInteraction();
},
onFeedbackClosed: function() {
this.layout.overlayRegion.off("close", this.onFeedbackClosed, this);
if (this.model.get("currentItem").get("correct") === true) {
this.model.initThumb();
}
},
onModelUpdate: function() {
trace("controller onModelUpdate()");
switch (this.model.get("mode")) {
case "initInteraction":
this.layout.mainRegion.show(this.mainView);
break;
case "initFeedback":
this.layout.overlayRegion.on("close", this.onFeedbackClosed, this);
this.feedbackView = new FeedbackView({feedbackBoxID:"vrFeedbackBox"})
this.feedbackView.setContent(this.model.get("currentItem").get("feedback"));
this.layout.overlayRegion.show(this.feedbackView );
break;
case "initVideo":
this.layout.overlayRegion.show(new VideoView({videoContainerID:"vrVideoPlayer"}));
break;
case "interactionComplete":
vent.trigger('interactionmodule:completed', this);
vent.trigger('interactionmodule:ended', this);
break;
}
}
});
});
Here is the FeedbackView class:-
define(['marionette', 'tweenmax', 'text!templates/ui/feedbackWithScrim.html', 'shell/vent'], function (Marionette, TweenMax, text, vent) {
return Marionette.ItemView.extend({
template: text,
initialize: function (options) {
this.model = options.model;
this.content = options.content; // content to add to box
this.feedbackBoxID = options.feedbackBoxID; // id to add to feedback box
this.hideScrim = options.hideScrim; // option to fully hide scrim
},
ui: {
feedbackBox: '.feedbackBox',
scrimBackground: '.scrimBackground'
},
events : {
'click button': 'onButtonClick' // any button events within scope will be caught and then relayed out using the vent
},
setContent: function(content) {
this.content = content;
},
// events
onRender: function () {
this.ui.feedbackBox.attr("id", this.feedbackBoxID);
this.ui.feedbackBox.html(this.content);
if (this.hideScrim) this.ui.scrimBackground.css("display", "none");
this.$el.css('visibility', 'hidden');
var tween;
tween = new TweenMax.to(this.$el,0.5,{autoAlpha:1});
},
onButtonClick: function(e) {
trace("onButtonClick(): "+ e.target.id);
vent.trigger("feedbackview:buttonclicked", e.target.id) // listen to this to catch any button events you want
},
onShow : function(evt) {
this.delegateEvents(); // when rerendering an existing view the events get lost in this instance. This fixes it.
}
});
});
Any idea why the region is not being retained in the layout when the module is restarted or what I can do to correct this?
Much thanks,
Sam
Okay.... I got there in the end after much debugging. I wouldn't have got there at all if it wasn't for the generous help of the others on this thread so THANKYOU!
Chris Camaratta's solutions definitely pushed me in the right direction. I was getting a Zombie layout view in my Controller class. I decided to switch a lot of my on listeners to listenTo listeners to make their decoupling and unbinding simpler and hopefully more effective. The key change though was to fire the Controller class's close method. I should have had this happening all along but honestly it's my first time getting into this mess and it had always worked before without needing to do this. in any case, lesson hopefully learned. Marionette does a great job of closing, unbinding and handling all of that stuff for you BUT it doesn't do everything, the rest is your responsibility. Here is the key modification to the Module class:-
Module.addFinalizer(function(){
trace("Module.addFinalizer()");
Module.controller.close();
});
And here is my updated Controller class:-
define(["marionette", "shell/vent", "shell/shellapp", "interaction/videoreveal/layout", "interaction/videoreveal/views/mainview", "ui/feedbackview", "ui/videoview"], function(Marionette, vent, shellApp, Layout, MainView, FeedbackView, VideoView){
return Marionette.Controller.extend({
initialize: function(options){
trace("controller.initialize()");
// store a region that will be used to show the stuff rendered by this component
this.mainRegion = options.mainRegion;
this.model = options.model;
this.listenTo(this.model, "model:updated", this.onModelUpdate);
this.listenTo(vent, "feedbackview:buttonclicked", this.onFeedbackClick);
this.listenTo(vent, "videoview:buttonclicked", this.onVideoClick);
},
// call the "show" method to get this thing on screen
show: function(){
// get the layout and show it
// defensive measure - ensure we have a layout before axing it
if (this.layout) {
this.layout.close();
}
this.layout = new Layout();
this.mainRegion.show(this.layout);
this.model.initInteraction();
},
initFeedback: function (index) {
this.model.set("currentItem", this.model.itemCollection.models[index]);
this.model.set("itemIndex", index);
this.model.initFeedback();
},
initVideo: function (index) {
this.model.set("currentItem", this.model.itemCollection.models[index]);
this.model.set("itemIndex", index);
this.model.initVideo();
},
onClose: function() {
trace("controller onClose()");
if (this.layout) {
this.layout.close();
}
},
// events
onFeedbackClick: function(e) {
this.layout.overlayRegion.close();
},
onVideoClick: function(e) {
this.layout.overlayRegion.close();
},
onFinishClick: function() {
this.model.endInteraction();
},
onFeedbackClosed: function() {
if (this.model.get("currentItem").get("correct") === true) {
this.model.initThumb();
}
},
onModelUpdate: function() {
trace("controller onModelUpdate()");
switch (this.model.get("mode")) {
case "initInteraction":
this.layout.mainRegion.show(new MainView({model:this.model, controller:this}));
break;
case "initFeedback":
var feedbackView = new FeedbackView({feedbackBoxID:"vrFeedbackBox", controller:this});
feedbackView.setContent(this.model.get("currentItem").get("feedback"));
this.layout.overlayRegion.show(feedbackView);
this.listenTo(this.layout.overlayRegion, "close", this.onFeedbackClosed);
break;
case "initVideo":
this.layout.overlayRegion.show(new VideoView({videoContainerID:"vrVideoPlayer"}));
break;
case "interactionComplete":
vent.trigger('interactionmodule:completed', this);
vent.trigger('interactionmodule:ended', this);
break;
}
}
});
});
If I understand your question correctly your views do not work well after they are closed and re-opened.
It looks like you are using your layout/views after they are closed, and keeping them for future use with these references:
this.feedbackView = new FeedbackView();
Marionette does not like this, once you close a view, it should not be used again. Check out these issues:
https://github.com/marionettejs/backbone.marionette/pull/654
https://github.com/marionettejs/backbone.marionette/issues/622
I would advise you not to store these views and just recreate them when you show them
layout.overlayRegion.show(new FeedbackView());
#ekeren's answer is essentially right; I'm just expanding on it. Here's some specific recommendations that I believe will resolve your issue.
Since you're utilizing regions you probably don't need to create your views ahead of time:
initialize: function(options) {
this.mainRegion = options.mainRegion;
this.model = options.model;
this.model.on("model:updated", this.onModelUpdate, this);
vent.on("feedbackview:buttonclicked", this.onFeedbackClick, this);
vent.on("videoview:buttonclicked", this.onVideoClick, this);
},
Instead, just create them dynamically as needed:
onModelUpdate: function() {
switch (this.model.get("mode")) {
case "initInteraction":
this.layout.mainRegion.show(new MainView({model:this.model, controller:this}));
break;
case "initFeedback":
var feedbackView = new FeedbackView({feedbackBoxID:"vrFeedbackBox"})
feedbackView.setContent(this.model.get("currentItem").get("feedback"));
this.layout.overlayRegion.show(feedbackView);
this.layout.overlayRegion.on("close", this.onFeedbackClosed, this);
break;
case "initVideo":
this.layout.overlayRegion.show(new VideoView({videoContainerID:"vrVideoPlayer"}));
break;
case "interactionComplete":
vent.trigger('interactionmodule:completed', this);
vent.trigger('interactionmodule:ended', this);
break;
}
}
The layout is a bit of a special case since it can be closed in several places, but the principle applies:
show: function(){
// defensive measure - ensure we have a layout before axing it
if (this.layout) {
this.layout.close();
}
this.layout = new Layout();
this.mainRegion.show(this.layout);
this.model.initInteraction();
},
Conditionally cleanup the layout:
finalizer: function() {
if (this.layout) {
this.layout.close();
}
},

increment issue with backbone

I could not find anything that mentioned my issue, I am using my router file in backbone to navigate to a different page based on the current page ID through the use of next and previous buttons. However, when I click next or previous once, it works fine, but the second time the button is clicked, the functin is called twice instead of once, and then if I click it another time it seems to be called even more than twice to a point where it seems to go berserk.
Here is my router file:
define([
'jquery',
'underscore',
'backbone',
'views/page',
'models/search',
'views/search',
'text!templates/search.html',
'models/song',
'text!templates/song.html'
], function($, _, Backbone, PageV, SearchM, SearchV, SearchT, SongM, SongT) {
var vent = _.extend({}, Backbone.Events);
var AppRouter = Backbone.Router.extend ({
routes: {
'page/:id': 'showPage',
'search': 'showView' ///:page
}
});
var initialize = function () {
var app_router
app_router = new AppRouter;
console.log('router file hit');
app_router.on('route:showPage', function (id) {
console.log('page rendered');
var songies, collected, songM;
songM = new SongM();
songM.localStorage = new Backbone.LocalStorage("music");
songM.localStorage.findAll().forEach(function (i) {
collected = i;
});
var songPages = Math.ceil(collected.music.length / 25); //10 pages
var start = id * 25;
var songies = collected.music.splice(start, 25);
var titles = {
week: collected.week,
year: collected.year,
channel: collected. channel
};
var page = new PageV({model: songM, collection: songies, vent: vent, titles: titles});
page.render(id);
vent.on('next', advance);
vent.on('previous', moveBack);
var currentId = parseInt(id);
//PROBLEM OCCURS TO THE BOTTOM TWO FUNCTIONS, and they are triggered by the vent.on above.
function moveBack () {
console.log('here is the current ID');
var newPage = 'page/' + (currentId - 1);
if(currentId <= songPages && currentId > 0 ) {
app_router.navigate(newPage, true);
} else {
app_router.navigate('search', true);
}
}
function advance () {
console.log('here is the current ID');
var newPage = 'page/' + (currentId + 1);
console.log(currentId);
console.log(songPages);
console.log(newPage);
if(currentId < songPages && currentId >=0 ) {
app_router.navigate(newPage, true);
} else {
app_router.navigate('search', true);
}
}
});
app_router.on('route:showView', function () {
console.log('search page loaded');
var searchM = new SearchM();
var search = new SearchV({model: searchM, vent: vent}); //
search.render();
vent.on('nextPage', printCons);
function printCons () {
console.log('changing pages');
app_router.navigate('page/0', true);
};
});
Backbone.history.start();
};
return {
initialize: initialize
};
});
Here is the page with the page view:
define([
'jquery',
'underscore',
'backbone',
'models/song',
'collections/songs',
'views/song',
'text!templates/page.html',
'text!templates/song.html'
], function($, _, Backbone, Song, Songs, SongV, PageT, SongT){
var Page = Backbone.View.extend({
el: $("#Sirius"),
events: {
"click .prev": "previous",
"click .next": "next"
},
previous: function () {
this.options.vent.trigger('previous');
},
next: function () {
this.options.vent.trigger('next');
},
render: function () {
var headings = this.options.titles;
var info = {
week: headings.week,
channel: headings.channel,
year: headings.year
}
var pagetemp = _.template( PageT, info);
this.$el.html( pagetemp );
var songColl = this.collection;
var songV = new SongV({collection: songColl});
songV.render();
}
});
return Page;
});
The only problems I can think of is that it somehow remembers the past instance and calls the function on both of them... or else I have no idea why it gets called twice... because if I refresh the page with that id and then click previous or next it does not increment it twice... so it must be in memory or something not sure how to go around it...
The problem is with the following event handler bindings within your app_router.on event handler:
vent.on('next', advance);
vent.on('previous', moveBack);
Each time you show a new route, you are binding those functions to the event aggregator again. You should move both of these bindings outside to the initialize function so you don't bind it multiple times.
Another quick fix, if for some reason moving these bindings outside breaks the functionality, would be to unbind the previous bindings and then bind the event handlers again:
vent.off('next');
vent.on('next', advance);
vent.off('previous');
vent.on('previous', moveBack);
See the Backbone docs for more details regarding this.
The problem is that you're creating a new view every time you change the route, but you're never deleting the old views. You're probably doubling the views every time you click on the next one!
Here's a post that might help:
Disposing of view and model objects in Backbone.js

Change Views Content based on different modules event

My content box module enumerates a collection and creates a container view for each item ( passing the model to the view). It sets the initial content to the content property of its model. Base on a layout property in the model the container view is attached to the DOM. This is kicked off by the “_contentBoxCreate” method.
The content box module responds to clicks to sub items in a sidemenu. The sidemenu is implemented in a different module. The sidemenu sub click event passes an object along as well that contains a sub_id and some text content. I want to take the content from this object and use it to update container view(s).
Currently I’m doing this via the “_sideMenuClick” method. In backbonejs is there a best practice for updating a views content, given that no data was changed on its model?
thanks,
W.L.
APP.module("contentbox", function(contentbox) {
//Model
var Contentbox = Backbone.Model.extend({});
//Collection
var Contentboxes = Backbone.Collection.extend({
model: Contentbox,
url: 'ajax/contentboxResponse/tojson'
});
/*
* View:
*/
var Container = Backbone.View.extend({
initialize: function() {
contentbox.on('update', jQuery.proxy(this.update, this));
contentbox.on('refresh', jQuery.proxy(this.render, this));
var TemplateCache = Backbone.Marionette.TemplateCache;
this.template = TemplateCache.get("#contentbox-container");
},
render: function() {
var content = this.model.get('content').toString();
var html = this.template({content: content});
this.$el.html(html);//backbone element
return this;
},
update: function(fn) {
var content = fn.apply(this);
if (content !== null) {
var html = this.template({content: content});
this.$el.html(html);
}
}
});
//collection
var contentboxes = new Contentboxes();
var _sideMenuToggle = function() {
contentbox.trigger('refresh');
};
var _sideMenuClick = function(sideMenu) { //view contex
var fn = function() {
// this fn will have the context of the view!!
var linksub = this.model.get('linksub').toString();
if (linksub === sideMenu.id.toString()) {
return sideMenu.content.toString();
}
return null;
};
contentbox.trigger('update', fn);
};
var _contentBoxCreate = function() {
var create = function(cboxes) {
cboxes.each(function(model) {
var layout = "#body-" + model.get('layout');
var $el = jQuery(layout);
var container = new Container({model: model});
$el.append(container.render().$el);
});
};
contentboxes.fetch({
success: create
});
};
this.on("start", function() {
_contentBoxCreate();
});
this.addInitializer(function() {
APP.vent.on('sidemenu:toggle', _sideMenuToggle);
APP.reqres.setHandler('sidemenu:submenu', _sideMenuClick);//event and content...
//from another module
});
});
UPDATE:
Changed the view...
/*
* View
*/
var Container = Backbone.View.extend({
initialize: function() {
this.renderableModel = this.model; // Define renderableModel & set its initial value
contentbox.on('update', this.update, this);
contentbox.on('refresh', this.reset, this); // 3rd param gives context of view
var TemplateCache = Backbone.Marionette.TemplateCache;
this.template = TemplateCache.get("#contentbox-container");
},
render: function() {
var content = this.renderableModel.get('content').toString();
var html = this.template({content: content});
this.$el.html(html);//backbone element
return this;
},
update: function(fn) {
/**
* The "update" event is broadcasted to all Container views on the page.
* We need a way to determine if this is the container we want to update.
* Our criteria is in the fn
*/
var content = fn.apply(this); //criteria match return content, else null.
/*
* The render method knows how to render a contentbox model
*/
if (content !== null) {
this.renderableModel = new Contentbox();
this.renderableModel.set({content: content}); //add content to new contentbox model
this.render(); //Rerender the view
}
},
reset: function() {
this.renderableModel = this.model;
this.render(); // restore view to reflect original model
}
});

backbone js , binding model to view

I'm trying out backbonejs but got stuck on how to bind the model to the view.
yepnope({
load : ["/static/js/lib/jquery-1.6.2.min.js", "/static/js/lib/underscore-min.js", "/static/js/lib/backbone-min.js"],
complete: nameList
});
function nameList() {
var PageItem = Backbone.Model.extend({
defaults: {name: "default name" }
});
var Page = Backbone.Collection.extend({
model: PageItem
});
var page = new Page;
var AppView = Backbone.View.extend({
el: $("#names"),
$artistList: $('#names_list'),
$inputField: $('input#new_name'),
events: {
"keypress input": "processKeyPress"
},
processKeyPress: function(event){
if(event.charCode == 13) {
event.preventDefault();
this.addName();
}
},
addName: function(event) {
var newName = this.$inputField.val();
this.$artistList.prepend('<li>' + newName + '</li>');
page.push(new PageItem({name: newName}));
// I've also tried page.push({name: newName});
} });
var app = new AppView;}
When I press enter on the input field, it runs processKeyPress which calls addName, the new name is added the the html list but not pushed onto the model. I keep getting:
Uncaught TypeError: Object function (a){return new l(a)} has no method 'isObject'
ok, please test this out for yourself, but it appears to work with the Github version of underscore so maybe there was a bug which has been fixed.. http://jsfiddle.net/yjZVd/6/

Categories

Resources