I'm using angular-ui-fullcalendar to show and edit events. Users can log in and have unique uid when logged in. I want to use this to distinguish events made by current user from other events. I want to give current user events another backgroundColor.
What is the best way to do this??
I tried several things. My data looks like this:
```
database
bookings
-KWnAYjnYEAeErpvGg0-
end: "2016-11-16T12:00:00"
start: "2016-11-16T10:00:00"
stick: true
title: "Brugernavn Her"
uid: "1f17fc37-2a28-4c24-8526-3882f59849e9"
```
I tried to filter all data with current user uid like this
var ref = firebase.database().ref().child("bookings");
var query = ref.orderByChild("uid").equalTo(currentAuth.uid);
var bookings = $firebaseArray(query);
$scope.eventSources = [bookings];
This doesn't return anything. If I omit the filter in line 2 it returns all bookings as expected. But even if the filter worked it would not solve my problem, because I want to fetch both current user events and all other events. Firebase does not have a "not equal to" filter option...
I tried to loop through each record and compare uids and setting backgroundColor if condition was met:
var ref = firebase.database().ref().child("bookings");
var bookings = $firebaseArray(ref);
bookings.$ref().on("value", function(snapshot) {
var list = snapshot.val();
for (var obj in list) {
if ( !list.hasOwnProperty(obj) ) continue;
var b = list[obj];
if (b.uid === currentAuth.uid) {
b.className = "myBooking";
b.backgroundColor = "red";
}
}
});
$scope.eventSources = [bookings];
But this causes asynchronous problems so the 'bookings' array assigned to $scope.eventSources wasn't modified. I tried to move the $scope.eventSources = [bookings] inside the async code block but FullCalendar apparently can't handle that and renders nothing.
I also tried this but no luck either:
bookings.$loaded()
.then(function(data) {
$scope.eventSources = [data];
})
.catch(function(error) {
console.log("Error:", error);
});
What is the best solution to my problem?
If you're looking to modify the data that is loaded/synchronized from Firebase, you should extend the $firebaseArray service. Doing this through $loaded() is wrong, since that will only trigger for initial data.
See the AngularFire documentation on Extending $firebaseArray and Kato's answer on Joining data between paths based on id using AngularFire for examples.
Related
I am storing my data in firebase with update() like so
var newKey = firebase.database().ref().push().key;
var updates = {};
updates['metadata/' + newKey] = {
name: $scope.formData.name,
price: $scope.formData.price
};
updates['realdata/' + newKey] = {
name: $scope.formData.name,
price: $scope.formData.price,
date: $scope.formData.date
};
return firebase.database().ref().update(updates)
.then(function(ref){
console.log("added in fb");
}, function(error){
console.log("error " + error)
});
Now on an other page I am pulling the data out of firebase, but I can't seem to map it to my list in my view.
I tried multiple ways to pull the data out and in both ways I can see the data when logging it to the console.
var dbRef = firebase.database().ref('/metadata');
//Method 1
$scope.list = $firebaseArray(dbRef);
/*
Result here in the console is an array with objects
but when setting this in my list, I get the same amount of items pulled out but they are empty
*/
//Method 2 - I prefer this way as per docs it's a best practice
var loadmetadata = function(data){
console.log(data.val().name); // I get the actual name
$scope.list = data.val().name
};
dbRef.on('child_added', loadmetadata);
dbRef.on('child_changed', loadmetadata);
My view is just a simple
<ion-item ng-repeat="listitem in list">
{{ listItem.name }}
</ion-item>
What am I missing? I prefer the second method, if someone can help me achieve this?
The thing is I've found someone with the same problem here on SO, and he was able to solve it with the methods I have above. Here is the link to the question/answer Firebase 3 get list which contain generated keys to ionic list
The only difference I am seeing is that he's sorting the results, but I don't need that currently.
I've just figured it out! Instead of using $firebaseArray, I need to use the $firebseObject method.
$scope.metadataObj = $firebaseObject(dbRef);
and in my view I can do the following:
<ion-item ng-repeat="(key, value) in metadataObj">{{value.name}}</ion-item>
This method contains all the child methodes too. So no need to listen for them separetely.
I have a small app that users can use to search for a movie, and then add it to their watchlist. Currently it is possible to add 1 movie multple times for the same user. Which ofcourse isn't expected behaviour.
My solution would be to find the unique id of the movie that's being added and crosscheck that with my movies_users data. If the movie_id value exists, do this, else do this.
At the moment I do have the unique movie id of the movie that's being added,
$scope.movieListID = response;
console.log ($scope.movieListID.id)
Which gets ouputted like a string, like so,
314365
And I got the movie records from the current user,
$scope.moviesCheck = response.data;
console.log ($scope.moviesCheck)
Which looks like this,
[{"id":2,"title":"Black Panther", "movie_id":"284054"}]
So what would be a good way to check if the result from $scope.movieListID.id already exists in the $scope.moviesCheck data?
* update *
Trying a suggestion below does not give the expected result.
var exists = function (id) {
$scope.moviesCheck.forEach(function (movie) {
console.log (movie.movie_id)
console.log (id)
if (movie.movie_id === id)
console.log ('duplicate')
else
console.log ('not duplicate')
});
}
exists($scope.movieListID.id);
The console.log output from this is,
312221
312221
not duplicate
Which clearly are duplicate results.
You can add a function in your controller to check if the movie exists in the list
var exists = function (id) {
$scope.moviesCheck.forEach(function (movie) {
if (movie.id === id)
return true;
});
return false;
}
// and call it
exists($scope.movieListID.id); // true or false
I'm not 100% if this is a good way to do this, but for me it works and I think it's pretty low on performance,
movieService.loadMovies().then(function(response) {
$scope.moviesCheck = response.data;
var arr = $scope.moviesCheck
function myIndexOf(o) {
for (var i = 0; i < arr.length; i++) {
if (arr[i].movie_id == o.exisitingMovie_id) {
return i;
}
}
return -1;
}
var checkDuplicate = (myIndexOf({exisitingMovie_id:movie.id}));
if (checkDuplicate == -1) {
From your question I've understood that, based on the object exists using id in the array of object, you have to do different action.
You can use $filter for this. Inject the filter for your controller and assign it to the scope. So this will be available whenever you want in this controller.
$scope.cFilter('filter')($scope.movies, {movie_id:$scope.existingMovie.movie_id}, true);
$sope.movies - is the list of movies passed to the filter. You can
send any list based on your need.
{movie_id:$scope.existingMovie.movie_id} - This one is the object
which one we need to find. This can be based on your need. Since we
are searching movie_id, we need to send the object with property
and value. {movie_id:$scope.existingMovie.movie_id}, Here movie_id is
the property and followed by the value with the colon.
true: This indicates that, to search exact matched values. By default
this is false. If this is set to false, then if we want to search 54
in the movie id, this will returns the objects whichever contains 54
as part of the value.
app.controller('controller', ['$filter',function($filter){
$scope.cFilter= $filter;
$scope.Exists=function(){
$scope.movies=[{"id":2,"title":"Black Panther", "movie_id":"284054"},{"id":3,"title":"Black Panther", "movie_id":"32343"},{"id":4,"title":"Black Panther", "movie_id":"98863"}]
$scope.existingMovie={"id":3,"title":"Black Panther", "movie_id":"32343"};
var _obj=$scope.cFilter('filter')($scope.movies, {movie_id:$scope.existingMovie.movie_id}, true);
if(_obj && _obj[0])
{
Console.log('object exists')
}
else
{
Console.log('Object is not found')
}
}
}])
Many Thanks Jeeva Jsb. This got me on the right track, however I thought I would clarify with a practical example that seems to work as expected.
So I have a function called getData which get the AJAX array but we need to check if the record exist before added to scope (else we get duplicates)
if (d.data.length) {
for (var i = 0; i < d.data.length; i++) {
var doesExist = $filter('filter')($scope.notifications, {NotifId:d.data[i].NotifId}, true);
if (doesExist.length == 0){
$scope.notifications.push(d.data[i]);
}
}
}
This should look familier...
when we are iterating through the returned AJAX object we need to check the ID of the (in my case notificiation)
var doesExist = $filter('filter')($scope.notifications, {NotifId:d.data[i].NotifId}, true);
This line creates a new array by filtering the existing array in scope ($scope.notifications) and passing in the same value from you interation.
If the value exists the object will be copied to the new array called doesExist.
A simple check of length will determine if the record needs to be written.
I hope this helps someone.
I have a Firebase structure like this:
user {
uid {
Lessons {
lid1 {
Title: ...
}
lid2 {
Title: ...
}
}
}
}
I want to use AngularFire to convert user as array so I can filter them using Angular like this:
var usersRef = new Firebase($rootScope.baseUrl + "users");
var userListfb = $firebase(usersRef).$asArray();
The problem is, I also need the number of child of the Lessons object. When I log the userListfb, it is an array. But inside the array, the Lessons node still an object. I can not user length to get its length. What is the correct way to find out the number of child of the Lessons Node with Firebase AngularFire?
Edit 1
According to Frank solution, I got an infinite loop (digest circle error from AngularJS).
The problem is, I will not know the "uid" key. I need to loop it in the first array to get the uid into the second firebaseArray.
Let's say I have a ng-repeat="user in users" in the view and call this on view level in each repeat:
{{getLessonLength(user.uid)}}
Then in the controller, I have this function:
$scope.users = $firebaseArray($scope.usersRef);
$scope.getLessonLength = function (uid) {
var userRef = $rootScope.baseUrl + "users/" + uid + "/lessons/";
var lessonsNode = $firebaseArray(new Firebase(userRef));
return lessonsNode.length;
}
}
And it throw this error: Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations: []
All I want it is something like var lessonsCount = snapshot.child('lessons').numChildren() in regular Firebase .on('child_added' ...), the numChildren() function in FirebaseArray. Please help!
AngularFire contains quite some code to ensure that an ordered collection in your Firebase maps correctly to a JavaScript array as Angular (and you) expect it.
If you have a reference to a specific user, you can just create a new sync ($firebase) and call $asArray on that.
var usersRef = new Firebase($rootScope.baseUrl + "users");
var userListfb = $firebase(usersRef).$asArray();
var uid1LessonsRef = userRef.child('uid1').child('Lessons');
var uid1LessonsArray = $firebase(uid1LessonsRef).$asArray();
uid1LessonsArray.$loaded().then(function(arr) {
console.log('Loaded lessons, count: '+arr.length);
});
The data will only be synchronized once, no matter how many references you create to it.
I have a list of employees in a select, where the user can pick an employee and edit its details. Then he triggers an ajax call through a button, so the server can update the record in the DB.
I am binding with ngModel the fields and the data from my list of employees, but it is problematic if the update fails on the database side, because my list of employees is updated through the two way binding.
Is there a way to initialize my fields when the user picks an element in the select and update my employee list only when I get response?
Here is my explicit code from my directive (view):
select(ng-model='selectedEmployee' ng-options="employee.name for employee in employees")
form(role='form')
input(type='input' ng-model='selectedEmployee.userId')
input(type='input' ng-model='selectedEmployee.name')
button (type='button' ng-click='updateEmployee()') update
and the directive
app.directive('employeeList', ['employeeServices',
function(employeeServices) {
var employeeListController = function($scope) {
employeeServices.getEmployees()
.success(function(result) {
$scope.employees = result.data
})
.error(function(err) {
})
$scope.selectedEmployee = null
$scope.updateEmployee = function() {
employeeServices.updateEmployee({
userId: $scope.selectedEmployee.userId,
name: $scope.selectedEmployee.name
})
.success(function(data) {
//I want to update my $scope.employees here
})
.error(function(data) {
//Otherwise I show some error message
})
.then(function() {
$scope.selectedEmployee = {}
})
}
}
return {
...
controller: employeeListController
}
}
])
Solution
So in order to solve the problem I used angular.copy along with ng-change. I've added ng-change to the select, where I copied the selectedEmployee to selectedEmployeeDirty that I supplied as model for my form. Then in the service's callback I updated the selectedEmployee.
Very simple. Object, in javascript, are shared throught a "reference".
In fact, this is a C pointer - or something like that -, how share the memory location of your object.
If you do this:
var a = {},
b = a;
a.toto = true;
console.log(b);
You will see
b = { toto: true }
Keep that in mind.
Now, how can we isolate your edited object, without updating the original one? Make a copy! angular.copy is a friend, and would duplicate every properties of src to the dst.
Use the ng-model as you did, save change, and, only on callback, update the original one :-)
I have a "Cancel" button on my page which should reverts all the changes I made back to the state it was loaded from server..
I guess I need to store an initial state of Backbonejs model and restore a current (changed) state back to initial.
What is the best way to achieve that?
Thank you
FWIW - i wrote a plugin to handle this automatically, specifically with the idea of "cancel" buttons in mind: http://github.com/derickbailey/backbone.memento
model.previousAttributes() returns all of the previous attributes, while model.changedAttributes() returns all the changed attributes, but with their new values (or false if nothing has changed). So you could combine them to write a cancelChanges method in your prototype :
var MyModel = Backbone.Model.extend({
cancelChanges: function() {
var changed = this.changedAttributes();
if(!changed)
return;
var keys = _.keys(changed);
var prev = _.pick(this.previousAttributes(), keys);
this.set(prev, {silent: true}); // "silent" is optional; prevents change event
},
});
I dont believe there's a single method call for returning a model to its unedited state.. but the unedited values are available individually through model.previous(attribute) and collectively via model.previousAttributes.
Here is what I came up with:
var RollbackEnabledModel = Backbone.Model.extend({
initialize: function() {
this._initAttributes = _.clone(this.attributes);
},
parse: function(data) {
this._initAttributes = _.clone(data);
return data;
},
rollback: function() {
this.set(this._initAttributes);
}
});
Take a look at NYTimes' backbone.trackit. It tracks multiple changes to the model instead of only the most recent change like model.changedAttributes() and model.previousAttributes(). From the README:
var model = new Backbone.Model({id:1, artist:'Samuel Beckett', 'work':'Molloy'});
model.startTracking();
model.set('work', 'Malone Dies');
console.log(model.unsavedAttributes()); // >> Object {work: "Malone Dies"}
model.set('period', 'Modernism');
console.log(model.unsavedAttributes()); // >> Object {work: "Malone Dies", period: "Modernism"}
model.save({}, {
success: function() {
console.log(model.unsavedAttributes()); // >> false
}
});
In addition, the library adds functionality to resetAttributes to
their original state since the last save, triggers an event when the
state of unsavedChanges is updated, and has options to opt into
prompting to confirm before routing to a new context.