I have this big resource object that has lots of methods inside of it with more objects, etc. I'm also converting xml into json for this.
One in particular is a resourceType object that sometimes has more than one resourceTypes. When 2 or more are present, it's an Array. When it's 1 it's just an object and I can't loop through it consistantly in my view.
I made a filter that checks if it's just an object then casts it to an array if it's not already one i'm just having trouble how to put it in my service call. Right now I have it has:
getResources: function(resourceId){
var self = this;
return Restangular.one('resource/resourceState', resourceId).get().then(function(response){
$filter('castToArray')(response.resources.resourceState.resourceTypes)
self.resources = response;
return self.resources;
});
},
This doesn't work. I want to maintain the integrity of the entire resources object but I want the resourceTypes method/object to cast to an array if only 1 is present.
In case anyone stumbles on this the fix was quite simple.
I just set the above to:
getResources: function(resourceId){
var self = this;
return Restangular.one('resource/resourceState', resourceId).get().then(function(response){
self.resources = response;
self.resources.resourceState.resourceTypes = $filter('castToArray')(response.resourceState.resourceTypes);
return self.resources;
});
},
I just needed to set the the self.resources method to the response method with the filter casted on it. Okay I'm done.
Related
I have a dictionary of type {name: value}
A = {
name: x,
name: y,
name: z
}
I want to fetch a collection (consisting of models having one of their attribute as 'name'), but to be optimal I want to fetch such that the value of the attribute 'name' exists in my dictionary.
Is there a way to do specific filtering like that?
If you're doing the filtering client-side, overriding the filter method is really NOT the way to go.
Now you no longer have it available, should you need it later. Also, modifying the collection itself from within the filter method is an undesirable sideeffect.
Instead you should be using the parse method, which will automatically be called when fetching the collection.
Now as I understand it, you want to limit the fetched set to models with names matching the keys in your dictionary.
If so, I would do the following:
parse: function(response, options) {
// Do we want to filter the response?
if (options.filterNames) {
// Filter
response = _.filter(response, function(obj) {
// Check if this model name is one of the allowed names
return _.contains(options.filterNames, obj.name);
});
}
// Backbone will use the return value to create the collection
return response;
}
And then call fetch using
someCollection.fetch({filterNames: _.keys(someDictionary)});
If you're certain, you will always be filtering the collection on fetch, you can omit passing the option and just use the dictionary within parse.
Alternatively you could create a fetchFiltered() method on the collection, which would then invoke the line above.
After investigations and trials, here are the two ways this can be resolved:
1. Client side filtering after fetching the collection from the server. This is a less optimal method, especially when the collection is huge. In situations when you really want 5 models out of a 1000 model collection, it can be an overkill. But if the server side has no logic of accepting and using the filtering client side filtering should look something like:
Overload the collection filter code something like:
var filter = {
filter: function() {
var results = _.filter(this.models, function(model) {
// Perform the check on this model, like compare it to your local dict
if (checkPassed) {
return true;
}
return false;
});
results = _.map(results, function(model) {
return model.toJSON();
});
// Reset the existing collection to filtered models
this.reset(results) ;
};
var ExtendedCollection = OriginalCollection.extend(filter);
Pass a filter option in the fetch ajax call to the server, and the server should understand the filter and return the collection based off that.
Sorry I'm not sure how to ask this question but the code is pretty straight forward I think. I want to check if the data being returned from the DB has changed and if so update the scope. However when I do the compare it always comes back as different and does the update:
$scope.tickets = Ticket.tickets.query();
updateTickets = $interval(function(){
var deferred = $q.defer();
$scope.curTickets = $scope.tickets;
deferred.resolve(Ticket.tickets.query());
var promise = deferred.promise;
promise.then(function(newTickets){
$scope.newTickets = newTickets;
if($scope.curTickets != $scope.newTickets){
$scope.tickets = $scope.newTickets;
console.log('newtickets!');
} else {
console.log('no newtickets');
}
});
},90000);
As mentioned in https://stackoverflow.com/a/201471/3878940, there is no definite way of comparing the equality of 2 objects in JavaScript. So, you may need to look for an alternative approach to recursively compare the values of the response object by writing a custom method.
According to the Angularfire docs, when working with an object returned through $asObject(), you can set priority for said object by defining a $priority property on the object and then using $save().
My code works great, but $priority isn't doing anything. Here's some code with complete explanations in the comments:
app.factory('MyService', function($rootScope, $firebase) {
// The complete Firebase url
var ref = *obfuscated*;
// Returning the dataset as an object containing objects
var data = $firebase(ref).$asObject;
// This object is what's returned by MyService
var Data = {
// Method to create a new object within the data set,
// keyed by myId. Attempting to set priority for the
// record via $priority. returnData.uid is a valid string.
create: function(returnData, myId) {
data[myId] = {
myId: myId,
$priority: returnData.uid
};
// No need to explain the stuff between here and the
// $rootScope listener below, just added for context
data.$save().then(function() {
setMyId(myId);
});
},
findByMyId: function(myId) {
if (myId) {
return data[myId];
}
}
};
function setMyId(myId) {
$rootScope.myId = User.findByMyId(myId);
}
// This event listener works fine, fires
// at user login and returns data
$rootScope.$on('$firebaseSimpleLogin:login', function(e, returnData) {
// returnData.uid has the correct value - this
// function should return the object(aka record) with
// a priority matching returnData.uid
var query = $firebase(ref.startAt(returnData.uid).endAt(returnData.uid)).$asObject();
// console shows an object with the normal $firebase
// properties, but no records. If I define query without
// limiting the set ($firebase(ref).$asObject()), it returns
// the full set just fine. The priority on all records is still
// null.
console.log(query);
query.$loaded(function() {
setData(query.myId);
});
});
return Data;
});
Yes, I'm following Thinkster.io's tutorial and I'm in Chapter 7. No, this is not a duplicate of the other questions about that chapter, I already found my way around the pre-Angularfire 0.8 code present in their examples, just can't set $priority, and I've spent about 5 hours so far trying to find a solution through my own efforts and on the web.
Any takers?
When viewed in the light of how JavaScript works with objects (i.e. unordered), how JSON handles objects (i.e. unordered), and in light of the expectation that AngularFire's $asObject() method is intended for storing key/value pairs, and singular records that are not used as a collection, this starts to make some sense.
Internally, the synchronize'd object's $save method calls Firebase's setWithPriority. In set or setWithPriority calls, the child nodes are replaced. Any meta data like priorities on those children are replaced.
In AngularFire, $asArray is intended to handle ordered collections, and provides the ability to set $priority on child nodes (only one level deep, of course, as it treats its children as singular records that are not themselves collections).
Since, in your case, you want to work with fixed keys rather than push ids, you'll probably want to override the $add method using $extendFactory and do something like the following:
angular.module(MY_APP).factory('FixedKeysFactory', function($FirebaseArray, $firebaseUtils) {
return $FirebaseArray.$extendFactory({
$add: function(data) {
this._assertNotDestroyed('$add');
if( angular.isObject(data) && typeof data.$id === 'string' ) {
return this.$inst().$set(data.$id, $firebaseUtils.toJSON(data));
}
else {
return this.$inst().$push($firebaseUtils.toJSON(data));
}
}
});
});
You could then pass this into your $firebase instance in place of the default factory:
var list = $firebase(ref, {arrayFactory: FixedKeysFactory}).$asArray();
A simpler but less awesomatic™ solution would be to manually add your objects to the array, manually giving them a $id, then call $save:
var list = $firebase(ref).$asArray();
var i = list.length;
list.push({ foo: 'bar', $id: 'kato' });
list.$save(i);
Some notes on the future: It will soon be possible to use any field as sort criteria and there will be no need to set priorities (yay!). It will probably be possible to set your own $id before calling $add on a synchronized array in AngularFire as soon as I clear that with the other devs (like the 0.8.3 release).
I have a study, which has multiple cases and multiple executionMessages. Also each case has multiple executionSteps. I am able to access the study, the case and even each case's executionSteps. I cannot figure out why I cannot access the complete executionMessages. By that I meant each executionMessage has a type, message which are accessible but any objects inside executionMessage is not accessing. Here it the code
StudyService.studies.get({id: $routeParams.studyIdentifier}).$promise.then(function(study) {
$scope.study = study;
StudyService.executionMessagesForStudy.get({id: study.id}).$promise.then(function(executionMessages){
$scope.study.executionMessages = executionMessages;
});
for(var i=0;i<study.cases.length;i++){
var callback = callbackCreator(i);
StudyService.executionstepsForCase.get({id: $routeParams.studyIdentifier,caseId:study.cases[i].id})
.$promise.then(callback);
}
});
function callbackCreator(i) {
return function(executionSteps) {
$scope.study.cases[i].executionSteps = executionSteps;
}
}
It looks like you are using $resource for your service types (a guess at the .get().$promise pattern you are using), From the docs:
...invoking a $resource object method immediately returns an empty reference (object or array depending on isArray). Once the data is returned from the server the existing reference is populated with the actual data. This is a useful trick since usually the resource is assigned to a model which is then rendered by the view.
see: https://docs.angularjs.org/api/ngResource/service/$resource
So your code can be simplified like this:
// get() returns an object that will "fill-in" with the attributes when promise is resolved
$scope.study = StudyService.studies.get({id: $routeParams.studyIdentifier})
$scope.study.$promise.then(function(study) {
// again, executionMessages gets a placeholder that will be filled in on the resolved promise
$scope.study.executionMessages = StudyService.executionMessagesForStudy.get({id: study.id});
angular.forEach(study.cases, function(case, idx) {
// again, executionSteps is a placeholder that will be filled in on the resolved promise
var executionSteps = StudyService.executionstepsForCase.get({id: $routeParams.studyIdentifier, caseId: case.id})
// not sure if ordering is important, the service calls can return in any order
// so using push can produce list with elements in any order.
$scope.study.cases.push(executionSteps);
});
});
You still need to use the study.$promise to get the nested data after the first service call resolves.
When I use the Backbone.Collection.where function to filter the collection I get an array of models as return value but not an other filtered collection object. So I can't use other collection functions with that.
What is the purpose of such behavior?
where isn't the only method that returns an Array. where returns a new Array because you definitely don't want it mutating your existing Collection automatically. Also, many times you may want the result in Array form.
For whatever reason, the BB devs decided that it was better to return a new Array rather than a new Collection. One thought could be that, perhaps the returned data would be used in a different type of Collection. Another reason could be so that you always know what is returned from one of these methods. 2+ types of collections will ALWAYS return Arrays from these types of methods rather than having to try and inspect via instanceof or something else that isn't very reliable.
Edit
In addition, you COULD make your collections behave in a manner where you return new Collections. Create a base Collection to do something like this:
// Override the following methods
var override = ["where","find",...];
var collectionProto = Backbone.Collection.prototype;
BaseCollection = Backbone.Collection.extend({});
for (var key in collectionProto) {
if (collectionProto.hasOwnProperty(key) && override.indexOf(key) > -1) {
BaseCollection.prototype[key] = function () {
return new this.constructor(collectionProto[key].apply(this, arguments);
};
}
}
Instead over extending off Backbone.Collection, extend off BaseCollection.
Note that you can still use most of the underscore utilities on arrays. Here's how to use each() after a filter()
_.each( MyCollection.filter( filter_fn() {} ), each_fn() {} )