Related
According to file account/static/src/js/reconciliation_model.js in Odoo module, there is an object assignment :
var StatementModel = BasicModel.extend({
...
...
...
load: function (context) {
var self = this;
var statement_ids = context.statement_ids;
if (!statement_ids) {
return $.when();
}
this.context = context;
var def_statement = this._rpc({
model: 'account.bank.statement',
method: 'reconciliation_widget_preprocess',
args: [statement_ids],
})
.then(function (statement) {
self.statement = statement;
self.bank_statement_id = statement_ids.length === 1 ? {id: statement_ids[0], display_name: statement.statement_name} : false;
self.valuenow = 0;
self.valuemax = statement.st_lines_ids.length;
self.context.journal_id = statement.journal_id;
_.each(statement.st_lines_ids, function (id) {
self.lines[_.uniqueId('rline')] = {
id: id,
reconciled: false,
mode: 'inactive',
mv_lines: [],
offset: 0,
filter: "",
reconciliation_proposition: [],
reconcileModels: [],
};
});
});
var def_reconcileModel = this._rpc({
model: 'account.reconcile.model',
method: 'search_read',
})
.then(function (reconcileModels) {
self.reconcileModels = reconcileModels;
});
var def_account = this._rpc({
model: 'account.account',
method: 'search_read',
fields: ['code'],
})
.then(function (accounts) {
self.accounts = _.object(_.pluck(accounts, 'id'), _.pluck(accounts, 'code'));
});
return $.when(def_statement, def_reconcileModel, def_account).then(function () {
_.each(self.lines, function (line) {
line.reconcileModels = self.reconcileModels;
});
var ids = _.pluck(self.lines, 'id');
ids = ids.splice(0, self.defaultDisplayQty);
self.pagerIndex = ids.length;
return self.loadData(ids, []);
});
},
...
...
...
});
I want to change the statement :
var def_statement = this._rpc({
model: 'account.bank.statement',
method: 'reconciliation_widget_preprocess',
args: [statement_ids],
})
to
var def_statement = this._rpc({
model: 'account.bank.statement',
method: 'reconciliation_widget_preprocess_with_line',
args: [statement_ids, statement_line_ids],
})
My code is something like this :
odoo.define('my_accounting.ReconciliationModel', function (require) {
"use strict";
var BasicModel = require('web.BasicModel');
var field_utils = require('web.field_utils');
var utils = require('web.utils');
var session = require('web.session');
var CrashManager = require('web.CrashManager');
var core = require('web.core');
var _t = core._t;
var ReconciliationModel = require('account.ReconciliationModel');
var StatementModel = ReconciliationModel.StatementModel;
var MyStatementModel = StatementModel.extend({
load: function (context) {
var self = this;
var statement_ids = context.statement_ids;
if (!statement_ids) {
return $.when();
}
var statement_line_ids = context.statement_line_ids;
this.context = context;
var def_statement = this._rpc({
model: 'account.bank.statement',
method: 'reconciliation_widget_preprocess_with_line',
args: [statement_ids, statement_line_ids],
})
.then(function (statement) {
self.statement = statement;
self.bank_statement_id = statement_ids.length === 1 ? {id: statement_ids[0], display_name: statement.statement_name} : false;
self.valuenow = 0;
self.valuemax = statement.st_lines_ids.length;
self.context.journal_id = statement.journal_id;
_.each(statement.st_lines_ids, function (id) {
self.lines[_.uniqueId('rline')] = {
id: id,
reconciled: false,
mode: 'inactive',
mv_lines: [],
offset: 0,
filter: "",
reconciliation_proposition: [],
reconcileModels: [],
};
});
});
var domainReconcile = [];
if (context && context.company_ids) {
domainReconcile.push(['company_id', 'in', context.company_ids]);
}
if (context && context.active_model === 'account.journal' && context.active_ids) {
domainReconcile.push(['journal_id', 'in', [false].concat(context.active_ids)]);
}
var def_reconcileModel = this._rpc({
model: 'account.reconcile.model',
method: 'search_read',
domain: domainReconcile,
})
.then(function (reconcileModels) {
self.reconcileModels = reconcileModels;
});
var def_account = this._rpc({
model: 'account.account',
method: 'search_read',
fields: ['code'],
})
.then(function (accounts) {
self.accounts = _.object(_.pluck(accounts, 'id'), _.pluck(accounts, 'code'));
});
return $.when(def_statement, def_reconcileModel, def_account).then(function () {
_.each(self.lines, function (line) {
line.reconcileModels = self.reconcileModels;
});
var ids = _.pluck(self.lines, 'id');
ids = ids.splice(0, self.defaultDisplayQty);
self.pagerIndex = ids.length;
return self.loadData(ids, []);
});
}
});
});
It not working well. I've performed upgrade my module and still call reconciliation_widget_preprocess method instead of reconciliation_widget_preprocess_with_line in my Odoo module.
Can someone tell me what I missing? I'm using Odoo 11 community edition. I thanks to you for any clue.
You need to use include method when Patching an existing class.
var Hamster = require('web.Hamster');
Hamster.include({
sleep: function () {
this._super.apply(this, arguments);
console.log('zzzz');
},
});
I put the fetch url with deferred method and I expect it will only invoke the remote ajax request one time.
However, it calls three times when I load the page.
How could I fix it? Thanks
js scripts
var Comments = Backbone.Collection.extend({
model: Comment,
url: fetch_comments_url,
initialize: function() {
this.fetch({
success: this.fetchSuccess,
error: this.fetchError
});
this.deferred = new $.Deferred();
},
deferred: Function.constructor.prototype,
fetchSuccess: function(collection, response) {
collection.deferred.resolve();
},
fetchError: function(collection, response) {
throw new Error("Products fetch did get collection from API");
},
var comments = new Comments();
...
comments.deferred.done(function() {
commentView.render();
emptyCommentView.render();
});
compelte js scripts
var Comments = Backbone.Collection.extend({
model: Comment,
url: fetch_comments_url,
initialize: function() {
this.fetch({
success: this.fetchSuccess,
error: this.fetchError
});
this.deferred = new $.Deferred();
},
deferred: Function.constructor.prototype,
fetchSuccess: function(collection, response) {
collection.deferred.resolve();
},
fetchError: function(collection, response) {
throw new Error("Products fetch did get collection from API");
},
wellFormedComments: function () {
var MESSAGE_LIMIT_LENGTH = 80
var models = comments.select(function (model) {
var msg = model.get("message")
if (msg!=null) {
msg = msg.replace(/^\s+|\s+$/g, '')
if (msg.length >= MESSAGE_LIMIT_LENGTH) {
model.set("preview_message", msg.substr(0, MESSAGE_LIMIT_LENGTH/2));
} else{
};
return true
}
else{
return false
};
});
return new Comments(models);
},
emptyComments: function () {
var models = comments.select(function (model) {
var msg = model.get("message")
return false===_(msg).notBlank();
});
return new Comments(models);
}
});
var comments = new Comments();
var CommentView = Backbone.View.extend({
el: $("#comments_section"),
render: function() {
var notNullComments = comments.wellFormedComments();
if (notNullComments.length > 0) {
$("#dadasay_comments_plugin").show();
}
var html = commentsTmpl(notNullComments.toJSON());
$(this.el).append(html);
},
});
var EmptyCommentView = Backbone.View.extend({
el: $("#empty_comments_list"),
render: function() {
var source = $('#empty_comments_list_tmpl').html();
var emptyComments = comments.emptyComments();
var html = emptyCommentsTmpl(emptyComments.toJSON());
$(this.el).html(html);
},
});
var commentView = new CommentView({
collection: comments
});
var emptyCommentView = new EmptyCommentView({
collection: comments
});
comments.deferred.done(function() {
commentView.render();
emptyCommentView.render();
});
The problem is that your comments collection triggers fetch when initialized. It's methods wellFormedComments and emptyComments creates new comments collections so they triggers fetch as well.
You can fix this by manually triggering fetch when required, something like:
var Comments = Backbone.Collection.extend({
model: Comment,
url: fetch_comments_url,
wellFormedComments: function() {
var MESSAGE_LIMIT_LENGTH = 80
var models = this.select(function(model) {
var msg = model.get("message")
if (msg != null) {
msg = msg.replace(/^\s+|\s+$/g, '')
if (msg.length >= MESSAGE_LIMIT_LENGTH) {
model.set("preview_message", msg.substr(0, MESSAGE_LIMIT_LENGTH / 2));
} else {};
return true
} else {
return false
};
});
return new Comments(models);
},
emptyComments: function() {
var models = this.select(function(model) {
var msg = model.get("message")
return false === _(msg).notBlank();
});
return new Comments(models);
}
});
var CommentView = Backbone.View.extend({
el: $("#comments_section"),
render: function() {
var notNullComments = comments.wellFormedComments();
if (notNullComments.length > 0) {
$("#dadasay_comments_plugin").show();
}
var html = commentsTmpl(notNullComments.toJSON());
$(this.el).append(html);
},
});
var EmptyCommentView = Backbone.View.extend({
el: $("#empty_comments_list"),
render: function() {
var source = $('#empty_comments_list_tmpl').html();
var emptyComments = comments.emptyComments();
var html = emptyCommentsTmpl(emptyComments.toJSON());
$(this.el).html(html);
},
});
var comments = new Comments();
var commentView = new CommentView({
collection: comments
});
var emptyCommentView = new EmptyCommentView({
collection: comments
});
comments.fetch({ // <--------- Do this manually once
success: function() {
commentView.render();
emptyCommentView.render();
},
error: function() {}
});
I think you can better structure your code as shown below, hope the comments explain the changes
var Comments = Backbone.Collection.extend({
model: Comment,
url: fetch_comments_url,
wellFormedComments: function() {
var MESSAGE_LIMIT_LENGTH = 80
var models = this.select(function(model) {
var msg = model.get("message")
if (msg != null) {
msg = msg.replace(/^\s+|\s+$/g, '')
if (msg.length >= MESSAGE_LIMIT_LENGTH) {
model.set("preview_message", msg.substr(0, MESSAGE_LIMIT_LENGTH / 2));
}
return true
}
return false
});
return new Comments(models);
},
emptyComments: function() {
var models = this.select(function(model) {
var msg = model.get("message")
return false === _(msg).notBlank();
});
return new Comments(models);
}
});
var CommentView = Backbone.View.extend({
el: $("#comments_section"),
template: commentsTmpl, // template reference, better create it here
initialize: function() {
this.render(); // self rendering
},
render: function() {
if (this.collection.length) { // use this.collection to refer to view's collection rather than external variables
$("#dadasay_comments_plugin").show(); //This shouldn't be a global selection
}
var html = this.template(this.collection.toJSON());
this.$el.append(html);
//---^ use cached jQuery object rather than creating new one
},
});
var EmptyCommentView = Backbone.View.extend({
el: $("#empty_comments_list"),
template: emptyCommentsTmpl,
initialize: function() {
this.render();
},
render: function() {
var source = $('#empty_comments_list_tmpl').html(); // unused?
var html = this.template(this.collection.toJSON());
this.$el.html(html);
},
});
var comments = new Comments();
comments.fetch({ // <--------- Do this manually once
success: function(collection, response) {
//----------------^ comments collection, all comments
var commentView = new CommentView({
collection: collection.wellFormedComments() // pass the resuting collection
});
var emptyCommentView = new EmptyCommentView({
collection: collection.emptyComments() // pass the resuting collection
});
},
error: function() {}
});
I am new to Ember and want a Utility class in Ember which does the following, takes in rowItems and returns an object (finalMeta)
var myMeta1 = new Array();
var myMeta2 = new Array();
dojo.forEach(rowItems, dojo.hitch(this, function(rowItem){
var metaData = {
Id: rowItem.Id,
version: rowItem.version
};
if(rowItem.tranMetaData.tpl){
myMeta2.push(metaData);
}else{
myMeta1.push(metaData);
}
}));
if(myMeta1.length == 0){
myMeta1 = null;
}
if(myMeta2.length == 0){
myMeta2 = null;
}
var finalMeta = {
"myMeta1": myMeta1,
"myMeta2": myMeta2
};
return finalMeta;
Where/How do I write this Utility class, such that it can be accessed from a different place (say from a different route) ?
Just to add, I want to use the finalMeta in a child route (part of some workflow) as inputs/request params to some API.
In the child route, I would then make an AJAX call,
Ember.$.ajax({
url: someUrl,
type: "POST",
data: JSON.stringify({
'ids': idKeys,
'metaData': finalMeta
}),
})
Two solutions come to mind. The first is probably the simplest to implement. The second might technically be more object oriented, but introduces another class with a very limited purpose.
The easy way: Include this as a method in your API service object:
function SomeApiService() {
}
SomeApiService.prototype = {
constructor: SomeApiService,
saveSomething: function(rows) {
var finalMeta = this.getMetaData(rows);
var idKeys = // create array of id keys
Ember.$.ajax({
url: someUrl,
type: "POST",
data: JSON.stringify({
'ids': idKeys,
'metaData': finalMeta
}),
});
},
doSomethingElse: function(rows) {
var finalMeta = this.getMetaData(rows);
Ember.$.ajax({
...,
data: JSON.stringify({
metaData: finalMeta
})
});
},
getMetaData: function(rowItems) {
var myMeta1 = [];
var myMeta2 = [];
dojo.forEach(rowItems, dojo.hitch(this, function(rowItem){
var metaData = {
Id: rowItem.Id,
version: rowItem.version
};
if(rowItem.tranMetaData.tpl){
myMeta2.push(metaData);
}else{
myMeta1.push(metaData);
}
}));
if(myMeta1.length == 0){
myMeta1 = null;
}
if(myMeta2.length == 0){
myMeta2 = null;
}
var finalMeta = {
"myMeta1": myMeta1,
"myMeta2": myMeta2
};
return finalMeta;
}
};
Or, roll this into its own helper class and have the API service class use it:
Your API Service class becomes slimmer, but introduces a dependency. You can pass your own metaHelper in the constructor and provide a mock object for testing, but it could default to a new MetaDataHelper object.
function SomeApiService(metaHelper) {
this.metaHelper = metaHelper || new MetaDataHelper();
}
SomeApiService.prototype = {
constructor: SomeApiService,
saveSomething: function(rows) {
var finalMeta = this.metaHelper.getMetaData(rows);
var idKeys = // create array of id keys
Ember.$.ajax({
url: someUrl,
type: "POST",
data: JSON.stringify({
'ids': idKeys,
'metaData': finalMeta
}),
});
},
doSomethingElse: function(rows) {
var finalMeta = this.metaHelper.getMetaData(rows);
Ember.$.ajax({
...,
data: JSON.stringify({
metaData: finalMeta
})
});
}
};
And the MetaDataHelper class doesn't contain much at this point, however you will have separated your concerns and made the meta data helper object testable by itself. This also allows you to write other API service classes that use the MetaDataHelper object to prevent the duplication of this logic.
function MetaDataHelper() {
}
MetaDataHelper.prototype.getMetaData = function(rowItems) {
var myMeta1 = [];
var myMeta2 = [];
dojo.forEach(rowItems, dojo.hitch(this, function(rowItem){
var metaData = {
Id: rowItem.Id,
version: rowItem.version
};
if(rowItem.tranMetaData.tpl){
myMeta2.push(metaData);
}else{
myMeta1.push(metaData);
}
}));
if(myMeta1.length == 0){
myMeta1 = null;
}
if(myMeta2.length == 0){
myMeta2 = null;
}
var finalMeta = {
"myMeta1": myMeta1,
"myMeta2": myMeta2
};
return finalMeta;
};
I'm starting to learn and azure phonejs.
Todo list get through a standard example:
$(function() {
var client = new WindowsAzure.MobileServiceClient('https://zaburrito.azure-mobile.net/', 'key');
var todoItemTable = client.getTable('todoitem');
// Read current data and rebuild UI.
// If you plan to generate complex UIs like this, consider using a JavaScript templating library.
function refreshTodoItems() {
var query = todoItemTable.where({ complete: false });
query.read().then(function(todoItems) {
var listItems = $.map(todoItems, function(item) {
return $('<li>')
.attr('data-todoitem-id', item.id)
.append($('<button class="item-delete">Delete</button>'))
.append($('<input type="checkbox" class="item-complete">').prop('checked', item.complete))
.append($('<div>').append($('<input class="item-text">').val(item.text)));
});
$('#todo-items').empty().append(listItems).toggle(listItems.length > 0);
$('#summary').html('<strong>' + todoItems.length + '</strong> item(s)');
}, handleError);
}
function handleError(error) {
var text = error + (error.request ? ' - ' + error.request.status : '');
$('#errorlog').append($('<li>').text(text));
}
function getTodoItemId(formElement) {
return $(formElement).closest('li').attr('data-todoitem-id');
}
// Handle insert
$('#add-item').submit(function(evt) {
var textbox = $('#new-item-text'),
itemText = textbox.val();
if (itemText !== '') {
todoItemTable.insert({ text: itemText, complete: false }).then(refreshTodoItems, handleError);
}
textbox.val('').focus();
evt.preventDefault();
});
// Handle update
$(document.body).on('change', '.item-text', function() {
var newText = $(this).val();
todoItemTable.update({ id: getTodoItemId(this), text: newText }).then(null, handleError);
});
$(document.body).on('change', '.item-complete', function() {
var isComplete = $(this).prop('checked');
todoItemTable.update({ id: getTodoItemId(this), complete: isComplete }).then(refreshTodoItems, handleError);
});
// Handle delete
$(document.body).on('click', '.item-delete', function () {
todoItemTable.del({ id: getTodoItemId(this) }).then(refreshTodoItems, handleError);
});
// On initial load, start by fetching the current data
refreshTodoItems();
});
and it works!
Changed for the use of phonejs and the program stops working, even mistakes does not issue!
This my View:
<div data-options="dxView : { name: 'home', title: 'Home' } " >
<div class="home-view" data-options="dxContent : { targetPlaceholder: 'content' } " >
<button data-bind="click: incrementClickCounter">Click me</button>
<span data-bind="text: listData"></span>
<div data-bind="dxList:{
dataSource: listData,
itemTemplate:'toDoItemTemplate'}">
<div data-options="dxTemplate:{ name:'toDoItemTemplate' }">
<div style="float:left; width:100%;">
<h1 data-bind="text: name"></h1>
</div>
</div>
</div>
</div>
This my ViewModel:
Application1.home = function (params) {
var client = new WindowsAzure.MobileServiceClient('https://zaburrito.azure-mobile.net/', 'key');
var todoItemTable = client.getTable('todoitem');
var toDoArray = ko.observableArray([
{ name: "111", type: "111" },
{ name: "222", type: "222" }]);
var query = todoItemTable.where({ complete: false });
query.read().then(function (todoItems) {
for (var i = 0; i < todoItems.length; i++) {
toDoArray.push({ name: todoItems[i].text, type: "NEW!" });
}
});
var viewModel = {
listData: toDoArray,
incrementClickCounter: function () {
todoItemTable = client.getTable('todoitem');
toDoArray.push({ name: "Zippy", type: "Unknown" });
}
};
return viewModel;
};
I can easily add items to the list of programs, but from the server list does not come:-(
I am driven to exhaustion and can not solve the problem for 3 days, which is critical for me!
Specify where my mistake! Thank U!
I suggest you use a DevExpress.data.DataSource and a DevExpress.data.CustomStore instead of ko.observableArray.
Application1.home = function (params) {
var client = new WindowsAzure.MobileServiceClient('https://zaburrito.azure-mobile.net/', 'key');
var todoItemTable = client.getTable('todoitem');
var toDoArray = [];
var store = new DevExpress.data.CustomStore({
load: function(loadOptions) {
var d = $.Deferred();
if(toDoArray.length) {
d.resolve(toDoArray);
} else {
todoItemTable
.where({ complete: false })
.read()
.then(function(todoItems) {
for (var i = 0; i < todoItems.length; i++) {
toDoArray.push({ name: todoItems[i].text, type: "NEW!" });
}
d.resolve(toDoArray);
});
}
return d.promise();
},
insert: function(values) {
return toDoArray.push(values) - 1;
},
remove: function(key) {
if (!(key in toDoArray))
throw Error("Unknown key");
toDoArray.splice(key, 1);
},
update: function(key, values) {
if (!(key in toDoArray))
throw Error("Unknown key");
toDoArray[key] = $.extend(true, toDoArray[key], values);
}
});
var source = new DevExpress.data.DataSource(store);
// older version
store.modified.add(function() { source.load(); });
// starting from 14.2:
// store.on("modified", function() { source.load(); });
var viewModel = {
listData: source,
incrementClickCounter: function () {
store.insert({ name: "Zippy", type: "Unknown" });
}
};
return viewModel;
}
You can read more about it here and here.
I've just started using Backbone.js and my test cases are churning up something pretty weird.
In short, what I am experiencing is -- after I call a Backbone Model's constructor, some of the fields in my object seem to come from a previously item. For instance, if I call:
var playlist = new Playlist({
title: playlistTitle,
position: playlists.length,
userId: user.id
});
playlist.get('items').length; //1
however if I do:
var playlist = new Playlist({
title: playlistTitle,
position: playlists.length,
userId: user.id,
items: []
});
playlist.get('items').length; //0
Here's the code:
define(['ytHelper', 'songManager', 'playlistItem'], function (ytHelper, songManager, PlaylistItem) {
'use strict';
var Playlist = Backbone.Model.extend({
defaults: {
id: null,
userId: null,
title: 'New Playlist',
selected: false,
position: 0,
shuffledItems: [],
history: [],
items: []
},
initialize: function () {
//Our playlistItem data was fetched from the server with the playlist. Need to convert the collection to Backbone Model entities.
if (this.get('items').length > 0) {
console.log("Initializing a Playlist object with an item count of:", this.get('items').length);
console.log("items[0]", this.get('items')[0]);
this.set('items', _.map(this.get('items'), function (playlistItemData) {
var returnValue;
//This is a bit more robust. If any items in our playlist weren't Backbone.Models (could be loaded from server data), auto-convert during init.
if (playlistItemData instanceof Backbone.Model) {
returnValue = playlistItemData;
} else {
returnValue = new PlaylistItem(playlistItemData);
}
return returnValue;
}));
//Playlists will remember their length via localStorage w/ their ID.
var savedItemPosition = JSON.parse(localStorage.getItem(this.get('id') + '_selectedItemPosition'));
this.selectItemByPosition(savedItemPosition != null ? parseInt(savedItemPosition) : 0);
var songIds = _.map(this.get('items'), function(item) {
return item.get('songId');
});
songManager.loadSongs(songIds);
this.set('shuffledItems', _.shuffle(this.get('items')));
}
},
//TODO: Reimplemnt using Backbone.sync w/ CRUD operations on backend.
save: function(callback) {
if (this.get('items').length > 0) {
var selectedItem = this.getSelectedItem();
localStorage.setItem(this.get('id') + '_selectedItemPosition', selectedItem.get('position'));
}
var self = this;
console.log("Calling save with:", self);
console.log("my position is:", self.get('position'));
$.ajax({
url: 'http://localhost:61975/Playlist/SavePlaylist',
type: 'POST',
dataType: 'json',
contentType: 'application/json; charset=utf-8',
data: JSON.stringify(self),
success: function (data) {
console.log('Saving playlist was successful.', data);
self.set('id', data.id);
if (callback) {
callback();
}
},
error: function (error) {
console.error("Saving playlist was unsuccessful", error);
}
});
},
selectItemByPosition: function(position) {
//Deselect the currently selected item, then select the new item to have selected.
var currentlySelected = this.getSelectedItem();
//currentlySelected is not defined for a brand new playlist since we have no items yet selected.
if (currentlySelected != null && currentlySelected.position != position) {
currentlySelected.set('selected', false);
}
var item = this.getItemByPosition(position);
if (item != null && item.position != position) {
item.set('selected', true);
localStorage.setItem(this.get('id') + '_selectedItemPosition', item.get('position'));
}
return item;
},
getItemByPosition: function (position) {
return _.find(this.get('items'), function(item) {
return item.get('position') == position;
});
},
addItem: function (song, selected) {
console.log("this:", this.get('title'));
var playlistId = this.get('id');
var itemCount = this.get('items').length;
var playlistItem = new PlaylistItem({
playlistId: playlistId,
position: itemCount,
videoId: song.videoId,
title: song.title,
relatedVideos: [],
selected: selected || false
});
this.get('items').push(playlistItem);
this.get('shuffledItems').push(playlistItem);
this.set('shuffledItems', _.shuffle(this.get('shuffledItems')));
console.log("this has finished calling");
//Call save to give it an ID from the server before adding to playlist.
songManager.saveSong(song, function (savedSong) {
song.id = savedSong.id;
playlistItem.set('songId', song.id);
console.log("calling save item");
$.ajax({
type: 'POST',
url: 'http://localhost:61975/Playlist/SaveItem',
dataType: 'json',
data: {
id: playlistItem.get('id'),
playlistId: playlistItem.get('playlistId'),
position: playlistItem.get('position'),
songId: playlistItem.get('songId'),
title: playlistItem.get('title'),
videoId: playlistItem.get('videoId')
},
success: function (data) {
playlistItem.set('id', data.id);
},
error: function (error) {
console.error(error);
}
});
});
return playlistItem;
},
addItemByVideoId: function (videoId, callback) {
var self = this;
ytHelper.getVideoInformation(videoId, function (videoInformation) {
var song = songManager.createSong(videoInformation, self.get('id'));
var addedItem = self.addItem(song);
if (callback) {
callback(addedItem);
}
});
},
//Returns the currently selected playlistItem or null if no item was found.
getSelectedItem: function() {
var selectedItem = _.find(this.get('items'), function (item) {
return item.get('selected');
});
return selectedItem;
}
});
return function (config) {
var playlist = new Playlist(config);
playlist.on('change:title', function () {
this.save();
});
return playlist;
};
});
basically I am seeing the property 'items' is populated inside of initialize when I've passed in a config object that does not specify items at all. If I specify a blank items array in my config object, then there are no items in initialize, but this seems counter-intuitive. Am I doing something wrong?
The problem is with using reference types (arrays) in the defaults object. When a new Playlist model is created without specifying an items value, the default is applied. In case of arrays and objects this is problematic, because essentially what happens is:
newModel.items = defaults.items
And so all models initialized this way refer to the same array. To verify this, you can test:
var a = new Playlist();
var b = new Playlist();
var c = new Playlist({items:[]});
//add an item to a
a.get('items').push('over the rainbow');
console.log(b.get('items')); // -> ['over the rainbow'];
console.log(c.get('items')); // -> []
To get around this problem, Backbone supports defining Model.defaults as a function:
var Playlist = Backbone.Model.extend({
defaults: function() {
return {
id: null,
userId: null,
title: 'New Playlist',
selected: false,
position: 0,
shuffledItems: [],
history: [],
items: []
};
}
});