Mutating array within an array (Polymer iron-list) - arrays

I currently have an iron-list within another iron-list. The parent's data comes from a firebase-query element, and the child's data is computed from each parent item. The db structure and code looks a bit like this:
DB: [
category1: [
itemId1: {
price: 10,
title: "title"
}
]
]
<iron-list id="categoryList" items="{{categories}}" multi-selection as="category">
<template>
<div class="category-holder">
<iron-list id="{{category.$key}}" items="{{_removeExtraIndex(category)}}" as="item" selection-enabled multi-selection selected-items="{{selectedItems}}" grid>
<template>
<div class$="{{_computeItemClass(selected)}}">
<p>[[item.title]]</p>
<p>[[item.price]]</p>
</div>
</template>
</iron-list>
</div>
</template>
</iron-list>
After selecting any number of items, the user can tap on a fab to batch edit the price. This is where I'm having issues. I can't figure out how to access the correct child iron-list in order to call list.set...I'm currently trying the following very nasty method:
var categories = this.$.categoryList;
var categoryItems = categories.items;
(this.selectedItems).forEach(function(item) {
var index = item.itemId;
categoryItems.forEach(function(itemList, categoryIndex) {
if (itemList[index]) {
categories.set('item.' + categoryIndex + '.price', 10);
}
}, this);
}, this);
I'm iterating over the selected items in order to extract the item index and then iterating over the parent iron-list data (categoryItems) in order to check if the given item exists in that subset of data. If so, then I use the category index and attempt to call set on the parent iron-list using the given path to access the actual item I want to edit. As expected, this fails. Hopefully I've made myself clear enough, any help would be appreciated!
EDIT #1:
After much experimenting, I finally figured out how to correctly mutate the child iron-list:
(this.selectedItems).forEach(function(item) {
var list = this.$.categoryList.querySelector('#' + item.category);
var index = list.items.indexOf(item);
list.set(["items", index, "price"], 30);
}, this);
A couple of things worth noting. I'm using querySelector instead of the recommended this.$$(selector) because I keep running into a "function DNE" error. But now I have another problem...after calling the function, the value gets updated correctly but I get the following error:
Uncaught TypeError: inst.dispatchEvent is not a function
Here's a picture of the full error message:
I see the light, hopefully someone can help me out!

