Backbone.Marionette: Pass data down through a CompositeView to its itemView? - marionette

I'm wondering if/how a CompositeView can pass data down into its defined itemView. Example (reduced) code:
var TableView = Backbone.Marionette.CompositeView.extend({
template: '#table-template',
itemView: TableRowView,
itemViewContainer: 'tbody',
});
var TableRowView = Backbone.Marionette.ItemView.extend({
tagName: 'tr',
template: '#table-row-template',
serializeData: function () {
var data = {
model: this.model,
// FIXME This should really only be called once. Pass into TableView, and down into TableRowView?
// That way, getDisplayColumns can be moved to the collection as well, where it makes more sense for it to belong.
columns: this.model.getDisplayColumns()
};
return data;
}
});
I'm using the two to render a html table. #table-row-template has some render logic for supporting different types of "columns". This allows me to use the same views for different types of Collections/Models (as long as they follow the API). So far, it's working pretty well!
However, as you can see above, each "row" makes a call to get the same "columns" data every time, when really I just wanted to pass that on down once, and use for all.
Recommendations?
Thanks!

You can use itemViewOptions either as an object or a function
var TableView = Backbone.Marionette.CompositeView.extend({
template: '#table-template',
itemView: TableRowView,
itemViewContainer: 'tbody',
itemViewOptions: {
columns: SOMEOBJECTORVALUE
}
});
OR
var TableView = Backbone.Marionette.CompositeView.extend({
template: '#table-template',
itemView: TableRowView,
itemViewContainer: 'tbody',
itemViewOptions: function(model,index){
return{
columns: SOMEOBJECTORVALUE
}
}
});
and then receive the options with:
var TableRowView = Backbone.Marionette.ItemView.extend({
tagName: 'tr',
template: '#table-row-template',
initialize: function(options){
this.columns = options.columns;
}
});
(* Note that itemView, itemViewContainer and itemViewOptions are changed in version 2 to childView , childViewContainer and childViewOptions).

Related

How to use two different collection in CompositeView

I am new to Marionette.
I am trying to create a view in which I need to show a dropdown and a table.
Dropdown is rendered from a collection. and based on value selected in dropdown I need to render collection in table.
Can you please tell me how can I achieve this kind of view ?
Thanks
**UPDATE**
I tried to achieve this thing like
http://jsfiddle.net/H8AZY/17/
var compositeView = Marionette.CompositeView.extend({
template : Handlebars.compile($("#sample-template").html()),
itemView: itemview,
itemViewContainer: 'tbody',
templateHelpers: function(){
var helpers = {};
console.log(this.coll.models);
helpers.users = this.coll.models;
return helpers;
},
triggers: {
'change select': 'selectName'
},
ui:{
select: 'select'
},
onSelectName: function() {
console.log(this.ui.select.val());
var newModel = this.coll.findWhere({name: this.ui.select.val()});
//this.collection = new Backbone.Collection(newModel.get('res'));
this.collection.reset(newModel.get('res'));
},
initialize: function(options) {
this.coll = options.coll;
this.collection = new Backbone.Collection();
}
});
Is it a good way to achieve what I want ?
There're two ways you can accomplish this.
You can just create the DOM manually.
class MyCompositeView extends Marionette.CompositeView
ui:
"select" : "select.my-select"
onRender: ->
_.each #options.states, (state) =>
$option = $("<option>", { id: state.id,
value: state.get("name") })
this.ui.select.append($option)
view = new MyCompositeView({collection: this.collection, states: stateCollection });
OR you can create an entire CollectionView for your select:
class StateView extends Marionette.ItemView
tagName: "option"
class StateCollectionView extends Marionette.CollectionView
tagName: "select"
itemView: StateView

backbone click event fires as many times as there is views

I searched info on this topic but found only info about getting event element.
Yes, I can get an element of clicked div, but why it's fired all 19 times? (it's the number of views total). Info of clicked event is same - of the clicked div.
Here is what divs look like: http://d.pr/i/AbJP
Here is console.log: http://d.pr/i/zncs
Here is the code of index.js
$(function () {
var Table = new Backbone.Collection;
var TrModel = Backbone.Model.extend({
defaults: {
id: '0',
name: 'defaultName'
},
initialize: function () {
this.view = new Tr({model: this, collection: Table});
this.on('destroy', function () {
this.view.remove();
})
}
});
var Tr = Backbone.View.extend({
el: $('.pop-tags').find('.container'),
template: _.template($('#td_template').html()),
events: {
'click .tag': 'clicked'
},
clicked: function (event) {
event.preventDefault();
event.stopPropagation();
console.log(event.currentTarget);
},
initialize: function () {
this.render();
},
render: function () {
this.$el.append(this.template(this.model.attributes));
return this;
}
});
for (var i = 0, size = 19; i < size; i++) {
var trModel = new TrModel({id: i, name: 'name_' + i});
Table.add(trModel);
}
});
How can I avoid all elements from firing an event and fire only one that clicked and only 1 time?
el: $('.pop-tags').find('.container'),
Don't do that. You are attaching every view instance to the same DOM node. Each backbone view needs a distinct DOM node or, as you see, delegate events become complete chaos. In your view, set tagName: 'tr', then when creating your views, create them, call .render() and then append them to the DOM with something like $('.table-where-views-go').append(trView.el);.
You also may want to brush up on the basic MVC concept because Tables and Rows are view-related notions, not model-related, so a class called TrModel is a code smell that you aren't clear on Model vs View.
I would use a slightly different approach to solve your problem.
Instead for one view for every tr I would create one view for the entire table.
When I create the view I would pass the collection containing the 19 models to the view and in view.initialize use the collection to render the rows.
I created a jsbin with a working example.

