How to group Array & Objects? - javascript

I have an array with 3 Objects, however the second Object does not have an array inside the 'Data' Object.
I need to do an ng-repeat for each 'Artist' in the correct order, however the second object is causing issues. How would I combine each Object together?
In my Factory, I set up a call to receive three response from three different API. I set a promise for each one so they come in a the exact order I call them.
FACTORY
.factory('timeline', function($http, $q) {
var promise1 = $http({
method: "GET",
url: "http://api.example.com/last/3/?limit=3"
});
var promise2 = $http({
method: "GET",
url: "http://api.example.com/current/3/"
});
var promise3 = $http({
method: "GET",
url: "http://api.example.com/next/3/?limit=3"
});
return {
data: $q.all([promise1, promise2, promise3])
}
})
In my controller, I get the response like so.
[
Object
config
data: [Array 3]
-0: Object
artist : 'Artist'
title : 'Title'
-1: Object
-2: Object
,
Object
config
data: Object
artist : 'Artist'
title : 'Title
,
Object
config
data: [Array 3]
-0: Object
artist : 'Artist'
title : 'Title'
-1: Object
-2: Object
]
CONTROLLER
My Attempt to filter using Underscore.
.controller('StationCtrl', function($scope, $stateParams, $http, timeline) {
timeline.data.then(function(musicData) {
var row = [];
for (var i = 0; i < musicData.length; i++) {
var data = _.filter(musicData[i].data, function(x){
row.push(x);
})
}
})
})
My Goal eventually if possible would be to combine everything in order
Object
data: [Array 7]
-0: Object
-1: Object
-2: Object
-3: Object
-4: Object
-5: Object
-6: Object
,
I am still trying to figure out how to work with Objects & Arrays, any help/tips would be great.

This is a simple approach of how you can solve your problem without underscore. You just need to check whether your data is an object or an array.
var arr = [
{ data: [{ artist: 'Artist' }, { artist: 'Artist2' }]},
{ data: { artist: 'Artist3' } },
{ data: [{ artist: 'Artist4' }]}
];
var flattened = [];
arr.forEach(function (el) {
if(Array.isArray(el.data)) {
flattened = flattened.concat(el.data);
} else {
flattened.push(el.data);
}
});
See example on jsbin.

Ideally, I think you should send an array only for 2nd object with its length as 1. If the API is not in your control i.e. 3rd party or anything else then we can look forward to solve the issue in other way.

You could strip out the underscore and just do a nested for:
.controller('StationCtrl', function($scope, $stateParams, $http, timeline) {
timeline.data.then(function(musicData) {
var row = [];
var dataElement;
var i;
var j;
for (i = 0; i < musicData.length; i++) {
dataElement = musicData[i].data;
if(typeof dataElement === 'object') {
row.push(dataElement)
} else if(typeof dataElement === 'array') {
for(j = 0; j < dataElement.length; j++) {
row.push(dataElement[j]);
}
}
}
})
})

Related

Recursive function in javascript that outputs JSON

