Theme Package III: Customizations

In the previous sections we prepared our setup and created our custom theme, adjusted the template and modified and added some Diazo rules.

We will add some final customizations to the theme using CSS and define theme attributes.

Understanding And Using The Grunt Build System

Before we begin customizing the CSS for our theme we have to take a look at the Grunt Task Runner. We already have a Gruntfile.js - generated by bobtemplates.plone - in the top level directory of our theme package.

For our example we have to adjust the configuration for the browserSync:html and browserSync:plone tasks.

This allows us to get automatic reloads in the browser when we work on the HTML template (with or without Plone) and change any of the configured files (more on that later in this chapter).

module.exports = function (grunt) {
    'use strict';
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        // we could just concatenate everything, really
        // but we like to have it the complex way.
        // also, in this way we do not have to worry
        // about putting files in the correct order
        // (the dependency tree is walked by r.js)
        less: {
            dist: {
                options: {
                    paths: [],
                    strictMath: false,
                    sourceMap: true,
                    outputSourceFiles: true,
                    sourceMapFileInline: true,
                    sourceMapURL: '++theme++ploneconf-theme/less/theme-compiled.less.map',
                    sourceMapFilename: 'less/theme-compiled.less.map',
                    modifyVars: {
                        "isPlone": "false"
                    }
                },
                files: {
                    'less/theme-compiled.css': 'less/theme.local.less',
                }
            }
        },
        postcss: {
            options: {
                map: true,
                processors: [
                    require('autoprefixer')({
                        browsers: ['last 2 versions']
                    })
                ]
            },
            dist: {
                src: 'less/*.css'
            }
        },
        watch: {
            scripts: {
                files: [
                    'less/*.less',
                    'barceloneta/less/*.less'
                ],
                tasks: ['less', 'postcss']
            }
        },
        browserSync: {
            html: {
                bsFiles: {
                    src : [
                      'css/*.css',
                      'js/*.js',
                      '*.html'
                    ]
                },
                options: {
                    watchTask: true,
                    debugInfo: true,
                    online: true,
                    server: {
                        baseDir: "."
                    },
                }
            },
            plone: {
                bsFiles: {
                    src : [
                      'less/*.less',
                      'barceloneta/less/*.less',
                      '*.html',
                      '*.xml'
                    ]
                },
                options: {
                    watchTask: true,
                    debugInfo: true,
                    proxy: "localhost:8080",
                    reloadDelay: 3000,
                    // reloadDebounce: 2000,
                    online: true
                }
            }
        }
    });


    // grunt.loadTasks('tasks');
    grunt.loadNpmTasks('grunt-browser-sync');
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-contrib-less');
    grunt.loadNpmTasks('grunt-postcss');

    // CWD to theme folder
    grunt.file.setBase('./src/ploneconf/theme/theme');

    grunt.registerTask('compile', ['less', 'postcss']);
    grunt.registerTask('default', ['compile']);
    grunt.registerTask('bsync', ["browserSync:html", "watch"]);
    grunt.registerTask('plone-bsync', ["browserSync:plone", "watch"]);
};

At the end of the file we can see some registered Grunt tasks. We can use these tasks to control what happens when we run the grunt command.

By default, grunt will run the compile task, which compiles the configured Less files into CSS and afterwards transforms the generated CSS with postcss:

$ grunt
Running "less:dist" (less) task
>> 1 stylesheet created.

Running "postcss:dist" (postcss) task
>> 1 processed stylesheet created.

Done, without errors.

We can automate this task by running grunt watch, which will check the configured Less files for changes. If you change on of the Less files, you will see the output on the command line:

$ grunt watch
Running "watch" task
Waiting...
>> File "less/custom.less" changed.
Running "less:dist" (less) task
>> 1 stylesheet created.

Running "postcss:dist" (postcss) task
>> 1 processed stylesheet created.

Done, without errors.

There are also two browserSync tasks pre-configured. The first one, bsync will watch for changes on the template files and reload the browser window for you automatically. This is especially useful when you want to make bigger changes on the template itself, without the Plone related Diazo rules.

$ grunt bsync
Running "browserSync:html" (browserSync) task
[Browsersync] Access URLs:
 ---------------------------------------
       Local: http://localhost:3000
    External: http://192.168.178.30:3000
 ---------------------------------------
          UI: http://localhost:3001
 UI External: http://192.168.178.30:3001
 ---------------------------------------
[Browsersync] Serving files from: .
[Browsersync] Watching files...

Running "watch" task
Waiting...
[Browsersync] Reloading Browsers...

The next task, plone-bsync, will connect to your development Plone instance and open another browser window for you. If you now change one of the configured theme files in the less or barceloneta folder, the files will be compiled to CSS and your browser window will reload.

