Access collection on two views in backbone.js - javascript

Hi i have a collection and two views. On my view1 i'm adding data to my collection and view2 will just render and display any changes about the collection. But i can't get it to work. The problem is originally i'm doing this
return new CartCollection();
But they say its a bad practice so i remove changed it. But when i instantiate cart collection on view1 it would add but it seems view2 doesn't sees the changes and renders nothing.
Any ideas?
here is my cart collection.
define([
'underscore',
'backbone',
'model/cart'
], function(_, Backbone, CartModel) {
var CartCollection = Backbone.Collection.extend({
model : CartModel,
initialize: function(){
}
});
return CartCollection;
});
Here is my itemView ( view1 )
AddToCart:function(ev){
ev.preventDefault();
//get data-id of the current clicked item
var id = $(ev.currentTarget).data("id");
var item = this.collection.getByCid(id);
var isDupe = false;
//Check if CartCollection is empty then add
if( CartCollection.length === 0){
CartCollection.add([{ItemCode:item.get("ItemCode"),ItemDescription:item.get("ItemDescription"),SalesPriceRate:item.get("RetailPrice"),ExtPriceRate:item.get("RetailPrice"),WarehouseCode: "Main",ItemType : "Stock",LineNum:1 }]);
}else{
//if check if the item to be added is already added, if yes then update QuantityOrdered and ExtPriceRate
_.each(CartCollection.models,function(cart){
if(item.get("ItemCode") === cart.get("ItemCode")){
isDupe = true;
var updateQty = parseInt(cart.get("QuantityOrdered"))+1;
var extPrice = parseFloat(cart.get("SalesPriceRate") * updateQty).toFixed(2);
cart.set({ QuantityOrdered: updateQty });
cart.set({ ExtPriceRate: extPrice });
}
});
//if item to be added has no duplicates add new item
if( isDupe == false){
var cartCollection = CartCollection.at(CartCollection.length - 1);
var lineNum = parseInt( cartCollection.get("LineNum") ) + 1;
CartCollection.add([{ItemCode:item.get("ItemCode"),ItemDescription:item.get("ItemDescription"),SalesPriceRate:item.get("RetailPrice"),ExtPriceRate:item.get("RetailPrice"),WarehouseCode: "Main",ItemType : "Stock",LineNum:lineNum}]);
}
}
CartListView.render();
}
My cartview (view2)
render: function(){
this.$("#cartContainer").html(CartListTemplate);
var CartWrapper = kendobackboneModel(CartModel, {
ItemCode: { type: "string" },
ItemDescription: { type: "string" },
RetailPrice: { type: "string" },
Qty: { type: "string" },
});
var CartCollectionWrapper = kendobackboneCollection(CartWrapper);
this.$("#grid").kendoGrid({
editable: true,
toolbar: [{ name: "save", text: "Complete" }],
columns: [
{field: "ItemDescription", title: "ItemDescription"},
{field: "QuantityOrdered", title: "Qty",width:80},
{field: "SalesPriceRate", title: "UnitPrice"},
{field: "ExtPriceRate", title: "ExtPrice"}
],
dataSource: {
schema: {model: CartWrapper},
data: new CartCollectionWrapper(CartCollection),
}
});
},

The problem is you've created 2 different instances of CartCollection. So when you update or fetch data into one instance the other does not change but remains the same.
Instead you need to use the same instance of CartCollection across the 2 views (or alternatively keep the 2 insync) .Assuming both views are in the same require.js module you would need to:
1) Instantiate the CartCollection instance and store it somewhere that both views have access to. You could put this in the Router, the parent view, or anywhere else really.
e.g.
var router = Backbone.Router.extend({});
router.carts = new CartCollection();
2) You need need to pass the CartCollection instance to each of your views.
e.g.
var view1 = new ItemView({ collection: router.carts });
var view2 = new CartView({ collection: router.carts });
You may also want to just pass the Cart model to the CartView instead of the entire collection.
e.g.
var cartModel = router.carts.get(1);
var view2 = new CartView({ model: cartModel });

Related

Problem with variable scope in nested Backbone views

