Source code for each page of the present demo site

Step1

    ---
title: A HTML page
summary: This page is a static HTML page.
---
    <h2>This is a pure HTML file</h2>
<p>This is a pure HTML file in the filesystem that Pages renders as if it were an Article.</p>
<p><a href="https://github.com/joomlatools/joomlatools-pages/wiki" target="_blank">Joomlatools Pages' Wiki</a></p>

Step2

    ---
title: A Markdown page
summary: This page is rendered by the Markdown engine.
---
    ## MarkDown

This is a simple ".md" (markdown) file in the filesystem that Pages renders as if it were an Article

Welcome to my new page in **MarkDown** format.

Step3

    ---
title: A PHP file that Pages renders as if it were an Article
summary: This page is rendered by the PHP engine.
---
    <h2><?= $title ?></h2>
<? $greeting = 'Hello World' ?>
<p><?= sprintf('%s from PHP', $greeting); ?></p>
<p>Today is <?= date('l h:i', 'now'); // Prints something like: Monday 10:56 ?></p>

<ktml:partial format="md">
**Why not** even put some *markdown* written directly in our PHP file?
</ktml:partial>

Step4

    ---
title: A PHP page fetching external data from json
summary: This page is rendered by the PHP engine.
---
    <h2>Fetching data from external json</h2>
<p>directly from <a target="_blank" href="https://social.brussels/rest/organisation/13817">https://social.brussels/rest/organisation/13817</a></p>
<h3>In Dutch</h3>
<h4>->nameOfficialNl<h4>
<p><pre><code><?= data('https://social.brussels/rest/organisation/13817', '1day')->nameOfficialNl; ?></code></pre></p>
<h4>->legalStatus->labelNl<h4>
<p><pre><code><?= data('https://social.brussels/rest/organisation/13817', '1day')->legalStatus->labelNl; ?></code></pre></p>
<h3>In French</h3>
<h4>->nameOfficialFr<h4>
<p><pre><code><?= data('https://social.brussels/rest/organisation/13817', '1day')->nameOfficialFr; ?></code></pre></p>
<h4>->legalStatus->labelFr<h4>
<p><pre><code><?= data('https://social.brussels/rest/organisation/13817', '1day')->legalStatus->labelFr; ?></code></pre></p>
<h3>That was easy peasy!</h3>

Step5

    ---
@collection:
    model: database?table=content
@process:
    filters: highlight
---
    <h2>Blog view using database?table=content</h2>
<p>Hereafter we list all articles <em>directly from the database</em></p>
<p>Note: we could do the same with the Users table for example and allow Search on it. See <a target="_blank" href="https://github.com/joomlatools/joomlatools-pages/discussions/506">https://github.com/joomlatools/joomlatools-pages/discussions/506</a></p>
<hr>
<div class="well">
<? foreach(collection() as $article): ?>
   <div style="background: white; padding: 20px;">
        <? $article_images  = json_decode($article->images);?>
       <h3><b>Title:</b> <?= $article->title ?></h3>
       <p><img src="<?= $article_images->image_intro; ?>" alt="<?= $article_images->image_intro_alt; ?>" title="<?= $article_images->image_intro_caption; ?>"></p>
       <p><b>Published on:</b> <?= date('d M Y', $article->published_date); // // Prints something like: 08 Jun 2021 ?></p>
       <p><b>Category ID:</b> <?= $article->catid ?></p>
       <p><b>Introtext:</b> <?= $article->introtext ?></p>
   </div>
   <hr>
<? endforeach; ?>
</div>
<p>And here is a little <code>var_dump(collection())</code> so that you can see all what is immediately available in the array itself:</p>
<pre><code><?= var_dump(collection()); ?></code></pre>

Step6

    ---
@collection:
    model: ext:joomla.model.articles
    state:
        published: 1
        category: [organizations]
        sort: date
        order: asc
        limit: 3
@process:
    filters: highlight
---
    <h2>Blog view using <samp>ext:joomla.model.articles</samp> and links with the Joomla router</h2>
<p>Note: I am just adding a few CSS lines in my PHP file in order to display the Articles as nice Cards. That CSS is put into the <samp>head</samp> of the page automatically by Pages.</p>
<style>
ul.pages-cards {
   display: grid;
   grid-template-columns: repeat(3, 1fr);
   grid-gap: 20px;
   margin-left: 0; /* otherwise we have the natural left margin of the Unordered List */
}
ul.pages-cards > li {
   background: white;
    display: flex;
   flex-direction: column;
   padding: 10px;
   border: 1px solid lightgray;
   box-shadow: 3px 3px 2px 1px rgba(0, 0, 0, 0.2);
   transition: 0.5s;
}
ul.pages-cards > li:hover {
    box-shadow: 3px 3px 2px 1px rgba(0, 0, 0, 0.4);
}
</style>

<div class="well">
    <ul class="pages-cards">
       <? foreach(collection() as $article): ?>
          <? $data = data('https://social.brussels/rest/organisation/'. $article->fields->get('socialbrusselsid')->value, '1day') ?>
          <li>
             <h3><a href="<?= route($article) ?>"><?= $article->title; ?></a></h3>
             <p><img src="<?= $article->image->url; ?>" alt="<?= $article->image->alt; ?>" title="<?= $article->image->caption; ?>"></p>
             <p><b>Category: <?= $article->category->title; ?></b></p>
             <p><small><?= 'This article was published on ' . date('d M Y', $article->published_date) . ' for the demo'; ?></small></p>
             <p><b>Excerpt (ex-introtext):</b></p><?= $article->excerpt ?>
             <p><b>Text (ex-fulltext):</b></p><?= $article->text ?>
             <p><b>(ex-introtext + ex-fulltext):</b></p><?= $article ?>
             <p><b>Custom Field Label and Value:</b><br /><?= $article->fields->socialbrusselsid->label ?>: <?= $article->fields->socialbrusselsid->value ?></p>
             <p><b>Name from external json:</b><br /><?= $data->nameOfficialNl ?></p>
             <p><b>Address from external json:</b><br /><?= $data->address->number ?> <?= $data->address->streetNl ?> <?= $data->address->postalCodeNl ?></p>
          </li>
       <? endforeach ?>
    </ul>
</div>
<hr>
<p>And here is a little <code>var_dump(collection())</code> so that you can see all what is immediately available in the array itself:</p>
<pre><code><?= var_dump(collection()); ?></code></pre>

Step7

    ---
@collection:
    model: ext:joomla.model.articles
    state:
        published: 1
        category: [organizations]
        sort: date
        order: asc
@process:
    filters: highlight
---
    <h2>Blog view using <samp>ext:joomla.model.articles</samp> and links with the Joomla router (and partials for Custom Fields)</h2>

<style>
ul.pages-cards {
   display: grid;
   grid-template-columns: repeat(3, 1fr);
   grid-gap: 20px;
   margin-left: 0; /* otherwise we have the natural left margin of the Unordered List */
}
ul.pages-cards > li {
   background: white;
   display: flex;
   flex-direction: column;
   padding: 10px;
   border: 1px solid lightgray;
   box-shadow: 3px 3px 2px 1px rgba(0, 0, 0, 0.2);
   transition: 0.5s;
}
ul.pages-cards > li:hover {
    box-shadow: 3px 3px 2px 1px rgba(0, 0, 0, 0.4);
}
</style>

<div class="well">
    <ul class="pages-cards">
       <? foreach(collection() as $article): ?>
          <li>
             <h3><a href="<?= route($article) ?>"><?= $article->title; ?></a></h3>
             <p><img src="<?= $article->image->url; ?>" alt="<?= $article->image->alt; ?>" title="<?= $article->image->caption; ?>"></p>
             <p><b>Category: <?= $article->category->title; ?></b></p>
             <p><small><?= 'This article was published on ' . date('d M Y', $article->published_date) . ' for the demo'; ?></small></p>
             <p><b>Excerpt (ex-introtext):</b></p><?= $article->excerpt ?>
             <p><b>Text (ex-fulltext):</b></p><?= $article->text ?>
             <p><b>(ex-introtext + ex-fulltext):</b></p><?= $article ?>
             <p><?= partial('/embeds/socialbrussels.html', ['id' => $article->fields->socialbrusselsid->value]) ?></p>
             <p><?= partial('/embeds/url.html', ['url' => $article->fields->youtube->value]) ?></p>
             <p><?= partial('/embeds/youtube.html', ['url' => $article->fields->youtube->value]) ?></p>
          </li>
       <? endforeach ?>
    </ul>
</div>
<hr>
<p>And here is a little <code>var_dump(collection())</code> so that you can see all what is immediately available in the array itself:</p>
<pre><code><?= var_dump(collection()); ?></code></pre>

Step7 Article embed social.brussels

Partial in /templates/partials/embeds/socialbrussels
    <? $data = data('https://social.brussels/rest/organisation/'. $id, '1day') ?>
<h4>social.brussels data (using a reusable 'partial')</h4>

