Module which fetches data from the server and returns a Promise
MedicineManager.module("Entities", function (Entities, MedicineManager, Backbone, Marionette, $, _) {
Entities.Suggestion = Backbone.Model.extend({
default: {
suggestion: ""
}
});
Entities.SuggestionCollection = Backbone.Collection.extend({
model: Entities.Suggestion,
comparator: "suggestion"
});
var API = {
getSuggestions: function (medicine) {
var suggestions = new Entities.SuggestionCollection();
suggestions.url="/medicine_suggestions/?id=" + medicine;
return suggestions.fetch();
}
};
MedicineManager.reqres.setHandler("suggestion:entities", function (medicine) {
return API.getSuggestions(medicine);
});
});
Module which calls the above module for getting fetched data
MedicineManager.module("MedicinesApp.Suggest", function (Suggest, MedicineManager,
Backbone, Marionette, $, _) {
MedicineManager.request("suggestion:entities", "paracetamol").then(function (medicines) {
});
});
How do I get collection instead of an array as the parameter to the callback function in then() because the medicines parameter passed to the callback is giving an array after fetching the values.
Pass the success callback along with options, which will be invoked with the collection as first argument by backbone:
Entities.SuggestionCollection = Backbone.Collection.extend({
model: Entities.Suggestion,
comparator: "suggestion"
});
var API = {
getSuggestions: function(medicine, options) {
var suggestions = new Entities.SuggestionCollection([],{
url : "/medicine_suggestions/?id=" + medicine
});
return suggestions.fetch(options);
}
};
MedicineManager.reqrest.setHandler("suggestion:entities",API.getSuggestions);
Which you can use like:
MedicineManager.request("suggestion:entities", "paracetamol", {
success: function(medicines) {}
});
Instead of directly returning the xhr promise, filter it via .then to set your collection as the promised value :
var API = {
getSuggestions: function (medicine) {
var suggestions = new Entities.SuggestionCollection();
suggestions.url="/medicine_suggestions/?id=" + medicine;
return suggestions.fetch().then(function() {
return suggestions;
});
}
};
The first argument (medicines) in MedicineManager.request(...).then(function (medicines) { }); will be your collection.
Related
I have a collection of models with boolean value interviewed and I want to filter them so that my view would display either models with this property set to true or models with this property set to false. In my collection I have the following methods:
var ResumeCollection = Backbone.Collection.extend({
filterActive: function () {
var active = this.where({interviewed: false});
return active;
},
filterInterviewed: function () {
var interviewed = this.where({interviewed: true});
return interviewed;
}
});
and in my view I have the following:
var ResumeList = Backbone.View.extend({
events: {
'click #active': 'showActive',
'click #interviewed': 'showInterviewed'
},
initialize: function () {
this.collection = new ResumeCollection();
},
render: function () {
var self = this;
this.$el.html( $('#filter') );
_.each(this.collection.toArray(), function (cv) {
self.$el.append((new ResumeView({model: cv})).render().$el);
});
},
showActive: function () {
this.collection.filterActive();
this.render();
},
showInterviewed: function () {
this.collection.filterInterviewed();
this.render();
}
});
But any time I click #active or #interviewed buttons, it happens nothing and the models with required properties aren't rendered. I've already tried to manage that with reset method or returning a new collection instance with required models, but that's not a solution, because when I succesfully filter the initial collection, it returns me a new collection with the models I need (e.g. models where interviewed: true), and I can't filter it more -- it returns just an empty collection.
I just can't get how can I filter this one collection in the way I need.
You're returning a successfully filtered collection, and then not doing anything with them.
showActive: function () {
this.collection.filterActive();//returns a value you're not using
this.render();
},
showInterviewed: function () {
this.collection.filterInterviewed();//returns a value you're not using
this.render();
}
I suggest adding an optional parameter to your render method that represents the filtered collection. If the parameter is defined, use it. If not, use the unfiltered collection.
Borrowing some of #Simo's code to return a new collection.
filterActive: function () {
var active = this.where({interviewed: false});
return new ResumeCollection(active);
},
filterInterviewed: function () {
var interviewed = this.where({interviewed: true});
return new ResumeCollection(interviewed);
},
render: function (filtered) {
var self = this;
var data = filtered ? filtered.toArray() : this.collection.toArray();
this.$el.html( $('#filter') );
_.each(data , function (cv) {
self.$el.append((new ResumeView({model: cv})).render().$el);
});
},
showActive: function () {
var filtered = this.collection.filterActive();
this.render(filtered);
},
showInterviewed: function () {
var filtered = this.collection.filterInterviewed();
this.render(filtered);
}
Your issue is that you are not returning the filtered collection.
This should work:
filterActive: function () {
var active = this.filter(function(item) {
return item.get('interviewed') === false;
});
return new ResumeCollection(active);
},
filterInterviewed: function () {
var interviewed = this.filter(function(item) {
return item.get('interviewed') === true;
});
return new ResumeCollection(interviewed);
},
i would suggest you to modify you render function to accept a argument which will be array of models.
Now when rendering full collection you can call render as
render(this.collection.models) // reference to list of models
also if you filter out the collection then the filter function most probably be returning the subset of models from collection. Which you can again pass to render function
this.render(this.showActive()) // showActive returns subset of models from collection
This way your render function becomes modular.. which accepts array and render then on page..
Now for Filtering out Collection you can use filter , where methods exposed by underscore .. Remember to capture the return and pass it along to render Function..
I wrote an angular service which querys a db and should return the Categories:
(function() {
'use strict';
angular.module('budget')
.service('CategoriesService', ['$q', CategoriesService]);
function CategoriesService($q) {
var self = this;
self.loadCategories = loadCategories;
self.saveCategorie = saveCategorie;
self.datastore = require('nedb');
self.db = new self.datastore({ filename: 'datastore/default.db', autoload : true});
function saveCategorie (categorie_name) {
var entry = {name: categorie_name,
type: 'categorie'}
self.db.insert(entry);
};
function loadCategories () {
self.db.find({type: 'categorie'}, function (err, docs) {
var categories = docs;
return categories;
});
};
return {
loadCategories: self.loadCategories,
saveCategorie: self.saveCategorie
};
}
})();
When I console.log inside the function loadCategories() it returns me an array of 6 objects (the objects from the database) but outside of the function it just gives me undefined.
I am calling via the controller with CategoriesService.loadCategories()
So I think I might have to do something thas called promise but Iam not sure about that.
How can I get acctual data back from this service?
First of all you don't need to return anything from the service factory recipe, you just need to assign a method to the this variable.
At least, you need:
// service.js
self.loadCategories = function() {
var deferred = $q.defer();
db.find({type: 'categorie'}, function (err, docs) {
deferred.resolve(docs);
});
return deferred.promise;
};
// controller.js
service
.loadCategories()
.then(function(categories) {
$scope.categories = categories;
})
;
you need to return your promise first so just add one more return and you are good to go...
function loadCategories () {
// you need to return promise first and you can resolve your promise in your controller
return self.db.find({type: 'categorie'}, function (err, docs) {
var categories = docs;
return categories;
});
};
I am trying to perform a search on my current collection and if the results aren't retrieved i am trying to query my search api
Collection:
var Backbone = require('backbone'),
_ = require('underscore'),
Urls = require('../../libs/urls'),
services = require('../../libs/services'),
skuListModel = require('../../models/sku/SkuListModel');
var SkuListCollection= Backbone.Collection.extend({
model: skuListModel,
sync: function (method, model, options) {
options = _.defaults({}, options, {
readUrl: Urls.sku.list
});
return services.sync.call(model, method, model, options);
}
});
View
searchData: function (e) {
var self = this;
var models = this.skuCollection.filter(function (item) {
return item.get("sku_code").indexOf(e.target.value) > -1
});
console.log(models);
if (models != null) {
self.skuCollection.set(models);
}
else {
self.skuCollection.fetch({
data: {
search_string: e.target.value
}
}).then(function (response) {
console.log(response);
//self.skuCollection.add(self.skuSearchCollection.toJSON(), { silent: true });
});
}
}
My question effectively is how do i modify my current collection to store the retrieved results and if my solution seems effective.
Move your filtering logic to the collection
Use promises to unify your response : an immediately resolved deferred if you find models, the xhr object if you have to fetch the data
Customize the behavior of fetch via the set options, e.g {remove: false} to keep the existing models
These points lead to a collection definition :
var SkuListCollection = Backbone.Collection.extend({
skus: function(code) {
var self = this;
var filtered = function() {
return self.filter(function (item) {
return item.get("sku_code").indexOf(code) !== -1;
});
};
var models = filtered();
if (models.length) {
// models found : define a promise and resolve it
var dfd = $.Deferred();
dfd.resolve(models);
return dfd.promise();
} else {
// models missing: fetch and add them
return this.fetch({
remove: false,
data: {
search_string: code
}
}).then(filtered);
}
}
});
Your view would then be rewired as :
searchData: function (e) {
this.skuCollection.skus(e.target.value).then(function(models) {
// do what you have to do with the filtered models
});
}
And a demo http://jsfiddle.net/nikoshr/84342xer/1/
I've got a factory that sends a POST request to get some JSON key-value pairs:
.factory('Rest', ['$resource',
function($resource) {
// returns JSON key-value pairs, e.g. "{'foo', 'bar'}"
return $resource('rest/get', {}, {
get: {
method: 'POST'
}
});
}])
I've got another factory intended to be exposed to controllers in order to access a key-value pair given its key:
.factory('Pairs', ['Rest',
function(Rest) {
var pairs;
Rest.get(function(response) {
pairs = response;
});
return {
get: function(key) {
return pairs[key];
}
};
}])
The problem is that, when I call Pairs.get('foo'), the pairs object of the Pairs factory is not initialized yet (since Rest.get is asynchronous), and thus results in a TypeError: Cannot read property 'foo' of undefined:
.controller('AnyController', ['Pairs',
function (Pairs) {
console.log(Pairs.get('foo')); // error
}])
Any idea how to solve this?
As you stated in your question, Rest.get is asynchronous, so your Pairs.get has to be asynchronous too. You can implement it as the following:
.factory('Pairs', ['Rest', '$q',
function(Rest, $q) {
var pairs;
var deferredList = [];
Rest.get(function(response) {
pairs = response;
angular.forEach(deferredList, function(o) {
o.deferred.resolve(pairs[o.key]); // resolve saved defer object
});
deferredList = null; // we don't need the list anymore
});
return {
get: function(key) {
if (pairs) {
return $q.when(pairs[key]); // make immediate value as a promise
}
var deferred = $q.defer(); // create a deferred object which will be resolved later
deferredList.push({ // save both key and deferred object for later
key: key,
deferred: deferred
});
return deferred.promise;
}
};
}])
Then use it like this:
Pairs.get('foo').then(function(value) {
console.log(value);
});
You want to wrap your async function in a promise. Here's how I've done something similar.
Note: safeApply triggers the $digest cycle, if necessary, so that angular can react to any data changes it might be watching.
var safeApply = function (scope, fn) {
if (scope.$$phase || scope.$root.$$phase) {
fn();
} else {
scope.$apply(fn);
}
};
ret.getAll = function(type) {
var deferred = $q.defer();
var where = "apm_type = '" + type + "'";
query(type, where, function(err, response) {
var objs = [];
if (err) {
safeApply($rootScope, function() { deferred.reject(err);});
} else {
safeApply($rootScope, function() { deferred.resolve(response);});
}
});
return deferred.promise;
};
I've got a Collection and a Model, both using attributes/options to augment them with additional capabilities. Here's the Model (LoadRouteGroup):
return Backbone.Model.extend({
initialize: function () {
console.log(this);
},
fetchf: function () {
console.log("FETCH");
}
});
And the Collection (LoadRouteGroups):
return Backbone.Collection.extend({
constructUrl: function(options) {
if (options.groupingType === "facility") {
// TODO: new endpoint: /api/v1/loadroutes?grouping=facility
this.url = clawConfig.endpoints.webApiRootUrl + "/api/loads/facilities";
}
else {
this.url = clawConfig.endpoints.webApiRootUrl + "/api/v1/loadroutes";
}
},
initialize: function (models, options) {
options || (options = {});
this.constructUrl(options);
console.log(this);
}
});
They're instantiated as such:
var loadRouteGroup = new LoadRouteGroup({
entityType: "facility"
});
// WORKS
loadRouteGroup.fetchf();
// assign groupingType option to collection to denote which URL to use
var loadRouteGroups = new LoadRouteGroups({
model: loadRouteGroup
}, {
groupingType: "facility"
});
var firstGroup = loadRouteGroups.at(0);
// DOESN'T WORK
firstGroup.fetchf();
// WORKS
firstGroup.attributes.model.fetchf();
I would expect that call to firstGroup.fetchf() to work... but it doesn't. Instead, I have to weirdly drill down and use firstGroup.attributes.model.fetchf() in order to access the method.
What's going on here? This would seem straightforward to me, but I can't for the life of me figure out what's wrong with the relationship between my Collection and Model.
The collection definition should include the model type:
return Backbone.Collection.extend({
// ....
model: LoadRouteGroup
});
When initializing the collection, pass in an array of models:
var loadRouteGroup = new LoadRouteGroup({
entityType: "facility"
});
var loadRouteGroups = new LoadRouteGroups([loadRouteGroup], {
groupingType: "facility"
});
Specify the model when you extend the collection instead of when you instantiate.