Backbone.Marionette Layout: Access region.layout.view directly - javascript

I have a Marionette application which as more than 1 region.
App.addRegions({
pageRegion: '#page',
contentsRegion :'#contents'
});
As part of App.pageRegion, I add a layout.
App.ConfiguratorLayout = Marionette.Layout.extend({
template: '#configurator-page',
regions: {
CoreConfiguratorRegion: '#Core-Configurator-Region',
SomeOtherRegion:'#someOtherregion'
}
});
This layout is rendered before the application starts.
App.addInitializer(function() {
var configLayout = new App.ConfiguratorLayout();
App.pageRegion.show(configLayout);
});
Later on in the application, I just need to change the contents of the configLayout.
I am trying to achieve something like this.
App.pageRegion.ConfiguratorLayout.CoreConfiguratorRegion.show(someOtherLayout);
Is there a way to do this besides using DOM selector on the $el of App.pageRegion.
App.pageRegion.$el.find('#...')

Instead of in an app initializer, move the configLayout initialization code into a controller that can keep a reference to it and then show() something in one of its regions.
// pseudocode:
ConfigController = Marionette.Controller.extend({
showConfig: function() {
var layout = new App.ConfiguratorLayout();
App.pageRegion.show(configLayout);
var someOtherLayout = new App.CoreConfiguratorLayout();
layout.coreConfiguratorRegion.show(someOtherLayout);
// ... maybe create and show() some views here?
}
});
App.addInitializer(function() {
var controller = new ConfigController();
// more likely this would be bound to a router via appRoutes, instead of calling directly
controller.showConfig();
});

Related

How do I prevent backbone remove() from removing "el" in a view?

I want remove a view before creating a new one. But my requirement is view.remove() should remove the view but not delete the el element. Having said this, I do not want to set tagName as it creates a new element which is unnecessary. Is there any way to remove a view from the memory leaving the el content cleared?
You can override Backbone's view remove method from within your abstract view:
remove: function() {
// this._removeElement();
this.$el.empty();
this.stopListening();
return this;
}
Default source code: http://backbonejs.org/docs/backbone.html#section-158
I have solved this before with a disposable launcher view.
make sure your html contains a (class or id) anchor for your disposable view:
<div class="content-container"></div>
then make a LauncherView:
var LauncherView = Backbone.View.extend({
initialize: function(options) {
this.render();
},
render: function() {
this.$el.html(this.template());
return this;
},
// inner views will be bound to ".launcher-container" via
// their .el property passed into the options hash.
template: _.template('<div class="launcher-container"></div>')
});
then instantiate your disposable launcher view:
app.currentLauncherView = new LauncherView({});
and append it to your DOM anchor:
$('.content-container').append(app.currentLauncherView.el);
then you can instantiate a view that will attach to the disposable launcher view:
app.throwAway1 = new DisposableView({el: '.launcher-container'});
And then when you want to destroy that view, you can do so with:
app.throwAway1.off();
app.throwAway1.remove();
app.currentLauncherView.remove();
Then you can put up a new view by instantiating a new LauncherView, attaching it to the DOM, and making your next view appear by binding it to '.launcher-container' .
app.currentLauncherView = new LauncherView({});
$('.content-container').append(app.currentLauncherView.el);
app.throwAway2 = new DisposableView({el: '.launcher-container'});

backbone js - table pagination updating all visited views