<ul>
   <li><b>id:</b> <a href="<?= 'https://social.brussels/rest/organisation/'. $id ?>" target="_blank"><?= $id ?></a></li>
   <li><b>FRENCH</b></li>
   <ul>
   <li><b>nameOfficialFr:</b> <?= $data->nameOfficialFr ?></li>
   <li><b>legalStatus->labelFr:</b> <?= $data->legalStatus->labelFr ?></li>
   <li><b>emailFr:</b>
      <ul>
         <? foreach( $data->emailFr as $email){ ?>
            <li><?= $email ?></li>
         <? } ?>
      </ul> 
   </li>
   <li><b>address->number + address->streetNl + address->postalCodeNl:</b> <?= $data->address->number ?> <?= $data->address->streetNl ?> <?= $data->address->postalCodeNl ?></p></li>
   </ul>
   <li><b>DUTCH</b></li>
   <ul>
   <li><b>nameOfficialNl:</b> <?= $data->nameOfficialNl ?></li>
   <li><b>legalStatus->labelNl:</b> <?= $data->legalStatus->labelNl ?></li>
   <li><b>emailNl:</b>
      <ul>
         <? foreach( $data->emailNl as $email){ ?>
            <li><?= $email ?></li>
         <? } ?>
      </ul> 
   </li>
   <li><b>address->number + address->streetFr + address->postalCodeFr:</b> <?= $data->address->number ?> <?= $data->address->streetFr ?> <?= $data->address->postalCodeFr ?></p></li>
   </ul>
</ul>

<?//= partial('/embeds/openstreetmap.html') ?>

Step7 Article embed URL

Partial in /templates/partials/embeds/youtube
    <a class="btn btn-primary" href="<?= $url ?>">url transformed into a button via a Partial in embeds folder</a>

Step7 Article embed YouTube

Partial in /templates/partials/embeds/youtube
    <? if (preg_match('%(?:youtube(?:-nocookie)?\.com/(?:[^/]+/.+/|(?:v|e(?:mbed)?)/|.*[?&]v=)|youtu\.be/)([^"&?/ ]{11})%i', $url, $match)): ?>
   <iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/<?= $match[1] ?>" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<? endif ?>

Step8 Blog

    ---
@collection:
    model: ext:joomla.model.articles
    state:
        published: 1
        category: [organizations]
        sort: date
        order: asc
@process:
    filters: highlight
---
    <h2>Blog view using ext:joomla.model.articles and links with the Pages router (in one file)</h2>

<p><small>Of course, in a real site, when using Custom Fields we could filter by Category since not all CF are assigned to all Categories for example)</small></p>
<div class="well">
    <ul>
       <? foreach(collection() as $article): ?>
          <li>
             <a href="<?= route('/step8article', ['slug' => $article->slug]) ?>"><?= $article->title; ?></a>
          </li>
       <? endforeach ?>
    </ul>
</div>
<hr>
<p>And here is a little <code>var_dump(collection())</code> so that you can see all what is immediately available in the array itself:</p>
<pre><code><?= var_dump(collection()); ?></code></pre>

Step8 Article

    ---
@route: /blabla/[:slug]
@collection:
    extend: articles
@process:
  filters: highlight
---
    <h2>Article View override done with Pages (in one file)</h2>

<article>
   <h3><?= collection()->title ?></h3>
   <p><?= collection()->text ?></p>
   <?= partial('/embeds/socialbrussels.html', ['id' => collection()->fields->socialbrusselsid->value]) ?>
   <?= partial('/embeds/openstreetmap.html', ['id' => collection()->fields->socialbrusselsid->value]) ?>
</article>

<hr>
<!-- <h3>A var_dump of all Custom Fields with <code>var_dump(collection()->fields)</code></h3>-->
<pre><code><?//= var_dump(collection()->fields); ?></code></pre>

Step9 Blog

    ---
@collection:
    model: ext:joomla.model.articles
    state:
        published: 1
        category: [organizations]
        sort: date
        order: asc
@process:
    filters: highlight
---
    <h2>Blog view using <samp>ext:joomla.model.articles</samp> and links with the Pages router (with partials)</h2>

<?= partial('/myorganizations/liststep9.html') ?>

Step9 Blog partial

    <p><small>Of course, in a real site, when using Custom Fields we could filter by Category since not all CF are assigned to all Categories for example)</small></p>
<div class="well">
    <ul>
       <? foreach(collection() as $article): ?>
          <li>
             <a href="<?= route('/step9/org', ['slug' => $article->slug]) ?>"><?= $article->title; ?></a>
          </li>
       <? endforeach ?>
    </ul>
</div>
<hr>
<p>And here is a little <code>var_dump(collection())</code> so that you can see all what is immediately available in the array itself:</p>
<pre><code><?= var_dump(collection()); ?></code></pre>

Step9 Article

    ---
@route: /myslug/[:slug]
@collection:
    extend: articles
@process:
  filters: highlight
---
    <?= partial('/myorganizations/myarticle.html') ?>

Step9 Article partial

    <h2>Article View override done with Pages (with partials)</h2>

<article>
   <h3><?= collection()->title ?></h3>
   <p><?= collection()->text ?></p>
   <?= partial('/embeds/socialbrussels.html', ['id' => collection()->fields->socialbrusselsid->value]) ?>
</article>

<h3>A var_dump of all Custom Fields with <code>var_dump(collection()->fields)</code></h3>
<pre><code><?= var_dump(collection()->fields); ?></code></pre>

Single Article

    <h2>How to display content from a specific article ID or Alias</h2>

<p>See <a target="_blank" href="https://github.com/joomlatools/joomlatools-pages/discussions/746">https://github.com/joomlatools/joomlatools-pages/discussions/746</a></p>
<p>The Joomla extension provides a customarticle() template function that allows to get a single article by it's id or alias.</p>
<p>Note:</p>
<ul>
    <li><samp>excerpt</samp> is what is know as the <samp>ìntrotext</samp> in Joomla</li>
    <li><samp>text</samp> is what is know as the <samp>fulltext</samp> in Joomla</li>
</ul>

<p>For example to retrieve the article with id = 1</p>
<div class="well">
    <article>
        <h1><?= article(2)->title ?></h1>
        <p><?= article(2)->excerpt.article(2)->text ?></p>
        <p><img src="<?= article(2)->image->url ?>" alt="sample image"></p>
    </article>
</div>

<p>For example to retrieve the article with alias = 'organization-1'
<div class="well">
    <article>
        <h1><?= article('organization-1')->title ?></h1>
        <p><?= article('organization-1')->excerpt.article('organization-1')->text ?></p>
        <p><img src="<?= article('organization-1')->image->url ?>" alt="sample image"></p>
    </article>
</div>

Decoration

    ---
@process:
    decorate: true
---
    <mark>
<h1><mark>I'm decorated by Pages</mark></h1>
<p><mark>What does it mean?</mark></p>
<p><mark>Simply that there is a native menu having blabla as Alias... and by adding a file called blabla.html.php I can add my own content to the native component content.</mark></p>
<p><mark>And now starts original's component view:</mark></p>
</mark>
<div class="well">
<ktml:content>
</div>
<p><mark>PS: oh, btw, this line is also added by Pages after the original content.</mark></p>
    ---
@collection:
    model: ext:joomla.model.articles
    state:
        sort: title
    config:
        search: [title, alias, date]
---
    <h2>Joomlatools Pages also allows to search</h2>
<p>This can be done directly from the Frontmatter... or even via the url if you allow it in the Frontmatter.</p>
<p><small>Note: on this page we have removed <samp>category: [organizations]</samp> in order to show all articles by default</small></p>
<h3>Searching via url</h3>
<p>Click on the following links and see the list of articles changing in function of the search criteria:</p>
<ul>
   <li><a href="/search">/search</a></li>
   <li><a href="/search?search=title:org">/search?search=title:org</a></li>
   <li><a href="/search?search=title:2">/search?search=title:2</a></li>
   <li><a href="/search?search=title:Organization%201">/search?search=title:Organization%201</a></li>
</ul>
<hr>
<div class="well">
	<p>List of (searched) articles:</p>
	<ul>
	<? foreach(collection() as $article): ?>
	<li><?= $article->title ?> (<?= $article->fields->socialbrusselsid ?>)</li>
	<? endforeach ?>
	</ul>
</div>
<hr>
<p>For more information see <a href="https://github.com/joomlatools/joomlatools-pages/discussions/506" target="_blank">https://github.com/joomlatools/joomlatools-pages/discussions/506</a></p>

Filter

    ---
@collection:
    model: ext:joomla.model.articles
    state:
        sort: title
@process:
    filters: highlight
---
    <h2>Joomlatools Pages also allows to filter</h2>
<div class="muted">
<p>Preliminary note: difference between Searching and Filtering:
<ul>
	<li>when you search you start with nothing and you go find something</li>
	<li>when you filter you start with everything and you remove items that don't follow the constraints you provide</li>
</ul>
</div>
<hr>
<h3>Filtering via url</h3>
<p>Click on the following links and see the list of articles changing in function of the filter criteria.</p>
<ul>
   <li><a href="/filter">/filter</a></li>
   <li><a href="/filter?id=2">/filter?id=2</a> (ID 2)</li>
   <li><a href="/filter?filter[id][]=2">/filter?filter[id][]=2</a> (ID 2)</li>
   <li><a href="/filter?id=1,2">/filter?id=1,2</a> (ID 1 or 2)</li>
   <li><a href="/filter?id[]=1&id[]=2">/filter?id[]=1&id[]=2</a> (ID 1 or 2)</li>
   <li><a href="/filter?filter[id][]=gt:2">/filter?filter[id][]=gt:2</a> (ID greater than 2)</li>
   <li><a class="work-in-progress" href="/filter?filter[id][]=gt:1&filter[id][]=lt:4">/filter?filter[id][]=gt:1&filter[id][]=lt:4</a></li>
