Frontend Best Practices and Restrictions
This page gives the rules for frontend work in a Festi project. Each rule has its reason.
The most important rule: use what the framework has before you write something new. The theme, the DGS, and the HUI components supply most of an admin interface.
Decide what to use
flowchart TD
A["You need a new screen"] --> B{"A list or a form<br>of stored data?"}
B -- yes --> DGS["Use the DGS"]
B -- no --> C{"A dashboard with<br>blocks?"}
C -- yes --> ST["Use StageBuilder"]
C -- no --> D{"Standard elements:<br>forms, buttons, dialogs?"}
D -- yes --> HUI["Plugin template<br>with HUI components"]
D -- no --> E{"Much state in<br>the browser?"}
E -- no --> TPL["Plugin template<br>with a small script"]
E -- yes --> R["React application<br>in the plugin"]
| Need | Use | Page |
|---|---|---|
| List, filter, add, edit, remove stored data | DGS | Data Grid Store |
| A special input in a DGS form | Custom DGS field | Custom DGS Fields |
| Dashboard of blocks | StageBuilder | StageBuilder |
| Form, button, dialog, message, tabs | HUI components | HUI guide |
| Content that is special to one page | Plugin template | Templates |
| Page with much state in the browser | React application | ReactJS |
Reuse the theme
- Use one theme for the admin area. SmartAdmin is the primary admin theme. A second admin theme makes each plugin more difficult to test.
- Do not edit a theme that comes from a package or a submodule. The next update removes your change. Use the methods in How to Adjust SmartAdmin.
- Do not edit files in
vendor/. This includes the Theme package templates and ThemeAssets. - Add to the layout with theme events. Do not copy
main.phtmlto add one tag. See the Theme package. - Make a custom theme only for a different design. A website for visitors needs its own theme. An admin panel does not. See How to Make a Custom Theme.
Reuse the DGS
- Do not write a list or a form by hand when a DGS can make it. The DGS supplies permissions, validation, filters, the pager, and the dialogs.
- Change a DGS in this sequence: schema attributes, then events, then a custom field, then a template. See DGS templates.
- Replace the smallest template part. A copy of
store/list.phtmldoes not get the corrections of later framework versions. - Keep the CSS classes of the DGS templates.
jimbo.jsfinds the elements with them.
Templates
- A template shows data. It does not get data. Do not call the database or a different plugin in a template. Get the data in the plugin method.
- Give data to a template as local variables (
fetch('name.phtml', $vars)). Useassign()only for data that many templates of the request use. - Do not use the variable names
templatePathandlocalVars. The template system uses them. - Divide a large template into parts in
templates/parts/. Small parts are easy to test and to use again. - Escape each value that comes from a user:
htmlspecialchars($value). - Use
__()for each text. - Use the full PHP tag
<?php echo ... ?>. Short tags (<?=) are not permitted. - Use
$this->getUrl()and$this->getStaticUrl()for URLs. Do not write a URL as fixed text. - Add the IDE type hints at the top of each template (
@var).
See Templates.
HUI components
- Use a HUI component for each form, input, button, dialog, and message. Do not write this HTML in a template.
- Change the look with
setCSS()and the project CSS. Replace a component template only when the HTML structure must change. - Replace a component template in the smallest scope that is sufficient: the
templates/ui/directory of one plugin, or the projecttemplates/ui/directory for all plugins. - Add a component when three or more templates need the same HTML.
See the HUI guide.
CSS
Where to put CSS
| CSS for | File |
|---|---|
| The project (colors, logo, corrections of the theme) | static/css/styles.css of the project |
| One plugin | plugins/<Name>/static/css/ |
| One DGS field class or one React application | The directory of that field or application |
- Do not use the
styleattribute and<style>blocks in templates. The exception is a value that the server calculates, for example a color from the database. - Do not use
!important. The project CSS loads after the theme CSS. A selector with the same weight is sufficient. - Do not change a Bootstrap class or a theme class for all pages from a plugin. Add your own class to the element.
See Static Files.
Class names
Festi uses a naming system with a prefix for the role of the class. It is a simple form of BEM (block, element, modifier).
| Prefix | Role | Example |
|---|---|---|
b- |
Block: an independent part of the interface. | b-order-card |
e- |
Element: a part of a block. | e-title, e-total |
m- |
Modifier: a variant or a state. | m-paid, m-hidden |
f- |
A form that JavaScript processes. | f-db-form |
i- |
An input that JavaScript reads. | i-grouped-item |
<div class="b-order-card m-paid" id="order-15">
<div class="e-title">Order #15</div>
<div class="e-total">120.00</div>
</div>
.b-order-card { border: 1px solid #ddd; }
.b-order-card .e-title { font-weight: bold; }
.b-order-card.m-paid { border-color: #5cb85c; }
Rules:
- Start the block name with the subject of your plugin (
b-order-card, notb-card). Then two plugins do not use the same name. - Write the CSS of an element with its block:
.b-order-card .e-title. An element name alone (e-title) is not unique. - Use lowercase letters and hyphens.
- A modifier is a separate class. Do not join modifiers into one name.
- Use a class for CSS and for JavaScript selectors. Use an
idonly for one unique element. - Do not give a new meaning to a class of the framework:
b-db-table,e-db-action,f-db-form,e-input,i-grouped-item,m-field-invalid-value.
JavaScript
- Put JavaScript in files in the
static/jsdirectory of the plugin. Register the files in the plugin method withincludeStatic(). - Do not write
<script>blocks with logic in a template. A short call of a function with data from the server is permitted. - Do not make JavaScript text on the server from user input. Send data (JSON or
data-attributes). - Use
jQuery, not$. - Use
constandlet, notvar. - Do not start a variable name with
$. - Put the functions of a plugin in one object with the name of the plugin (
const Orders = {...}). Do not add many global functions. - Do not load a second copy of jQuery or Bootstrap.
- Start the code of a DGS form in a listener for
Jimbo.EVENT_ON_DISPLAY_STORE_FORM. The form is not in the page when the document is ready. - Do not change
jimbo.js. Add a listener or a functionJimbo.set<Type>Responsein your file.
See the Jimbo.js reference.
Server and browser
- Do not print output in a plugin method. Fill the
Responseobject. - Use
onJsonmethods for requests from JavaScript andonAjaxmethods for forms that submit into the hidden<iframe>. - Give each reply to
Jimbo.response(). Then messages, form errors, and redirects work in the same manner on each page. - Confirm a change with
addNotification()andResponse::ACTION_RELOADorResponse::ACTION_REDIRECT. - Validate on the server. The validation in the browser helps the user. It does not protect the data.
- Start the names of your actions and events with the name of your plugin.
See Response and Frontend and backend events.
Restrictions
These things do not work. Do not design a solution that needs them.
| Restriction | Use this |
|---|---|
A plugin templates directory does not replace the template of a different plugin. |
The directory templates/plugins/<Name>/ of the theme, or a template directory with a postfix. See Templates. |
A plugin templates directory does not replace a default DGS template. The DGS looks there last. |
A lookup directory of the store view. See DGS templates. |
| A file in a theme or a plugin does not replace a DGS field template. | A field class with its own template directory. See Custom DGS Fields. |
| A JSON response does not load registered JS and CSS files. A response for a dialog loads them only if the theme adds them (SmartAdmin does). | Register the files on the page that opens the dialog. See Static Files. |
includeStatic() accepts only .js and .css files. |
getStaticUrl() for other files. |
There is no list of scripts for the end of <body>. |
jQuery(document).ready() in a file that <head> loads. |
jimbo.js does not submit a form with the class f-form. |
The HUI form or the HUI dialog with an onAjax method. |
| Plugins do not share template variables. | Call a method of the other plugin to get data. |
| The framework has no microfrontend runtime. | One React application for each plugin. See ReactJS. |
A project has no JavaScript test runner for the scripts of its plugins. Only jimbo.js has tests, in the ThemeAssets repository. |
PHPUnit render tests. See Frontend Tests. |
Before you send a change to review
- The page shows no error in the browser console.
- Each new text uses
__(). - No file in
vendor/or in a theme from a package has a change. - New CSS classes use the prefixes
b-,e-,m-. - New JavaScript is in a file in
static/js. - A render test exists for each new template part or component with logic. See Frontend Tests.