How to develop a Pagekit theme

In this tutorial you will learn how to develop your own theme from our Hello Theme blueprint. You will find out about the theme structure and follow the essential steps to add new positions and options.

Note The completed theme can be found on Github.

Getting started

For this tutorial, we assume you have a running Pagekit installation on a local server environment. If that is not the case, download a Pagekit installation package and quickly install it. Then login to the admin area and have a look at the Theme section of the integrated Marketplace.

The Pagekit Marketplace features our Hello Theme, a blueprint for developing themes, including code examples and a general foundation to help you get started.

Hello Theme unstyled Hello theme doesn't provide any styling, but gives you a good starting point to develop your own theme.First of all install the theme from the Pagekit marketplace and have a look at the overall file structure. When installed, the Hello theme is located in packages/pagekit/theme-hello. If you develop your own themes in the future, we recommend to install a simple theme on the side for reference, like the default Theme One. That way you can compare structural elements and get some inspiration. For this tutorial though, we only need Hello theme.

Structure

Let's take a look at some of the central files and folders that you will deal with when developing your own theme.

  1. /css
  2. theme.css // skeleton css with pre-defined classes
  3. /js
  4. theme.js // empty file for your scripts
  5. /views
  6. template.php // renders the theme's output; logo, menu, main content, sidebar and footer positions are available by default
  7. composer.json // package definition so that Pagekit recognizes the theme
  8. image.jpg // preview screenshot
  9. index.php // the central theme configuration
  10. CHANGELOG.md // changes of current and previous versions
  11. README.md // contains basic information

Rename your theme

If you simply change the files of Hello theme, any Marketplace updates to the theme will overwrite your changes. Also, you will probably want to give the theme your own custom name. If you decide to upload the theme to the marketplace, you even need to name it differently. This requires three simple steps:

  • Copy all files from packages/pagekit/theme-hello to packages/your-name/your-theme (you will need to create these folders).
  • Open composer.json and replace "name": "pagekit/theme-hello", with "name": "your-name/your-theme". ALso change "title": "Hello" to "title": "Your theme".
  • Open index.php and replace 'name' => 'theme-hello' with 'name' => 'your-theme'.Out of simplicity, the rest of the tutorial will still call it theme-hello in the examples.

CSS

Hello theme doesn't contain any styles. The included css/theme.css file lists all CSS classes that are rendered by the Pagekit core extensions without additional styling. You can add your own CSS to these classes.

The Pagekit admin interface and the Pagekit default themes are built with the UIkit front-end framework. So you might want to consider using it for your project as well. In the following example we will be building our theme using UIkit. If you use another framework or no framework at all, the basic approach of including CSS is similar.

There are many possible ways to set up your theme's file structure. We can recommend two approaches here. One is the "classic" way of including plain CSS. The second one is more complex to set up initially, but allows for far more flexibility.

The simple way: Include plain CSS files

Go to the UIkit website, download the latest release and unpack it. UIkit comes with three themes: Default, Gradient and Almost Flat. To include the Default theme, copy the css/uikit.min.css file from the UIkit package and paste it into the theme-hello/css/ folder of your theme.

To make sure the file is loaded by Pagekit, open the main layout file of the theme which is located at theme-hello/views/template.php.

In the <head> section of this layout file, we see that one CSS file is included already.

  1. <?php $view->style('theme', 'theme:css/theme.css') ?>

Now add another line to add the UIkit CSS. Make sure to add it above the line that is already present, so that it looks as follows.

  1. <?php $view->style('custom-uikit', 'theme:css/uikit.min.css') ?>
  2. <?php $view->style('theme', 'theme:css/theme.css') ?>

That's it, your theme now contains UIkit's CSS. To add your own CSS rules, simply edit theme-hello/css/theme.css.

Default UIkit styling Adding the CSS from UIkit will add default styling to the rendered markup. To actually make it pretty, we also need to add some classes to the markup, customize the default UIkit style and maybe add our own CSS styling

The advanced way: Setup with Gulp and LESS

In the previous section we have seen how easy it is to add plain CSS files to your theme. If you have experience with building websites, you are probably familiar with more flexible ways of styling your content. A good example for this is using a CSS pre-processor like LESS. This allows you to use things such as variables, which make your code easier to read and manage.

