Backbone ID double-increment and wrong start - backbone.js

I have a model that looks like this:
var TodosModel = Backbone.Model.extend({
defaults: {
id: null,
content: 'Something Todo',
completed: false
},
url: function() { return 'api/'+this.id; }
});
I'm adding models via:
var todoID = _.uniqueId();
var todoContent = this.newTodoField.val();
var todoCompleted = false;
// Ensure there's something to save
if(todoContent.length>0){
var _this = this;
// Create model
var todo = new TodosModel({
id: todoID,
content: todoContent,
completed: todoCompleted
});
todo.save({}, {
wait: true,
success: function(model, response) {
// Let the events deal with rendering...
_this.collection.add(model);
},
error: function(model, response) {
console.log('Could not create todo');
}
});
}
The problem I'm having is that for some reason every id is double incremented - so if I start with no elements I get 1,3,5,7...
Which holds alright, except if I reload and those ID's are brought in from the API, and then the next generated _.uniqueID is based on the count rendered out.
Any help would be greatly appreciated, here's the full code: http://sandbox.fluidbyte.org/todos/js/todos.js

Related

Backbone.js View with multiple collections and sorting

I have a view with multiple collections, implemented like this:
collection: {
folders: new FolderCollection(),
images: new ImageCollection(),
files: new FileCollection()
},
And example collection is like this:
var FolderCollection = Backbone.Collection.extend({
model: folderObj,
initialize:function (){
// this.bindAll(this);
// this.setElement(this.at(0));
},
comparator: function(model) {
return model.get("id");
},
getElement: function() {
return this.currentElement;
},
setElement: function(model) {
this.currentElement = model;
},
next: function (){
this.setElement(this.at(this.indexOf(this.getElement()) + 1));
return this;
},
prev: function() {
this.setElement(this.at(this.indexOf(this.getElement()) - 1));
return this;
}
});
As you can imagine, this View is a display for files, images, and folders. I then populate the view by calling three different functions; one to populate the view with folders, another for files, and another for images. Each of these functions is a separate ajax request. So, because these calls are asynchronous, there's no way to first load folders, then images, then files and there is no consistency when the page loads.
So, my problem is, I need to be able to order these three collections in multiple ways. The first problem is, since the calls are async, sometimes the folders load first, or maybe the files, etc. I can think of two ways to fix this:
Only call the next function after the previous is completed. Is this the best way? If so, how do I do that
After all the collections are loaded, sort them. If so, how is the best way to sort and order multiple collections?
If more code is needed (ie: model or view) please let me know and I can provide what ever is needed.
thanks
jason
EDIT - SHOWING VIEW
var FileManagementView = TemplateView.extend({
viewName: 'fileManagement',
className: 'fileManagement',
events: {
//my events
},
collection: {
folders: new FolderCollection(),
images: new ImageCollection(),
files: new FileCollection()
},
//life cycle
initialize: function (options) {
TemplateView.prototype.initialize.apply(this, [options]);
},
templateContext: function (renderOptions) {
},
postRender: function () {
//more functions to set up the view
this.repopulateViewWithFoldersAndFiles(currentFolderId);
},
template: function (renderOptions) {
return 'MyMainTemplate';
},
repopulateViewWithFoldersAndFiles: function(currentFolderId){
//code to do stuff to create view
//these functions are all async, so theres no way to know what will finish first
this.getFolders(currentFolderId);
this.getImages();
this.getFiles();
},
getFiles: function(){
try{
var that = this;
var url = '?q=url to function';
$.ajax({
url: url,
context: that,
data:{'methodName': 'getFiles'}
}).done(function(data) {
var results = jQuery.parseJSON(data.result.results);
if(results){
$.each(results, function( key, value ) {
var file = new fileObj;
file.set('id', value.id);
file.set('fileName', value.fileName);
//...set more attributes
that.collection.files.add(file);
that.renderFile(file);
});
}
});
} catch(e){
throw e;
}
},
renderFile: function(file){
try{
if(file) {
var template = window.app.getTemplate('AnotherTemplate');
var html = $(template({
id: file.get('id'),
fileName: file.get('fileName'),
fileIconPath: file.get('fileIconPath')
}));
this.$el.find('#fileDropZone').append(html);
}
} catch(e){
throw e;
}
},
getImages: function(){
try{
var url = '?q=url to function';
$.ajax({
url: url,
context: that,
data:{'methodName': 'getImages'}
}).done(function(data) {
var results = jQuery.parseJSON(data.result.results);
if(results){
$.each(results, function( key, value ) {
var image = new imageObj;
image.set('id', value.id);
image.set('imgTitle', value.image_name);
//....set more attributes
that.collection.images.add(image);
that.renderImage(image);
});
}
});
} catch(e){
throw e;
}
},
renderImage: function(image){
try{
if(image) {
var template = window.app.getTemplate('myViewTemplate');
var html = $(template({
imgId: image.get('id'),
imgTitle: image.get('imgTitle'),
//..more attributes
}));
this.$el.find('#fileDropZone').append(html);
}
} catch(e){
throw e;
}
},
getFolders:function(parentId){
var that = this;
var url = '?q=...path to function';
$.ajax({
url: url,
context: that,
data:{'methodName': 'getFolders'}
}).done(function(data) {
var results = jQuery.parseJSON(data.result.results);
if(results){
$.each(results, function( key, value ) {
var folder = new folderObj();
folder.set('folderName', value.folder_name);
folder.set('id', value.folder_id);
//more attributes
that.collection.folders.add(folder);
that.renderFolders(folder);
});
}else{
this.renderFolders(null);
}
});
},
//renders the folders to the view
renderFolders: function(folder){
try{
if(folder) {
var template = window.app.getTemplate('myFolderTemplate');
var html = $(template({
folderId: folder.get('id'),
folderName: folder.get('folderName'),
}));
this.$el.find('#fileDropZone').append(html);
}
} catch(e){
throw e;
}
}
});
What I ended up doing was rewriting my models and creating one model that the others inherit from. Example:
var DataModel =MyBaseModel.extend({
defaults: {
id: null,
//other shared fields
}
});
All my other models inherited, like this:
var folderObj = DataModel.extend({
// Whatever you want in here
urlRoot: '?q=myApp/api/myClassName/',
defaults: {
//other fields here
},
validate: function(attributes){
//validation here
}
});
I then used deferred, which I answered here: Jquery Promise and Defered with returned results

