how do I show just one item of my marionette collection and navigate between them - marionette

I have a route
routes: { "pages/:id" : "page"
},
page: function (id) {
var pageView = new contentCollectionView({
collection: collection,
tagName: "div",
className: "pages"
});
pageView.close();
PAGE.content.show(pageView);
},
However what I want to do is to only show the individual page in my collection that matches the id I am passing - I also want to be able to be navigate from that page to the preceding and next page in the collection.
So - how do I pass the identifier in to my pageView at the time of rendering so it only does the one item in the collection ( I believe I know how to get the item index in the collection as it loops through, but if you want to show an example of that as well it would be nice)

According to your requirement i would recommend to render ItemView instead of CollectionView.
If you have CollectionView you have ItemView for sure :)
and pass special by ID model from you collection.
So you code may look like this:
page: function (id) {
var pageView = new contentItemView({
model: collection.get(id)
});
pageView.close();
PAGE.content.show(pageView);
}
About navigation - you can check with at method index of model in collection to actualize navigation logic. If you also need to keep browser navigation(back and forward buttons) you have to checkout Backbone.history

Related

Filling MVC DropdownList with AJAX source and bind selected value

I have view for showing Member details. Inside there is an EditorFor element which represents subjects taken by the member.
#Html.EditorFor(m => m.Subject)
Inside the editor template there is a Html.DropDownListFor() which shows the selected subject and button to delete that subject
I am using an Html.DropDownListFor element as :
#Html.DropDownListFor(m => m.SubjectID, Enumerable.Empty<SelectListItem>(), "Select Subject",
new { #class = "form-control subjects" })
The second parameter(source) is set empty since I want to load the source from an AJAX request; like below:
$.getJSON(url, function (response) {
$.each(response.Subjects, function (index, item) {
$('.subjects').append($('<option></option>').text(item.Text).val(item.ID));
});
// code to fill other dropdowns
});
Currently the html loads before the dropdowns are filled. So the values of all subject dropdowns are set to the default "Select Subject". Is there a way around this or is it the wrong approach?
Note: There are a number of dropdowns in this page. That's why I would prefer to load them an AJAX request and cache it instead of putting in viewModel and filling it for each request.
** EDIT **
In AJAX call, the action returns a json object containing dropdowns used across all pages. This action is decorated with [Output Cache] to avoid frequent trips to server. Changed the code in $.each to reflect this.
You can initially assign the value of the property to a javascript variable, and use that to set the value of the selected option in the ajax callback after the options have been appended.
// Store initial value
var selectedId = #Html.Raw(Json.Encode(Model.Subject.SubjectID))
var subjects = $('.subjects'); // cache it
$.getJSON(url, function (response) {
$.each(response, function (index, item) {
subjects.append($('<option></option>').text(item.Text).val(item.ID));
});
// Set selected option
subjects.val(selectedId);
});
However its not clear why you are making an ajax call, unless you are generating cascading dropdownlists which does not appear to be the case. What you doing is basically saying to the client - here is some data, but I forgot to send what you need, so waste some more time and resources establishing another connection to get the rest of the data. And you are not caching anything despite what you think.
If on the other hand your Subject property is actually a collection of objects (in which case, it should be Subjects - plural), then the correct approach using an EditorTemplate is explained in Option 1 of this answer.
Note also if Subject is a collection, then the var selectedId = .. code above would need to be modified to generate an array of the SubjectID values, for example
var selectedIds = #Html.Raw(Json.Encode(Model.Subject.Select(x => x.SubjectID)))
and then the each dropdownlist value will need to be set in a loop
$.each(subjects, function(index, item) {
$(this).val(selectedIds[index]);
});
If your JSON tells you what option they have selected you can simply do the following after you have populated your dropdown:
$('.form-control.subjects').get(0).selectedIndex = [SELECTED_INDEX];
Where [SELECTED_INDEX] is the index of the element you want to select inside the dropdown.

Backbone routing and view relations

I'm preparing to build a medium sized website with Backbone for the first time. There are 7 menu items, and I cant figure out whats the best routing/view relationship when it comes to performance. When a route is triggered, do I create a new instance of the "active" view every time it's triggered or do I only create one view instance for each view, when the user loads the page?
... And whats the best way to handle views – adding and removing dom elements and events, without having trouble with performance. e.g.
$('selector').html(my-new-view);
You can create your menu only once, if it doesnt change. You can create some html template like this.
<div>
<nav>render-here-only-one</nav>
<section>render-here-when-page-changes/loads</section>
</div>
I prefer backbone's render method initialize and append itself.
var testView = Backbone.View.extend({
el: $("#section"),
template:_.template("<strong>hello.</strong>")
initialize: function () {
// any change on view will trigger render
_.bindAll(this, "render");
},
render: function (item) {
this.el.html(this.template());
}
});
var myView = new testView();
myView.render();

How can I change ItemView to CompositeView in Marionette application?

I'm trying to make in-row editing in Marionette application. What is the best approach to do this?
I have table which is Marionette.CompositeView and rows of this table are Marionettes ItemViews. Now I'm trying to change clicked table row (ItemView) to a CompositeView which will contain inputs and selects with ajax fetched data. Is this a good approach?
You can use CollectionView.getChildView to render different view for edited items, but this can cause performance problems if you need to render large collections.
I modified Derick Bailey's Tree View example to show how this can be done - http://jsfiddle.net/msamujlo/8g3abfg2/
// The recursive tree view
var TreeView = Backbone.Marionette.CompositeView.extend({
template: "#node-template",
tagName: "ul",
getChildView: function(item){
return item.get('isEditable')? EditorView : TreeView;
},
// ... more methods
};
var EditorView = TreeView.extend({
template: "#editor-template",
});
...

Backbone.Marionette and Backbone.Paginator. CompositeView rerendering

I'm working on Infinite Pagination
(http://addyosmani.github.io/backbone.paginator/examples/infinite-paging/index.html)
I'm using CompositeView for pagination view.
And I've got the following problem. Each time after I get new portions of data Paginator's collection removes old data and adds new so it makes CompositeView to rerender and erase old results.
How can I resolve this problem? I'm thinking about disabling rerender functionality but how it should be done properly?
Thanks in Advance!
var BaseFeedChronoCompositeView = Backbone.Marionette.CompositeView.extend({
tagName: "div",
template: _.template(ChronoFeedComposite_html),
itemView: Article,
events: {
'click #loadmore-button-manual': function (e) {
e.preventDefault();
this.collection.requestNextPage();
},
appendHtml: function (collectionView, itemView, index) {
collectionView.$("#chronoFeed-content").append(itemView.$el);
}
});
Here is the basic code.
this.collection.requestNextPage() - sends request for data to server. After it gets data this.collection removes old models and adds new models.
Composite View is listening for these events and removes itemViews for old models and append itemViews for new models.
And I need CompositeView not to remove old itemViews.
Im not quite sure how this paginator works, as I've never used it. But I think the easiest way to fix this is to make sure that your Collection doesnt remove old models. I assume that when your data is being returned it is doing a set on the collection. If you look at the backbone docs you can see that you can disable this method from removing models http://backbonejs.org/#Collection-set
If you'd like to customize the behavior, you can disable it with
options: {add: false}, {remove: false}, or {merge: false}.
So when you are updating the collection, instead of just calling
myCollection.set([o1,o2,o3]);
you should be doing
myCollection.set([o1,o2,o3], {remove:false});

Backbone JS where to put Application main logic

Using Backbone i'm starting to build an App where i have everything cleanly separated. But now i have the following question. Where should i put the App main logic, in the views or in the model.
For example i have a view and a model, which are binded to a button and when i click that button i have to make
$.ajax(params)
do i put that in the view or the view calls a method with :
this.model.doAction(params)
which do you think is the best approach?
You can define an events property in the view which is of the format {"event selector": "callback"} for eg. {"click .collapse": "collapse"} where collapse would be a function defined as a property of the view. Then write your ajax request code in this callback function.
Also, unless I am missing something, "binding a view and model to a button" doesn't sound correct Backbone way to me. Instead you should think of one instance of model associated with one instance of the view. Whenever an attribute of the the model instance changes, a model change event will be triggered. You can bind a view function to this event so that change in the model is reflected in the view. Here is a quick example
var Book = Backbone.Model.extend({
// ...
});
var BookView = Backbone.View.extend({
initialize: function () {
this.model.bind('change', this.render, this);
},
render: function () {
// here, make changes to the dom as per changes in model
}
});
To associate a model with a view instance, you can pass the it while instantiating a new
view object..
var book = new Book({
title: "A great book"
});
var view = new BookView({model: book});
view.model.set('author', 'AGreatAuthor');
The set function call will fire change event and will result in render function of view
to be called.
Refer to the annotated source of Todos app example for a complete example.

Resources