MarionetteJS: Application Regions vs. Layouts [duplicate] - backbone.js

This question already has an answer here:
What to use since Marionette Application Regions are deprecated
(1 answer)
Closed 6 years ago.
I was reading the documentation of the latest version (2.3.0) and it is saying that Application Regions are now deprecated.
Application Regions
Warning: deprecated This feature is deprecated. Instead of using the
Application as the root of your view tree, you should use a Layout
View. To scope your Layout View to the entire document, you could set
its el to 'body'. This might look something like the following: var
RootView = Marionette.LayoutView.extend({ el: 'body' });
In most of the tutorials, including David Sulc's book Backbone Marionette: A Gentle Introduction it uses the following code snippet to add regions to an application.
Instead of the following example below, which uses addRegions, what should I be doing instead?
i.e.
var ContactManager = new Marionette.Application({});
ContactManager.addRegions({
mainRegion: "#main-region"
});
var ContactView = Marionette.ItemView.extend({
template: "#whatever",
ui: {
button: ".button".
},
events: {
"click #ui.button": "click",
},
click: function () {
console.log("do stuff here...");
}
});
ContactManager.on("start", function () {
var contactView = new ContactView({
model: someModel
});
ContactManager.mainRegion.show(contactView);
});

Use a layoutview instead.
You could do for example:
var ContactManager = new Marionette.Application({});
var LayoutView = Backbone.Marionette.LayoutView.extend({
template: "#layout-view-template",
regions: {
menu: "#menu",
content: "#content"
}
});
ContactManager.layout_view = new LayoutView();
ContactManager.layout_view.render();
I never actually add regions to my app object directly.

Related

Rendering a closed Marionette view

Shouldn't a closed Marionette view re-delegate the defined events (events, modelEvents, CollectionEvents) when rendering again?
It seems as if I have to manually call delegateEvents after closing and re-rendering a view. Otherwise the view won't work as expected.
http://jsfiddle.net/4DCeY/
var app = new Marionette.Application();
app.addRegions({
main: '.main'
});
var MyView = Marionette.ItemView.extend({
template: _.template('Hi, I\'m a view! Foo is: <%= foo %>'),
modelEvents: {
'change': 'onChange'
},
onChange: function() {
alert('change!');
}
});
var Model = Backbone.Model.extend({});
app.addInitializer(function() {
var m = new Model({foo: 'bar'});
var myView = new MyView({
model: m
});
app.main.show(myView);
myView.close();
app.main.show(myView);
m.set({foo: 'baz'});
});
$(document).ready(function(){
app.start();
});
If I understand your question right, there are multiple open github issues about this.
For example:
https://github.com/marionettejs/backbone.marionette/pull/654
https://github.com/marionettejs/backbone.marionette/issues/622
Last time I checked, Derick (the creator of Marionette) didn't feel like reusing closed views should be something regions should do.
So you could
simply create a new view and show that one
manually call delegateEvents - but there was an issue with multiple event bindings that I can't remember right now, so be careful about that one (not at work right now, so can't take a peek at the code, sorry)
write your own region manager
or wait and see if Derick will merge one of the pull requests
a couple of points:
You do not need to call myView.close() Marionette Region will take care of that when you show another view
Marionette.Region will not replace the same view with itself. It will just skip the redundant procedure if you want to test this correctly you need 2 views
If you want a change in model to invoke render you must explicitly write it
I altered the jsfiddle with the following things:
added myView1 and myView2
removed explicit call to myView.close
added a call for this.render() from the onChange function
Here is the corrected jsfiddle http://jsfiddle.net/4DCeY/1/ :
app.addInitializer(function() {
var m = new Model({foo: 'bar'});
var myView1 = new MyView({
model: m
});
var myView2 = new MyView({
model: m
});
app.main.show(myView1);
app.main.show(myView2);
m.set({foo: 'baz'});
});
And:
onChange: function() {
alert('change!');
this.render();
}

Share some logic between Backbone.Marionette views

Previously, I made a backbone view for handling a toggle button:
Star = Backbone.View.extend({
events: {
'click': 'toggle'
},
toggle: function() {
this.$('i').toggleClass('icon-star').toggleClass('icon-star-empty');
},
status: function() {
return this.$el.hasClass('active');
}
});
And i was using this subview in my views like this:
initialize: function() {
var star = new Star({ el: this.$('.new .btn.star') });
// ...
}
This way i can reuse this subview in many other independent views in backbone. (FYI: I'm no backbone expert. This code also can be wrong. Please correct me if it's wrong.)
Now i'm trying to learn Backbone.Marionette and i couldn't find a good way to accomplish same functionality. How can i use this view in my ItemViews and/or CompositeViews?
You can keep the exact same idea, just extend (e.g.) a Marionette ItemView:
Star = Marionette.ItemView.extend({...});
and then
var star = new Star({ el: this.$('.new .btn.star') });
Another option is to extend your Star view:
MyView = Star.extend({...});