Using durandal with jaydata and kendoui

I created a new VS2012 project using the hottowel template, which in return uses durandal, knockout and breeze.
I want to use jaydata instead of breeze and for the ui layer I want to use the excellent asKendoDataSource() functionality to power a kendoui grid.
I followed all the instructions to make kendoui work well with durandal. This is fine.
I have a model in which I get the jaydata entities and run asKendoDataSource() on it. My view is an MVVM declared kendoui grid with its source property set to the viewmodel's property that contains a reference to the asKendoDataSource().
In the knockout world the viewModel property would be an empty entities = ko.observableArray() which would then would be initialized by using entities(values) when the data service returns.
I need to mimic this such that I have a viewModel property that is an empty kendo datasource which then is initialized by the asKendoDataSource() conversion when the data comes back from jaydata. This way the mvvm kendo grid is bound initially to the empty datasource and then receives its items when the asKendoDataSource() is called.
This is all because the model - viewModel binding in durandal is asynchronous and there needs to be a placeholder property in the viewModel from the very beginning, which then, after the viewModel's activate() method promise is resolved, gets updated with the bound data and in return powers the DOM that the viewModel is bound to.
I can't figure out how to mimic the knockoutjs practice of an empty observable array which is bound to the dom and then (the same exact reference) gets initialized and populates the dom. There seems to be no way to create an empty kendo datasource which then is re-initialized by the asKendoDataSource() method. Reassigning the viewModel property to the new data source doesn't work because the kendo grid is bound to the original reference.
This is my airport view:
<section>
<h2 class="page-title" data-bind="text: title"></h2>
<div id="airportGrid" data-kendo-role="grid" data-kendo-sortable="true" data-kendo-pageable="true" data-kendo-page-size="25" data-kendo-editable="true" data-kendo-columns='["Id", "Abbrev", "Name"]' data-kendo-bind="source: airports"></div>
</section>
This is my datacontext:
define([
'durandal/system',
'services/logger'],
function (system, logger) {
var getAirports = function (airportsObservable) {
$data.Entity.extend("Airport", {
Id: { type: "int", key: true, computed: true },
Abbrev: { type: String, required: true, maxLength: 200 },
Name: { type: String, required: true, maxLength: 512 }
});
$data.EntityContext.extend("JumpSeatDatabase", {
Airports: { type: $data.EntitySet, elementType: Airport }
});
var airportDB = new AirportDatabase('http://localhost:2663/odata');
var deferred = Q.defer();
airportDB.onReady(function () {
deferred.resolve(airportDB.Airports);
});
return deferred.promise;
}
var datacontext = {
getAirports: getAirports
};
return datacontext;
function log(msg, data, showToast) {
logger.log(msg, data, system.getModuleId(datacontext), showToast);
}
function logError(msg, error) {
logger.logError(msg, error, system.getModuleId(datacontext), true);
}
//#endregion
});
This is my airport viewmodel:
define(['services/datacontext', 'durandal/plugins/router'],
function (datacontext, router) {
var airports = null;// = kendo.data.ObservableArray([]);
var activate = function () {
var airportRes = datacontext.getAirports();
return airportRes.then(function (airp) {
vm.airports = airp.asKendoDataSource();
});
};
var deactivate = function() {
//airports([]);
};
var viewAttached = function (view) {
//using this type of reach in to the viewModel is considered bad practice in durandal
//$('#airportGrid').kendoGrid({
// dataSource: airports.asKendoDataSource({ pageSize: 10 }),
// filterable: true,
// sortable: true,
// pageable: true,
// height: 500,
// columns: ['Id', 'Abbrev', 'Name']
//});
//kendo.init($("#container"));
kendo.bind(view, vm);//this should eventually go away the recommended ViewModelBinder code in main.js is not firing for me
};
var vm = {
activate: activate,
deactivate: deactivate,
airports: airports,
title: 'Airports',
viewAttached: viewAttached
};
return vm;
});
One last issue that I am seeing:
It seems to me that an MVVM declared kendoui grid, bound to the view model through data-kendo-bind={source: airports)" where airports is a property of the viewmodel that was created through entities.asKendoDataSource() does not work. Somehow the grid does not show the data. Is there something extra that needs to be done?
Thanks
My best guess is that this is a timing issue, where the Grid is binding to vm.airports while it is still null, then vm.airports = airp.asKendoDataSource(); is called after it is already bound? Perhaps try something like:
return airportRes.then(function (airp) {
vm.airports.data(airp.asKendoDataSource().data());
});

