Converting angular-dragdrop to work with firebase $asArray - angularjs

Pretty new to AngularJS and Firebase here, I am trying to convert angular-dragdrop.js as per the following link below to work with angularFire 0.8.0 $asArray feature:
https://github.com/codef0rmer/angular-dragdrop/blob/master/src/angular-dragdrop.js
I was just wondering if the following changes would be sufficient:
1) Include firebase within function declaration
(function (window, angular, firebase, undefined) {
2) Include $firebase within jqyoui callback function
var jqyoui = angular.module('ngDragDrop', []).service('ngDragDropService', ['$firebase', '$timeout', '$parse', function($timeout, $parse) {
3) Change all the "push" and "splice" update on the dropModelValue and dropModelValue to $add and $remove instead.
dropModelValue.$add(dragItem);
4) Add $save after dropModelValue and dropModelValue assignments
dragModelValue[dragSettings.index] = dropItem;
dragModelValue[dragSettings.index].$save(dragSettings.index);
Your help is much appreciated. Much thanks in advance.

You can utilize $extendFactory to override the push/splice behaviors instead of hacking on the drag drop lib. Ideally, you would just update the priority on the records and let the server move them.
Keep in mind that Firebase data is a JSON object (not an array and therefore not ordered in JavaScript), so moving items in the array has no effect on their position on the server. You must use priorities if you want to enforce an order on the data, other than lexicographical sorting by keys.
Also, you aren't using $save correctly--you call array.$save(item), not item.$save(itemIndex). Judging by these misconceptions, there are likely to be lots of other issues. A trip through the Angular Guide and the Firebase JS Guide be a great primer here.

One technique I have used to reorder a Firebase array using drap & drop, is to rebind the keys to the values so that their lexicographical order match the new order set by the user. Since Firebase will enforce lexicographical order of keys, swapping the keys of 2 values will swap the values. Every time the user drops an item, rebind the keys:
_(myFirebaseArray)
.map('$id')
.sortBy()
// at this stage we have the array of keys sorted lexicographically
// we pair each key with the values, which are sorted by the user
.zipObject(myFirebaseArray)
// for each pair, bind the key to the value and save
.each(function (value, newKey) {
value.$id = newKey
myFirebaseArray.$save(value)
})
This is probably sub-optimal. I was not aware of priorities. This technique can probably be adapted fairly easily to use priorities. The code above should be compatible with lodash from 2 to 4.
Example of this technique in an application here. The ranking array is bound to the drap & dropping through Angular UI.Sortable.

Related

How do deal with nested Arrays/objects in BehaviorSubjects, Observables?

I generally have problems using rxjs with nested Objects or Arrays.
My current use-case is this:
{a: [
{b: 0, c:[{d:1}]},
{b: 1, e:[{f: 'someString'}]}
]
Task: Get and set the Observable or value of a,b,c,d,e,f. I also want to be able to subscribe to each property.
I had this Problem in a similar use-case with an Array of BehaviorSubjects:
Efficiently get Observable of an array BehaviorSubjects
I generally have problems to use the basic functionality of nested arrays/objects in rxjs.
The basic functionality I mean includes:
Array:
getting Element by Index
using for of/in on Arrays
setting an Element by Index
push, pop, shift, slice, splice, ...
Object:
getting Value by Property name
going into the nested tree: object.key1.key2.key3[3].key4 ...
setting Value by Property name
assign
for of/in loops
Generally:
Destructuring: e.g.: let [variable1, variable2] = someObject;
Maybe other stuff I forgot.
I dont know if and which functions are possible for which rxjs Objects and which make sense (for example you should be able to set values in an Observable directly). But coming from a background without rxjs, I have trouble to manage my rxjs Objects properly.
I think reason for this besides my lack of knowledge and understanding is, that
a. The rxjs Objects don't provide the functionality as I'm used to from normal arrays and objects. e.g.:
let variable1 = array[1].property;
//becomes this (see related stack-Question I mentioned earlier)
let variable2 = array.pipe(mergeMap(d=> d[index].pipe(map(d1 => d1[property]));
// -> what happens here? You first need to know what mergeMap,
// map is doing and you have 5 levels of nested inline functions.
b. To implement the those mentioned functionalities I need to go over the .pipe() function and use some function like mergeMap, map, pluck, ... Functions that aren't directly indicating that you can get the Observable of let's say 'e' in my example. Making something like object.a[1].e wierd to implement (at least I don't know how to do that yet)
EDIT:
I also want to note, that I still love the idea of rxjs which works well in angular. I just have problems using it to it's full extend, as I'm a bit new to angular and consequently rxjs.
I thin RX is mainly focus on dealing with async operations. Mutation of array and object we can perfectly use the methods comes natively with javascript if theres no existing operators. or you can create your own operator for mutation/iteration etc.
Will try to answer some of your question on array/objects mutation, they are actually very straight forward.
Array:
getting Element by Index
map(arr=>arr[index])
using for of/in on Arrays
map(arr=>arry.map(item=>....))
setting an Element by Index
tap(arr=>arr[index]=somevalue)
Object:
getting Value by Property name
pluck('name')
going into the nested tree: object.key1.key2.key3[3].key4 ...
pluck('key1','key2')
setting Value by Property name
map(obj=>({a:value,obj...}))
assign
lets say your really want some pick array index method as rxjs operator you can create something like, same as for..in operations.
const pluckIndex=(index)=>source=>source.pipe(map(arr=>arr[index]))
const source = of([2,3])
source.pipe(pluckIndex(1)).subscribe(x => console.log(x));

sortBy in React?

I fetch the data from a server and some items have a specific attribute others don't. I need to sort data according to this specific attribute and I am using sortBy package but of course it doesn't work properly because when it tries to sort data and doesn't find the attribute, it is broken.
myItems.sort(sortBy('specificAttr'))
Basically, what I did (think of inside of a loop):
if(!myItems.specificAttr) {myItems.speficificAttr = 0);
I know it doesn't make sense at all, but I don't know what I can do.
Do you have any advice with code examples?
Using lodash's sortBy
Lodash handles this case out of the box. If it can't find the attribute, it gets pushed to the end of the sorted array.
var users = [
  { 'age': 48 },
  { 'user': 'barney', 'age': 36 },
  { 'user': 'fred',   'age': 40 },
  { 'user': 'barney', 'age': 34 }
];
 
_.sortBy(users, [(o) => { return o.user; }]);
// => objects for barney, barney, fred, and no-name in that order
_.sortBy(users, ['user']);
// => objects for barney, barney, fred, and no-name in that order
If you're using some other library's sortBy, check the documentation to see if there's an optional second argument that would allow you to customize the attribute getter function.
Rolling your own
If you're implementing your own sorting algorithm, simply do a check like if myItem.specificAttr to check if the attribute exists while you're doing the actual sorting (rather than prior sorting, as you described in your example).

Unshifting / Adding to Front of Firebase Object Using AngularFire or Vanilla JavaScript

I have an array / object stored on Firebase that I add items to using AngularFire's $add function. $add is the equivalent of using .push on a regular JavaScript array. The value you specify is added to the end of the array.
Is there anyway to add my value to the front of this array / object stored on Firebase? Something that does the same thing as .unshift in vanilla JavaScript?
I would prefer an AngularFire solution, but vanilla JavaScript solutions are fine if there isn't one.
Since the data isn't stored in an array (prior to AngularFire 0.8 due out next week), and objects are unsorted in js, there is technically no concept of front/back. This is achieved in Firebase by sorting your data keys or by using priorities. See ordered data for more on this topic.
In AngularFire, priorities are stored on records as $priority. You can set this value before saving a record to control its sort ordering:
var data = $firebase(ref);
data.$on('loaded', function() {
data['foo'].$priority = 99;
data.$save('foo');
});
To place an item first in the list, you would simply assign it a smaller priority than the other records.

Angular: where to calculate values

Given an array of “visits” [{ date:DATE, summary:TEXT }, { date:DATE, summary:TEXT }, …]
, if I need to show the last visit, where would I do the calculation:
In the controller and add the calculated value to the $scope - <div>{{lastVisit}}</div>
Using a $scope method - <div>{{getLastVisit()}}</div>
In the view (this definitely doesn’t feel right) - <div>{{visits[visits.length-1]}}</div>
I am avoiding for now the question whether the model should be manipulated inside the controller or in its own service.
With option 1, you'd have to add a watch to update the lastVisit in model any time the visits array changes. Option 2 is better but requires writing an additional one-liner function in your model.
The third option is legit and require zero javascript so if you only need to simply show the last element of the array this is the way to go.
It's also the most efficient as it doesn't require any additional objects in memory, and doesn't call any other function (than angular parse internally)
If you don't want the logic in your view, Option 2 is your best choice. But I would create a more generic method that returns the last element of the array like that:
<div>{{getLastItem(visits)}}</div>
$scope.getLastItem = function(arr){
return arr[arr.length - 1];
};
See fiddle: http://jsfiddle.net/hZM23/1/
My personal preference on the above approaches:
Yes, the best approach, since you don't have your business logic in your view.
This will have the same effect as the 1st option, but accessing Model from view comes under best practices.
No, since your business logic might change in future.
Option 1: Doing calculations in the controller and storing the value to $scope. When that value is updated, angular will automatically update your view. This is likely what you want.
To clarify: In your controller, when the visits array is updated ( element added, deleted, etc... ), calculate the last visit value and store it to $scope.lastVisit.
var function newVisit( visit ){
visits.append( visit );
$scope.lastVisit = visit; // this will update your view
}
Why I don't think option 2 is right: Angular will be binding the function and not the value itself. Binding to the value itself is likely what you mean.
You are correct about 3, keep logic out of the view if possible.

quering for objects with angularFireCollection?

I used the implicit method for retrieving data objects:
setData = function(segment){
var url = 'https://myFireBase.firebaseio.com/';
var rawData = angularFire(url+segment,$rootScope,'data',{});
rawData.then(function(data){
// sorting and adjusting data, and then broadcasting and/or assinging
}
}
This code is located inside a service that gets called from different locations, by development stages it'll probably be around 100 - 150 so I got out of the controllers and into a service, but now firebase data-binding would obviously over-write the different segments so I turned back to explicit methid, to have the different firebases only sending the data to site instead of data-binding and over-writing each other:
var rawData = angularFireCollection(url+segment);
And right there I discovered why I chose the implicit in the first place: There's an argument for the typeof, i could tell firebase if I'm calling a string, an array, an object etc. I even looked at the angularfire.js and saw that if the argument is not given, if falls back to identifying it as an array by default.
Now, I'm definitely going to move to the explicit method (that is, if no salvation comes with angular2.0), and reconstructing my firebase jsons to fit the array-only policy is not that big of a deal, but surely there's an option to explicitly call objects, or am I missing something?
I'm not totally clear on what the question is - with angularFireCollection, you can certainly retrieve objects just fine. For example, in the bundled chat app (https://github.com/firebase/angularFire/blob/gh-pages/examples/chat/app.js#L5):
$scope.messages = angularFireCollection(new Firebase(url).limit(50));
Each message is stored as an object, with its own unique key as generated by push().
I'm also curious about what problems you found while using the implicit method as your app grew. We're really looking to address problems like these for the next iteration of angularFire!

Resources