</ul>
<hr>
<div class="well">
	<p>List of (filtered) articles:</p>
	<ul>
	<? foreach(collection() as $article): ?>
	<li><?= $article->title ?> (id: <?= $article->id ?>)</li>
	<? endforeach ?>
	</ul>
</div>
<hr>
<p>Filtering can be done via the <b>Frontmatter</b>, the <b>url</b> or the <b>code</b></p>
<p><small>Note: it works great for data coming from webservices, or from arrays, for database this is a tad harder to implement, it works for data from the same table, for related data it's harder. Still working on making it so that you don't need special states for filtering on db.</small></p>
<p>Filters work on</p>
<ul>
    <li>ID</li>
    <li>category</li>
    <li>access (=> use userid)</li>
    <li>editor (=> use userid)</li>
    <li>author (=> user userid)</li>
</ul>
<p>For more information, see <a href="https://github.com/joomlatools/joomlatools-pages/pull/363">https://github.com/joomlatools/joomlatools-pages/pull/363</a></p>
<p>Filters also work with States: you can use the collection stats both in your frontmatter and in your url too.<br />States are easy to find: <a href="https://github.com/joomlatools/joomlatools-pages/blob/master/contrib/extensions/joomla/model/articles.php#L16">https://github.com/joomlatools/joomlatools-pages/blob/master/contrib/extensions/joomla/model/articles.php#L16</a></p>
<p><small>Note: 'filter' is an approach to try and make this just work, without needing much custom code. This works great for example for a database table if you use the base database collection  or for a webservice, but in case of the joomla extension we are not talking one to one to the daabase table, so filter doesn'y work there that easily.</small></p>
<hr>
<?= partial('/embeds/filtering') ?>
<hr>
<p>And here is a little <code>var_dump(collection())</code> so that you can see all what is immediately available in the array itself:</p>
<pre><code><?= var_dump(collection()); ?></code></pre>

Filter directly via Frontmatter (and not via URL)

    ---
@collection:
    model: ext:joomla.model.articles
    state:
        sort: title
        category: [organizations]
        filter:
            id: 4
---

Filter on Custom Fields

    ---
@collection:
    model: ext:joomla.model.articles
    state:
        sort: title
        order: asc
        category: [organizations]
@process:
    filters: highlight
---
    <h2>Joomlatools Pages also allows to filter directly on the Custom Fields!</h2>
<p>This is not possible in Joomla without an extension and can be done directly via the Frontmatter or via the url</p>
<hr>
<h3>Filtering on Custom Field via url</h3>
<p>Click on the following links and see the list of articles changing in function of the filter criteria.</p>
<ul>
   <li><a href="/filter-cf">/filter-cf</a></li>
   <li><a href="/filter-cf?field[socialbrusselsid]=13817">/filter-cf?field[socialbrusselsid]=13817</a></li>
   <li><a href="/filter-cf?field[socialbrusselsid]=13817,3322">/filter-cf?field[socialbrusselsid]=13817,3322</a> (which is an OR)</li>
</ul>

<p>Note: You cannot yet use filter or use filter contraints like <samp>gte</samp> for article fields. This is a limitation: it is only supported by <samp>filter</samp> at the moment<br />
The short of it is, filter works for base collections, but not for complex custom collections like the Joomla Extension which gets data from different database tables.<br />
It works for:</p>
    <ul>
        <li>A single database table, if you use the database collection type</li>
        <li>A webservice, if you use the webservice collection type</li>
        <li>A file on the filesystem, if you use the filesystem collection type (Filesystem works like webservice but uses a file on filesystem, technically this also supports files from ftp, etc)</li>
    </ul>

<hr>
<div class="well">
	<p>List of articles (filtered on CF):</p>
	<ul>
	<? foreach(collection() as $article): ?>
	<li><?= $article->title ?> (socialbrusselsid: <?= $article->fields->socialbrusselsid ?>)</li>
	<? endforeach ?>
	</ul>
</div>
<hr>
<h3>Filtering on Custom Field via Frontmatter</h3>
<?= source('template:/partials/embeds/filter-cf-frontmatter') ?>
<hr>
<p>About filtering for Custom Fields</p>
<ul>
<li>a (database) collection can define states: <a href="https://github.com/joomlatools/joomlatools-pages/blob/feature/175-joomla/contrib/extensions/joomla/model/articles.php#L16" target="_blank">https://github.com/joomlatools/joomlatools-pages/blob/feature/175-joomla/contrib/extensions/joomla/model/articles.php#L16</a> and then it can implement rules how to handle those states, for example the field state is transformed into a query here: <a href="https://github.com/joomlatools/joomlatools-pages/blob/feature/175-joomla/contrib/extensions/joomla/model/articles.php#L176" target="_blank">https://github.com/joomlatools/joomlatools-pages/blob/feature/175-joomla/contrib/extensions/joomla/model/articles.php#L176</a></li>
<li>The filter  state is an attempt to make this just work, so you don't need custom code. <a href="https://github.com/joomlatools/joomlatools-pages/blob/feature/175-joomla/code/site/components/com_pages/model/behavior/filterable.php" target="_blank">https://github.com/joomlatools/joomlatools-pages/blob/feature/175-joomla/code/site/components/com_pages/model/behavior/filterable.php</a>
It works great for data coming from webservices, or from arrays, for database this is a tad harder to implement, it works for data from the dame table, for related data it'es harrder. Still working on making it so that you don't need special states for filtering on db.</li>
</ul>

<hr>
<p>And here is a little <code>var_dump(collection())</code> so that you can see all what is immediately available in the array itself:</p>
<pre><code><?= var_dump(collection()); ?></code></pre>

Filter on Custom Fields directly via Frontmatter (and not via URL)

    ---
@collection:
    model: ext:joomla.model.articles
    state:
        sort: title
        category: [organizations]
        field:
            socialbrusselsid: [13817, 3322]
---

Form

    ---
name: Joomlatools Pages Demo Form
title: Joomlatools Pages Demo
summary: Please complete this form to test.
slug: mydemo
visible: false
metadata:
    og:type: false
    robots: [none]
@form:
    name: demo-form
    processors:
        - csv
        - 'email':
            recipients:
                - test@test.com
            title: Joomlatools Pages Demo Form
            subject: New request from Joomlatools Pages Demo Form
    schema:
        Name: [string, required]
        Address: [string, required]
        Addressline2: [string]
        TownCity: [string, required]
        Postcode: [string, required]
        MobileNumber: [string]
        Email: [email, required]
        JoomlaVersion: [string, required]
    redirect: /thank-you
---
    <h2>Form made with Joomlatools Pages</h2>
<p>with the following features</p>
<ul>
   <li>honeypot to avoid spam</li>
   <li>redirection after submission</li>
   <li>sends content by email</li>
   <li>logs content in csv</li>
</ul>
<hr>

<!-- Form -->
<?= partial('/forms/demoform') ?>
<!-- end Form -->

<hr>
<p>For more information see <a href="https://github.com/joomlatools/joomlatools-pages/wiki/Cookbook-Forms" target="_blank">https://github.com/joomlatools/joomlatools-pages/wiki/Cookbook-Forms</a></p>
<p>Note: previously to create a honeypot we would add in <samp>@form</samp> for example this: <samp>honeypot: middlename_543e9g01</samp><br />Now you can remove the honeypot (unless you want to specialise it) in the frontmatter: Pages handles that out of the box now. See: <a target="_blank" href="https://github.com/joomlatools/joomlatools-pages/pull/433">https://github.com/joomlatools/joomlatools-pages/pull/433</a></p>

Form Partial

Partial in /templates/partials/forms/demoform
    <style>
.myformlabel {text-align: right; margin-bottom: 9px; padding-top: 4px; padding-bottom: 4px /* same padding and margin as input */}
.myforminput {width: 400px;}
.myformselect {width: 414px;} /* select should be 14px more than input */
.myformrow {
   display: grid; 
   grid-template-columns: 150px 1fr;
   grid-gap: 20px;
}
</style>

<form method="post" action="<?= route('form') ?>" class="w-full">

  <div class="myformrow">
    <div class="myformleft">
      <label class="myformlabel" for="Name" required>Name</label>
    </div>
    <div class="myformright">
      <input class="myforminput" id="Name" name="Name" type="text" placeholder="Firstname Lastname" required>
    </div>
  </div>
  <div class="myformrow">
    <div class="myformleft">
      <label class="myformlabel" for="Address">Address</label>
    </div>
    <div class="myformright">
      <input class="myforminput" id="Address" name="Address" type="text" placeholder="Address Line 1" required>
    </div>
  </div>
  <div class="myformrow">
    <div class="myformleft">
      <label class="myformlabel" for="Addressline2">Address line 2</label>
    </div>
    <div class="myformright">
      <input class="myforminput" id="Addressline2" name="Addressline2" type="text">
    </div>
  </div>
  <div class="myformrow">
    <div class="myformleft">
      <label class="myformlabel" for="TownCity">Town / City</label>
    </div>
    <div class="myformright">
      <input class="myforminput" id="TownCity" name="TownCity" type="text" placeholder="Brussels" required>
    </div>
  </div>
  <div class="myformrow">
    <div class="myformleft">
      <label class="myformlabel" for="Postcode">Postcode</label>
    </div>
    <div class="myformright">
      <input class="myforminput" id="Postcode" name="Postcode" type="text" placeholder="1200" required>
    </div>
  </div>
  <div class="myformrow">
    <div class="myformleft">
      <label class="myformlabel" for="MobileNumber">Mobile or Phone Number</label>
    </div>
    <div class="myformright">
      <input class="myforminput" id="MobileNumber" name="MobileNumber" type="tel" placeholder="0474371312 or 027725869" pattern="[0-9]{9}|[0-9]{10}" required>
    </div>
  </div>
  <div class="myformrow">
    <div class="myformleft">
      <label class="myformlabel" for="Email">Email</label>
    </div>
    <div class="myformright">
      <input class="myforminput" id="Email" name="Email" type="email" placeholder="you@example.com" required>
    </div>
  </div>
  <div class="myformrow">
    <div class="myformleft">
      <label class="myformlabel" for="JoomlaVersion">Joomla Version</label>
    </div>
    <div class="myformright">
      <div class="">
        <select class="myformselect" id="JoomlaVersion" name="JoomlaVersion" required>
          <option value="">Please Select...</option>
          <option value="1">1.5</option>
          <option value="2">2.5</option>
          <option value="3">3.x</option>
          <option value="4">4.x</option>
        </select>
      </div>
    </div>
  </div>

  <div class="myformrow">
    <div class="myformleft"><?= helper('form.honeypot', page('form')->form->honeypot); ?></div>
    <div class="myformright">
      <button class="btn btn-primary" type="submit" data-errormsg="Send message" data-successmsg="Sending">
        Send
      </button>
    </div>
  </div>