A change of the HTML template file index.html or Diazo rules file rules.xml will reload your browser as well:

$ grunt plone-bsync
Running "browserSync:plone" (browserSync) task
[Browsersync] Proxying: http://localhost:8080
[Browsersync] Access URLs:
 ---------------------------------------
       Local: http://localhost:3000
    External: http://192.168.178.30:3000
 ---------------------------------------
          UI: http://localhost:3001
 UI External: http://192.168.178.30:3001
 ---------------------------------------
[Browsersync] Watching files...

Running "watch" task
Waiting...
[Browsersync] Reloading Browsers...
>> File "less/custom.less" changed.
Running "less:dist" (less) task
>> 1 stylesheet created.

Running "postcss:dist" (postcss) task
>> 1 processed stylesheet created.

Done, without errors.
Completed in 2.149s at Tue Sep 26 2017 12:56:21 GMT+0200 (CEST) - Waiting...
[Browsersync] Reloading Browsers...

Note

Don’t forget to start your Plone instance.

Note

If you use other ports or IP addresses for your Plone backend, you have to adjust the proxy settings in the Gruntfile.js to match your Plone configuration.

Theme manifest.cfg

Settings for our theme are declared in the file manifest.cfg. It contains settings for CSS files to use for development and production, a CSS file for the content editor TinyMCE and several other optional settings.

The one we get from bobtemplates.plone looks like this:

[theme]
title = Plone Theme: Ploneconf theme
description = A Diazo based Plone theme
doctype = <!DOCTYPE html>
rules = /++theme++ploneconf-theme/rules.xml
prefix = /++theme++ploneconf-theme
enabled-bundles =
disabled-bundles =

development-css = /++theme++ploneconf-theme/less/theme.less
production-css = /++theme++ploneconf-theme/less/theme-compiled.css
tinymce-content-css = /++theme++ploneconf-theme/less/theme-compiled.css

# development-js = /++theme++ploneconf-theme/js/theme.js
# production-js = /++theme++ploneconf-theme/js/theme-compiled.js

[theme:overrides]
directory = template-overrides

[theme:parameters]
# portal_url = python: portal.absolute_url()

The development-css file is used when Plone is running in development mode, otherwise the file defined in production-css will be used. The file tinymce-content-css tells Plone to load that particular CSS file inside TinyMCE, whenever a TinyMCE rich text field is displayed.

Hint

After making changes to the file manifest.cfg, we need to deactivate/activate the theme for them to take effect.

Note

You can read more about the manifest.cfg and the available options in the plone.app.theming documentation.

Final CSS Customization

Our example theme already looks pretty good. But with the help of some CSS we can give it the final touch.

We will re-use the definition of the box class from the file theme/css/business-casual.css for portlets in the left and right portlet column. Replace the example content with the following Less code in the file custom.less:

/* Custom Less file that is included from the theme.less file. */

.brand-name {
  margin-top: 0.5em;
}

.documentDescription{
  margin-top: 1em;
}

.clearFix{
  clear: both;
}

#left-sidebar {
  padding-left: 0;
}

#right-sidebar {
  padding-right: 0;
}

#content {
  label,
  .label {
    color: #333;
    font-size: 100%;
  }
}

.pat-autotoc.autotabs,
.autotabs {
  border-width: 0;
}

#portal-column-one .portlet,
#portal-column-two .portlet {
  .box;
}

footer {
  margin-top: 1em;

  .portlet {
    padding: 1em 0;
    margin-bottom: 0;
    border: 0;
    background: transparent;
    box-shadow: none;

    p {
      padding: 1em 0;
    }

    .portletContent {
      border: 0;
      background: transparent;

      ul {
        padding-left: 0;
        list-style-type: none;

        .portletItem {
          display: inline-block;

          &:not(:last-child) {
            padding-right: 0.5em;
            margin-right: 0.5em;
            border-right: 1px solid;
          }

          &:hover {
            background-color: transparent;
          }

          a {
            color: #000;
            padding: 0;
            text-decoration: none;

            &:hover {
              background-color: transparent;
            }

            &::before {
              content: none;
            }
          }
        }
      }
    }
  }
}

body.mce-content-body {
  background-image: none;
}

Install External CSS And JavaScript Libraries With npm And Use Them In Your Theme

As our theme is based on Bootstrap, we want to install Bootstrap with npm to have more flexibility, for example to use the Less file of Bootstrap.

To do that, we use npm, which we already installed in the preparations.

Note

The following steps are already included in the bobtemplates.plone template We show them here for documentation reasons, so you see how you can install and use external packages like Bootstrap.

To install Bootstrap with npm, run the following command inside the theme folder:

cd src/ploneconf/theme/theme
npm install bootstrap --save

