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

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(){
}
});

Related

Update state of like button in Backbone View

In the following code, I have a view which extends from another view (but does not inherit any functionality, only renders the template) and a model which I want to implement now. My view is for a like button, which I need to retrieve the state of the like button from the server each time the page is loaded. I am not sure how to do this using the model. Do I need to have an Ajax call in the model retrieving the state from the server or does that call fall into the view?
This is my code:
var likeButton = Backbone.Model.extend ({
initialize: function () {
this.isLiked = /* need something here! Ajax call to get state of button from server? */
}
});
var LikeButtonView = BaseButtonView.extend({ // extends form a previews view which simply extends from backbone and render's the template
template: _.template($('#like-button').html()),
sPaper: null,
sPolyFill: null,
sPolyEmpty: null,
isLiked: false,
events: {
"click .icon": "like",
},
model: new likeButton (),
initialize: function (options) {
BaseButtonView.prototype.initialize.apply(this, [options]); // inherit from BaseButtonView
this.likeButn = $("button.icon", this.$el);
this.svgNode = this.likeButn.find("svg").get(0); // find the svg in the likeButn and get its first object
this.sPaper = Snap(this.svgNode); // pass in the svg object into Snap.js
this.sPolyFill = this.sPaper.select('.symbol-solid');
this.sPolyEmpty = this.sPaper.select('.symbol-empty');
if (this.model.isLiked) {
this.likeButn.addClass("liked");
} else if (!this.model.isLiked) {
this.likeButn.addClass("unliked");
}
},
like: function() {
this._update();
},
_update: function () {
if ( !this.isLiked ) { // if isLiked is false, remove class, add class and set isLiked to true, then animate svg to liked position
this._like();
} else if ( this.isLiked ) { // is isLiked is false, remove class, add class, set isLiked to false, then animate svg to unliked position
this._unlike();
}
},
_like: function() {
this.likeButn.removeClass("unliked");
this.likeButn.addClass("liked");
this.isLiked = true;
this.sPolyFill.animate({ transform: 't9,0' }, 300, mina.easeinout);
this.sPolyEmpty.animate({ transform: 't-9,0' }, 300, mina.easeinout);
},
_unlike: function() {
this.likeButn.removeClass("liked");
this.likeButn.addClass("unliked");
this.isLiked = false;
this.sPolyFill.animate({ transform: 't0,0'}, 300, mina.easeinout);
this.sPolyEmpty.animate({ transform: 't0,0' }, 300, mina.easeinout);
}
});
There are three ways to implement the 'like' button's knowledge of the current state of the page: A hidden field delivered from the HTML, an Ajax call to the server, or generating your javascript server-side with the state of the like model already active.
Let's start with the basics. Your code is a bit of a mess. A model contains the state of your application, and a view is nothing more than a way of showing that state, receiving a message when the state changes to update the show, and sending messages to the model to change the state. The model and the view communicate via Backbone.Events, and the view and the DOM communicate via jQuery.Events. You have to learn to keep those two separate in your mind.
Here, I've turned your "like" model into an actual model, so that the Backbone.Event hub can see the changes you make.
var likeButton = Backbone.Model.extend ({
defaults: {
'liked': false
}
});
Now in your view, the initial render will draw the state in gets from the model. When a DOM event (described in the 'events' object) happens, your job is to translate that into a state change on the model, so my "toggleLike" only changes the model, not the view. However, when the model changes (explicitly, when the "liked" field of the model changes), the view will then update itself automatically.
That's what makes Backbone so cool. It's the way views automatically reflect the reality of your models. You only have to get the model right, and the view works. You coordinate the way the view reflects the model in your initialization code, where it's small and easy to reason about what events from the model you care about.
var LikeButtonView = BaseButtonView.extend({
template: _.template($('#like-button').html()),
events: {
"click .icon": "toggleLike",
},
initialize: function (options) {
BaseButtonView.prototype.initialize.call(this, options); // inherit from BaseButtonView
// A shortcut that does the same thing.
this.likeButn = this.$("button.icon");
this.model.on('change:liked', this._updateView, this);
},
render: function() {
BaseButtonView.prototype.render.call(this);
// Don't mess with the HTML until after it's rendered.
this.likeButn.addClass(this.model.isLiked ? "liked", "unliked");
},
toggleLike: function() {
this.model.set('liked', !this.model.get('liked'));
},
_updateView: function () {
if (this.model.get('liked')) {
this._showLikedState();
} else {
this._showUnlikedState();
}
}
});
How the like model gets initialized is, as I said above, up to you. You can set a URL on the model's options and in your page's startup code tell it to "fetch", in which case it'll get the state from some REST endpoint on your server. Or you can set it to a default of 'false'. Or you can set it in hidden HTML (a hidden div or something) and then use your page startup code to find it:
new LikeButtonView({model: new LikeButton({}, {url: "/where/page/state/is"}));
or
new LikeButtonView({model: new LikeButton({liked: $('#hiddendiv').data('liked')}, {}));
If you're going to save the liked state, I'd recommend the URL. Then you have someplace to save your data.

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.Marionette: Pass data down through a CompositeView to its itemView?

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).

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());
});

Backbone click event fires events for all collection rather than model

Can't figure out what's wrong. When I click on a model title, it fetches all models in collection at once rather than fetch one model. If I move this event from logView to logsView it works properly but doesn't have access to model, well I can find this model using index or ant other model's ID but don't think this is a nice way.
var Log = Backbone.Model.extend({});
window.LogsList = Backbone.Collection.extend({
model:Log,
url:function (tag) {
this.url = '/logs/' + tag;
return this;
}
});
window.colList = new LogsList();
window.logView = Backbone.View.extend({
el:$('.accordion'),
template:_.template($('#log').html()),
initialize:function () {
this.model.bind('add', this.render, this);
},
events:{
"click .accordion-toggle" :"getLogBody"
},
render:function () {
return this.template(this.model.toJSON());
},
getLogBody:function () {
this.model.fetch();
}
});
window.LogsView = Backbone.View.extend({
el:$("#content"),
initialize:function (options) {
colList.bind('reset', this.addAll, this);
colList.url(options.data).fetch();
},
addOne:function (model) {
var view = new logView({model:model});
$("#accordion").append(view.render());
},
addAll:function () {
colList.each(this.addOne);
}
});
window.listView = new LogsView({data:"Visa_Cl"});
The problem is caused by your el in the LogView: el:$('.accordion')
Backbone's view events are scope to the view's el. In this case, you've specified the view's el as ALL HTML elements with a class of "accordion". Therefore, when you click on any of your HTML elements with this class, the code runs for all of them, which is why you are seeing this behavior.
This article will show you a few options for doing what you want, correctly:
Backbone.js: Getting The Model For A Clicked Element
I would also recommend reading this one, to better understand the use of el in Backbone, and a few of the tricks and traps of it:
Backbone.js: Object Literals, Views Events, jQuery, and el

Resources