</form>

Lang

    ---
@collection:
    model: ext:joomla.model.articles
    state:
        published: 1
        sort: date
        order: asc
        language: fr
@process:
    filters: highlight
---
    <h2>Languages & Multilingual websites</h2>
<h3>state: language</h3>

<div>
<p>This site is not setup as multilingual and all the initial articles have the Language assigned to All in the interface. But still we created three additional articles for this page:</p>
<ul>
<li>English having Language assigned to "en-GB"</li>
<li>Dutch having language assigned to "nl-NL"</li>
<li>French having language assigned to "fr-FR"</li>
</ul>
</div>
<div class="well">
<p>List of articles when the Frontmatter has <code>language: fr</code> in the <code>state:</code></p>
<ul>
   <? foreach(collection() as $article): ?>
      <li>
         <?= $article->title; ?> (language: <?= $article->language; ?>)
      </li>
   <? endforeach ?>
</ul>
<p>Note: with <code>language:</code> (or without any mention of it), only the articles being assigned to all languages (<code>*</code>) are listed.
</div>

<h3>Multilingual content handling</h3>

<p>Dixit Johan:<br /><br /><i>I added support for multi-lingual content handling with Pages and the Joomla extension a while ago based on a question from @Marc Dechèvre. It's really simple:</i></p>

<?= source('template:/partials/embeds/multilingual') ?>

<p>See: <a target="_blank" href="https://github.com/joomlatools/joomlatools-pages/pull/363">https://github.com/joomlatools/joomlatools-pages/pull/363</a></p>
<p>For more info about the lang route restraint see: <a target="_blank" href="https://github.com/joomlatools/joomlatools-pages/pull/391">https://github.com/joomlatools/joomlatools-pages/pull/391</a></p>

<hr>
<p>And here is a little <code>var_dump(collection())</code> so that you can see all what is immediately available in the array itself:</p>
<pre><code><?= var_dump(collection()); ?></code></pre>

List of files

    <ul>
<? foreach(collection('pages', ['level' => 1]) as $page): ?>
    <li><a href="<?= route($page) ?>"><?= $page->name ?></a></li>
<? endforeach ?>
</ul>

TOC

    ---
title: An automatic Table of Content (TOC)
summary: This page is a static HTML page.
@process:
    filters: [highlight, toc]
---
    <ktml:toc>

<h2>An automatic Table of Content (TOC)</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?</p>
<h3>Submenu A</h3>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?</p>
<h4>Subsubmenu a</h4>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?</p>
<h4>Subsubmenu b</h4>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?</p>
<h3>Submenu B</h3>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?</p>

Webservice

    ---
@collection:
    model: ext:joomla.model.articles
    state:
        published: 1
        category: [organizations]
        sort: date
        order: asc
        limit: 3
@process:
    filters: highlight
---
    <h2>This is a Collection of Articles of a given Category using <samp>ext:joomla.model.articles</samp> rendered here as HTML. But it can also been rendered as json, rss or csv!</h2>
<p>If you choose CSV you could even display directly the data in a Google Sheet by using <code>=IMPORTDATA("https://pages.joomlacustomfields.org/webservice.csv")</code></p>
<p>Note: csv is more tricky then json, it doesn't support nesting. That is why (Custom) Fields are not displayed in csv format. To go further, see this: https://github.com/bradjasper/ImportJSON</p>
<p>json: http://example.com/foo.json?fields=title,date,fields or via the front matter: state: fields: [title, date, fields]</p>
<p>For more information about "Webservice features offered by Pages" and "Differences with Joomla 4", see <a target="_blank" href="https://github.com/joomlatools/joomlatools-pages/discussions/675">https://github.com/joomlatools/joomlatools-pages/discussions/675</a></p>
<p><small>Example herafter taken from https://github.com/joomlatools/joomlatools-pages/tree/master/contrib/sites/blog/pages/blog</small></p>

<? foreach(collection() as $article): ?>

    <article class="well">
        <h2>
            <a href="<?= route('/webservice/article', ['slug' => $article->slug]) ?>" class="text-black text-xl md:text-2xl no-underline hover:underline">
                <?= $article->title; ?>
            </a>
        </h2>
        <div>
            <? $image = $article->image->url ?? 'https://picsum.photos/seed/'.$article->id.'/700/350' ;  ?>
            <img itemprop="image" class="featured-image rounded object-cover object-left-top w-full" src="<?= $image ?>" alt="<?= $article->title; ?>">
        </div>
        <div>
            by <a href="#"><?= $article->getAuthor()->getName(); ?></a> on <?= date('d M, Y', $article->published_date); ?>
            <span class="font-bold mx-1"> | </span>
            <a href="#" class="text-gray-700">
                <?= $article->category->title; ?>
            </a>
        </div>

        <p>
            <?= $article->excerpt; ?>
        </p>

    </article>

    <hr>

<? endforeach ;?>

json

    ---
@collection:
    extend: /webservice
    state:
        limit: 0
        fields: [title, date, fields]
visible: false
---

csv

    ---
@collection:
    extend: /webservice
    state:
        limit: 0
visible: false
---

rss

    ---
title: The blog
summary:  Description for the blog
@collection:
    extend: /webservice
    state:
        limit: 10
visible: false
---
    <?= import('template://pages/newsfeed.rss'); ?>

All

    ---
title: My Articles in html, csv or json format
@route:
  - /all
  - /all.csv
  - /all.json
@collection:
    model: ext:joomla.model.articles
    state:
        published: 1
    format: [csv, json]
---
    <h2>A single page, that can render html, json and csv all in one go!</h2>

<p>Explanation of the frontmatter:</p>
<ul>
	<li>The route has three options, meaning it accepts all of the following url:
		<ul>
			<li><a href="/all">/all</a></li>
			<li><a href="/all.csv">/all.csv</a></li>
			<li><a href="/all.json" target="_blank">/all.json</a></li>
		</ul>
	</li>
	<li><samp>format: csv, json</samp>: This tells the page to also accepts csv and json formats</li>
	<li>the route needs to be setup as static routes (namely one line for each). Doing it with a dynamic route <samp>route: /content.[csv|json]?</samp> doesn't work (yet). A static route is a route without any wildcards [...]. The default format is the format you specify in the page filename, aka content.html.php, default format here is html</li>
</ul>

List of articles

<div class="well">
<ul>
<? foreach(collection() as $item) : ?>
   <li><a href="<?= route($item) ?>"><?= $item->url ?><?= $item->title ?></a></li>
<? endforeach; ?>
</ul>
</div>

Local json

    <h2>Can also fetch data from local file</h2>
<p>Example here with a file <a target="_blank" href="/joomlatools-pages/data/somefolder/localfile.json"><samp>/joomlatools-pages/data/somefolder/localfile.json</samp></a></p>
<p>This can also be useful if you have data on a site that is repeated often, like an address, or title, or product info etc</p>

<div class="well">
<ul>
<? foreach (data('somefolder/localfile') as $item) : ?>
    <li>
        <a <? if($item->path) : ?>href="<?= route($item->path); ?>"<? else: ?>rel="nofollow" href="<?= $item->url ?>"<? endif ?> ><?= $item->title; ?></a>
    </li>
<? endforeach ?>
</ul>
</div>

External json

    ---
title: External json - K2 Showcase
@collection:
    model: webservice?url=https://getk2.org/showcase?format=json
    config:
        data_path: items
---
    <h2>Fetching data from an external JSON, taking <samp>items</samp> and looping on them</h2>
<p>Fetching data from <a href="https://getk2.org/showcase?format=json" target="_blank">https://getk2.org/showcase?format=json</a></p>
<p>Note the <samp>config: data_path: items</samp> in the frontmatter. This is because all the data in the json are childs of <samp>items</samp>.</p>
<p>We can also filter on the external json:</p>
<ul>
    <li><a href="/ext-json">/ext-json</a> (all)</li>
    <li><a href="/ext-json?id=2703">/ext-json?id=2703</a> (id = a certain value)</li>
    <li><a href="/ext-json?filter[id][]=gte:2702">/ext-json?filter[id][]=gte:2702</a> (id >= a certain value)</li>