I'm quite new to backbone so there could be a really simple solution to this problem. I have an app where you can view a show page of which there is a table I'm adding pagination to. I have created a utility object Table to handle the pagination so it can be used on every table on each show page:
var Table = function(rowsStart, increment, data) {
this.rowsStart = rowsStart;
this.increment = increment;
this.data = data;
this.totalRows = _.size(data);
this.totalRowsRoundUp = Math.ceil(_.size(data)/10)*10;
this.paginate = function(paginateVol) {
// Scope the figures
this.rowsStart += paginateVol
this.increment += paginateVol
rS = this.rowsStart;
inc = this.increment;
// Show first increment results
var rowsToDisplay = [];
$.each(this.data, function(i,e){
if (i < inc && i >= rS) {
rowsToDisplay.push(e)
}
});
// Send back the rows to display
return rowsToDisplay
};
}
This works fine when visiting the first show page table in the backbone history but when I visit further show pages and action this pagination object it triggers on all visited table views and produces weird results on my current view.
My View look like this:
// Create a view for the outer shell of our table - not inclusive of rows
queriesToolApp.ReportKeywordsView = Backbone.View.extend({
el: '#report-queries-js',
events: {
'click #prev' : 'clickBack',
'click #next' : 'clickNext'
},
initialize: function() {
// compile our template
this.template = _.template($('#tpl_indiv_report').html());
// create an instance of the Table paginator object
this.paginator = new Table(0, 10, this.collection.attributes);
},
render: function(paginateVol) {
// Scope this
_this = this;
var data = _this.collection.attributes;
// Render the script template
this.$el.html(this.template());
// Select the table body to append
var tableBody = $('#report-queries-row');
// Store the keyword object and the pagination amount
var keywordObj = this.paginator.paginate(paginateVol);
// Append keyword data to page
$.each(keywordObj, function(index, keyword){
// Create a new instance of the individual view
var reportKeywordIndView = new queriesToolApp.ReportKeywordIndView({model: keyword})
// append this to the table
tableBody.append(reportKeywordIndView.render().el);
// start table listen when last row appended
});
},
clickBack: function() {
// Render the view passing in true as it's going back
this.render(-10);
},
clickNext: function() {
// Render the view passing in nothing as default is forward
this.render(10);
}
})
Here is the individual view:
queriesToolApp.ReportKeywordIndView = Backbone.View.extend({
tagName: 'tr',
className: 'table-row',
initialize: function() {
// Define the template and compile the template in the view
this.template = _.template($('#tpl_indiv_report_row').html());
},
render: function() {
// Set the view with the data provided from the model
this.$el.html(this.template(this.model));
return this;
}
})
And I start backbone here:
$(function() {
// create a new instance of the router
var router = new queriesToolApp.AppRouter();
// Record the history of the url hash fragments
Backbone.history.start();
});
Is there a way around this?
I think to make it work keeping the objects you defined, I'd be helpful to see the AppRouter code. One thing seems certian: the ReportKeywordIndView instances keep getting created but probably not being deleted. This could work a different way, without that extra view.
As its written here, the row view looks too simple to be a backbone view. You could probably fix this by changing the template to include the TR tag, remove the view and make some adjustments in the main view.
This can go in the initialize function:
this.rowTemplate = _.template($('#tpl_indiv_report_row').html());
This can replace the each code which creates the row view and adds it:
// append this to the table
tableBody.append(_this.rowTemplate(keyword));

Backbone Marionette: can I supply options to a collectionView's emptyView?

