BackboneJS and Model/Collection - model-view-controller

Have json-data like this:
{ tournaments: [ {
tournament_id: "..."
tournament_name: "..."
events: [ {
event_id: ...
event_name: ....
param : [ {
param_a :
param_b : ..
subparan : [ {
sub_1: 1
sub_2 : 2...
So. I don't understand - how to it implement into BackBone Collection/Model style?
How to handle change sub_1? - Made Collection of Collection of Collection?

Simpliest way described in backbone tutorial:
var Events = Backbone.Collection.extend({
initialize: function(){
this.params = new Params()
}
})
var Tournaments = Backbone.Model.extend({
initialize: function(){
this.events = new Events()
}
})
var tournaments = new Tournaments()
You can continue nesting by you needs. When I was working on similar task I wrap each collection in model representing collection state and change itself in answer of collection events. This allows not to asking nested collections about its state having actual state in model.
var CollModel = Backbone.Model.extend({
defaults: {
state = ''//or list or dict or whatever
},
initialize: function(){
this.items = new Backbone.Collection();
this.items.on('all', this.setState, this)
},
setState: function(){
this.set(
'state',
this.items.reduce(function(state, item){
/*calculate state*/
}, '')
)
},
info: function(){
return this.get('state')
}
})
So you can nest collection-models with similar technic and read their state directly through instance.info() depends on how you calculate it. Your top model state will be updated from cascade updates of underneath models-collections.

Related

How to render a sorted collection from unsorted data in Backbone?

I'm fetching unsorted data from a server and want to display it in a sorted list using Backbone. For that purpose I'm using a comparator in the collection. However, Backbone fires the add events in an inconvenient order when adding multiple models to a collection at once.
Here's an example illustrating my problem (JSFiddle: http://jsfiddle.net/5wtnjj8j/2/):
In the initialize function of the PersonCollectionView I'm adding three persons to the collection (note that they are not sorted correctly). Each time Backbone inserts one of these models into the collection it fires an add event and my personAdded function is called. This function outputs the name of the inserted person and the index at which it is inserted.
This is the output I get:
insert "Alice" at index: 0
insert "Eve" at index: 2
insert "Bob" at index: 1
Obviously, the indices are correct (i.e., sorted by name). But why does Backbone fire the add events in the order that the models are specified in, not in the order of the indices?
I think this behavior is counter-intuitive because it makes it hard to build a sorted list of views. For example, imagine that I want to build a <ul> for the models. Inserting Alice would work (because her index is 0), but when the second add event arrives, I'm about to insert Eve at index 2 without having received Bob at index 1 first.
Is there a particular reason why Backbone fires the add events in the 'wrong' order and is there a way to receive the events sorted by index?
Model
var Person = Backbone.Model.extend({
defaults: {
name: 'Unknown'
}
});
Collection
var PersonCollection = Backbone.Collection.extend({
model: Person,
comparator: 'name'
});
View
var PersonCollectionView = Backbone.View.extend({
initialize: function() {
this.collection = new PersonCollection();
this.collection.on('add', this.personAdded, this);
var models = [{name: 'Alice'}, {name: 'Eve'}, {name: 'Bob'}];
this.collection.add(models);
},
personAdded: function(model, collection, options) {
var index = collection.indexOf(model);
var message = 'insert "' + model.get('name') + '" at index: ' + index + '<br>';
$('body').append(message);
}
});
A simple solution to your problem is to sort the models list before adding it to the collection.
var models = [{name: 'Alice'}, {name: 'Eve'}, {name: 'Bob'}];
this.collection.add(_.sortBy(models, 'name'));
Here's an example http://jsfiddle.net
Backbone is adding the models in the order you gave them to it and sort them after that, and before the first event reach your personAdded function Backbone has already added all the models that's why you got index 2 for Eve and not 1
For the fetch call, try to redefine the parse function:
var PersonCollectionView = Backbone.View.extend({
...
parse: function(response) {
return _.sortBy(response, 'name')
}
I believe you can also create a new collection and instantiate the collection with the unsorted array
initialize: function() {
var models = [{name: 'Alice'}, {name: 'Eve'}, {name: 'Bob'}];
this.collection = new PersonCollection(models );
this.collection.on('add', this.personAdded, this);
},
another approach you can take is declare your collection add binding after your items have been added.
initialize: function() {
this.collection = new PersonCollection();
var models = [{name: 'Alice'}, {name: 'Eve'}, {name: 'Bob'}];
this.collection.add(models);
this.collection.on('add', this.personAdded, this);
},
Edit: alternative approach to creating a collection view. It is generally not a good idea for the view to know where/what the container is (body in this case). It's a better practice to return the content of view from the render() function. Then add the view's el to the body.
var PersonCollectionView = Backbone.View.extend({
initialize: function(opts) {
var models = [{name: 'Alice', index:1}, {name: 'Eve', index:2}, {name: 'Bob', index:3}];
this.collection = new PersonCollection(models);
this.collection.on('add', this.personAdded, this);
},
personAdded: function(model, collection, options) {
var index = collection.indexOf(model);
var message = 'insert "' + model.get('name') + '" at index: ' + model.get('index') + '<br>';
this.$el.append(message);
},
render: function(){
this.collection.each(function(model){
this.$el.append('insert "' + model.get('name') + '" at index: ' + model.get('index')+"</br>");
},this);
return this; //making it chainable (render().$el);
}
});
//from server
var personCollectionView = new PersonCollectionView();
$('body').append(personCollectionView.render().el);

Dojo Cache - Tree - Uncaught TypeError: object is not a function

I am trying to use a data store cache with a tree.
I am getting Uncaught TypeError: object is not a function error.
I have tested the data and it is being pulled correctly.
I have checked the JSON and it is also correct.
Where am I going wrong?
require(["dojo/store/JsonRest"
, "dojo/store/Memory"
, "dojo/store/Cache"
, "dojo/json"
, "dijit/tree/ObjectStoreModel"
, "dijit/Tree"
, "dojo/domReady!"],
function (JsonRest, Memory, Cache, ObjectStoreModel, Tree) {
var restStore = new JsonRest({ target: "/DataStore/", idProperty: "id" });
var memoryStore = new Memory({
idProperty: "id",
getChildren: function (object) {
return this.query({ parent: object.id });
}
});
var store = new Cache(restStore, memoryStore);
store.query({ type: "index" }).forEach(function (item) {
console.log(item.name);
});
var docModel = new ObjectStoreModel(
{
store: store,
getRoot: function (onItem) {
this.store.get("index").then(onItem);
},
mayHaveChildren: function (object) {
return object.type === "folder" || object.type === "root";
}
});
var docTree = new Tree({
model: docModel,
onOpenClick: true,
onClick: function (item) {
if (item.type == "link") {
OpenLink(item.link);
}
},
persist: false
}, "divTree");
docTree.startup();
});
This has to do how Cache works. The first time store.get() is called, it uses the JsonRest store which returns a Promise. A Promise has a then() function so there is no problem. The next time it's called, it uses the Memory store which returns your JavaScript object itself. Your JavaScript object has no then() function, so an error is thrown. This can be fixed by surrounding the store.get() in a when().
Try changing
this.store.get("index").then(onItem);
to
when(this.store.get("index")).then(onItem);
Take a look here for more details.

AngularJS - testing factories which store state/data

I am working on new version of an app using legacy API (I have no controll of what the API returns etc.. ).
On the app init I request & store some site-wide info the factory which I have called stateFactory. Inside the stateFactory there is categories property (array of objects) which is storing the id -> category name relation.
Inside my app template I am using a filter to extract the name of the category by id {{ cat_id | categoryNameByIdFilter }} which is doing a lookup in the stateFactory.categories and returns the category name.
How do I write a unit test for such functionality (jasmine, mocha, chai, anything)?
// represantion of what the stateFactory looks like with some data in it
app.factory('stateFactory', ['', function(){
return {
categories = [
{ cat_id: 1, cat_name: "some category name" },
{ cat_id: 2, cat_name: "another category name" }
];
};
}])
// categoryNameByIdFilter
app.factory('categoryNameByIdFilter', ['stateFactory', function(stateFactiry){
return function(cat_id){
if ( !cat_id ) return null;
var cat_obj = _.findWhere(stateFactiry.categories, {
id: cat_id
});
if ( !cat_obj ) return null;
return cat_obj.cat_name;
};
}]);
I suggest using Jasmine and angular's mock module. You can create a mock of the stateFactory so that it does not hit a web service while unit testing. I have used Sinon to create my mocks and spies. You can, then, have angular inject your mock instead of the real service. This way, the only system under test is the categoryNameByIdFilter and not your web service.
// representation of what the stateFactory looks like with some data in it
app.factory('stateFactory', ['', function ()
{
return function ()
{
//This is the real stateFactory, which we are going to mock out.
};
}]);
// categoryNameByIdFilter - The system under test in this example
app.factory('categoryNameByIdFilter', ['stateFactory', '_', function (stateFactiry, _)
{
return function (cat_id)
{
if (!cat_id) return null;
var cat_obj = _.findWhere(stateFactiry.categories, {
id: cat_id
});
if (!cat_obj) return null;
return cat_obj.cat_name;
};
}]);
Given the code above, we can test categoryNameByIdFilter by doing this...
describe("categoryNameByIdFilter", function ()
{
beforeEach(module('YOUR_APP_MODULE'));
beforeEach(function ()
{
//The following line creates a mock of what we expect the state factory to return.
//We're mocking this because it is no the system under test, the filter is.
//A sinon 'stub' is a spy
mockStateFactory = sinon.stub({
categories: [
{ id: 1, cat_name: "some category name" },
{ id: 2, cat_name: "another category name" }
]
});
module(function ($provide)
{
//When Angular asks for a stateFactory, give them this mock instead
$provide.value('stateFactory', mockStateFactory);
});
});
//You can inject a filter using the "inject" method below
it("should filter by id", inject(function (categoryNameByIdFilter)
{
//Wrap categoryNameByIdFilter in a spy so that we can make assertions off of it.
var spy = sinon.spy(categoryNameByIdFilter);
var result = spy(1);
expect(result).toEqual("some category name");
expect(spy.calledBefore(mockStateFactory)).toBeTruthy();
expect(spy.returned("some category name")).toBeTruthy();
sinon.assert.calledOnce(spy);
spy(2);//Returns something besides "some category name"
expect(spy.alwaysReturned("some category name")).not.toBeTruthy();
sinon.assert.calledTwice(spy);
}));
});

Marionette controller.listenTo not capturing event

I'm having a weird problem with some Backbone Marionette controller in a sub-application module.
I cannot make it to work capturing one of its view events with the "controller.listenTo(view, 'event', ...)" method, although the "view.on('event',...)" works no problem.
Here is the sample code for the module views :
MyApp.module("SubApp.Selector", function (Selector, App, Backbone, Marionette, $, _) {
"use strict";
// Category Selector View
// ------------------
// Display a list of categories in the selector
// Categories list item
Selector.CategoryOption = Marionette.ItemView.extend({
template: "#my-category-template",
tagName: "option",
onRender: function () { this.$el.attr('value', this.model.get('id')); }
});
// Categories list container
Selector.CategoryListView = Marionette.CompositeView.extend({
template: "#my-category-list-template",
tagName: "div",
id: "categories",
itemView: Selector.CategoryOption,
itemViewContainer: "select",
events: {
'change #category_items': 'categoryClicked'
},
categoryClicked: function() {
var catID = this.$('#category_items').val();
console.log("category clicked "+catID);
this.trigger("category:changed", catID);
}
});
// Selector Component Controller
// -------------------------------
Selector.Viewer = Marionette.Controller.extend({
initialize: function (_options) {
this.region = _options.region;
this.collection = _options.collection;
},
show: function () {
var view = new Selector.CategoryListView({ collection: this.collection });
this.listenTo(view, 'category:changed', this.categoryChanged);
//view.on('category:changed', this.categoryChanged, this);
this.region.show(view);
},
categoryChanged: function (_category) {
console.log("category changed in controller");
}
});
});
Is there anything I got wrong about event listening from a controller object ?
Shouldn't I use the listenTo syntax as is seems to be recommended widely for proper event handling and listener destruction ?

How to read inline data written in a store from a controller in Sencha Touch 2.0 mvc

I am learning about Sencha so i have very basilar question.
I am trying to build a simple mvc application, with controller, views model and store.
I have this nmodel
Ext.define('rpc.model.Studenti', {
Extend: 'Ext.data.Model',
fields: [
{ name: 'cognome', type: 'string' },
{ name: 'previsione', type: 'string' }
]
});
this store (with inline data)
Ext.define('rpc.store.Studenti', {
model: 'rpc.model.Studenti',
storeId: 'gruppoStore',
autoLoad: true,
data: [
{cognome: 'uno', previsione: 'aaa'},
{cognome: 'due', previsione: 'bbb'},
{cognome: 'tre', previsione: 'ccc'}
]
});
the controller
Ext.define('rpc.controller.Home', {
extend: 'Ext.app.Controller',
stores: ['Studenti'],
models: ['Studenti'],
views: ['home.Fila','home.Griglia','home.Previsio'],
store: 'gruppoStore',
init: function() {
this.control({
'griglia button': {
tap: this.faqualcosa
}
});
},
faqualcosa: function(button){
...
var gruppoStoreMgr=this.getStudentiStore();
alert (gruppoStoreMgr.count());
for (var key in gruppoStoreMgr) {
//alert (key);
}
alert (gruppoStoreMgr.storeId);
alert (gruppoStoreMgr.isInstance);
//alert (gruppoStoreMgr.data);
alert (gruppoStoreMgr.autoLoad);
for (var key in gruppoStoreMgr.data[0]) {
//alert ("0"+key);
}
for (var key in gruppoStoreMgr.data[1]) {
//alert ("1"+key);
}
}
});
Please, what is the right way to access in function faqualcosa to the store data?
I've been able to have something like an instance (sure it is very near...) of the model with var gruppoStoreMgr=this.getStudentiStore(); but i have no trace of the data i've written in the store ("uno", "due"....). How to get that data? How to have in a controller funcion an object who refers to the real data in the store?
this.getStudentiStore().data.items
That is the most direct way to get to the data. It returns an array of the model records. If that's what you're looking for.
for(var i = 0; i < this.geStudentiStore().getCount(); i++ ) {
var record = this.getStudentiStore().getAt(i)
console.log(record.get('cognome'));
}
That should print out all the "cognomes"

Resources