Hugo: How to create PayPal partial

Overview

Acknowledgements

One should be aware that one's site will require JavaScript to function correctly. Further this guide requires some knowledge in creating templates in the layouts directory. This approach uses the power of single content pieces within a directory to populate a page. Thus one might need to adjust layouts/_default/single.html to get the desired outcome.

This guide has been written as a result to developing the store at the Munich Rucking Crew website1.

Goal

The goal is to create a drop-down with respective image and item name change. When clicking on Add to Cart one will be redirected to PayPal with the selected item already added to the cart.

The live example can be found on the Munich Rucking Crew's merch page.

Patch GORUCK Europe switch name and image relative to selection in the drop-down.

Requirements

One will need all the variations of a specific item and its name and image. Additionally, one will need to generate the PayPal HTML code, which looks like

 1<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank"> 
 2  <input type="hidden" name="cmd" value="_s-xclick" /> 
 3  <input type="hidden" name="hosted_button_id" value="<PAYPAL_TOKEN>" /> 
 4  <table> 
 5    <tr> 
 6      <td> 
 7        <input type="hidden" name="<PAYPAL_INIT_REFERENCE>" value="<PAYPAL_INIT_VALUE>"/> 
 8        <PAYPAL_INIT_NAME>
 9      </td> 
10    </tr> 
11    <tr> 
12      <td> 
13        <select name="os0"> 
14          <option value="<PAYPAL_ITEM_1_VALUE>"><PAYPAL_ITEM_1_NAME></option> 
15          <option value="<PAYPAL_ITEM_2_VALUE>"><PAYPAL_ITEM_2_NAME></option> 
16        </select> 
17      </td> 
18    </tr> 
19  </table> 
20  <input type="hidden" name="currency_code" value="EUR" /> 
21  <input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_cart_LG.gif" border="0" name="submit" title="PayPal - The safer, easier way to pay online!" alt="Add to Cart" /> 
22</form>

with

  • <PAYPAL_TOKEN> is the main PayPal hook, so it knows how to update the store relative to all further actions taken within the form
  • <PAYPAL_INIT_REFERENCE> usually as on0
  • <PAYPAL_INIT_VALUE> is usually equivalent to <PAYPAL_ITEM_1_VALUE>
  • <PAYPAL_INIT_NAME> is usually equivalent to <PAYPAL_ITEM_1_NAME>
  • <PAYPAL_ITEM_1_NAME> is usually equal to <PAYPAL_ITEM_1_VALUE>
  • <PAYPAL_ITEM_2_NAME> is usually equal to <PAYPAL_ITEM_2_VALUE>

Setup

One will first create the paypal-button.html partial template2, then the list-store.html partial template, and finally the two items to trigger the paypal-button partial template.

Creating the paypal-button partial template

  1. Create a new file layouts/partials/paypal-button.html.
  2. Add this code
 1{{ $output := "" }}
 2{{ $uniqueID := .uniqueID }}
 3{{ $output = print $output (printf "<div class=\"paypal-button-%s\">" $uniqueID) }}
 4{{ $output = print $output "<form action=\"https://www.paypal.com/cgi-bin/webscr\" method=\"post\" target=\"_blank\">" }}
 5{{ $output = print $output "<input type=\"hidden\" name=\"cmd\" value=\"_s-xclick\" />" }}
 6{{ $output = print $output (printf "<input type=\"hidden\" name=\"hosted_button_id\" value=\"%s\" />" .paypal) }}
 7{{ if .items }}
 8  {{ $output = print $output "<table>" }}
 9  {{ $output = print $output ( printf "<tr><td><input type=\"hidden\" name=\"on0\" value=\"%s\"></td></tr>" (index .items 0).value )}}