When using UIkit, this has the great advantage that you can simply modify a variable to apply global changes, for example altering the primary theme color.

To comfortably work with the LESS pre-processor, you should have a few tools installed and available on your command line: npm, gulp and bower. If you do not have them installed, do a quick Google search, there are plenty of tutorials available online.

There are a number of possible file structure setups. In the following passage we suggest the structure that is also used for the official Pagekit themes. While it might look like many steps when you create a theme for the first time, in our experience this setup allows for well structured code, easy UIkit customizations and a comfortable way to keep UIkit up to date using Bower.

Step 1

In your theme, create the new files package.json, bower.json, .bowerrc, gulpfile.js and less/theme.less. Paste the following contents into these files. If you have named your theme differently, replace any occurences of the string theme-hello with your theme name.

package.json. This file defines all JavaScript dependencies that are installed when running npm install and includes several npm packages that we will we use, for example when compiling LESS to CSS:

  1. {
  2. "name": "theme-hello",
  3. "devDependencies": {
  4. "bower": "*",
  5. "gulp": "*",
  6. "gulp-less": "*",
  7. "gulp-rename": "*"
  8. }
  9. }

bower.json tells bower to fetch the newest release of UIkit. That way you can always run bower install to fetch the current LESS source files from UIkit:

  1. {
  2. "name": "theme-hello",
  3. "dependencies": {
  4. "uikit": "*"
  5. },
  6. "private": true
  7. }

.bowerrc includes configuration settings for bower. By default, bower installs everything in a directory called /bower_components inside the theme directory. Simply out of preference, we change that default directory:

  1. {
  2. "directory": "app/assets"
  3. }

gulpfile.js contains all tasks which we can run using Gulp. We only need a task to compile LESS to CSS. For convenience we also add a watch task that can be run to automatically recompile LESS when any changes to the files have been detected:

  1. var gulp = require('gulp'),
  2. less = require('gulp-less'),
  3. rename = require('gulp-rename');
  4.  
  5. gulp.task('default', function () {
  6. return gulp.src('less/theme.less', {base: __dirname})
  7. .pipe(less({compress: true}))
  8. .pipe(rename(function (file) {
  9. // the compiled file should be stored in the css/ folder instead of the less/ folder
  10. file.dirname = file.dirname.replace('less', 'css');
  11. }))
  12. .pipe(gulp.dest(__dirname));
  13. });
  14.  
  15. gulp.task('watch', function () {
  16. gulp.watch('less/*.less', ['default']);
  17. });

less/theme.less is the place where you store your theme's styles. Mind that you first need to import UIkit so that it is also compiled by the Gulp task we have defined above.

  1. @import "uikit/uikit.less";

  2. // use icon font from system@icon-font-path: "../../../../app/assets/uikit/fonts";

  3. // your theme styles will follow here…

