Ember Utility class for accessing data across different places - javascript

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

Related

How to inherit Odoo's StatementModel class in account.ReconciliationModel?

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');
},
});

Twitch API returning info in wrong order using Array.map()

I'm calling the Twitch API (should mention I'm doing this in React) to get information on a few channels. I'm getting the data back, but it gets added to the array in the wrong order, different on every reload. Since I have two make two different calls, this ends up leaving me with mismatched information. I'm assuming it's because some calls take longer than other, and array.map() is running regardless if the first call was done yet, I'm just not sure how to fix that.
Here is my script:
export default class TwitchApp extends React.Component {
state = {
games: [],
statuses: [],
names: [],
logos: [],
streams: ["nl_kripp", "ESL_SC2", "Day9tv",
"DisguisedToastHS" ]
};
findStreams = () => {
const channels = this.state.streams;
const statusUrls = channels.map((channel) => {
return 'https://api.twitch.tv/kraken/streams/' + channel;
})
const infoUrls = channels.map((channel) => {
return 'https://api.twitch.tv/kraken/channels/' + channel;
})
statusUrls.map((statusUrl)=> {
let url = statusUrl;
return $.ajax({
type: 'GET',
url: url,
headers: {
'Client-ID': 'rss7alkw8ebydtzisbdbnbhx15wn5a'
},
success: function(data) {
let game;
let status = data.stream != null ? "Offline" : "Online";
this.setState((prevState)=> ( { statuses: prevState.statuses.concat([status]) } ) );
status = '';
}.bind(this)
});
});
infoUrls.map((url)=> {
return $.ajax({
type: 'GET',
url: url,
headers: {
'Client-ID': 'rss7alkw8ebydtzisbdbnbhx15wn5a'
},
success: function(data) {
let name = data.display_name != null ? data.display_name : 'Error: Can\'t find channel';
let logo = data.logo != null ? data.logo : "https://dummyimage.com/50x50/ecf0e7/5c5457.jpg&text=0x3F";
let game = data.game != null ? data.game : "Offline";
//let status = data.status != null ? data.status: "Offline";
this.setState((prevState)=> ( { games: prevState.games.concat([game]), names: prevState.names.concat([name]), logos: prevState.logos.concat([logo]) } ) );
game = '';
logo = '';
name = '';
}.bind(this)
});
});
};
You have really many options here...
could either look into AsyncJs and it is the async.series(); you are looking for, that will make all call go into specific order.
You could also go for a promised based HTTP requester, like Axios in which you can chain all your requests.
But really, I would go with the 3rd option which is that you make an array of objects as your state like this:
state = {
info: {
"nl_kripp": {
game: '',
status: '',
name: '',
logo: '',
},
"ESL_SC2": {
game: '',
status: '',
name: '',
logo: '',
},
"Day9tv": {
game: '',
status: '',
name: '',
logo: '',
},
"DisguisedToastHS": {
game: '',
status: '',
name: '',
logo: '',
}
},
streams: ["nl_kripp", "ESL_SC2", "Day9tv", "DisguisedToastHS"]
};
and do something like this:
var streams = this.state.streams;
var fetchedArray = [];
fetchedArray.map(streams , stream => {
let url = 'https://api.twitch.tv/kraken/streams/' + stream;
return $.ajax({
type: 'GET',
url: url,
headers: {
'Client-ID': 'rss7alkw8ebydtzisbdbnbhx15wn5a'
},
success: function(data) {
var currentState = Object.assign({}, this.state.info[stream]);
currentState.status = data.stream === null ? 'Offline' : 'Online';
currentState.name = data.display_name;
currentState.logo = data.channel.logo;
currentState.game = data.game;
}.bind(this)
});
});
Now fetchedArray will hold the same information but stacked together and easily handle with javascript after, rather than unsorted arrays.

How to pass a parameter to Collection parse? backbone.js

How could one pass a parameter through the parse/fetch function?
I want to pass the variable VARIABLE_PARAMETER in the lower Initialize-part.
Otherwise I have to write three mostly identical Collections.
Thank you for you help.
app.js
//--------------
// Collections
//--------------
DiagnoseApp.Collections.Param1_itemS = Backbone.Collection.extend({
model: DiagnoseApp.Models.Param1_item,
url: 'TestInterface.xml',
parse: function (data) {
var parsed = [];
$(data).find(/*VARIABLE_PARAMETER*/).find('PARAMETER').each(function (index) {
var v_number = $(this).attr('Number');
var v_Desc_D = $(this).attr('Desc_D');
parsed.push({ data_type: v_data_type, number: v_number, Desc_D: v_Desc_D});
});
return parsed;
},
fetch: function (options) {
options = options || {};
options.dataType = "xml";
return Backbone.Collection.prototype.fetch.call(this, options);
}
});
This is the way I initialize the app:
//--------------
// Initialize
//--------------
var VARIABLE_PARAMETER = "OFFLINE";
var offline_Collection = new DiagnoseApp.Collections.Param1_itemS();
var offline_Collection_View = new DiagnoseApp.Views.Param1_itemS({collection: offline_Collection});
//VARIABLE_PARAMETER has to be passed here in fetch I guess ??
offline_Collection.fetch({
success: function() {
console.log("JSON file load was successful", offline_Collection);
offline_Collection_View.render();
},
error: function(){
console.log('There was some error in loading and processing the JSON file');
}
});
The fetch method accepts an option argument : http://backbonejs.org/#Collection-fetch
The parse method also accepts an option argument: http://backbonejs.org/#Collection-parse
These objects are actually the same. So you may write:
parse: function (data, options) {
var parsed = [];
$(data).find(options.variableParameter).find('PARAMETER').each(function (index) {
var v_number = $(this).attr('Number');
var v_Desc_D = $(this).attr('Desc_D');
parsed.push({ data_type: v_data_type, number: v_number, Desc_D: v_Desc_D});
});
return parsed;
},
Not sure I understand your question, but if you want to "pass a parameter" from fetch to parse, and if that parameter value doesn't change for a given collection, you could just store it in the collection. You could pass the parameter to fetch as an additional property in options:
fetch: function (options) {
options = options || {};
options.dataType = "xml";
this.variableParameter = options.variableParameter;
return Backbone.Collection.prototype.fetch.call(this, options);
},
And then simply retrieve it
parse: function (data) {
// do something useful with this.variableParameter
// ...
}

Binding view with javascript knockout

Here i have my get method that gets the data that i want to return in order to bind it with the view page. I am having trouble wrapping my head to how i could bind this information to the view.
Get Method:
var getRoster = function () {
Ajax.Get({
Url: ....,
DataToSubmit: {id: properties.Id },
DataType: "json",
OnSuccess: function (roleData, status, jqXHR) {
console.log("roles:", roleData.length);
Ajax.Get({
Url: ...,
DataToSubmit: { pageNumber: 1, id: properties.Id },
DataType: "json",
OnSuccess: function (userData, status, jqXHR) {
for (var x in roleData)
{
var role = roleData[x];
console.log(role);
for (var y in userData)
{
var user = userData[y];
if (user.ContentRole == role.ContentRole)
{
rosterViewModel.PushUser(new userViewModel(user));
console.log(user);
}
}
roleTypesViewModel.PushRole(new roleViewModel(role));
}
}
});
}
});
rosterViewModel.PushUser = function (user) {
viewModel.RosterUsers.push(new userViewModel(user));
};
roleTypesViewModel.PushRole = function (role) {
viewModel.RosterRoleTypes.push(new roleViewModel(role));
}
var userViewModel = function (data) {
var _self = this;
_self.ID = ko.observable(data.ID);
_self.Name = ko.observable(data.Name);
_self.Email = ko.observable(data.Email);
_self.ContentRole = ko.observable(data.ContentRole);
};
var roleViewModel = function (data) {
var _self = this;
_self.ContentRole = ko.observable(data.ContentRole);
_self.RoleName = ko.observable(data.RoleName);
_self.RoleRank = ko.observable(data.RoleRank);
_self.UserCount = ko.observable(data.UserCount);
};
var viewModel = {
RosterRoleTypes: ko.observableArray([]),
RosterUsers: ko.observableArray([])
};
View:
<div id="gridView" data-bind="foreach: RosterRoleTypes">
<h3 class="roleHeader"><span data-bind="text:RoleName"></span>
<span class="userCount">(<span data-bind="text:UserCount"></span>)</span>
</h3>
<div data-bind="template: { name: 'grid', foreach: RosterUsers}">
</div>
</div>
How can i bind my data to display in my view?
If you are trying to bind multiple areas of your page to different view models, that is possible by passing in an additional parameter to your ko.applyBindings() method that you call. Your problem is that you are mixing models and view models and using them improperly. If you want to have one view model adjust your code to include all of the functions of your view model and set your models as models instead of viewmodels -
function rosterViewModel() {
var self = this;
self.RosterRoleTypes = ko.observableArray([]),
self.RosterUsers = ko.observableArray([])
self.PushUser = function (user) {
viewModel.RosterUsers.push(new userModel(user));
};
self.PushRole = function (role) {
viewModel.RosterRoleTypes.push(new roleModel(role));
};
self.getRoster = function () {
Ajax.Get({
Url: ....,
DataToSubmit: {id: properties.Id },
DataType: "json",
OnSuccess: function (roleData, status, jqXHR) {
Ajax.Get({
Url: ...,
DataToSubmit: { pageNumber: 1, id: properties.Id },
DataType: "json",
OnSuccess: function (userData, status, jqXHR) {
for (var x in roleData)
{
var role = roleData[x];
for (var y in userData)
{
var user = userData[y];
if (user.ContentRole == role.ContentRole)
{
self.PushUser(new userModel(user));
}
}
self.PushRole(new roleModel(role));
}
}
});
}
});
}
var userModel = function (data) {
var _self = this;
_self.ID = ko.observable(data.ID);
_self.Name = ko.observable(data.Name);
_self.Email = ko.observable(data.Email);
_self.ContentRole = ko.observable(data.ContentRole);
};
var roleModel = function (data) {
var _self = this;
_self.ContentRole = ko.observable(data.ContentRole);
_self.RoleName = ko.observable(data.RoleName);
_self.RoleRank = ko.observable(data.RoleRank);
_self.UserCount = ko.observable(data.UserCount);
};
ko.applyBindings(new rosterViewModel());
This assumes you want to use a single view model for your view. If you are combining multiple content areas that should be bound separately you can create two view models and merge them as shown in this question - KnockOutJS - Multiple ViewModels in a single View - or you could also bind them separately by passing in an additional parameter to the ko.applyBindings() method as showm here - Example of knockoutjs pattern for multi-view applications
All of the data that you want to bind to UI will be properties of your viewmodel as KO observable or observable arrays. Once the view model is created and its members are assigned with data(callbacks in your case), you need to apply bindings using ko.applyBindinds so that the data is bound to UI. In your case the last AJAX success callback seems to be the appropriate place.
Also your HTML makes using of template bindings however apparently there is no template defined with name 'grid'. Check on this.
Knockout tutorial link http://learn.knockoutjs.com/#/?tutorial=intro
Add
ko.applyBindings(viewModel);
somewhere in your application.

Backbone object fields are from previous item

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: []
};
}
});

Categories