Joomla, Custom Fields and external data ● powerful overrides made easy with Joomlatools Pages

by Marc Dechèvre | Woluweb

Marc

woluweb

all presentations on https://slides.woluweb.be

1. JoomlaDay USA

This presentation -which could alternatively be named
Joomlatools Pages – a beginner’s guide
was made especially for

JoomlaDay USA | 24 April 2021 | #jdayusa

Note: the video content will only be available to paid registrants of https://jdayusa.com

2. Introduction

2.1. Demo site

We have built a demo site so that you can see the results of all the actions explained step by step in this presentation

https://pages.joomlacustomfields.org/

The corresponding code is

Joomlatools Pages is free and open source (feel free to contribute/give back!)


Note:

will download the latest versions of pages or the joomla extension

2.3. Joomlatools Pages – presentations by the author himself

Johan Janssens, the father of Joomlatools Pages, has already made three general presentations:

2.4. Why this presentation

I share two qualities with Johan Janssens :

Johan, who is also one of the cofounders of Joomla itself, is of course an excellent developer.


I don’t consider myself as a developer (I don’t code extensions, even if I play a lot with overrides which entails some PHP knowledge).

I am more what could be called a power user: I know Joomla by heart and like to use its full capabilities.

And I am definitely a Joomla Custom Fields 'evangelist’ since it was released with J!3.7 thanks to Allon Moritz aka Laoneo.

Custom Fields are indeed a game changer and has made Joomla much more powerful, especially if you use overrides in order to customise the Layout.


So when I discovered Joomlatools Pages, I immediately saw a great opportunity because


So why this presentation?

Simply because I am always happy to share my enthousiasm a.o. about everything related to Joomla Custom Fields.

And also because I like to learn by doing. So I thought a good way of showing some of the features offered by Joomlatools Pages would be to make a step by step demo.

2.5. Focus on Pages to make overrides including Custom Fields and fetching external data

Joomlatools Pages can already do so many things (plus the fact that features are regularly added/improved) that it would probably take a full day to cover them all…

So in this presentation we will 'only’ see how to make (blog view and article view) overrides including Custom Fields and fetching external data because even so there is already so much to tell!

As you will see, Pages is so powerful and yet so easy…


In particular, Joomlatools Pages allows to fetch and display collections from

3. The initial setup

Before playing with our new tool, let’s prepare Joomlatools Pages and our website

3.1. Install


When you see “Installation successfully completed!” you are done
(don’t worry if the interface is a bit different from usual after installation: Pages uses its own installer)

pages_install


For other installation methods, see https://github.com/joomlatools/joomlatools-pages/wiki/Installation

3.2. Nothing new in the administration interface…

Now that Pages is installed (together with the Joomlatools Framework), you will notice… that nothing has changed in the backend 🙂

No new Component, no Configuration, no anything.

Actually this is totally normal: the only thing you will maybe do in Joomla’s backend related to Joomlatools Pages is adding a few Menu Items.

3.3. Joomla Update Manager

Note:

3.4. Create the folder

Using your File Manager or your FTP connexion, create the following folder and subfolder

/joomlatools-pages/pages/

3.5. Before starting: SEF

Do not run Joomla sites anymore with SEF disabled, this setting should be enabled at all times. The only reason it was allowed not to turn SEF on in Joomla 1.5 was to allow for a solid migration path from 1.0 to 1.5, we are now 15 years later, every Joomla site should have SEF enabled by default.

So if you have not already done so

3.6. Before starting: URL Rewriting

Oh btw nobody wants to see that /index.php/ in the url, right?

So if you have not already done so

Then the url https://pages.joomlacustomfields.org/index.php/step1 simply becomes https://pages.joomlacustomfields.org/step1

3.7. Let’s create some content with Custom Fields


The socialbrusselsid Custom Field will allow us to combine our articles with information fetched from an external website via webservices.

Example:

3.8. See the latest version of the source code on the website itself

To make it easy, I have copied the source of each page in the slides of this presentation itself.

