Populate JSON values in multiple tables With Knockout JS - json

I have a bootstrap accordion that obtains it's header info from JSON, within each accordion pane I have a table, and the information for each table also get's populated with JSON.
The issue I have is that the all of the table data populates within the first accordion pane.
It does not move on to the secondary table and populate the information in there, my JSON data does include ID's so it is possible to navigate between the items just not sure how.
Here is Some of the code:
<div class="well">
</div>
<div data-bind="attr: {id: 'collapse'+$index()}" class="accordion-body collapse">
<div class="accordion-inner">
<div id="injectbody">
<table class="table table-striped">
<thead>
<tr>
<th>ContentID</th>
<th>Content</th>
<th>Qty To Assign</th>
</tr>
</thead>
<tbody data-bind="foreach: Locations">
<tr>
<td id="Lot" data-bind="text: ContentID"></td>
<td id="Area" data-bind="text: Content"></td>
<td id="QtyToAssign">
<input type="text" />
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
And the JQuery to make it all work:
var data = {
"d": [{
"__type": "Sample",
"ItemID": "1",
"ItemName": "First Item",
"QtyUnassigned": 10
}, {
"__type": "Sample",
"ItemID": "2",
"ItemName": "Second Item",
"QtyUnassigned": 15
}]
};
var data2 = {
"d": [{
"__type": "Sample2",
"ItemID": 1,
"ContentID": "1",
"Content": "This Is The First Item Content"
}, {
"__type": "Sample2",
"ItemID": 2,
"ContentID": "2",
"Content": "This Is The Second Item Content"
}]
};
var ProductViewmodel;
//debugger;
//Binds ViewModel
function bindProductModel(Products) {
var self = this;
self.items = ko.mapping.fromJS([]);
ProductViewmodel = ko.mapping.fromJS(Products.d, self.items);
console.log(ProductViewmodel());
}
//Binds First DataSet
function bindModel(vm, data) {
var self = this;
vm.Locations = ko.mapping.fromJS(data.d);
console.log(ProductViewmodel());
}
//Starting Function
$(document).ready(function () {
bindProductModel(data);
bindModel(ProductViewmodel()[0], data2);
ko.applyBindings(ProductViewmodel);
});
I have also created this Fiddle to demonstrate what I am trying to get to.

Your error is that since your ViewModel is actually an array, you are only binding Locations to the first element of your variable ProductViewmodel here.
bindModel(ProductViewmodel()[0], data2);
This means you have something like...
[0].Locations = [],
[1].Locations = undefined
Thus throwing an error when binding your markup (see the console in your fiddle).
In a related note, your variable naming is extremely misleading. ProductViewmodel is an array, yet you name it as ViewModel and you applyBindings to it.
I would recommend you to give Learn KnockoutJS a review. Also, stick to conventions, when variable naming pick camelCase, or underscore_case, or PascalCase or something, but just don't mix them. Finally, if you have functions that do something only applicable for specific objects, try to use a better name other than bindModel like bindLocationsToModel.

Related

How can I create a table of variable height in which every single cell contains an input field?

