Pass dynamic content to template in Middleman - yaml

I'm building a static site using Middleman that has a portfolio section of all the client's recent projects.
The portfolio section will display project thumbnail images in a 3 X 3 gallery fashion and, when clicked on, will open their co-responding html page inside a lightbox.
The layout for the pages inside the light box is the same so rather than markup each individual page, I thought there would be a way for Middleman handling the content served from a yaml data file (projects.yml) using [a link.
Here's what I've got in my config.rb file
###
# Page options, layouts, aliases and proxies
###
# A path which all have the same layout
with_layout :popup do
page "/projects/*"
end
# Proxy (fake) files
# page "/this-page-has-no-template.html", :proxy => "/template-file.html" do
# #which_fake_page = "Rendering a fake page with a variable"
# end
data.projects.details.each do |pd|
proxy "/projects/#{pd[:client_name]}.html", "/projects/template.html", locals: { project: pd }, ignore: true
end

Ok so after some digging I came across the two posts below which helped me under stand how dynamic pages work in middleman. (Unfortunately there's not a lot of doco and the Middleman example for Dynamic pages is really basic)
http://benfrain.com/understanding-middleman-the-static-site-generator-for-faster-prototyping/
http://forum.middlemanapp.com/discussion/134/best-way-to-use-yaml-same-html-but-parameter-driven-data-fixed/p1
My solution...
data/projects.yml (contains project details)
details:
- client: "Company X"
title: "Company X Event"
video_url: ""
logo:
- "logo_companyx.gif"
image_path: "/img/projects/companyx"
total_images: 10
content: "<p>Blah blah blah</p>"
responsibilities:
"<li>Something</li>
<li>Some task</li>"
config.rb:
data.projects.details.each do |pd|
proxy "/projects/#{pd[:client]}.html", "/projects/template.html", :layout => false, :locals => { :project => pd }, :ignore => true
end
The trick with the snippet above is passing the entire project data object to the template via a proxy using locals and setting the layout to false so it doesn't inherit the default site layout (as I - or the client rather - want to display these in a lightbox popup)
The last step in the process was to create /projects/template.html.erb (in the source folder), declaring the following at the top of the template
<% p = locals[:project] %>
This allowed me to output each property of the p object within template.html.erb.
eg:
<%= p[:title] %>
I hope this helps someone as it took me a few days of playing around and LOTS of searching online for example or hints.

Related

Specifying parameters in yml file for Quarto

I am creating a quarto book project in RStudio to render an html document.
I need to specify some parameters in the yml file but the qmd file returns
"object 'params' not found". Using knitR.
I use the default yml file where I have added params under the book tag
project:
type: book
book:
title: "Params_TEst"
author: "Jane Doe"
date: "15/07/2022"
params:
pcn: 0.1
chapters:
- index.qmd
- intro.qmd
- summary.qmd
- references.qmd
bibliography: references.bib
format:
html:
theme: cosmo
pdf:
documentclass: scrreprt
editor: visual
and the qmd file looks like this
# Preface {.unnumbered}
This is a Quarto book.
To learn more about Quarto books visit <https://quarto.org/docs/books>.
```{r}
1 + 1
params$pcn
When I render the book, or preview the book in Rstudio the error I receive is:
Quitting from lines 8-10 (index.qmd)
Error in eval(expr, envir, enclos) : object 'params' not found
Calls: .main ... withVisible -> eval_with_user_handlers -> eval -> eval
I have experimented placing the params line in the yml in different places but nothing works so far.
Could anybody help?
For multi-page renders, e.g. quarto books, you need to add the YAML to each page, not in the _quarto.yml file
So in your case, each of the chapters that calls a parameter needs a YAML header, like index.qmd, intro.qmd, and summary.qmd, but perhaps not references.qmd.
The YAML header should look just like it does in a standard Rmd. So for example, your index.qmd would look like this:
---
params:
pcn: 0.1
---
# Preface {.unnumbered}
This is a Quarto book.
To learn more about Quarto books visit <https://quarto.org/docs/books>.
```{r}
1 + 1
params$pcn
But, what if you need to change the parameter and re-render?
Then simply pass new parameters to the quarto_render function
quarto::quarto_render(input = here::here("quarto"), #expecting a dir to render
output_format = "html", #output dir is set in _quarto.yml
cache_refresh = TRUE,
execute_params = list(pcn = 0.2))
For now, this only seems to work if you add the parameters to each individual page front-matter YAML.
If you have a large number of pages and need to keep parameters centralized, a workaround is to run a preprocessing script that replaces the parameters in all pages. To add a preprocessing script, add the key pre-render to your _quarto.yml file. The Quarto website has detailed instructions.
For example, if you have N pages named index<N>.qmd, you could have a placeholder in the YML of each page:
---
title: This is chapter N
yourparamplaceholder
---
Your pre-render script could replace yourparamplaceholder with the desired parameters. Here's an example Python script:
for filename in os.listdir(dir):
if filename.endswith(".qmd"):
with open(filename, "r") as f:
txt = f.read()
f.replace('yourparamplaceholder', 'params:\n\tpcn: 0.1\n\tother:20\n')
with open(filename, "w") as ff:
ff.write(txt)
I agree with you that being able to set parameters centrally would be a good idea.

Unable to click download button with web scraper

for whatever reason mechanize is unable to click this export button to automate downloading a public government csv file, i'm trying to automate downloading a fishing report, does anyone have any ideas on how to get it to work?
agent = Mechanize.new
url = 'https://nrm.dfg.ca.gov/FishPlants/'
log :debug, "reading HTML from #{url}"
page = agent.get(url)
log :debug, 'loaded page'
form = page.search('#aspnetForm').first
button = page.search('.application_button').first
log :debug, 'clicking Export button'
response = agent.submit(form, button)
i get a stack trace with the following error
...
form.add_button_to_query(button) if button
^^^^^^^^^^^^^^^^^^^^
/Users/aronlilland/.rvm/gems/ruby-3.1.2/gems/mechanize-2.8.5/lib/mechanize.rb:581:in `submit'
/Users/aronlilland/Documents/dev/fishing-report/tasks/download/fishing_report.rake:27:in `block (2 levels) in <top (required)>'
Tasks: TOP => download:fishing_report
(See full trace by running task with --trace)
the form is confirmed returns successfully, but the button is an input field
the page seems relatively straight forward, so dont know why i'm unable to scrape it - and its also public data
<form method="post" action="./" id="aspnetForm">
<!-- .... -->
<input
type="submit"
name="ctl00$cphContentMiddle$btnExport"
value="Export"
id="ctl00_cphContentMiddle_btnExport"
class="application_button"
>
<!-- .... -->
</form>
agent.submit got the wrong type for form
The issue here could be seen if you had included the beginning of the stack trace:
.../ruby/gems/3.1.0/gems/mechanize-2.8.5/lib/mechanize.rb:581:in `submit': undefined method `add_button_to_query' for #<Nokogiri::XML::Element:0x1ea14 name="form" attributes=
[#<Nokogiri::XML::Attr:0xbd38 name="method" value="post">, ...
Mechanize expects the submit to act on a Mechanize::Form instance, but instead got an instance of Nokogiri::XML::Element, as you can see by adding this to your code:
form.class # => Nokogiri::XML::Element
If you check the docs for the Mechanize::Form class, you can see the example they give to get you the form object is this:
form = page.forms.first # => Mechanize::Form
as opposed to what you used:
form = page.search('#aspnetForm').first
The call to search here is delegated to Nokogiri, and therefore doesn't return the object type you need, but rather a Nokogiri element.
button also has the wrong type
By the way the same applies to this line:
button = page.search('.application_button').first
If you fix the type of form, you'll get into a similar issue with button not being the expected type. Again, there's an example in the docs showing how a button is found:
agent.submit(page.forms.first, page.forms.first.buttons.first)
You'll need to figure out how to find the specific button you need though, I haven't worked with Mechanize before, so I can't offer a suggestion here. Presumably there's a way to convert the button you find through search to a Mechanize::Form::Button instance.

CKEDITOR4 Mentions Plugin with ajax : javascript error

I have a javascript error using CKEDITOR 4 and the Mentions Plugin.
I can't solve this problem for 2 days, I'm stuck.
I've used the online builder to get CKEDITOR + Mentions plugin.
See my build here: https://ckeditor.com/cke4/builder/fbe187b32ec7c025e28e01a537c72c62
With the following configuration it works fine: I see the drop down list with the names : Anna, Thomas, John
CKEDITOR.config.mentions = [{feed: ['Anna', 'Thomas', 'John']}];
However, when doing an ajax call to get the data, I got a javascript error:
The script /ajax_mention.php
displays
["Anna", "Thomas", "John"]
with the following configuration :
CKEDITOR.config.mentions = [{feed: '/ajax_mention.php'}];
when I type in the editor "#anna", the names do not display
the /ajax_mention.php script is launched and displays the correct data (when I look at the "network" tab on Chrome. see screenshot)
["Anna", "Thomas", "John"]
However, this triggers a javascript error (looking at the Chrome console tab. see screenshot)
ckeditor.js?1645882460:916 Uncaught TypeError: Cannot read properties of null (reading 'addClass')
at g.selectItem (ckeditor.js?1645882460:916:473)
at d.onSelectedItemId (ckeditor.js?1645882460:912:276)
at f.q (ckeditor.js?1645882460:10:246)
at f.fire (ckeditor.js?1645882460:12:91)
at f.select (ckeditor.js?1645882460:920:294)
at f.selectFirst (ckeditor.js?1645882460:920:371)
at d.open (ckeditor.js?1645882460:910:503)
at d.modelChangeListener (ckeditor.js?1645882460:911:234)
at f.q (ckeditor.js?1645882460:10:246)
at f.fire (ckeditor.js?1645882460:12:91)
See screen copy:
https://polyglotclub.com/bug_ckeditor_mentions.jpg
screen copy
The solution was given by the Ckeditor team : see https://github.com/ckeditor/ckeditor4/issues/5107
When we use a hardcoded data in the array, such as ['Anna, 'Geralt'] the createArrayFeed() function changes the input structure from the mentioned above to:
[
{
id: 1,
name: 'Anna'
},
{
id: 2,
name: 'Geralt'
}
]
I've just adjusted data on the backend side to the structure above.

How to get devcards working with shadow-cljs

My devcards used to work with Figwheel. However I can't get them to display with shadow-cljs.
Shadow emits this message:
shadow-cljs - HTTP server for :cards available at http://localhost:3450
The namespace cards.card-ui is just a series of requires.
I have a println message in cards.card-ui that is being displayed.
In shadow-cljs.edn I have two :builds. This is the second one:
:cards {:target :browser
:output-dir "resources/public/js/cards"
:asset-path "js/cards"
:modules {:main {:entries [cards.card-ui]}}
:compiler-options {:static-fns false}
:devtools {:http-root "resources/public"
:http-resource-root "resources/public"
:http-port 3450
:http-handler shadow.http.push-state/handle
:push-state/index "cards.html"
:preloads [devtools.preload
default-db-format.preload]}
:dev {:compiler-options {:devcards true}}
}
cards.html has a body tag that has a div tag that has id "app". I take the browser to http://localhost:3450/cards.html and just get a blank page. My best theory is that the cards.card-ui namespace is not being mounted at app.
Currently the only way to get an example Fulcro application that uses shadow-cljs rather than Figwheel is via the lein template. So at the command prompt:
lein new fulcro app shadow-cljs
Here app is any name you choose and shadow-cljs is an option. After studying the resultant application I realised that the namespace cards.card-ui should not just be a list of requires, but needs to have these lines as well:
(devcards.core/start-devcard-ui!)
(defn refresh []
(println "In cards.card-ui that starts the ui"))
The :cards build in shadow-cljs.edn becomes a bit simpler:
:cards {:target :browser
:output-dir "resources/public/js/cards"
:asset-path "js/cards"
:compiler-options {:devcards true}
:modules {:main
{:entries [cards.card-ui]}}
:devtools {:after-load cards.card-ui/refresh
:http-root "resources/public"
:http-port 3450}
}
Another thing I had wrong was the HTML (cards.html). Here is just the body tag of the markup:
<body>
<div id="app"></div>
<script src="js/cards/main.js"></script>
</body>
Some import pointers from the Devcards site: https://github.com/bhauman/devcards#usage-without-figwheel
The lein template project: https://github.com/fulcrologic/fulcro-lein-template

Select2 feature test with Capybara

I'm trying to write a rspec feature test with capybara but,
I have some trouble with a test on a select2 element.
see my test code.
Using feature test with capybara
feature "Backend Landing Pages" do
let!(:landing_page) { create(:landing_page, country: country) }
let!(:country) { create(:country, id: 2) }
let!(:restaurant) { create(:restaurant_with_locale) }
let!(:landing_page_restaurant) { create(:landing_page_restaurant,landing_page: landing_page, restaurant: restaurant) }
before(:each) do
login
click_on("Website")
click_on("Landing Pages")
end
scenario "user creates a landingpage", js: true do
first(:link,"netherlands").click
fill_in "landing_page_domain", with: landing_page.domain
fill_in "landing_page_slug_nl", with: landing_page.slug_nl
fill_in "landing_page_slug_en", with: landing_page.slug_en
fill_in "landing_page_header_title", with: landing_page.header_title
fill_in "landing_page_title", with: landing_page.title
attach_file "landing_page_header_image",( Rails.root + 'app/assets/images/site_builder/image3.jpg')
page.find('#landing_page_restaurant_select').set('Le Garage - Amsterdam')
page.save_screenshot ("test.png")
click_on("Save")
expect(page).to have_content("successfully created")
expect(LandingPage.count).to eq 2
expect(LandingPage.last.landing_page_restaurants.count).to eq 1
end
scenario "user edits a LandingPage", js: true do
click_on("Edit")
expect(page).to have_content 'Edit landing Page '
page.save_screenshot ("edit.png")
end
end
I'm getting the next error.
Failures:
1) Backend Landing Pages user creates a landingpage
Failure/Error: expect(LandingPage.last.landing_page_restaurants.count).to eq 1
expected: 1
got: 0
(compared using ==)
# ./spec/features/backend/landing_pages_spec.rb:33:in `block (2 levels) in <top (required)>'
Who can see what im doing wrong here
cant figure out why the restaurant is not connected to the landingPages
thanks in advance
In the question you mention you are using a select2, but then you are calling set on what I assume is a regular select element. When you are using JS widgets they will often overwrite the values of the hidden elements they build off on a submit event of the form they're in. Because of this it is probable the value you're setting is getting overridden. Rather than set the values of hidden elements (which most capybara drivers don't allow for this specific reason - not sure which driver you're using) you need to replicate the users behaviors. From the select2 examples page it appears the select2 widget is built from a span element and a ul list with the options in it. Therefore something along the lines of
find('span.select2').click # the selector may need to be more specific to locate the exact span, without your html I don't know what that selector would be
find('li', text: 'Le Garage - Amsterdam').click
This would click on the select2 element opening the dropdown, and then click on the li element with the correct text - thereby selecting it.

Resources