Backbone Views as CommonJS modules

I'm trying to get my head around using CommonJS modules within a Backbone application, so I have a skeleton Backbone View defined in /views/categories/edit.js:
app.Views.quoteCategoriesEdit = app.Ui.ModalView.extend({
className: '',
template: JST["templates/quotes/categories/quote-categories-edit.html"],
events: {
'click [data-key="save"]': 'save',
'click [data-key="cancel"]': 'cancel'
},
initialize: function (options) {
var that = this;
_.bindAll(this, 'save', 'cancel');
app.Collections.quotesCategories.on('change add', function () {
that.remove();
});
},
render: function () {
var that = this;
// boilerplate render code
return this;
}
});
If someone could show me how I can convert this into a CommonJS module to be used with Browserify, then I would be very grateful and it'd really help me understand how I go about modularising the rest of the application! Thanks
//once you get things into folders/files, this path may change
//but for now I'm assuming all your views will live in the same directory
var ModalView = require('./modal-view');
var QuoteCategoriesEdit = ModalView.extend({
className: '',
template: JST["templates/quotes/categories/quote-categories-edit.html"],
events: {
'click [data-key="save"]': 'save',
'click [data-key="cancel"]': 'cancel'
},
initialize: function (options) {
var that = this;
_.bindAll(this, 'save', 'cancel');
app.Collections.quotesCategories.on('change add', function () {
that.remove();
});
},
render: function () {
var that = this;
// boilerplate render code
return this;
}
});
//Simplest convention is just 1-class-per-module
//Just export the constructor function
module.exports = QuoteCategoriesEdit;
Follow-up question from the comments:
Very much appreciate this! How would you approach: app.Collections.quotesCategories as I house everything under the app namespace? Do I just require the Collection itself?
So the idea of an "app" namespace is the opposite of being modular/commonjs/browserify/requirejs. You don't need an app object anymore. Any module that needs to create a new instance of this collection would just do var QuotesCategories = require('app/collections/quotes-categories'); and that is all. No more globals or namespace objects. Mostly your views will get the models/collections they need in their constructor function options. Most of your models will get created by calling fetch on a collection, and most of your collections will be instantiated by your router.
Oh, and yes in this specific example it's probably best if non-view code creates the collection and passes it to the view via the constructor options.collection parameter. However, if you decided yes you really wanted your view to instantiate the collection, it wouldn't come from the app global namespace object, it would just come from a require call as you describe in your comment.

Creating backbone views with models from other views