The --save option will add the bootstrap package to the file package.json in the theme folder for us. Now we can install all dependencies on any other system by running the following command from inside of our theme folder:

npm install

Now that we have installed bootstrap using npm we have all the bootstrap components available in the subfolder called node_modules:

tree node_modules/bootstrap/
node_modules/bootstrap/
├── CHANGELOG.md
├── Gruntfile.js
├── LICENSE
├── README.md
├── dist
│   ├── css
│   │   ├── bootstrap-theme.css
│   │   ├── bootstrap-theme.css.map
│   │   ├── bootstrap-theme.min.css
│   │   ├── bootstrap-theme.min.css.map
│   │   ├── bootstrap.css
│   │   ├── bootstrap.css.map
│   │   ├── bootstrap.min.css
│   │   └── bootstrap.min.css.map
│   ├── fonts
│   │   ├── glyphicons-halflings-regular.eot
│   │   ├── glyphicons-halflings-regular.svg
│   │   ├── glyphicons-halflings-regular.ttf
│   │   ├── glyphicons-halflings-regular.woff
│   │   └── glyphicons-halflings-regular.woff2
│   └── js
│       ├── bootstrap.js
│       ├── bootstrap.min.js
│       └── npm.js
├── fonts
│   ├── glyphicons-halflings-regular.eot
│   ├── glyphicons-halflings-regular.svg
│   ├── glyphicons-halflings-regular.ttf
│   ├── glyphicons-halflings-regular.woff
│   └── glyphicons-halflings-regular.woff2
├── grunt
│   ├── bs-commonjs-generator.js
│   ├── bs-glyphicons-data-generator.js
│   ├── bs-lessdoc-parser.js
│   ├── bs-raw-files-generator.js
│   ├── change-version.js
│   ├── configBridge.json
│   ├── npm-shrinkwrap.json
│   └── sauce_browsers.yml
├── js
│   ├── affix.js
│   ├── alert.js
│   ├── button.js
│   ├── carousel.js
│   ├── collapse.js
│   ├── dropdown.js
│   ├── modal.js
│   ├── popover.js
│   ├── scrollspy.js
│   ├── tab.js
│   ├── tooltip.js
│   └── transition.js
├── less
│   ├── alerts.less
│   ├── badges.less
│   ├── bootstrap.less
│   ├── breadcrumbs.less
│   ├── button-groups.less
│   ├── buttons.less
│   ├── carousel.less
│   ├── close.less
│   ├── code.less
│   ├── component-animations.less
│   ├── dropdowns.less
│   ├── forms.less
│   ├── glyphicons.less
│   ├── grid.less
│   ├── input-groups.less
│   ├── jumbotron.less
│   ├── labels.less
│   ├── list-group.less
│   ├── media.less
│   ├── mixins
│   │   ├── alerts.less
│   │   ├── background-variant.less
│   │   ├── border-radius.less
│   │   ├── buttons.less
│   │   ├── center-block.less
│   │   ├── clearfix.less
│   │   ├── forms.less
│   │   ├── gradients.less
│   │   ├── grid-framework.less
│   │   ├── grid.less
│   │   ├── hide-text.less
│   │   ├── image.less
│   │   ├── labels.less
│   │   ├── list-group.less
│   │   ├── nav-divider.less
│   │   ├── nav-vertical-align.less
│   │   ├── opacity.less
│   │   ├── pagination.less
│   │   ├── panels.less
│   │   ├── progress-bar.less
│   │   ├── reset-filter.less
│   │   ├── reset-text.less
│   │   ├── resize.less
│   │   ├── responsive-visibility.less
│   │   ├── size.less
│   │   ├── tab-focus.less
│   │   ├── table-row.less
│   │   ├── text-emphasis.less
│   │   ├── text-overflow.less
│   │   └── vendor-prefixes.less
│   ├── mixins.less
│   ├── modals.less
│   ├── navbar.less
│   ├── navs.less
│   ├── normalize.less
│   ├── pager.less
│   ├── pagination.less
│   ├── panels.less
│   ├── popovers.less
│   ├── print.less
│   ├── progress-bars.less
│   ├── responsive-embed.less
│   ├── responsive-utilities.less
│   ├── scaffolding.less
│   ├── tables.less
│   ├── theme.less
│   ├── thumbnails.less
│   ├── tooltip.less
│   ├── type.less
│   ├── utilities.less
│   ├── variables.less
│   └── wells.less
└── package.json

9 directories, 117 files

We will include the “carousel” part and some other bootstrap components which our downloaded theme uses. To do this, we include the required bootstrap components in our theme.less file (they were already added from bobtemplates.plone):

// theme.less file that will be compiled

/* ### PLONE IMPORTS ### */

@barceloneta_path: "barceloneta/less";

