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
Related
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);
});
};
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).
As my app is growing, I'm finding more need for more effective form validation. I personally don't like the angular built in validation that evaluates on field change. And there are always things it won't account for like verifying that a youtube video id is valid. Currently I'm doing validation in each forms controller. I have a function that looks like this. Each field has a message and if there is an error the message will appear red using ng-class.
$scope.validate = function (callback) {
// reset default messages
setMessages();
// create shorter references
var item = $scope.item,
message = $scope.itemMessage;
// title exists
if (item.title === '') {
message.title.message = 'You must give your item a title.';
message.title.error = true;
message.errors += 1;
}
// extract and clear video id with youtube api
if ($scope.temp.video !== undefined && $scope.temp.video !== '') {
var id = '';
var url = $scope.temp.video.replace(/(>|<)/gi,'').split(/(vi\/|v=|\/v\/|youtu\.be\/|\/embed\/)/);
if(url[2] !== undefined) {
id = url[2].split(/[^0-9a-z_]/i);
id = id[0];
} else {
id = url;
}
$http.get("http://gdata.youtube.com/feeds/api/videos/" + id)
.then(function (res) {
$scope.item.video = id;
}, function (res) {
message.video.message = 'That is not a valid youtube video.';
message.video.error = true;
message.errors += 1;
$scope.item.video = '';
});
}
if (message.errors === 0) {
callback();
}
};
and then my actual form submission function calls $scope.validate(); passing it a function containing the $http.post(). The two major problems I see are that my callback isn't promise base so there's no guarantee it won't be called when an error exists and I've read again and again to keep large chunks of logic outside of your controller. I haven't found great examples of how this should be done but it must be a common problem.
You can still use Angular's built-in validation and have it not evaluate unless the form has been submitted:
http://scotch.io/tutorials/javascript/angularjs-form-validation#only-showing-errors-after-submitting-the-form
Essentially you set $scope.submitted = true when the form is submitted and set a conditional check so that error messages and classes are only shown when $scope.submitted is set.
I have a JavaScript object that I am stringifying with JSON.stringify that returns a JSON string with parent and children data.
When I try to parse this string back to an object, the children objects are now null.
function cacheForm(agency) {
var agency = ko.toJS(this); //easy way to get a clean copy
delete agency.contacts; //remove an extra property
for (i in agency.offices) {
for (val in agency.offices[i]) {
//delete agency.offices[i].agency;
//delete agency.offices[i].agencyID;
}
}
for (i in agency.offices) {
for (ii in agency.offices[i].contacts) {
for (val in agency.offices[i].contacts[ii]) {
//delete agency.offices[i].contacts[ii].office;
//delete agency.offices[i].contacts[ii].agencyID;
//delete agency.offices[i].contacts[ii].officeID;
}
}
}
var value = agency;
var cache = [];
parsed = JSON.stringify(value, function (key, value) {
if (typeof value === 'object' && value !== null) {
if (cache.indexOf(value) !== -1) {
// Circular reference found, discard key
return;
}
// Store value in our collection
cache.push(value);
}
return value;
});
var data = JSON.parse(parsed);
}
Edit
Agency part of my view model that I am passing into my cacheForm function and I am using
var agency = ko.toJS(this);
to have my data available in an object which can be parsed to JSON string. I may of deleted this code in my post because my original code had many annotations.
Your question initially showed a screen shot where data.offices = [null] was highlighted.
It's not a parsing error, but an error in stringify. Your paste already has data.offices = [null].
MDN states regarding replacer:
Note: You cannot use the replacer function to remove values from an array. If you return undefined or a function then null is used instead.
And furthermore regarding stringify:
If undefined, a function, or an XML value is encountered during conversion it is either omitted (when it is found in an object) or censored to null (when it is found in an array).
I don't have access to your original object, and hence cannot tell which of the two you are hitting...
Implementing toJSON (or just explicitly constructing another object from the source object) instead of a replacer to filter arrays would be the way to go, if the problem is within your current replacer implementation.
there are various js libraries predefined for parsing json and to get children values . What i usually do to parse json is use http://developer.yahoo.com/yui/json/ YUI library.
So I eventually solved my problem and this is how I did it.
function cacheForm(agency) {
// GET my object from agency vm
var agency = ko.toJS(agency);
var s = YUI().use("json-stringify", function (Y) {
var jsonStrAgency = Y.JSON.stringify(agency, ["activities", "agencyName", "agencyID", "campaignBillings", "category", "declaredBillings", "immediateParent", "numberOfEmployees", "ultimateParent", "uRL"]); // Use an array of acceptable object key names as a whitelist.
var jsonStrOffices, jsonStrContacts;
for (i in agency.offices) {
jsonStrOffices = Y.JSON.stringify(agency.offices, ["address1", "address2", "address3", "address4", "address5", "agencyID", "faxNumber", "officeID", "postCode", "telephoneNumber"]);
for (ii in agency.offices[i].contacts) {
jsonStrContacts = Y.JSON.stringify(agency.offices[i].contacts, ["agencyID", "emailAddress", "firstName", "jobName", "officeID", "personID", "surName", "title"]);
}
}
localStorage.setItem('Agency', jsonStrAgency);
localStorage.setItem('Offices', jsonStrOffices);
localStorage.setItem('Contacts', jsonStrContacts);
});
}
Firstly I am passing in my ko.observableArray to the function cacheForm. This parameter is called agency and it is part of my viewmodel.
I want to parse my observableArray and convert it into a standard javascript object. By using ko.toJS I can do this. There will be no ko constructors after using toJS.
Then I have to get my JSON strings. Since my object has children and grandchildren I have to parse these parts separately. Stringify doesn't like arrays within an object, they will be changed to null and your children data will be lost.
Because of circular recursion, I have to use this:
var s = YUI().use("json-stringify", function (Y) {
This is part of the Yahoo API. This is the script reference:
<script src="http://yui.yahooapis.com/3.11.0/build/yui/yui-min.js"></script>
Y.JSON.stringify takes an object as one parameter and an option paremter which is an array. The purpose of this array is to contain the property names of the object you want to stringify. From other forums I found out this is known as whitelisting.
With all my JSON strings I can store them in HTML5 local storage.
When the page loads I then check to see if my local storage contains data. If true I retrieve my data and serialize from JSON string to a javascript object.
define(['services/datacontext'], function (dataContext) {
var initialized = false;
var agency;
if (localStorage.Agency && localStorage.Offices && localStorage.Contacts) {
var objAgency = new Object(ko.mapping.fromJSON(localStorage.getItem('Agency')));
var objOffices = new Object(ko.mapping.fromJSON(localStorage.getItem('Offices')));
var objContacts = new Object(ko.mapping.fromJSON(localStorage.getItem('Contacts')));
objAgency.offices = objOffices;
objAgency.offices._latestValue[0].contacts = objContacts;
agency = ko.observableArray([ko.mapping.fromJS(objAgency)]);
ko.applyBindings(agency);
initialized = true;
}
else {
agency = ko.observableArray([]);
}
Finally I reconstruct my object to how it was before stringify and map it back to an observableArray and finally bind it.
Hopefully this helps other people using a combination of knockoutJS and complicated objects.
See below for my full code:
define(['services/datacontext'], function (dataContext) {
var initialized = false;
var agency;
if (localStorage.Agency && localStorage.Offices && localStorage.Contacts) {
var objAgency = new Object(ko.mapping.fromJSON(localStorage.getItem('Agency')));
var objOffices = new Object(ko.mapping.fromJSON(localStorage.getItem('Offices')));
var objContacts = new Object(ko.mapping.fromJSON(localStorage.getItem('Contacts')));
objAgency.offices = objOffices;
objAgency.offices._latestValue[0].contacts = objContacts;
agency = ko.observableArray([ko.mapping.fromJS(objAgency)]);
ko.applyBindings(agency);
initialized = true;
}
else {
agency = ko.observableArray([]);
}
var save = function (agency, myStoredValue) {
// Clear Cache because user submitted the form. We don't have to hold onto data anymore.
//amplify.store("Agency", null);
return dataContext.saveChanges(agency);
};
var vm = { // This is my view model, my functions are bound to it.
//These are wired up to my agency view
activate: activate,
agency: agency,
title: 'agency',
refresh: refresh, // call refresh function which calls get Agencies
save: save,
cacheForm: cacheForm
};
return vm;
function activate() {
vm.agency;
if (initialized) {
return;
}
initialized = false;
return refresh();
}
function refresh() {
return dataContext.getAgency(agency);
}
function cacheForm(agency) {
// GET my object from agency vm
var agency = ko.toJS(agency);
var s = YUI().use("json-stringify", function (Y) {
var jsonStrAgency = Y.JSON.stringify(agency, ["activities", "agencyName", "agencyID", "campaignBillings", "category", "declaredBillings", "immediateParent", "numberOfEmployees", "ultimateParent", "uRL"]); // Use an array of acceptable object key names as a whitelist.
var jsonStrOffices, jsonStrContacts;
for (i in agency.offices) {
jsonStrOffices = Y.JSON.stringify(agency.offices, ["address1", "address2", "address3", "address4", "address5", "agencyID", "faxNumber", "officeID", "postCode", "telephoneNumber"]);
for (ii in agency.offices[i].contacts) {
jsonStrContacts = Y.JSON.stringify(agency.offices[i].contacts, ["agencyID", "emailAddress", "firstName", "jobName", "officeID", "personID", "surName", "title"]);
}
}
localStorage.setItem('Agency', jsonStrAgency);
localStorage.setItem('Offices', jsonStrOffices);
localStorage.setItem('Contacts', jsonStrContacts);
});
}
});
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.