</ul>
<div class="well">
<ul>
	<? foreach(collection() as $item): ?>
		<li>
			<span><?= $item->title ?></span><br/>
			<span><small><?= date($item->created) ?></i></small><br/>
			<span><?= $item->introtext ?></span>
		</li>
	<? endforeach; ?>
</ul>
</div>

External json (advanced) using Collection

    ---
title: External json - with filters
@collection:
    model: webservice
    config:
        url: https://social.brussels/rest/search/organisation?locatedAt=Schaerbeek
        cache: 1month
    state:
        limit: 9
        sort: id
        order: desc
---
    <h2>Fetching data from an external JSON, taking <samp>items</samp> and looping on them... and filtering</h2>
<p>Fetching data from <a href="https://social.brussels/rest/search/organisation?locatedAt=Schaerbeek" target="_blank">https://social.brussels/rest/search/organisation?locatedAt=Schaerbeek</a>
<br /><small>Since that url is complex, we cannot use the simplified writing (<samp>model: webservice?https://social.brussels/rest/search/organisation?locatedAt=Schaerbeek</samp>): we have to put the url in the <samp>config: url:</samp> parameter.</small></p>
<p>The list is ordered by "id" descending. We cannot order by "lastUpdate" given the format of the field (it would display 31 then 30 etc)</p>
<p>Adding <samp>id: [17632, 18817]</samp> in the <samp>state</samp> of the frontmatter would filter on the IDs in the array.
    <br />Adding <samp>filter: 'id gte 17000'</samp>in the <samp>state</samp> of the frontmatter would filter on id >= the given number.
    <br />Same if we add <samp>filter:</samp> in the <samp>state</samp> of the frontmatter and then add on a new line <samp>id: 'gte:17000'</samp></p>
<p>Generally speaking, sorting can also be done via the url (see <a target="_blank" href="https://github.com/joomlatools/joomlatools-pages/wiki/Collection#state">https://github.com/joomlatools/joomlatools-pages/wiki/Collection#state</a>. Examples:</p>
<ul>
    <li><samp>?sort=title</samp> which is equivalent to <samp>?sort=title,asc</samp> (since asc by default)</li>
    <li><samp>?sort=-title</samp> which is equivalent to <samp>?sort=title,desc</samp> (notice the '-')</li>
    <li>another option for order is <samp>shuffle</samp> (useful if you want to show a list that always changes, like for example the testominials on our site)</li>
</ul>
<p>We can also filter on the external json:</p>
<ul>
    <li><a href="/ext-json-filter">/ext-json-filter</a> (all, taking of course into account the filter which might be set in the frontmatter)</li>
    <li><a href="/ext-json-filter?id=17632">/ext-json-filter?id=17632</a> (id = a certain value)</li>
    <li><a href="/ext-json-filter?filter=id gte 19061">/ext-json-filter?filter=id gte 19061</a> (id >= a certain value)</li>
    <li><a href="/ext-json-filter?filter[id][]=gte:19061">/ext-json-filter?filter[id][]=gte:19061</a> (id >= a certain value)</li>
    <li><a href="/ext-json-filter?filter[address][districtCode]=21">/ext-json-filter?filter[address][districtCode]=21</a> (address is an array containing a districtCode)</li>
    <li><a href="/ext-json-filter?filter[lastUpdate][]=11/12/20">/ext-json-filter?filter[lastUpdate][]=11/12/20</a> (lastUpdate = a certain value)</li>
    <li><a href="/ext-json-filter?filter[lastUpdate][]=gte:30">/ext-json-filter?filter[lastUpdate][]=gte:30</a> (lastUpdate >= a certain value)</li>
    <li class="work-in-progress"><a class="work-in-progress" href="/ext-json-filter?filter[lastUpdate][]=gte:30%2F10">/ext-json-filter?filter[lastUpdate][]=gte:30%2F10</a> (lastUpdate >= a certain value) Note: given the structure of the date field, does not work as expected (date should have been expressed in the source as in linux-format)</li>
</ul>

<div class="well">
    <ul class="pages-cards">
        <? // foreach (data('https://social.brussels/rest/search/organisation?locatedAt=Schaerbeek') as $item) : ?>
        <? foreach(collection() as $item): ?>
            <li>
                <ul>
                    <li><b>id: </b><?= $item->id ?></li>
                    <li><b>lastUpdate: </b><?= $item->lastUpdate ?></li>
                    <li><b>nameOfficialFr: </b><?= $item->nameOfficialFr ?></li>
                    <li><b>address['districtCode']: </b><?= $item->address['districtCode'] ?? '<i>unknown</i>' ?></li>
                    <li><b>websiteOfficialFr[0]: </b><?= $item->websiteOfficialFr[0] ?? '<i>unknown</i>' ?></li>
                    <li><b>legalStatus['labelFr']: </b><?= $item->legalStatus['labelFr'] ?? '<i>unknown</i>' ?></li>
                    <li><b>address['streetFr']: </b><?= $item->address['streetFr'] ?? '<i>unknown</i>' ?></li>
                </ul>
            </li>
        <? endforeach; ?>
    </ul>
    <?= helper('paginator.pagination') ?>
</div>

<hr>

<h4>OpenStreetMap</h4>

<div id="mapid" style="width: 100%; height: 400px;"></div>

<!-- ktml:style and ktml:script are the best way in Pages to put the style and the script directly in the Header. If defer is needed simply type defer="defer" -->
<!-- When we don't want a script to be put automatically by Pages into the Header we can use <script data-inline> -->

<!-- Leaflet Library - SEE https://leafletjs.com/ & https://unpkg.com/browse/leaflet@1.7.1/dist/ -->
<ktml:style src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />
<ktml:script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js" defer="defer" />

<script data-inline defer>
document.addEventListener("DOMContentLoaded", function(){

	// create the Map - Tiles can come from OpenStreetMap, Mapbox or any other source
	var mymap = L.map('mapid').setView([50.846688933961815, 4.352506399154664], 12);
	// Tiles from OpenStreetMap
	// L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {attribution: '&copy; <a href="https://www.openstreetmap.org/copyright" target="_blank">OpenStreetMap</a> contributors'}).addTo(mymap);
	// Tiles from MapBox with API key taken from https://leafletjs.com/examples
	L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw', {
		maxZoom: 18,
		attribution: 'Map data &copy; <a href="https://www.openstreetmap.org/copyright" target="_blank">OpenStreetMap</a> contributors, ' +
			'Imagery &copy; <a href="https://www.mapbox.com/" target="_blank">Mapbox</a>',
		id: 'mapbox/streets-v11',
		tileSize: 512,
		zoomOffset: -1
	}).addTo(mymap);
	
	// create all the Markers
	<? foreach(collection() as $item): ?>
		var tooltip = '<a href="https://social.brussels/organisation/<?= $item->id ?>" target="_blank"><?= $item->nameOfficialFr ?></a>';
		var marker = L.marker([<?= $item->address['lat'] ?>, <?= $item->address['lon'] ?>]).bindPopup(tooltip).addTo(mymap);
	<? endforeach; ?>

});
</script>

<hr>
<h4>Caching</h4>
<p>In Step4 of the present Demo site, we used the Data API allowing to cache the source easily like this: <samp>data('https://social.brussels/rest/organisation/13817', '1day')</samp></p>
<p>In the present case, we use a Collection (in the frontmatter). In order to turn on collection caching for collection data fetched over http, simply add <samp>'http_client_cache' => true,</samp> to <samp>config.php</samp>.</p>
<p>Alternatively, we could also configure that caching directly in the frontmatter <b>but that is quite advanced. By default enabling the http_client_cache is sufficient.</b></p>
<code>config:
    cache: true</code>
<p>would enable cache, whatever is mentioned in the potential <samp>config.php</samp></p>
<code>config:
    cache: 1month</code>
<p>would keep the json 1 month in cache</p>
<hr>
<h4>Comments (Object or Array)</h4>
<p>In step4 we were using the Data API, so we could write <samp>data('https://social.brussels/rest/organisation/13817', '1day')->address->streetFr;</samp></p>
<p>On the present page, we are using the Collection API so we should write <samp>$item->address['streetFr']</samp> (and not <samp>$item->address->streetFr</samp>).</p>
<p>We should indeed make a difference between:</p>
<ul>
    <li>Data API</li>
    <li>Collection API</li>
</ul>
<p>The Data API turns a structured data file into an <b>object</b> and offers a very nice and simple API to work with the data. It can do that because it knows the data is simple structured data.</p>
<p>The Collection API is built to handle more complex data, it doesn't turn the data into objects automatically, but it servers each entity property as is. In our case: <samp>$item->address </samp>.<br />
    This works, since $item is an object and address  a property of the object, however address is no longer an object. The collection doesn't turn you data into an object like the data api does, unless you tell it so, by creating a custom collection. This is why this works for ext:joomla.model.articles but not here.<br />
    <samp>address</samp> is an array and by default you need to use the array notation in PHP to access the data so <samp>$item->address['streetFr']</samp></p>
<p>This explains the differences. PHP wise this is the difference between Object and Array: Object you use <samp>-></samp> Array you use <samp>[]</samp></p>
<p>Sometimes the field does not exist. So <samp>$item->address['streetFr'] ?? 'unknown'</samp> would display the streetFr if available and the text 'unknown' otherwise</p>
<p><b>See the difference in practice on <a target="_blank" href="/ext-json-filter-data">/ext-json-filter-data</a></b></p>

