I need to figure out how to print the full JSON response onto my page (or even parts of it), but I can't seem to figure it out. I am going to eventually fill out the page with more context later.
JS file:
app.get('/', function(req, res) {
var context
apiLib.fetch('acct:chars', function(err, result){
if(err) throw err
context = result;
console.log(result);
res.render('/', context);
});
});
Handlebars:
{{#each context.characters}}
{{this.name}}
{{/each}}
JSON Data:
{
"result": {
'1234':{ //this is the character ID
"characters": {
"name": 'Daniel',
"CharacterID": '1234'
etc...
}
}
}
My page prints nothing, but console logs everything. I am using express.js to handle the routing.
If you want to display the stringified JSON, you can use an helper
JS
Handlebars.registerHelper('toJSON', function(obj) {
return JSON.stringify(obj, null, 3);
});
HTML
<pre>{{toJSON result}}</pre>
One problem is the each helper, here: {{#each context.characters}}. What each does is look within the current context for the variable. You've already passed Handlebars the context object when you called res.render('/', context). So now Handlebars is looking within the context object for an attribute called context, and it won't find it. So you should change that line of code to {{#each characters}}.
The same thing applies to where you wrote {{this.name}}. I think that will actually work if you fix the other problem, but the this is unnecessary, because Handlebars looks at this (the current context) automatically. So just {{name}} should be fine there.
Finally, if you're really trying to just display the JSON file as a page of plain text, you don't need to run it through a Handlebars template. You can just use res.json(context). This will return a JSON response. If your server is configured to handle the application/json MIME type, it should render as plain text in the browser.
Since you are iterating over objects in handlebars, it should be done as -
{{#each context.characters}}
{{#each this}}
{{#key}} - {{this}}
{{/each}}
{{/each}}
If you are using it with Node.js and mongoDB this answer is for you;
Let's say you have the following response from your database called data:
{
_id: "5AAAAAAAAAe04fc1a1", //some id idk
BUSINESS: 'FLEX TAPE',
SHOWMAN: 'PHIL SWIFT',
CONTACT: 'PHIL SWIFT',
PHONE: '11111111113',
ETC: 'yes',
}
What you might want to look for to display the fields is to take advantage from client-side rendering. piping the data with the classic JSON.stringify(data) as okData for instance.. then you can just reference it using the {{}} notation.
e:g.
<p id="some-id" style="display: none"> {{okData}} </p>
<script>
let jsonStrLookup = document.getElementById('some-id').innerHTML
let js = JSON.parse(jsonStrLookup)
console.log(js)
</script>
This would work for SIMPLE applications hope it helps out, and if something seems off comment, I always check on here, and will try to help you.
Related
I have a NodeJS/Express app that is using Handlebars for templates.
All of the templates and partials load fine except where I am returning data from an Express API.
The data is returned and I can see it in the Chrome debugger.
In this template, I am defining the HTML in a script and compiling it in JS.
Here is the template HTML:
<script id="search-result-template" type="text/x-handlebars">
<div>String</div>
{{#each patient}}
<div>
{{cp_first_name}}
</div>
{{!-- {{> searchresultpartial}} --}}
{{/each}}
</script>
The actual page is quite a bit more structured but I've narrowed it down to this for debugging.
Here is the code that compiles the template:
let patientSearchButton = document.getElementById('patient-search-execute');
patientSearchButton.addEventListener("click", async function (e) {
e.preventDefault();
let patientSearchFirstname = document.getElementById('patient-search-firstname')
let cp_first_name = patientSearchFirstname.value;
let url = baseURL + 'patientsearchquery/' + cp_first_name;
const response = await fetch(url, {
method: 'get',
headers: {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json'
}
});
var data = response.json();
let patientList = await data;
patient = patientList;
if (response.status === 200) {
let patientSearchResultTemplate = document.querySelector("#search-result-template").innerHTML;
let patientSearchResultTemplateFunction = Handlebars.compile(patientSearchResultTemplate);
let patientSearchResultTemplateObject = patientSearchResultTemplateFunction(patient);
let contentPartial = document.getElementById('patient-search-table');
contentPartial.innerHTML = patientSearchResultTemplateObject;
if (Handlebars.Utils.isArray(patient)) {
console.log("Array");
} else {
console.log("Not");
}
console.log(patient);
} else {
alert("HTTP-Error: " + response.status);
}
});
I can see the data from the API and I'm verifying that Handlebars sees it as an Array.
It seems to break when it enters the #each helper.
I've tried to shift the context with ../ and I've tried every variation I can think of the coax the data into the template.
I was concerned that being in an event handler tied to a button click, that the "this" context was breaking. I moved the code outside of the event handler and "this" seemed to be correct in Chrome but the behavior did not change.
Here is the array data in Chrome:
When paused on a breakpoint in Chrome, I can see that the patient data is present when being passed to the template.
I know it's something dumb but my head hurts from banging it against the wall.
This has happened on two different templates. Granted, they were similar, but I've tried numerous variations and it still isn't loading.
Thanks for any help you might offer.
Does anybody see anything obvious?
Addendum:
I changed the code to pass the property and I can see it in Chrome now.
It still doesn't show up in Handlebars.
this.patients shows the data in the console. Why won't it render the variable?
The {{#each patient}} in your template anticipates that the data that you pass to your template function has a property named patient and that the value of this property is iterable - like an Array.
However, it appears that you are simply passing an Array directly to your template function, and that Array does not have a patient property, and so the #each loop never executes.
One way to make this work would be to pass an Object to your template function and to assign to that Object a key, patient, with its value being your Array of patients.
This would require changing:
let patientSearchResultTemplateObject = patientSearchResultTemplateFunction(patient);
to:
let patientSearchResultTemplateObject = patientSearchResultTemplateFunction({ patient: patient });
Or use the shorthand:
let patientSearchResultTemplateObject = patientSearchResultTemplateFunction({ patient });
Note: As patient is an Array of Patient Objects, if I were on your team, I would urge you to add an "s" to its name to make it plural.
Ultimately, the problem was that the data was seen Handlebars as a Sequelizer Object. It was unable to access the prototype due to a security update in Handlebars 4.6.0.
Rather than using "allowInsecurePrototypeAccess" which is a widely suggested fix but one that seems like unsound advice, I made a deep copy of the data by using JSON.stringify() and JSON.parse() and Handlebars was able to access it.
It was a context error, just not the one I was expecting.
I started studying node.js, and now I'm trying to do a "Todo-App".
I'm trying to find the best way to transfer data from my database (using mongodb) into my hbs files, so I could display it.
From the server.js -> server to the hbs -> client (correct to me if I'm wrong please, by assuming that server.js is the server of course and the hbs file is the client)
So, I succeeded to do it by passing an array.
but when I'm trying to display in html desing, it just looking bad.
The code:
app.get('/allTasks',(req,res)=>{ //get (go to) the allTasks (hbs file)
Todo.find().then((todos) => {
console.log(todos);
var arrayOfTodos = [];
todos.forEach(function(element){
console.log("\n\n\n\n\n elemnt details: ",element.text + "\n",element.completed+"\n");
arrayOfTodos.push(element.text,element.completed);
});
res.render("allTasks.hbs", {
pageTitle: "Your tasks: ",
todos: arrayOfTodos
});
});
});
The result is:
You can see a picture
As you can see, its just looking bad... cause it just display an array,
an I want to display each task seperately.
Any tips?
Thanks a lot,
Sagiv
Instead of using push just do:
Todo.find().toArray(function(err, result){
arrayOfTodos = result;
})
Once you have your array, the design got nothing to do with mongodb. You will need to learn how to use your render technology. You need to touch your html template, so you should start by posting that.
The problem solved.
I just had to learn how to handle the data in the hbs side.
so the code is: (in hbs)
{{#each todos}}
{{missionNumber}} <br>
{{text}}<br>
completed = {{completed}}<br><br>
{{/each}}
as you can see, the each is a loop , that pass on the todos parameter (my array)
and i just have to display the data in the way i want it to be displayed.
thanks for your help.
This is the json from my sever:
data = {"id":393, "state":"mississippi", "lat":null, "lng":null, "title":"someTitle", "country":null};
I then pass that through a Jade temple like so: res.render('editUpload', {fromServer:data});
Then in my Jade template I have this:
var data = !{fromServer};
console.log(data.address);
Which correctly prints mississippi
However, I'm trying to get my mixins to use that data
Mixin:
mixin textBoxMixin(name, label, value)
div(style="display:flex; width:100%;")
span.label #{label}
input(type="text", name="#{name}", value="#{value}")
Then I use that like this:
+textBoxMixin("title", "Title", fromServer)
which fills in the text box with the entire json "{"id":393, "state":"mississippi", "lat":null, "lng":null, "title":"someTitle", "country":null}" so I go and try this:
+textBoxMixin("title", "Title", fromServer.title)
it prints "undefined".
How do I get this mixin to accept what should just be fromServer.title?
I've tried JSON.stringify(fromServer), data.address (which obviously doesn't work because data is undefined during the rendering of the page). I know that I could go back to my server and parse out all the json there and pass each item as an individual item, but that would be a giant pain since my json is actually much larger than what I've posted here.
The problem was that I was sending the json from the route.js file to the template after having passed it through JSON.stringify(), this let me use var data = !{fromServer}; in the page's scripts, but Jade couldn't parse the String. So now I'm doing this in the route so that I have both the json available for templating and the String for JavaScript:
data = rows[0][0];
stringFromServer = JSON.stringify(rows[0][0]);
res.render('editUpload', {fromServer:data, stringFromServer:stringFromServer, username: req.session.user});
When you reference parameters in your mixin definition, you pass them exactly as if it were normal pug code, without quotes and braces.
mixin textBoxMixin(name, label, value)
div(style="display:flex; width:100%;")
span.label= label
input(type="text", name=name, value=value)
+textBoxMixin("title", "Title", fromServer.title)
Here's what renders:
Also, Jade is now Pug, and I know you know that since you tagged Pug in this question, you should start referencing it as Pug :).
So thanks to SO I can pass an object from node to the client, but then getting it into a knockout view model is a bit awkward. These are the steps I have so far (I've included links to the relevant lines as they appear in my github project. Thought the context might help.):
Apply JSON.stringify and pass to the jade file
recipeJSON: JSON.stringify(recipe);
Wrap this in a function in a header script that just parses the JSON and returns the result
script
function getRecipeObject() {
var r = '!{recipeJSON}';
return JSON.parse(r);
}
Call this function and pass the result to a view model constructor
self.recipe = ko.observable(new Recipe(getRecipeObject()));
This works but is there a better way?
Question clarification (Edit): I feel step 2 shouldn't be necessary. Is there a way to directly pass the JSON from node to the Recipe() constructor, without the getRecipeObject() acting as an intermediate step? I tried passing recipeJSON in directly like so
self.recipe = ko.observable(JSON.parse('!{recipeJSON}'));
That doesn't work I think because its not a jade template and has no access to the variable.
According to the answer to this question rendering data into scripts is bad practice and I should instead make an XHR call on page load instead.
Edit
I just saw you linked a github repo! So you're already familiar with most of this...you even have an endpoint set up at /recipe/:id/view, so now I'm really confused...what isn't working out for you? Just the last step of deserialization using ko.utils.*?
Sorry about all the exposition -- I thought this was way more rudimentary than it actually was; I hope no offense taken there!
You really don't want to return a script to execute -- instead, treat this as a DTO: an object that just stores data (no behaviors). An example would be:
{
recipeID: 12,
reviewIDs: [42, 12, 55, 31],
rating: 4.2
recipeName: "A super tasty pie!"
}
This object (representation) is a projection -- a simplified version of the full data stored in the database.
The next step is to create an endpoint to access that data on the server. Let's assume you're using Express:
var app = express();
app.get('/recipes/:recipeID', function(req, res) {
var recipeID = req.params.recipeID;
// It would be cool if this existed, huh?
getRecipeAsync(recipeID, function(recipe) {
res.status(200).json(recipe);
});
});
If you send a GET request to your (hypothetical) application (let's say it's https://localhost:8080/recipes/12), you'll get json representing the (admittedly imaginary) recipe with ID 12.
You can accomplish getting the JSON with jQuery (or any other library that makes XHR nice and pretty)
var recipeID = 12;
$.ajax({
url: "/recipes/" + recipeID,
type: "GET"
}).then(function(recipe) {
console.log("Hey! I got the recipe: %O", recipe);
// Note: you might need to use ko.utils.fromJS(recipe) if the returned
// data is JSON that ISN'T deserialized into an object
var recipeObservable = ko.utils.fromJS(recipe);
});
That's about everything you need to know. Obviously, the devil's in the details, but that's basic idea; let me know if that helps!
I don't get what I'm doing wrong.
I'm trying to populate a form from a JSON string from the server and it doesn't work. I get nothing at all back. I examine the object and it's undefined. I've been beating my head against the wall for 3 days now. I need a simple example that works and I'll build from there.
Here's the simple example that I've been trying to use:
var messages = new Ext.data.JsonStore({
url: '/user/' + user_id,
method: 'GET',
root: 'user',
fields: [
{name: 'user_id'},
{name: 'first_name'}
],
listeners: {
load: messagesLoaded
}
});
messages.load();
function messagesLoaded(messages) {
console.log(messages);
}
Here's my JSON string:
{"success":"true","user":{"user_id":"2","first_name":"Test","last_name":"Test","email":null,"password":null,"city_id":"6379","birth_date":"2009-06-09","gender":"F","created_on":"2009-06-01 17:21:07","updated_on":"2009-06-14 17:20:14","active":"Y","cuisine_id":null}}
I really don't see what I'm doing wrong, but my JSON string isn't loading. Thanks!
Ok so you're almost there, but one problem. The root ("user" in this case) has to be an array. Even if it's an array with only 1 object. Ext.data.JsonReader (the default reader for a Ext.data.JsonStore) only accepts an array of results.
So your javascript looks just fine, but the JSON object returned by the server needs to look more like this.
{
"success":"true",
"user": [{
"user_id":"2",
"first_name":"Test",
"last_name":"Test",
"email":null,
"password":null,
"city_id":"6379",
"birth_date":"2009-06-09",
"gender":"F",
"created_on":"2009-06-01 17:21:07",
"updated_on":"2009-06-14 17:20:14",
"active":"Y",
"cuisine_id":null
}]
}
One more thing, consoloe.logging your store object will produce something like [Object] in Firebug... not too useful. You should either console.dir it, or log your actual data instead.
One comment about loading your form, once you get past loading your JSON (even though this example does not show that). Make sure your form is actually rendered before trying to load it with data, e.g. if trying to use something like form.loadRecord. Otherwise you'll end up with an empty form and no errors.