I am wondering if there is a way to display for example an id in a table cell as another name from another object as their id's correspond to each other.
What i want to do is to display it in a way like this if there is something similar?
<td class="tableRowText"><p>{{l.SenderId}}</p></td>
in a way like this.
ng-options="x.ProcessId as x.Name for x in PL"
so that would be something like:
<td class="tableRowText"><p>{{l.SenderId}} as x.Name for x in PL</p></td>
WISHFULLY! :P hope you guys understand i'm just trying to get my point through.
Thanks in advance!
_____________EDIT:_______________________________
So this is the Table and how i request the data.
app.factory('getTableGridDataService', function ($resource, config) {
return $resource(config.apiURL + '/Logs/GetLogEvents', {}, { 'post': { method: 'POST' } })
});
scope.loggItems = [];
$scope.fillRealTable = function () {
var arrayBody = {
Sending: $scope.paramSending,
Receiving: $scope.paramReceiving,
Logging: $scope.paramLogging,
};
var query = postTableGridDataService.post({}, arrayBody);
query.$promise.then(function (data) {
var loggItemList = data;
$scope.loggItems = loggItemList
})
}
<table class="table table-striped table-condensed table-hover" id="MainTable">
<thead>
<tr>
<th ng-click="sort('SenderId')" style="cursor:pointer;">
Sender
<span class="glyphicon glyphicon-sort" ng-show="sortKey=='SenderId'" ng-class="{'glyphicon glyphicon-menu-up':reverse, 'glyphicon glyphicon-menu-down':!reverse}"></span>
</th>
<th ng-click="sort('ReceiverId')" style="cursor:pointer;">
Reciever
<span class="glyphicon glyphicon-sort" ng-show="sortKey=='ReceiverId'" ng-class="{'glyphicon glyphicon-menu-up':reverse, 'glyphicon glyphicon-menu-down':!reverse}"></span>
</th>
<th ng-click="sort('LoggingId')" style="cursor:pointer;">
Logging source
<span class="glyphicon glyphicon-sort" ng-show="sortKey=='LoggingId'" ng-class="{'glyphicon glyphicon-menu-up':reverse, 'glyphicon glyphicon-menu-down':!reverse}"></span>
</th>
</tr>
</thead>
<tbody>
<tr dir-paginate="l in loggItems.LogEventList|filter:search|orderBy:sortKey:reverse|itemsPerPage:15" pagination-id="mainPagination">
<td class="tableRowText"><p>{{l.SenderId}}</p></td>
<td class="tableRowText"><p>{{l.ReceiverId}}</p></td>
<td class="tableRowText"><p>{{l.LoggingId}}</p></td>
</tr>
</tbody>
</table>
I also get another object from my api in which i have the names for the Sending-ID, Reciever-ID and Logging-ID and i want those names displayed in the table instead of the id's from my Object in wich i display in the tabl. how do i achieve that?
The other object with the corresponding ID's and names:
app.factory('getSearchFormData', function ($resource, config) {
return $resource(config.apiURL + '/SearchFormObj/getSearchFormItems', {} ,{'get': {method:'GET'}})
});
$scope.SL = [];
function SearchData() {
var query = getSearchFormData.get();
query.$promise.then(function (data) {
$scope.SL = data.SystemList;
console.log($scope.SL);
});
};
SearchData()
Here is the object returned from getSearchFormData:
The basic idea is in your controller which depends on both services, you wait for both promises to resolve, then loop through your log items and either replace the ids with the names, or add then names to each log item, if you want to keep the ids.
$scope.fillRealTable = function () {
var arrayBody = {
Sending: $scope.paramSending,
Receiving: $scope.paramReceiving,
Logging: $scope.paramLogging,
};
var p1 = postTableGridDataService.post({}, arrayBody).$promise;
var p2 = getSearchFormData.get().$promise;
//it might be $q.all([p1.promise,p2.promise])
$q.all([p1,p2]).then(function(data1, data2) {
var loggItemList = data1;
var SL = data2.SystemList;
loggItemList.forEach(function (item) {
item.SenderName = SL[item.SenderId].name;
item.ReceiverName = SL[item.ReceiverId].name;
item.LogginName = SL[item.LogginId].name;
});
$scope.loggItems = loggItemList;
});
}
The code in the forEach is all speculation, since you do not provide the structure of the data returned by getSearchFormData, so this example assumes it's key-value list of objects with the key being one of the ID. E.g:
{
"A001" : { name: "John Smith" },
"B001" : { name: "Bruce Wayne" }
}
P.S: An alternative to this method is to move the whole mapping in getTableGridDataService and have that service call the other, meaning your view logic remains simple, since it will receive the full result from the service.
Related
So I'm making a React program that's about sports. In there I have a table that shows user inputs and those are saved in Firebase realtime database. I'm getting the data from the database and showing it in the table, but I have to make some calculations with those inputs. If the user for example hasn't filled some fields, the end "Total score" will show as NaN because in my calculation the first thing will b 0-x and that makes it NaN.
table example
I have tried making a function for calculating the points outside or the return and then I possibly could call it out but Im not really sure how to access the data cause the database thing kinda makes it difficult for me. My code snippets are down here
Separate calculation.
const hundredPoints = () => {
var number = 0;
let result = decathletes.map(a => a.hundredMeters);
if(result === '')
{number += 0
} else if(result < 9.5 || result > 20) {
number += 0;
} else {
number += Math.floor(hundred.hundredMeters.a*Math.pow((hundred.hundredMeters.b-Number(result)), hundred.hundredMeters.c))
}
console.log(number);
return number;
}
My array where I add them database object locally.
const [decathletes, setDecathletes] = useState([])
useEffect(() => {
getDecathletes()
}, [])
function getDecathletes() {
const decathletesCollectionRef = collection(db, 'decaTable');
getDocs(decathletesCollectionRef)
.then(response => {
const deca = response.docs.map(doc => ({ data: doc.data(), id: doc.id }))
setDecathletes(deca)
console.log(q)
})
.catch(e => console.log(e.message));
}
The Table in the return statement
< Table>
<Fragment>
<div>
<thead>
<tr>
<th>Name</th>
<th>Date of birth</th>
<th>100m</th>
<th>Long</th>
<th>Shot Put</th>
<th>Total score</th>
</tr>
</thead>
<tbody>
{decathletes.map((decas) => (<tr>
<td key={decas.id}>{decas.data.name}</td>
<td key={decas.id}>{decas.data.dateOfBirth}</td>
<td key={decas.id}>{decas.data.hundredMeters}</td>
<td key={decas.id}>{decas.data.longJump}</td>
<td key={decas.id}>{decas.data.shotPut}</td>
<td key={decas.id}>{Number(Math.floor(hundred.hundredMeters.a * Math.pow((hundred.hundredMeters.b - Number(decas.data.hundredMeters)), hundred.hundredMeters.c))) +
Number(Math.floor(long.longJump.a * Math.pow(Number(decas.data.longJump) * 100 - long.longJump.b, long.longJump.c))) +
Number(Math.floor(shot.shotPut.a * Math.pow((Number(decas.data.shotPut - shot.shotPut.b)), shot.shotPut.c)))}</td>
</tr>))}
</tbody>
</div>
</Fragment>
</Table>
If anyone can help, I'd appreciate it immensely, my own brain cant figure it out anymore although it might be really obvious. :D
ISSUE
Hello guys, can anyone please help me to solve this complicated issue? :
I'm creating an application using Spring boot v2.0.5 and React.js v15.6.2, ReactDom v15.6.2, React Bootstrap v0.32.4, and the linking between my frontend and serverside parts is made of restful web services using Spring annotations on the back and fetch API on the front. My React components are made by following the Parent-Children design pattern concept, that means: some of my components can be children of some others and vice versa.
How it works?
I have a table with columns and rows, each row inside the table has a unique id, 2 drop-downs, 1 text input, 2 datepickers and 1 file upload input which is causing the main issue; The user can add more rows that has same components as the previous ones, by clicking on the "+ Document" button; Each row has a unique incremental Id of type number (integer); the drop-downs and the inputs events are handled by one method inside the parent component based on their tag names;
I'm storing all data entered by the user inside a list ([]) of objects({}).
Example: if the user fill only the first row; the object stored inside the list state will be like this:
[{id:0,type:"forms",lang:"all",libelle:"some strings",dateBegin:"11-12-2018",dateEnd:"12-12-2018",document:{File(154845)}]
if the user adds one other row and then filled it like the first one, the list will be like this:
[{id:0,type:"forms",lang:"all",libelle:"some strings",dateBegin:"11-12-2018",dateEnd:"12-12-2018",document:{File(154845)},{id:1,type:"howTo",lang:"en",libelle:"some strings",dateBegin:"11-12-2018",dateEnd:"01-01-2019",document:{File(742015)}]
Check this image to see how the table look like: Table Demo
The table as code in Presentational component Class (child of the main component)
class Presentational extends React.Component {
constructor(props) {
super(props);
this.state = {
docObjList: [],
element: (
<FormDocRowItem // this contains the table tbody tds elements..
id={1}
handleChanges={this.props.handleChanges}/>)
};
this.handleAddDocumentRow = this.handleAddDocumentRow.bind(this);
}
// handleAddDocumentRow method
handleAddDocumentRow(e) {
const value = e.target.value;
const name = e.target.name;
if (name === 'add') {
let arr = this.state.docObjList; // get the list state
// assign the new row component
arr = [...arr, Object.assign({}, this.state.element)];
// set the new list state
this.setState({docObjList: arr});
}
// if name === 'delete' logic..
}
// render method
render() {
const {handleReset} = this.props;
return(
<FormGroup>
<Form encType="multipart/form-data">
<Table striped bordered condensed hover>
<thead>
<tr>
<th>id</th>
<th>Type</th>
<th>Lang</th>
<th>Title</th>
<th>Date begin</th>
<th>Date end</th>
<th>+ Document</th>
<th>Options</th>
</tr>
</thead>
<tbody>
{this.state.element} // this row is required as initialization
{
this.state.docObjList.map((doc, index) => {
// as index in map() starts from 0 and there is an
// already row component above => The index inside the
// table should start from 1 except The key property
// which should know the right index of the function
const id = index+1;
return (
<tr key={index}>
<td>
{id}
</td>
<td>
<DocumentTypes id={id} handleChange={this.props.handleChanges}/>
</td>
<td>
<DocumentLanguage id={id} handleChange={this.props.handleChanges}/>
</td>
<td>
<DocumentLibelle id={id} handleChange={this.props.handleChanges}/>
</td>
<td>
<FormControl id={''+id} name="dateBegin" componentClass="input" type="date"
onChange={this.props.handleChanges}/>
</td>
<td>
<FormControl id={''+id} name="dateEnd" componentClass="input" type="date"
onChange={this.props.handleChanges}/>
</td>
<td>
<Document id={id} handleChange={this.props.handleChanges}/>
</td>
{
this.state.docObjList.length == index + 1 &&
<td>
<button type="button" style={{verticalAlign: 'middle', textAlign: 'center'}} id={index + 1}
name="delete"
onClick={this.handleAddDocumentRow}>
Delete
</button>
</td>
}
</tr>
);
})
}
</tbody>
</Table>
<button type="button" name="add" onClick={this.handleAddDocumentRow}>+ Document</button>
<FormGroup>
<Button type="reset"
style={{marginRight: '20%'}}
className="btn-primary"
onClick={this.props.handleClickSubmit}>Submit</Button>
<Button name="back" onClick={this.props.handleClickSubmit}>Annuler</Button>
</FormGroup>
</Form>
</FormGroup>
)
}
}
The row component class (Child component of Presentational)
const FormDocRowItem = (props) => {
const {id} = props; // the ID here is refering the column that is going to be
// show inside the table not the index of the map function
return(
return (
<tr>
<td>
{id}
</td>
<td>
<DocumentTypes id={id} handleChange={this.props.handleChanges}/>
</td>
<td>
<DocumentLanguage id={id} handleChange={this.props.handleChanges}/>
</td>
<td>
<DocumentLibelle id={id} handleChange={this.props.handleChanges}/>
</td>
<td>
<FormControl id={''+id} name="dateBegin" componentClass="input" type="date" onChange={this.props.handleChanges}/>
</td>
<td>
<FormControl id={''+id} name="dateEnd" componentClass="input" type="date" onChange={this.props.handleChanges}/>
</td>
<td>
<Document id={id} handleChange={this.props.handleChanges}/>
</td>
</tr>
);
}
}
Parent Component Class (The main component)
constructor(props) {
this.state ={
docDataList: [],
formIsReadyToSubmit: false
}
this.handleSubmit = this.handleSubmit.bind(this); // button submit click
this.handleReset = this.handleReset.bind(this); // button reset click
this.fillWithData = this.fillWithData.bind(this); // handle changes
}
// handleReset method..
fillWithData(e) {
const name = e.target.name; // get the name of the target
const id = parseInt(e.target.id); // get the id of the target
let value = e.target.value; // get the value of the target
let arr = this.state.docDataList; // get the list state
// if the target is a file upload
if (name === 'selectDocument')
value = e.target.files[0];
// create properties with null values starting from the first onchange
// event handling, to not get a misplaced properties inside the
// objects of the list state
arr.map((x) => {
x.type = x.type ? x.type : null;
x.lang = x.lang ? x.lang : null;
x.libelle = x.libelle ? x.libelle : null;
x.dateBegin = x.dateBegin ? x.dateBegin : null;
x.dateEnd = x.dateEnd ? x.dateEnd : null;
x.document = x.document ? x.document : null;
});
// if the event target name is not delete
if (name != 'delete') {
// check if the object id already exist in the table
// if it exists, the new value should replace the previous one
// and not allowed to add a new object to the list state
if ((arr.find((x) => x.id == id))) {
// loop through the list state to find the id of the object
arr.map((x) => {
if (x.id == id) {
// helper variable to prevent empty strings
const val = value != '' ? value : null;
switch (name) {
case 'selectType':
x.type = val;
break;
case 'selectLang':
x.lang = val;
break;
case 'libelle':
x.libelle = val;
break;
case 'dateBegin':
x.dateBegin = val;
break;
case 'dateEnd':
x.dateEnd = val;
break;
case 'selectDocument':
x.document = val;
break;
}
}
});
// assign the new list to my docDataList state
// mentioning that the id of the element already exist
this.setState({docDataList: arr}, () => {
console.log(' ID exist; new dataList :', this.state.docDataList);
});
}
// if the id doesn't exist (means that the button +document is clicked)
else {
// again, a helper variable as the previous statement
const val = value != '' ? value : null;
this.setState({
docDataList: [...arr, Object.assign({
id: id,
type: name === 'selectType' ? val : null,
lang: name === 'selectLang' ? val : null,
libelle: name === 'libelle' ? val : null,
dateBegin: name === 'dataBegin' ? val : null,
dateEnd: name === 'dateEnd' ? val : null//,
//document: name==='selectDocument'? val:null
})]
}, () => {
console.log('ID doesnt exist; new dataList :', this.state.docDataList);
});
}
}
}
HandleSubmit() method (Inside the Parent component class)
// Submit button click handler
handleSubmit(e) {
let docDataList = this.state.docDataList;
// if the user didn't touch any thing on the table rows
// that means the list is empty and its length = 0
if (docDataList.length === 0) {
this.setState({
alerts: {
message: 'Please enter your document information ',
show: true
}
});
}
// if the user has entered a data on the table row
else if (docDataList.length > 0) {
let data = new FormData(); // object which will be sent
// check the docDataList before request
console.log('DocDataList before request:', docDataList);
data.append('docDataList', JSON.stringify(docDataList));
fetch('http://localhost:8080/api/files/uploadFile', {
method: 'POST',
body: data
}).then(response => {
console.log('success document upload', response);
}).catch(error => {
console.log('error', error);
});
this.setState({
formIsReadyToSubmit: true,
docDataList: [], // reset the list
alerts: {updateAlert: true} // make an alert
});
}
}
To see what the console show when I fill the row with data: CLICK HERE PLEASE
To see the response of the request: CLICK HERE PLEASE
NOTE: You may notice after watching those screenshots, that there is an extra list of data called "arrContrats" which which I didn't mention it in my issue because it doesn't have any problem; the problem is with the "docDataList" list. Thanks in advance
If your problem is that you're getting a File object from the browser, and then later using JSON.stringify on it (or something containing it) and getting {} for it in the JSON, that's correct. The browser's File object has no own, enumerable properties. JSON.stringify only includes own, enumerable properties.
If you want the various properties that File objects have (inherited accessor properties), you'll need to copy them to a new object.
If you want the file data, it's not accessible as a property on the object, you have to use one of the methods it inherits from Blob to read the file data, such as stream, text, or arrayBuffer (or alternatively, you could use a FileReader, but there's no need to except in obsolete environments that don't have the modern methods).
function stringifyFileObject(arrWithFiles = []) {
const arrData = [];
for (let i=0; i < arrWithFiles.length; i++) {
const file = arrWithFiles[i];
const obj = {
lastModified: file.lastModified,
name: file.name,
size: file.size,
type: file.type,
webkitRelativePath: file.webkitRelativePath,
}
arrData.push( obj );
}
}
Or whatever that suits your needs. You get the idea...
Don't use JSON.stringify() with FormData instance. And don't use headers: {'Content-Type': 'application/json'} if you are sending a file.
Example:
let data = new FormData(form);
let config = {
method: "POST",
body: data
}
const response = await fetch(url, config);
const responseData = response.json();
I want to sort the table by name and first name and if we find duplicate fields we leave one line by adding an accordion to show duplicates or masked.
I put the ts and html code
i have create this sample for you, i have remove Observable to simplify the code :
First i have create new structure with some meta data information.
isFirst and isExpended are useful on html side to manage "hide/show" state of related Clients.
export interface OrderedClient {
isUniq: boolean;
isFirst: boolean;
isExpended: boolean;
info: Client;
}
I have put dummy data on constructor to show you how to classify this:
export class AppComponent {
private clients: Client[];
displayClients: OrderedClient[];
constructor() {
this.clients = [
{id: 1, lastname: 'git',firstName: 'yanis', birthDate:'03/19/1990'},
{id: 2, lastname: 'git',firstName: 'yanis', birthDate:'01/01/1990'},
{id: 3, lastname: 'lorem',firstName: 'yanis', birthDate:'01/01/1990'}
];
this.classify();
}
private classify() {
this.displayClients = [];
// For each existing clients.
this.clients.forEach((client: Client) => {
// We create new structure describe by OrderedClient interface.
this.displayClients.push(
// We merge client and getInformation output.
Object.assign({
info: client
},this.getInformation(client))
);
});
}
private getInformation(client: Client): {
isFirst: boolean,
isUniq: boolean,
isExpended: boolean
} {
// We fetch all clients who have same first and last name.
const clients = this.clients.filter(current => {
return current.lastname === client.lastname && current.firstName === client.firstName;
});
// We create meta information
return {
// If is first item of list.
isFirst: clients[0].id === client.id,
// if he is alone.
isUniq: clients.length === 1,
// If he is visible by default.
isExpended: clients.length === 1 || clients[0].id === client.id
};
}
toggleRelated(client:Client)
{
//When we click on toggle button. we loop on all clients.
this.displayClients.forEach(e => {
// If current one have same first and last name and is not the first element of associated clients.
if(e.info.firstName === client.firstName && e.info.lastname === client.lastname && !e.isFirst) {
// Then we toggle expended state.
e.isExpended = !e.isExpended;
}
});
}
}
for each clients, i will check if we can find similar other entry on document. Goal is to populate isFirst and isExpended. Now we can use this new structure on our html as bellow :
<div class="m-table" data-module="scrollable">
<table>
<thead>
<tr>
<th>#</th>
<th sortable-column="lastname" scope="col">Nomtrie</th>
<th sortable-column="firstname" scope="col">Prénomtrie</th>
<th scope="col">Date de naissancetrie</th>
</tr>
</thead>
<tbody>
<ng-container *ngFor="let data of displayClients">
<tr *ngIf="data.isExpended">
<td><button *ngIf="data.isFirst && !data.isUniq" (click)="toggleRelated(data.info)">toggle</button></td>
<td scope="col">{{data.info.lastname}}</td>
<td scope="col">{{data.info.firstName}}</td>
<td scope="col">{{data.info.birthDate}}</td>
</tr>
</ng-container>
</tbody>
</table>
</div>
online sample
You can "map" your result, adding a property to check if is repeated, but first of all you must ordered the array
this.searchService.getAllClients()
.subscribe(results =>{
//first sort
results.sort((a, b) => {
return a.lastname + a.firstname == b.lastname + b.firstname ? 0 :
a.lastname + a.firstname > b.lastname + b.firstname ? 1 : -1;
//After, map the result adding a new propertie
this.results=result.map((x) => {
return {...x, uniq: results.find(
f => f.lastname + f.firstname == x.lastname + x.firstname) == x }
})
),
error =>console.log('results', error );
Notice, I use this.result.find that return an element. if this element is equal so self, is the first one of them
I am trying to bind a list of object in Spring controller that is annotated with #RequestBody. I am using Thymeleaf for template engine. I am using Kotlin programming language.
Here is the HTML code
<form class="attendanceBook" id="form_submit" role="form" method="post" action="/attendances">
<table class="table table-striped">
<th>Role Number</th>
<th style="width: 30%">Student Name</th>
<th>Present</th>
<div th:each="std, stat : ${studentList}" class="separator">
<tr>
<td th:text="${std.roleNumber}"></td>
<td th:text="${std.studentName}"></td>
<td><input type="checkbox" th:name="|i[__${stat.index}__]|" id="i" /></td>
<td><input type="text" th:name="|t[__${stat.index}__]|" th:value="*{studentList[__${stat.index}__].studentId}"/> </td>
</tr>
</div>
</table>
<input type="submit" id="submit_btn" value="Click"/>
</form>
When I submit the form that converted to the Json format with jQuery. Here is only that JavaScript part which convert form data to Json.
var genFormSubmitParams = function (context) {
var _this = $(context),
data = {};
_this.find('[name]').each(function (index, value) {
var _this = $(this),
name = _this.attr('name'),
value = _this.val();
data[name] = value;
});
var params = {};
params["data"] = JSON.stringify(data);
console.log(params["data"]);
return params;
};
Here is the Controller.
#PostMapping
fun patchAttendance(#RequestBody attendanceJson: AttendanceJsonWrapper): ResponseEntity<*> {
{
logger.info("attendanceJson list size {}", attendanceJson.attendanceJsons?.size)
logger.info("attendanceJson {}", attendanceJson)
return responseOK(attendanceJson)
}
This is the data class in which I need to bind one single row.
data class AttendanceJson (
var i: String = "",
var t: String = ""
)
And here is the wrapper class for the actual data class for list of object. I have written this in Java.
public class AttendanceJsonWrapper {
List<AttendanceJson> attendanceJsons;
public AttendanceJsonWrapper() {
}
public AttendanceJsonWrapper(List<AttendanceJson> attendanceJsons) {
this.attendanceJsons = attendanceJsons;
}
public List<AttendanceJson> getAttendanceJsons() {
return attendanceJsons;
}
public void setAttendanceJsons(List<AttendanceJson> attendanceJsons) {
this.attendanceJsons = attendanceJsons;
}
}
When I am posting the JavaScript console show this in log
{"i[0]":"on","t[0]":"90","i[1]":"on","t[1]":"106"}
But it is not binding in the data class AttendanceJson as object. The attendanceJsons list remain null but the ajax call has success.
How can I bind this object list to back end ? Thanks in advance.
try .serializeArray() from jquery
$( "form" ).submit(function( event ) {
console.log( $( this ).serializeArray() );
event.preventDefault();
});
https://api.jquery.com/serializeArray/
The client (typescript) gets different results from the server, all are in json format. Sometimes a simple json result, sometimes complex nested jsons.
I need to present the results in a (very) simple html table.
function run(): void {
var url = this.selectedKnockoutDropList();
$.ajax(url, "GET").done(data => {
console.log(data);
*** here I want to do something like :
- open/embedd results.html
- fill a table with the json results parsed somehow
});
I tried $('#...).html(data) with no success.
You need to transform the JSON into meaningful HTML. This can be done in typescript, javascript. It can be done with jQuery it can be done using javascript templates such a handlebars. It can be done with SPA frameworks like Angular.
Please post an a sample of the returned JSON and I can update with a TypeScript example not using any toolsets or libs.
JSON is an object. To output it to the document you need to first convert it to a string:
$.ajax(url, "GET").done(data => {
$('#...).html(JSON.stringify(data));
}
However, if you know what kind of data you are getting back and want to output just parts of it, you'll need to loop the JSON. For example, if you had a JSON object like:
[
{name: "Joe", age: 32},
{name: "Suzy", age 23},
{name: "Tom", age 28}
]
You could output the names like this:
$.ajax(url, "GET").done(data => {
data.forEach(function(person) {
$(ul).append('<li>' + person.name + '</li>');
});
}
Martin and Robert - thanks for the quick replies. I took your advises and came with the following generic solution (which can display html text returned from the server or nested json object in a table) :
HTML:
<!--HTML-->
<div data-bind="visible: isHtml, html: htmlView"></div>
<!--SIMPLE JSON-->
<div data-bind="visible: isJson">
<table>
<thead>
<tr data-bind="foreach: columns">
<th data-bind="text: $data"></th>
</tr>
</thead>
<tbody data-bind="foreach: rows">
<tr data-bind="foreach: $parent.columns">
<td data-bind="text: $parent[$data]"></td>
</tr>
</tbody>
</table>
</div>
TS:
function run(): void {
this.isHtml(false);
this.isJson(false);
$.ajax(url, "GET").done(data => {
var jsonObj = data;
if (typeof (data) === "string") {
this.htmlView(data);
this.isHtml(true);
return;
}
if (data instanceof Array === false) {
jsonObj = [data];
}
this.columns([]);
this.rows([]);
for (var i = 0; i < jsonObj.length; i++) {
var item = jsonObj[i];
var newItem = {};
for (var key in item) {
if (i === 0)
this.columns.push(key);
if (typeof item[key] !== "object") {
newItem[key] = item[key];
} else {
for (var deeperKey in item[key]) {
this.columns.push(deeperKey);
newItem[deeperKey] = item[key][deeperKey];
}
}
}
this.rows.push(newItem);
}
this.isJson(true);
});
I used another stackoverflow post (which I can't find right now) for the nested rows. Obviously flatting the sub-rows is not what I want but it can easily be changed.
It's my first typescript/html program :)
Thanks Again!