As I said in the title, I need to create a table in which every cell has a numeric input. So I created a component and I want to capture in the ts file what the user inserted in every cell (I also need to know in which cell he inserted the data too). In addition, the table should have a scalable number of rows(I need to populate the table with data from db, so please let me know if there is a way to precompile it)
Here I have added the html I tried even though it's not enough to do what I want to do.
<form #login="ngForm" (ngSubmit)="submit(login.value)" novalidate>
<table class="rtable">
<tr>
<th></th>
<th *ngFor="let column of months">
{{column}}
</th>
</tr>
<tr *ngFor="let row of FigProfs">
{{row.nome}}
<td *ngFor="let column of mesi">
<input type="number" #{{column}}_{{row}}="ngModel" required name="{{column}}_{{row}}" class="formControl" ngModel>
</td>
</tr>
</table>
<button class="btnsubmit" type="submit">GO</button>
This table has months in the heading and some characteristics on the left side. I need to let the user edit the cell of the table and I have to bring this information to my ts code, example:
"january/characteristic x -> data written from the user " ;
so I want to have in my ts all the data that I inserted down here (the heigth of the table is variable so I cannot insert form controls for the many inputs I have in the pic rn)
I know this code is probably completely wrong but I don't know how to do it in an easier way.
Thank you in advice for your help, I really appreciate it.
Well done. Thank you for adding some code. For reference, I really just used information from Angular's Reactive Forms below.
I did want to re-use some of your sample code, but unfortunately couldn't get it to adapt in a logical way. But you should be able to use the below to get it adapted as you need it.
My assumption is that you are using the Angular CLI to generate your components, and thus your .ts and .html are already referenced and created properly. In your something.component.ts, you will need to add something similar to:
import { FormGroup, FormControl } from '#angular/forms';
import { Component, OnInit } from '#angular/core';
//Create an interface for the data
interface testIntf {
index: string,
fname: string,
lname: string,
}
...skipping the existing stuff...
export class SomethingComponent implements OnInit {
//Some sample data for headings
headings = ["Index", "First name", "Surname"];
//Some sample data for the form to be pre-filled. Note the use of the interface. Going forward you will likely import your data using Observables.
dataArray: testIntf[] = [
{"index": "1", "fname": "Test", "lname": "One"},
{"index": "2", "fname": "Tester", "lname": "Two"},
{"index": "3", "fname": "", "lname": ""},
{"index": "4", "fname": "", "lname": ""},
]
//Your form related stuff!!
//Note where this matches in the html
//Create an empty form
dataForm = new FormGroup({});
//Note that ngOnInit must be imported and implemented above
ngOnInit(): void {
this.generateControls(this.dataArray);
}
//Add controls based on number of entries in array
generateControls(x: testIntf[]) {
var y = x.length
for (let i = 0; i < y; i++){
var z = x[i].index;
this.dataForm.addControl("firstName"+z, new FormControl);
this.dataForm.addControl("lastName"+z, new FormControl);
}
}
//Getting your form data when submitted
onSubmit() {
console.log(this.dataForm.value); //Push the form data into console.log of your browser
}
}
And now in your something.component.html:
<form [formGroup]="dataForm" (ngSubmit)="onSubmit()">
<table class="rtable">
<tr>
<th *ngFor="let column of headings"> <!--Processs the "headings" array in the ts file-->
{{column}}
</th>
</tr>
<tr *ngFor="let row of dataArray">
<td>
{{row.index}}
</td>
<td>
<input id="first-name{{row.index}}" type="text" formControlName="firstName{{row.index}}" value="{{row.fname}}">
</td>
<td>
<input id="last-name{{row.index}}" type="text" formControlName="lastName{{row.index}}" value="{{row.lname}}">
</td>
</tr>
</table>
<button type="submit">Submit</button>
</form>
<p>Form Status: {{ dataForm.status }}</p> <!--Echo form data locally-->>
The information from your database can easily replace the "dataArray". Just update your headings, and add the necessary extra input fields to "dataForm".
Once you are comfortable with that, I would encourage you to look at Angular's Material for tables.
EDIT
Going forward, should you want to add more fields, you would edit the following (I have used "telNo" as an example):
Add extra fields to the form in something.component.ts:
generateControls(x: testIntf[]) {
var y = x.length
for (let i = 0; i < y; i++){
var z = x[i].index;
this.dataForm.addControl("firstName"+z, new FormControl);
this.dataForm.addControl("lastName"+z, new FormControl);
this.dataForm.addControl("telNo"+z, new FormControl);
}
}
Add extra form field in your table in something.component.html:
<td>
<input id="tel-no{{row.index}}" type="text" formControlName="telNo{{row.index}}" value="{{row.telno}}">
</td>
Obviously your dataArray will also need to be updated to have a "telno" field:
{"index": "1", "fname": "Test", "lname": "One", "telno": "1234587"},
And do not forget to update your interface:
interface testIntf {
index: string,
fname: string,
lname: string,
telno: string,
}

Using ng-repeat with dynamic value

