Using Backgrid and PageableCollection fetch method firing twice - backbone.js

I'm using Backgrid and Pageablecollection for pagination and each time I make a request I can see on the Network tab in Chrome Developer that the request is being fired twice, and I can't figure out why.
My code looks like this:
RecipeManager.module('Entities', function (Entities, RecipeManager, Backbone, Marionette, $, _) {
Entities.Recipe = Backbone.NestedModel.extend({
urlRoot: 'recipes,
parse: function (response) {
return response.results[0]
}
}
Entities.RecipeCollection = Backbone.PageableCollection.extend({
model: Entities.Recipe,
sync: function (method, model, options)
{ // custom headers in here},
state: {
firstPage: 1
}
queryParams: {
currentPage: 'page',
pageSize: 'per_page'
},
parseState: function (resp, queryParams, state, options) {
return {totalRecords: resp.meta.total}
},
parseRecords: function (response, options) {
return response.results
}
})
var API = {
getRecipeEntities: function () {
var recipes = new Entities.RecipeCollection()
var defer = $.Deferred()
recipes.fetch({
reset: true,
url: 'https://myurl/forgetting/data',
success: function (data) {
defer.resolve(data)
}
})
var promise = defer.promise()
$.when(promise).done(function (fetchedRecipes) {
})
return promise
}
}
RecipeManager.reqres.setHandler('recipe:entities', function () {
return API.getRecipeEntities()
})
And then in the Recipes.ListController I have the following:
RecipeManager.module('RecipesApp.List', function (List, RecipeManager, Backbone, Marionette, $, _) {
List.Controller = {
var recipesListLayout = new List.Layout()
listRecipes: function () {
var fetchingRecipes = RecipeManager.request('recipe:entities')
$.when(fetchingRecipes).done(function (recipes) {
var recipesListView = new List.Recipes({
collection: recipes
})
})
var columns = [{ // load of columns defined here }]
recipesListLayout.on('show', function(){
var paginator = new Backgrid.Extension.Paginator({
collection: recipes,
render: function () {
this.$el.find('ul').addClass('pagination')
return this
}
)}
recipesListLayout.$el.append(paginator.render().el)
}
}
RecipeManager.regions.main.show(recipesListLayout)
}

Related

Populating data from two different url in a backbone collection

I have a Marionette.LayoutView which calls a backbone collection and fetches the data and renders based on response. Now the issue that I am facing is, this collection needs to get data from two different endpoints, both should be independent, and then return the combined result. Below is my code:
My Marionette.LayoutView
var View = Marionette.LayoutView.extend({
template: _.template(some.html),
regions: {
div1: '[data-region="div1"]',
div2: '[data-region="div2"]',
},
initialize: function () {
this.collection = new MovieCollection();
},
onRender: function () {
if (this.collection.length) {
this.div1.show(new TopMoviesByLikesView({
collection: this.collection,
movieCount: 10,
}));
this.div2.show(new TopMovieByRatingsView({
collection: this.collection,
movieCount: 10,
}));
}
},
});
module.exports = AsyncView.extend({
ViewConstructor: View,
});
My Collection
module.exports = Backbone.Collection.extend({
model: TopMovieModel,
initialize: function (response) {
let movieCollection = [];
let movieSourceOne = new TopMovieFromSourceOne();
movieSourceOne.fetch({
success: function (collection, response) {
movieCollection = [...movieCollection, ...response.data];
},
error: function (collection, response, options) {
console.info('~ Response::ERROR', collection, response, options);
}
});
let movieSourceTwo = new movieSourceTwo();
movieSourceTwo.fetch({
success: function (collection, response, options) {
movieCollection = [...movieCollection, ...response.data];
},
error: function(collection, response, options) {
console.info('~ Response::ERROR', collection, response, options);
}
});
this.collection = movieCollection;
},
The error I get is A “url” property or function must be specified is there a way where I can do this without using a url in backbone collection? Note: I want to keep two endpoints independent since I don't want the collection to fail if primary API fails.
To avoid that error with url, you should override your fetch method, to call both collections fetch instead.
function promisifyFetch(collection) {
return new Promise(function(resolve, reject) {
collection.fetch({
success() {
resolve(collection);
},
error() {
reject();
}
});
});
}
module.exports = Backbone.Collection.extend({
model: TopMovieModel,
initialize() {
this.movieSourceOne = new TopMovieFromSourceOne();
this.movieSourceTwo = new movieSourceTwo();
},
fetch(options) {
return Promise.all([
promisifyFetch(this.movieSourceOne),
promisifyFetch(this.movieSourceTwo)
]).then(([one, two]) => {
const response = [
...one.toJSON(),
...two.toJSON()
];
this.set(response, options);
this.trigger('sync', this, response, options);
});
}
});
You probably want to handle errors here aswell.

Fetch data did not render in template

This is one of question from a Backbone newbie.
So, I was trying to use this.model.bind and this.model.on('change', this.render), but it could not work for my model. I checked my console & my model function getStoreByName. This is returning an array, but render function is rendering before the fetch array, that's why I want to bind model to view when model change.
Here is how far I have gotten so far.
This is Backbone view:
var storeTemplate = Backbone.View.extend({
initialize: function () {
console.log('view inti is running');
this.template = template;
this.model = new storeModel();
this.model.getStoreByName();
this.stores = this.model.get('stores');
this.model.on('change', this.render);
console.log(this.stores);
console.log('ready to call this.render()');
this.render();
console.log('end to call this.render()');
console.log('view init end');
},
render: function () {
console.log('render is start');
this.logger.log(Logger.LOG_LEVELS.TRACE, "Entering render.");
console.log(this.model.stores);
this.$el.html(_.template(this.template, { stores: this.model.get('stores') }));
return this;
}
});
return storesTemplate;
});
and this is my Backbone Model
var store= Backbone.Model.extend({
initialize: function () {
console.log('init models is running');
this.stores = [];
this.set({ 'stores': this.stores});
console.log(this.get('stores'));
this.service = Service;
},
getStoreByName: function () {
console.log('getting store');
stores = [];
this.service.getStoreByName(function (xml) {
$(xml).find("element").each(
function () {
var store = {
"storeID": $(this).find("ID").text(),
"storeType": $(this).find("Type").text(),
"storeName": $(this).find("Name").text(),
};
if (xml !== null) {
stores.push(store);
}
else {
this.model.set({ stores: [] });
}
}
);
that.set('stores', store)
},
);
},
})
return store;
});
Try this.listenTo(this.model,'change:stores', this.render);
If that doesn't work use promises. Update model like this:
getStoreByName: function() {
var deferred = $.Deferred();
var stores = [];
return this.service.getStoreByName(function(xml) {
if (xml === null) {
return Deferred.resolve([]);
}
$(xml).find("element").each(function() {
var store = {
"storeID": $(this).find("ID").text(),
"storeType": $(this).find("Type").text(),
"storeName": $(this).find("Name").text(),
};
stores.push(store);
});
return Deferred.resolve(stores);
});
this.model.set('stores', stores);
return deferred.promise();
}
and in the view you can do this.model.getStoreByName().then(this.render);

Uncaught TypeError: _.create is not a function at Function.extend backbonejs

var node = Backbone.Model.extend({
defaults: function () {
return {
tag: null,
value: null
};
}
});
var elements = Backbone.Collection.extend({
model: node,
url: "/api/xml/get",
parse: function (data) {
var $xml = $(data);
return $xml.map(function () {
var tag = $(this).each(function () {
$(this).tagName;
});
return { tag: tag };
}).get();
},
fetch: function (options) {
options = options || {};
options.dataType = "xml";
return Backbone.Collection.prototype.fetch.call(this, options);
}
});
var elementsView = Backbone.View.extend({
initialize: function () {
this.listenTo(this.collection, "sync", this.render);
},
render: function () {
console.log(this.collection.toJSON());
}
});
var eles = new elements();
new elementsView({ collection: eles });
eles.fetch();
First line errors out with the subject line using Backbone.js latest. Trying to get a simple demo working with it unable to resolve this myself. underscore.js is referenced.
This method was added in underscore version 1.8.3. You probably have an old version.

Backbone collection on save error

I'm trying to get it so that when my backbone collection is saving if it hits an error I can do something with it. However when the form is saved the Render All Tasks button's click event is always triggered. How can I get it to stop iterating the collection if an error is found? Or otherwise how can I get it to call the fail function?
var EditTaskView = AddTaskView.extend({
template: _.template($("#individualTaskEditView").html()),
events: {
"submit": function (e) {
e.preventDefault();
if (this.model.isValid(true) && this.ScheduleView.isValid() && this.ProviderView.isValid()) {
$.when(this.model.save(), this.FiltersView.saveAll())
.done(function () {
$("#RenderAllTasks").trigger("click");
})
.fail(function (xhr, status, errorMessage) {
var message = JSON.parse(xhr.responseText).Message;
RenderError(message, "#EditTaskDetailsTabError");
});
}
}
}
};
var FieldCollectionAddView = Backbone.View.extend({
tagName: "div",
render: function () {
// iterate the collection
this.collection.each(function (field) {
// render the view and append to the collection
var view = new FieldAddView({ model: field });
var rendered = view.render();
this.$el.append(rendered.el);
}, this);
return this;
},
isValid: function () {
var valid = true;
_.each(this.collection.models, function (model) {
if (!model.isValid(true)) {
valid = false;
}
});
return valid;
},
saveAll: function () {
var errorsFound = false;
_.each(this.collection.models, function (model) {
model.save(null, {
error: function (error) {
//TODO: do soemthing with the error
var message = JSON.parse(el.responseText).Message;
RenderError(message, "#ProviderDetailsTabError");
}
}, { wait: true });
});
return errorsFound;
}
});
var ProviderAddView = Backbone.View.extend({
tagName: "div",
template: _.template($("#providerAddTemplate").html()),
render: function () {
// append the template to the element
this.$el.append(this.template);
//render provider types
this.ProviderTypes = RenderProviderTypes(this.model.attributes.ProviderTypes);
var providerTypesDiv = _.template($("#ProviderTypesTemplate").html());
$("#ProviderTypesDiv", this.$el).html(providerTypesDiv);
$("#ProviderTypesSelectDiv", this.$el).html(this.ProviderTypes.render().el);
$("#ProviderTypes", this.$el).val(this.model.attributes.ProviderType);
// render field collection
var collection = new FieldCollection(this.model.attributes.ProviderFieldList);
var fieldsView = new FieldCollectionAddView({
collection: collection
});
this.FieldsAddView = fieldsView;
// append the fields to the element
$("#fieldsDiv", this.$el).append(fieldsView.render().el);
this.stickit();
return this;
},
events: {
"submit #NewProviderForm": function (e) {
e.preventDefault();
if (this.FieldsAddView.isValid()) {
var fieldsView = this.FieldsAddView;
this.model.save(null, {}, { wait: true })
.success(function (result) {
var filters = new FilterCollection();
$.when(fieldsView.saveAll(),
filters.fetch({
data: $.param({
taskId: result.attributes.TaskId
})
}))
.done(function() {
if (!$("#FiltersForm").html()) {
var view = new FilterCollectionView({ collection: filters });
assign(view, "#FilterDetails");
$("#FiltersForm").append(buttonsTemplate);
$("#FilterDetailsTab").parent("li").removeClass("disabled");
$("#FilterDetailsTab").attr("data-toggle", "tab");
}
$("#FilterDetailsTab").tab("show");
});
})
.error(function (xhr, el, other) {
var message = JSON.parse(el.responseText).Message;
RenderError(message, "#ProviderDetailsTabError");
});
}
}
},
isValid: function () {
return this.model.isValid(true) && this.FieldsAddView.isValid();
},
save: function () {
this.model.save();
this.FieldsAddView.saveAll();
}
});
Ok, so I found how this has to be done. In short the code had to be changed so that it was syncing the entire collection at once using Backbone.sync instead of iterating over it and saving each model individually. Also when you're ready to save the collection you use the $.when function so it completes the whole sync before deciding what to do. Below is the relevant code showing the changes that were made.
var FieldCollection = Backbone.Collection.extend({
url: "/api/Field",
model: FieldModel,
syncAll: function () {
return Backbone.sync("create", this)
.error(function (xhr, el, other) {
var message = JSON.parse(xhr.responseText).Message;
var tab = "";
var activeTab = $("#Tabs li.active a").attr("id");
if (activeTab === "TaskListTab") {
tab = "#EditTaskDetailsTabError";
}
else if (activeTab === "NewTaskTab") {
tab = "#ProviderDetailsTabError";
}
RenderError(message, tab);
});
}
});
var FieldCollectionAddView = Backbone.View.extend({
saveAll: function () {
return this.collection.syncAll();
}
});
var ProviderAddView = Backbone.View.extend({
events: {
"submit #NewProviderForm": function (e) {
e.preventDefault();
if (this.FieldsAddView.isValid()) {
var fieldsView = this.FieldsAddView;
$.when(fieldsView.saveAll())
.done(function() {
// success
});
}
}
}
});

Pass attributes or options to backbone model

I'm not getting any attributes or options in model. I need to pass a route number to it in order to build a url. anyone see what im missing or how I should be doing this? I tried setting the attribute I want on the model but it's not in the model when I try to grab it.
view
define([
'text!html/tplDirection.html',
'models/direction',
'core'
], function (template, Direction) {
return Backbone.View.extend({
el: '',
template: _.template(template),
initialize: function (options) {
this.model = new Direction();
this.model.set({rtnm: options.routeNumber});
console.log(this.model);
},
setup: function (routeNumber) {
var self = this;
// self.model.set({rtnm: routeNumber});
$.when(self.model.fetch())
.done(function () {
console.log(self.model.toJSON());
self.render();
})
.fail(function (response) {
console.log(response);
console.log('request for data has failed');
});
},
render: function () {
var data = {
model: this.model.toJSON()
};
this.$el.html(_.template(template, data));
},
Model
define([
'core'
], function () {
return Backbone.Model.extend({
initialize: function (attributes, options) {
console.log(attributes);
},
/* model: Routes,*/
//url: '/apiproxy.php?method=getdirections&rt=',
parse: function (data) {
var parsed = [];
$(data).find('dir').each(function (index) {
var dir = $(this).find('dir').text();
parsed.push({
dir: dir,
});
});
return parsed;
},
fetch: function (options) {
options = options || {};
options.dataType = "xml";
return Backbone.Model.prototype.fetch.call(this, options);
}
});
});
Solved by passing options to model on instantiating. What confused me is that they come through as attributes and not options in the model. How come?
view:
initialize: function (options) {
this.model = new Direction(options);
},
model:
initialize: function (attributes, options) {
console.log(attributes);
},
url: function () {
//'this' now contains attributes
var route = this.get("routeNumber);
//var route = this.attributes.routeNumber;
return '/apiproxy.php?method=getdirections&rt=' + route;
},

Resources