Backbone update model if its already exist in collection - javascript

I'm trying to implement basic cart on backbone.js I'm completely new in it.itemsListView adds object to cartCollection. Problem is that when model is added in collection I want to increment this model quantity attribute if this model already exist in cartCollection.
var Phone = Backbone.Model.extend({});
var PhonesCollection = Backbone.Collection.extend({
model: Phone
});
var itemListView = Backbone.View.extend({
collection: null,
_template: _.template($('#listTemplate').html()),
el: $('#phonesDiv'),
events: {
'click .buyButton': '_addToCart'
},
initialize: function () {
'use strict';
this.render();
},
render: function () {
'use strict';
var rendTemplate = this._template({items: this.collection.toJSON()});
this.$el.html(rendTemplate);
return this;
},
_addToCart: function (e) {
'use strict';
var buttonId = $(e.currentTarget).attr('id');
var result = this.collection.findWhere({id: buttonId});
var purchase = {
id: result.attributes.id,
name: result.attributes.name,
price: result.attributes.price
};
cartcollection.add(new cartModel({
id: buttonId,
item: _.pick(purchase, 'id', 'name', 'price'),
itemTotalPrice: purchase.price
}));
console.log(cartcollection);
}
});
cartModel and cartCollection:
var cartModel = Backbone.Model.extend({
defaults: {
id: null,
item: {
id: null,
name: null,
price: null
},
itemTotalPrice: 0,
quantity: 1
}
});
var cartCollection = Backbone.Collection.extend({
model: cartModel,
defaults:{
totalQuantity: 0,
totalPrice: 0
}

You can do this by adding a method to your collection class. Here's one way to do it, with a method I'm calling addToCart:
var cartModel = Backbone.Model.extend({
defaults: {
quantity: 0
}
});
var cartCollection = Backbone.Collection.extend({
model: cartModel,
addToCart: function (model) {
this.add(model);
var q = model.get('quantity');
model.set('quantity', q + 1);
}
});
When you call a Backbone collection's add method, if the model you use as an argument is already in the collection, it will not be added again. Then, you can just increment the model's quantity manually.
The code below shows how this would work; you can play with it in JSBin.
var m1 = new cartModel({ name: 'm1' });
var m2 = new cartModel({ name: 'm2' });
var cart = new cartCollection();
cart.addToCart(m1);
cart.addToCart(m1);
cart.addToCart(m2);
console.log('cart length:', cart.length); // 2
console.log('m1 quantity:', m1.get('quantity')); // 2
console.log('m2 quantity:', m2.get('quantity')); // 1

$(function() {
var cartModel = Backbone.Model.extend({
defaults: {
id: null,
item: "",
quantity: 1
}
});
var cartCollection = Backbone.Collection.extend({
model: cartModel
});
// sample data
var data = [{id: 1, item:"testA"}, {id: 2, "item":"testB"}, {id: 1, "item":"testA"}, {id: 1, "item":"testA"}]
var cart = new cartCollection();
for(i in data){
var item = data[i];
// check if item already exists in collection
var model = cart.get(item.id);
if(model){
// increment model's quantity by 1
var quantity = model.get("quantity");
model.set("quantity", ++quantity);
// remove the model from collection and add updated model
cart.remove(item.id);
cart.add(model);
}else{
// if model doesn't exist in collection
// simple add it to collection
cart.add(item);
}
}
console.log(cart);
});
<script src="http://getfirebug.com/firebug-lite-debug.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.2.1/backbone-min.js"></script>

Related

ko.mapping (update array issue)

I have the problem with ko.mapping
1) On client ViewModel:
var self = {
books: ko.observableArray([ {id : 1, title: "A"},{id : 2, title: "B" } ])
};
2) From server (array of objects) :
var data = [ {id : 1, title: "C"} ]
3)I need to replace data only if it exists:
[ {id : 1, title: "C"},{number : 2, title: "B" } ]
3) I try to using ko.mapping plugin, but result [ {id : 1, title: "C"} ] => data replace self.books
var mappingOptions = {
key: function (data) {
return ko.utils.unwrapObservable(data.id);
}
};
ko.mapping.fromJS(data, mappingOptions, self.books);
Thx:)
You can (probably) use the ko.mapping plugin for this, but I think it'll benefit you if you try to solve this in plain javascript first. It's not much code, and at the very least you'll better understand what kinds of things the plugin does under the hood.
Your books array needs to be merged with an array of updates (data). You use the id property to check for equality. So, in short, you'll have to:
Loop through your updates
Find the old book that matches the id
Replace it with the new book
Set the books array with the updated list so knockout can figure out what has changed
A straightforward implementation of the update function:
var books = ko.observableArray([
{id : 1, title: "A"},
{id : 2, title: "B" }
]);
var updateBooks = function(updates) {
var oldBooks = books();
var newBooks = oldBooks.map(function(book) {
var update = updates.find(function(update) {
return update.id === book.id;
});
return update || book;
});
books(newBooks);
};
ko.applyBindings({
books: books,
updateBooks: updateBooks
});
updateBooks([{ id: 1, title: "C" }]);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<ul data-bind="foreach: books">
<li>
<span data-bind="text:id"></span>
<strong data-bind="text: title"></strong>
</ul>
This implementation will work if you don't mind performance requirements, and if you can be certain all of the ids in the updates are already available in the oldBooks (i.e. the updates do not contain new books). It also creates a new array with a combination of new and old book objects. This makes it harder for knockout to render the list efficiently.
With some more work, you can make a book viewmodel with an observable title property. By using an id based map, you can speed up the update cycle.
var Book = function(options) {
this.id = options.id;
this.title = ko.observable(options.title);
// Instead of creating new Books all the time,
// we update its observable property so only the changed values
// are re-rendered
this.update = function(options) {
this.title(options.title);
}.bind(this);
};
Book.create = function(options) { return new Book(options); };
var BookList = function(bookData) {
this.books = ko.observableArray(bookData.map(Book.create));
// Compute an object that stores each book by id.
// Whenever the books array changes, this object is updated.
// Access a book by calling: this.bookMap()[bookId]
this.bookMap = ko.pureComputed(function() {
return this.books().reduce(function(map, book) {
map[book.id] = book;
return map;
}, {});
}, this);
};
BookList.prototype.updateBooks = function(updates) {
// Apply each update
updates.forEach(function(newBook) {
// Find the book by id in our map
var bookRef = this.bookMap()[newBook.id];
if (bookRef) {
// The book has its own viewmodel, with an update method
bookRef.update(newBook);
}
}.bind(this));
};
var data = [
{id : 1, title: "A"},
{id : 2, title: "B" }
];
var vm = new BookList(data);
ko.applyBindings(vm);
vm.updateBooks([{ id: 1, title: "C" }]);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<ul data-bind="foreach: books">
<li>
<span data-bind="text:id"></span>
<strong data-bind="text: title"></strong>
</ul>

