Unable to loop through array when using 'eleventy-cache-assets' - json

In eleventy, I'm using the 'eleventy-cache-assets' utility to retrieve API data from TMDB and place it in a cache. The JSON is successfully retrieved and stored in the cache. I can also use the Nunjucks dump filter to dump the full JSON to a page. However, I cannot run a for loop over the JSON content. It's behaving as if the data does not exist. I'm likely making a schoolboy error here but I can't see it.
Here's the JS that retrieves the data (successfully).
module.exports = async function () {
try {
// Grabs either the fresh remote data or cached data (will always be fresh live)
let json = await Cache(
`https://api.themoviedb.org/3/movie/upcoming?api_key=${TOKEN}&language=en-GB&region=GB`,
{
duration: "1d", // 1 day
type: "json",
}
);
return {
films: json,
};
} catch (e) {
return {
films: 0,
};
}
};
Here is how I'm trying to loop through the content. The else condition is returning. When I remove the else condition, nothing is returned (just the empty ul). If I've targeted the node incorrectly, I should have x number of empty li tags, but I don't.
<ul>
{% for film in films %}
<li>{{ results.title }}</li>
{% else %}
<li>This displays if the 'films' collection were empty</li>
{% endfor %}
</ul>

The issue is likely just that you're wrapping the result from the API in an additional object, and eleventy adds an additional wrapper based on the file name of the data file, so you need to modify the way you access the results.
According to the TMDB documentation, the API will return something like this (JSON-encoded):
const json = {
page: 1,
results: [
{/** ... */},
{/** ... */},
{/** ... */},
]
}
Then you're wrapping that result in another object:
return {
films: json,
};
Finally, eleventy makes that data available to templates under a variable name based on the name of your data file — i.e. the file contains your API call. You didn't mention the name of the file, let's assume it lives in your data directory as tmdb.js. That means the variable accessible to your template will look something like this:
const tmdb = {
films: {
page: 1,
results: [
{/** ... */},
{/** ... */},
{/** ... */},
]
}
}
See the documentation on how eleventy parses data files for more information.
This means you can access the results in this way:
<ul>
{% for film in tmdb.films.results %}
<li>{{ film.title }}</li>
{% else %}
<li>This displays if the 'films' collection were empty</li>
{% endfor %}
</ul>
Note also that your original code used {{ results.title }} which isn't defined in your scope, I changed that to use the loop variable film. Also, adjust the tmdb variable to the name of your data file.
I would also recommend not wrapping the response of the API in an additional object (the key films also implies it's a list of films, not the complete API response). It doesn't really add anything and only increases complexity.

Related

Include additional data/value in Vuejs v-for loop

I'm trying to include external data/value into a v-for loop in VueJS. Have no idea how to go about it. Basically I have data/value in value as follows:
External Data
{{ time.one }}
{{ time.two }}
NOTE: I am receiving the data exactly as above as its coming from an API
JSON (own data)
{
"persons": [
{
"title": "Prof",
"name": "SomeProf"
},
{
"title": "Dr",
"name": "SomeDr"
},
]
}
And the loop is basic v-for loop
<ul v-for="person in persons">
<li> {{ person.title }}{{ person.name }} - <!-- Inc. external data {{ time.one }} -->
</ul>
The end result being:
Prof. SomeProf - 10:00pm
Dr. SomeDr - 09:00am
Thank You
I don't know if I understood your question properly, but you can also just modify the data right away.
Let's say you have your own data and make a call to the API when the component is created. What you can do is:
import persons from '#/data/persons'
export default {
data() {
return {
dataToLoop: []
}
},
async created() {
const timeData = await yourApiCall();
let results = [];
for (let i in persons) {
results.push({
...persons[i],
time: timeData[i] // or whatever key matches the person
})
}
this.dataToLoop = results;
}
}
And then all you need to do is loop through "dataToLoop" in your component.

How to render specific json response data fields on frontend web using django

I am using django with templates and trying to send my json response data to the frontend web page. But in the frontend ui i want to show only specific fields of the json response data which i am unable to figure out. Right now i can send the complete json response data and show it in the frontend web page.
Here is my code details -
This function connects to my backend index and gets each document from index and appends it to jsonitems dictionary.
def sampledata(request):
samplecount = requests.get(sampleindex + "/_count", auth=auth, verify=sslcheck)
print(samplecount.json()['count'])
count = samplecount.json()['count']
jsonitems = {}
for item in range(count):
data = requests.get(sampleindex + "/_doc/" + str(item), auth=auth, verify=sslcheck)
jsondata = data.json()
jsonitems[item] = jsondata
print(jsonitems)
context = {'jsonitems': jsonitems}
return render(request, 'samplewebapp/sampledata.html', context)
This is the template view which i am using to render on the frontend web ui.
{% if jsonitems %}
<ul>
{% for k, v in jsonitems.items %}
<table>
<tr><th> Sample data Item </th></tr>
<tr>
<td>{{ v }}</td>
</tr>
</table>
{% endfor %}
</ul>
{% else %}
<p>No CVE data available</p>
{% endif %}
Currently table data 'v' (ie value) shows the complete json data. But i want to show only specific fields from the json data on the frontend (as in v['year'] and v['title'] should show me year and title respectively).
eg. JSON response data
{
"year": 2013,
"title": "Monica Z",
"info": {
"directors": [
"Per Fly"
],
"release_date": "2013-09-13T00:00:00Z",
"rating": 7.3,
"genres": [
"Biography",
"Drama",
"Music"
],
"image_url": "http://ia.media-imdb.com/images/M/MV5BODA5NzUyNDA0M15BMl5BanBnXkFtZTgwODY5MjQwMDE#._V1_SX400_.jpg",
"plot": "Ambitious singer must struggle with her musical career, her love life and to bring up her daughter by herself.",
"rank": 4957,
"actors": [
"Edda Magnason",
"Sverrir Gudnason",
"Kjell Bergqvist"
]
}
Actually there is no json here, because the result of data.json() will be a regular python object (in this case a dictionary). So v (i.e. value) is a dictionary and in the django templates the keys are available as attributes: v.year, for example.

Terraform - Iterate over a List of Objects in a Template

I'm having issues iterating over a list of objects within a template interpreted by the templatefile function.
I have the following var:
variable "destinations" {
description = "A list of EML Channel Destinations."
type = list(object({
id = string
url = string
}))
}
This is passed in to the templatefile function as destinations. The snippet of template relevant is this:
Destinations:
%{ for dest in destinations ~}
- Id: ${dest.id}
Settings:
URL: ${dest.url}
%{ endfor }
When planning Terraform this gives an error of:
Error: "template_body" contains an invalid YAML: yaml: line 26: did not find expected key
I have tried switching the template code to the following:
Destinations:
%{ for id, url in destinations ~}
- Id: ${id}
Settings:
URL: ${url}
%{ endfor }
Which gives a different error:
Call to function "templatefile" failed:
../../local-tfmodules/eml/templates/eml.yaml.tmpl:25,20-23: Invalid template
interpolation value; Cannot include the given value in a string template:
string required., and 2 other diagnostic(s).
[!] something went wrong when creating the environment TF plan
I get the impression my iterating over the data type here is somehow incorrect but I cannot fathom how and I cannot find any docs about this at all.
Here is a cut down example of how I'm calling this module:
module "eml" {
source = "../../local-tfmodules/eml"
name = "my_eml"
destinations = [
{
id = "6"
url = "https://example.com"
},
{
id = "7"
url = "https://example.net"
}
]
<cut>
}
I've just found (after crafting a small Terraform module to test templatefile output only) that the original config DOES work (at least in TF v0.12.29).
The errors given are a bit of a Red Herring - the issue is to do with indentation within the template, e.g. instead of:
Destinations:
%{ for destination in destinations ~}
- Id: ${destination.id}
Settings:
URL: ${destination.url}
%{ endfor ~}
it should be:
Destinations:
%{~ for destination in destinations ~}
- Id: ${destination.id}
Settings:
URL: ${destination.url}
%{~ endfor ~}
Notice the extra tilde's (~) at the beginning of the Terraform directives. This makes the Yaml alignment work correctly (you get some lines incorrectly indented and some blank lines). After this the original code in my question works as I expected it to & produces valid yaml.
You can't pass var.destinations as a list of maps to the template. It must be list/set of strings.
But you could do the following:
templatefile("eml.yaml.tmpl",
{
ids = [for v in var.destinations: v.id]
urls = [for v in var.destinations: v.url]
}
)
where eml.yaml.tmpl is
Destinations:
%{ for id, url in zipmap(ids, urls) ~}
- Id: ${id}
Settings:
URL: ${url}
%{ endfor ~}
Since you are aiming to generate a YAML result, I suggest following the advice in the templatefile documentation about generating JSON or YAML from a template.
Using the yamlencode function will guarantee that the result is always valid YAML, without you having to worry about correctly positioning newlines or quoting/escaping strings that might contain special characters.
Write your templatefile call like this:
templatefile("${path.module}/templates/eml.yaml.tmpl", {
destinations = var.destinations
})
Then, in the eml.yaml.tmpl, make the entire template be the result of calling yamlencode, like this:
${yamlencode({
Destinations = [
for dest in destinations : {
Id = dest.id
Settings = {
URL = dest.url
}
}
]
})
Notice that the argument to yamlencode is Terraform expression syntax rather than YAML syntax, because in this case it's Terraform's responsibility to do the YAML encoding, and all you need to do is provide a suitable value for Terraform to encode, following the mappings from Terraform types to YAML types given in the yamldecode documentation.

external JSON data fails to output anything in Jekyll

I'm trying to fetch external JSON data into Jekyll and things are not working out.
I found this code (link to a gist), that is a fork of another gist... this just adds the external method ("from url").
I tried to morph that into my own tag plugin (or what ever they are called), in order to simplify it and possibly resolve some issues:
_plugins/externaljson.rb
require 'json'
require 'net/http'
module ExternalJSON
class ExternalJSON_tag < Liquid::Tag
def initialize(tag_name, text, tokens)
super
#text = text
end
def render(context)
if /(.+) from (.+)/.match(#text)
url = context[$2].strip
uri = URI( url )
response = Net::HTTP.get( uri )
data = JSON.parse( response )
context[$1] = JSON data
return ''
end
end
end
end
Liquid::Template.register_tag('externalJSON', ExternalJSON::ExternalJSON_tag)
...but I didn't really solve all my issues or learn much of anything from that. The only thing I think I learned is that the problem is likely somewhere between parsing the file in ruby and jekyll reading it.
I ran this test using the tag plugin code above(↑):
---
layout: default
---
<!-- Using the code I modified -->
<!-- This capture exists to combine a string and a variable, but it's just a static url for the purposes of this example -->
{% capture myUrl %}
https://api.guildwars2.com/v2/recipes/2889
{% endcapture %}
{% externalJSON jsonData from myUrl %}
{% for data in jsonData %}
{{ data}}
{% endfor %}
<!-- Jekyll's native way of handling local data files -->
<!-- I just saved that json data from the url above(↑) locally for this one -->
{% for data in site.data.example %}
{{ data }}
{% endfor %}
This test made me realize that both methods output the data slightly differently.
My external attempt:
{"type":"Meal","output_item_id":12210,"output_item_count":2,"min_rating":0,"time_to_craft_ms":1000,"disciplines":["Chef"],"flags":[],"ingredients":[{"item_id":24359,"count":1},{"item_id":12132,"count":1}],"id":2889,"chat_link":"[&CUkLAAA=]"}
Jekyll's native method (for local files)
{"type"=>"Meal", "output_item_id"=>12210, "output_item_count"=>2, "min_rating"=>0, "time_to_craft_ms"=>1000, "disciplines"=>["Chef"], "flags"=>[], "ingredients"=>[{"item_id"=>24359, "count"=>1}, {"item_id"=>12132, "count"=>1}], "id"=>2889, "chat_link"=>"[&CUkLAAA=]"}
And if I try to do for example {{ data.type }}, my external attempt returns nothing and the Jekyll method returns the value just like it should. I just can't figure out how to change the formatting or what ever the missing piece is there.
What am I doing wrong?
replace your render(context) with following:
def render(context)
if /(.+) from url (.+)/.match(#text)
resp = Net::HTTP.get_response(URI($2.strip))
data = resp.body
context[$1] = JSON data
nil
else
# syntax error
raise ArgumentError, 'ERROR:bad_syntax'
end
end
Then call the data like so:
{% externalJSON data from url http://foo.json %}
This will provide you with a data object that can be called to render individual keys.
If the data is an array, loop through the elements and call the desired key
{% for entry in data %}
{{ entry.type }}
{% endfor %}
If the data is an object (hash), call the key directly.
{{ data.type }}

Can't access data from html/template Golang

I have three concatenated templates. base.html, menu.html, users.html. But when I execute these templates I can access data of context only from base.html.
Here is my Handler:
func HandlerUser(res http.ResponseWriter, req *http.Request){
if req.Method == "GET" {
context := Context{Title: "Users"}
users,err:= GetUser(0)
context.Data=map[string]interface{}{
"users": users,
}
fmt.Println(context.Data)
t,err := template.ParseFiles("public/base.html")
t,err = t.ParseFiles("public/menu.html")
t,err = t.ParseFiles("public/users.html")
err = t.Execute(res,context)
fmt.Println(err)
}
}
This is what I want to show in users template
{{ range $user := index .Data "users" }}
<tr id="user-{{ .Id }}">
<td id="cellId" >{{ $user.Id }}</td>
<td id="cellUserName" >{{ $user.UserName }}</td>
</tr>
{{ end }}
Note: I can access "{{.Title}}" that is used in base.html template.
First, you should check errors returned by the Template.ParseFiles() method. You store the returned error, but you only check it at the end (and by then it is overwritten like 3 times).
Next, never parse templates in the request handler, it's too time consuming and resource wasting. Do it once at startup (or on first demand). For details see It takes too much time when using "template" package to generate a dynamic web page to client in golang.
Next, you can parse multiple files at once, just enumerate all when passing to the Template.ParseFiles() function (there is a method and a function).
Know that Template.Execute() only executes a single (named) template. You have 3 associated templates, but only the "base.html" template is executed by your code. To execute a specific, named template, use Template.ExecuteTemplate(). For details, see Telling Golang which template to execute first.
First you should define a structure of your templates, decide which templates include others, and execute the "wrapper" template (using Template.ExecuteTemplate()). When you execute a template that invokes / includes another template, you have the possibility to tell what value (data) you what to pass to its execution. When you write {{template "something" .}}, that means you want to pass the value currently pointed by dot to the execution of the template named "something". Read more about this: golang template engine pipelines.
To learn more about template association and internals, read this answer: Go template name.
So in your case I would imagine that "base.html" is the wrapper, outer template, which includes "menu.html" and "users.html". So "base.html" should contain lines similar to this:
{{template "menu.html" .}}
{{template "users.html" .}}
The above lines will invoke and include the results of the mentioned templates, passing the data to their execution that was passed to "base.html" (if dot was not changed).
Parse the files using the template.ParseFiles() function (not method) like this:
var t *template.Template
func init() {
var err error
t, err = template.ParseFiles(
"public/base.html", "public/menu.html", "public/users.html")
if err != nil {
panic(err) // handle error
}
}
And execute it like this in your handler:
err := t.ExecuteTemplate(w, "base.html", context)