<style>
    /* adding some quick styling for the automatic Pagination made by Pages */
    ul.k-pagination__pages {
        display: flex;
        justify-content: space-between;
        list-style-type: none;
    }
    /* just removing the icon-next icon coming along with Protostar */
    ul.k-pagination__pages li.icon-next:before { content:unset;}

    ul.pages-cards {
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        grid-gap: 20px;
        margin-left: 0; /* otherwise we have the natural left margin of the Unordered List */
    }
    ul.pages-cards > li {
        background: white;
        display: flex;
        flex-direction: column;
        padding: 10px;
        border: 1px solid lightgray;
        box-shadow: 3px 3px 2px 1px rgba(0, 0, 0, 0.2);
        transition: 0.5s;
    }
    ul.pages-cards > li:hover {
        box-shadow: 3px 3px 2px 1px rgba(0, 0, 0, 0.4);
    }
</style>

External json (advanced) using Data

    ---
title: External json - with filters
@process:
    cache: false
---
    <h2>Fetching data from an external JSON, taking <samp>items</samp> and looping on them... and filtering</h2>
<p>In this example we use 'Data' and not a 'Collection', as explained previously (1) filtering does not apply and (2) we can directly write <samp>$item->address->streetFr</samp></p>
<p>Fetching data from <a href="https://social.brussels/rest/search/organisation?locatedAt=Schaerbeek" target="_blank">https://social.brussels/rest/search/organisation?locatedAt=Schaerbeek</a></p>
<div class="well">
    <ol>
        <? foreach (data('https://social.brussels/rest/search/organisation?locatedAt=Schaerbeek') as $item) : ?>
            <li>
                <span><?= $item->id ?></span><br/>
                <span><small><?= date($item->lastUpdate) ?></i></small><br/>
                <span><?= $item->nameOfficialFr ?></span><br />
                <span><?= $item->address->streetFr ?></span>
            </li>
        <? endforeach; ?>
    </ol>
</div>

External json & OpenStreetMap

    ---
title: External json into a Map
@collection:
    model: webservice
    config:
        url: https://sociaal.brussels/rest/search/organisation?categories=263
        cache: 1month
    state:
        limit: 1000
        sort: id
        order: desc
---
    <h2>Fetching data from an external JSON, taking <samp>items</samp> and looping on them and displaying on an Map</h2>
<p>Fetching data directly from <a href="https://sociaal.brussels/rest/search/organisation?categories=263" target="_blank">https://sociaal.brussels/rest/search/organisation?categories=263</a></p>
<p><small>Note: category 263 = <i>Milieux d'accueil pour jeunes enfants où la participation financière est proportionnelle aux revenus</i></small></p>
<p><small>Note: a source URL like https://sociaal.brussels/rest/sector/46/organisations works when filtering on some Zipcodes but not for the global map. Probably an issue with the source data</small></p>

<p>We can also filter on the external json:</p>
<ul>
    <li><a href="/osm">/osm</a> (all, taking of course into account the filter which might be set in the frontmatter)</li>
    <li><a href="/osm?id=12926">/osm?id=12926</a> (id = a certain value)</li>
    <li><a href="/osm?filter=id gte 19061">/osm?filter=id gte 19061</a> (id >= a certain value)</li>
    <li><a href="/osm?filter[id][]=gte:19061">/osm?filter[id][]=gte:19061</a> (id >= a certain value)</li>
    <li><a href="/osm?filter[address][districtCode]=21">/osm?filter[address][districtCode]=21</a> (address is an array containing a districtCode)</li>
    <li><a href="/osm?filter[lastUpdate][]=11/12/20">/osm?filter[lastUpdate][]=11/12/20</a> (lastUpdate = a certain value)</li>
    <li><a href="/osm?filter[lastUpdate][]=gte:30">/osm?filter[lastUpdate][]=gte:30</a> (lastUpdate >= a certain value)</li>
    <li class="work-in-progress"><a class="work-in-progress" href="/osm?filter[lastUpdate][]=gte:30%2F10">/osm?filter[lastUpdate][]=gte:30%2F10</a> (lastUpdate >= a certain value) Note: given the structure of the date field, does not work as expected (date should have been expressed in the source as in linux-format)</li>
</ul>

<p>address is an array containing a zipCode</p>
<ul>
	<li><a href="/osm?filter[address][zipCode]=1200">/osm?filter[address][zipCode]=1200</a> Woluwe-Saint-Lambert</li>
	<li><a href="/osm?filter[address][zipCode]=1060">/osm?filter[address][zipCode]=1060</a> Saint-Gilles</li>
	<li><a href="/osm?filter[address][zipCode]=1030">/osm?filter[address][zipCode]=1030</a> Schaerbeek</li>
	<li><a href="/osm?filter[address][zipCode]=1180">/osm?filter[address][zipCode]=1180</a> Uccle</li>
</ul>

<p>legalStatus is an array containing a labelFr</p>
<ul>
	<li><a href="/osm?filter[legalStatus][labelFr]=Service public - Commune">/osm?filter[legalStatus][labelFr]=Service public - Commune</a> Service public - Commune</li>
	<li><a href="/osm?filter[legalStatus][labelFr]=Association sans but lucratif (ASBL)">/osm?filter[legalStatus][labelFr]=Association sans but lucratif (ASBL)</a> Association sans but lucratif (ASBL)</li>
</ul>


<h4>Map</h4>

<div id="mapid" style="width: 100%; height: 600px;"></div>

<!-- ktml:style and ktml:script are the best way in Pages to put the style and the script directly in the Header. If defer is needed simply type defer="defer" -->
<!-- When we don't want a script to be put automatically by Pages into the Header we can use <script data-inline> -->

<!-- Leaflet Library - SEE https://leafletjs.com/ & https://unpkg.com/browse/leaflet@1.7.1/dist/ -->
<ktml:style src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />
<ktml:script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js" defer="defer" />

<!-- Leaflet Cluster plugin - SEE https://github.com/Leaflet/Leaflet.markercluster & https://unpkg.com/browse/leaflet.markercluster@1.5.0/dist/ -->
<ktml:style src="https://unpkg.com/leaflet.markercluster@1.5.0/dist/MarkerCluster.css" />
<ktml:style src="https://unpkg.com/leaflet.markercluster@1.5.0/dist/MarkerCluster.Default.css" />
<ktml:script src="https://unpkg.com/leaflet.markercluster@1.5.0/dist/leaflet.markercluster-src.js" defer="defer" />

<!-- Leaflet Search plugin : SEE https://github.com/stefanocudini/leaflet-search & https://unpkg.com/browse/leaflet-search@2.9.11/dist/ -->
<ktml:style src="https://unpkg.com/leaflet-search@2.9.11/dist/leaflet-search.src.css" />
<ktml:script src="https://unpkg.com/leaflet-search@2.9.11/dist/leaflet-search.src.js" defer="defer" />

<script data-inline defer>
document.addEventListener("DOMContentLoaded", function(){
	
	// create the Map - Tiles can come from OpenStreetMap, Mapbox or any other source
	var mymap = L.map('mapid').setView([50.846688933961815, 4.352506399154664], 12);
	// Tiles from OpenStreetMap
	// L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {attribution: '&copy; <a href="https://www.openstreetmap.org/copyright" target="_blank">OpenStreetMap</a> contributors'}).addTo(mymap);
	// Tiles from MapBox with API key taken from https://leafletjs.com/examples
	L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw', {
		maxZoom: 18,
		attribution: 'Map data &copy; <a href="https://www.openstreetmap.org/copyright" target="_blank">OpenStreetMap</a> contributors, ' +
			'Imagery &copy; <a href="https://www.mapbox.com/" target="_blank">Mapbox</a>',
		id: 'mapbox/streets-v11',
		tileSize: 512,
		zoomOffset: -1
	}).addTo(mymap);
	
	// create all the Markers
	var myClusterGroup = L.markerClusterGroup();
	<? foreach(collection() as $item): ?>
		var tooltip = `
			<b><a href="https://social.brussels/organisation/<?= $item->id ?>" target="_blank"><?= $item->nameOfficialFr ?? '<i>unknown</i>' ?></a></b><br />
			<?= $item->address['number'] ?? '<i>unknown</i>' ?> <?= $item->address['streetFr'] ?? '<i>unknown</i>' ?><br /><?= $item->address['postalCodeFr'] ?? '<i>unknown</i>' ?>&nbsp;
			<small>[<?= $item->address['districtFr'] ?? '<i>unknown</i>' ?>]</small>
			<hr style="margin:5px 0px">
			<small><?= $item->activitiesFr ?? '<i>unknown</i>' ?></small>
			`;
		var marker = L.marker([<?= $item->address['lat'] ?>, <?= $item->address['lon'] ?>],{"title":"<?= $item->nameOfficialFr ?? '<i>unknown</i>' ?>"}).bindPopup(tooltip);
		// Note: if we did not need the Layer for Clustering and for Searching then we would add each single marker to the map simply by adding .addTo(mymap) at the end of the previous line
		myClusterGroup.addLayer(marker);
	<? endforeach; ?>
	
	// displays all the Markers (using Clustering)
	mymap.addLayer(myClusterGroup);

	// displays the Search (works also fine with Clustering)
	var controlSearch = new L.Control.Search({
		// position:'topleft',		
		layer: myClusterGroup,
		initial: false,
		zoom: 16
	});
	mymap.addControl( controlSearch );
	
	// displays the popup after Search
	controlSearch.on('search:locationfound', function(e) {
		// console.log('-----test-----', );
		e.layer.openPopup();
	});
	
	// displays the LatLng when clicking anywhere on the map
	// var thislatlng = L.popup();
	// function onMapClick(e) {
	// 	thislatlng
	// 		.setLatLng(e.latlng)
	// 		.setContent("You clicked the map at " + e.latlng.toString())
	// 		.openOn(mymap);
	// }
	// mymap.on('click', onMapClick);
	
});
</script>