Model is not a constructor-Backbone

I have created a model and collection for a json to be fetched as shown here.When i'm instantiating in the service i'm getting error that my model is not a constructor.My model uses collection of models for storing time/value pairs.
ServiceMonitoringModel.js
define(function(require) {
'use strict';
var _ = require('underscore');
var Backbone = require('backbone');
var ServiceMonitoringCollection=require('./ServiceMonitoringCollection');
var ServiceMonitoringModel = Backbone.Model.extend({
modelNAme: 'ServiceMonitoringModel',
idAttribute: 'id',
defaults: {
// todo
content_type: '',
content_graph: {
capacity: null,
performance: {
memory: new ServiceMonitoringCollection(),
cpu: new ServiceMonitoringCollection()
}
}
},
initialize: function() {
//todo
},
validate: function(attributes) {
},
parse: function(response) {
return {
content_type: response.content_type,
content_graph: {
capacity:this.getDeepJsonValue(response, 'capacity'),
performance: {
memory: new ServiceMonitoringCollection(this.getDeepJsonValue(response, 'memory'),{parse:true}),
cpu: new ServiceMonitoringCollection(this.getDeepJsonValue(response, 'cpu'),{parse:true})
}
}
};
}
});
return ServiceMonitoringModel;
});
Service.js
...
var ServiceMonitoringModel=require('common/model/server/ServiceMonitoringModel');
var ServiceMonitoringModel = new ServiceMonitoringModel();
Your problem is:
var ServiceMonitoringModel = new ServiceMonitoringModel();
You are assigning a value to your Model definition. Try:
var serviceMonitoringModel = new ServiceMonitoringModel();
Notice the lowercase s

Backbone collection inside model

I'm new to backbone and I wonder if there is a way to save previous models in a collection as an attribute of the model itself. For example,
var history = Backbone.Collection.extend({});
var myModel = Backbone.Model.extend({
defaults: {
id: '',
name: '',
history: history //history is a collection of myModel
},
//override setter so when set method is called, it will save the previous model inside history collection.
})
This would be ephemeral history
var myModel = Backbone.Model.extend({
defaults:{
id:''
},
constructor: function(){
this.history = new Backbone.Collection();
},
set: function(){
var args = Array.prototype.slice.call(arguments);
this.history.add(this.toJSON());
return Backbone.Model.prototype.set.apply(this, args);
}
});

Backbone - Possible to get the collection from a model