I have HTML that is rendered based on loaded metadata. It ends up forming a table of data based on a scoped value in the controller.
In the controller:
$scope.arrayOfCustomObjects = [{ entry: data1 },{ entry: data2 }];
The metadata is contained in a .json file, with the following format (I added two here, in reality there are hundreds, each one basically ends up describing an HTML element):
loadedMetaData:
{
"field_1": {
"index": 0,
"type": "selectbox",
"nameId": "arrayOfCustomObjects" <-- this is the string name of a scoped variable in a controller.
},
"field_2": {
"index": 1,
"type": "textinput",
"nameId": "textField"
}
}
And the HTML looks like this:
<div ng-repeat="field in loadedMetaData>
<div ng-repeat="item in field.nameId">
<!-- build out HTML for each -->
</div>
</div>
When I run this, it doesn't work (it never iterates over $scope.arrayOfCustomObjects). If I add a line to display {{field.nameId}} is displays 'arrayOfCustomObjects' but I think it's just the string, not the value it represents.
If I change the HTML to this it does work:
<div ng-repeat="field in loadedMetaData>
<div ng-repeat="item in arrayOfCustomObjects">
<!-- build out HTML for each -->
</div>
</div>
...but is there any way to keep the abstraction I'm going for? I'd like to be able to define the target array in the metadata so I don't have to define the controller-specific names in the HTML itself.
Suppose that your loadedMetadata is like:
[
"arrayOfCustomObjects",
// other 'data' object keys
]
Then you could have an object like this that stores your actual data:
data = {
"arrayOfCustomObjects": [
// some items
]
}
Then, in the template you do:
<div ng-repeat="field in loadedMetaData>
<div ng-repeat="item in data[field]">
<!-- display the items in arrayOfCustomObjects -->
</div>
</div>
This takes the value as string
var field = { "nameId": "arrayOfCustomObjects" }
change this to
var field = { "nameId": arrayOfCustomObjects }
Use a bracket notation property accessor with the this keyword:
<div ng-repeat="(fieldKey, fieldValue) in loadedMetaData>
<div ng-repeat="item in this[fieldValue.nameId]">
<!-- build out HTML for each -->
</div>
</div>
From the Docs:
It is possible to access the context object [($scope)] using the identifier this and the locals object using the identifier $locals.
— AngularJS Developer Guide - Expression Context
To achieve expected result, use below changes from your plunker code
Using scope function to convert string to scope variable
Rendering that variable inside inner ng-repeat
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.arrayOfCustomObjects = [1,2,3]
$scope.loadedMetaData = {
"field_1": {
"index": 0,
"type": "selectbox",
"nameId": "arrayOfCustomObjects"
},
"field_2": {
"index": 1,
"type": "textinput",
"nameId": "textField"
}
}
$scope.getVariable = function(val){
return $scope[val]
}
});
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>
<body>
<div ng-app="myApp" ng-controller="myCtrl as ctrl">
<div>
<div ng-repeat="field in loadedMetaData">
<div ng-repeat="item in getVariable(field.nameId)">
{{item}}
</div>
</div>
</div>
</div>
</body>
</html>
codepen - https://codepen.io/nagasai/pen/XGLZzX

angular2 how to access a json child

I would like to access a json child using Angular2, here is what I tried:
my json :
orderList=
{
"ref1": {
"Id": "57987"
"order": [
{
"amount": 4900,
"parent": "CIDPT8AO"
}
]
}
}
in the view:
<tr *ngFor="let order of orderList">
<td>{{order[0]}}</td> // NOT OKAY
</tr>
I would like to access "Id" and "parent", any idea?
First of all: you're missing a , after the "Id" line.
You're trying to access a JSON object like an array. Not sure if that works with Angular... If the error is not solved by adding the , I mentioned above, try declaring your orderList as an array.
If you're bound to this format you could do it like that:
orderListValues = Object.keys(orderList).map(function(_) { return j[_]; })
Then you should be able to access the values like that:
<tr *ngFor="let order of orderListValues">
<td>{{order.Id}}</td>
</tr>
Example:
orderList = {
"ref1": {
"Id": "57987",
"order": [
{
"amount": 4900,
"parent": "CIDPT8AO"
}
]
}
};
orderListValues = Object.keys(orderList).map(function(_) { return orderList[_]; });
console.log(orderListValues);
Your orderList is an object, you need
<tr *ngFor="let order of orderList.ref1.order">
<td>{{order}}</td>
</tr>
You can try this approach :
HTML
<table>
<tr *ngFor="let order of obj">
<td>{{order.Id}}</td>
</tr>
</table>
Component
var orderList=
{
"ref1": {
"Id": "57987",
"order": [
{
"amount": 4900,
"parent": "CIDPT8AO"
}
]
}
};
var obj = orderList[Object.keys(orderList)[0]];
NgForOf provides several exported values that can be aliased to local variables, for example:
index: number -> The index of the current item in the iterable.
first: boolean -> True when the item is the first item in the iterable.
<tr *ngFor="let ref of orderList">
<td>{{order.id}}</td>
<span *ngFor="let order of ref.order; let first = isFirst">
<td *ngIf="isFirst">{{order.parent}}</td>
</span>
</tr>
https://angular.io/api/common/NgForOf

