How do I render a nested data structure with Node.js/Jade - node.js

In my .js file I have the following data structure
var menu = { "Sport":"Racing",
"Region":{
"AUS":{ "name":"APrk", "key":"1234" },
"GB":{ "name":"Cran", "key":"5678" }
}
};
res.render('layout.jade', {locals: {menu: menu}});
In my layout.jade I have the following
for item in menu
p= item
This produces the following output
Racing
[object Object]
Which is on the right track. What I would like to know is how can I access the nested structures?

I believe you're looking for this
// layout.jade
each value, key in locals.menu
// may nest more iteration
each v, k in value
Iteration of object key/value does not grantee order. You may want to use array.
Details: https://github.com/visionmedia/jade#a9

Related

Persist data sent to a Pug template via render

I'm trying to find out how I can persist the data I pass to my Pug template from the Express render method.
I pass in some JSON data to the res.render() method in Express that renders my view with Pug. On the Pug template, I use that data immediately to populate one of my select elements with drop down values from the JSON data.
What I want to then do is store this data that was passed so I can use it in an event handler function I create for another field.
Basically, I'm passing a table name and the field names for the table, but for each table I have in the JSON data.
So the shape is like [{ tableName: "table name here", fieldNames: ['field1', 'field2', ...] }, ... ]
I have a select field for "choose a table name" and when the user picks a table name, I then want to get the fieldNames for a second select field that allows them to choose a field name to use. So I have an event handler setup on the "choose a table name" field that runs a little event handler I have setup in the pug template. Only problem is the event handler does not have access to the data that was passed to the Pug template originally.
I'm trying to google this but having no luck finding anything, so does anyone know how I can persist data sent via the res.render() method in a pug template for using after the page has been rendered inside an event handler or other functions?
Thank you!
Always be clear what is done at the server (pug) and what is done in client Javascript (browser).
While data passed to pug scripts are meant to be consumed at the server, it is possible to inject, for want of a better word, server data into client side Javascript variables.
The following creates two dropdown lists on the same page using the exact same data passed by Express. One is generated at the server, while the second is created entirely by Javascript running in the browser.
Express code:
app.get("/testdata", (req, res) => {
res.render("testdata", { data: [ 1, 2, 3, 4, 5]});
});
testdata.pug:
html
head
body
p Dropdown list generated at the server:
p
select
each n in data
option(value=n)=n
br
p Dropdown list generated in browser Javascript:
p
select#dropdown
script.
document.body.onload = () => {
const dropdown = document.getElementById("dropdown");
let data = JSON.parse(`!{JSON.stringify(data)}`); // line 18
data.forEach(d => {
const item = document.createElement("option");
item.innerText = d;
dropdown.appendChild(item);
})
}
I have used the same variable name in totally different contexts pointing to different entities. Be careful not to trip. For example, look at line 18:
let data = JSON.parse(`!{JSON.stringify(data)}`); // line 18
The first instance of data is a Javascript variable in the browser.
The second instance of data is a server object passed to the pug script in the render method. Basically any !{expression} instances found in a pug file are evaluated¹ when the view is rendered.
¹ I think the expression is evaluated and its toString method called. If I know it is an array, I could have used:
let data = [!{data}]; // line 18

change the expression in lwc component while iterating

We can use variables in aura components to concatenate some expression, we have to use variable name itself in lwc components, while looping how to change the lwc comp variable in js file.
I tried to access the dom using this.template.querySelector(); but this one is only giving the value if I use a rendered callback.
<template for:each={documentLinks} for:item="item">
//here I need to pass the item.ContentDocument.LatestPublishedVersionId to the end of a URL string
<img src={item.srcUrl} alt="PDF"/>
we can modify the returned data from apex but the data is proxy we cannot modify it.
One of the possible solutions to change the URL on the dom when it loads is to change the returned data from the server. here, In lightning web components the returned data is a proxy object, only readable. so we have to clone it(there are multiple ways to clone it), to make any changes. but here what I did.
therefore, overrides array is going to be the new data.
let overrides = [];
let newData = {
contentDocs: data[i],
srcUrl: '/sfc/servlet.shepherd/version/renditionDownloadrendition=thumb120by90&versionId=' + data[i]['ContentDocument']['LatestPublishedVersionId']
};
makeLoggable(newData);
overrides.push(newData);
function makeLoggable(target) {
return new Proxy(target, {
get(target, property) {
return target[property];
},
set(target, property, value) {
Reflect.set(target, property, value);
},
});
}