I'm wondering if there's a way to get a reference to a collection from one of its models. For instance, if any of the people in the collection below are somehow aware of belonging to a collection, or multiple collections. Fiddle
(function() {
window.App = {
Models: {},
Views: {},
Collections: {}
};
App.Models.Person = Backbone.Model.extend({
defaults: {
name: 'John',
phone: '555-555-5555'
}
});
App.Views.Person = Backbone.View.extend({
tagName: 'li',
template: _.template("<%= name %> -- <%= phone %>"),
render: function(){
var template = this.template( this.model.toJSON() );
this.$el.html( template );
return this;
}
});
App.Collections.People = Backbone.Collection.extend({
model: App.Models.Person
});
App.Views.People = Backbone.View.extend({
tagName: 'ul',
add: function(person){
var personView = new App.Views.Person({ model: person });
this.$el.append( personView.render().el );
return this;
},
render: function() {
this.collection.each(this.add, this);
return this;
}
});
})();
var peeps = [ { name: 'Mary' }, { name: 'David' }, { name: 'Tiffany' } ];
var people = new App.Collections.People(peeps);
var peopleView = new App.Views.People({ collection: people });
peopleView.render().$el.appendTo('body');
Each model has a property called collection. In your fiddle, adding console.log(people.models[0].collection) will print out the collection.
Looking through the source code, it looks like this is what's used to do things like remove a model from a collection when the model's destroy() method is called.
Update: see this updated fiddle which creates three person models and two collections. It prints them to the console. It looks like model.collection only refers to the first collection the person was added to, not the second.

KnockOutJS trigger parent function on child subscribe

I am currently trying to learn KnockOutJS. I thought it would be a great idea to create a simple task-list application.
I do not want to write a long text here, let's dive into my problem. I appreciate all kind of help - I am new to KnockOutJS tho!
The tasks are declared as followed:
var Task = function (data) {
var self = this;
self.name = ko.observable(data.name);
self.status = ko.observable(data.status);
self.priority = ko.observable(data.priority);
}
And the view model looks like this
var TaskListViewModel = function() {
var self = this;
self.currentTask = ko.observable();
self.currentTask(new Task({ name: "", status: false, priority: new Priority({ name: "", value: 0 }) }));
self.tasksArr = ko.observableArray();
self.tasks = ko.computed(function () {
return self.tasksArr.slice().sort(self.sortTasks);
}, self);
self.sortTasks = function (l, r) {
if (l.status() != r.status()) {
if (l.status()) return 1;
else return -1;
}
return (l.priority().value > r.priority().value) ? 1 : -1;
};
self.priorities = [
new Priority({ name: "Low", value: 3 }),
new Priority({ name: "Medium", value: 2 }),
new Priority({ name: "High", value: 1 })
];
// Adds a task to the list
// also saves updated task list to localstorage
self.addTask = function () {
self.tasksArr.push(new Task({ name: self.currentTask().name(), status: false, priority: self.currentTask().priority() }));
self.localStorageSave();
self.currentTask().name("");
};
// Removes a task to a list
// also saves updated task list to localstorage
self.removeTask = function (task) {
self.tasksArr.remove(task);
self.localStorageSave();
};
// Simple test function to check if event is fired.
self.testFunction = function (task) {
console.log("Test function called");
};
// Saves all tasks to localStorage
self.localStorageSave = function () {
localStorage.setItem("romaTasks", ko.toJSON(self.tasksArr));
};
// loads saved data from localstorage and parses them correctly.
self.localStorageLoad = function () {
var parsed = JSON.parse(localStorage.getItem("romaTasks"));
if (parsed != null) {
var tTask = null;
for (var i = 0; i < parsed.length; i++) {
tTask = new Task({
name: parsed[i].name,
status: parsed[i].status,
priority: new Priority({
name: parsed[i].priority.name,
value: parsed[i].priority.value
})
});
self.tasksArr.push(tTask);
}
}
};
self.localStorageLoad();
}
What I want to do in my html is pretty simple.
All tasks I have added are saved to localStorage. The save function is, as you can see, called each time an element has been added & removed. But I also want to save as soon as the status of each task has been changed, but it is not possible to use subscribe here, such as
self.status.subscribe(function() {});
because I cannot access self.tasksArr from the Task class.
Any idea? Is it possible to make the self.tasksArr public somehow?
Thanks in advance!
Try this:
self.addTask = function () {
var myTask = new Task({ name: self.currentTask().name(), status: false, priority: self.currentTask().priority() })
myTask.status.subscribe(function (newValue) {
self.localStorageSave();
});
self.tasksArr.push(myTask);
self.localStorageSave();
self.currentTask().name("");
};

Categories

Resources