generating with angularJS HTML from JSON-Objects

I'm developing an app with the Ionic framework based on angularjs. I'd like to let generate HTML elements or components from a JSON file. These are buttons, lists, labels, etc.
My JSON objects look like this:
[
{
"category": "communicationPage",
"type": "Button",
"id": "communicationButton",
"icon": "ion-chatboxes",
"name": "Communication",
"onClick": "window.location.href='communicationPage.html'",
"ngclick": "open()",
"ngcontroller": "openctrl",
"color": "white",
"background-color": "#ff5db1",
"font-size": "20px"
},
{
"category": "servicePage",
"type": "Button",
"id": "serviceButton",
"icon": "ion-briefcase",
"name": "Service",
"onClick": "window.location.href='servicePage.html'",
"color": "blue",
"background-color": "#009900",
"font-size": "26px"
}
]
I can access via my Controller on the JSON file and parse as follows:
myApp.controller('generateHTMLCtrl', function ($scope, $http) {
$http.get('myJSONFile.json').success(function(data){
$scope.components = data;
//...
});
});
The code translates of course nothing.
My question is, how can I adapt my JavaScript code so that from a
JSON object following HTML element is generated?:
<button style="color: blue; background-color: #ff5db1; font-size: 20px" onclick="window.location.href='communicationPage.html'" id="communicationButton" class="button">
<i class="ion-chatboxes"></i> <br> Communication
</button>
Once my JSON object is located always in the JSON file, should always be created the HTML element on the page.
The second question is how I can position this generated HTML
element just in my HTML page?
I want that the HTML element is generated between the responsive grid element, such as:
<div class="row responsive-sm">
<div class="col">
<!-- The Button should be generated hier-->
</div>
</div>
The third and final question is how I can let generate the HTML
element on the appropriate page? Such as: If in JSON object the key-value pair of "category": "communicationPage" occurs should the corresponding HTML element be created on 'communicationPage.html'
I would look forward to an example. Many thanks.
For the two first point, use the data-binding and ng-repeat : directive
<div class="row reponsive-sm" ng-controller="generateHTMLCtrl">
<div ng-repeat="component in components">
<button style="color : {{component.color}}; background-color : {{component.background-color}} ... ">
</button>
</div>
</div>
For the last point, I'm not sure if it's possible with AngularJS ...

Angular ng-repeat with nested json objects?