The scenario is this: I have a collectionView that gets used in a couple of places. I pass a few options into the view to change certain display aspects (verbiage mostly), since the behavior is exactly the same everywhere.
I'd really like to extend this customization to the emptyView, but I can't find a way to do so. There seems to be no reference to the collectionView on the emptyView, and neither can I seem to access the emptyView from the collectionView, outside of defining it.
Basically, I'd like to be able to do something like this:
var noItemsView = Backbone.Marionette.ItemView.extend({
tagName: "li",
className: "no-results",
template: Handlebars.compile(noResultsTemplate),
}),
leftToggleListView = Backbone.Marionette.CollectionView.extend({
tagName: "ul",
className: "left-toggle-view-list",
emptyView: noItemsView,
initialize: function() {
this.emptyView.model.set("name": "some custom name");
}
});
And then have the noItemsView be able to render {{ name }} within its template.
Is there any way to accomplish this, short of modifying Marionette?
In the collectionView you can use the buildItemView, this function will be called also at the time to build the emptyView
I did a little demo in jsFiddle http://jsfiddle.net/rayweb_on/TN34P/
var leftToggleListView = Backbone.Marionette.CollectionView.extend({
tagName: "ul",
className: "left-toggle-view-list",
emptyView: noItemsView,
ValuethatMakesSense : "I do!",
buildItemView: function(item, ItemViewType, itemViewOptions){
var options = _.extend({model: item}, itemViewOptions);
var name = this.ValuethatMakesSense;
var view = new ItemViewType({name : name});
return view;
}
});
And in the initialize function of your item view you can read the options passed.
var noItemsView = Backbone.Marionette.ItemView.extend({
initialize : function (options) {
var name = this.options.name;
console.log(name);
},
tagName: "li",
className: "no-results",
template: "#noresults"
});
Im using a property inside the collectionView and then reading it/passing it to the empty view in the buildItemView just to test the functionality of the buildItemView function, you can do the proper logic checks and validations there.
It was a while since this question was asked, but it might be helpful to anybody looking:
Marionette CollectionView have an emptyViewOptions property now that works exactly like itemViewOptions but for emptyView:
http://marionettejs.com/docs/v2.4.2/marionette.collectionview.html#collectionviews-emptyviewoptions
Actually you can access to the emptyView in this way:
this.children._views[_.keys(this.children._views)[0]];
Seems like in the new version we will have a method that allows to get an emptyView.
https://github.com/marionettejs/backbone.marionette/pull/727

Backbone events not firing on new element added to DOM

