-
Notifications
You must be signed in to change notification settings - Fork 9
Cookbook Articles
It's extremely easy to build a collection from a list of articles in Joomla's database.
The first thing we need to do is to create a collection that calls the correct table. When we're constructing this, we don't need to use the table prefixes, or 'com_, we can just use 'content' instead of 'com_content'.
TIP: While working with Pages in the early stages, it's very handy to have phpMyAdmin open inspecting the columns of the database table you're working with.
The Frontmatter of the page would look like this:
---
collection:
model: database?table=content
---
This tells the page we're going to access data from the com_content table.
NOTE: Frontmatter uses YAML formatting. Indents are done with either 2 or 4 spaces, not tabs.
The easiest first step is to show a list of article names.
If you've already installed Joomlatools Pages, create a new file inside /joomlatools-pages/pages/ called articles.html.php and insert the following:
---
collection:
model: database?table=content
---
<? foreach(collection() as $article): ?>
<p>Title: <?= $article->title ?></p>
<? endforeach; ?>
View yoursite.com/articles in a browser and if Pages has been installed correctly, and you have articles in your database, you will see a list of article titles.
By viewing your database (either via CLI or graphical interface like phpMyAdmin), you will also be able to retrieve additional content by using the column name. For example, if you wanted to retrieve the introtext, it's very easy to add that to the list of fields you want to display:
---
collection:
model: database?table=content
---
<? foreach(collection() as $article): ?>
<p>Title: <?= $article->title ?></p>
Introtext: <?= $article->introtext ?>
<? endforeach; ?>
Showing a single article is not much more difficult, but it does help to become familiar with how routes work.
To make a route in this case, we need to be able to do the following:
- define that route in the frontmatter
- have something unique to filter on
- create a link using that route, based on the 'articles' collection we've already built
Defining the route
Defining the route is a simple addition to the frontmatter:
---
route: articles/[digit:id]?
collection:
model: database?table=content
---
This defines 'articles' (the collection we've already created with our articles.html.php file) as the base of the route, with the article ID as the identifier that will build a unique URL path to that article.
'digit' forms part of the route configuration and specifies that it only accepts digits as values.
By doing this, we are stating that our individual article pages will follow that route or path.
Filtering by 'isUnique'
state()->isUnique(); is a simple statement that filters results if they match a unique index set in the database. To put it simply, in table of articles, the only absolutely reliable column that will contain unique data in each field is the 'id' field.
It's important to understand that 'isUnique' only works on the entire table. Even if you are filtering the results by another method, unless that field data in that column is unique for the entire table, it won't work.
---
route: articles/[digit:id]?
collection:
model: database?table=content
---
<? if(state()->isUnique()): ?>
<h1><?= collection()->title ?></h1>
<article><?= collection()->introtext ?></article>
<? else : ?>
<ul>
<? foreach(collection() as $article): ?>
<li><?= $article->title ?></li>
<? endforeach; ?>
</ul>
<? endif; ?>
The above shows us how to display all articles either as a list, or as an individual article... but it doesn't provide us with the link to get from list items to articles.
Creating a route
---
route: articles/[digit:id]?
collection:
model: database?table=content
---
<? if(state()->isUnique()): ?>
<h1><?= collection()->title ?></h1>
<article><?= collection()->introtext ?></article>
<? else : ?>
<ul>
<? foreach(collection() as $article): ?>
<li><a href="<?= route('articles', ['id' => $article->id]) ?>"><?= $article->title ?></a></li>
<? endforeach; ?>
</ul>
<? endif; ?>
Let's break down that route in the link:
<?= route('articles', ['id' => $article->id]) ?>
We are saying to use the collection 'articles' (which has defined the route pattern in its frontmatter), and then to use the id of the current article data being displayed as the unique identifier in the URL of the single article.
You should now be able to go yoursite.com/articles/1 and see the content from the article with the ID of 1 in your Joomla database.
Sometimes it's necessary to separate the list and article views. One reason for this might be because the particular filter you need might not support isUnique. For example, if you wanted to filter by 'created_by' (the user id of the article author in Joomla), then isUnique will fail; it only works if the value is unique across the entire column.
NOTE It may be worth reviewing the Pages folder structure documentation at this point.
Let's look at creating a 'team' page with sub-pages for individual team members. We'll use the Joomla article system to create a category called 'team' and then each article will be a person's bio with the title as the name, content as the bio, and photo as the headshot.
This is what our structure might look like:
/pages
├── people.html.php
├── person.html.php
The first thing we'll do is to add a filter to the frontmatter that restricts our collection to the correct category. Because the com_content table only contains the catid of the article's category, we need to use that to filter by:
---
collection:
model: database?table=content
state:
filter:
catid: 2
---
In this example, the collection will only return items from the category with the id of 2.
Note that I also have no route data in this frontmatter.
Now I need to show a list of articles in that category:
---
collection:
model: database?table=content
state:
filter:
catid: 2
---
<ul>
<? foreach(collection() as $person): ?>
<li><?= $person->title ?></li>
<? endforeach; ?>
</ul>
Now I have a list of 'people' articles, but not the individual team member pages. I'm going to need to do two things to make this work;
- Build the 'person' page
- Reference the route to the person page in the links from the team page
---
route: people/[:alias]
collection:
model: database?table=content
state:
filter:
catid: 2
---
<div class="person">
<h1><?= collection()->title; ?></h1>
<?= collection()->introtext; ?>
<?= collection()->fulltext; ?>
</div>
Now we have a fairly simple 'person' page, that displays the title, introtext and fulltext of the article.
We also have defined a route that specifies that the path to this page will be 'people/article-alias'.
So now we can go back to our 'people' list page, and add in the route to the 'person' page:
---
collection:
model: database?table=content
state:
filter:
catid: 2
---
<ul>
<? foreach(collection() as $person): ?>
<li><a href="<?= route('person', ['alias' => $person->alias]) ?>"><?= $person->title ?></a></li>
<? endforeach; ?>
</ul>
Voila! We've built a route that looks at the route data in the frontmatter of person.html.php to build the route.
If you decided to use folders for organisation instead of just files, you only have minor changes. Let's look at a folder structure that looks like this:
/pages
/people
index.html.php
person.html.php
In this case, we've renamed people.html.php to index.html.php to ensure our paths are simple: /people will work as a path whether it's a file named people.html.php in the pages root, or if it's a folder named 'people' in the root with an index.html.php file inside.
The only change we make is to the route construction in the index.html.php page:
<?= route('people/person', ['alias' => $person->alias]) ?>
The important thing to note here is that because the 'person' route frontmatter is defined as 'route: people/[:alias]' that's how the path will be constructed.
The first part of the route in a link doesn't define the output in the path, it defines where to find the route data.
As we learned previously, it's easy to create a collection and a route to that collection. You can also use this on any page to pull in data from other collections.
One of the biggest issues that Joomla web designers face is that of 'module-itis'; where the solution to ever layout problem is to add another module. Even in relatively small sites, this can mean a large number of modules that make editing the site unwieldy. Clients often have difficulty understanding what modules do or how to edit them, and if the module was installed specifically to solve a particular challenge it can add additional scripts and styles which cause performance issues on the site.
Pages allows you the flexibility of using existing article data on pages without the need to create modules. It encourages developers to think differently about where and how they store data, and how it's accessed.
If you wanted to create a 'featured' module that showed the most recent featured article, it would usually require a module to do so.
Instead, with pages we're able to use a collection that filters the article data and provide us with what we need.
Using our previous 'people' collection as an example, let's assume we want to create a 'Featured Person' module on another page.
If the collection exists at /pages/people.html.php we can use the following:
<? foreach(collection('people') as $person):?>
<h2><?= $person->title; ?></h2>
<? endforeach; ?>
To generate a list of article titles from that collection.
If I want to grab just one person, I can use the 'limit' filter:
<? foreach(collection('people', ['limit' => 1]) as $person:?>
This will limit it to just 1 result, which will in turn be influenced by the collection's frontmatter. This is documented in the Wiki, but will be covered shortly in this cookbook. If the collection is sorted by date created, descending, then the limit will return the most recent article from the collection.
If I want to restrict that further to articles that are featured, then you can add an additional filter:
<? foreach(collection('people', ['limit' => 1, 'featured' => 1]) as $person:?>
In this case 'featured' is the name of a column in the content table, but it's also a core filter for Pages. Not all columns are supported for filtering by default, but it is possible to create a custom filter for the table that will extend which columns are supported.
Now you have the ability to display a single, featured 'person' from the people collection. You can use any of the columns, including 'images', 'introtext', and more to build a dynamic section on a page that would have previously required a module, and possibly template overrides to style.
We've previously looked at simple filtering in a page's frontmatter:
---
collection:
model: database?table=content
state:
filter:
catid: 2
---
In this case the only 'state' we're filtering by is the category id. We can also add in other states, including ordering:
---
collection:
model: database?table=content
state:
sort: ordering
filter:
catid: 2
---
'Ordering' is the name of a column in the content table, and when we use Joomla's article ordering inside a category, it will assign a numerical 'position' to indicate its order.
In this example, we've used it to specify that we want to order our collection via the ordering column.
If we wanted to order things chronologically, we'd use a different column 'created'. However, without specifying an additional direction, we'll get the oldest articles first (which might be the desired outcome). To make the order reverse chronological, we'd do this:
---
collection:
model: database?table=content
state:
sort: created
order: desc
filter:
catid: 2
---
In this way we can make collections behave in ways we're used to using the Joomla category manager.
When using pages to view content from Joomla articles, you'll notice that the *_content table in your database doesn't contain all of the data referenced in the article.
For example you'll have a category ID, but not the category name or category alias; using just the content table for your collection, you won't be able to show the category that the article belongs, or create a route that uses the category alias.
To solve this, we need to create a view in MySQL which acts as a virtual table and combines data from both the *_content table and the *_categories table.
Using this query (which can be done in PHPMyAdmin) will create the view:
CREATE VIEW ##_articles AS
SELECT article.*,
category.alias AS category_alias,
category.name AS category_title,
category.description AS category_description
FROM ##_content AS article
LEFT JOIN ##_categories AS category ON category.id = article.catid
What this does is:
- create a new view called 'articles'
- create new columns called category_alias, category_title, category_description which are mapped to the alias, name and description source table
- Use the content table as the source and all columns from it to create the view
- Add to that view those columns we created above and match the data from the source tables; based on the data in the id column in the category table, and the catid column in the content table, it will populate the matching data from the categories table for each row.
Using this formula, it's possible to extend this out to include other matched data. Adding in usernames is a great example.
Got a question or need help? We have a forum on Github Discussions where you can get in touch with us.