am trying to render a json array which is sent from a java servlet in ember. the work flow goes like follows
1.sending an ajax request to the servlet which will return the jsonobject
2.i will get the json object and try to render in the handlebar file
the problem is when i consoled the response it came with an indexes
0: {CAPACITY: '4', PRICE: '918', RTYPE: 'basic', ID: '4'}
1: {CAPACITY: '2', PRICE: '885', RTYPE: 'villa', ID: '8'}
2: {CAPACITY: '2', PRICE: '1579', RTYPE: 'villa', ID: '5'}
3: {CAPACITY: '1', PRICE: '1526', RTYPE: 'villa', ID: '1'}
but what the server sent was
{"CAPACITY":"4","PRICE":"918","RTYPE":"basic","ID":"4"}
{"CAPACITY":"2","PRICE":"885","RTYPE":"villa","ID":"8"}
{"CAPACITY":"2","PRICE":"1579","RTYPE":"villa","ID":"5"}
{"CAPACITY":"1","PRICE":"1526","RTYPE":"villa","ID":"1"}
the controller code
export default class RoomselectController extends Controller {
@service router;
@tracked obj = null ;
@tracked price =0;
get res(){
var dis = this;
const mobile = "123123123";
$.ajax({
url:'My',
method : 'GET',
dataType:'json',
data : {mobile:mobile},
success:function(response){
console.log(response);
dis.price = response[0].PRICE;//when rendered this works
dis.obj = response;//when changed to dis.obj = response[0] it renders the first
return response[0];
},
error: function(xhr,status,error){
var errorMessage = xhr.status ": " xhr.statusText;
alert("error " errorMessage);
}
})
}
the .hbs code
<table border ="1">
<tr><td><b>Capacity</b></td><td><b>Price</b></td><td><b>Room Type</b></td><td><b>Room No</b></td></tr>
{{#each-in this.obj as |key value|}}
<td>{{value}}</td>
{{/each-in}}
</table>
it is not working but when i use
dis.obj = response[0]
it works and renders only the first index
how to make it render every data?
CodePudding user response:
Async in JS reactivity is kinda non-trivial -- but! there are many abstractions to help out.
Here is a video/talk that goes over the problem, and various approaches to solving it: "What is the hardest thing TIMING! about async?" -- Jim Schofield, Emberconf 2022
The gist is a the following:
- getters should never cause side-effects (setting anything on
this
within a getter) - getters should never return a Promise.
Some notes about what I see in your code:
- the getter,
res
, is never accessed, so it cannot run. - the getter,
res
, does not return anything, so accessing it doesn't make sense -- there is a lint in modern ESLint configs that will complain about getters not returning something - effects occur within the getter, which makes overall state of your component unstable -- remember that the content of a getter is re-ran every time it is accessed -- so if you had multiple things accessing
this.res
, you would create new ajax requests per access. - the template iterates over an object, but ignores the other entries in the response array -- not sure if intentional, but in the following code, I'll assume you want to iterate over all entries in the response.
But! it's not too much work to convert this into something that
- models async lifecycle (loading, success, error, etc)
- reactively provides you the above states as well as gives you something to iterate over (which you ultimately want to do with your
each-in
in your template.
My favorite way to do this is with a Resource.
There are a couple ways to implement what you want with the utilities provided by the library ember-resources
.
With a utility, trackedFunction
This is a utility that wraps the Resource primitives and provides some async state.
Your code with this utility would look like this:
import { trackedFunction } from 'ember-resources/util/function';
export default class RoomselectController extends Controller {
@service router;
request = trackedFunction(this, async () => {
const mobile = '123123123';
// I'm constructing a custom promise here
// because I don't know if your $.ajax supports async/await
return new Promise((resolve, reject) => {
$.ajax({
url: 'My',
method: 'GET',
dataType: 'json',
data: { mobile },
success: (response) => resolve(response),
error: (xhr, status, error) => reject(`${status}: ${xhr.statusText}`),
});
});
});
get result() {
return this.request.value || [];
}
}
{{#if this.request.isPending}}
loading data...
{{/if}}
<table border ="1">
<thead>
<tr>
<th>Capacity</th>
<th>Price</th>
<th>Room Type</th>
<th>Room No</th>
</tr>
</thead>
<tbody>
{{#each this.result as |row|}}
<tr>
{{#each-in row as |key value|}}
{{value}}
{{/each-in}}
</tr>
{{/each}}
</tbody>
</table>
With a custom Resource
The second way you could do this is to use your own state, created by you and used within a custom resource
.
Using the same template, you could implement a custom resource like this:
import { use, resource } from 'ember-resources';
import { tracked } from '@glimmer/tracking';
class RequestState {
@tracked value;
@tracked error;
get isPending() {
return !this.error && !this.value;
}
}
export default class RoomselectController extends Controller {
@service router;
@use request = resource(({ on }) => {
const mobile = '123123123';
const state = new RequestState();
$.ajax({
url: 'My',
method: 'GET',
dataType: 'json',
data: { mobile },
success: (response) => state.value = response;,
error: (xhr, status, error) => state.error = `${status}: ${xhr.statusText}`,
});
return state;
});
get result() {
return this.request.value || [];
}
}
References
- (my) Ember Cookbook for various concepts
- demos of various component / template concepts
- information about
Resource
s - API docs for
ember-resources
(library)