I have a parent view which contains a Backgrid view. In the parent view's initialize section I define a variable isWellSelected. The variable is toggled in the Backgrid column logic when a tickbox is checked. I am able to watch the variable toggled when a box is ticked and unticked.
However, once an event fires the variable is no longer in scope for the event to see. I suspect I may need to pass the variable to the Backrgrid view but I am unsure how to do that correctly. Please advise.
app.wellCollectionView = Backbone.View.extend({
template: _.template($('#wellTemplate').html()),
initialize: function() {
this.isWellSelected = false;
// isWellSelected toggled to true when a tickbox is checked in the columns block.
var columns = [...];
// instantiate collection
var wellCollection = new app.wellCollection;
// Set up a grid view to use the pageable collection
var wellGrid = new Backgrid.Grid({
columns: columns,
collection: wellCollection
});
// Initialize the paginator
var paginator = new Backgrid.Extension.Paginator({
collection: wellCollection
});
// Render the template
this.$el.html(this.template());
// Render the grid
this.$el.append(wellGrid.render().el);
this.$el.append(paginator.render().$el);
wellCollection.fetch({reset: true}).then(function () {...});
},
events: {
'click #EvaluateWell': function(){
this.evalWell(event, this.isWellSelected);
console.log("In events - isWellSelected: " + this.isWellSelected);}
},
// More stuff
}
Constructive feedback welcome.
Thanks!
Adding a snippet for "columns" as per JT's request:
var columns = [
{
name: '',
label: 'Select',
cell: Backgrid.BooleanCell.extend({
events : {
'change': function(ev){
var $checkbox = $(ev.target);
var $checkboxes = $('.backgrid input[type=checkbox]');
if($checkbox.is(':checked')) {
$checkboxes.attr("disabled", true);
this.isWellSelected = true;
// Disable all checkboxes but this one
$checkbox.removeAttr("disabled");
} else {
// Enable all checkboxes again
$checkboxes.removeAttr("disabled");
this.isWellSelected = false;
}
}
}
})
}, {
name: "api",
label: "API",
editable: false, // Display only!
cell: "string"
}, {
name: "company",
label: "Operator",
editable: false, // Display only!
cell: "string"
}];

Marionette.js - Uncaught ReferenceError: text is not defined

I wonder if someone can help to find what's wrong in this case. I get "Uncaught ReferenceError: text is not defined" in line 6 app.js:
((__t=( text ))==null?'':_.escape(__t))+
driver.js:
var Marionette = require('backbone.marionette');
var TodoView = require('./views/layout');
var initialData = {
items: [
{assignee: 'Scott', text: 'Write a book about Marionette'},
{assignee: 'Andrew', text: 'Do some coding'}
]
};
var App = new Marionette.Application({
onStart: function(options) {
var todo = new TodoView({
collection: new Backbone.Collection(options.initialData.items),
model: new ToDoModel()
});
todo.render();
todo.triggerMethod('show');
}
});
App.start({initialData: initialData});
views/layout.js
var Backbone = require('backbone');
var Marionette = require('backbone.marionette');
var ToDoModel = require('../models/todo');
var FormView = require('./form');
var ListView = require('./list');
var Layout = Marionette.View.extend({
el: '#app-hook',
template: require('../templates/layout.html'),
regions: {
form: '.form',
list: '.list'
},
collectionEvents: {
add: 'itemAdded'
},
onShow: function() {
var formView = new FormView({model: this.model});
var listView = new ListView({collection: this.collection});
this.showChildView('form', formView);
this.showChildView('list', listView);
},
onChildviewAddTodoItem: function(child) {
this.model.set({
assignee: child.ui.assignee.val(),
text: child.ui.text.val()
}, {validate: true});
var items = this.model.pick('assignee', 'text');
this.collection.add(items);
},
itemAdded: function() {
this.model.set({
assignee: '',
text: ''
});
}
});
module.exports = Layout;
todoitem.html
<%- item.text %> — <%- item.assignee %>
Any can me explain why text is not defined?
check your ToDoModel for a typo, the Backbone Model field should be "defaults" not "default", while parsing for a template Marionette view looks for "defaults" field:
https://marionettejs.com/docs/master/template.html#rendering-a-model
so the ToDoModel code should go like this:
...
var ToDo = Backbone.Model.extend({
defaults: {
assignee: '',
text: ''
},
...
You should take a look at the Marionnette's ItemView documentation which explain how to render a template with custom data.
var my_template_html = '<div><%= args.name %></div>'
var MyView = Marionette.ItemView.extend({
template : function(serialized_model) {
var name = serialized_model.name;
return _.template(my_template_html)({
name : name,
some_custom_attribute : some_custom_key
});
}
});
new MyView().render();
Note that using a template function allows passing custom arguments
into the .template function and allows for more control over how the
.template function is called.
With the code you provided at the moment, I can't help.
Marionette calls 'serializeModel' before passing the context to 'template'. So, if you have a backbone.model like
{
.
.
.
attributes: {
text: 'someText',
asignee: 'someAsignee'
}
.
.
}
your template will receive
{
text: 'someText',
assignee: 'someAsignee'
}
I have worked with handlebars but not underscore exactly. There {{this.text}} and {{this.assignee}} works like a charm in the template. So, try this.text or text in place of item.text, see if that works

Backbone.Paginator infinite mode, with Marionette

In my Marionette app, I have a Collection view, with a childView for it's models.
The collection assigned to the CollectionView is a PageableCollection from Backbone.paginator. The mode is set to infinite.
When requesting the next page like so getNextPage(), the collection is fetching data and assigning the response to the collection, overwriting the old entries, though the full version is store in collection.fullCollection. This is where I can find all entries that the CollectionView needs to render.
Marionette is being smart about collection events and will render a new childView with it's new model when a model is being added to the collection. It will also remove a childView when it's model was removed.
However, that's not quite what I want to do in this case since the collection doesn't represent my desired rendered list, collection.fullCollection is what I want to show on page.
Is there a way for my Marionette view to consider collection.fullCollection instead of collection, or is there a more appropriate pagination framework for Marionette?
Here's a fiddle with the code
For those who don't like fiddle:
App = Mn.Application.extend({});
// APP
App = new App({
start: function() {
App.routr = new App.Routr();
Backbone.history.start();
}
});
// REGION
App.Rm = new Mn.RegionManager({
regions: {
main: 'main',
buttonRegion: '.button-region'
}
});
// MODEL
App.Model = {};
App.Model.GeneralModel = Backbone.Model.extend({});
// COLLECTION
App.Collection = {};
App.Collection.All = Backbone.PageableCollection.extend({
model: App.Model.GeneralModel,
getOpts: function() {
return {
type: 'POST',
contentType: 'appplication/json',
dataType: 'json',
data: {skip: 12},
add: true
}
},
initialize: function() {
this.listenTo(Backbone.Events, 'load', (function() {
console.log('Load more entries');
// {remove: false} doesn't seem to affect the collection with Marionette
this.getNextPage();
})).bind(this)
},
mode: "infinite",
url: "https://api.github.com/repos/jashkenas/backbone/issues?state=closed",
state: {
pageSize: 5,
firstPage: 1
},
queryParams: {
page: null,
per_page: null,
totalPages: null,
totalRecords: null,
sortKey: null,
order: null
},
/*
// Enabling this will mean parseLinks don't run.
sync: function(method, model, options) {
console.log('sync');
options.contentType = 'application/json'
options.dataType = 'json'
Backbone.sync(method, model, options);
}
*/
});
// VIEWS
App.View = {};
App.View.MyItemView = Mn.ItemView.extend({
template: '#item-view'
});
App.View.Button = Mn.ItemView.extend({
template: '#button',
events: {
'click .btn': 'loadMore'
},
loadMore: function() {
Backbone.Events.trigger('load');
}
});
App.View.MyColView = Mn.CollectionView.extend({
initialize: function() {
this.listenTo(this.collection.fullCollection, "add", this.newContent);
this.collection.getFirstPage();
},
newContent: function(model, col, req) {
console.log('FullCollection length:', this.collection.fullCollection.length, 'Collection length', this.collection.length)
},
childView: App.View.MyItemView
});
// CONTROLLER
App.Ctrl = {
index: function() {
var col = new App.Collection.All();
var btn = new App.View.Button();
var colView = new App.View.MyColView({
collection: col
});
App.Rm.get('main').show(colView);
App.Rm.get('buttonRegion').show(btn);
}
};
// ROUTER
App.Routr = Mn.AppRouter.extend({
controller: App.Ctrl,
appRoutes: {
'*path': 'index'
}
});
App.start();
You could base the CollectionView off the full collection, and pass in the paged collection as a separate option:
App.View.MyColView = Mn.CollectionView.extend({
initialize: function(options) {
this.pagedCollection = options.pagedCollection;
this.pagedCollection.getFirstPage();
this.listenTo(this.collection, "add", this.newContent);
},
// ...
}
// Create the view
var colView = new App.View.MyColView({
collection: col.fullCollection,
pagedCollection: col
});
Forked fiddle

Backbone: how to get and display just one model data in a view via router

I have created a very basic backbone app, to understand how it works.
In the router, I just wanna display just 1 model, i.e. a user, not the whole collection, by passing an id in the url, how to do that?
For example, I'd like to do someapp.com/app/#user/2, and this would display just user no2 details.
Please see my work in jsfiddle
// router
var ViewsRouter = Backbone.Router.extend({
routes: {
'': 'viewOne',
'one': 'viewOne',
'two': 'viewTwo',
'user/:id': 'user'
},
viewOne: function() {
var view = new TheViewOne({ model: new TheModel });
},
viewTwo: function() {
var view = new UserView({ model: new TheModel });
},
user: function(id) {
// how to get just 1 user with the corresponding id passed as argument
// and display it???
}
});
Many thanks.
https://github.com/coding-idiot/BackboneCRUD
I've written a complete Backbone CRUD with no backend stuff for beginners. Below is the part where we get the user from the collection and show/render it.
View
var UserEditView = Backbone.View.extend({
render: function(options) {
if (options && options.id) {
var template = _.template($("#user-edit-template").html(), {
user: userCollection.get(options.id)
});
this.$el.html(template);
} else {
var template = _.template($("#user-edit-template").html(), {
user: null
});
// console.log(template);
this.$el.html(template);
}
return this;
},
Router
router.on('route:editUser', function(id) {
console.log("Show edit user view : " + id);
var userEditView = new UserEditView({
el: '.content'
});
userEditView.render({
id: id
});
});
Update
Particularly, sticking to your code, the router will look something like this :
user: function(id) {
var view = new UserView({ model: userCollection.get(id) });
}

Backbone.js routing depending on model value

I have a collection that looks something like this
[
{
"year": 1868,
....
]
},
{
"year": 1872,
....
},
{
...
}
]
Is there a way to set a route either with '/year/:year': 'year' or '/(:year)': 'year' ?
I have tried making a lookup table in the main App view, which passes the year index to the model views. I have tried using _.map, _.each, _.pluck and _.where but I guess I must be doing something wrong.
Here is a non Backbone view of what it looks like. So navigating to /(:year) would go straight to that year, which corresponds to an model index
Edit: to clarify, basically I want the user to be able to go to /year/:year, but :year value corresponds to a certain model (see above). In this case going to /year/1868, would render the first model from the above collection.
EDIT #2: Here is how my app looks like.
this is the router
var router = Backbone.Router.extend({
routes: {
'': 'root',
'year(/:year)': 'year'
},
root: function() {
new App();
},
year: function(year) {
new App({
year: year
});
}
});
which calls this file
define(['backbone', 'assets/js/collections/elections.js', 'assets/js/views/election.js', 'jqueryui'], function(Backbone, Elections, ElectionView, simpleSlider) {
var AppView = Backbone.View.extend({
current_election_index: 0,
active_btn_class: 'dark_blue_bg',
wiki_base: 'http://en.wikipedia.org/wiki/United_States_presidential_election,_',
started: 0,
el: 'body',
playback: {
id: '',
duration: 1750,
status: false
},
initialize: function() {
elections = new Elections();
_.bindAll(this, 'render');
this.listenTo(elections, 'reset', this.render);
elections.fetch();
this.remove_loader();
},
render: function () {
if (this.started === 0) {
this.election_year();
}
var view = new ElectionView({
model: elections.at(this.current_election_index),
election_index: this.current_election_index
});
this._make_slider();
this.update_ui();
return this;
},
I took out some of the methods, since they are non essential.
A typical solution would looks something like this:
var AppRouter = Backbone.Router.extend({
routes: {
"year(/:year)" : "electionByYear"
},
electionByYear: function(year) {
//all of your data, wherever you get it from
var results = this.allElectionResults;
//if a year parameter was given in the url
if(year) {
//filter the data to records where the year matches the parameter
results = _.findWhere(results, { year: parseInt(year, 10)});
}
doSomething(results);
}
});
Edit based on comments: If your view is responsible for creating the collection, you should move the above logic to your view, and simply pass the year parameter to your view as an argument:
var View = Backbone.View.extend({
initialize: function(options) {
this.year = options.year;
}
});
var view = new View({year:year});
view.render();
Or if you're using an existing view:
view.year = year;
view.render();

Categories

Resources