External md

    ---
title: External MarkDown
url: https://slides.woluweb.be/akeeba-unite/presentation.md
---
    <h2>Fetching and displaying an external .md</h2>
<p>Example with <a target="_blank" href="<?= $url ?>"><?= $url ?></a></p>
<p>Note: that .md file uses local images which have *not* been copied to the present site, therefore the "broken" images in the .md rendering.</p>
<p><small>Note: we could also play with <samp>file_get_contents($url)</samp> instead of <samp>data($url)->content</samp> according to the usecase.</small></p>
<h3><?= data($url)->title; ?></h3>
<div class="well">
    <article>
        <?= data($url)->content; ?>
    </article>
</div>

Gdoc

    ---
url: https://docs.google.com/document/d/e/2PACX-1vTK9nkPIsoq6ZJeVm86ns4BX7Q0bOcMHV5jYoaGlsREXUxt22kDW2zt0Zh3wSF9mquuVQKlPFyRw9HK/pub
---
    <h2>Fetching and displaying a Google Doc</h2>

<p>Data coming from: <a target="_blank" href="https://docs.google.com/document/d/e/2PACX-1vTK9nkPIsoq6ZJeVm86ns4BX7Q0bOcMHV5jYoaGlsREXUxt22kDW2zt0Zh3wSF9mquuVQKlPFyRw9HK/pub">https://docs.google.com/document/d/e/2PACX-1vTK9nkPIsoq6ZJeVm86ns4BX7Q0bOcMHV5jYoaGlsREXUxt22kDW2zt0Zh3wSF9mquuVQKlPFyRw9HK/pub</a></p>
<p>For more information see <a href="https://github.com/joomlatools/joomlatools-pages/pull/264" target="_blank">https://github.com/joomlatools/joomlatools-pages/pull/264</a></p>

<h3>Is there also a cache with Gdoc & Gsheet?</h3>
<p>Both use caching if:</p>
<ul>
    <li>Joomla caching is enabeld.</li>
    <li>you set <samp>'http_client_cache' => true</samp> in your config.php in the <samp>/joomlatools-pages/</samp> directory</li>
</ul>
<p>Is on now so the cache for the Gdoc and Gsheet is working, you will find a new dir called /joomlatools-pages/cache/responses, containing two files, one for the Gdoc and one for the Gsheet.<br />
    The Gdoc is cached always, aka pages will not recheck of the original file has changed, the Gsheet uses pages webservice caching which is a smart cache, it will know when you update the csv.</p>

<hr>
<h3>The content of the Gdoc hereafter</h3>
<? $doc = data($url); ?>
<title><?= $doc->get('html/head/title') ?></title>
<?
$content = $doc->get('html/body/div')->filter('@attributes', ['id' => 'contents'])->remove('style')->toHtml();
foreach ($content->query('//*') as $node)
{
   foreach(['style', 'class', 'id'] as $attribute) {
      $node->removeAttribute($attribute);
   }
}
?>
<div class="well">
<article>
   <?= $content; ?>
</article>
</div>

Gsheet

    ---
title: Google Sheet
@collection:
    model: webservice
    config:
        url: https://docs.google.com/spreadsheets/d/e/2PACX-1vSSxdNS80jca7uKpvDQtUq0KsDfSPxgk6LB3vMGZcwgHU2rdkRLn3vtTsWCtEVwvWUOfPT6MD8PwuKb/pub?output=csv
        identity_key: null
    state:
        limit: 20
@process:
    filters: highlight
---
    <h2>Fetches a table from Google Sheet</h2>
<p>Procedure: create a Google Sheet with three columns (here: city, country, event), go to menu File > Publish to Web > csv. Copy the url and put it in the Frontmatter</p>
<p>Data coming from: <a target="_blank" href="https://docs.google.com/spreadsheets/d/e/2PACX-1vSSxdNS80jca7uKpvDQtUq0KsDfSPxgk6LB3vMGZcwgHU2rdkRLn3vtTsWCtEVwvWUOfPT6MD8PwuKb/pub">https://docs.google.com/spreadsheets/d/e/2PACX-1vSSxdNS80jca7uKpvDQtUq0KsDfSPxgk6LB3vMGZcwgHU2rdkRLn3vtTsWCtEVwvWUOfPT6MD8PwuKb/pub</a></p>

<hr>

<div class="well">
<ul>
<? foreach(collection() as $row) : ?>
	<li>
		<?= $row->city ?>, <?= $row->country ?>, <?= $row->event ?>
	</li>
<? endforeach; ?>
</ul>
<?= helper('paginator.pagination') ?>
</div>

<hr>

<p>See another example: <a href="/gsheet2">/gsheet2</a></p>

<hr>

<p>Notice the 'identity_key' null in the frontmatter.<br />Pages assumes a identity_key == id (ie a first column called id in the Google Sheet), but that doesn't exist in the present Sheet.<br /><small>This will be fixed in the future to make pages smarter to handle this use-case, for now setting it to null works fine. There is another weird problem here though which i think is a bug with google. If i set the http protocol to 1.1 when making this request google is returning the csv without the headers, if i change it to 1.0 it does return th csv header. Without the header pages cannot create the collection, it needs to know city, country and event headers of course. Very weird issues, smells like a Google bug</small></p>

<?= source('/gsheet') ?>

<h3>See hereafter the var_dump(collection())</h3>
<pre><code><?= var_dump(collection()); ?></code></pre>

Gsheet 2

    ---
title: Google Sheet
@collection:
    model: webservice
    config:
        url: https://docs.google.com/spreadsheets/d/e/2PACX-1vTEbblQfWW2ZjGlt1bXO59yixftz9Ok1dnDuCMIOYyk-Gow4mkxolXOsj_iECGDKHq9j1xNHQcdC8lw/pub?output=csv
    state:
        limit: 20
@process:
    filters: highlight
---
    <style>
    /* adding some quick styling for the automatic Pagination made by Pages */
    ul.k-pagination__pages {
        display: flex;
        justify-content: space-between;
        list-style-type: none;
    }
    /* just removing the icon-next icon coming along with Protostar */
    ul.k-pagination__pages li.icon-next:before { content:unset;}
</style>

<h2>Fetches a table from Google Sheet</h2>
<p>Data coming from <a target="_blank" href="https://docs.google.com/spreadsheets/d/e/2PACX-1vTEbblQfWW2ZjGlt1bXO59yixftz9Ok1dnDuCMIOYyk-Gow4mkxolXOsj_iECGDKHq9j1xNHQcdC8lw/pub">https://docs.google.com/spreadsheets/d/e/2PACX-1vTEbblQfWW2ZjGlt1bXO59yixftz9Ok1dnDuCMIOYyk-Gow4mkxolXOsj_iECGDKHq9j1xNHQcdC8lw/pub</a></p>

<p>Filtering works particularly well with webservices (or single database database tables). Examples:</p>
<ul>
    <li><a href="https://pages.joomlacustomfields.org/gsheet2?filter[profession]=jedi">https://pages.joomlacustomfields.org/gsheet2?filter[profession]=jedi</a></li>
    <li><a href="https://pages.joomlacustomfields.org/gsheet2?filter[profession]=doctor">https://pages.joomlacustomfields.org/gsheet2?filter[profession]=doctor</a></li>
    <li><a href="https://pages.joomlacustomfields.org/gsheet2?filter[profession]=police%20officer">https://pages.joomlacustomfields.org/gsheet2?filter[profession]=police%20officer</a></li>
</ul>

<hr>

<div class="well">
<ul>
<? foreach(collection() as $person) :  ?>
	<li>
		<?= $person->firstname ?>,<?= $person->lastname ?>: <?= $person->profession ?>
	</li>
<? endforeach; ?>
</ul>
<?= helper('paginator.pagination') ?>
</div>
<hr>

<?= source('/gsheet2') ?>

<hr>

<h3>See hereafter the var_dump(collection())</h3>
<pre><code><?= var_dump(collection()); ?></code></pre>

Sitemap

    ---
@process:
    cache: false

metadata:
    robots: [none]
visible: false
---
    <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">

    <? $pages = collection('pages', [
        'level'  => 0,
        'limit'  => 0,
        'sort'   => 'date',
        'order'  => 'desc',
        'filter' => [
            'metadata' => [
                'robots' => ['nin:noindex', 'nin:none'],
            ],
            'redirect' => 'null',
        ]
    ]); ?>

    <? $urls = []; ?>
    <? foreach($pages as $page): ?>

        <? $url = (string) route($page); ?>

        <? if($url) : ?>
            <url>
                <loc><?= escape($url) ?></loc>
                <lastmod><?= $page->date->format(DateTime::ATOM); ?></lastmod>
            </url>

            <? $urls[$url] = $url; ?>
        <? endif ?>
    <? endforeach ?>

    <? $cache = collection('/cache.json', [
        'filter' => [
            'robots' => ['nin:noindex', 'nin:none'],
            'format' => 'html',
            'status' => 200,
        ]
    ]); ?>

    <? foreach($cache as $item) : ?>

        <? if(!isset($urls[$item->url])) : ?>
            <url>
                <loc><?= escape($item->url) ?></loc>
                <lastmod><?= $item->date->format(DateTime::ATOM); ?></lastmod>
            </url>
        <? endif ?>
    <? endforeach ?>
