by Marc Dechèvre | Woluweb
all presentations on https://slides.woluweb.be
This presentation which could alternatively be named
Joomlatools Pages – a beginner’s guide
was made especially for
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!)
Example taken from the Wiki: https://github.com/joomlatools/joomlatools-pages/wiki/Functions
Joomlatools Pages comes out-of-the-box with template functions that help you to easily customise your template. Functions are only supported for the PHP engine. Of course, you can also use any PHP function in your templates.
Note:
will download the latest versions of Pages and the Joomla Extension
Johan Janssens, the father of Joomlatools Pages, has already made three general presentations:
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 an integrator and what could be called an 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.
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
Before playing with our new tool, let’s prepare Joomlatools Pages and our website
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)
For other installation methods, see https://github.com/joomlatools/joomlatools-pages/wiki/Installation
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…
Note:
See this explanation (which also covers When to update Pages):
https://github.com/joomlatools/joomlatools-pages/discussions/760
Don’t forget to also replace your extension(s) if they were updated (which you see in the release’s changelog for example with a fixed – ext:joomla
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
Oh btw nobody wants to see that /index.php/
in the url, right?
So if you have not already done so
htacces.txt
was renamed into .htaccess
Then the url https://pages.joomlacustomfields.org/index.php/step1 simply becomes https://pages.joomlacustomfields.org/step1
Note: Pages requires 'SEF’ to be enabled but it can work without 'SEF rewrite on’. Then of course you need to keep the /index.php/ in the url and do http://example.com/index.php/[page]
Pages is build using a “no configuration required, everything has a default” approach.
This means that you do not require anything in your config.php
to get started. Anything you add there would override a default.
Using your File Manager or your FTP connexion, create the following folder and subfolder
/joomlatools-pages/pages/
Article Category
with Alias: organizations3 articles
in that Category with an image (intro or full)Custom Field
(of type Text for example) with the following Name: socialbrusselsid
Custom Field
(of type URL) with the following Name: youtube
The socialbrusselsid Custom Field will allow us to combine our articles with information fetched from an external website via webservices.
Example:
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:
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
JoomlaTools Pages 0.21 was released in June 2021 and came with plenty of improvements and new features.
It can now also run in Standalone mode.
For an overview (and also for more information on a few breaking changes) see https://github.com/joomlatools/joomlatools-pages/discussions/738
Pages
, those don’t exist any more => simply use Menu Items of type System Links > URL
instead (one exception to that at the moment: the Home menu item. See https://github.com/joomlatools/joomlatools-pages/discussions/758)/cache/
directory/templates/
and move the /partials/
directory into it@
in order to have respectively @route
, @layout
, @collection
, @form
, @process
extend: path
: add a slash before the path to get extend: /path
data: items
into data_path: items
/
both in frontmatter and in route calls<?= import('/partials/embeds/myfile') ?>
into <?= partial('/embeds/myfile') ?>
<?= $item->getRoute() ?>
into <?= route($item) ?>
The idea of this step by step demo is to onboard everybody, even completely non technical persons.
Let’s start with something super easy: a little HTML 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>
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
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
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. Strictly speaking the Menu Item Type could be anything: if Pages has a file named after the alias of the menu item, it will take precedence.
So a typical choice would be to create a Menu Item of type 'System Links > URL’ and set the Link field to /step1
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
You don’t know what MarkDown is ?
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.
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.
Let’s start with a simple PHP example
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>
Go to https://pages.joomlacustomfields.org/step3 and see the result.
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>
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.
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.
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>
Go to https://pages.joomlacustomfields.org/step4 and see the result.
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.
One caveat here: if you call it with a higher cache time first that call might result in data not being updated, while data lower on the same page with a call with a lower cache time is updated. This would happen only on the first refresh of the page, the second time you render the same page since the data was updated in cache you get the correct results for all calls
It is now time to build a simple Blog view.
Pages allow indeed to query directly any Joomla table
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>
Go to https://pages.joomlacustomfields.org/step5 and see the result.
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
joomlatools-pages/extensions/
So actually with Pages 'installing’ extensions is just 'uploading’ them…
For more information, see https://github.com/joomlatools/joomlatools-pages/discussions/738#Overview
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')
Go to https://pages.joomlacustomfields.org/step6 and see the result.
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]
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!
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="<?= route($article) ?>"><?= $article->title; ?></a>
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
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; ?>
If you look at the code you will notice that, using ext:joomla.model.articles
, we don’t use introtext/fulltext
but excerpt/text
.
For more information, see https://github.com/joomlatools/joomlatools-pages/discussions/667
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.
How do we fetch information from an external website ?
<?= data('https://social.brussels/rest/organisation/'. $article->fields->get('socialbrusselsid')->value, '1day')->address->streetNl ?>
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
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
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>
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.
The main file refers to a partial transforming the socialbrusselsid
Custom Field into a block of several informations fetched from the external json file.
/templates/partials/embeds/socialbrussels.html.php
having the following content
(latest version on https://pages.joomlacustomfields.org/code#step7-article-embed-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>
</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.
/templates/partials/embeds/url.html.php
having the following content
(latest version on https://pages.joomlacustomfields.org/code#step7-article-embed-url)
<a class="btn btn-primary" href="<?= $url ?>">url transformed into a button via a Partial in embeds folder</a>
The main file refers to a partial transforming the youtube
Custom Field into a customized video player.
/templates/partials/embeds/youtube.html.php
having the following content
(latest version on https://pages.joomlacustomfields.org/code#step7-article-embed-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 ?>
Go to https://pages.joomlacustomfields.org/step7 and see the result.
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.
We will only need two files
Just for fun, in the Article View we will import a partial (ie an external file).
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#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>
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#step8-article)
---
@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>
See above
Go to https://pages.joomlacustomfields.org/step8 and see the result.
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>
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
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
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#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') ?>
As can be seen in the index file, we import a file /templates/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/templates/partials/myorganizations
create a file named
liststep9.html.php
having the following content
(latest version on https://pages.joomlacustomfields.org/code#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>
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#step9-article)
---
@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:
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 /templates/partials/myorganizations/myarticle.html
In the folder /templates/partials/myorganizations/
create a file named
myarticle.html.php
having the following content
(latest version on https://pages.joomlacustomfields.org/code#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>
Same as in the previous step.
Go to https://pages.joomlacustomfields.org/step9 and see the result.
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.
With Pages we can also directly call an Article by its ID or Alias.
So I am now done with what I really wanted to share with your about Pages.
But Pages ships with many other features.
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, …
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>
Go to https://pages.joomlacustomfields.org/decoration and see the result.
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
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>
Go to https://pages.joomlacustomfields.org/search and see the result.
Another feature is Filtering.
This can be done via the Frontmatter, via the url of via the code.
Note: difference between Searching and Filtering:
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>
Go to https://pages.joomlacustomfields.org/filter and see the result.
Another feature is Filtering directly on Custom Fields.
This can be done directly within your Frontmatter… or even via the url.
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>
Go to https://pages.joomlacustomfields.org/filter-cf and see the result.
You can actually filter on many many things.
Example here with Tags.
For more information, see https://github.com/joomlatools/joomlatools-pages/pull/363 > Filtering a collection for all possibilities
To see some examples see https://pages.joomlacustomfields.org/filter-tags
Pages is also able to dynamically display a list of files.
See our example on https://pages.joomlacustomfields.org/list
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') ?>
Go to https://pages.joomlacustomfields.org/list and see the result.
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
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>
Go to https://pages.joomlacustomfields.org/gdoc and see the result.
Go to https://pages.joomlacustomfields.org/gsheet + https://pages.joomlacustomfields.org/gsheet2 and see the result.
You can even filter on a Google Sheet 👍
Go to https://pages.joomlacustomfields.org/airtable and see the result based on the corresponding code on https://pages.joomlacustomfields.org/code#airtable
To make this work you first need to add the Airtable Collection Model to JoomlaTools Pages by adding the following code to
/joomlatools-pages/extensions/pages/model/airtable.php: https://github.com/joomlatools/joomlatools-pages/discussions/623
Pages is able to generate the sitemap automatically.
Pages is super-powerful regarding caching.
If you are a coder, you can even create your own collection (similar to ext:joomla.model.articles).
Pages is able to inject directly Modules in Joomla in any position.
Pages is even able to create a Menu Module if you want to bypass the Menu management in Joomla.
which came along with Pages 0.21 in June 2021
Pages also allows to use Joomla’s ACL.
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]
Since version 0.21 Pages also allows 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.
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
See
See
https://github.com/joomlatools/joomlatools-pages/pull/369 Template (layout and partial) location handling and importing got a major overhaul for 0.21, it’s now possible to distribute templates with extensions
This opens a whole world of flexibility, imagine creating a Pages extension that provides re-usable layouts and partials, together with additional template function and or helpers, all that nicely zipped in a phar for easy distribution.
Don’t know what phar format is? See https://en.wikipedia.org/wiki/PHAR_(file_format)
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:
First of all I would like to thank Johan Janssens for different reasons
Second, I should make a confession
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