.gitignore is an optional file that is useful when you manage your code using Git. In Hello theme, a version of the file already exists. You can add new entries so that it looks as follows. You probably don't want to commit the downloaded packages by bower and the generated CSS. Just make sure to include the generated CSS when you upload the theme to your server or the Pagekit Marketplace.

  1. /app/bundle/*
  2. /app/assets/*
  3. /node_modules
  4. /css
  5. .DS_Store
  6. .idea
  7. *.zip

Step 2

After you have created the files above, go to the UIkit Github repository, download the Zip, unpack it and find the themes/default folder (or one of the other themes, if you like). Note that you need the Github version for this, not the css-only version we have downloaded in the simple setup.

Download UIkit from Github

Step 3

Create a /less folder inside your theme, copy and paste the default theme folder in there and rename it to /uikit, so that it is located at less/uikit inside your theme folder.

Step 4

The style we just copied needs to import the core UIkit LESS, so that it can be compiled successfully. To make this possible, you need to update the import path in your theme's less/uikit/uikit.less file. Make sure to change the import in line 4 to the following path: @import "../../app/assets/uikit/less/uikit.less";

Step 5

Open your theme in a new console tab (for example cd pagekit/packages/theme-hello) and run npm install, bower install and gulp.

Now, that were quite a few steps. Make sure your file structure looks as follows now (plus additional theme files that were there before):

  1. app/
  2. assets/
  3. jquery/ result of bower install
  4. uikit/ result of bower install
  5. css/
  6. theme.css result of gulp
  7. less/
  8. uikit/
  9. ... uikit components
  10. uikit.less
  11. theme.less
  12. node_modules/ result of npm install
  13. .bowerrc
  14. bower.json
  15. gulpfile.js
  16. package.json
  17. ... other theme files

With this file setup in place, we have now achieved the following:

  • Separation of theme styles and UIkit customizations. Add your own styles in less/theme.less, customize UIkit in less/uikit/*
  • Easily customize UIkit: Every UIkit component's settings are located in its own *.less file. For example, to change the body font size, open less/uikit/base.less and change the value of @base-body-font-size, then re-run gulp. To use any of the UIkit add-on components, open less/uikit.less and import the add-on's less file from the app/assets directory, for example for the slideshow, add the line: @import "../../app/assets/uikit/less/components/slideshow.less"; and re-run gulp.
  • Easily update UIkit: Run bower install to fetch the newest version of UIkit and run gulp to re-compile your LESS files to CSS.

Adding JavaScript

Included in Hello theme, you will find an empty JavaScript file js/script.js. Here, you can add your own JavaScript code. In Hello Theme, the file is loaded because it is already included in the template.php as follows:

  1. <?php $view->script('theme', 'theme:js/theme.js') ?>

When including a script, it needs a unique identifier (theme) and the path to the script file (theme:js/theme.js). As you can see, you can use theme: as a short version for the file path to your theme directory. To add more JavaScript files, simply add more lines in the same way. Make sure to assign different identifier to the scripts. If you would call all of them theme, only the last one will be included.

  1. <?php $view->script('theme', 'theme:js/theme.js') ?>
  2. <?php $view->script('plugins', 'theme:js/plugins.js') ?>
  3. <?php $view->script('slideshow', 'theme:js/slideshow.js') ?>

While adding multiple script() calls is the simplest way to include JavaScript, the script() helper allows for way more powerful ways to include JavaScript files which can also depend on each other. Let us look at that in more depth in the next sections.

Adding multiple JavaScript files with dependencies

In the earlier examples we have now worked with CSS from UIkit. If you also want to use UIkit's JavaScript components and utilities, it makes sense to add the UIkit JavaScript files. Note that UIkit requires loading jQuery before you can use the UIkit JavaScript components.

Locate the previous line script('theme', …) in the head of views/template.php and replace it with the following three lines.

  1. <?php $view->script('theme-jquery', 'theme:app/assets/jquery/dist/jquery.min.js') ?>
  2. <?php $view->script('theme-uikit', 'theme:app/assets/uikit/js/uikit.min.js', 'theme-jquery') ?>
  3. <?php $view->script('theme', 'theme:js/theme.js', 'theme-uikit') ?>

This example assumes you have used the advanced setup from above, where bower has installed both UIkit and jQuery into the app/assets folder. If you are working with a simpler setup instead, just download and copy jquery.min.js and uikit.min.js to some place in your theme and change the paths in the example accordingly.

Note how we now add a third parameter, which defines dependencies of the script that we are loading. Dependencies are other JavaScript files that have to be loaded earlier. So in the example, Pagekit will definitely make sure to load the three files in the following order: jQuery, UIkit and then our own theme.js. In this specific example, this mechanism does not seem very useful, because the scripts will probably be loaded in exactly the order that we put them in the template.php, right? That is correct, but imagine these lines being located in different files and sub-templates of your theme. Defining dependencies will make sure that Pagekit always loads the files in a correct order.

As you can already see in the example above, dependencies are referenced using the unique string identifier (e.g. theme-jquery). In our example, this identifier is given to the script the first time it is included using the script() method. So, as you have seen now, this method takes three parameters: $view->script($identifier, $path_to_script, $dependencies).

To confirm this has worked, open views/template.php and add the following lines (data-uk-* is the prefix for UIkit's JavaScript components).

  1. <!-- ADD id="up" to body -->
  2. <body id="up">
  3. <!-- LEAVE existing content ... -->
  4. ...
  5. <!-- ADD to-top-scroller -->
  6. <div class="uk-text-center">
  7. <a href="#up" data-uk-smooth-scroll=""><i class="uk-icon-caret-up"></i></a>
  8. </div>
  9. <!-- LEAVE rendering of footer section -->
  10. <?= $view->render('footer') ?>
  11. </body>

When you refresh the browser, you will see a small arrow that you can use to smoothly scroll to the top of the browser window. If the browser does not scroll smoothly, but jumps immediately, please check if you have written everything exactly as in the examples.

Adding third party scripts, like jQuery

You may ask yourself why we called the included jQuery script theme-jquery and not simply jquery. In general, it is always useful to prefix your own identifiers, to avoid collisions with other extensions. However, in this specific example, the identifiers jquery and uikit are already taken, because Pagekit itself includes jQuery and UIkit. This means that you can already load these JavaScript files without including them in your theme. That way, all themes and extensions can share a single version of jQuery (and UIkit, if they use UIkit) to avoid conflicts.

  1. <?php $view->script('theme', 'theme:js/theme.js', ['uikit', 'jquery']) ?>

As you can see in the example, the third parameter of the script() method can also take a list of multiple dependencies. In the earlier example we have only passed in a single string (for example theme-jquery). Pass in a string for a single dependency, or a list for multiple depencies — both are possible.

The currently loaded version of jQuery and UIkit depend on the current version of Pagekit. With new releases of Pagekit, the versions of these libraries will continually be updated. While this allows for always having a current version available, a potential downside would be that you need to make sure your code also works for the new versions of these libraries.

Layout

The central files for your theme's layout are views/template.php and index.php. The actual rendering happens in the template.php.

When you open the template.php, you see a very basic setup for you to get started. Let's wrapping a container around our main content and divide the system output and sidebar into a grid.

Around line 30 the views/template.php file renders the sidebar position and the actual content.

  1. <!-- Render widget position -->
  2. <?php if ($view->position()->exists('sidebar')) : ?>
  3. <?= $view->position('sidebar') ?>
  4. <?php endif; ?>
  5. <!-- Render content -->
  6. <?= $view->render('content') ?>

Using UIkit's Block and Utility components, we will create a position block and a container with a fluid width.

It is always a good idea to prefix your own classes, so they will not collide with other CSS you might be using. For example, all UIkit classes are prefixed uk-. To distinguish classes or IDs that come from this theme, we will use the prefix tm-. Consequently, we add the class and ID tm-main to identify the section.

  1. <div id="tm-main" class="tm-main uk-block">
  2. <div class="uk-container uk-container-center">
  3. <!-- Render widget position -->
  4. <?php if ($view->position()->exists('sidebar')) : ?>
  5. <?= $view->position('sidebar') ?>
  6. <?php endif; ?>
  7. <!-- Render content -->
  8. <?= $view->render('content') ?>
  9. </div>
  10. </div>

Now we want the system output and sidebar to actually be side by side. The Grid component can help us here. For a more semantic layout, we will use <main> and <aside> elements for the containers.

  1. <div id="tm-main" class="tm-main uk-block">
  2. <div class="uk-container uk-container-center">
  3. <div class="uk-grid" data-uk-grid-match data-uk-grid-margin>
  4. <main class="<?= $view->position()->exists('sidebar') ? 'uk-width-medium-3-4' : 'uk-width-1-1'; ?>">
  5. <!-- Render content -->
  6. <?= $view->render('content') ?>
  7. </main>
  8. <?php if ($view->position()->exists('sidebar')) : ?>
  9. <aside class="uk-width-medium-1-4">
  10. <?= $view->position('sidebar') ?>
  11. </aside>
  12. <?php endif; ?>
  13. </div>
  14. </div>
  15. </div>

Theme elements

To create more complex layouts, you can add your own widget positions, menus and options for both. A regular theme basically consists of widgets, menus and the page content itself.

The page content is nothing other than Pagekit's system output. That means that the content of any page you create will be rendered in this area.

Widgets are small chunks of content that you can render in different positions of your site, so that they will be displayed in specific locations of your site's markup.

To navigate through any site, you first need to set up a menu. For this purpose, Pagekit provides different menu positions that allow users to publish menus in several locations of the theme markup.

Typical theme elements A typical site layout consists of the main navigation, the page content and several widget positionsHowever, your theme needs to register all positions before. This happens in the index.php file through the menus and positions properties. These contain arrays of the position name and a label, which is displayed in the admin panel. This file is also used to load additional scripts and much more.

Navbar

One of the first things you will want to render in your theme is the main navigation.

Main navigation unstyled By default, Hello theme renders menu items in a very simple vertical navigation.

Step 1

Hello theme comes with the predefined Main menu position. When adding a new position it needs to be defined by an identifier (i.e. main) and a label to be displayed to the user (i.e. Main).

  1. 'menu' => [
  2. 'main' => 'Main',
  3. ]

Menu position in Site Tree A menu can be published to the defined positions in the Pagekit Site Tree.

Step 2

With the concept of modularity in mind, Pagekit renders position layouts in separate files. For the navigation, create the views/menu-navbar.php file containing the following:

  1. <?php if ($root->getDepth() === 0) : ?>
  2. <ul class="uk-navbar-nav">
  3. <?php endif ?>
  4. <?php foreach ($root->getChildren() as $node) : ?>
  5. <li class="<?= $node->hasChildren() ? 'uk-parent' : '' ?><?= $node->get('active') ? ' uk-active' : '' ?>" <?= ($root->getDepth() === 0 && $node->hasChildren()) ? 'data-uk-dropdown':'' ?>>
  6. <a href="<?= $node->getUrl() ?>"><?= $node->title ?></a>
  7. <?php if ($node->hasChildren()) : ?>
  8. <?php if ($root->getDepth() === 0) : ?>
  9. <div class="uk-dropdown uk-dropdown-navbar">
  10. <?php endif ?>
  11. <?php if ($root->getDepth() === 0) : ?>
  12. <ul class="uk-nav uk-nav-navbar">
  13. <?php elseif ($root->getDepth() === 1) : ?>
  14. <ul class="uk-nav-sub">
  15. <?php else : ?>
  16. <ul>
  17. <?php endif ?>
  18. <?= $view->render('menu-navbar.php', ['root' => $node]) ?>
  19. </ul>
  20. <?php if ($root->getDepth() === 0) : ?>
  21. </div>
  22. <?php endif ?>
  23. <?php endif ?>
  24. </li>
  25. <?php endforeach ?>
  26. <?php if ($root->getDepth() === 0) : ?>
  27. </ul>
  28. <?php endif ?>

Step 3

To render the actual navbar in the template.php file, create a <nav> element and add the .uk-navbar class. Load the menu-navbar.php file inside the element as follows (you can remove the existing block where $view->menu('main') is rendered).

  1. <nav class="uk-navbar">
  2. <?php if ($view->menu()->exists('main')) : ?>
  3. <div class="uk-navbar-flip">
  4. <?= $view->menu('main', 'menu-navbar.php') ?>
  5. </div>
  6. <?php endif ?>
  7. </nav>

The main menu should now automatically be rendered in the new Navbar position.

Step 4

You will probably also want the logo to appear inside the navbar. So wrap the <nav> element around the logo as well and add the .uk-navbar-brand class, to apply the appropriate spacing.

  1. <nav class="uk-navbar">
  2. <!-- Render logo or title with site URL -->
  3. <a class="uk-navbar-brand" href="<?= $view->url()->get() ?>">
  4. <?php if ($logo = $params['logo']) : ?>
  5. <img src="<?= $this->escape($logo) ?>" alt="">
  6. <?php else : ?>
  7. <?= $params['title'] ?>
  8. <?php endif ?>
  9. </a>
  10. <?php if ($view->menu()->exists('main')) : ?>
  11. <div class="uk-navbar-flip">
  12. <?= $view->menu('main', 'menu-navbar.php') ?>
  13. </div>
  14. <?php endif ?>
  15. </nav>

Horizontal navbar With our changes, menu items are now rendered in a horizontal navbar.

Adding theme options

Pagekit uses Vue.js to build its administration interface. Here is a Video tutorial on Vue.js in Pagekit.

A frequently requested feature is for the navbar to remain fixed at the top of the browser window when scrolling down the site. In the following steps, we are going to add this as an option to our theme.

Step 1

First, we need to create the folder app/components and in it the file site-theme.vue. Settings stored in this file affect the entire website and can be found under Theme in the Settings tab of the Site Tree. They cannot be applied to a specific page.

Site Tree You can add any kind of Settings screen to the admin area.In the freshly created file site-theme.vue we add the option which will be displayed in the Pagekit administration.

  1. <template>
  2. <div class="uk-margin uk-flex uk-flex-space-between uk-flex-wrap" data-uk-margin>
  3. <div data-uk-margin>
  4. <h2 class="uk-margin-remove">{{ 'Theme' | trans }}</h2>
  5. </div>
  6. <div data-uk-margin>
  7. <button class="uk-button uk-button-primary" type="submit">{{ 'Save' | trans }}</button>
  8. </div>
  9. </div>
  10. <div class="uk-form uk-form-horizontal">
  11. <div class="uk-form-row">
  12. <label for="form-navbar-mode" class="uk-form-label">{{ 'Navbar Mode' | trans }}</label>
  13. <p class="uk-form-controls-condensed">
  14. <label><input type="checkbox" v-model="config.navbar_sticky"> {{ 'Sticky Navigation' | trans }}</label>
  15. </p>
  16. </div>
  17. </div>
  18. </template>

Step 2

Now we still have to make this option available in the Site Tree. To do so, we can create a Theme tab in the interface by adding the following script below the option in the site-theme.vue file.

  1. <script>
  2. module.exports = {
  3. section: {
  4. label: 'Theme',
  5. icon: 'pk-icon-large-brush',
  6. priority: 15
  7. },
  8. data: function () {
  9. return _.extend({config: {}}, window.$theme);
  10. },
  11. events: {
  12. save: function() {
  13. this.$http.post('admin/system/settings/config', {name: this.name, config: this.config}).catch(function (res) {
  14. this.$notify(res.data, 'danger');
  15. });
  16. }
  17. }
  18. };
  19. window.Site.components['site-theme'] = module.exports;
  20. </script>

Step 3

To load the script and add the option to the Site Tree, you also need to add the following to the index.php file.

  1. 'events' => [
  2. 'view.system/site/admin/settings' => function ($event, $view) use ($app) {
  3. $view->script('site-theme', 'theme:app/bundle/site-theme.js', 'site-settings');
  4. $view->data('$theme', $this);
  5. }
  6. ]

Step 4

Add the default setting for the navbar mode in the index.php file.

  1. 'config' => [
  2. 'navbar_sticky' => false
  3. ],

Step 5

Vue components need to be compiled into JavaScript using Webpack. To do so, create the file webpack.config.js in your theme folder:

  1. module.exports = [
  2. {
  3. entry: {
  4. "site-theme": "./app/components/site-theme.vue"
  5. },
  6. output: {
  7. filename: "./app/bundle/[name].js"
  8. },
  9. module: {
  10. loaders: [
  11. { test: /\.vue$/, loader: "vue" }
  12. ]
  13. }
  14. }
  15. ];

Step 6

After that, run the command webpack inside the theme folder and site-theme.vue will be compiled into /bundle/site-theme.js with the template markup converted to an inline string.

If you haven't worked with it before, you will quickly need to install webpack globally and then also run the following in your project directory to have a local webpack version and a Vue compiler available.

  1. npm install webpack vue-loader vue-html-loader babel-core babel-loader babel-preset-es2015 babel-plugin-transform-runtime --save-dev

Whenever you now apply changes to the Vue component, you need to run this task again. Alternatively, you can run webpack —watch or webpack -w which will stay active and automatically recompile whenever you change the Vue component. You can quit this command with the shortcut Ctrl + C For more information on Vue and Webpack, take a closer look at this doc.

Step 7

Lastly, we want to load the necessary JavaScript dependencies in the head of our views/template.php file. In our case we are using the Sticky component from UIkit. Since it is not included in the framework core, it needs to be loaded explicitely. YOu can do that by adding a dependency to the sticky component when loading the theme's JavaScript.

  1. <?php $view->script('theme', 'theme:js/theme.js', ['uikit-sticky']) ?>

Now all you need to do is render the option into the actual navbar in template.php.

  1. <nav class="uk-navbar uk-position-z-index" <?= $params['navbar_sticky'] ? ' data-uk-sticky' : '' ?>>

In the admin area, go to Site > Settings > Theme and enable the Sticky Navigation option to see it take effect on your website.

Widgets

Widget positions allow users to publish widgets in several locations of your theme markup. They appear in the Widgets area of the Pagekit admin panel and can be selected by the user when setting up a widget.

Step 1

To render a new widget position, you first need to register it with the index.php file. For example, if we want a create a new Top position, we will define it through the positions property.

  1. 'positions' => [
  2. 'sidebar' => 'Sidebar',
  3. 'top' => 'Top'
  4. ],

Step 2

Now that we have made the new position known to Pagekit, we need also to create a position renderer. We could skip this step and use a default renderer, but then all widgets would simply render below each pther. So to lay out the widgets in a grid, create the file views/position-grid.php:

  1. <?php foreach ($widgets as $widget) : ?>
  2. <div class="uk-width-medium-1-<?= count($widgets) ?>">
  3. <div class="uk-panel">
  4. <h3 class="uk-panel-title"><?= $widget->title ?></h3>
  5. <?= $widget->get('result') ?>
  6. </div>
  7. </div>
  8. <?php endforeach ?>

Step 3

To render the new position in the theme's markup, we need to add it to the views/template.php file, after the closing </nav> tag:

  1. <?php if ($view->position()->exists('top')) : ?>
  2. <div id="top" class="tm-top">
  3. <div class="uk-container uk-container-center">
  4. <section class="uk-grid uk-grid-match" data-uk-grid-margin>
  5. <?= $view->position('top', 'position-grid.php') ?>
  6. </section>
  7. </div>
  8. </div>
  9. <?php endif ?>

Now can select Top for any widget that you want to render in the newly created position.

Widget position Select the new position when creating a widget.

Adding position options

The example before added configuration options which applied for the whole site. We can also extend the Site Tree with configuration that only applies for a certain page. Let us add the option to apply a different background color to our new Top position.

Step 1

First, we need to create the file node-theme.vue inside the folder app/components. Here we add the option which will be displayed in the Pagekit administration. Settings that are stored in this file can be applied to each page separately by entering the appropriate item in the site tree and clicking the Theme tab.

  1. <template>
  2. <div class="uk-form-horizontal">
  3. <div class="uk-form-row">
  4. <label for="form-top-style" class="uk-form-label">Top {{ 'Position' | trans }}</label>
  5. <div class="uk-form-controls">
  6. <select id="form-top-style" class="uk-form-width-large" v-model="node.theme.top_style">
  7. <option value="uk-block-default">{{ 'Default' | trans }}</option>
  8. <option value="uk-block-muted">{{ 'Muted' | trans }}</option>
  9. </select>
  10. </div>
  11. </div>
  12. </div>
  13. </template>

Step 2

Now we still have to make this option available in the Site Tree. To do so, we can create a Theme tab in the interface by adding the following to the node-theme.vue file.

  1. <script>
  2. module.exports = {
  3. section: {
  4. label: 'Theme',
  5. priority: 90
  6. },
  7. props: ['node']
  8. };
  9. window.Site.components['node-theme'] = module.exports;
  10. </script>

Step 3

In the chapter about theme options, we inserted the event listener to the index.php file in order to load the script and add the option to the Site Tree. Now we need to do the same thing for the site setting. The complete events section should then look like this.

  1. 'events' => [
  2. 'view.system/site/admin/settings' => function ($event, $view) use ($app) {
  3. $view->script('site-theme', 'theme:app/bundle/site-theme.js', 'site-settings');
  4. $view->data('$theme', $this);
  5. },
  6. 'view.system/site/admin/edit' => function ($event, $view) {
  7. $view->script('node-theme', 'theme:app/bundle/node-theme.js', 'site-edit');
  8. }
  9. ]

Step 4

The default setting for the widget position also needs to be added in the index.php.

  1. 'node' => [
  2. 'top_style' => 'uk-block-muted'
  3. ],

Step 5

In the chapter about theme options we created the file webpack.config.js. Our node-theme.vue file also needs to be registered to be compiled into JavaScript.

  1. entry: {
  2. "node-theme": "./app/components/node-theme.vue",
  3. "site-theme": "./app/components/site-theme.vue"
  4. },

Step 6

Now you can run the command webpack inside the theme folder and node-theme.vue will be compiled into app/bundle/node-theme.js.

Step 7

Lastly, to actually render the chosen setting into the widget position, we need to add the .uk-block class and the style parameter to the position itself in the template.php file.

  1. <div id="top" class="tm-top uk-block <?= $params['top_style'] ?>">

Step 8

In the site tree, you now see a Theme tab when editing a page. Here you can configure the new option. This configuration only applies for this specific page.

Node options added by the theme Node options added by the theme.

Adding widget options

You can also add specific options to widgets themselves. In this case, we would like to provide a panel style option that can be selected for each widget.

Widget options A theme can add any kind of options to the Widget editor.

Step 1

First, we need to create a app/components/widget-theme.vue file inside the folder app/components. This renders the select box from which we will be able to choose the widget's style.

  1. <template>
  2. <div class="uk-form-horizontal">
  3. <div class="uk-form-row">
  4. <label for="form-theme-panel" class="uk-form-label">{{ 'Panel Style' | trans }}</label>
  5. <div class="uk-form-controls">
  6. <select id="form-theme-panel" class="uk-form-width-large" v-model="widget.theme.panel">
  7. <option value="">{{ 'None' | trans }}</option>
  8. <option value="uk-panel-box">{{ 'Box' | trans }}</option>
  9. <option value="uk-panel-box uk-panel-box-primary">{{ 'Box Primary' | trans }}</option>
  10. <option value="uk-panel-box uk-panel-box-secondary">{{ 'Box Secondary' | trans }}</option>
  11. <option value="uk-panel-header">{{ 'Header' | trans }}</option>
  12. </select>
  13. </div>
  14. </div>
  15. </div>
  16. </template>

Step 2

Now we still have to make this option available in the widget administration. To do so, we can create a Theme tab in the interface by adding the following to the widget-theme.vue file.

  1. <script>
  2. module.exports = {
  3. section: {
  4. label: 'Theme',
  5. priority: 90
  6. },
  7. props: ['widget', 'config']
  8. };
  9. window.Widgets.components['theme'] = module.exports;
  10. </script>

Step 3

To make the option available in the widget administration, we can create a Theme tab to the interface by adding the following to the events section in the file index.php.

  1. 'view.system/widget/edit' => function ($event, $view) {
  2. $view->script('widget-theme', 'theme:app/bundle/widget-theme.js', 'widget-edit');
  3. }

Step 4

The default setting for the widget also needs to be added in the index.php.

  1. 'widget' => [
  2. 'panel' => ''
  3. ],

Step 5

Add the widget-theme entry to the webpack.config.json file, so it will be compiled to JavaScript (don't forget to run webpack in the theme folder afterwards).

  1. entry: {
  2. "node-theme": "./app/components/node-theme.vue",
  3. "site-theme": "./app/components/site-theme.vue",
  4. "widget-theme": "./app/components/widget-theme.vue"
  5. },

Step 6

Now all we need to do is render the chosen setting into the widget in the position-grid.php file.

  1. <div class="uk-panel <?= $widget->theme['panel'] ?>">

Step 7

When editing a widget, you will now see a Theme tab on top of the editor, where you can access the Panel Style configuration that we have just added.

Overwrite system views

A theme can also customize the way how Pagekit renders static pages and blog posts. You simply overwrite system view files to apply your own layout. To do so, create corresponding folders inside your theme to mimic the original structure of the Pagekit system and put the template files there:

FileOriginal view fileDescription
views/system/site/page.php/app/system/site/views/page.phpDefault static page view
views/blog/post.php/packages/pagekit/blog/views/post.phpBlog post single view
views/blog/posts.php/packages/pagekit/blog/views/posts.phpBlog posts list view

To understand which variables are available in these views, look at the markup in the original view file.

Wrapping up

In this tutorial, you have learned the basic knowledge and tools to create themes for Pagekit. Let us summarize which topics we have covered.

Note The completed theme can be found on Github.

  • You are now familiar with the file structure of a Pagekit theme. The main entry point for configuration and custom code is the theme's index.php, the main template file is located at views/template.php inside the theme.
  • We have presented an approach for a modern front-end workflow with tools such as LESS, Gulp and Bower. You can customize this setup to your own preferences.
  • To add JavaScript to your theme, you can add your own code and include the script files in your template. You can also add third party libraries like UIkit and jQuery that help you to add interaction to your website.
  • Widgets and menus are managed from the Pagekit admin area and are rendered in special positions of your theme. You have learned how to create these positions and how to change the default widget rendering.
  • To make sure your theme is customizable from the admin area, you can add your own settings screens. These can be added to the Site Tree, to the Site settings and to the Widget editor.
  • To overwrite system views, your theme can include custom view files to alter the way static pages and blog posts are rendered.With these skills you are now in a position to create Pagekit themes, both for client projects and also for the Pagekit marketplace.