Display properties of nested GeoJson in Mapbox

I am unable to query nested properties feature, which is imported from a geojson source and displayed on a mapbox map.
I am able to get the feature with an on click listener, but as soon as the property is nested, the object is represented as a string
{
...
properties: {
address: "{"place":"Bern","street":"Fischerweg","street_nr":"11","zip":"3012"}"
price: 4500
}
}
Therefore I am only able to display the not-nested properties in a popup, when it is clicked.
my code:
const features = this.queryRenderedFeatures(e.point, {
layers: ['unclustered-point']
});
console.log(feature.properties.price) --> 4500
console.log(feature.properties.address) --> string instead of object {"place":"Bern","street":"Fischerweg","street_nr":"11","zip":"3012"}
console.log(feature.properties.address.street) --> undefined (--> NOT WORKING because of nested property)
const popup = new mapboxgl.Popup({offset: [0, -15]})
.setLngLat(feature.geometry.coordinates)
.setHTML('<h3>Price: ' + feature.properties.price + '</h3>
<p>Street: ' + feature.properties.address.street + '</p>')--> NOT WORKING because of nested property
.setLngLat(feature.geometry.coordinates)
.addTo(map);
I read that you can access nested properties with expressions, but how can expressions be applied in the html of the popup?
Is there a way how I can access the nested properties or do I have to redesign the geojson's structure?
any help is highly appreciated! Thank you so much,
Regards Simon
Since the property values are converted into JSON strings, you need a reverse conversion:
Object.keys(feature.properties).forEach(function(key) {
feature.properties[key] = JSON.parse(feature.properties[key]);
});
[ https://jsfiddle.net/s1d7hL82/ ]

Jade Cannot read property 'length' of undefined in each loop

I have this code in my controller retrieving an object from mongo and sending it to the client:
index: function(req, res){
List.find({user:req.session.user.id}).exec(function foundLists(error, foundLists) {
if(error) {
return res.json({error:error});
} else {
return res.view({ title:'Lists',lists:foundLists });
}
});
}
In my view I do the following:
extends ../layout
block content
.container
p #{lists}
Which renders:[object Object],[object Object]
If I do p= JSON.stringify(lists)
It renders:
[{"user":"546109c0d640523d1b838a32","name":"third","createdAt":"2014-11-11T19:39:36.966Z","updatedAt":"2014-11-11T19:39:36.966Z","id":"546265f83e856b642e3b3fed"},{"user":"546109c0d640523d1b838a32","name":"forth","createdAt":"2014-11-11T19:42:09.268Z","updatedAt":"2014-11-11T19:42:09.268Z","id":"546266913e856b642e3b3fef"}]
I'm trying to achieve:
#lists
each list in lists
p #{list}
But I get this error:
Cannot read property 'length' of undefined
I'm using Sails and Jade 1.7.0
You have an array of objects, so if you do each list in lists then list is an object. I assume Jade wants a string. If you put p #{list.name} or something similar that should work.
If you want to show everything you can try nesting your loops like
each list in lists
each item in list
p #{item}
This error can occurred when at least one of the items in the lists does not have a list property. You can protect this using an if statement:
each val in lists
if val.list
each item in val
p #{item}

How to display arbitrary, schemaless data in HTML with node.js / mongodb

I'm using mongodb to store application error logs as json documents. I want to be able to format the error logs as HTML rather than returning the plain json to the browser. The logs are properly schemaless - they could change at any time, so it's no use trying to do this (in Jade):
- var items = jsonResults
- each item in items
h3 Server alias: #{item.ServerAlias}
p UUID: #{item.UUID}
p Stack trace: #{item.StackTrace}
h3 Session: #{item.Session}
p URL token: #{item.Session.UrlToken}
p Session messages: #{item.Session.SessionMessages}
as I don't know what's actually going to be in the JSON structure ahead of time. What I want is surely possible, though? Everything I'm reading says that the schema isn't enforced by the database but that your view code will outline your schema anyway - but we've got hundreds of possible fields that could be removed or added at any time so managing the views in this way is fairly unmanageable.
What am I missing? Am I making the wrong assumptions about the technology? Going at this the wrong way?
Edited with extra info following comments:
The json docs look something like this
{
"ServerAlias":"GBIZ-WEB",
"Session":{
"urltoken":"CFID=10989&CFTOKEN=f07fe950-53926E3B-F33A-093D-3FCEFB&jsessionid=84303d29a229d1",
"captcha":{
},
"sessionmessages":{
},
"sessionid":"84197a667053f63433672873j377e7d379101"
},
"UUID":"53934LBB-DB8F-79T6-C03937JD84HB864A338",
"Template":"\/home\/vagrant\/dev\/websites\/g-bis\/code\/webroot\/page\/home\/home.cfm, line 3",
"Error":{
"GeneratedContent":"",
"Mailto":"",
"RootCause":{
"Message":"Unknown tag: cfincflude.",
"tagName":"cfincflude",
"TagContext":[
{
"RAW_TRACE":"\tat cfhome2ecfm1296628853.runPage(\/home\/vagrant\/dev\/websites\/nig-bis\/code\/webroot\/page\/home\/home.cfm:3)",
"ID":"CFINCLUDE",
"TEMPLATE":"\/home\/vagrant\/dev\/websites\/nig-bis\/code\/webroot\/page\/home\/home.cfm",
"LINE":3,
"TYPE":"CFML",
"COLUMN":0
},
{
"RAW_TRACE":"\tat cfdisplay2ecfm1093821753.runPage(\/home\/vagrant\/dev\/websites\/nig-bis\/code\/webroot\/page\/display.cfm:6)",
"ID":"CFINCLUDE",
"TEMPLATE":"\/home\/vagrant\/dev\/websites\/nig-bis\/code\/webroot\/page\/display.cfm",
"LINE":6,
"TYPE":"CFML",
"COLUMN":0
}
]
}
}
... etc, but is likely to change depending on what the individual project that generates the log is configured to trigger.
What I want to end up with is a formatted HTML page with headers for each parent and the children listed below, iterating right through the data structure. The Jade sample above is effectively what we need to output, but without hard-coding that in the view.
Mike's analysis in the comments of the problem being that of creating a table-like structure from a bunch of collections that haven't really got a lot in common is bang-on. The data is relational, but only within individual documents - so hard-coding the schema into anything is virtually impossible as it requires you to know what the data structure looks like first.
The basic idea is what #Gates VP described. I use underscore.js to iterate through the arrays/objects.
function formatLog(obj){
var log = "";
_.each(obj, function(val, key){
if(typeof(val) === "object" || typeof(val) === "array"){
// if we have a new list
log += "<ul>";
log += formatLog(val);
log += "</ul>";
}
else{
// if we are at an endpoint
log += "<li>";
log += (key + ": " + val);
log += "</li>";
}
});
return log;
}
If you call formatLog()on the example data you gave it returns
ServerAlias: GBIZ-WEBurltoken: CFID=10989&CFTOKEN=f07fe950-53926E3B-F33A-093D-3FCEFB&jsessionid=84303d29a229d1sessionid: 84197a667053f63433672873j377e7d379101UUID: 53934LBB-DB8F-79T6-C03937JD84HB864A338Template: /home/vagrant/dev/websites/g-bis/code/webroot/page/home/home.cfm, line 3GeneratedContent: Mailto: Message: Unknown tag: cfincflude.tagName: cfincfludeRAW_TRACE: at cfhome2ecfm1296628853.runPage(/home/vagrant/dev/websites/nig-bis/code/webroot/page/home/home.cfm:3)ID: CFINCLUDETEMPLATE: /home/vagrant/dev/websites/nig-bis/code/webroot/page/home/home.cfmLINE: 3TYPE: CFMLCOLUMN: 0RAW_TRACE: at cfdisplay2ecfm1093821753.runPage(/home/vagrant/dev/websites/nig-bis/code/webroot/page/display.cfm:6)ID: CFINCLUDETEMPLATE: /home/vagrant/dev/websites/nig-bis/code/webroot/page/display.cfmLINE: 6TYPE: CFMLCOLUMN: 0
How to format it then is up to you.
This is basically a recursive for loop.
To do this with Jade you will need to use mixins so that you can print nested objects by calling the mixin with a deeper level of indentation.
Note that this whole thing is a little ugly as you won't get guaranteed ordering of fields and you may have to implement some logic to differentiate looping on arrays vs. looping on JSON objects.
You can try util.inspect. In your template:
pre
= util.inspect(jsonResults)

Resources