For clarity when I say invalid references I mean something like:
https://[appname].firebaseIO.com/assets/:id
Where the ID doesn't match an item in firebase
Previously I have been doing this by looking at the item in the scope and checking if it's empty but this new version of AngularFire attaches all its methods ($add, $bind, $child etc) even when there is no data to load so checking if the object in the scope is empty no longer works.
Is there a more correct way to do this or should I perhaps check for items in the object that aren't functions as an alternative way to do this?
In 0.5, the loaded event returns the raw value, which should suit your needs here:
var fbRef = new Firebase(URL);
$firebase( fbRef ).$on('loaded', function(value) {
if( value === null ) { console.log('no record at this path'); }
});
Related
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 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.
I imagine this is an easy thing to do, but I wasnt able to find the information I was looking for through google. I have popupProperties which is just default stuff. I then call to the service which returns specific overrides depending on the popup. How can I iterate through all of the service's overrides and apply them to the popupProperties?
var popupProperties = getDefaultPopupProperties();
var popupOverrides= popupService.getPopupOverrides(currPopupId);
angular.forEach(popupOverrides, function(popupProperty, propertyName){
//replace defaults with popupData's properties
});
You should have a look at the solution of Josh David Miller which uses the extend method of angular (documentation).
var defaults = {name:'John',age:17,weight:55};
var overrides = {name:'Jack',age:28,color:'brown'};
var props = angular.extend(defaults, overrides);
// result
props: {
name:'Jack',
age:28,
weight:55,
color:'brown'
}
The values are copied in the defaults variable. There is no need of using the return value (var props =).
I presume you mean both functions are returning objects with a number of properties (as opposed to an array).
If so, the following should work - just JavaScript, nothing AngularJS specific:
for (var attrname in obj2) { obj1[attrname] = obj2[attrname]; }
See this question for more details How can I merge properties of two JavaScript objects dynamically?
I have a an "Asset" backbone model that has a custom attribute called "selected". Its custom in the sense that it is not part of the object on the server side. I use to represent which of the list of assets the user has currently selected.
var Asset = Backbone.Model.extend({
defaults: {
selected: false
},
idAttribute: "AssetId"
});
This model is part of a backbone collection that I fetch periodically to get any changes from the server.
The problem I have is that every time I fetch the collection, the collection is doing a reset (I can tell by the listening for the reset event) and hence the value of the selected attribute is wiped out by the data coming in from the ajax request.
The backbone.js documentation seems to suggest that there is a intelligent merge that will solve this problem. I believe I'm doing this in my fetch methods
allAssets.fetch({ update: true ,cache: false});
And I have also set the "idAttribute" field in the model so that the ids of the object coming in can be compared with the objects in the collection.
The way I have solved this is by writing my own Parse method in my collection object
parse: function (response) {
// ensure that the value of the "selected" for any of the models
// is persisted into the model in the new collection
this.each(function(ass) {
if (ass.get("selected")) {
var newSelectedAsset = _.find(response, function(num) { return num.AssetId == ass.get("AssetId"); });
newSelectedAsset.selected = true;
}
});
return response;
}
Is there a better way to do this?
Collection.update (introduced in Backbone 0.9.9) does indeed try to merge existing models, but does so by merging all set attributes in the new model into the old model. If you check Backbone source code, you'll see
if (existing || this._byCid[model.cid]) {
if (options && options.merge && existing) {
existing.set(model.attributes, options);
needsSort = sort;
}
models.splice(i, 1);
continue;
}
All attributes, including defaults, are set, that's why your selected attribute is reset to false. Removing the default value for selected will work as intended: compare http://jsfiddle.net/nikoshr/s5ZXN/ to http://jsfiddle.net/nikoshr/s5ZXN/3/
That said, I wouldn't rely on a model property to store my app state, I would rather move it to a controller somewhere else.
I have a grid bound to a form the forms submit action is to update the loaded record if there is one and add a new record if its a blank form. but if I select a record first and then call
myGrid.getSelectionModel().deselectAll();
myform.getForm().reset();
to clear the form so I can add a new record it overwrites the previously selected record with an update.
record = myform.getRecord();
if(record){
record.set(values);
}
shouldn't myform.getRecord(); be null after a reset? how do I clear the record selection?
In short, no, it shouldn't and you don't have legal approaches to clear the record after the first time you load anything via loadRecord.
Although, you could still do myform.getForm()._record = null assignment, I would strongly object against that, as it may break some internal functionality by ExtJS.
Here is an extract from ExtJS API:
getRecord() : Ext.data.Model
Returns the last Ext.data.Model instance
that was loaded via loadRecord
And it does exactly that, returns the last record loaded via loadRecord.
Here are some sources:
getRecord: function() {
return this._record;
},
loadRecord: function(record) {
this._record = record;
return this.setValues(record.data);
},
Actually, those are the only methods of Ext.form.Basic (an instance of which is returned by getForm()) dealing with this._record field.
As for reset
reset: function() {
var me = this;
me.batchLayouts(function() {
me.getFields().each(function(f) {
f.reset();
});
});
return me;
},
As you could see, reset has nothing to do with the record returned by getRecord(), it's just resetting field values.
For anyone interested, you can override the default form panel to add functionality to clear form fields. Add the following to your code:
Ext.override(Ext.form.Panel, {
clearForm:function(){
Ext.each(this.getForm().getFields().items, function(field){
field.setValue('');
});
}
});
You can then clear a form using:
myForm.clearForm()
Where myForm is your form panel.
This is what you looking for:
form.getForm().reset(true);
True to unbind any record set by loadRecord.
See Ext.form.Basic.reset() syntax
I find this question because I have a similar scenario but slightly different:
ExtJS 4.1.1
TreePanel
In ExtJS 4.1 in sync() method you can use an options object in which you can define a callback, success and failure functions. Since I'm sure I'm only synchronozing just one record, I loaded the returned record:
me.getHelpItemsStore().sync({
success: function(batch) {
// We expect single operation so...
var record = batch.operations[0].getRecords()[0];
form.loadRecord(record);
}
});
Little late but hope helps you.