In plain javascript, I am trying to create a function that will return a tree structure (json) of a folder, its subfolders and any files. I'm trying to achieve this using recursion.
The problem with the below code is that it stops after the first recursive call.
I know that in JS you do references, and I need to create a new object that I pass the values from the previous call to, but I am struggling to do so.
function fun(file, json) {
var tempJson = {
'name' : json.name || '',
'children' : obj.children || new Object()
};
if (file.type == 'file') {
tempJson.type = 'file';
tempJson.children = {}; // this will be empty, since there are no children
}
else {
tempJson.type = 'dir';
var listed = file.listFiles();
if (listed.length > 0) {
for each (var item in listed) {
tempJson.children = fun(item, tempJson);
}
} else {
tempJson.children = {};
}
}
return tempJson;
}
Example
From a directory structure like:
-root
--file1
--dir1
---file1.1
--dir2
I would like to get a json like:
{
name: 'root',
type: 'dir',
children : [
{
name: 'file1',
type: 'file',
children: {}
},
{
name: 'dir1',
type: 'dir',
children:
{
name: 'file1.1',
type: 'file',
children: {},
}
},
name: 'dir2',
type: 'dir',
children: {}
}
First call:
var object = new Object();
fun(rootdir, object);
Hope this makes sense.
Thanks!
As pointed out in the comments, children should be an array:
function fun(entry) {
var entryObj = { // construct the object for this entry
name: entry.name || "",
type: entry.type, // put the type here instead of using an if
children: [] // children must be an array
};
if(entry.type === "dir") { // if this entry is a directory
var childEntries = entry.listFiles(); // get its child entries
for(var childEntry of childEntries) { // and for each one of them
entryObj.children.push(fun(childEntry)); // add the result of the call of 'fun' on them to the children array
}
}
return entryObj;
}
Then call it like so:
var tree = fun(rootEntry);

filtering json result in js

filter data from result of json call
I am trying to do pre-processing JSON data before feed it to the dataTables. the reason is I need to separate the source data into 3 datatables. i can do it in the server-side in java but that approach is undesirable.
Please don't ask why, it is just due to load balancing calculation.
type: "GET",
dataType: 'json',
contentType: 'application/json',
success: function (data) {
console.log(data);
var table = $('#tbl1');
var tableUsul = $('#tbl1Terima');
var tableTolak = $('#tbl2Tolak');
table.DataTable({
"data": data.object, // i want to maipulate this
"processing": true,
"destroy": true,
"pagingType": "numbers",
"columns": [
{"data": null, "class": "rowCenter"},
{"data": "0"},
{"data": "1"},
{"data": "2"}
........
console.log(data.object) result
[
[1,"myname1","1000"],
[0,"myname2","0"],
[1,"myname5","12121"],
[1,"myname2","23455"]
]
i want to filter data.object. so in the database, the 1st column consists of 1 and 0, I want to show only 1 or only 0.
i was trying to use data.filter(function(){}) but the js does not recognize the function filter.
you can try something like below.
var data = [
[1,"myname1","1000"],
[0,"myname2","0"],
[1,"myname5","12121"],
[1,"myname2","23455"]
];
var newArray = [];
for (i = 0; i < data.length; i++) {
var element = data[i];
if(element[0] == 1)
{
newArray.push(element);
}
}
console.log(newArray)
You can try the following:
let data = [[1,"myname1","1000"],[0,"myname2","0"],[1,"myname5","12121"],[1,"myname2","23455"]];
function group2DArrayDataByIndex(dataArr, groupByIndex) {
let groupedData = dataArr.reduce(function (acc, arr) {
if (Array.isArray(arr) && (arr[groupByIndex] !== undefined || arr[groupByIndex] !== null)) {
let key = arr[groupByIndex];
if (Array.isArray(acc[key])) {
acc[key].push(arr);
} else {
acc[key] = [arr];
}
}
return acc;
}, {});
return groupedData;
}
let groupByIndex = 0;
let groupedData = group2DArrayDataByIndex(data, groupByIndex);
console.log(groupedData);
console.log(groupedData[0]); //access data with only 0
console.log(groupedData[1]); //access data with only 1
What it does: This code groups the data (Array of Arrays) based on the supplied index to be used to group data. All the grouped data is stored in an object such that you can access any data without the need to group/filter again.

How to combine two values of same property in object by using comma separator?

I have scenario to combine the values of same property with seperator. Is it possible. My json structure is like this:
{
"name": "aa",
"name": "bb",
"name1": "cc",
"name2": "dd"
}
I want to display the value of key (name) as aa, bb.
You can modify the json to get required solution
var obj = [
{ key : 'name' , value : 'foo' },
{ key : 'name' , value : 'bar' },
{ key : 'name1', value : 'baz' }
];
values = [];
for (var i = 0; i < obj.length; i++)
{
if (obj[i].key === 'name')
{
values.push(obj[i].value);
}
}
console.log(values.join());
You are using the same key name in your JSON.
This does not make sense. Instead you can try this approach:
Create unique keys in your JSON, like,
var mockDataForThisTest = "json=" + encodeURI(JSON.stringify([
{
"name":"aa",
"name0":"bb",
"name1":"cc",
"name2":"dd"},
]));
Then you need to load the JSON in a $scope variable from your controller
function nameCtrl($scope, $http) {
$scope.names= [];
$scope.loadNames = function() {
var httpRequest = $http({
method: 'POST',
url: '/echo/json/',
data: mockDataForThisTest
}).success(function(data, status) {
$scope.names= data;
});
};
}
Then in the html page,
<div ng-repeat="name in names">
{{name.name}}
<span>,</span>
{{name.name0}}
<span>,</span>
{{name.name1}}
<span>,</span>
{{name.name2}}
</div>
Hope it helps!
Create filter like this
app.filter('join', function () {
return function join(array, separator, prop) {
if (!Array.isArray(array)) {
return array; // if not array return original - can also throw error
}
return (!!prop ? array.map(function (item) {
return item[prop];
}) : array).join(separator);
};
});
Use in template like this
<span>{{var1 | var2 | join:', ' }}</span>
Creating filter enables you to use it any times. You will have to just place variables that you want to join by separator in place of var1 and var2.
Regards.

How can I pass an data from factory to my controller in angularjs?

I'm trying to move all the business logic from my controller to the factory, but I'm having some trouble passing fields data.
factory.js
app.factory("Quote", function ($resource) {
// TODO: this shouldn't start with /en/
var quoteStatus = [];
var quoteLanguage = [];
var Quote = $resource("/en/quote/api/quote/:id", {}, {
retrieve: {
method: 'GET',
params: {},
isArray: true
},
query: {
method: 'GET',
params: {},
isArray: true,
url: '/en/quote/api/quote/'
},
fields: {
method: 'GET',
url: '/en/quote/api/quote/fields/ '
},
update: {
method: 'PATCH',
},
});
Quote.fields().$promise.then(function (fields) {
var tempObj = [];
for (key in fields.status) {
// must create a temp object to set the key using a variable
tempObj[key] = fields.status[key];
quoteStatus.push({
value: key,
text: tempObj[key]
});
}
for (key in fields.language) {
// must create a temp object to set the key using a variable
tempObj[key] = fields.language[key];
quoteLanguage.push({
value: key,
text: tempObj[key]
});
}
//$scope.addLanguage($scope.language);
Quote.status = quoteStatus;
Quote.language = quoteLanguage;
});
return Quote;
});
controller.js
$scope.quoteStatus = Quote.status;
However this is not working since $scope.quoteStatus is undefined. What am I missing?
Thanks in advance.
You can't expect async operation to behave in synchronous way.
Basically when controller inject Quote in its factory function that time Quote service object gets created & then calls Quote.fields(). hen you ask Quote.status inside a controller will always return undefined. You are not maintaining promise anywhere so that controller will come to know that the data is ready or not.
I think you should introduce $q.when flag there to check the Quote.fields() operation completed or not & then do get the desired variable there.
For implementing above mention thing you need to store the promise of Quote.fields() call somewhere in service. like below
var quoteFieldsPromise = Quote.fields().$promise.then(function (fields) {
/// the code will be same here
};
Then add new method which will hold of quoteFieldsPromise promise object and return the value of quoteStatus & quoteLanguage.
var getQuoteDetails = function(){
$q.when(quoteFieldsPromise).then(function(){
return { quoteStatus: Quote.quoteStatus, quoteLanguage: Quote.quoteLanguage };
})
}
But the way you have returned whole Quote object, which only has $resource object which needs to be changed. I mean to say that the getQuoteDetails method which I've created can not be return with Quote object. So I'd rather rather refactor service to below.
Service
app.factory("Quote", function($resource, $q) {
// TODO: this shouldn't start with /en/
var quoteStatus = [], //kept private if needed
quoteFieldsPromise,
quoteLanguage = [];//kept private if needed
var QuoteApi = $resource("/en/quote/api/quote/:id", {}, {
//inner code is as is
});
//preserve promise of .fields() call
quoteFieldsPromise = Quote.fields().$promise.then(function(fields) {
//inner code is as is
//below lines are only changed.
Quote.status = quoteStatus;
Quote.language = quoteLanguage;
});
var getQuoteDetails = function() {
return $q.when(quoteFieldsPromise).then(function() {
return {
quoteStatus: quoteStatus,
quoteLanguage: quoteLanguage
};
})
};
return {
QuoteApi: QuoteApi,
getQuoteDetails: getQuoteDetails
};
});
Controller
Quote.getQuoteDetails().then(function(quoteDetails){
$scope.quoteStatus = quoteDetails.quoteStatus;
$scope.quoteStatus = quoteDetails.quoteLanguage;
});

Build a JSON object from absolute filepaths

I receive (in my angularjs application) from a server a list of directories like this:
['.trash-user',
'cats',
'cats/css',
'cats/images/blog',
'cats/images/gallery']
And I would like to build a javascript variable which looks like this:
[{
label: '.trash-user'},
{label: 'cats',
children: [{
label: 'css'},
{label: 'images',
children: [{
label: 'blog'},
{label: 'gallery'}
]}
]}
}]
The paths are in random order.
Hope somebody has some really elegant solution, but any solution is appreciated!
Edit:
Here is my naive approach, I have real trouble with recursion.
I could only make level 0 to work:
var generateTree = function(filetree){
console.log('--------- filetree -------');
var model = [];
var paths = [];
for(var i=0;i<filetree.length;i++) {
paths = filetree[i].split('/');
for(var j=0;j<paths.length;++j) {
var property = false;
for(var k=0;k<model.length;++k) {
if (model[k].hasOwnProperty('label') &&
model[k].label === paths[0]) {
property = true;
}
}
if (!property) {
model.push({label: paths[0]});
}
}
}
console.log(model);
};
If you want an elegant solution, lets start with a more elegant output:
{
'.trash-user': {},
'cats': {
'css': {},
'images': {
'blog': {},
'gallery': {},
},
},
}
Objects are much better than arrays for storing unique keys and much faster too (order 1 instead of order n). To get the above output, do:
var obj = {};
src.forEach(p => p.split('/').reduce((o,name) => o[name] = o[name] || {}, obj));
or in pre-ES6 JavaScript:
var obj = {};
src.forEach(function(p) {
return p.split('/').reduce(function(o,name) {
return o[name] = o[name] || {};
}, obj);
});
Now you have a natural object tree which can easily be mapped to anything you want. For your desired output, do:
var convert = obj => Object.keys(obj).map(key => Object.keys(obj[key]).length?
{ label: key, children: convert(obj[key]) } : { label: key });
var arr = convert(obj);
or in pre-ES6 JavaScript:
function convert(obj) {
return Object.keys(obj).map(function(key) {
return Object.keys(obj[key]).length?
{ label: key, children: convert(obj[key])} : { label: key };
});
}
var arr = convert(obj);
I'll venture that generating the natural tree first and then converting to the array will scale better than any algorithm working on arrays directly, because of the faster look-up and the natural impedance match between objects and file trees.
JSFiddles: ES6 (e.g. Firefox), non-ES6.
Something like this should work:
function pathsToObject(paths) {
var result = [ ];
// Iterate through the original list, spliting up each path
// and passing it to our recursive processing function
paths.forEach(function(path) {
path = path.split('/');
buildFromSegments(result, path);
});
return result;
// Processes each path recursively, one segment at a time
function buildFromSegments(scope, pathSegments) {
// Remove the first segment from the path
var current = pathSegments.shift();
// See if that segment already exists in the current scope
var found = findInScope(scope, current);
// If we did not find a match, create the new object for
// this path segment
if (! found) {
scope.push(found = {
label: current
});
}
// If there are still path segments left, we need to create
// a children array (if we haven't already) and recurse further
if (pathSegments.length) {
found.children = found.children || [ ];
buildFromSegments(found.children, pathSegments);
}
}
// Attempts to find a ptah segment in the current scope
function findInScope(scope, find) {
for (var i = 0; i < scope.length; i++) {
if (scope[i].label === find) {
return scope[i];
}
}
}
}

Categories

Resources