I have JSON string of Nested Arrays with unequal length elements as shown below , I want to display it in a page using Javascript or Angular.js.
{ [
'id':1,
'value':'tes1'
'nextLevel':['id':12,
'value':'tes12'
'nextLevel':['id':13,
'value':'tes13',
nextLevel:null ],
]
],
['id':2,
'value':'tes2'
'nextLevel':null ]
]
here nextLevel can be null or it can have another array nested, nested array in turn will have another nested array.
I want to display all array elements into a list, with Parent list element have link to child list Elements, until no child element found.
can somebody post the steps on how to perform this task? As I am able to find example for equal length array, but no example can be found for this nested case.
I think this would need what is considered a recursive function. Recursion (JavaScript)
Each time through the method, it checks to see if the next level is null, and if it isn't it "recursively" calls itself, passing the next level.
http://jsfiddle.net/k8Lratja/
var ary = {
'id': 1,
'value': 'tes1',
'nextLevel': {
'id': 12,
'value': 'tes12',
'nextLevel': {
'id': 13,
'value': 'tes13',
'nextLevel': null
}
}
};
function objectDisplay(objectIn) {
document.write(objectIn.id + ' ' + objectIn.value + "<br />");
if (objectIn.nextLevel !== null) {
objectDisplay(objectIn.nextLevel);
}
}
objectDisplay(ary);
Angular
example
<div class="ctrl" ng-app="app" ng-controller="ctrl" >
<div ng-repeat="idLevel in data">
<div>
{{idLevel.id}} = {{idLevel.value}}
<div ng-repeat="nextLevel1 in idLevel.nextLevel" style="margin-left: 20px;">
{{nextLevel1.id}}={{nextLevel1.value}}
<div ng-repeat="nextLevel2 in nextLevel1.nextLevel" style="margin-left: 20px;">
{{nextLevel2.id}}={{nextLevel2.value}}
<div ng-repeat="nextLevel3Item in nextLevel2.nextLevel" style="margin-left: 20px;">
{{nextLevel3Item.id}}={{nextLevel3Item.value}}
</div>
</div>
</div>
</div>
</div>
</div>
Related
I'm having trouble making proper table with nested ng-repeat.
What I wanted is this https://jsbin.com/razamagabo/1/edit?output
but I'm stuck at here https://plnkr.co/edit/d5voXIpzYL81sSl9BSY2?p=preview
I don't mind my markup is not table but I'm still stuck with div
<div class="row">
<div class="col-xs-6" ng-repeat="obj in data">
{{obj.date}}
<div ng-repeat="user in obj.users">
<br>
{{user.name}}
<br>
{{user.mark}}
</div>
</div>
</div>
In order for you to be able to display your data in the desired way, it will probably be easiest if you restructure your data in the JS before trying to render it.
It will be very complicated to try and match on the user names when they are in separate objects in the data array.
I would suggest processing your scope.data in the controller. (I'm assuming that you don't have much control on how you are receiving the data).
For example after you get your data...
$scope.data = [
{
date:'1-1-2016',
users:[
{
'name':'james',
'mark':18
},
{
'name':'alice',
'mark':20
}
]
},
{
date:'2-1-2016',
users:[
{
'name':'james',
'mark':60
},
{
'name':'alice',
'mark':55
}
]
}
]
var userData = {};
var possibleDates = [];
for (dataObj of Object.entries($scope.data)) {
for (userObj of dataObj) {
if ( !userData[userObj.name] ) {
userData[userObj.name] = {};
}
userData[userObj.name][dataObj.date] = userObj.mark;
if (dates.indexOf(dataObj.date) < 0) {
dates.push(dataObj.date);
}
}
}
$scope.users = userData;
$scope.dates = possibleDates;
this will give you an object like this on your scope
$scope.users = {
'james': {
'1-1-2016': 18,
'2-1-2016': 60
},
'alice': {
'1-1-2016': 20,
'2-1-2016': 55
}
};
$scope.dates = ['1-1-2016', '2-1-2016'];
This to me seems easier to structure for your template. Though this assumes each user has an entry for each date.
<div>
<div id='header-row'>
<div id='empty-corner></div>
<div class='date-header' ng-repeat='date in $scope.dates></div>
</div>
<div class='table-row' ng-repeat='{key, value} in $scope.users'>
<div class='user-name'>{{ key }}</div>
<div class='user-data' ng-repeat='date in $scope.dates>
{{ value[date] }}
</div>
</div>
</div>
As long as you apply inline-block styles to the rows/elements this should give you what you are looking for.
Though you can also think of ways to simplify your data even further. You could instead of having each user have an object where the dates are keys, you could just push the values into an array.
With your current data structure it is not possible to display it like you want. You are trying to loop over date-users objects in data array but then you want to display user from inside users array in separate rows. With ng-repeat you can loop through rows tr but not through columns. First you would need to map your data array to group elements that are supposed to be visible in 1 row into 1 object in array. Currently you have them in 2 separate objects:
James mark: 18 and James mark: 60.
I'm currently working on a game and am trying to use NG repeat to dynamically add divs depending on how many latter and words there are. so for example if the answer to a queston in the game was "Clean Sheet" then i would want NG-Repeat to make the correct amount of columns so that it would say __ __ __ __ __ space __ __ __ __ __ then the user can fill them. Ive currently wrote this :
try{
//in this example presume $stateParams.answer is "clean sheet"
var answerArr = $stateParams.answer.toString().split(' ');
var finalLines = "";
$scope.mainWordHolder = [];
angular.forEach(answerArr, function(value, key) {
var amt = value.length;
$scope.amtofLetters=[];
for (var i=0; i<amt; i++) {
$scope.amtofLetters.push(i);
}
$scope.mainWordHolder.push($scope.amtofLetters);
$scope.amtofLetters = [];
});
console.log($scope.mainWordHolder);
}catch(e){console.log("error : "+ e);}
at this point my $scope.mainWordHolder is :
[ [ 0, 1, 2, 3, 4 ], [ 0, 1, 2, 3, 4 ] ]
which is exactly what i want so i know how many letters are needed for each word. How can i use ng-repeat to show this as divs so that i can create a keyboard and the user can enter keys like other popular games.
Ive tried this :
<div class="row">
<div ng-repeat="content in answerArr" class="col">
<div ng-repeat="contentt in mainWordHolder" class="col">
</div>
</div>
</div>
but i get nothing for some reason. Any help appreciated.
It looks like you're trying to reference answerArr in your ng-repeat, but answerArr is not stored on the scope.
When you use ng-repeat="content in answerArr", answerArr needs to be stored on the scope to be accessible in the HTML.
Try storing answerArr on your scope to be able to access it's contents via ng-repeat.
You need to iterate over the 'content' which represents the array in the whole array (the outer). Try this :
<div class="row">
<div ng-repeat="content in answerArr" class="col">
<div ng-repeat="contentt in content" class="col">
</div>
</div>
</div>
I currently have a basic ng-repeat within my template that iterates over an object that I have. If my object only has a length of 2, I need to then run another ng-repeat to make sure that I always have at least 6 elements on my page. The main ng-repeat has the class of main, along with the 'dummy' ng-repeat having the class of placeholder.
My object:
$scope.myObj = [{
id: 0
}, {
id: 1
}];
My basic repeat:
<div class="main" ng-repeat="item in myObj"></div>
I need to run another ng-repeat to somehow loop out another 4 times - since the above repeat has already looped twice, hence needing 6 elements altogether.
Could I use the length of myObj to somehow achieve this?
Psudo code:
<div class="placeholder" ng-repeat="item in (6 - myObj.length)"></div>
As per #elclanrs comment, I solved this issue by returning a dynamic array from within the controller:
My repeat:
<div ng-repeat="item in myDynamicArray()"></div>
My controller function:
$scope.myDynamicArray = function() {
var thisArr = [];
for (var i = 0; i < (6 - $scope.myObj.length); i++) {
thisArr.push(i);
}
return thisArr;
};
You can also use a flag variable to check for the length.
MyController.js
$scope.isLess = true;
$scope.myObj = [{
id: 0
}, {
id: 1
}];
$scope.myObj1 = [];
if ($scope.myObj.length < 6) {
for (var i = $scope.myObj.length; i < 6; i++) {
$scope.myObj1.push({
id: i
})
}
} else {
$scope.isLess = false;
}
index.html
<p>Length of the object is {{ myObj.length }}</p>
<div class="main" ng-repeat="item in myObj">
<p> Id values are : {{item.id}}</p>
</div>
<div class="placeholder" ng-if="isLess" ng-repeat="item in myObj1">
<p> Id values are : {{item.id}}</p>
</div>
Working Plunker URL:
https://plnkr.co/edit/mEm46n0Tpsa0bgo73sHp
So the question is how can I keep the HTML elements in sync while I add and delete from an existing array.
If I have an array of javascript objects say element 1 is:
{
"firstName": "John",
"address": {
"streetAddress": "21 2nd Street",
"city": "New York",
"state": "NY",
"postalCode": "10021-3100"
},
"phoneNumbers": [
{
"type": "home",
"number": "212 555-1234"
},
{
"type": "office",
"number": "646 555-4567"
}
....
]
.... ETC ETC COMPLICATED ....
}
Then my initial html might be generated by using the array index for each object of initial size 3 elements [ {}, {}, {} ]:
<div id="arrayPos-0">
<div>John</div>
<input>PROCESS<input>
<input>DELETE<input>
</div>
<div id="arrayPos-1">
<div>Sam</div>
<input>PROCESS<input>
<input>DELETE<input>
</div>
<div id="arrayPos-2">
<div>Timmy</div>
<input>PROCESS<input>
<input>DELETE<input>
</div>
If I add to the Javascript Array, and I expect to increment it and become [ {}, {}, {}, {} ]:
<div id="arrayPos-0">
...
</div>
<div id="arrayPos-1">
...
</div>
<div id="arrayPos-2">
...
</div>
<div id="arrayPos-3">
<div>Simone</div>
<input>PROCESS<input>
<input>DELETE<input>
</div>
However as soon as I delete from the javascript array say index 2 (arrayPos-2) I get the following HTML:
<div id="arrayPos-0">
...
</div>
<div id="arrayPos-1">
...
</div>
<div id="arrayPos-3">
...
</div>
This is all messed up and I'm unable to match "arrayPos-3" to now index 2. In addition when I add a new javascript object which is index 3:
<div id="arrayPos-0">
...
</div>
<div id="arrayPos-1">
...
</div>
<div id="arrayPos-3">
...
</div>
<div id="arrayPos-3">
...
</div>
I am not able to use AngularJS and hence ng-repeat can't be used due to support needed on older browsers. It would be pretty simple to use an observer to bind the javascript objects directly to the html markup.
I can only use jQuery and regular javascript.
But surely this can be solved in a simpler manner? Do I even need to bind by using IDs? Do I need to autogenerate GUIDs for IDs and use a dictionary to match ID with Javascript object index (I hope not)?
The simplest way is to have a generator function which will regenerate the HTML after any changes have been made (adding/deleting/editing).
That way you won't have any confusions with IDs and will always have your current JS object represented in the DOM. That's quite the "Angularish" way to do it - building the DOM from the JS data without really caring what's in the DOM.
Here's a super-simple example to see what I meant:
var data = [
{name: 'John'},
{name: 'Shomz'},
{name: 'Jimmy'}
]
var c = document.getElementById('c');
function build() {
var output = "";
for(var i = 0; i < data.length; i++) {
output += '<div id="arrayPos-' + i + '"><button onclick="del(' + i + ')">Del</button><button onclick="edit(' + i + ')">Edit</button>' + data[i].name + '</div>';
}
c.innerHTML = output;
}
function del(id) {
data.splice(id ,1);
document.getElementById('arrayPos-' + id).className = 'del';
setTimeout(build, 200);
}
function add() {
data.push({name: prompt('Enter name')});
build();
}
function edit(id) {
data[id].name = prompt('Enter name', data[id].name);
build();
}
build();
#arrayPos-0 {color: green}
button {margin: 4px}
#c div {opacity: 1; transition: all 0.2s linear}
#c div:hover {background: #eee}
#c div.del {opacity: 0}
<div id="c"></div>
<br>
<button onclick="add()">Add</button>
You could use JQuery's .attr() method to dynamically set the id property when looping through the objects in the array - if that's the approach you want to take. You could also look at dynamically setting the inner html using JQuery's .html() to set the contents of the divs. If you review traversing the DOM using JQuery it will help with the latter approach. Hope that helps -
If you want to keep it this way and keep them in order then you caan create a function that:
1.deletes the current divs
2. Re generates the divs in the new order with the corresponding new id's
And just have this function fire every time you add or delete something from the array. It's relatively simple and wont take all that long for javascript to do.
Or:
Create a for loop that loops through the array and identifies which position(number of loops) the object with "x" name is in the array and assigns it to the div.
You can use the jQuery .index() method to get the index of the element relative to its siblings. With that, you can match it to the index in the array. No need for adding id's to the elements.
Let's say you already have an index and you want to access the dom element. You can do this:
$('.class-name').get(index).remove();
Or if you're responding to some event and you're not sure which element in the array it corresponds to, you can do this:
$('.class-name').click(function(e){
var index = $('.class-name').index(e.target);
});
In my example I'm using a click event, but it can be any other way.
In-case, if you can modify your existing js object, I suggest to use id for both html element and the js object.
Probably you could go for something:
var id = Math.random().toString(16).slice(-6); // for random id, aplha-numeric
Your html:
<div id="23xc45">
...
</div>
<div id="cd567u">
...
</div>
Your js object:
[{
"id" : "23xc45",
"firstName": "John",
...
},{
"id" : "cd567u",
"firstName": "Sam",
...
}]
I have an issue when trying to delete an object from an array using splice. I have an array that is dynamically created through a UI and stored in a scope variable called $scope.productAttributes.Products. This is an example of what it looks like...
[
{
"ProductLabel":"Net",
"Code":"ela",
"Site":"SITE1"
},
{
"ProductLabel":"Link",
"Code":"eli",
"Site":"SITE1"
},
{
"ProductLabel":"24-port managed PoE switch",
"Code":"24p",
"Site":"SITE2"
},
{
"ProductLabel":"Dedicated Firewall",
"Code":"ded",
"Site":"SITE2"
},
{
"ProductLabel":"Link",
"Code":"eli",
"Site":"SITE3"
},
{
"ProductLabel":"IPv4 Addresses",
"Code":"ip4",
"Site":"SITE3"
}
]
I then display that array in an angular repeater and group it by 'site' (which might be part of the problem)...
<div ng-repeat="(key, value) in productAttributes.Products | groupBy: 'Site'">
<strong>{{key}}</strong>
<div ng-repeat="site in value">
<h4>{{site.ProductLabel}}</h4>
<sapn href="" ng-click="deleteItem($index)" class="text-danger">Remove {{site.ProductLabel}}</span>
</div>
</div>
</div>
On the delete button I pass in the index of the object and use the splice function...
$scope.deleteItem = function (index) {
$scope.productAttributes.Products.splice(index, 1);
};
So the issue is that the $index is always zero (I noticed this from a console.log) as I mentioned that I think it might be down to the groupBy but I am not sure. anyone know whats going wrong? Thanks
UPDATE:
It would seem the problem is with the $index in the nested repeater. So if the json above the structure would be...
SITE1:
Product: Net - $index: 0
Product: Link - $index: 1
SITE2:
Product: 24-port - $index: 0
Product: Dedicated - $index: 1
SITE3:
Product: Link - $index: 0
Product: IPV4 - $index: 1
So if I try to delete the IPV4 product in SITE3, it removes the LINK product in Site1 as it has the same $index. any ideas how I can fix that?
We can not rely on $index as it does not contain the updated value after you remove an item from array.
Pass the object dynamically from UI and delete it from model using below code:
In Html:
<div ng-repeat="(key, value) in productAttributes.Products | groupBy: 'Site'">
<strong>{{key}}</strong>
<div ng-repeat="site in value">
<h4>{{site.ProductLabel}}</h4>
<sapn href="" ng-click="deleteItem(site)" class="text-danger">Remove {{site.ProductLabel}}</span>
</div>
</div>
</div>
In JavaScript:
$scope.productAttributes.Products.splice
($scope.productAttributes.Products.indexOf(site), 1);
This causes model to update with updates values in repeater and re-renders it on UI.
OK - I ended up doing it this way and it seems to work
$scope.deleteItem = function (item) {
var index = $scope.productAttributes.Products.indexOf(item);
$scope.productAttributes.Products.splice(index, 1);
};
So passing in the whole object seems to have worked. I'm not sure why.