Background:
I am making changes to an application that uses backbone.js with Handlebars as the templating engine. After a change event fires I need to create html that is appended to the current DOM structure which is basically just a spit-out of information that is contained in the model. This change needed to fit in the already established application structure.
Issue:
I have created a new view that uses a Handlebars template and the model to create the html. I then instantiate that view and call the render function and append the output using JQuery. What I am noticing is that when the html is rendered the model that is passed in because attributes on the $el instead of filling in the template (like I think it should).
View I'm altering:
$.hart.TestView = Backbone.View.extend({
tagName: "li",
template: Handlebars.compile($('#templateOne').html()),
initialize: function () {
this.model.on('change', function () {
this.createMoreInfoHtml();
}, this);
},
selectSomething: function () {
this.$el.removeClass('policies');
this.createMoreInfoHtml(); //function created for new view stuff
},
createMoreInfoHtml: function () {
var id = this.$el.attr('data-id', this.model.get("ID"));
$('.info').each(function () {
if ($(this).parent().attr('data-id') == id
$(this).remove();
});
var view = new $.hart.NewView(this.model, Handlebars.compile($("#NewTemplate").html()));
$('h1', this.$el).after(view.render().el);
},
render: function () {
... //render logic
}
});
View I Created:
$.hart.NewView = Backbone.View.extend({
initialize: function (model, template) {
this.model = model;
this.template = template;
},
render: function () {
this.$el.html(this.template({ info: this.model }));
this.$el.addClass('.info');
return this;
}
});
Json the is the model:
{
"PetName":"Asdfasdf",
"DateOfBirth":"3/11/2011 12:00:00 AM",
"IsSpayNeutered":false,
"Sex":"F",
"SpeciesID":2,
"ID":"ac8a42d2-7fa7-e211-8ef8-000c2964b571"
}
The template
<script id="NewTemplate" type="text/html">
<span>Pet Name: </span>
<span>{{this.PetName}}</span>
</script>
So now to the question: What am I doing wrong? Why are the properties of the model being created as attributes on the $el instead of filling in the template? Can someone please direct me as to how to get the results I am looking for?
Let's skip the problem Jack noticed.
The way you're creating your view is just wrong. It may work as you get the expected arguments in the initialize function, but it has unexpected behaviors you don't see. See the View's constructor:
var View = Backbone.View = function(options) {
this.cid = _.uniqueId('view');
this._configure(options || {});
Now let's have a look at this _configure method:
_configure: function(options) {
if (this.options) options = _.extend({}, _.result(this, 'options'), options);
_.extend(this, _.pick(options, viewOptions));
And of course...
var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
Ok here we are... Basically when passing the model as the options argument, you're passing an object with an attributes key (the attributes of your model). But this attributes key is also used in the View to bind attributes to its element! Therefore the behavior your noticed.
Now, other wrong thing. You're compiling your template each time you create a new function, but not using it as a singleton either. Put your template in the view:
$.hart.NewView = Backbone.View.extend({
template: Handlebars.compile($("#NewTemplate").html(),
And change your view's creation to make the whole thing work:
new $.hart.NewView({model: this.model});
Oh, and get rid of this useless initialize method. You're just doing things Backbone already does.

can't get application to work after namespacing

I have a Backbone app that is working properly, however, when I tried to reorganize the code under a namespace I can't get it to do anything. I can't even trigger events (by clicking on ids) for views that I know are getting initialized (through console log messages), so I'm wondering if I've introduced some fundamental flaw somehow. I'm following a pattern set out by this blog (in french) http://www.atinux.fr/2011/12/10/organiser-son-code-backbone-js-en-modules/
In the main application.js (see below), I instantiate all of the views and models after initiating the app on document ready. One change introduced as a result of creating this namespace was setting the models for the views with this.models.game
this.views.clock_view = new this.Views.clockView({ model: this.models.game});
Inside the modules folder, I had a views.js and a models.js. I created each view and object like this, prefaced with app.Views or app.Models accordingly
app.Views.announceView = Backbone.View.extend({
....
app.Views.optionsView = Backbone.View.extend({
...
This app.Views.optionsView is getting initialized (according to a console.log statement in the initializer) but when I click on #new_game, the console.log in the startNewGame is not getting triggered
'click #new_game': 'startNewGame'
// 'click .action_button': 'startNewGame'
},
startNewGame: function() {
console.log("startNewGame");
this.model.new();
},
As a result of the namespacing, one other key change I made was when I created new views inside one of the other views. Under the previous (non-namespaced app), I created individual question items from a QuestionListView
var view = new QuestionListItemView({ model: game });
but now I'm doing
var view = new app.Views.questionListItemView({ model: app.models.game })
because the instance of the model was saved to this.models.game in application.js, however, I also tried using 'this.models.game'
var view = new app.Views.questionListItemView({ model: this.models.game })
Either way, before the games model gets involved, I can't trigger the startNewGame function outlined above, so it's not solely an issue of how to identify the model.
I also wondered whether i should be using this.Views or app.Views after the 'new' when creating new views from within
var view = new app.Views.questionListItemView({ model: this.models.game })
I'd be grateful if you could help me identify any flaws I've introduced.
application.js
var app = {
// Classes
Collections: {},
Models: {},
Views: {},
// Instances
collections: {},
models: {},
views: {},
init: function () {
this.models.game = new this.Models.game();
this.views.story_view = new this.Views.storyView(); #doesn't have a model
this.views.clock_view = new this.Views.clockView({ model: this.models.game});
this.views.field_view = new this.Views.fieldView({ model: this.models.game});
this.views.options_view = new this.Views.optionsView({ model : this.models.game});
this.views.announcement_view = new this.Views.announceView({ model: this.models.game});
this.views.question_list_view = new this.Views.questionListView({ model : this.models.game});
this.views.question_list_item_view = new this.Views.questionListItemView({ model : this.models.game});
}
};
$(document).ready(function () {
app.init();
}) ;
The options view is getting initialized but I can't trigger the startNewGame function when I click that #id
app.Views.optionsView = Backbone.View.extend({
// el: $("#options"),
el: $("#options"),
initialize: function() {
console.log("app views OptionsView initialized");
// this.model.bind("gameStartedEvent", this.removeGetAnswerButton, this);
this.model.bind("gameStartedEvent", this.disableNewGame, this);
},
events: {
'click #demo': 'startDemo',
'click #new_game': 'startNewGame'
// 'click .action_button': 'startNewGame'
},
startDemo: function(){
console.log("start demo");
this.model.demo();
},
startNewGame: function() {
console.log("startNewGame");
this.model.new();
},
disableNewGame: function(){
$('#new_game').attr("disabled", true);
}
});
Update
My file structure looks like this
<%= javascript_include_tag 'application.js'%>
<%= javascript_include_tag 'modules/models'%>
<%= javascript_include_tag 'modules/views'%>
At the top of the views and models file, I just do something like this
app.Views.optionsView = Backbone.View.extend({
ie.. there is no further document ready. In fact, including another document ready in these files breaks the application.js init
Prior to using the namespace, I defined the element this way in the view
el: $("#options")
which, as was pointed out in the comments to this question, is not the ideal way to do it(see #muistooshort comment below), (even though it worked).
Defining the el this way instead
el: '#options'
got it working, and let Backbone "convert it to a node object" on its own.

Resources