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.
Related
I'm working on tincan JavaScript API. The issue my data format is total change and TinCan have specified a why to pass data along with call. Help me to adjust my data in TinCan Api format. Here is sample data one of my call.
var data = {
"groupId": "groupId",
"groupName": "gNameEncrypt",
"tutorNames": "tutorNames",
"actorNames": "actorNames",
"otherNames": "otherNames"
};
Current what i do i simply decode this data and send it like this.
var actionList = new TinCan(
{
recordStores: [{
endpoint: "http://example.com",
username: username,
password: password,
allowFail: false
}]
});
var action = new TinCan.Agent({
"name": "insert"
});
actionList.getStatements({
'params': {
'agent': action,
'verb': {
'id': $.base64.encode(data)
}
},
'callback': function (err, data) {
console.info(data.more);
var urlref = "http://<?php echo $_SERVER['SERVER_NAME'] . ":" . $_SERVER['SERVER_PORT'] . $uriParts[0] . "?" ?>t=" + data.more.TutorToken;
window.location.href = urlref;
}
});
crypt.finish();
});
There are really two parts here:
need to get data into an xAPI (formerly Tin Can) format, and
the code itself.
In depth,
I think you need to take another look at how xAPI is used in general. Data is stored a JSON "Statement" object that has 3 required properties and various other optional ones. These properties often contain complex objects that are very extensible. It is hard to tell from what you've shown what you are really trying to capture and what the best approach would be. I suggest reading some material about the xAPI statement format. http://experienceapi.com/statements-101/ is a good starting point, and to get at least some coverage of all the possibilities continue with http://experienceapi.com/statements/ .
The code you've listed is attempting to get already stored statements based on two parameters rather than trying to store a statement. The two parameters being "agent" and "verb". In this case We can't tell what the verb is supposed to be since we don't know what data contains, I suspect this isn't going to make sense as a verb which is intended to be the action of a statement. Having said that the fact that the "actor" has a value of action is questionable, as that really sounds more like what a "verb" should contain. Getting the statements right as part of #1 should make obvious how you would retrieve those statements. As far as storing those statements, if you're using the TinCan interface object you would need to use the sendStatement method of that object. But this interface is no longer recommended, the recommended practice is to construct a TinCan.LRS object and interact directly with it, in which case you'd be using the saveStatement method.
I would recommend looking at the "Basic Usage" section of the project home page here: http://rusticisoftware.github.io/TinCanJS/ for more specifics look at the API doc: http://rusticisoftware.github.io/TinCanJS/doc/api/latest/
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.
I am running an angular app. I am having the strangest effect ever...
I am calling a backend which returns a json. I parse that json and build an object structure client side. It works perfectly in dev but the exact same code does provide strange effects on prod. See code inline comments for hints. The only thing I could think of is that the data comes different from prod...
I can't see what's wrong, as it's the exact same code, and it's driving me completely nuts, probably the worse thing I ever saw in 10+ years programming!
Basically the json structure is a list of objects, every object has a reference ID, and several objects are correlated by the same reference ID - I need a structure where I'd access all objects with the same reference ID.
Maybe I'll make a fool of myself here but I really can't see it...I just ran the data in two JSON validators and both say the data is valid.
app.factory('ItemService', ['ItemProvider', function(itemProvider) {
var itemSrv;
var obj_by_id = {}; //create empty object
itemSrv = function(callback) {
itemProvider.get_data()
.success(function(data) { // callback ok, data has json content
for (var i=0; i<data.length; i++) {
obj = data[i]; // I get the object in the json
if (! (obj.identificador in obj_by_id)) {
obj_by_id[obj.identificador] = {}; //build a key in the object if not already available
}
obj_by_id[obj.identificador][obj.campo_nombre] = obj; //assign the object by keys
console.log(obj_by_id); **//HERE obj_by_id is ALWAYS EMPTY!!!! BUT ONLY ON PROD!!! On dev works fine...**
}
callback(obj_by_id); //Here I would get the whole structure, but it's empty on prod...
})
.error(function(data) {
console.log("Error getting data from server");
});
}
//factory function body that constructs shinyNewServiceInstance
return itemSrv;
}]);
EDIT: console.log(data) right after success, on request
dev:
http://imgur.com/10aQsx2,rJJw2bb#0
prod:
http://imgur.com/10aQsx2,rJJw2bb#1
EDIT2: you can have a look at the returned data here (will remove this link later!): http://bibliolabs.cc/milfs/api.php/ison?id=2
I am concerned about all those \u unicode chars, could that be an issue?
Questions
How to serve javascript file dynamically? Specifically, the scripts maintain most of its body but with some variables changable (imagine HTML Jade template, but this is for pure javascript).
Scenario
When user or browser (http GET in general) visits /file.js passing parameter api, e.g. /file.js?api=123456, I would like to output pure javascript where I can take that 123456 and put in inside of my code, dynamically. Content-Type is application/javascript.
Sample:
var api = #{req.query.api}; //Pseudo
//The rest of my javascripts template
...
From my main .js file, I have set up the route:
app.get( '/file.js', function( req, res ) {
//Pseudo code that I would like to achieve
var name = req.query.name;
res.render( 'out_put_javascript_file_from_jade_file.jade', { name: name } );
});
So when a person visits /file.js, the script file will be rendered differently based on the parameter api passed in the URL. The only possible dynamic way I can think of is using Jade, but it doesn't allow pure javascript template. I believe there must be other solutions.
Please excuse my explanation. The problem is somewhat like this: How to generate a pure JavaScript file with Jade
If you want to do something quick and dirty, then you can do something like this (based on your example in the comments).
App init - read the .js template file and cache it:
// this should be async, but hey, not teaching you that part here yet
var fileJs = fs.readFileSync('file.js.template');
File.js:
(function() {
$(window).on('load', function() {
alert('Your api key is API_KEY_CONST');
});
})();
Request:
GET /api/file.js?key=123
Router:
app.get('/api/file.js', function(req, res) {
var key = req.query.key;
var key = fetchKeyFromDBSync(); // just to make it easier here, no async.
var out = fileJs.replace(API_KEY_CONST, key);
res.setHeader('content-type', 'text/javascript');
res.write(out);
res.end();
});
Now, this is really dumb and you should not try it at home, but it simply demonstrates how to do what you wanted.
Edit:
Depending on the file length, you might perform a bit better if you put the chunks of the file into an array, like:
var fileChunks = ['(function(){ blablabla;', 'var myAPIKey=', 'KEY_PLACEHOLDER', '; alert (myAPIKey);', '})()']
So later when you're resolving it with the real API key, you join the file.
fileChunks[2] = '12345';
var responseData = fileChunks.join('');
res.write(responseData);
But your last-accessed api key is then held in an array. Not quite future proof, but it shouls work if you need something quick.
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!