But regularly I bring improvements. So if you want to be sure to get the latest version, they are directly visible on the demo website:

https://pages.joomlacustomfields.org/code


Note: how can I display the source code automatically on the website? Txs to Joomlatools Pages itself.

All I have to do is to create a file

code.html.php

with the following content

 ---
process:
    filters: [highlight]
 ---

<?= source('step6') ?>

where step6 refers to my file step6.html.php which is created later in this presentation.


Note: in this example and on most of the following ones, I have added

process: filters: [highlight]

in the Frontmatter. This gives automatic code highlighting.

On a production site you would probably remove it, but here I have included it bc I display on purpose the whole array of articles with

<pre><code><?= var_dump(collection()); ?></code></pre>

See https://github.com/joomlatools/joomlatools-pages/discussions/668 for more information.

Note: to change the style from light atom-one-light.min.css to dark atom-one-dark.min.css, simply adapt /components/com_pages/template/filter/highlight.php accordingly

4. A step by step demo

The idea of this step by step demo is to onboard everybody, even completely non technical persons.

4.1. Joomlatools Pages with HTML input

Let’s start with something super easy: a little HTML file.

4.1.1. Create the file

In the folder /joomlatools-pages/pages/ create a file named

step1.html

having the following content
(latest version on https://pages.joomlacustomfields.org/code#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>

4.1.2. See the result

Go to https://pages.joomlacustomfields.org/step1 and see the result: step1.html is displayed as if it were a regular Joomla article!

So actually, so far we already have a kind of Flat File CMS 🙂

But the nice thing is that you still benefit from

4.1.3. Metadata

Do you want to customize the Title and the Description of the page ?

This can also be done very easily by adding this at the beginning of your file

 ---
title: A HTML page
summary: This page is a static HTML page.
 ---

See https://github.com/joomlatools/joomlatools-pages/wiki/Metadata for more information

Note: the part between --- and --- is called the Frontmatter

4.1.4. Creating a menu item – optional

The following step is not required, however if you want Joomla to show a menu item for your page you can do so by creating one.

In the administrator interface, go to your menu manager and create a new menu item. The Menu Item Type should be 'Pages > Default’. Add the title and alias, making sure that the alias matches the file you created. For example:

Note: you could of course also create a Menu Item of type 'System Links > URL’ and set the Link field to /step1

4.1.5. Multilingual websites

Once you have played with Joomlatools Pages it will seem obvious to you: if you have a multilingual website and the url of a page in English is YOURSITE.COM/en/step1 then the corresponding file should be

/joomlatools-pages/pages/en/step1.html

and not

/joomlatools-pages/pages/step1.html

4.2. Joomlatools Pages with MarkDown input

You don’t know what MarkDown is ?

4.2.1. Create the file

In the folder /joomlatools-pages/pages/ create a file named

step2.html.md

having the following content
(latest version on https://pages.joomlacustomfields.org/code#step2)

 ---
title: A Markdown page
summary: This page is rendered by the Markdown engine.
 ---
 ## Hello World!

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

4.2.2. See the result

Go to https://pages.joomlacustomfields.org/step2 and see the result: step2.html.md is displayed as if it were a regular Joomla article!

All my presentations (including this one) are written in MarkDown format (.md file). This means that I could now very easily display on my website a nice HTML version of those Presentations.

4.3. Joomlatools Pages with PHP input

Let’s start with a simple PHP example

4.3.1. Create the file

In the folder /joomlatools-pages/pages/ create a file named

step3.html.php

having the following content
(latest version on https://pages.joomlacustomfields.org/code#step3)

 ---
title: A PHP page
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>

Note the short syntax
<h2><?= $title ?></h2>
instead of what we usually have in Joomla files
<h2><?php echo $title; ?></h2>

4.3.2. See the result

Go to https://pages.joomlacustomfields.org/step3 and see the result.

4.3.3. Insert markdown directly in your PHP file

With Pages you can also insert directly markdown (or other types of file) in your PHP file with the following syntax:

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

4.4. Joomlatools Pages with PHP input fetching external data

And now a killing feature: how to get data from an external website (in this case in json format) in 1 (one!) line of code.

For this demo, let’s use the open data made from https://social.brussels/ which gives all the details (name, address, phone number, …) of all Social organizations in Brussels.

4.4.1. How does social.brussels work

On that website, if you search and select a given Organization, you will get such an url. Example:
https://social.brussels/organisation/13817

where 13817 is the ID of that organization.

And if you add /rest/ after the domain name, you actually get the same information… in json format
https://social.brussels/rest/organisation/13817

You will note that most of the fields have either a suffix Fr (for French) or Nl for Dutch since our country is multilingual.

Note: if you open the latter url in Firefox then you don’t even need an add-on in the browser to see the json in a structured way and with code highlighting.

4.4.2. Create the file

In the folder /joomlatools-pages/pages/ create a file named

step4.html.php

having the following content
(latest version on https://pages.joomlacustomfields.org/code#step4)

 ---
title: A PHP page fetching external data from json file
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>

4.4.3. See the result

Go to https://pages.joomlacustomfields.org/step4 and see the result.

4.4.4. Caching

And the good news is: external data can be easily cached and you can choose the duration easily.

For more info see: https://github.com/joomlatools/joomlatools-pages/pull/512

By default caching is enabled and the data will not be revalidated.

But thanks to the data([url], [cache]) template function you can customize this.

Example if you want to cache for 24 hours:

<? $data = data([url], '1day'); ?>

In this example we make multiple calls to the same resources.

So the question is: what does happen with the Cache ?

Each time you make a call to data, Pages will check if the resource it has cached is still valid. If it is it will use it from cache, if not it will update. So multiple data() functions to the same url with a diff cache time the lowest time wins.

4.5. Blog view using table=content from database

It is now time to build a simple Blog view.

Pages allow indeed to query directly any Joomla table

4.5.1. Create the file

In the folder /joomlatools-pages/pages/ create a file named

step5.html.php

having the following content
(latest version on https://pages.joomlacustomfields.org/code#step5)

 ---
collection:
    model: database?table=content
 ---
<h2>Blog view using database?table=content</h2>
<p>We list all articles directly from the database</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>

4.5.2. See the result

Go to https://pages.joomlacustomfields.org/step5 and see the result.

4.5.3. Images options are stored in JSON format in Joomla

If you have already seen Joomla’s native code or made overrides, you know about this: the Article Image’s options are stored in the database in JSON format. So to get the path/name of an image, we must first json_decode it:

<? $article_images  = json_decode($article->images);?>
<img src="<?= $article_images->image_intro; ?>

Accessing directly a table from the database was already nice but it is possible to do much more thanks to Joomlatools Pages’ Joomla Extensions

For more information see https://github.com/joomlatools/joomlatools-pages/pull/363

4.6.1. Install Joomlatools Pages’ Joomla Extensions

So actually with Pages 'installing’ extensions is just 'uploading’ them…

4.6.2. Create the file

In the folder /joomlatools-pages/pages/ create a file named

step6.html.php

having the following content
(latest version on https://pages.joomlacustomfields.org/code#step6)

 ---
collection:
    model: ext:joomla.model.articles
    state:
        published: 1
        category: [organizations]
        sort: date
        order: asc
        limit: 3
 ---

<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>aa<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>

Note:

The ext:joomla.model.articles is available also with the alias articles, so you can do collection('articles')

4.6.3. See the result

Go to https://pages.joomlacustomfields.org/step6 and see the result.

4.6.4. Filtering and sorting

In the Frontmatter we can very easily filter and sort our content thanks to the state:

Very easy to visualize and write…


Note: if you want to filter on multiple categories, simply use the following syntax:

category: [organizations, uncategorised]

4.6.5. Some customized styling with CSS Grid and/or Flexbox

For the HTML/CSS of this demo, we did not want to bother with Bootstrap because

So if I had used Bootstrap, you could not necessarily simply copy/paste the code to get the same result if your Bootstrap version is different (or if you are using another framework like UIKit, Tailwind, …).


Instead, to get something easier, lighter and more universal, we simply use here CSS Grid and Flexbox.

If you have no clue what it is, you should definitely have a look at it. Especially for CSS Grid since Joomla4 is based on CSS Grid, which is a (very) good thing.

Note: as you can see, I am using here 'inline CSS’ with a style tag directly inside my HTML.
You don’t even need to put it in your template 'custom css’ because Pages will automatically put the CSS into the <header> of your site!

4.6.6. Using the Joomla Router

You want the Title to have a link to the Article itself?

Using the native Joomla Router the following single line will do:

<a href="<?= $article->getRoute() ?>"><?= $article->title; ?></a>

4.6.7. A direct access to the image path/name

If you look at the code you will notice that, using ext:joomla.model.articles, we don’t need to play with json_decode to get the image path/name. It is indeed immediately available:

<?= $article->image->url; ?>

Note 1: actually, in the code itself I should first check that there is an image before displaying it. So here is the code to display an Article image (with its Title and Description) only if it exists:

<? if( !empty( $article->image ) ): ?>
    <img src="<?= $article->image->url; ?>" alt="<?= $article->image->alt; ?>" title="<?= $article->image->caption; ?>">
<? endif; ?>

Note 2: is it the intro image or the full text image ? See the answer here: https://github.com/joomlatools/joomlatools-pages/discussions/673

4.6.8. Category title/alias

If you look at the code you will notice that, using ext:joomla.model.articles, we can access the Category title/alias directly:

<?= $article->category->title; ?>

(whereas using database?table=content we could only access the Category ID since only the ID is mentioned in the content table. For more information if you wish to make links between tables, see https://github.com/joomlatools/joomlatools-pages/wiki/Cookbook-Articles#creating-a-mysql-view-to-merge-data-from-joomla-categories-and-articles)

4.6.9. introtext/fulltext

If you look at the code you will notice that, using ext:joomla.model.articles, we don’t use introtext/fulltext but exerpt/text.

For more information, see https://github.com/joomlatools/joomlatools-pages/discussions/667

4.6.10. Custom Fields

Custom Fields are sooooo easy to display:

Custom Field's label: <?= $article->fields->socialbrusselsid->label ?>
Custom Field's rawvalue (ie the value in the database): <?= $article->fields->socialbrusselsid->value ?>
Custom Field's rendering (ie using Joomla's default rendering with the CF's options): <?= $article->fields->socialbrusselsid ?>

Note: using the simple $article->fields->customfieldalias (so without referring to label or value) is very nice because it will use the Joomla’s rendering.

This is particularly useful if you are using third-party Custom Fields allowing to easily display Maps, Videos, Sound etc (see for example those proposed by Tassos: https://www.tassos.gr/joomla-extensions/advanced-custom-fields)


But beware: if you have spaces in the Title of your Custom Fields, Joomla will by default suggest a Name with dashes.

This is OK but then you will have to use one of these syntaxes

$article->fields->get('with-dash')->value
$article->fields->{'with-dash'}->value

instead of the short syntax

$article->fields->withoutdash->value

So to keep things simple I suggest to avoid dashes in the Names of the CF.

4.6.11. External JSON

How do we fetch information from an external website ?

<?= data('https://social.brussels/rest/organisation/'. $article->fields->get('socialbrusselsid')->value, '1day')->address->streetNl ?>

4.6.12. More generally, retrieving data from a SAAS service

See https://github.com/joomlatools/joomlatools-pages/discussions/505

Retrieving data from a SAAS service with Pages is very easy. Pages handles data sources in a transparent way. It doesn’t make a difference if you connect to a database, a webservice or a file on the filesystem. They all work in exactly the same way.

To work with data Pages uses collections and specifically for webservices, there is a base webservice collection you can use.

Every collection can be: filtered / sorted / searched / paginated

Pages offers out of the box support for JSON and can even read CSV from a webservice, which is a great way to integrate with things like Google Spreadsheets.


An example would be

 ---
collection:
   model: webservice?url=http://example.com/api/endpoint
 ---

<? foreach(collection() as $item) : ?>
   <?= $item->title ?>
<? endforeach; ?>

assuming that the service returns a title property

4.6.13. Comparison with a typical native Joomla override

Of course, the scope is not exactly the same (I did not include some Article Options to keep it short for the presentation), but see how simple/concise/readable this code is compared to the code of a typical native Joomla override:

https://github.com/joomla/joomla-cms/tree/staging/components/com_content/views/category/tmpl

See for example blog.php and blog_item.php

Let’s put some parts of the code in little files (called partials) so that


This will allow us for example to easily transform

4.7.1. Create the file

In the folder /joomlatools-pages/pages/ create a file named

step7.html.php

having the following content
(latest version on https://pages.joomlacustomfields.org/code#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>

4.7.2. Create the partials for the Custom Fields

In Joomla when you want to create a new type of Custom Field you typically have to create a little plugin.

This is fairly easy for someone who can code a bit.

But it becomes even much easier with Pages since all you need to to is to create a little file containing the rendering.

4.7.2.1. A partial for TEXT Custom Field – socialbrusselsid

The main file refers to a partial transforming the socialbrusselsid Custom Field into a block of several informations fetched from the external json file.

/partials/embeds/socialbrussels.html.php

having the following content
(latest version on https://pages.joomlacustomfields.org/code#step7articleembedsocialbrussels)

<? $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>
   </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>
   </ul>
</ul>

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

The main file refers to a partial transforming the youtube Custom Field into a button.

/partials/embeds/url.html.php

having the following content
(latest version on https://pages.joomlacustomfields.org/code#step7articleembedurl)

<a class="btn btn-primary" href="<?= $url ?>">url transformed into a button via a Partial in embeds folder</a>
4.7.2.3. A partial for URL Custom Field – YouTube video player

The main file refers to a partial transforming the youtube Custom Field into a customized video player.

/partials/embeds/youtube.html.php

having the following content
(latest version on https://pages.joomlacustomfields.org/code#step7articleembedyoutube)

<? 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 ?>

4.7.3. See the result

Go to https://pages.joomlacustomfields.org/step7 and see the result.

4.7.4. Not necessary any more to create plugins to get new CF Types

So as you can see: it is so easy to create a little file that will take care of transforming a basic native Text field into anything (video player, map, …) that you don’t necessarily need to build a plugin to create a new 'type of Custom Field’.

Pages ships with its own router.

This means you have direct control on the slug/url of your Articles.

4.8.1. Create the files

We will only need two files

Just for fun, in the Article View we will import a partial (ie an external file).

4.8.1.1. The Blog View

In the folder /joomlatools-pages/pages/ create a file named

step8.html.php

having the following content
(latest version on https://pages.joomlacustomfields.org/code#step8blog)

 ---
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>
4.8.1.2. The Article View

In the folder /joomlatools-pages/pages/ create a file named

step8article.html.php

having the following content
(latest version on https://pages.joomlacustomfields.org/code#step8article)

 ---
collection:
    model: ext:joomla.model.articles
    state:
        published: 1
        category: [organizations]
        sort: date
        order: asc
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]) ?>
</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>
4.8.1.3. A partial for TEXT Custom Field – socialbrusselsid

See above

4.8.2. See the result

Go to https://pages.joomlacustomfields.org/step8 and see the result.

4.8.3. Pointing to the Article view

In the Blog view, this is how we point to the file of the Article view (and make sure the slug will be as defined in the Article view):

<a href="<?= route('step8article', ['slug' => $article->slug]) ?>"><?= $article->title; ?></a>

4.8.4. Customizing the slug

In the Article view we define in the Frontmatter what the slug will be.

In this case, /blabla/article-alias:

route: blabla/[:slug]

Last but not least, after having made a simple 'override’ of the Blog view and of the Article view let us play further with the partials

4.9.1. Create the files

So far we have always has all our xxx.html.php files in the /joomlatools-pages/pages/ folder.

But actually you can of course create subfolders, subsubfolders etc. For more information, see https://github.com/joomlatools/joomlatools-pages/wiki/Folder-Structure#pages

Example: the following two options produce exactly the same result:

/joomlatools-pages/pages/test.html.php

/joomlatools-pages/pages/test/index.html.php
4.9.1.1. the Blog view

In the folder /joomlatools-pages/pages/step9/ create a file named

index.html.php

having the following content
(latest version on https://pages.joomlacustomfields.org/code#step9blog)

 ---
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') ?>
4.9.1.2. the Blog partial

As can be seen in the index file, we import a file /partials/myorganizations/liststep8.html Of course, the content of that file could have been placed directly in index.html.php. But putting it in the partials makes it reusable and easier to maintain.

So in the folder /joomlatools-pages/partials/myorganizations create a file named

liststep9.html.php

having the following content
(latest version on https://pages.joomlacustomfields.org/code#step9blogpartial)

<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>
4.9.1.3. the Article view with its router

As can be seen in the partial file, we refer to a file step9/org

In the folder joomlatools-pages/pages/step9/ create a file named

org.html.php

having the following content
(latest version on https://pages.joomlacustomfields.org/code#step9article)

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

You can choose whatever you like to replace myslug, which will be visible in the url of Article “Organization 1” as follows:

https://pages.joomlacustomfields.org/myslug/organization-1

4.9.1.4. the partial of the Article view

Just to make things more fun, we create a partial which is imported in the article view.

As can be seen in the article view, we refer to a file /partials/myorganizations/myarticle.html

In the folder /partials/myorganizations/ create a file named

myarticle.html.php

having the following content
(latest version on https://pages.joomlacustomfields.org/code#step9articlepartial)

<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>
4.9.1.5. A partial for the socialbrusselsid Custom Field

Same as in the previous step.

4.9.2. See the result

Go to https://pages.joomlacustomfields.org/step9 and see the result.

4.9.3. Could have been done with 2 files

Of course, what we have made here in 4 files could have been done in just 2 files like seen at the previous Step.

But the idea was to show that snippets of code can be imported and so reused.

And this facilitates the maintenance of the code of course.

4.10. Other features

So I am now done with what I really wanted to share with your about Pages.

But Pages ships with many other features.

4.10.1. Decoration

Let us illustrate one of those features, namely the ability to 'decorate’ (namely add content before/after/…) the component’s view, whatever it is: article, third-party extension, …

4.10.1.1. Create the file

In the folder /joomlatools-pages/pages/ create a file named

decoration.html.php

having the following content
(latest version on https://pages.joomlacustomfields.org/code#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>
4.10.1.2. See the result

Go to https://pages.joomlacustomfields.org/decoration and see the result.

4.10.1.3. How to name the decoration file

So actually if the URL of the page I want to decorate is MYSITE.COM/blabla then all I need to do to decorate it is to create a file named

blabla.html.php

and customize it.

Another feature is Searching.

This can be done directly from the Frontmatter… or even via the url if you allow it in the Frontmatter.

For more information see https://github.com/joomlatools/joomlatools-pages/discussions/506

4.10.2.1. Create the file

In the folder /joomlatools-pages/pages/ create a file named

search.html.php

having the following content
(latest version on https://pages.joomlacustomfields.org/code#search)

 ---
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>
4.10.2.2. See the result

Go to https://pages.joomlacustomfields.org/search and see the result.

4.10.3. Filtering

Another feature is Filtering.

This can be done via the Frontmatter, via the url of via the code.

Note: difference between Searching and Filtering:

4.10.3.1. Create the file

In the folder /joomlatools-pages/pages/ create a file named

filter.html.php

having the following content
(latest version on https://pages.joomlacustomfields.org/code#filter)

 ---
collection:
    model: ext:joomla.model.articles
    state:
        sort: title
 ---

<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>
4.10.3.2. See the result

Go to https://pages.joomlacustomfields.org/filter and see the result.

4.10.4. Filtering directly on Custom Fields

Another feature is Filtering directly on Custom Fields.

This can be done directly within your Frontmatter… or even via the url.

4.10.4.1. Create the file

In the folder /joomlatools-pages/pages/ create a file named

filter-cf.html.php

having the following content
(latest version on https://pages.joomlacustomfields.org/code#filter-cf)

 ---
collection:
    model: ext:joomla.model.articles
    state:
        sort: title
        category: [organizations]
 ---

<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>
<h4>Filtering on Custom Field via url</h4>
<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>
4.10.4.2. See the result

Go to https://pages.joomlacustomfields.org/filter-cf and see the result.

4.10.5. List of files

Pages is also able to dynamically display a list of files.

See our example on https://pages.joomlacustomfields.org/list

4.10.5.1. Create the file

In the folder /joomlatools-pages/pages/ create a file named

list.html.php

having the following content
(latest version on https://pages.joomlacustomfields.org/code#list)

<h2>This list of files is dynamically generated by Pages</h3>

<?= partial('/embeds/list') ?>

<h3>Code</h3>
<?= source('template:/partials/embeds/list') ?>
4.10.5.2. See the result

Go to https://pages.joomlacustomfields.org/list and see the result.

4.10.6. GDoc

Yet another feature of Joomlatools Pages: it is possible to load any type of structured data file by file by url.

In this example, Pages will extract the content from a published Google Doc and inject it in the page.

For more information see https://github.com/joomlatools/joomlatools-pages/pull/264

4.10.6.1. Create the file

In the folder /joomlatools-pages/pages/ create a file named

gdoc.html.php

having the following content
(latest version on https://pages.joomlacustomfields.org/code#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>
4.10.6.2. See the result

Go to https://pages.joomlacustomfields.org/gdoc and see the result.

5. Y O U R next steps

Now time to go and play for yourself!

Curious to know even more? Have a look here to have examples of sites built with Joomlatools Pages:

6. Future features

6.1. ACL

Pages also allows to use Joomla’s ACL.

6.1.1. Via the Frontmatter

Simply add the following in the Frontmatter.

You can specify roles (called Access Levels in Joomla) and/or groups :

access:
    roles: [public]
    groups: [public, guest]

6.1.2. Via the code

In the next version (0.21) Pages will also allow to make it work directly via the code (for example if only a part of the page has restricted access).

And again it is super easy to use:

<h3>Only visible for hasRole or hasGroup</h3>
<? if(user()->hasGroup(['manager', 'administrator'], true)) : ?>
   <p>blabla</p>
<?  endif ?>
<? if(user()->hasRole('special')) : ?>
   <p>blabla</p>
<?  endif ?>

Go to https://pages.joomlacustomfields.org/acl and see the result.

6.2. Transform your site into a webservice

As you might know, Joomla!4 will ship with a Rest API.

But you don’t need to wait: with the next version (0.21) Pages will also allow you to transform your content into json:

Example:

The corresponding code:


Pages supports indeed rendering to different machine readable formats: CSV, JSON, RSS, XML. Support for all of those formats is included, and you do not need to write any code. Just create a file with the right format, defined a collection in it and Pages will render the data from the collection in the specific format.

For more informatoin, see https://github.com/joomlatools/joomlatools-pages/discussions/675

7. Conclusion

First of all I would like to thank Johan Jansens for different reasons


Second, I should make a confession

8. Get in touch

https://slides.woluweb.be

Any suggestion about this presentation ?
Please feel free to contact me. I’ll be happy to keep improving it:)

Marc Dechèvre | woluweb
+32 474 37 13 12 | +32 2 772 58 69

https://www.woluweb.be/contact