// Core variables and mixins
@import "@{barceloneta_path}/fonts.plone.less";
@import "@{barceloneta_path}/variables.plone.less";
@import "@{barceloneta_path}/mixin.prefixes.plone.less";
@import "@{barceloneta_path}/mixin.tabfocus.plone.less";
@import "@{barceloneta_path}/mixin.images.plone.less";
@import "@{barceloneta_path}/mixin.forms.plone.less";
@import "@{barceloneta_path}/mixin.borderradius.plone.less";
@import "@{barceloneta_path}/mixin.buttons.plone.less";
@import "@{barceloneta_path}/mixin.clearfix.plone.less";
// @import "@{barceloneta_path}/mixin.gridframework.plone.less"; //grid Bootstrap
@import "@{barceloneta_path}/mixin.grid.plone.less"; //grid Bootstrap

@import "@{barceloneta_path}/normalize.plone.less";
@import "@{barceloneta_path}/print.plone.less";
@import "@{barceloneta_path}/code.plone.less";

// Core CSS
@import "@{barceloneta_path}/grid.plone.less";
@import "@{barceloneta_path}/scaffolding.plone.less";
@import "@{barceloneta_path}/type.plone.less";
@import "@{barceloneta_path}/tables.plone.less";
@import "@{barceloneta_path}/forms.plone.less";
@import "@{barceloneta_path}/buttons.plone.less";
@import "@{barceloneta_path}/states.plone.less";

// Components
@import "@{barceloneta_path}/breadcrumbs.plone.less";
@import "@{barceloneta_path}/pagination.plone.less";
@import "@{barceloneta_path}/formtabbing.plone.less"; //pattern
@import "@{barceloneta_path}/views.plone.less";
@import "@{barceloneta_path}/thumbs.plone.less";
@import "@{barceloneta_path}/alerts.plone.less";
@import "@{barceloneta_path}/portlets.plone.less";
@import "@{barceloneta_path}/controlpanels.plone.less";
@import "@{barceloneta_path}/tags.plone.less";
@import "@{barceloneta_path}/contents.plone.less";

// Patterns
@import "@{barceloneta_path}/accessibility.plone.less";
@import "@{barceloneta_path}/toc.plone.less";
@import "@{barceloneta_path}/dropzone.plone.less";
@import "@{barceloneta_path}/modal.plone.less";
@import "@{barceloneta_path}/pickadate.plone.less";
@import "@{barceloneta_path}/sortable.plone.less";
@import "@{barceloneta_path}/tablesorter.plone.less";
@import "@{barceloneta_path}/tooltip.plone.less";
@import "@{barceloneta_path}/tree.plone.less";

// Structure
@import "@{barceloneta_path}/header.plone.less";
@import "@{barceloneta_path}/sitenav.plone.less";
@import "@{barceloneta_path}/main.plone.less";
@import "@{barceloneta_path}/footer.plone.less";
@import "@{barceloneta_path}/loginform.plone.less";
@import "@{barceloneta_path}/sitemap.plone.less";

// Products
@import "@{barceloneta_path}/event.plone.less";
@import "@{barceloneta_path}/image.plone.less";
@import "@{barceloneta_path}/behaviors.plone.less";
@import "@{barceloneta_path}/discussion.plone.less";
@import "@{barceloneta_path}/search.plone.less";

/* ### END OF PLONE IMPORTS ### */

/* ### UTILS ### */

// import bootstrap files:
@bootstrap_path: "node_modules/bootstrap/less";

@import "@{bootstrap_path}/variables.less";
@import "@{bootstrap_path}/mixins.less";
@import "@{bootstrap_path}/utilities.less";
@import "@{bootstrap_path}/grid.less";
@import "@{bootstrap_path}/type.less";
@import "@{bootstrap_path}/forms.less";
@import "@{bootstrap_path}/navs.less";
@import "@{bootstrap_path}/navbar.less";
@import "@{bootstrap_path}/carousel.less";

/* ### END OF UTILS ### */
@import (less) "../css/business-casual.css";

// include our custom css/less
@import "custom.less";

Here you can see how we include the resources like @import "@{bootstrap_path}/carousel.less"; in our Less file.

But before they can be used, it is important to add the path to the less files:

// import bootstrap files:
@bootstrap_path: "node_modules/bootstrap/less";

This defines the path to the bootstrap files, so that we can use it in all bootstrap includes.

Note

Don’t forget to run grunt compile after you changed any of the Less files or use grunt watch to do this automatically after every file change.

More Diazo And plone.app.theming Details

For more information on how to build a Diazo based theme look at the diazo documentation and the plone.app.theming manual.

In the next part we will take a look at template customizations for our theme and make the slider dynamic and let users change the pictures for the slider.