JSON.stringify() function is replacing the file object with an empty one - json

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();

Related

React loop through json object and display data

I have a demo here
I have a simple json file that I'm importing and I would like to loop through and output the json data in a div
I'll probable want to pick out parts of the json but for now I just need to be able to output the json
Do I need to create an array from the json data and then map over that.
const showProductData = Object.keys(ProductData).map(function(key) {
return <div>{ProductData[key]}</div>;
});
const App = () => {
return (
<div>
<h2>JSON</h2>
{showProductData}
</div>
);
};
If you read the error message, Objects are not valid as a React Child. To modify your current code to just show the json, you will need to convert the object into a string.
const showProductData = Object.keys(ProductData).map(function(key) {
return <div>{JSON.stringify(ProductData[key])}</div>;
});
To be more concise with what we're accessing, we can instead use Object.values() instead:
const showProductData = Object.values(ProductData).map(function(value) {
return <div>{JSON.stringify(value)}</div>;
});
To further access specific points of the data, you can use dot notation to access primitive values:
const showProductData = Object.values(ProductData).map(function(value) {
return <div>Weight: {value.ProductWeight}</div>;
});
well, when i show ur a question, immediately i thought 'recursive solution' :)
so basically this is the code, I tried to explain it, feel free to dig into it
function getAllProps(obj) {
let value = obj;
// in case it is an object / array, which true, at any json file, at least at the beginning
if (typeof obj === "object") {
value = Object.keys(obj).map(key => {
// and then check again if the value of the 'key' is an object
// because we cant just out put object in react
if (typeof obj[key] === "object") {
// then out put the key name (property, 'ProductOne' for example)
// and call the function again since we know it is an object.
// basiclly the function will call it self again and again
// until the value will be different than 'object'
return (
<div key={key}>
<div style={{ marginLeft: 20 }}>
<strong> {key} </strong>
{getAllProps(obj[key])}
</div>
</div>
);
}
return (
<div key={key} style={{ marginLeft: 20 }}>
{key}: {obj[key]}
</div>
);
});
}
return value;
}
const products = getAllProps(ProductData);
const App = () => {
return (
<div>
<h2>JSON</h2>
{products}
</div>
);
};
actually, just check that link
read the comments, try to understand my 'recursive solution'

How to save an array of JSON objects (with nested objects) to state in React

I'm trying to save an array of JSON objects returned from an API call to state in React (so that I can use the data to render a table). I'm getting the error Error: Objects are not valid as a React child (found: object with keys {street, suite, city, zipcode, geo}). If you meant to render a collection of children, use an array instead.
I can't figure out how to fix this. It looks like the JSON is being stored inside an array as it should be. However, there are also nested objects inside the objects that may be causing an issue, for example:
address": {
"street": "Victor Plains",
"suite": "Suite 879",
"city": "Wisokyburgh",
"zipcode": "90566-7771",
Any assistance would be much appreciated. Here's my code below:
let tableData = []
fetch("https://jsonplaceholder.typicode.com/users")
.then(response => response.json())
.then(data => {
tableData = data
props.addItem(tableData)
})
Here's the addItem function:
addItem(item) {
this.setState(function(prevState) {
return {
tables: [...prevState.tables, item]
}
})
}
UPDATE
Here's how I am rendering the data:
App.js:
render() {
return (
<div>
{this.state.tables.map(item => {
return (<TableComponent key={item} data={item} />)
})}
</div>
)
}
TableComponent.js:
class TableComponent extends React.Component {
constructor(props){
super(props);
this.getHeader = this.getHeader.bind(this);
this.getRowsData = this.getRowsData.bind(this);
this.getKeys = this.getKeys.bind(this);
}
getKeys = function(){
return Object.keys(this.props.data[0]);
}
getHeader = function(){
let keys = this.getKeys();
return keys.map((key, index)=>{
return <th key={key}>{key.toUpperCase()}</th>
})
}
getRowsData = function(){
let items = this.props.data;
let keys = this.getKeys();
return items.map((row, index)=>{
return <tr key={index}><RenderRow key={index} data={row} keys={keys}/></tr>
})
}
render() {
return (
<div>
<table>
<thead>
<tr>{this.getHeader()}</tr>
</thead>
<tbody>
{this.getRowsData()}
</tbody>
</table>
</div>
);
}
}
const RenderRow = (props) =>{
return props.keys.map((key, index)=>{
return <td key={props.data[key]}>{props.data[key]}</td>
})
}
The error message threw me off here because it made it seem like the issue was in saving the objects to state. However, as was pointed out in the comments, the error happened during rendering. To solve the issue I changed RenderRow to the following:
const RenderRow = (props) =>{
return props.keys.map((key, index)=>{
return <td key={props.data[key]}>{typeof props.data[key] === "object" ? JSON.stringify(props.data[key]) : props.data[key]}</td>
})
}
Specifically, the piece that I changed is to first check whether a specific element is an object, and if it is, to use JSON.stringify() to convert it to a string before rendering it to the screen.

