Ember Search App

I added a console.log(err);

        this.set('error', err);
		console.log(err);
      });

and I’m seeing this error. TypeError: “data.recordsets is undefined”

this is the full JSON returned from the api, i wrote this api also in node.js and can make changes if needed as well… :

{
	"recordsets": [
		[
			{
				"nebid": "NEB2940",
				"catalognumber": "M0645",
				"organism": "Escherichia coli ER3489",
				"depositor": "Andy Gardner",
				"productionstatus": "Archive",
				"productname": "Thermostable FEN1",
				"depositdate": "2015-06-24",
				"erhost": "ER3489"
			}
		]
	],
	"recordset": [
		{
			"nebid": "NEB2940",
			"catalognumber": "M0645",
			"organism": "Escherichia coli ER3489",
			"depositor": "Andy Gardner",
			"productionstatus": "Archive",
			"productname": "Thermostable FEN1",
			"depositdate": "2015-06-24",
			"erhost": "ER3489"
		}
	],
	"output": {},
	"rowsAffected": []
}

here is the current home.js component and template.

import Ember from 'ember';

export default Ember.Component.extend({
	andor: null,
  nebid: null,
  catalognumber: null,
  organism: null,
  depositor: null,
  productionstatus: null,
  productname: null,
  depositdate: null,
  erhost: null,
  
  actions: {		
    search12: function () {			
			let { andor, nebid, catalognumber, organism, depositor, productionstatus, productname, depositdate, erhost } = this;

      andor = andor ? 1 : 0;

      let data = { andor, nebid, catalognumber, organism, depositor, productionstatus, productname, depositdate, erhost };
      console.log(data);
      data = Object.keys(data).map((key) => {
        let obj = { };
        obj[key] = data[key];
        return obj;
      });
      this.set('data', data);

      fetch("http://localhost:3000/routes", {
        method: 'POST',				
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
        record: {
          andor,
          nebid,
          catalognumber,
          organism,
          depositor,
          productionstatus,
          productname,
          depositdate,
          erhost
        }
		})
      }).then((data) => {
        this.set('data', data.recordsets[0]);
      }).catch((err) => {
        this.set('error', err);
		console.log(err);
      });
      
    },		
  }
});

home.hbs

{{!-- templates/components/home.js --}}
<h2>{{this.title}}</h2>
<header class="logo"></header>

