AngularFire unshift items in AngularJS array - javascript

Please help me prepend items when they are pushed over Firebase RESTful service, new item should be on top order when the are displayed in DOM with ng-repeat.
//post.js service
var ref = new Firebase(FIREBASE_URL + 'posts');//creates posts object on root of url
var posts = $firebase(ref);//we get posts Object from firebase
....
var Post = {
create: function(post){
return posts.$add(post);
}
.....
//posts.js controller
$scope.submitPost = function(){
Post.create($scope.post).then(function(){
console.log('success! post submitted');
});
}
HTML:
<div class="col-xs-12" ng-repeat="(postId, post) in posts">
{{post.title}}
{{post.url}}
</div>
But in DOM the newest item goes at the bottom, where as I need the new item should be on the top.
Is there any unshift method in AngularFire ($firebase) ?

If you don't want to get into setting priorities, a simple solution is to let Firebase order your list naturally and reverse it on the client side using a filter such as:
angular.module("ReverseFilter",[])
.filter('reverse', function() {
function toArray(list) {
var k, out = [];
if( list ) {
if( angular.isArray(list) ) {
out = list;
}
else if( typeof(list) === 'object' ) {
for (k in list) {
if (angular.isObject(list[k])) { out.push(list[k]); }
}
}
}
return out;
}
return function(items) {
return toArray(items).slice().reverse();
};
});
Add this module dependency to your app and you'll have a filter that you can use in the ng-repeat attribute like this:
(ng-repeat="(idx, entry) in journal.months[0] | reverse")

Keeping in mind that Firebase stores JSON objects, never arrays, it should make sense that it's not possible to perform an unshift operation against a list. (What would that mean to an object whose keys are sorted lexicographically?) You'll need to utilize priorities and ordered data if you want your item to appear first. Or, if the list is terse, just sort client side.
Here's a quick and dirty way to implement unshift, although this will almost always be inferior to utilizing proper data ordering or simply using endAt():
app.factory('OrderedList', function($FirebaseArray, $firebase, $firebaseUtils) {
var OrderedList = $FirebaseArray.$extendFactory({
unshift: function(data) {
var self = this, list = this.$list;
self.$add(data).then(function(ref) {
var newId = ref.name();
var pos = self.$indexFor(newId);
if( pos > 0 ) {
// place the item first in the list
list.splice(0, 0, list.splice(pos, 1)[0]);
// set list priorities to match
self.reorder();
}
});
},
// order items by their current index in the list
reorder: function() {
var list = this.$list;
angular.forEach(list, function(rec, i) {
rec.$priority = i;
list.$save(rec);
});
}
});
return function(ref) {
return $firebase(ref, {arrayFactory: OrderedList}).$asArray();
}
});
Keep in mind that this particular example is not concurrency safe (if multiple users are modifying the same list at the same time, it's going to have some unpredictable results). Implementing a concurrency-safe solution is similar, but use-case specific (the method for generating the priorities will depend on the use case).

Related

Javascript object to array, length = 0

I'm building a webshop where users are able to add products for one of more stores in their basket and checkout (like AliExpress).
On the cart overview page, the content of the basket is shown sorted by store. If the same product is added multiple times over different stores, the product is show by every store.
Now, I want to create an order for every store with the products ordered by that store. I'm using Angular to create the list with products ordered/filtered by store.
That data will be sent to my Node.JS server, to loop the contents and create some orders with items.
The problem, I think, is that the data is processed like a 'object' and not an 'array'. I have found a function which converts a object to an array, but the length is still '0'.
How can I process the data so I can loop through the different items?
AngularJS code to sort cart by store
$scope.filterProducts = function(groupName) {
$scope.productList = [];
$http({
method: 'GET',
xhrFields: {withCredentials: true},
url: '/loadCart'
}).then(function successCallback(response){
if (response.data) {
var mapByShop = function(arr, groupName) {
return arr.reduce(function(result, item) {
result[item[groupName]] = result[item[groupName]] || {};
result[item[groupName]][item['productId']] = item;
console.log('GROUPNAME en RESULT', groupName, result);
return result;
}, {});
};
if (response.data.length > 0) {
if (groupName == 'shopName') {
$scope.productList = mapByShop(response.data, groupName);
} else {
$scope.checkoutList = mapByShop(response.data, groupName);
}
}
}
}, function errorCallback(response){
console.log(response);
});
}
The $scope.productList is sent as 'data' in a $http POST function.
Node.JS code to convert an object to an array
function convertObjectToArray(object, cb){
var cartContent = [];
for (var i in object) {
cartContent[i] = object[i];
}
console.log("convertObjectToArray");
return cb(cartContent);
}
Code to process the data (where length is zero)
convertObjectToArray(req.body.cart, function(result){
console.log(isArray(result));
console.log('result', result);
console.log("lenght", result.length);
})
FYI: the isArray function
function isArray(myArray) {
return myArray.constructor.toString().indexOf("Array") > -1;
}
if array order is not important, you should use
cartContent.push(object[i]);
It will update the .length property automaticly
Your problem is that you are adding properties to the array object, and not using the Array API to insert at integer locations in the object. This means the array essentially remains "empty". If you key on an integer when inserting into the array then your code will work better.
The broken bit:
for (var i in object) {
cartContent[i] = object[i];
}
i is a string key here and will not increment the length of the Array unless the value coerces to an integer value (I think).
Something like this might work:
// Untested...
var keys = Object.keys(object);
for (var i = 0; i < keys.length; i++) {
cartContent[i] = object[keys[i]];
}
Or like the other answer suggested, use the push API.
Aside:
If you are in a modern JS engine you can:
Use Object.values, or
Write a couple of utility functions and convert an object to an array using the following
:
var o = iterable({foo:'fooValue', bar: 'barValue'});
console.log([...o]);
The utility functions:
function iterable(o) {
if(o[Symbol.iterator]) {
return o;
}
o[Symbol.iterator] = iter.bind(null, o);
return o;
}
function* iter(o) {
var keys = Object.keys(o);
for (var i=0; i<keys.length; i++) {
yield o[keys[i]];
}
}

How Can I Filter an Array from Another Array in Ember?

Goal:
I have two arrays: One of vehicles scheduled for a day, and one of all vehicles. If a driver wants to use a vehicle on Monday, my program should check all driver's schedules for Monday and see what vehicles are being used and display a list of available vehicles BEFORE the user can select one (the select dropdown should never show anything that isn't available)
I can't seem to get Ember to filter the data. It's all or nothing. I can't get the filtered data. Are you willing to take a look?
Github Repository: [https://github.com/djbreen7/ejs-taxi-app/blob/master/app/routes/schedules/view.js][1]
routes/schedules/view.js
model(params) {
return this.store.findRecord('schedule', params.schedule_id);
},
setupController(controller, model) {
controller.set('schedule', model);
var self = this;
this.store.findAll('schedule').then(function(schedules) {
controller.set('schedules', schedules);
var scheduledVehicles = schedules.filter(function(day) {
if (day.get('day_of_week') === model.get('day_of_week') && day.get('vehicle.id')) {
return self.store.findRecord('vehicle', day.get('vehicle.id'));
}
});
var vehicles = self.store.findAll('vehicle');
controller.set('vehicles', vehicles.filter(function(vehicle) {
return scheduledVehicles.indexOf(vehicle) === -1;
}));
});
},
I think all these filtering should be done in backend server.
Anyway for doing that in client side, you shouldn't use async in setupController. I mean don't use ember data finding in setupController.
model(params){
return Ember.RSVP.hash({
schedule: this.store.findRecord('schedule', params.schedule_id),
allSchedules: this.store.findAll('schedule'),
allVehicles: this.store.findAll('vehicle')
});
}
setupController(controller, model) {
controller.set('schedule', model.schedule);
controller.set('schedules', model.allSchedules);
var self = this;
var scheduledVehicles = schedules.filter(function(day) {
if (day.get('day_of_week') === model.get('day_of_week') && day.get('vehicle.id')) {
return self.store.peekRecord('vehicle', day.get('vehicle.id'));
}
});
var destVehicles = model.allVehicles.filter(function(vehicle) {
return scheduledVehicles.indexOf(vehicle) === -1;
}));
controller.set('vehicles', destVehicles);
}
Also for this usage I recommend using computed properties placed in controllers even.

Populating array with nested $each functions and conditionals in Angular with jQuery

I'm having a json object I get from a get request in an angular application. It gives me two main attributes data and included. The included object is somehow a relationship with the data object. For example data shows messages and included the senders of those messages. I managed to associate every message with the sender by checking if an attribute from the data object is the same with another attribute in the included object. here is my code
$http.get('data.json').then(
function(jsonAPI) {
console.log(jsonAPI);
var dataObj = {};
var messages = [];
$.each(jsonAPI.data.data, function(x, data) {
dataObj[x] = data;
$.each(jsonAPI.data.included, function(y, included) {
if (data.relationships.sender.data.id == included.id) {
dataObj[x].sender = included;
}
});
messages.push(dataObj[x]);
});
$scope.newMessages = messages;
},
function(errorResponse) {
// todo handle error.
}
);
I create a new array that contains all the data object and also sender's information under the sender attribute. The problem is that a new relationship is added called user_data_template. I want with a similar way to be able to pass to the messages array the corresponding user_data_template data. How can I change the above nested $each function to achieve that?
Here is a working plunker
I managed to find the solution. I had to change the code to the following:
$.each(jsonAPI.data.data, function(x, data) {
dataObj[x] = data;
$.each(jsonAPI.data.included, function(y, included) {
if(data.relationships.sender.data.id == included.id && included.type == "user") {
dataObj[x].sender = included;
}
if(data.relationships.user_data_template.data !== undefined) {
if(data.relationships.user_data_template.data.id == included.id && included.type == "user_data_template") {
dataObj[x].question = included;
}
}
});
messages.push(dataObj[x]);
});
And the new plunker

How to extract data from array in javascript

I have an object (array type) ,its console representation looks like following image . please see the image
This array is created by restangulr using following code ,
restangularProvider.addResponseInterceptor(function (data, operation, what, url, response, deferred) {
if (operation == "getList") {
var extractedData;
extractedData = data.result;
extractedData.paginginfo = data.paginginfo;
return extractedData;
}
if (operation != "get") {
var item = { status: response.status };
feedBackFactory.showFeedBack(item);
}
return response.data;
});
How can I read the elements from this array, I want to extract properties like paginginfo ,also object collection
// The EDIT :1 js libraries I used here angularjsu 1.3.4, and restangular 1.4
My app.js : here I configured rest angular provider
restangularProvider.addResponseInterceptor(function(data, operation, what, url, response, deferred) {
if (operation == "getList") {
var extractedData;
extractedData = data.result;
extractedData.paginginfo = data.paginginfo;
return extractedData;
}
if (operation != "get") {
var item = {
status: response.status
};
feedBackFactory.showFeedBack(item);
}
return response.data;
});
// according to my knowledge this function will intercept every ajax call (api calls) and modify the response , unfortunately I need to apply custom modification because the getlist method must return collection but my api returning object, so according to restangular ,the above code is the possible solution, and here its fine its fetching the data.
userservice.js : this is angular service which using restangular
function(restangular) {
var resourceBase = restangular.all("account");
this.getUsers = function(pagenumber, recordsize) {
var resultArray = resourceBase.getList({
page: pagenumber,
size: recordsize
}).$object;
};
};
according to my knowledge .$object in restangulr resolve the promise and bring back the data, also I am getting the resultArray its looks like in the image in the console, here I can log this array so I think I got all the data from server and filled in this object. I applied some array accessing techniques available jquery and JavaScript like index base accessing , associate accessing but I am getting undefined ie.
resultArray[1] //undifiend;
In angular you can use angular.forEach(items, function(item){ //your code here});
Where items is the array you want to traverse.
If you want to access to one specific position use [], for example var item= items[5].
Then you can do item.property.
UPDATE
Your problem is that you are setting properties in an Array JS Object:
extractedData.paginginfo = data.paginginfo;
You should return the object data like it is and in your controller do something like:
var results= data.result;
var pagInfo= data.paginationInfo;
angular.forEach(results,function(result){});
It looks like the array is numerically indexed (0..1..5); you should be able to simply iterate through it using ForEach (in Angular) or .each (in Jquery).
Something like (JQuery):
$.each(array, function(key, value)
{
// key would be the numerical index; value is the key:value pair of the array index's element.
console.log(value.firstname); // should print the firstname of the first element.
});
First of all, as I said in the comments, you shouldn't be attaching named properties to arrays. Return an object thact contains what you need:
if (operation == "getList") {
return { values: data.result, paging: data.pagingInfo };
}
The getList() method returns a promise, so you need to use that:
this.getUsers = function(pagenumber, recordsize) {
resourceBase.getList({
page: pagenumber,
size: recordsize
}).then(function (data) {
console.log(data.values[0]);
console.log(data.paging.totalRecords);
});
};

How do I remove all the extra fields that DOJO datastore adds to my fetched items?

When fetching an item from a DOJO datastore, DOJO adds a great deal of extra fields to it. It also changes the way the data is structure.
I know I could manually rebuild ever item to its initial form (this would require me to make updates to both JS code everytime i change my REST object), but there certainly has to be a better way.
Perhaps a store.detach( item ) or something of the sort?
The dojo.data API is being phased out, partly because of the extra fields. You could consider using the new dojo.store API. The store api does not add the extra fields.
I have written a function that does what you are looking to do. It follows. One thing to note, my function converts child objects to the { _reference: 'id' } notation. You may want different behavior.
Util._detachItem = function(item) {
var fnIncludeProperty = function(key) {
return key !== '_0'
&& key !== '_RI'
&& key !== '_RRM'
&& key !== '_S'
&& key !== '__type'
};
var store = item._S;
var fnCreateItemReference = function(itm) {
if (store.isItem(itm)) {
return { _reference: itm.id[0] };
}
return itm;
};
var fnProcessItem = function(itm) {
var newItm = {};
for(var k in itm) {
if(fnIncludeProperty(k)) {
if (dojo.isArray(itm[k])) {
// TODO this could be a problem with arrays with a single item
if (itm[k].length == 1) {
newItm[k] = fnCreateItemReference(itm[k][0]);
} else {
var valArr = [];
dojo.forEach(itm[k], function(arrItm) {
valArr.push(fnCreateItemReference(arrItm));
});
newItm[k] = valArr;
}
} else {
newItm[k] = fnCreateItemReference(itm[k]);
}
}
}
return newItm;
};
return fnProcessItem(item);
};
NOTE: this function is modified from what I originally wrote and I did not test the above code.

Categories

Resources