I have a JSON object, represented as such:
{
"orders" : [
{
"ordernum" : "PRAAA000000177800601",
"buyer" : "Donna Heywood"
"parcels" : [
{
"upid" : "UPID567890123456",
"tpid" : "TPID789456789485"
},
{
"upid" : "UPID586905486090",
"tpid" : "TPID343454645455"
}
]
},
{
"ordernum" : "ORAAA000000367567345",
"buyer" : "Melanie Daniels"
"parcels" : [
{
"upid" : "UPID456547347776",
"tpid" : "TPID645896579688"
},
{
"upid" : "UPID768577673366",
"tpid" : "TPID784574333345"
}
]
}
]
}
I need to do a repeater on the second level of this, a list of the "upid" numbers.
I know already how to get the top level
<li ng-repeat="o in orders">{{o.ordernum}}</li>
But I am unclear on the sequence to loop a level down. For example, this is wrong:
<li ng-repeat="p in orders.parcels">{{p.upid}}</li>
I also know how to nest repeaters to get this, but in this case i don't need to display the top level at all.
CLARIFICATION
The goal here is to have one list with the 4 "upid" numbers (there are 2 for each parcel, and there are 2 parcels in the order).
Actually its same answer of #sylwester. The better way to put it in filter. And you can reuse it by passing propertyName parameter.
In your case we passed parcels
JS
myApp.filter('createarray', function () {
return function (value, propertyName) {
var arrayList = [];
angular.forEach(value, function (val) {
angular.forEach(val[propertyName], function (v) {
arrayList.push(v)
});
});
return arrayList;
}
});
HTML
<ul>
<li ng-repeat="o in ordersList.orders | createarray: 'parcels'">{{o.upid}}</li>
</ul>
Here is working Fiddle
You can just create new array 'parcels' like in demo below:
var app = angular.module('app', []);
app.controller('homeCtrl', function($scope) {
$scope.data = {
"orders": [{
"ordernum": "PRAAA000000177800601",
"buyer": "Donna Heywood",
"parcels": [{
"upid": "UPID567890123456",
"tpid": "TPID789456789485"
}, {
"upid": "UPID586905486090",
"tpid": "TPID343454645455"
}]
}, {
"ordernum": "ORAAA000000367567345",
"buyer": "Melanie Daniels",
"parcels": [{
"upid": "UPID456547347776",
"tpid": "TPID645896579688"
}, {
"upid": "UPID768577673366",
"tpid": "TPID784574333345"
}]
}]
};
$scope.parcels = [];
angular.forEach($scope.data.orders, function(order) {
angular.forEach(order.parcels, function(parcel) {
$scope.parcels.push(parcel)
})
})
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="homeCtrl">
<ul>
<li ng-repeat="o in parcels">{{o.upid}}</li>
</ul>
</div>
</div>
Seems like you just need a double-nested for loop -
<ul>
<div ng-repeat="o in orders">
<li ng-repeat="p in o.parcels">{{p.upid}}</li>
</div>
</ul>
The HTML might be a little ugly here, but I'm not sure what exactly you are going for. Alternatively you could just create a new array of the parcels via mapping.
Searching a lot for nice and simple solution for iterating dynamically. I came up with this
JAVASCRIPT (angular): a person is an example of nested object. the is_object function will be use in the HTML view.
$scope.person = {
"name": "john",
"properties": {
"age": 25,
"sex": "m"
},
"salary": 1000
}
// helper method to check if a field is a nested object
$scope.is_object = function (something) {
return typeof (something) == 'object' ? true : false;
};
HTML: define a template for simple table. the 1st TD is the key which is displayed. another TD (2 or 3, but never both) will be show the value if its not an object (number / string), OR loop again if its an object.
<table border="1">
<tr ng-repeat="(k,v) in person">
<td> {{ k }} </td>
<td ng-if="is_object(v) == false"> {{ v }} </td>
<td ng-if="is_object(v)">
<table border="1">
<tr ng-repeat="(k2,v2) in v">
<td> {{ k2 }} </td>
<td> {{ v2 }} </td>
</tr>
</table>
</td>
</tr>
</table>
The reason that <li ng-repeat="p in orders.parcels">{{p.upid}}</li> does not work the way you expect is because the parcels array is an object inside each individual order in your order array, i.e. it is not an object of the orders array itself.
If your orders array is defined on the $scope of a controller, then you create the array on the $scope variable:
$scope.allParcels = $scope.orders
.map(function (elem) {
return elem.parcels;
}) // get an array where each element is an array of parcels.
.reduce(function (previousValue, currentValue) {
return previousValue.concat(currentValue);
}); // concat each array of parcels into a single array of parcels
then on the template, you can use <li ng-repeat='p in allParcels'>{{p.upid}}</li>
If, however, you do not want to place the array on the $scope, I believe you can do something similar to this:
<li ng-repeat="p in orders
.map(function (elem) {
return elem.parcels;
})
.reduce(function (previousValue, currentValue) {
return previousValue.concat(currentValue);
})">{{p.upid}}</li>
although I'm not 100% sure that Angular will evaluate the .map/.reduce in the ng-repeat expression (also having an array generated this way in an ng-repeat is ill-advised since angular would have to constantly generate a new array via map/reduce on each $digest cycle).