$firebaseArray losing functions after saving a child element - javascript

I am trying to implement a list-details view. The list is generated with $firebaseArray(ref). When an item on the list is clicked, the list controller uses list.$getRecord(item.$id) to get the particular record. Puts it in a global service(just and empty object with set and get) and in my detail controller i assign that service(already set to the selected item) to a scope variable in the detail controller and display it.
The information in the detail view is editable. and when it is editted, a save button appears which when clicked saves the edit using this code
item = editItem; //editItem contains the new changes made on the detail page
list.$save(item).then(function(ref){
//ref.key() === item.$id; //
console.log("Your Edit has been saved');
});
This works. The edits are reflected on the remote firebase data.
But the problem occurs when i navigate back to the list view and try to click another item. It gets an error which says list.$getRecord() is not a function. Now this error doesn't occur when you don't save an edit on the details view.
I printed out the list array before and after i save and i realised this
List array before an item is saved (contains AngularFire methods)
List array after an item is saved (no longer contains AngularFire methods)
I have no idea why $firebaseArray is reacting this way. Is there something i am missing? is this a normal behaviour?
PS: i am using ionic and angularFire.
I can post more code if neccesary
EDIT
Here is an abstraction of the code
List.html
<ion-list>
<ion-item href="#/lead/info" ng-click="vm.selectItem(l.$id)" ng-repeat="l in vm.list" >
<h3>{{l.firstName}} {{l.lastName}}</h3>
<h4 class="">
<p>{{l.topic}}</p>
</h4>
</ion-item>
</ion-list>
list.js (controller function)
function ListCtrl(selectedItem, $firebaseArray) {
/* jshint validthis: true */
var vm = this;
vm.list= {};
vm.selectItem = selectItem;
loadList(); //Loads the List array
function loadList() {
var fireList = new Firebase("https://xxxxx.firebaseio.com/list");
var r = $firebaseArray(fireList);
r.$loaded(function () {
vm.list = r;
});
console.log(vm.list); //Outputs the first image(above). But after an item edit and i go back, outputs the second image(above)
}
function selectItem(index) {
var sItem = vm.list.$getRecord(index);
selectedItem.setList(vm.list);
selectedItem.setItem(sItem);
}
}
The selectedItem service is simple. i use it to set a single object or array of objects
function selectedItem() {
var sItem = {};
var List = {};
return {
getItem: function () {
return sItem;
},
setItem: function (authObject) {
sItem = authObject;
},
getList: function(){
return List;
},
setList: function(al){
List = al;
}
};
};
The detail view controller is ass so:
item.js(controller function)
function ItemCtrl(selectedItem, $scope, $firebaseObject) {
/* jshint validthis: true */
var vm = this;
$scope.selectedItem = selectedItem.getItem();
$scope.listArray = selectedItem.getList();
//vm.item = $scope.selectedItem;
function saveEdit() {
var t = $scope.selectedItem;
$scope.listArray.$save(t).then(function(ref){
//console.log(ref);
});
}
};
UPDATE
After serious cross checking throughout my code i realised the issue is not from AngularFiire array. Even the workaround i did with the r.$watch and r.$loaded was unnecessary. the need for the work around was cause by another part of my code i didnt think was relevant.
I apologise for the mistake. I'd be deleting this question and a related one soon

Try using a watcher to reload the data:
var fireList = new Firebase("https://xxxxx.firebaseio.com/list");
var r = $firebaseArray(fireList);
r.$watch(function() {
r.$loaded(function () {
vm.list = r;
});
});
This is a common way of dealing with updates in an easy way, might solve your problem.

Related

Delete multiple rows in JavaScript

I have a 'notifications app' in my program. In the app there is an 'notices' section. Each notice has an option to delete the notice with a delete button on that row. The 'notices' section also has a clear button. I want to make that clear button delete all of the notices in the 'notices' section. However I am having trouble doing this.
CONTROLLER
(function () {
'use strict';
angular
.module('notifications')
.controller('NotificationsCtrl', NotificationsCtrl);
function NotificationsCtrl($scope, $state, SecService, Service, RecService) {
var vm = this;
vm.ctrlName = 'NotificationsCtrl';
$scope.showAppsMenu = false;
$scope.appList = {};
$scope.dismissNotification = function(notification) {
EcosystemService.dismissNotification(notification.id).then(
function (response) {
delete $scope.appList[notification.appId].notifications[notification.id];
});
};
$scope.dismissAppNotifications = function(app) {
var notifications = app.notifications;
for (var i = 0; i < notifications.length; i++) {
EcosystemService.dismissNotification(notifications[i].id).then(
function(index) {
return function (response) {
delete app.notifications[index];
}
}(i));
}
};
I need to make a change in $scope.dismissAppNotifications. I am just stumped on what I need to add. The 'alerts' are not being cleared still. I added where the Service is being used. Maybe I need to add something here as well?
If I understand you correctly - you want to delete all notifications within one app - so iterating over appList is not necessary. Iterate over one app's notifications instead:
$scope.dismissAppNotifications = function(app) {
var notifications = app.notifications;
for (var id in notifications) {
// may be reasonable to add checking for only own properties
EcosystemService.dismissNotification(notifications[id].id).then(
function(notificationId) { // outer function needed because of the way scopes and closures works in js
return function (response) {
delete app.notifications[notificationId];
}
}(id));
}
};
notifications[id].id can be replaced with simply id if key and id property are the same.

Meteor: Lazyload, load after rendering. Best practise

i have a Meteor Application which is very "slow" as there are a lot of API-Calls.
What i try to do is to break apart the loading/calls.
What i just did is:
i have loading template via iron-router
i waitOn for the first API-Call has finished
then i start the next API-calls in the Template.myTemplate.rendered - function
This was already a big benefit for the speed of my Application, but i want to break it up even more as the second call is in fact more like 5-25 API-calls.
So what i try to do now is inside the rendered function is a self-calling function which calls itself as long as there are no more to do and saves the response inside a session. (Until now it just rewrites, but even to this point i can´t get)
Template.detail.rendered = function(){
//comma separated list of numbers for the API-Call
var cats = $(this.find(".extra")).attr('data-extra').split(',');
var shop = $(this.find(".extra")).attr('data-shop');
var counter = 0;
var callExtras = function(_counter){
var obj = {
categories : [cats[_counter]],
shop : shop
};
if(_counter <= cats.length){
Meteor.subscribe('extra', obj,function(result){
//TODO dickes todo... nochmal nachdenken und recherchieren
//console.log(_counter);
Session.set('extra',Extra.find('extra').fetch()[0].results);
counter++;
callExtras(counter);
});
}
};
callExtras(counter);
Session.set('loading_msg', '' );
};
Now i have again problems with my reactive parts of the app desscribed here - Meteor: iron-router => waitOn without subscribe As i can´t find a proper way to update my client-side per user base collection. Also in the docs it is described the publish method also creates a new collection. (The new document´s ID) here - http://docs.meteor.com/#/full/publish_added
here is the publish from server
Meteor.publish('extra', function(obj){
var that = this;
Meteor.call('extra', obj, function(error, result){
if (result){
//console.log(result);
that.added("extra", "extra", {results: result});
//that.changed('extra','extra',{results: result});
that.ready();
} else {
//that.ready();
}
});
});
So my question is: Is there from scratch a better way to structuring my code means solving the problem somehow different? If not how can i achive it the cleanest way? Because for my understanding this is just strange way to do it.
EDIT:
For example.
Can i do a per-user-collection (maybe only client-side like now) and push data from the server and just subscribe to this collection? But then how can i check when the async API-Call has finshed to start the next round. So the view gets data piece by piece. I am just confused right now.
My fault was simple as i thaught: You don´t need to use subscribe.
I just added "error,result" in the callback of Meteor.call
Only "result" leads to the result is always undefined.
var cats = $(this.find(".extra")).attr('data-extra').split(',');
var shop = $(this.find(".extra")).attr('data-shop');
var counter = 0;
var callExtras = function(_counter){
var obj = {
categories : [cats[_counter]],
shop : shop
};
if(_counter <= cats.length){
Meteor.call('extra', obj,function(error,result){
var actual_session = Session.get('extra');
if(actual_session === false){
actual_session = [];
}
actual_session = actual_session.concat(result);
Session.set('extra',actual_session);
counter++;
callExtras(counter);
});
}
};
callExtras(counter);
Then in the template helper
"extra" : function(){
return Session.get('extra');
},

Cant get the current id of a data from local Storage using jquery

I am working on an app to store data offline. My problem is when I try to retrieve the data from local storage for update/edit, it keeps calling only the id of the first item, and not calling the id of the data in view.
Please what am I doing wrong?
Here is my code for loading employees:
// load cases from localStorage
var employees;
if (localStorage.getItem('employees')) {
employees = JSON.parse(localStorage.getItem('employees'));
} else {
// If no cases, create and save them
employees = [];
// offling storing of our cases
localStorage.setItem('employees', JSON.stringify(employees));
}
// show case listing in list view page
var showEmployees = function () {
//erase existing content
$('#employee_list').html('');
//insert each employee
for (var i = 0; i<employees.length; i++) {
addEmployees(employees[i]);
}
};
Here is my code to add an employee to list view:
//add an eliment to list view
var addEmployees = function (empData) {
//HTML content of one list element
var listElementHTML = '<li><a class="employee_list" ui-btn ui-btn-e ui-btn-icon-right ui-icon-carat-r" data-transition="fade" data-split-icon="delete" href="#item'+empData.id+'">' + empData.employeename + '<br> ' + empData.dateofbirth + '</br></a></li>';
//appending the HTML code to list view
$('#employee_list').append(listElementHTML);
};
Here is my code for Edit function:
//User input to edit form
$('#edit_employee_page').on('click' , function () {
var editEmployee = JSON.stringify({
id: employees.length+1,
employeeno: $('#employeeno').val(),
employeename:$('#employeename').val(),
stateoforigine:$('#stateoforigine').val(),
employeephone: $('#employeephone').val(),
dateofbirth:$('#dateofbirth').val()
});
//Alter the slected data
localStorage.setItem("employees", JSON.stringify(employees));
return true;
});
for (var i in employees) {
var id = JSON.parse(localStorage.getItem(employees[i]));
}
Here is my code for the Edit button:
//register Edit button
$('.edit_button').live('click', function (e) {
alert('I was Cliked!');
e.stopPropagation();
$.each(employees, function(a, b) {
//if(b.id == employees[i]){
$('#id').val(b.id);
$('#employeeno').val(b.employeeno);
$('#employeename').val(b.employeename);
$("#stateoforigine").val(i.stateoforigine);
$('#employeephone').val(b.employeephone);
$('#dateofbirth').val(b.dateofbirth);
$("#id").attr("readonly","readonly");
$('#employeeno').focus();
$.mobile.changePage('#edit_employee_page');
return false;
//}
});
});
Here is my local Storage:
[
{"id":1,
"employeeno":"DEF/234/20014",
"employeename":"Bill Gates",
"stateoforigine":"Osun",
"employeephone":"080765432",
"dateofbirth":"12/11/1965"},
{"id":2,
"employeeno":"DEF/234/20014",
"employeename":"Bill Gates",
"stateoforigine":"Osun",
"employeephone":"080765432",
"dateofbirth":"12/11/1966"},
{"id":3,
"employeeno":"DEF/234/20014",
"employeename":"Bill Gates",
"stateoforigine":"Osun",
"employeephone":"080765432",
"dateofbirth":"12/11/1966"},
{"id":4,
"employeeno":"DAST/003/2003",
"employeename":"Gold Base",
"stateoforigine":"",
"employeephone":"",
"dateofbirth":"12/03/1986"}
]
Thanks for helping me out
The way you are storing your employees into localStorage is correct, but the way you are getting them out is incorrect. You stored your employees by stating:
localStorage.setItem("employees", JSON.stringify(employees));
So, in order to retrieve them, you must use:
var employees = JSON.parse(localStorage.getItem("employees"));
You see, you stored the data as a string with a key of "employees"; therefore, you can only retrieve it by that key. Since all data stored in localStorage is saved as a string, you must use JSON.parse() to convert the data back into an object - an array in this case. Then you can iterate over your employees.
Update:
You should be running this code as soon as the page is rendered (see below). I'm not sure how you're doing that - if you're using an IIFE or jQuery's document.ready() function. I don't think it's necessary to store an empty array into localStorage if none were loaded initially, so, I took your else clause out.
var employees = [];
if (localStorage.getItem('employees') !== null) {
employees = JSON.parse(localStorage.getItem('employees'));
}
Debug this line-by-line when it runs and make positive your employees variable contains data. If it doesn't contain data, well then, there's nothing to edit.
If, however, there is data, then execute your showEmployees() function. Oddly, I'm not seeing in your code where you actually call this. Is it bound to a button or action in your UI? Also, what is that for loop doing after your $('#edit_employee_page') click event function? It's trying to read data from localStorage improperly and it does nothing.
I think if you simply stepped through your code one line at a time using breakpoints and desk-checking your inputs/outputs you'd find out where you're going wrong.
It also appears that there's a disconnect in your code. May be you left out some lines; you define a string editEmployee but out of the blues you store JSON.stringify(employees) whereas employees is not defined in your code:
$('#edit_employee_page').on('click' , function(){
var editEmployee = JSON.stringify({
id: employees.length+1,
//........
});
//Alter the slected data
localStorage.setItem("employees", JSON.stringify(employees));
return true;
});
I had a similar task to do . I did it this way.
I passed the dynamic Id to be passed as an id attribute
id="'+empData.id+'"
and then inside the
$('.edit_button').live('click', function (e) {
alert('I was Cliked!');
var empId=$(this).attr('id');
rest of the code is same.

Unable to update object in angular

Right now, I am able to create objects and save them to a rails database through a json api. It is a simple todo list app that when I add an object, it goes into the todo list above the input fields. I am also able to query all the objects on the index view and list all of my todo objects.
When I click on a checkbox next to each todo item, it is supposed to change it's :done attribute to true, so that I do not show it anymore after they click a "Clear" button which is the clearCompleted function.
Here is my js code
angular.module("Todo", ["ngResource"])
function TodoController($scope, $filter, $resource) {
Todo = $resource("/todos.json", {id: "#id"}, {update: {method: 'PUT'}})
$scope.todos = Todo.query()
$scope.getTotalTodos = function () {
return $scope.todos.length;
};
$scope.clearCompleted = function () {
Todo.save({done:true})
$scope.todos = $filter("filter")($scope.todos, {done:false});
};
$scope.addTodo = function () {
entry = Todo.save({title:$scope.newTodo.title, importance:$scope.newTodo.importance, description:$scope.newTodo.description, done:false})
$scope.todos.push(entry);
$scope.newTodo.title = '';
$scope.newTodo.importance = '';
$scope.newTodo.description = '';
};
}
What is happening is that rather than updating that object in the database, it is creating a brand new todo item and just giving it the attribute of :done => true. It makes sense because in that clearCompleted function it looks like we are creating an object not updating an existing one.
How do I update the record in the database then?
You shouldn't use Todo.save({}) to update a todo. As you said it create a new object.
Write code as follows.
Todo = $resource("/todos.json", {id: "#id"}, {'save': {method: 'PUT'}})
$scope.todos = Todo.query();
var todo = todos[0];//A todo that should be updated
todo.done = true;
todo.$save();
Mabe you need a parameter for todo key in your save url.
using $resource

AngularJS promise is resolved before data is loaded

In my app, I have to fetch some JSON data and assign it to an array before the page is loaded. This is my code for fetching the JSON using the CardService service:
cards = [];
var cs = {
...
fetchCards: function() {
var d = $q.defer();
$http.get("data/cards.php").success(function(data) {
cards = data;
d.resolve();
}).error(function(data, status) {
d.reject(status);
});
return d.promise;
},
getCards: function() { return cards; };
...
}
In the controller's resolve block, I have the following:
WalletController.resolve = {
getCards: function(CardService) {
CardService.fetchCards().then(loadView, showError);
}
}
And in the actual controller, I have the following:
function WalletController($scope, CardService) {
$scope.cards = CardService.getCards();
}
The problem is, the fetchCards function in the service seems to resolve the promise before the JSON data is assigned to the cards variable. This leads to my view loading with blank data until I refresh a couple times and get lucky.
I can confirm the late loading as when I log the cards variable in the console, I get an empty array at line 122 (when my view is loaded) and a full array at line 57 (when the JSON call is successful). Line 57's code somehow executes after the view is loaded.
How do I fix this?
I haven't used resolve but I'm throwing this out there just in case the issue you are having is related to binding to an array returned from a service.
If you are returning your cards array from a service and binding to it in the UI you may want to try to populate that same array instead of setting cards = data; (which will overwrite the local cards with a new array which is not bound to the UI).
Something like:
fetchCards: function() {
var d = $q.defer();
$http.get("data/cards.php").success(function(data) {
cards.length = 0;
for(var i = 0; i < data.length; i++){
cards.push(data[i]);
}
d.resolve();
}).error(function(data, status) {
d.reject(status);
});
return d.promise;
},
See this fiddle for a working example of what I'm trying to describe. Clicking the first button multiple times will update the view but once you click on the second button the binding will be broken.
The main difference between the two is:
First button uses data.length = 0 and data.push() to retain the original array's reference
Second button overwrites the original data array reference with a new one using data = newArray
Update: Also, as Mark Rajcok, mentioned below you can use angular.copy to retain the original array's reference by emptying it out and adding new ones from the source like this:
fetchCards: function() {
var d = $q.defer();
$http.get("data/cards.php").success(function(data) {
angular.copy(data, cards);
d.resolve();
}).error(function(data, status) {
d.reject(status);
});
return d.promise;
},

Categories

Resources