OK, I'll take a shot at this. I think the following happens, and I guess this based on how dom-repeat works:
var categories = this.$.categoryList;
var categoryItems = categories.items;
You take the variable that the iron-list is based on, but setting one array to another just creates a reference in javascript. As soon as you update categoryItems, you also update this.$.categoryList.items. When you later sets the new value, iron-list will do a dirty check and compare all subproperties, and because they are equal (because ... reference), the iron-list wont update the dom.
What you should do is to make sure it's a totally new copy and the way of doing that is to use JSON.parse(JSON.stringify(myArray)).
Further on, one major flaw I see in your code is that you're using querySelector to select an element, and then manipulate that. What you should do is to use this.categories and only that variable.
So your method should look something like:
// Get a freshly new array to manipulate
var category = JSON.parse(JSON.stringify(this.categories);
// Loop through it
category.forEach(category) {
// update your categoryList variable
}
// Update the iron list by notifying Polymer that categories has changed.
this.set('categories', category);

Related

how to retrieve data nested in two collections from firebase with angular

I'm new in Angular - Firebase development, and I am having problems to understand how to retrieve data nested in two collections.
I have a collection named "Orders", which includes a field call "auth", which is the user ID, and I have another collection that is the "User Profile", wich it's $id is the value of "auth". Inside the User Profile I have a field named roomNumber, and it's content I that I want to retrieve every time I read, in ng-repeat of the Orders.
In my view I was trying to do something like this :
<tr ng-repeat="item in items | filter: searchKeyword ">
<td align="left">{{item.$id}} - {{roomNumber(item.$id)}}</td></tr>
roomNumber is a function in my controller
$scope.roomNumber = function(id) {
var rootRef = new Firebase("https://xxxx-fire-yyyy.firebaseio.com/userProfile"+ '/' + id);
$scope.userdet = $firebaseArray(rootRef);
rootRef.on("value", function(rootSnapshot) {
var key = rootSnapshot.key();
var childKey = rootSnapshot.child("room").val();
console.log("room ", childKey)
});
return childKey
}
When I run this code and see results in my js console, strange things happend:
1. It repeat a lot of times
2. I can never get the childKey value
I have been reading Firebase documentation, but really I do not understand how to do this "silly" thing, does anybody give me a tip of how to do it?
When you bind a function to the $scope and call it within the html it expects to get an answer back right away when called. So when you query firebase and it takes its sweet time getting you back an answer, angularjs has already gotten an answer of undefined from the function.
So what is happening is that you are registering a callback when you provide the function to rootRef.on and then right after you register the callback you are returning the value of childKey. Unfortunately, childKey only gets set by the callback function (which firebase hasn't executed yet). Therefore angularjs gets an answer of undefined from your roomNumber function.
In order to make this work, you are going to have to get the room numbers beforehand and then probably add them to each of your items in $scope.items then use
<td align="left">{{item.$id}} - {{item.room}}</td></tr>
instead of
<td align="left">{{item.$id}} - {{roomNumber(item.$id)}}</td></tr>
To load all the room numbers you could call some function like this one after $scope.items has loaded
for (var i = 0; i < $scope.items.length; i++) {
var rootRef = new Firebase("https://xxxx-fire-yyyy.firebaseio.com/userProfile"+ '/' + $scope.items[i].$id);
$scope.userdet = $firebaseArray(rootRef);
rootRef.on("value", function(rootSnapshot) {
var key = rootSnapshot.key();
var childKey = rootSnapshot.val().room;
$scope.items[i].room = childKey;
});
}
It would change each of the items to have a reference to the room. Unfortunately, that list wouldn't update as the data updates, so the better solution would be to do that same query in whatever function was getting your items from the server and add the room to each item as it was being added to the items list.
To fix the issue with childKey not reading you need to use this:
var childKey = rootSnapshot.val().room;
instead of this:
var childKey = rootSnapshot.child("room").val();
console.log("room ", childKey)
Reference: https://www.firebase.com/docs/web/guide/retrieving-data.html

How to Fetch a set of Specific Keys in Firebase?

Say I'd like to fetch only items that contains keys: "-Ju2-oZ8sJIES8_shkTv", "-Ju2-zGVMuX9tMGfySko", and "-Ju202XUwybotkDPloeo".
var items = new Firebase("https://hello-cambodia.firebaseio.com/items");
items.orderByKey().equalTo("-Ju2-gVQbXNgxMlojo-T").once('value', function(snap1){
items.orderByKey().equalTo("-Ju2-zGVMuX9tMGfySko").once('value', function(snap2){
items.orderByKey().equalTo("-Ju202XUwybotkDPloeo").once('value', function(snap3){
console.log(snap1.val());
console.log(snap2.val());
console.log(snap3.val());
})
})
});
I don't feel that this is the right way to fetch the items, especially, when I have 1000 keys over to fetch from.
If possible, I really hope for something where I can give a set of array
like
var itemKeys = ["-Ju2-gVQbXNgxMlojo-T","-Ju2-zGVMuX9tMGfySko", "-Ju202XUwybotkDPloeo"];
var items = new Firebase("https://hello-cambodia.firebaseio.com/items");
items.orderByKey().equalTo(itemKeys).once('value', function(snap){
console.log(snap.val());
});
Any suggestions would be appreciated.
Thanks
Doing this:
items.orderByKey().equalTo("-Ju2-gVQbXNgxMlojo-T")
Gives exactly the same result as:
items.child("-Ju2-gVQbXNgxMlojo-T")
But the latter is not only more readable, it will also prevent the need for scanning indexes.
But what you have to answer is why want to select these three items? Is it because they all have the same status? Because they fell into a specific date range? Because the user selected them in a list? As soon as you can identify the reason for selecting these three items, you can look to convert the selection into a query. E.g.
var recentItems = ref.orderByChild("createdTimestamp")
.startAt(Date.now() - 24*60*60*1000)
.endAt(Date.now());
recentItems.on('child_added'...
This query would give you the items of the past day, if you had a field with the timestamp.
You can use Firebase child. For example,
var currFirebaseRoom = new Firebase(yourFirebaseURL)
var userRef = currFirebaseRoom.child('users');
Now you can access this child with
userRef.on('value', function(userSnapshot) {
//your code
}
You generally should not be access things using the Firebase keys. Create a child called data and put all your values there and then you can access them through that child reference.

Transform business object into view model in angular-meteor

Background
I have an angular-meteor app and a collection of business objects in mongodb, e.g.:
{ startDate: new Date(2015, 1, 1), duration: 65, value: 36 }
I want to render this data in different views. One of the views is a graph, another is a list of entries.
The list of entries is easy. Just bind the collection to the model:
vm.entries = $scope.meteorCollection(MyData, false);
In the view I would do:
<div ng-repeat="entry in vm.entries">{{entry.startDate}} - ...</div>
But now for the graph. I want to transform each element into a { x, y } object and give the view that, e.g.:
vm.graphPoints = transformData(getDataHere());
The problem is that the data is not fetched here, in angular-meteor is looks like it is fetched when calling the entry in vm.entries in the view. The kicker is that the transformData method, needs to loop through the data and for each item index into other items to calculate the resulting x, y. Hence I cannot use a simple forEach loop (without having access to the other items in some way).
Question
So how can i fetch the data in the controller - transform it - and still have one-way binding (observing) on new data added to the database?
Thanks
Update
The following code works, but I think there could be performance problems with fetching the whole collection each time there is a change.
$scope.$meteorSubscribe("myData").then(function() {
$scope.$meteorAutorun(function() {
var rawData = MyData.find().fetch();
vm.model.myData = transformData(rawData);
});
});
EDIT:
This is the current solution:
$scope.collectionData = $scope.meteorCollection(MyData, false);
$meteor.autorun($scope, function() {
vm.graphPoints = transformData($scope.collectionData.fetch());
});
There is some missing information. do you wan't to have some kind of model object in the client? if that is correct I think you have to do something like this:
$meteor.autorun($scope, function() {
vm.graphPoints = transformData($scope.meteorCollection(MyData, false));
});
How about using the Collection Helper Meteor package to add the function:
https://github.com/dburles/meteor-collection-helpers/
?

AngularJS UI-calendar not updating events on Calendar

I am using Angular UI-Calendar to show some events on the Calendar. The events are showing fine on the Calendar. But when I update any event's details, the event's detail is actually modified, but not modified on the Calendar display(eg: start).
Initially, after I modified the event's details, I did a page reload to display modified changes and it worked too.In that method, I had empty $scope.events = []; array, which I filled after retrieving entries from DB.
But now, I want to avoid that page reload. So, once the event's details are modified from modal window, I clear the contents of $scope.events array using $scope.events = []; and then using API call, I fill the new events again in $scope.events array. This is working fine as the List view shows the modified events details. But the Calendar itself shows old entries. e.g if I change start from 11 April to 13 April, the calendar shows event on 11 April whereas List views shows the modified data i.e 13 April. Using Page reload, corrects this i.e event is shown on modified date(13 April).
How can I ensure that the event is modified on Calendar too without a Page reload ?
I tried calendar.fullCalendar('render'); after fetching new entries from DB, but it does not solve the Problem.
Here are some codes :
Initially I did this to send data to DB and then did a page reload to retrieve updated data.
$http.put(url,senddata).success(function(data){$window.location.reload();});
Now I want to avoid the page reload, so I did
$http.put(url,senddata).success(function(data){//$window.location.reload();//sends modified data to server
$scope.events = []; //clear events array
$http.get(url2).success(function(data){ //fetch new events from server, and push in array
$scope.schedule = data;
for(var i=0;i<data.length;i++)
{
$scope.events.push({
idx: data[i].idx,
title: data[i].title,
description : data[i].description,
allDay: false,
start: new Date(data[i].start),
end: new Date(data[i].end),
});
calendar.fullCalendar('render'); //Tried even this, didn't work
}
});
Above code pushes modified event in event array as visible in List view, but calendar still shows old event until page is reloaded.
Try maintaining the same array instance.
Instead of doing:
$scope.events = []
Try:
$scope.events.slice(0, $scope.events.length);
Then when your server request comes back, add each item individually to the existing array:
for(var i = 0; i < newEvents.length; ++i) {
$scope.events.push(newEvents[i]);
}
The reason I suggest this is because what you're describing sounds like the calendar might be holding onto the old list of events. The calendar might not know to update its reference, so instead, let it keep the same reference but change the contents of the array.
Just a quick correction to Studio4Development's answer. You should use "splice" not "slice". Slice returns the trimmed array. What we want to do is actually alter the original array. So you can use:
$scope.events.splice(0, $scope.events.length)
and to add new events:
$scope.events.push(newEvent)
Don't know if you found the solution to your problem, but what worked for me was:
calendar.fullCalendar('refetchEvents');
Here is how I fixed a similar problem on my page.
view (simplified, note using jade)
div#calendarNugget(ng-show="activeCalendar == 'Nugget'" ui-calendar="uiConfig.calendarNugget" ng-model="eventSources")
div#calendarWillow(ng-show="activeCalendar == 'Willow'" ui-calendar="uiConfig.calendarWillow" ng-model="eventSources2")
controller:
As per ui-calendar docs, I start with an empty array for my event sources. Ignore that I should probably rename these eventSources to something about the shop names (willow, nugget)
$scope.eventSources = [];
$scope.eventSources2 = [];
I then call a factory function that makes an http request and pulls a series of events from the DB. The events all have "title", "start", "end" properties (make sure your Date format is correct). They also have a "shop" property, which tells me which calendar to add the event to.
So after receiving the data I make two local arrays, loop through the received http data, and assign the events to those local arrays by shop. Finally, I can re-render the calendars with the proper event data by calling addEventSource, which automatically re-renders as per the fullCalendar docs
It looks something along the lines of this iirc:
function splitShiftsByShop(shifts) {
var nuggetShifts = [];
var willowShifts = [];
for (var i=0; i<shifts.length; i++) {
if (shifts[i].shop === "Nugget") {
var nshift = {
title : shifts[i].employee,
start : new Date(shifts[i].start),
end : new Date(shifts[i].end),
allDay: false
}
nuggetShifts.push(nshift);
} else if (shifts[i].shop === "Willow") {
var wshift = {
title : shifts[i].employee,
start : new Date(shifts[i].start),
end : new Date(shifts[i].end),
allDay: false
}
willowShifts.push(wshift);
}
}
/*render the calendars again*/
$('#calendarNugget').fullCalendar('addEventSource', nuggetShifts);
$('#calendarWillow').fullCalendar('addEventSource', willowShifts);
}
I was having some similar issues where events weren't being refetched after emptying my "eventSource" array ($scope.eventSources = [$scope.completedEvents]) and repopulating it with new events. I was able to overcome this at first by calling 'removeEvents',
uiCalendarConfig.calendars.calendar.fullcalendar('removeEvents')
This is hackish, so after further tinkering I found that events were refetched when my child array is modified,
$scope.completedEvents.splice(0, $scope.completedEvents.length)
After reviewing the source, I can see that the eventSource array is being watched, but the 'refetch' is never occurring. furthermore, I was never able to get the following to work,
uiCalendarConfig.calendars.calendar.fullcalendar('refetchEvents')
According to ui-calendar code, it does actually watch eventSources but never the actual individual sources. So doing a push to, let's say $scope.events ($scope.eventSources = [$scope.events]), will never trigger anything.
So push events to $scope.eventSources[0]
$scope.eventSources[0].splice(0, $scope.eventSources[0].length);
$scope.eventSources[0].push({
title : title,
start : start,
end : end
});
As you already know Full Calendar is dependant on JQuery. Therefore, all you need to do is put an ID on your calendar like this:
<div id="calendar" ui-calendar="uiConfig.calendar" class="span8 calendar" ng-model="eventSources"></div>
and then when ever you want to update the calendar just call:
$('#calendar').fullCalendar('refetchEvents')
I struggled with this for some time as well and this fixed all my problems. I hope this will help you too!
Goodluck!
Although it's late response but hope it may help some. I am putting it forth step by step.
You will need to maintain the same event source as Studio4Development mentioned earlier.
$scope.events //no need to reinitialize it
You will remove the updated event from events array like this (for last event in event array):
$scope.events.splice($scope.events.length - 1, 1);
For event that may exist anywhere in the events array you'll need to find its index using:
var eventIndex = $scope.events.map(function (x) { return x.Id; }).indexOf(eventId);
and then you'll remove it from events array like this:
$scope.events.splice(eventIndex, 1);
Next you'll fetch this event from the API again like this:
getEvent(data.Id);
and in the callback method you'll again add the event in events array:
var getSingleEventSuccessCallback = function (event) {
$scope.events.push(event);
};
Was stuck on this for a while.
What seems to be working for me turned out to be quite simple
I fetch my calendar from google and run an event formatter function. In this I define a new array and at the end of the function I set my eventSource equal to the new array. Then I call $scope.$apply
x = []
for e in events
x.push(
title: e.summary
etc...
)
$scope.eventSource = x
$scope.$apply()
Try this. It worked for me.
function flushEvents()
{
$scope.events.splice(0,$scope.events.length);
for(var i=0; i<$scope.events.length; i++)
{
$scope.events.splice(i,1);
}
}
I found this and it helped me:
$(id_your_div).fullCalendar('removeEvents');
$(id_your_div).fullCalendar('refetchEvents');

ExtJS: Added grid rows wont de-highlight

When adding a rows to a grid, and then clicking on it, it gets selected (and highlighted). Then, clicking elsewhere but the new row remains highlighted (so now there are to highlighted rows).
Please, does anyone know what the problem could be? How to make it behave normally, i.e. clicking a row deselects (de-highlights) the other one?
After I reload the page (so the new row is not new anymore), everything works as expected.
Edit: Here's the code for adding rows:
var rec = new store.recordType({
test: 'test'
});
store.add(rec);
Edit 2: The problem seems to be listful: true. If false, it works! But I need it to be true so I'm looking at this further... It looks like as if the IDs went somehow wrong... If the ID would change (I first create the record and then the server returns proper ID, that would also confuse the row selector, no?)
(Note, correct as ExtJS 3.3.1)
First of all, this is my quick and dirty hack. Coincidentally I have my CheckboxSelectionModel extended in my system:-
Kore.ux.grid.CheckboxSelectionModel = Ext.extend(Ext.grid.CheckboxSelectionModel, {
clearSelections : function(fast){
if(this.isLocked()){
return;
}
if(fast !== true){
var ds = this.grid.store,
s = this.selections;
s.each(function(r){
//Hack, ds.indexOfId(r.id) is not correct.
//Inherited problem from Store.reader.realize function
this.deselectRow(ds.indexOf(r));
//this.deselectRow(ds.indexOfId(r.id));
}, this);
s.clear();
}else{
this.selections.clear();
}
this.last = false;
}
});
And this is the place where the clearSelections fails. They try to deselect rows by using ds.indexOfId(r.id) and it will returns -1 because we do not have the index defined remapped.
And this is why we can't find the id:-
http://imageshack.us/photo/my-images/864/ssstore.gif/
Note that the first item in the image is not properly "remapped". This is because we have a problem in the "reMap" function in our Ext.data.Store, read as follow:-
// remap record ids in MixedCollection after records have been realized. #see Store#onCreateRecords, #see DataReader#realize
reMap : function(record) {
if (Ext.isArray(record)) {
for (var i = 0, len = record.length; i < len; i++) {
this.reMap(record[i]);
}
} else {
delete this.data.map[record._phid];
this.data.map[record.id] = record;
var index = this.data.keys.indexOf(record._phid);
this.data.keys.splice(index, 1, record.id);
delete record._phid;
}
}
Apparently, this method fails to get fired (or buggy). Traced further up, this method is called by Ext.data.Store.onCreateRecords
....
this.reader.realize(rs, data);
this.reMap(rs);
....
It does look fine on the first look, but when I trace rs and data, these data magically set to undefined after this.reader.realize function, and hence reMap could not map the phantom record back to the normal record.
I don't know what is wrong with this function, and I don't know how should I overwrite this function in my JsonReader. If any of you happen to be free, do help us trace up further for the culprit that causes this problem
Cheers
Lionel
Looks like to have multi select enabled for you grid. You can configure the selection model of the grid by using the Ext.grid.RowSelectionModel.
Set your selection model to single select by configuring the sm (selection model) in grid panel as show below:
sm: new Ext.grid.RowSelectionModel({singleSelect:true})
Update:
Try reloading the grid using the load method or loadData method of the grid's store. Are you updating the grid on the client side? then maybe you can use loadData method. If you are using to get data from remote.. you can use load method. I use load method to update my grid with new records (after some user actions like add,refresh etc). Or you could simply reload as follows:
grid.getStore().reload();

Resources