How to use Observables/ real-time change only one entry in table in angular2+

I am stuck with an issue in angular4 and node.js app. I display the data in a table that has 7 columns via *ngFor . Now the issue is , i need to dynamically and on real-time basis update the last column . i.e. if the column -> Active ... is Yes , then i display a green color and when the Active ...is No, then i display a red color. But then i am not sure how to update the last column and only the last column real-time.
[1]
I thought of using Observables code in init from where i call the table data, but that will only keep on showing the loader always , as the data will keep on uploading regularly after a few seconds and disturb the entire table view.
[2]
And i have shown the color based on an if condition, but in all the entries, it shows only green color.
code -
interface.component.ts
export class MasterInterfaceComponent implements OnInit {
activeCheck;
activeValue;
activeRedColor;
activeGreenColor;
ngOnInit() {
console.log("beginning of func");
Observable.interval(15000).subscribe(x => {
console.log("Called")
this.viewData();
});
}
viewData() {
this.loading = true;
var url = config.url;
var port = config.port;
this.http.post("http://" + url + ":" ...... ('Token') }) })
.map(result => this.result = result.json(),
)
.subscribe((res: Response) => {
this.loading = false;
this.records = res;
console.log("xxxx interface view result data ",this.result)
console.log("XXXXXXXXXXXX interface view res data ", res);
this.activeCheck = this.result;
for (let obj of this.activeCheck){
for (let key in obj){
if (key == "ACTIVE_YN"){
if (obj[key] == "Y"){
this.activeRedColor = false;
this.activeGreenColor = true;
console.log("this.activeGreenColor = true;");
}
else if (obj[key] == "N"){
this.activeRedColor = true;
this.activeGreenColor = false;
console.log("this.activeGreenColor = false;");
}
}
}
}
});
}
interface.component.html
<tbody>
<tr *ngFor="let data of result |filter:filter| orderBy : 'IF_ID'|
paginate: { itemsPerPage: 5, currentPage: p }; let i =
index">
<td class="text-center" style="width:8%">
<a [hidden]= "accessIdHide" [routerLink]="['/master-system/update-
interface']" (click)="forUpdate(data)" data-toggle="tooltip"
title="Update" style="color:#ffffff;;float:left"
type="link">
<span class="glyphicon glyphicon-plus-sign"></span>
</a>{{data.IF_ID}}
</td>
<td>{{data.IF_CD}}</td>
<td>{{data.IF_NAME}}</td>
<td>{{data.IF_DESCRIPTION}}</td>
<td>{{data.SRC_SYS_NAME}}</td>
<td>{{data.TRGT_SYS_NAME}}</td>
<td >
<img [hidden]= "activeGreenColor" src = "url" width="10" height =
"10">{{data.ACTIVE_YN}}
<img [hidden] = "activeRedColor" src = "url" width="10" height =
"10">{{data.ACTIVE_YN}}
</td>
</tr>
</tbody>
First of all, you do not need two switches, but the way you had it is ok, but for our discussion, let's just say we have one switch "isActive".
I think your code should work, but you need to make sure the subscription is firing, ex.
.subscribe((res: Response) => {
console.log(res);
If this code fires every time the data changes, then your img should be turned on and off
<img *ngIf="isActive" src="url">
or
<img [ngClass]="{ isActive: green }">
Either of the above is fine. So just try to debug your observable to work the way you wanted, ex. when it should be fired and in which frequency.

sort and hide duplicate fields table angular 4+

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 cannot get my jqGrid to populate

Edited with working code as per Mark's answer below.
I'm really starting to loath MVC. I have been trying all day to get a simple grid to work, but I'm having more luck banging a hole in my desk with my head.
I'm trying to implement a search page that displays the results in a grid. There are 3 drop-down lists that the user can use to select search criteria. They must select at least one.
After they have searched, the user will be able to select which records they want to export. So I will need to include checkboxes in the resulting grid. That's a future headache.
Using JqGrid and Search form - ASP.NET MVC as a reference I have been able to get the grid to appear on the page (a major achievement). But I can't get the data to populate.
BTW, jqGrid 4.4.4 - jQuery Grid
here is my view:
#model Models.ExportDatex
<script type="text/javascript">
$(document).ready(function () {
$('#btnSearch').click(function (e) {
var selectedSchool = $('#ddlSchool').children('option').filter(':selected').text();
var selectedStudent = $('#ddlStudent').children('option').filter(':selected').text();
var selectedYear = $('#ddlYear').children('option').filter(':selected').text();
var selectedOption = $('#exportOption_1').is(':checked');
if (selectedSchool == '' && selectedStudent == '' && selectedYear == '') {
alert('Please specify your export criteria.');
return false;
}
selectedSchool = (selectedSchool == '') ? ' ' : selectedSchool;
selectedStudent = (selectedStudent == '') ? ' ' : selectedStudent;
selectedYear = (selectedYear == '') ? ' ' : selectedYear;
var extraQueryParameters = {
school: selectedSchool,
student: selectedStudent,
year: selectedYear,
option: selectedOption
};
$('#searchResults').jqGrid({
datatype: 'json',
viewrecords: true,
url: '#Url.Action("GridData")?' + $.param(extraQueryParameters),
pager: '#searchResultPager',
colNames: ['SchoolID', 'Student Name', 'Student ID', 'Apprenticeship', 'Result'],
colModel: [
{ name: 'SchoolID' },
{ name: 'Student Name' },
{ name: 'StudentID' },
{ name: 'Apprenticeship' },
{ name: 'Result' }]
}).trigger('reloadGrid', [{ page: 1 }]);
});
});
</script>
#using (Html.BeginForm("Index", "Datex", FormMethod.Post))
{
<h2>Export to Datex</h2>
<div class="exportOption">
<span>
#Html.RadioButtonFor(model => model.ExportOption, "true", new { id = "exportOption_1" })
<label for="exportOption_1">VET Results</label>
</span>
<span>
#Html.RadioButtonFor(model => model.ExportOption, "false", new { id = "exportOption_0" })
<label for="exportOption_0">VET Qualifications</label>
</span>
</div>
<div class="exportSelectionCriteria">
<p>Please specify the criteria you want to export data for:</p>
<table>
<tr>
<td>School:</td>
<td>#Html.DropDownListFor(model => model.SchoolID, Model.Schools, new { id = "ddlSchool" })</td>
</tr>
<tr>
<td>Student: </td>
<td>#Html.DropDownListFor(model => model.StudentID, Model.Students, new { id = "ddlStudent" })</td>
</tr>
<tr>
<td>Year Completed:
</td>
<td>
#Html.DropDownListFor(model => model.YearCompleted, Model.Years, new { id = "ddlYear" })
</td>
</tr>
</table>
<table id="searchResults"></table>
<div id="searchResultPager"></div>
</div>
<div class="exportSearch">
<input type="button" value="Search" id="btnSearch" />
<input type="submit" value="Export" id="btnExport" />
</div>
}
Here is my Controller. As we don't have a database yet, I am just hardcoding some results while using an existing table from a different DB to provide record IDs.
[HttpGet]
public JsonResult GridData(string sidx, string sord, int? page, int? rows, string school, string student, string year, string option)
{
using (SMIDbContainer db = new SMIDbContainer())
{
var ds = (from sch in db.SCHOOLs
where sch.Active.HasValue
&& !sch.Active.Value
&& sch.LEVEL_9_ORGANISATION_ID > 0
select sch).ToList();
var jsonData = new
{
total = 1,
page = 1,
records = ds.Count.ToString(),
rows = (
from tempItem in ds
select new
{
cell = new string[]{
tempItem.LEVEL_9_ORGANISATION_ID.ToString(),
tempItem.SCHOOL_PRINCIPAL,
"40161",
"No",
"Passed (20)"
}
}).ToArray()
};
return Json(jsonData, JsonRequestBehavior.AllowGet);
}
}
Is the JSON you are passing back to the grid valid? Are you passing back the information that the jqGrid needs? Why setup your jqGrid inside of an ajax call instead of inside your $(document).ready(function () { ?
Here is an example of the portion of code I use to format my json for jqGrid:
var jsonData = new
{
total = (totalRecords + rows - 1) / rows,
page = page,
records = totalRecords,
rows = (
from tempItem in pagedQuery
select new
{
cell = new string[] {
tempItem.value1,
tempItem.value2, ........
}
}).ToArray()
};
return Json(jsonData, JsonRequestBehavior.AllowGet);
If you want to make your user search first, you can on the client side, set the jqgrid datatype: local and leave out the url. Then after your user does whatever you want them to do you can have the jqGrid go out and fetch the data, via something like:
$('#gridName').jqGrid('setGridParam', { datatype: 'json', url: '/Controller/getGridDataAction' }).trigger('reloadGrid', [{ page: 1}]);
If you want to pass in the search data, or other values to the controller/action that is providing the data to the jqGrid you can pass it via the postData: option in the jqGrid. To set that before going out you can set it via the setGridParam option as shown above via postData: { keyName: pairData}.
MVC and jqGrid work great...there are a ton of examples on stackoverflow and Oleg's answers are a vast resource on exactly what you are trying to do. No hole in desk via head banging required!