I'm using a combination of handlebars and Backbone. I have one "container" view which has an array to hold child views. Whenever I add a new view, click events are not being bound.
My Post View:
Post.View = Backbone.View.extend({
CommentViews: {},
events: {
"click .likePost": "likePost",
"click .dislikePost": "dislikePost",
"click .addComment button": "addComment"
},
render: function() {
this.model.set("likeCount", this.model.get("likes").length);
this.model.set("dislikeCount", this.model.get("dislikes").length);
this.$('.like-count').html(this.model.get("likeCount") + " likes");
this.$('.dislike-count').html(this.model.get("dislikeCount") + " dislikes");
return this;
}, ...
My callback code in the "container" view which creates a new backbone view, attaches it to a handlebars template and shows it on the page:
success: _.bind(function(data,status,xhr) {
$(this.el).find("#appendedInputButton").val('');
var newPost = new Post.Model(data);
var newPostView = new Post.View({model: newPost, el: "#wall-post-" + newPost.id});
var source = $("#post-template").html();
var template = Handlebars.compile(source);
var html = template(newPost.toJSON());
this.$('#posts').append(html);
newPostView.render();
this.PostViews[newPost.id] = newPostView;
}, this), ...
Not sure what's going on, but this sort of code is run initially to set up the page (sans handlebars since the html is rendered server-side) and all events work fine. If I reload the page, I can like/dislike a post as well.
What am I missing?
I dont see you appending newPostView.render().el to dom .Or am i missing somehting?
Assuming the "#post-template" contains the "likePost" button. The newPostView is never added to the DOM.
Adding el to the new Post.View makes backbone search the DOM (and the element won't exist yet)
4 lines later a HTML string is added to the DOM (assuming the this.el is already in the DOM)
If you create the Post.View after the append(html) the element can be found and events would be fireing.
But the natural Backbone way would be to render the HTML string inside the Post.View render function, append the result to it's el and append that el to the #posts element.
success: function (data) {
var view = new Post.View({model: new Post.Model(data)});
this.$('#posts').append(view.render().el);
this.PostViews[data.id] = view;
}

backbone view events don't work after re-render

I'm pulling my hair out, I cannot seem to get mouse events to work on my backbone view after the view is re-rendered unless i do the most ridiculous thing:
$("a").die().unbind().live("mousedown",this.switchtabs);
I actually had this in there but decided to update to the latest backbone and try to use the new delegateEvents()function.
Here is the way my project id structured:
Appview / AppRouter
|
----->PageCollection
|
------->PageView/PageModel
------->PageView/PageModel these page view/models are not rendered
------->PageView/PageModel
|
------->PageView/PageModel
|
----->render() *when a pageview is rendered*
|
-----> Creates new
Tabcollection
|
--->TabModel/TabView <-- this is where the issue is
What happens is that the tabcollection has a main tabview to manage all of the tabs, then creates a new model/view for each tab and puts a listener to re-render the tabview whenever a tab is loaded. If the tabview is re-rendered, no mouse events work anymore unless I put that contrived jQuery statement in there.
Heres the tabview and render (ive stripped it down quite a bit)
var TabPanelView = Backbone.View.extend({
className: "tabpanel",
html: 'no content',
model: null,
rendered: false,
events:{
'click a.tab-nav': 'switchtabs'
},
initialize: function(args)
{
this.nav = $("<ol/>");
this.views = args.items;
this.className = args.classname?args.classname:"tabpanel";
this.id = args.id;
this.container = $("<section>").attr("class",this.className).attr("id",this.id);
_.bindAll(this);
return this.el
},
/*
This render happens multiple times, the first time it just puts an empty html structure in place
waiting for each of the sub models/views to load in (one per tab)
*/
render: function(args){
if(!args)
{
//first render
var nav = $("<aside/>").addClass("tab-navigation").append("<ol/>").attr("role","navigation");
var tabcontent = $("<section/>").addClass("tab-panels");
for(i = 0;i<this.views.length;i++)
{
$("ol",nav).append("<li><a rel='"+this.views[i].id+"' href='javascript:;' class='tab-nav'></a></li>");
tabcontent.append(this.views[i].el);
}
this.$el.empty().append(nav).append(tabcontent);
}
else if(args && args.update == true){
// partial render -- i.e. update happened inside of child objects
var targetid = args.what.cid;
for(i = 0;i<this.views.length;i++)
{
var curcontent = this.$el.find("div#"+this.views[i].id);
var curlink = this.$el.find("a[rel='"+this.views[i].id+"']")
if(this.views[i].cid == targetid)
{
curcontent.html($(this.views[i].el).html());
curlink.text(this.views[i].model.rawdata.header);
}
if(i>0)
{
// set the first panel
curcontent.addClass("tab-content-hide");
}
if(i==0)
{
curcontent.addClass("tab-content-show");
curlink.addClass("tab-nav-selected");
}
// this ridiculous piece of jQuery is the *ONLY* this i've found that works
//$("a[rel='"+this.views[i].id+"']").die().unbind().live("mousedown",this.switchtabs);
}
}
this.delegateEvents();
return this;
},
switchtabs: function(args){
var tabTarget = args.target?args.target:false
if(tabTarget)
{
this.$el.find("aside.tab-navigation a").each(function(a,b)
{
$(this).removeClass("tab-nav-selected")
})
$(tabTarget).addClass("tab-nav-selected");
this.$el.find("div.tab-content-show").removeClass("tab-content-show").addClass("tab-content-hide");
this.$el.find("div#"+tabTarget.rel).removeClass("tab-content-hide").addClass("tab-content-show");
}
}
});
Can anyone think of why backbone mouse events simply don't fire at all, is it because they are not on the DOM? I thought that this was where backbone was particularly useful?...
This line of code is likely your problem:
this.delegateEvents();
Remove that and it should work.
The only time you need to call delegateEvents yourself, is when you have events that are declared separately from your view's events hash. Backbone's view will call this method for you when you create an instance of the view.
When the view is being re-rendered, are you reusing the same view and just calling render() on it again, or are you deleting the view and creating a whole new view?
Either way, it looks like the cause is that the view events are not being unbound before the view is re-rendered. Derick Bailey has a great post about this.
When you re-render, 1) make sure you unbind all the events in the old view and 2) create a new view and render it
When using $(el).empty() it removes all the child elements in the selected element AND removes ALL the events (and data) that are bound to any (child) elements inside of the selected element (el).
To keep the events bound to the child elements, but still remove the child elements, use:
$(el).children().detach(); instead of $(.el).empty();
This will allow your view to rerender successfully with the events still bound and working.

Categories

Resources