Static Files: JavaScript and CSS

This page tells you how a plugin adds JavaScript and CSS files to a page.

A plugin does not write <script> or <link> tags. A plugin registers a file. The theme then prints the tags in the <head> of the page.

Where the files are

Put the static files of a plugin in its static directory:

plugins/
`-- Users/
    |-- UsersPlugin.php
    `-- static/
        |-- css/
        |   `-- common.css
        |-- js/
        |   `-- user.js
        `-- images/
            `-- avatar.png

The web server must serve this directory. The URL of a file is:

<plugins_http><PluginName>/static/<relative path>

With the default options, static/js/user.js of the Users plugin has the URL /plugins/Users/static/js/user.js.

The methods

Method Class Use
includeStatic($relativePath) DisplayPlugin Register a .js or .css file from the static directory of the plugin.
getStaticUrl($relativePath) DisplayPlugin Get the URL of a file in the static directory (for example, an image).
includeJs($path, $theme = true) Core Register a JS file by path or URL.
includeCss($path, $theme = true) Core Register a CSS file by path or URL.

DisplayPlugin is the class Festi\Theme\Plugin\DisplayPlugin of the Theme package (festi-team/festi-framework-theme). The project must have this package.

includeStatic()

Call includeStatic() in the plugin method that renders the page. The path is relative to the static directory of the plugin.

use Festi\Theme\Plugin\DisplayPlugin;

class UsersPlugin extends DisplayPlugin
{
    public function onDisplayDefault(Response &$response): bool
    {
        $this->includeStatic('js/user.js');
        $this->includeStatic('css/common.css');

        $response->content = $this->fetch('users.phtml');

        return true;
    }
}

includeStatic() reads the file extension:

  • .js: the method registers a JavaScript file.
  • .css: the method registers a CSS file.
  • A different extension: the method throws a SystemException ("Undefined resource type").

getStaticUrl()

Use getStaticUrl() for files that are not JS or CSS.

<img src="<?php echo $this->getStaticUrl('images/avatar.png'); ?>" alt="">

Core::includeJs() and Core::includeCss()

Use these methods for a file that is not in the static directory of a plugin.

The second argument, $theme, controls the URL:

$theme Result
true (default) The framework adds the theme URL before the path. Use this for a file of the theme.
false The framework uses the value as it is. Use this for a full URL or a root path.
// A file of the active theme: <theme_url>js/plugin/select2/select2.min.js
$this->core->includeJs('js/plugin/select2/select2.min.js');

// A file from a CDN
$this->core->includeJs('https://cdn.example.com/chart.min.js', false);
$this->core->includeCss('https://cdn.example.com/chart.min.css', false);

How the files get to the page

sequenceDiagram participant P as Plugin method participant C as Core participant D as DisplayPlugin::fetchMain() participant T as Theme (head.phtml) P->>C: includeStatic('js/user.js') Note over C: Core adds the URL to the list "js".<br>A URL is in the list only one time. P->>D: Response with content D->>C: getProperties() C-->>D: title, js, css D->>T: $info['js'], $info['css'] T-->>T: Print one tag for each URL
  1. includeStatic() adds the URL to a list in Core. There is one list for JS and one list for CSS.
  2. If you register the same URL two times, the list contains it one time.
  3. When the plugin displays the page, DisplayPlugin::fetchMain() copies the lists into the $info variable of the theme.
  4. The theme template head.phtml prints one tag for each URL.

The theme prints the files in the sequence in which the plugins registered them. It prints all CSS files before all JS files.

Version parameter

The SmartAdmin theme adds ?v<version> to each registered URL. The version is the value of the js_version setting. Change this setting after each release. Then the browser loads the new files and does not use its cache.

The theme also adds the parameter to a full URL (for example, a CDN URL). Thus do not register a URL that already has a query string.

Location on the page

The theme prints the registered files in <head>, after the CSS and JS files of the theme. Thus your CSS rules can override the rules of the theme, and your JS can use jQuery and Jimbo.

There is no list for the end of <body>. If a script needs the page elements, wait for the document:

jQuery(document).ready(function () {
    // The page elements are available here.
});

When to register a file

Register the file before the theme renders the page. These locations are correct:

Location Use it when
The plugin method that renders the page (onDisplay*) The file is necessary for one page. This is the usual location.
A listener for ITheme::EVENT_THEME_BEFORE_FETCH_MAIN The file is necessary on each page (for example, a chat widget).
The onInit() method or the constructor of a DGS field class The file is necessary when a form or a list shows that field.

Example of a file for each page (init.php of the plugin). In init.php, $this is the Core object.

use Festi\Theme\ITheme;

$this->addEventListener(
    ITheme::EVENT_THEME_BEFORE_FETCH_MAIN,
    function (FestiEvent &$event) {
        $plugin = Core::getInstance()->getPluginInstance('My');

        $plugin->includeStatic('js/my.js');
        $plugin->includeStatic('css/my.css');
    }
);

Get the plugin instance in the listener, not in init.php. When init.php runs, the database connection is not ready, and a plugin with a data object cannot start.

Do not call includeStatic() in a template. A reader of the plugin method must see all the files that the page uses.

Content that the page loads later (dialogs, AJAX)

The theme prints the registered files only when it renders a full page.

These responses do not contain the page layout:

  • a DGS form or a DGS action that the page loads into a dialog (the request has the parameter popup)
  • a response for which the plugin method called $response->flush()
  • a JSON response (onJson* and onAjax* methods)

For such a response, the theme does not render head.phtml. Thus the browser does not get the files that the request registered.

The SmartAdmin theme has one exception. For a request with the parameter popup, it puts the registered JS and CSS files at the start of the content. A different theme does this only if it has the same listener. Thus do not design a plugin that needs this. Use the procedure below, which works with each theme.

For content that the page loads later:

  1. Register the files in the method that renders the full page which opens the dialog.
  2. In the script, start the logic when the content shows. Use a Jimbo event, for example Jimbo.EVENT_OPEN_DIALOG or Jimbo.EVENT_ON_DISPLAY_STORE_FORM.
Jimbo.addListener(Jimbo.EVENT_ON_DISPLAY_STORE_FORM, function (event, params) {
    // The form is in the page now. Start the components of the form here.
});

CSS of the project

Do not edit the CSS files of a theme that comes from a package.

The SmartAdmin theme loads two project files after its own CSS files:

File Media
<http_base>static/css/styles.css screen
<http_base>static/css/print.css print

Put the CSS rules of the project in static/css/styles.css. These rules override the rules of the theme. Put CSS rules that only one plugin uses in the static/css directory of that plugin.

Rules

  • Call includeStatic() in the plugin method, not in a template.
  • Use getStaticUrl() and getUrl() for URLs. Do not write a URL as fixed text.
  • Do not write inline <script> blocks with logic in a template. Put the logic in a file in static/js.
  • Do not load a second copy of jQuery or Bootstrap. The theme loads them.
  • Use a full URL with includeJs($url, false) for a library from a CDN.