10  {{ $output = print $output "<tr>" }}
11  {{ $output = print $output "<td class=\"paypal-cell\">" }}
12  {{ $output = print $output "<span>Choose:</span>" }}
13  {{ $output = print $output (printf "<select id=\"item-select-%s\" class=\"paypal-select\" name=\"os0\">" $uniqueID) }}
14  {{ range .items }}
15    {{ $output = print $output (printf "<option value=\"%s\" data-image=\"%s\">%s</option>" .value .image .name) }}
16  {{ end }}
17  {{ $output = print $output (printf "</select></td></tr></table>") }}
18  {{ $selectedValue := index .items 0 }}
19  {{ $output = print $output (printf `<script>
20    document.getElementById("item-select-%s").addEventListener("change", function() {
21      var selectedValue = this.value;
22      var selectedOption = this.options[this.selectedIndex];
23      var selectedImage = selectedOption.getAttribute("data-image");
24      var selectedItemContainer = document.getElementById("selected-item-%s");
25      selectedItemContainer.querySelector(".selected-value").innerText = selectedValue;
26      selectedItemContainer.querySelector(".selected-image").src = "/" + selectedImage;
27    });
28    document.getElementById("selected-item-%s").querySelector(".selected-value").innerText = "%s";
29    document.getElementById("selected-item-%s").querySelector(".selected-image").src = "/%s";
30    </script>` $uniqueID $uniqueID $uniqueID $selectedValue.value $uniqueID $selectedValue.image | safeJS) }}
31{{ end }}
32{{ $output = print $output "<input type=\"hidden\" name=\"currency_code\" value=\"EUR\" />" }}
33{{ $output = print $output "<input type=\"image\" src=\"https://www.paypalobjects.com/en_US/i/btn/btn_cart_LG.gif\" border=\"0\" name=\"submit\" title=\"PayPal - The safer, easier way to pay online!\" alt=\"Add to Cart\" />" }}
34{{ $output = print $output "</form>" }}
35{{ $output = print $output "</div>" }}
36{{ return $output }}
  1. Create the file layouts/partials/list-store.html.
  2. Add this code within the tag that holds the id="main" (assuming the theme uses this approach).
 1<!-- theme hugo header plus beginning of body -->
 2
 3<ul class="grid-list" style="list-style: none;">
 4  {{ range .Pages }}
 5    {{ if not .Params.draft}}
 6      <li>
 7        <h3>{{.Title}}</h3>
 8        {{ $uniqueID := .File.UniqueID }}
 9        {{ if not .Params.paypalItems }}
10          <img src="/{{.Params.image}}"/>
11        {{ end }}
12        {{ if .Params.paypalItems }}
13          <div id="selected-item-{{ $uniqueID }}">
14            <span class="selected-value">{{ (index .Params.paypalItems 0).value }}</span>
15            <img class="selected-image" src="/{{ (index .Params.paypalItems 0).image }}" />
16          </div> <!-- Default to the first item's value -->
17        {{ end }}
18        <p>{{.Params.description}}</p>
19        <div>{{.Content}}</div>
20        <p>{{.Params.price}} € per item</p>
21        {{ $itemsOutput := partial "paypal-button" (dict "paypal" .Params.paypal "items" .Params.paypalItems "headName" .Params.paypalHeadName "headValue" .Params.paypalHeadValue "uniqueID" $uniqueID) }}
22        {{ if $itemsOutput }}
23          <div class="items-container">
24            {{ $itemsOutput | safeHTML }}
25          </div>
26        {{ end }}
27        <p class="paypal-hint">(*) You will be redirected to PayPal and can purchase more items at once.</p>
28      </li>
29    {{ end }}
30  {{ end }}
31</ul>
32
33<!-- theme hugo footer -->

The code should also work within generic HTML5 template.

  1. Connect the layouts/_default/list.html to reference the newly created list-style.html file. One option is to use the listType approach.
  2. Add the styles to the file based off of the theme used.
 1.grid-list {
 2  display: grid;
 3  grid-template-columns: repeat(3, 1fr);
 4  grid-gap: 10px;
 5  padding: 0;
 6  margin: 0;
 7  list-style: none;
 8}
 9