How to use marionette collectionView. I am using https://github.com/marionettejs/backbone.marionette as boilerplate

I want to know how to use marionette collectionView. I am using https://github.com/marionettejs/backbone.marionette as boilerplate.
Currently I am unable to see anything rendered by collection view.
Backbone Collection
define(["jquery","backbone","models/Store"],
function($, Backbone, Store) {
// Creates a new Backbone Collection class object
var StoreCollection = Backbone.Collection.extend({
// Tells the Backbone Collection that all of it's models will be of type Model (listed up top as a dependency)
model: Store
});
storeCollectionObj = new StoreCollection();
storeCollectionObj.add({"title":"wherwr werwe"});
return storeCollectionObj;
});
Model
define(["jquery", "backbone"],
function($, Backbone) {
// Creates a new Backbone Model class object
var Store = Backbone.Model.extend({
// Model Constructor
initialize: function() {
},
// Default values for all of the Model attributes
defaults: {
},
// Get's called automatically by Backbone when the set and/or save methods are called (Add your own logic)
validate: function(attrs) {
}
});
// Returns the Model class
return Store;
}
);
itemView
define( ['underscore', 'jquery', 'handlebars', 'text!templates/merchant/store.html'],
function(_, $, Handlebars, template) {
//ItemView provides some default rendering logic
return Backbone.Marionette.ItemView.extend( {
//Template HTML string
template: Handlebars.compile(template),
id: "store",
attributes: function () {
return {class :"storeView"}
},
// View Event Handlers
events: {
}
});
});
CollectionView
define( ['underscore', 'jquery', 'handlebars' , 'views/merchant/StoreView','text!templates/merchant/storeCollection.html' , 'collections/StoreCollection'],
function(_, $, Handlebars, SroreView, template, StoreCollection) {
SroreViewObj = new SroreView();
//storeCollectionObj = new StoreCollection();
//alert(StoreCollection);
//StoreCollection.add({"title":"sdfsadfasd"});
//ItemView provides some default rendering logic
StoreCollectionView = Backbone.Marionette.CollectionView.extend({
id : "StoreListing",
template: Handlebars.compile(template),
itemView: SroreViewObj,
collection : StoreCollection,
render: function(){
}
});
return StoreCollectionView;
});
I found my mistake. I was passing itemView Object in my collection view.
line itemView: SroreViewObj should be itemView: SroreView
fixed just by replacing "SroreViewObj" with "SroreView" (itemView definition)
SroreViewObj = new SroreView();
//storeCollectionObj = new StoreCollection();
//alert(StoreCollection);
//StoreCollection.add({"title":"sdfsadfasd"});
//ItemView provides some default rendering logic
StoreCollectionView = Backbone.Marionette.CollectionView.extend({
id : "StoreListing",
template: Handlebars.compile(template),
itemView: SroreViewObj, // This should not be an object. So fixed just by replacing "SroreViewObj" with "SroreView" (itemView definition)**
collection : StoreCollection,
render: function(){
}
});

Should nested views be layout views?

A simple and short question: If a view contains two or more sub views. Should the view container be a layout view?
If not, what are good alternatives?
Update:
my code:
var LikeButtonModal = Backbone.Model.extend({
url: 'api/profile/like/'
});
var LikeButton = Backbone.Marionette.ItemView.extend({
tagName: 'button',
className: 'like',
template: '<div>like</div>',
events: {
'click' : 'like'
},
initialize: function(userId){
this.model = new LikeButtonModal();
},
like: function(){
this.model.save();
}
})
var LeftProfileView = Backbone.Marionette.Layout.extend({
template: '#profile-left',
regions:{
extra : '.extra'
},
initialize: function(){
this.on("item:rendered", this.editable, this);
},
onRender: function(){
if(this.model.get('userid') != ActiveUser.get('userid')){
this.extra.show(new LikeButton(this.model.get('userid')));
}
}
});
Layouts are good for this if you will be replacing the sub-views at different times, or if the sub-views are very different types... for example, a layout might contain your header, your navigation and your main content region.
Other options are CollectionViews and CompositeViews.
Collection views will render a collection of items, using the same type of view for each item in your collection. This works well for lists of things.
CompositeViews are CollectionViews that can render a wrapper template around the collection. For example, an HTML table structure. The table, thead, tbody and tfooter tags can be rendered in the CompositeView's wrapper template, and then a collection of items can be rendered in to the tbody tag.
This might shed a little more light on the subject, too: https://github.com/derickbailey/backbone.marionette/wiki/Use-cases-for-the-different-views

Resources