{{#if data}}
  {{#each data as |item|}}
    {{#each-in item as |recordName recordValue|}}
      {{recordName}}: {{recordValue}}
    {{/each-in}}
  {{/each}}
{{else}}
  <div>
    <form id="userSubmit" {{action "search12" on="submit"}}>
	
    	<label for="and">UNCHECKED "AND" CHECKED FOR "OR": 
      	<input type="checkbox" checked={{this.andor}} onclick={{action (mut this.andor) value="target.checked"}}><br><br>
    	</label>			
    	<hr>	
		
    	<label for="nebid">NEBID:</label>
    	<input id="nebid" value={{this.nebid}} oninput={{action (mut this.nebid) value="target.value"}}><br><br>
	    
		<label for="catalognumber">Catalog Number:</label>
    	{{input id="catalognumber" class = "inputField" type="text" value=this.catalognumber}}<br><br>
		
		<label for="organism">Organism:</label>
    	{{input id="organism" class = "inputField" type="text" value=this.organism}}<br><br>

    	<label for="depositor">Depositor:</label>
    	{{input id="depositor" class = "inputField" type="text" value=this.depositor}}<br><br>
	
    	<label for="productionstatus">Status:</label>
    	{{input id="productionstatus" class = "inputField" type="text" value=this.productionstatus}}<br><br>

		<label for="productname">Product Name:</label>
    	{{input id="productname" class = "inputField" type="text" value=this.productname}}<br><br>

    	<label for="depositdate">Deposit Date:</label>
    	{{input id="depositdate" class = "inputField" type="date" value=this.depositdate}}<br><br>
    	
		<label for="erhost">ER Host:</label>
    	{{input id="erhost" class = "inputField" type="text" value=this.erhost}}<br>
    	<br><br><br>
		<div>
    	  <button type="submit" name="search">Search</button> 
   	 </div> 
    </form>
  </div>
{{/if}}

Ah ok that clarifies what’s happening I think. First off you can remove this code:

      let data = { andor, nebid, catalognumber, organism, depositor, productionstatus, productname, depositdate, erhost };
      console.log(data);
      data = Object.keys(data).map((key) => {
        let obj = { };
        obj[key] = data[key];
        return obj;
      });
      this.set('data', data)

It was just for demonstrational purposes in the twiddle. That is what is setting the display to the form contents instead of the server response (i didn’t have access to your server in the twiddle so i just spit back the form contents).

Next, does the server response always have BOTH “recordsets” and “recordset” keys? Or does it depend? I guess I’m not really sure what the use case is since they’re both the same in the example you posted.

Lastly I’m an idiot and forgot the res.json() part which you had in your initial example, sorry! Should look like this I think:

      }).then(res => {
        return res.json()
      }).then((data) => {
        this.set('data', data.recordsets[0]);

Sweet it works now.
This is the results:

image

I’m not sure about the double recordset showing. That json is from the browser console…

this is what I see in the node.js console

I need to format the results now in a grid/spreadsheet looking view. Any sugestions?

Also what part of the code would I do the formatting of the results. Is this where the main part of the results data is coming from? Highlighted below?

Thank you very much for all your help so far. I spent three weeks trying to figure out what we have done in the last 3 days.

So the current code is unpacking and rendering the “recordsets” key of the response, if you want to do that with singular “recordset” key (what you have expanded in the screenshot) then just change the set call from:

        this.set('data', data.recordsets[0]);

to:

        this.set('data', data.recordset);

Does that make sense? All you’re really doing is specifying what part of the response json to make available to the template via the “data” property.

As for formatting, yes the highlighted portion is what is rendering the key/value pairs into the dom. It’s literally just rendering text nodes. You can throw whatever markup you want around it though. For example if you wanted to render it in a table you could do something like this:

{{#if data}}
  <table>
    <thead>
      <tr><th>Key</th><th>Value</th></tr>
    </thead>
    <tbody>
    {{#each data as |item|}}
      {{#each-in item as |recordName recordValue|}}
        <tr>
          <td>{{recordName}}</td>
          <td>{{recordValue}}</td>
        </tr>
      {{/each-in}}
    {{/each}}
    </tbody>
  </table>
{{else}}
...

That’s just an example though, probably not exactly what you’d want to do. You could also do slightly more “processing” in the javascript side before setting the data, so you only need to do one loop in the template, etc. There are a million ways you could present the data and it really depends on the way your data is returned from the server in a typical use case and exactly how you want to capture it on screen.

Thank you for the suggestions, they have been very helpful! In the template this section…

{{#each data as |item|}}	
  {{#each-in item as |recordName recordValue|}}

displays the data values like a list.

I’m trying to display the values more like a spreadsheet horizontally.

Ive tried all kinds of html methods but cant seem to get the values across.

How would I isolate each value separately so I can use them in the template?

something like

{{recordValue}}.field1 {{recordValue}}.field2 etcetc…

This kind of works. But only for one record.

{{!-- templates/components/home.js --}}
<header class="logo">
	<img src="assets/images/NEB_Logo-Long_White.png"  alt="logo">
</header>
<h2>{{this.title}}</h2>
{{#if data}}
<style>
  table, th, td {
    border: 1px solid black;
  }
  th, td {
    padding: 10px;
  }
</style>
<div class="table-wrapper">
  <table>
   <thead>
    <tr>
      <th>nebid</th>
      <th>catalognumber</th>
      <th>organism</th>
	  <th>depositor</th>
	  <th>productionstatus</th>
	  <th>productname</th>
	  <th>depositdate</th>
	  <th>erhost</th>				
    </tr>
  </thead>	
    <tbody>
    {{#each data as |item|}}	
      {{#each-in item as |recordName recordValue|}}
          <th>{{recordValue}}</th>        
      {{/each-in}}	           		  
    {{/each}}
    </tbody>
  </table>
  </div>
{{else}}
  <div>
    <form id="userSubmit" {{action "search12" on="submit"}}>
	
    	<label for="and">UNCHECKED "AND" CHECKED FOR "OR": 
      	<input type="checkbox" checked={{this.andor}} onclick={{action (mut this.andor) value="target.checked"}}>
    	</label>			
    	<hr>	
		
    	<label for="nebid">NEBID:</label>
    	<input id="nebid" value={{this.nebid}} oninput={{action (mut this.nebid) value="target.value"}}><br><br>
	    
		<label for="catalognumber">Catalog Number:</label>
		<input id="catalognumber" value={{this.catalognumber}} oninput={{action (mut this.catalognumber) value="target.value"}}><br><br>
    	<!-- {{input id="catalognumber" class = "inputField" type="text" value=this.catalognumber}}<br><br> -->
		
		<label for="organism">Organism:</label>
    	{{input id="organism" class = "inputField" type="text" value=this.organism}}<br><br>

    	<label for="depositor">Depositor:</label>
    	{{input id="depositor" class = "inputField" type="text" value=this.depositor}}<br><br>
	
    	<label for="productionstatus">Status:</label>
    	{{input id="productionstatus" class = "inputField" type="text" value=this.productionstatus}}<br><br>

		<label for="productname">Product Name:</label>
    	{{input id="productname" class = "inputField" type="text" value=this.productname}}<br><br>

    	<label for="depositdate">Deposit Date:</label>
    	{{input id="depositdate" class = "inputField" type="date" value=this.depositdate}}<br><br>
    	
		<label for="erhost">ER Host:</label>
    	{{input id="erhost" class = "inputField" type="text" value=this.erhost}}<br>
    	<br>
		<hr>
		<br><br>
		<div>
    	  <button type="submit" name="search">Search</button> 
   	 </div> 
    </form>
  </div>
{{/if}}
   <tbody>
    {{#each data as |item|}}	
      {{#each-in item as |recordName recordValue|}}
          <th>{{recordValue}}</th>        
      {{/each-in}}	           		  
    {{/each}}
    </tbody>
  </table>

This is the section that would render the table body. The {{#each data as |item|}} loops over each record in ‘data’ and each-in loops over the key/value pairs in an object, which if i’m understanding this right would be one “record”. Of course since (IIRC) that an objects keys aren’t guaranteed to appear in any order the easiest way to do what you want would just be to render the data without the each-in loop. So your table code would need to look more like this:

   <tbody>
    {{#each data as |record|}}	
      <tr>
        <td>{{record.nebid}}</td>
        <td>{{record.catalognumber}}</td>
        <td>{{record.organism}}</td>
        <td>{{record.depositor}}</td>
        <td>{{record.productionstatus}}</td>
        <td>{{record.productname}}</td>
        <td>{{record.depositdate}}</td>
        <td>{{record.erhost}}</td>
      </tr>
    {{/each}}
    </tbody>
  </table>

That guarantees that each column matches the headers and allows you to leave out any values/columns that you don’t want included (each-in would loop over everything).

1 Like

Thanks this worked awesome!

here are the current objects.

/components/home.js

import Ember from 'ember';

export default Ember.Component.extend({
	andor: null,
  nebid: null,
  catalognumber: null,
  organism: null,
  depositor: null,
  productionstatus: null,
  productname: null,
  depositdate: null,
  erhost: null,
  
  actions: {		
    search12: function () {			
			let { andor, nebid, catalognumber, organism, depositor, productionstatus, productname, depositdate, erhost } = this;

      andor = andor ? 1 : 0;

    //fetch("http://localhost:3000/routes", {
	fetch("https://strainsearchnodejsbackend.azurewebsites.net/routes", {
        method: 'POST',				
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
        record: {
          andor,
          nebid,
          catalognumber,
          organism,
          depositor,
          productionstatus,
          productname,
          depositdate,
          erhost
        },
		
		})
      }).then(res => {
        return res.json()
      }).then((data) => {
        this.set('data', data.recordsets[0]);
		//this.set('data', data.recordset);		
      }).catch((err) => {
        this.set('error', err);
		console.log(err);
      });
      
    },		
  }
});

/templates/components/home.hbs

{{!-- templates/components/home.js --}}
<header class="logo">
	<img src="assets/images/NEB_Logo-Long_White.png"  alt="logo">
</header>
<h2>{{this.title}}</h2>
{{#if data}}
<style>
  table, th, td {
    border: 1px solid black;
  }
  th, td {
    padding: 10px;
  }
</style>
<div class="table-wrapper">
 <table>
    <thead>
    <tr>
      <th>nebid</th>
      <th>catalognumber</th>
      <th>organism</th>
	  <th>depositor</th>
	  <th>productionstatus</th>
	  <th>productname</th>
	  <th>depositdate</th>
	  <th>erhost</th>				
    </tr>
  </thead>
   <tbody>
    {{#each data as |record|}}	
      <tr>
        <td>{{record.nebid}}</td>
        <td>{{record.catalognumber}}</td>
        <td>{{record.organism}}</td>
        <td>{{record.depositor}}</td>
        <td>{{record.productionstatus}}</td>
        <td>{{record.productname}}</td>
        <td>{{record.depositdate}}</td>
        <td>{{record.erhost}}</td>
      </tr>
    {{/each}}
    </tbody>
  </table>
  </div>
{{else}}
  <div>
    <form id="userSubmit" {{action "search12" on="submit"}}>
	
    	<label for="and">UNCHECKED "AND" CHECKED FOR "OR": 
      	<input type="checkbox" checked={{this.andor}} onclick={{action (mut this.andor) value="target.checked"}}>
    	</label>			
    	<hr>	
		
    	<label for="nebid">NEBID:</label>
    	<input id="nebid" value={{this.nebid}} oninput={{action (mut this.nebid) value="target.value"}}><br><br>
	    
		<label for="catalognumber">Catalog Number:</label>
		<input id="catalognumber" value={{this.catalognumber}} oninput={{action (mut this.catalognumber) value="target.value"}}><br><br>
		
		<label for="organism">Organism:</label>
    	{{input id="organism" class = "inputField" type="text" value=this.organism}}<br><br>

    	<label for="depositor">Depositor:</label>
    	{{input id="depositor" class = "inputField" type="text" value=this.depositor}}<br><br>
	
    	<label for="productionstatus">Status:</label>
    	{{input id="productionstatus" class = "inputField" type="text" value=this.productionstatus}}<br><br>

		<label for="productname">Product Name:</label>
    	{{input id="productname" class = "inputField" type="text" value=this.productname}}<br><br>

    	<label for="depositdate">Deposit Date:</label>
    	{{input id="depositdate" class = "inputField" type="date" value=this.depositdate}}<br><br>
    	
		<label for="erhost">ER Host:</label>
    	{{input id="erhost" class = "inputField" type="text" value=this.erhost}}<br>
    	<br>
		<hr>
		<br><br>
		<div>
    	  <button type="submit" name="search">Search</button> 
   	 </div> 
    </form>
  </div>
{{/if}}
1 Like

Hi

If no data is found the response is different:

Server started on port 3000
nebID: 3106
EXEC [neb].[usp_StrainSearchGetStrain2] 'AND' , '3106' , 'null' , 'julie' , 'null' , 'null' , 'null' , 'null' , 'null'
{
  recordsets: [ [] ],
  recordset: [],
  output: {},
  rowsAffected: [
    1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1,
    1, 1, 1, 0
  ]
}
Recordset Empty!

in my node js api I have this:

	  if (recordset.rowsAffected[15] == 0) {	  
        console.log(recordset);
		console.log('Recordset Empty!');
		response.send(recordset);
        sql.close();
      } else {
        console.log(recordset);
        response.send(recordset);
        sql.close();
      }

This works for detecting no records. But how would I handle the response to the user. Should I add logic to the template or the .js to handle this? Is there a quick way in the template?

Maybe somewhere around {{#if data}}

Does the #if data support conditional and else?

Something like

{{#if rowsAffected[15] <> 0}}

display output

else

display: "no records found"

I’ve been able to find rows affected and display in console. But I’ve been unable to send it to the template.

      }).then(res => {
        return res.json()
      }).then((data) => {
        this.set('data', data.recordsets[0]);
		//this.set('data', data.recordset[0]);
		//console.log(data);				
		
		//this doesnt seem to work
		rowsAffected = data.rowsAffected[15];
		
		//this displays rows affected in the console
		console.log(data.rowsAffected[15]);
		
		//this also displays rows affected in the console		
		console.log(rowsAffected);	
		
		//this doesnt seem to work
		this.set('rowsAffected', rowsAffected);
		
      }).catch((err) => {
        this.set('error', err);
		console.log(err);
      });

I added this to the template to try to display rowsAffected

   <tbody>
    {{#each data as |record|}}	
      <tr>	    
        <td>{{record.nebid}}</td>
        <td>{{record.catalognumber}}</td>
        <td>{{record.organism}}</td>
        <td>{{record.depositor}}</td>
        <td>{{record.productionstatus}}</td>
        <td>{{record.productname}}</td>
        <td>{{record.depositdate}}</td>
        <td>{{record.erhost}}</td>
      </tr>
	  <th>Rows</th>
	  <td>{{record.rowsAffected}}</td>
    {{/each}}
    </tbody>
'''

but nothing?

To display the ‘rowsAffected’ prop you want to use {{this.rowsAffected}} not {{record.rowsAffected}}. “record” is the current value of the each loop, whereas this references the backing class which is where things get attached with set. So this:

   <tbody>
    {{#each data as |record|}}	
      <tr>	    
        <td>{{record.nebid}}</td>
        <td>{{record.catalognumber}}</td>
        <td>{{record.organism}}</td>
        <td>{{record.depositor}}</td>
        <td>{{record.productionstatus}}</td>
        <td>{{record.productname}}</td>
        <td>{{record.depositdate}}</td>
        <td>{{record.erhost}}</td>
      </tr>
    {{/each}}
    <tr>
      <td>Rows:</td>
      <td>{{this.rowsAffected}}</td>
    </tr>
  </tbody>

… should be more in line with what you want.

And to answer your other question both {{#if}} and {{#each}} support else:

{{#if someCondition}}
  condition is true!
{{else}}
  condition is false!
{{/if}}

{{each records as |record|}}
  here is a {{record}}
{{else}}
  there are no records available!
{{/each}}

nice I was able to get rows affected when data is available.

but when I add this and there is 0 rows, it doesn’t display the rows? do I need a conditional in the each?

   <tbody>
    {{#each data as |record|}}	
      <tr>	    
        <td>{{record.nebid}}</td>
        <td>{{record.catalognumber}}</td>
        <td>{{record.organism}}</td>
        <td>{{record.depositor}}</td>
        <td>{{record.productionstatus}}</td>
        <td>{{record.productname}}</td>
        <td>{{record.depositdate}}</td>
        <td>{{record.erhost}}</td>
		</tr>
	{{else}}		
	  <th>Rows</th>
	  <td>{{this.rowsAffected}}</td>
	  <td>there are no records available!</td>
    {{/each}}
	  <th>Rows</th>
	  <td>{{this.rowsAffected}}</td>
    </tbody>

I noticed that when more than 1 record gets returned it shows blank for the rows affected. even if the rows affected has a value in the json results.

Do I need to add an async process?

Something like

async fetch("http://localhost:3000/routes", {

?