10.grid-list li {
11  background-color: #f0f0f0;
12  padding: 10px;
13  text-align: center;
14}
15
16.grid-list p {
17  margin: 0 0 1rem 0;
18}
19
20/* Styles for the PayPal button */
21[class^="paypal-button-"] {
22  display: flex;
23  justify-content: left;
24  align-items: center;
25}
26
27[class^="paypal-button-"] form {
28  width: 100%;
29  margin: 0 0 1rem 0;
30}
31
32[class^="paypal-button-"] .paypal-select {
33  background-image: none;
34  border: 1px solid #3396c5;
35  cursor: pointer;
36}
37
38[class^="paypal-button-"] .paypal-cell {
39  text-align: left;
40}
41
42.paypal-hint {
43  font-size: 0.6rem;
44}
  1. Create the files content/post/store/item-1.md and content/post/store/item-2.md.
  2. Each Markdown file should have its meta data like
 1+++
 2title = 'Item: Name'
 3slug = 'item-name'
 4image = 'path/to/image/1.webp'
 5description = 'item description'
 6disableComments = true
 7
 8paypal = '<PAYPAL_TOKEN>'
 9price = '<your_price>'
10
11paypalHeadName = '<PAYPAL_INIT_REFERENCE>'
12paypalHeadValue = '<PAYPAL_INIT_VALUE>'
13
14[[paypalItems]]
15name = '<PAYPAL_ITEM_1_NAME>'
16value = '<PAYPAL_ITEM_1_VALUE>'
17image = 'path/to/image/1.webp'
18
19[[paypalItems]]
20name = '<PAYPAL_ITEM_2_NAME>'
21value = '<PAYPAL_ITEM_2_VALUE>'
22image = 'path/to/image/2.webp'
23+++
24
25<your_description_of_the_item>

where all values should be adjusted accordingly. The first 5 parameters are based off of the theme in use, so adjusted if needed.

  1. The file tree looks like
 1content/
 2|_  post/
 3    |_  store/
 4        |_ item-1.md
 5        |_ item-2.md
 6
 7layouts/
 8|_  partials/
 9    |_  paypal-button.html
10    |_  list-store.html

Details

Approach listType

Hugo permits multiple ways to assign what list type should be reference relative to the directory. One approach is to use the listType parameter within the _index.md file.

This means if one looks back at the file tree, one would create the content/post/store/_index.md file.

 1content/
 2|_  post/
 3    |_  store/
 4        |_ _index.md
 5        |_ item-1.md
 6        |_ item-2.md
 7
 8layouts/
 9|_  partials/
10    |_  paypal-button.html
11    |_  list-store.html

This index file holds all the meta data relevant for the subdirectory store. It looks like

1+++
2title = "Store"
3listType = "list-store.html"
4+++
5
6Welcome to our store!

Further one will need to update the layouts/_default/list.html file to use the listType approach. The new file looks like

1{{ if .Params.listType }}
2  {{ partial .Params.listType . }}
3{{ end }}

The final file tree looks like

 1content/
 2|_  post/
 3    |_  store/
 4        |_ _index.md
 5        |_ item-1.md
 6        |_ item-2.md
 7
 8layouts/
 9|_ _default/
10|   |_ list.html
11|
12|_  partials/
13    |_  paypal-button.html
14    |_  list-store.html

Code explanation

For a more in depth explanation of how this code has come to be, one can read through the Qoto Mastodon thread3. Only regard the posts that start with #DailyBloggingChallenge. The other ones are commentary on the thread.

Got Comments !?

By default bf5.eu has comments deactivated. Nonetheless, this post has a Qoto Mastodon thread3, where comments can be made.

References


  1. Munich Rucking Crew's website store, Development, Production ↩︎

  2. Hugo partial templates, Source ↩︎

  3. In depth explanation on Qoto Mastodon, Source ↩︎ ↩︎