我刚刚开始使用Backbone.js,我的测试用例正在制造一些非常奇怪的东西。
简而言之,我所经历的是 - 在我调用Backbone Model的构造函数之后,我的对象中的一些字段似乎来自之前的项目。例如,如果我打电话:
var playlist = new Playlist({
title: playlistTitle,
position: playlists.length,
userId: user.id
});
playlist.get('items').length; //1
但是,如果我这样做:
var playlist = new Playlist({
title: playlistTitle,
position: playlists.length,
userId: user.id,
items: []
});
playlist.get('items').length; //0
以下是代码:
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;
};
});
基本上我看到当我传入一个完全没有指定项目的配置对象时,属性'items'被填充在initialize中。如果我在配置对象中指定了空白项目数组,那么初始化中没有项目,但这似乎是违反直觉的。我做错了吗?
答案 0 :(得分:1)
问题在于使用defaults
对象中的引用类型(数组)。在未指定Playlist
值的情况下创建新items
模型时,将应用默认值。在数组和对象的情况下,这是有问题的,因为基本上发生的是:
newModel.items = defaults.items
所有以这种方式初始化的模型都是指相同的数组。要验证这一点,您可以测试:
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')); // -> []
为了解决这个问题,Backbone支持将Model.defaults
定义为函数:
var Playlist = Backbone.Model.extend({
defaults: function() {
return {
id: null,
userId: null,
title: 'New Playlist',
selected: false,
position: 0,
shuffledItems: [],
history: [],
items: []
};
}
});