BackboneJS fetch collection based on parameters

I want to display multiple musical artists based on the genre in a view. So, first of all I have my menu tabs:
<a data-name="hiphop" class="genre">HipHop</a>
<a data-name="rock" class="genre">Rock</a>
<a data-name="alternative" class="genre">Alternative</a>
<a data-name="jazz" class="genre">Jazz</a>
then my genre.js contains:
Genres.Collection = Backbone.Collection.extend({
url: function() {
return 'path to my json';
},
parse: function(response, genre){
return response.data.genres[genre];
// when I do: return response.data.genres.rock;
// I get all artists from the genre "rock".
// but I want the response to be based on the variable "genre"
}
});
then, in my mainView.js:
events: {
'click .genre' : 'genre'
},
genre: function(event, genre){
event.preventDefault();
// get the clicked genre
var genreName = $(event.target).data('name');
var genresCollection = new Genres.Collection({genre:genreName });
genresCollection.fetch();
this.insertView('.genres', new Genres.View({collection: genresCollection}));
},
but no matter which genre I click, I get an empty Collection. can someone tlel me what I'm doing wrong here?
Many thanks!
Options are not stored by default, but you can override your initialize method to provide this functionality. You would then use this stored value in your parse method :
Genres.Collection = Backbone.Collection.extend({
url: function() {
return 'path to my json';
},
initialize: function(opts) {
opts = opts || {};
this.genre = opts.genre || 'rock';
},
parse: function(response){
return response.data.genres[this.genre];
}
});
You need to define a success callback. Try:
var genresCollection = new Genres.Collection();
genresCollection.fetch({
data: {
genre: genreName
},
success: (function (coll_genres) {
console.log(coll_genres.toJSON());
}),
error: (function (e) {
console.log(e);
})
});

Backbone.js. prevent GET

Im working on an application where i have found this weird problem. When i init my Model both GET and POST triggers. Its only supposed to trigger a POST, since i run Model.save();
What can be the problem here?
Here is the Model:
PostModel = Backbone.Model.extend({
url: function() {
return "/api?action=answerQuestion"+"&question_id="+this.get('questionId')+"&option_id=" + this.get('optionId')+"&type=" + this.get('role');
},
defaults: {
questionId: 0,
optionId: 0,
role: 0
}
});
Here is where the model gets created. (on a click event).
events: {
'click li': 'answerQuestion'
},
answerQuestion: function(event) {
event.preventDefault();
$('li').each(function() {
$(this).addClass('inactive');
});
$(event.currentTarget).removeClass('inactive').addClass('active');
var currentQuestion = 1;
var answer = parseInt($(event.currentTarget).find('a').attr('data-answer'));
var role = 1;
var postModel = new PostModel();
postModel.set({
questionId: currentQuestion,
optionId: answer,
role: role
});
postModel.save();
}

Remove all TestCases associated to a TestSet