</urlset>

Airtable

    ---
collection:
    model: airtable
    config:
        url: https://api.airtable.com/v0/appWVzJiBCYH4Ex59/Table%201
        api_key: key79zE0EHqAbTAiM
        identity_key: Autonumber
    state:
        sort: Notes
@process:
    cache: false
    filters: highlight
---
    <h2>Airtable collection model</h2>
<p>With Pages you can also create your own Collection Models. Example here with <a target="_blank" href="https://www.airtable.com/">Airtable</a></p>
<p><a target="_blank" href="https://github.com/joomlatools/joomlatools-pages/discussions/623">https://github.com/joomlatools/joomlatools-pages/discussions/623</a></p>

<div class="alert alert danger">
	<p><strong>WARNING:</strong> Do not share your API key with anyone (including anyone at Airtable) since it's effectively a password that allows access to all your bases. If you accidentally reveal your API key, you should regenerate your API key as soon as possible at https://airtable.com/account</p> 
	<p><a target="_blank" href="https://support.airtable.com/hc/en-us/articles/219046777-How-do-I-get-my-API-key-">How do I get my API key?</a></p>
	<p>Note: For this example, I created two (linked) tables on Airtable</p>
</div>

<hr>
<div style="font-size: smaller">
<h3>Considerations about caching</h3>
<p>Note: I have created a "Last Modified" automatic column in Airtable. But beware: rename the column "lastModified" bc it is named so in the Airtable Collection Model.</p>
<h4>My observation</h4>
<p>There is a cache file in /cache/airtable (that I deleted manually in order to force a refresh after Airtable source was changed</p>
<p>But even when I add the following to the frontmatter the cache seems to never refresh<p>
<pre><code>@process:
    cache: false</pre></code>
<h4>Explanation by Johan</h4>
<p>Different kind of cache. There is a cache for the data that is retrieved from Airtable, that cache is updated in the background, to turn it off you need to set the global http_client_cache to false. I wouldn’t do that though, because then pages needs to fetch the data from airtable for each time you request data friom the airtable model. This cache is updated in the background.<p>
<p>By <a target="_blank" href="https://support.airtable.com/hc/en-us/articles/360022745493-Last-modified-time-field">adding a lastModified field to your Airtable</a>, you make pages smarter so it can know when an individual row in the Airtable has been changed or not, see in the Airtable Collection Model:
<pre><code>//Check if the database has a lastModified column
        if(!$this->_hash_key && array_key_exists('lastModified', $data[0])) {
            $this->_hash_key = array('lastModified');
        }</pre></code>
<p>This code handles that.</p>
<p>The</p>
<pre><code>@process:
    cache: false</pre></code>
handles the output caching of the actually generated html page. Turn this off and the page is re-generated for each request, this is what Joomla does.</p>
</div>

<hr>
<h3>Here is a practical example with Airtable</h3>
<p>See the original data on <a target="_blank" href="https://airtable.com/shrz6XLIz7DJDEsIE">https://airtable.com/shrz6XLIz7DJDEsIE</a></p>

<? foreach(collection() as $item): ?>
	<div class="alert alert-info">
		<p><strong>Notes:</strong> <a href="<?= route('/airtableitem', ['Autonumber' => $item->Autonumber]) ?>"><?= $item->Notes ?></a></p>
		<p><strong>Autonumber:</strong> <?= $item->Autonumber ?></p>
		<p><strong>Status:</strong> <?= $item->Status ?></p>
		<p><strong>createdTime:</strong> <?= $item->createdTime ?></p>
		<p><strong>Feature (first and second from array):</strong> <?= $item['Feature (from Table 2)'][0] ?> | <?= $item['Feature (from Table 2)'][1] ?? '(no second one)' ?></p>
		<p><strong>Thumbnail (small):</strong>: <img src="<?= $item['Attachments'][0]['thumbnails']['small']['url'] ?>"></p>
	</div>
<? endforeach; ?>

<hr>

<p>And here is a little <code>print_r</code> of each $item of the collection so that you can see all what is immediately available in the array itself:</p>
<? foreach(collection() as $item): ?>
    <pre><code><?= print_r($item, true); ?></code></pre>
<? endforeach; ?>

Airtableitem

    ---
collection:
    model: airtable
    config:
        url: https://api.airtable.com/v0/appWVzJiBCYH4Ex59/Table%201
        api_key: key79zE0EHqAbTAiM
        identity_key: Autonumber
@route: /air/[:Autonumber]
@process:
    filters: highlight
---
    <h2>Airtable item</h2>

<article class="alert alert-info">
   <h3><?= collection()->Notes ?></h3>
   <p><?= collection()->Autonumber ?></p>
   <p><?= collection()->Status ?></p>
   <p><?= collection()->createdTime ?></p>
   <p>Feature: <?= collection()->get('Feature (from Table 2)')[0] ?></p><?// use get bc [] does not work in this context where the name has spaces ?>
   <p><img src="<?= collection()->Attachments[0]['thumbnails']['large']['url'] ?>"></p>
</article>

<hr>
<h3>A print_r</h3>
<pre><code><?= print_r(collection(), true); ?></code></pre>

Module

    ---
title: Module
@process:
    filters: ext:joomla.template.filter.module
---
    <h2>Modules injected directly from Pages</h2>
<p>This is a regular text on my page.</p>
<p>To enable this feature, I have to add the following filter in the frontmatter, in the @process part:<br />
<samp>filters: ext:joomla.template.filter.module</samp></p>
<p>If I have multiple filters (TOC, highlight, ...) then it will look as follows:<br />
<samp>filters: [highlight, ext:joomla.template.filter.module]</samp></p>

<? // note: position-7 is the position on the right in Protostar ?>
<ktml:module position="position-7" title="A Module from Pages">
    <p>This <b>Module</b> is injected directly from my page (and of course the content of that module could be a partial).</p>
    <p>Want to go even further? See the concept of <b>Block</b> which is even more powerful<br />
    <a href="https://github.com/joomlatools/joomlatools-pages/pull/704">https://github.com/joomlatools/joomlatools-pages/pull/704</a></p>
</ktml:module>

ACL

    ---
title: ACL
@process:
    filters: [highlight, ext:joomla.template.filter.module]
---
    <h2>ACL (Access Control Lists), namely Access Rights</h2>

<p><small>Note: this was introduced with Pages 0.21</small></p>
<p>To keep things simple for now, we had added <code>cache: false</code> in the frontmatter. This turns of caching for the page (Pages is build for caching anonymous requests at the moment, caching authenticated requests isn't fine-tuned yet.. </p>

<?= partial('/embeds/acl') ?>
<? // following would work as well: partial('template:/partials/embeds/acl') ?>

<?= source('template:/partials/embeds/acl') ?>

<? // note: position-7 is the position on the right in Protostar ?>
<ktml:module position="position-7" title="Want to test the ACL page?">
    <p>Use the following credentials</p>
    <ul>
        <li>Username: <samp>demo</samp></li>
        <li>Password: <samp>demo</samp></li>
    </ul>
    <p>You can also download a copy of this full website if you want to test it as Super User or whatever type of account you create.</p>
</ktml:module>

ACL partial

    <h3>Only visible for hasRole or hasGroup</h3>

<p>Please note that all Roles and Groups should be <i>all lowercase</i> when you code with Pages at the moment (in the future you can use whatever).</p>

<h4>Following text 'blabla' is only visible for hasRole <samp>'special'</samp></h4>
<? if(user()->hasRole('special')) : ?>
   <p>blabla</p>
<?  endif ?>

<h4>Following text 'blabla' is only visible for hasGroup <samp>'registered'</samp></h4>
<? if(user()->hasGroup('registered')) : ?>
   <p>blabla</p>
<?  endif ?>

<h4>Following text 'blabla' is only visible for hasGroup <samp>'super users'</samp></h4>
<? if(user()->hasGroup('super users')) : ?>
   <p>blabla</p>
<?  endif ?>

<h4>Following text 'blabla' is only visible for hasGroup <samp>'registered' OR 'super users'</samp></h4>
<? if(user()->hasGroup(['registered', 'super users'])) : ?>
   <p>blabla</p>
<?  endif ?>

<h4>Following text 'blabla' is only visible for hasGroup <samp>'registered' AND 'super users'</samp></h4>
<? if(user()->hasGroup(['registered', 'super users'], true)) : ?>
   <p>blabla</p>
<?  endif ?>

JSON-LD

    <? //Generate microdata
$segments  = [];
$microdata = data([
    "@context" => "https://schema.org",
    "@type"    => "BreadcrumbList",
    'itemListElement' => []
]);

foreach ($route as $key => $segment)
{
    $segments[] = $segment;
    $microdata->itemListElement = [
        "@type"    => "ListItem",
        "position" => $key + 1,
        "name"     => rtrim(page()->isCollection() ? ucfirst($segment) : page()->title, '.'),
        "item"     =>(string) url(route(implode('/', $segments)))
    ];
}
?>

<script data-inline type="application/ld+json">
<?= $microdata ?>
</script>