I've just started trying out Marionette after building a couple of vanilla Backbone applications. Right out of the gate I'm running into some questions with region management and multiple collections.
I have the following nested Model and Collection structure
FoosCollection
FooModel
BarsCollection
BarModel
I want to take advantage of all the great views and layout/region managers that Marionette provides. However, I need to render this information on the page in a bit of a unique way. My HTML looks like this:
<div id="container">
<div id="foos-description"></div>
<div id="bars-list"></div>
</div>
Basically, some high level information from each of the FooModels needs to be rendered into the foos-description element. Then, each BarsCollection needs to be rendered into the bars-list. The structure needs to be such that the list is directly below the corresponding information in the foos-description container.
So, I would love to use CompositeViews to solve this, but it looks like the View/DOM element structure also needs to be nested in the same way that the collection/model structure is nested, which won't work in my case.
I've also added two Regions
App.addRegions({
foosDescription: "#foos-description",
barsList: "#bars-list"
});
My initializer code looks like this
App.addInitializer(function (options) {
App.foosCollection = new FoosCollection(options.foos, {bars: options.bars});
App.foosDescription.show(new FoosView({collection: App.foosCollection}));
App.foosCollection.each(function(f) {
app.barsList.show(new BarsView({collection: f.barsCollection}));
})
});
Obviously this will only show the last BarsCollection in the barsList region, but I'm unsure of how I should structure this such that each collection is appended to the region. Any help would be greatly appreciated.
You'll need to show the view in the region:
// tweaked region and view names for clarification
App.foosDescriptionRegion.show(foosDescriptionView);
Related
I'm trying to apply Marionette into our architecture as it seems to fit our current application better than other solutions. Our frontend HTML is mostly server-side generated using PHP and Twig (just to give some context).
I'm now trying to use Marionette 3 Regions to achieve View compositions in a context where header, footer and generally the main content HTML are already there in the page.
I couldn't find any example with this approach so I'm asking here if someone could give some advice.
Thanks!
You can easily attach views to existing DOM elements using marionette:
var MyView = Mn.View({
el: '#base-element',
template: false
});
Also reference: http://marionettejs.com/docs/master/marionette.view.html
I am new to angular and was looking for an example of a master detail. I found one: http://embed.plnkr.co/DBSbiV/App.js, but this is using angular's ngRoute. As I heard ui.route is the state of the art kind of way to handle routing due to it is more flexible.
I want to have both master and detail on one template, so I tried to solve it with multiple named views like in https://github.com/angular-ui/ui-router/wiki/Multiple-Named-Views. But I now have to problem, that I don't know how to create a link in the master list to the detail view with .. I tried with ... but don't know what to put right here, because I have only one state with multiple views.
Can you tell me whether I'm on the wrong way or what I put into this ref to link to my detail?
Thanks in advance
Markus
You don't actually need named views for this. Just nested views. Lets imagine you have a list of blog posts. You have one state called blogs. And another state called blogs.edit, which takes a parameter of the postId.
Your blogs.html (the master list) could look something like this:
<a ng-repeat="post in blog.posts" ui-sref="blogs.edit({postId: post.Id})">{{post.title}}</a>
<div ui-view></div>
This will render a list of anchor tags, and a nested details view under.
I have the following view:
return Marionette.ItemView.extend({
el: '<section>',
template: JST['app/scripts/templates/grid.ejs'],
that is called like this:
// a Layout
regions: {
grid: '#grid',
detail: '#detail'
},
onShow: function () {
var detailModel = new DetailModel();
var g = new GridView(detailModel);
this.grid.show(g);
}
The question is: How do I get rid of the surrounding section element ? I tried to omit the el property but that gives me the following strange looking div:
<div productname>
Regards Roger
The surrounding element is required for backbone to work. It is essentially a container/placeholder for your view to sit in, whether its contents have been rendered or not.
If you really insist on not having the container then I would consider resorting to the following:
https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.region.md#set-how-views-el-is-attached
Marionette.Region.prototype.open = function(view){
this.$el.empty().append(view.$el.children());
}
I say 'resorting' because, in my opinion, this is not how Backbone is supposed to be used and may have side-effects. (im not quite sure what will happen when the view in that region tries to re render; what will it's el element be pointing to?)
To expand on Scott's answer, it's probably a very bad idea to try and force the removal of the surronding view tags.
All Backbone views are contained within an DOM element. Given this fact, you have 2 main options:
have Backbone put your view into the default div element
specify which element you want Backbone to wrap your view with, using the el or tagName attributes
If the "extra" tags are creating issues (e.g. you need to generate a specific HTML set for use with a plugin), then you're not defining the wrapping element properly. For more on the subject, take a look at this blog post of mine: http://davidsulc.com/blog/2013/02/03/tutorial-nested-views-using-backbone-marionettes-compositeview/
Edit based on jsFiddle: the reason for your strange behavior is that you were passing a model instance to the initialize function. This is then interpreted as attributes for the view and get set as HTML attributes.
The correct way to provide a model instance to a view is :
new App.FooterView({
model: new App.Model()
})
In other words, you provide a javascript object to the view, with a model property. If you want to learn Marionette basics quickly, check out the free preview to my book: http://samples.leanpub.com/marionette-gentle-introduction-sample.pdf (You'll find how to instantiate a view with a model on pages 15-21)
I'm reading up in choosing the correct client-side framework to segment/modularize my frontend code in Widgets.
Basically what I have/want is:
a complex website with multiple pagetypes, so no single-page application.
all pages are able to render a complete page WITHOUT the use of javascript. IOW: javascript is used as enrichment only.
Lots of pages have a very dynamic way in which widgets can be shown on screen. To overcome complexity at the server-side I've modularized my code into widgets (composite pattern), where each widget is responsible for it's own:
server-side controller code
server-side templating (using hogan/mustache)
routing endpoints, should it need to be called from the client
structural css (css converning the structure of the widget as opposed to the look&feel)
a server-side RegionManager ultimately decides which widgets are rendered and where they are rendered on screen. Endresults is that the RegionManager spits out the entire html (server-generated) as the composite of the rendering of all of it's widgets.
Now, some of these widgets DO have client-side logic and need rerendering on the client. Take a searchpage for instance, which needs to be able to update through ajax. (I've described this process, which uses DRY templating on client and server, here)
What I ultimately want is that, given I already use the composite pattern on the server, to extend this to the client somehow so that a Widget (1 particular logic block on the screen) contains all mentioned server-side code, plus all needed client-side code.
I hope this makes sense.
Would Marionette be suited to be used as a client side framework in this scenario? I'm asking since I'm not 100% sure if the concept of a Marionette Module is what I describe as being a Widget in above scenario. (I'm mentioning Twitter Flight in my question, since I believe this would be a fit, but it currently is so new that I'm hesitant to go with it at the moment_
I think basically what I'm asking is if anybody has some experience doing something along these lines.
I think just using Backbone.js is perfect for this type of application you are describing. You have probably already read this, but most of the backbone literature is focused around your views having associated server generated JSON models and collections, then using the View's render function to generate (on the client) the HTML UI that represents the model/collection.
However it doesn't have to be used this way. In fact there is nothing stopping you attaching views to existing elements that contain content already, which gives you all of the benefits of Backbone's modularity, events system and so on. I often use views that have no model or collection, purely because I like the conformity of style. I have also used an approach like I describe below in the cases where I have had to work with older, existing applications that have not yet got, or never will have a nice REST API, but they do provide content in HTML.
Firstly, lets assume the following HTML represents one of your widgets:
<div id="widget">
<div class="widget-title"></div>
<div class="widget-body">
<!-- assume lots more html is in here -->
Do something!
</div>
</div>
In this case, you could use backbone with a single Widget Model. This would be a very simple model, like this:
App.WidgetModel = Backbone.Model.extend({
intialize: function () {
this.url = this.options.url;
}
});
Take note of the fact the Widget receives a URL as a parameter to its constructor/initialize function. This widget model would represent many of your widgets (and of course you could adopt this general approach with more complicated models and pluck different data from the rendered HTML). So next for your views. As you probably know, normally you pass most views a model or collection when you instantiate them. However in this case, you could create the Widget model in your View's initialize Function and pass it a URL from the pre-rendered HTML as follows:
App.WidgetView = App.View.ComboboxView = Backbone.View.extend({
initialize: function () {
this.model = new App.WidgetModel({}, { url: this.$("a").attr("href") });
}
// rest of the view code
});
So instantiating the view would be something like:
new App.WidgetView({el: $("#widget")})'
By doing all of the above you can do pretty much everything else that backbone offers you and its modular and encapsulated nicely, which is what you are after.
The end result of this whole approach is:
You have rendered the Widget UI as pure HTML which (I assume) is functional without JavaScript.
You attach a View to the existing HTML.
You pass into the View as options, content by extracted (such as a URL) from the rendered HTML with jQuery.
The View is responsible for instantiating the Model passing on the relevant options the model needs (such as a URL).
This means all dynamic server side content is intially contained in the rendered HTML and your View is a modular JavaScript component that can do stuff to it, which I think is the end result you're after.
So you mentioned that you would like to have AJAX functionality for your widgets and that fine with this approach too. Using this approach, you can now use the standard Backbone fetch and save functions on the Widget model to get new content. In this example it is from the URL retrieved from the rendered HTML. When you get the response, you can use the view's, render function, or other finer grained functions to update the HTML on the page as required.
A few points:
The only thing to look out for is that you'll need to change the content type of the fetch and save functions to "text/html" if that's what the server is providing. For example:
this.model.fetch({
type: "POST",
contentType: "text/html"
});
Lastly, the model I have proposed is instantiated with no content. However if your ajax calls are a content type of "text/html", you may need to play around with you model so it can store this content in its attributes collection properly. See this answer for more information.
I'm working on a sample ToDo list project in Backbone and I'd like to understand how the framework would prefer me to organize its views and models in the nested list scenario.
To clarify what I mean by that, my single-page Backbone app should display lists of ToDo lists. From the backend standpoint, there's a List resource and an Item (a single entry in a todo list) resource. Something along the lines of:
Monday chores
Pick up the mail
Do the laundry
Pick up drycleaning
Grocery list
Celery
Beef
You get the idea...
Since mine is a Rails 3.2 app, I'm vaguely following the Railscasts Backbone.js tutorial, so that's where I'm getting the current design from. I would love to know if I'm wildly off the Backbone-prescribed pattern, or if I'm on the right track!
I thus far have:
ListsIndex View //index of all lists
\-- ListsCollection
\-- ListView / Model //individual list
\-- ItemsIndex View //index of items in one list
\-- ItemsCollection
\-- Item View / Model //individual todo item
The flow would be:
On router initialize, fetch() collection of lists on /lists backend route. On the 'reset' event for the collection part of ListsIndex, execute render() on each of the items in the collection, appending to the list index view template.
In the initialize method of each Item View (is this where you'd wire-up the second level fetch?) fetch() the items from the /lists/:id/items backend route into an ItemsCollection specific to that view.
In the same method, instantiate an ItemsIndex object and pass the collection into it. Once again, in ItemsIndex, have a 'reset' event handler for when the collection is populated, at which point it should render each fetched model from the item collection and append them to its own view.
I'm essentially taking the design of the List and mirroring it down one level to its items. The difference is that I no longer have a router to rely on. I therefore use the initialize method of ListView to a similar effect.
Yay / nay? Super wrong? Thanks!
TL:DR; 1) I would bootstrap your initial data instead of a fetch() reset(). 2) You can do a fetch in the initialize of a View as you need it. Or you could load the data at the start. Just remember that if you fetch in the init, the async nature won't have the data ready at render. Not a problem if you have a listener waiting for that sync/add/etc. 3) I don't know what you mean by itemIndex object but you can create objects and add to them collections as you need them. Or you can just bake the in at the start if you know all your lists are going to have a collection eventually. You can reset if you want (fetch automatically does this unless you give it option {add:true}) or just add them in one by one as they come in although reset(), remove prior views, render all views seems to be the common way people do things with a complete fetch().
I think it looks pretty good. The nice thing about Backbone is that you can do it many different ways. For example, your number 2 says to wire up a second fetch() from the view. You could do that if you want to lazy load. Or you could just grab all the data at app start before anything is done. It's really up to you. This is how I might do it.
This is how I might make an app like this (just my preference, I don't know that it's any better or worse or if its the same as you described.)
First I would create a model called ListModel. It would have an id and a name attr. This way, you can create many separate lists, each with their own id that you can fetch individually.
Each ListModel has an ItemsCollection inside of it. This collection has a url based on the ListModel it is a part of. Thus, the collection url for ListModel-1 would be something like /list/1
Finally you have ItemModel which is a resource id and text.
ListCollection
ListModel // Monday Chores
ItemCollection
ItemModel // Mail
ItemModel // Laundry
ItemModel // Drycleaning
ListModel // Grocery
ItemCollection
ItemModel // Celery
ItemModel // Beef
So in this little display you'll notice I didn't put anything to do with views in yet. I don't know if it's more of a conceptual thing but this is what the data hierarchy looks like and your views can be, should be totally independent of it. I wasn't exactly sure how you were including the views up above but I thought this might make it clearer.
As for defining these structures, I think two things.
First, I'd make sure my ListModel is defined in my collection. That way I can use the collection add(hash) to instantiate new models as I produce / add them.
Second, I would define the ListModel so that when one is created, it automatically creates an ItemCollection as a property of that ListModel object (not as an attribute).
So ideally, your ListModels would be like this:
ListModel.ItemCollection
Before the app initializes, I would bootstrap the data in and not fetch(). (This kind of addresses point 1 you make) Ideally, when your Backbone application starts it should have all the necessary data it needs from the get go. I would pass in the head some data like this:
var lists = [listModel-1-hash, listModel-2-hash];
Now when the app fires up, you can instantly create these two lists.
var myLists = new ListCollection();
_.each(lists, function(hash) {
myLists.add(hash); // Assumes you have defined your model in the ListCollection
}
Now your List Collection has all the list models it needs.
Here is where views come in. You can pass in anything to any view. But I might break views down into three things.
AppView, ListModelView, ItemModelView and that's it.
Imagine a structure like this:
<body> // AppView
<ul class="List"> // ListModelView
<li class="Item"></li> // ItemModelView
</ul>
<ul class="List"> // ListModelView
</ul>
</body>
When your start your app and create an AppView, inside AppView you'd generate each ListModelView and append it to the body. Our lists are empty. Maybe when you click on the it lazy loads the items. This is how you'd hook it up.
// In ListModelView
events: {'click':'fetchItems'}
fetchItems: function() {
this.model.itemCollection.fetch(); // Assumes you passed in the ListModel into view
}
So since I bootstrapped the data to begin with, this fetch() call would be your "second" fetch. (I'm addressing point 2 you made.) You can fetch it in your initialize. Just remember that it is an asynchronous function so if you need them at render time, it won't work. But, what you can do is add event listeners to this view that are listening for add events to your itemCollections.
this.model.itemCollection.on('add', this.addItemView, this);
addItemView() will generate new instances of the itemViews and append them.
As for point 3, you can instantiate a collection at that point you need it and throw it into your ListModel. Or you can do what I did and make sure all your models always have an ItemCollection. This depends on your preferences and goals. You probably didn't need all this but I felt like illustrating it out for some reason. I dunno, maybe it helps.