With Rally SDK 2.0 APIs, I want to associate new TestCases to a given TestSet. To do this I :
initialize a store:
me.selectedTestCasesStore = myTestSet.getCollection('TestCases',{...});
Remove all items (I don't want to keep them):
me.selectedTestCasesStore.removeAll();
Add the new TestCases
me.selectedTestCasesStore.add({'_ref':aTestCaseRecord.data._ref});
Then synchronize
me.selectedTestCasesStore.sync({...});
Step 1 is OK : console.log(me.selectedTestCasesStore) shows me the collection in data.items[].
Step 2 seems OK as a console.log(me.selectedTestCasesStore) shows me nothing in data.items[] (previous records are gone).
Step 3 is OK because added test cases which were not present at step 1 are now in the collection
Step 4 : Called function is "success"
BUT... only new TestCases are added, the old ones are not removed, as if step 2 has no effect. What's wrong in my code ? I extract the part of the concerned code :
// me.selectedTestCasesStore : my store, with old TestCase associated to a TestSet.
// It is initialized with something like :
// me.selectedTestCasesStore = myTestSet.getCollection('TestCases',{...});
//
// selectedTestCasesArray : an array of records with the new TestCases to assign to the test set.
_removeAllFromSelectedTestCaseStore:function()
{
var me = this ;
console.log('In _removeAllFromSelectedTestCaseStore');
me.selectedTestCasesStore.addListener({
clear : me._addSelectedTestCasesToSelectedTestCaseStore,
scope : me,
});
// Remove all associated TestCases from selectedTestCases store
me.selectedTestCasesStore.removeAll();
},
_addSelectedTestCasesToSelectedTestCaseStore:function()
{
var me = this ;
console.log('In _addSelectedTestCasesToSelectedTestCaseStore');
console.log(' After remove, store is now :',me.selectedTestCasesStore);
// Add each check TestCase to selectedTestCases store
for (var i=0; i < me.selectedTestCasesArray.length; i++)
{
// Add it to the collection
me.selectedTestCasesStore.add({'_ref':me.selectedTestCasesArray[j].data._ref});
}
console.log(' After add, store is now :',me.selectedTestCasesStore);
// Synchronyze
me.selectedTestCasesStore.sync(
{
success: function(batch, options) {
//success!
console.log(' Success', me.selectedTestSetStore);
},
failure: function(batch, options){
console.log(' Faillure :(', me.selectedTestSetStore);
},
});
},
Thanks for your help !
This works for me instead of removeAll():
var testcases = testCaseStore.getRange();
_.each(testcases, function(testcase) {
testCaseStore.remove(testcase);
});
Here is the full js file that empties the test case collection on a test set before adding a new test case
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
launch: function() {
console.log("launch");
Rally.data.ModelFactory.getModel({
type: 'TestSet',
success: this._onModelRetrieved,
scope: this
});
},
_onModelRetrieved: function(model) {
console.log("_onModelRetrieved");
this.model = model;
this._readRecord(model);
},
_readRecord: function(model) {
var id = 16805375409;
console.log("_readRecord");
this.model.load(id, {
fetch: ['Name', 'FormattedID', 'TestCases'],
callback: this._onRecordRead,
scope: this
});
},
_onRecordRead: function(record, operation) {
console.log('name:', record.get('Name'));
console.log('test cases:', record.get('TestCases'));
if(operation.wasSuccessful()) {
var testCaseStore = record.getCollection('TestCases', {
autoLoad: true,
listeners: { load: function() {
var testcases = testCaseStore.getRange();
_.each(testcases, function(testcase) {
testCaseStore.remove(testcase);
});
testCaseStore.sync({
callback: function() {
console.log('test cases after removeAll():', record.get('TestCases'));
}
});
testCaseStore.add({'_ref':'/testcase/14469886070'});
testCaseStore.sync({
callback: function() {
console.log('test cases after add():', record.get('TestCases'));
}
});
}}
});
}
},
});

Appending data to same collection after every pagination fetch

I am trying to populate instagram images using backbone,
I have basically 3 models as follows,
User model store all the user info related to instagram
App.Models.User = Backbone.Model.extend({
defaults: {
id: '',
access_token: '',
userid: '',
username: '',
full_name: '',
profile_picture: ''
},
urlRoot: "/api/user/",
initurl: function() {
return "https://api.instagram.com/v1/users/"+this.get('userid')+"/media/recent/?access_token=" + this.get('access_token');
},
initialize: function() {
this.set('id', $('#domdump .id').val());
this.fetch({
success: function(model) {
var photos = new App.Collections.Ig_photos([],{
url: model.initurl()
});
}
});
}
});
A model to store the next url for pagination
App.Models.Ig_next_url = Backbone.Model.extend({
defaults: {
next_url: ''
},
next_url:function(){
return this.get('next_url');
}
});
A model for the photo
App.Models.Ig_photo = Backbone.Model.extend({});
A collection for the multiple photo
App.Collections.Ig_photos = Backbone.Collection.extend({
model: App.Models.Ig_photo,
initialize: function(model, options) {
this.url = options.url;
this.nextSet();
},
sync: sync_jsonp,
parse: function( response ) {
if(response.pagination && response.pagination.next_url && response.pagination.next_url != this.url){
var next_url = new App.Models.Ig_next_url({ next_url: response.pagination.next_url });
this.url = next_url.next_url();
}
return response.data;
},
nextSet: function(){
this.fetch({
success: function(photos){
var ig_photos_views = new App.Views.Ig_photos_view({ collection: photos});
console.log(photos);
}
});
}
});
Also i have some views that does the render with a load more button that calls the nextset of the collection.
What i was trying to achieve is the photos get appended to the collection upon nextset() and the collection get updated with pervious data + new data but right now its getting overwritten.
Also is it okay to instantiate new collection from the modelfetch ?
You shouldn't need to make a new view. You should instead listen to the "add" event being triggered on the collection and render new items accordingly.
nextSet: function(){
this.fetch({add : true}); // The add option appends to the collection
}
This option is detailed in the very good documentation.

Resources