Skip to content
/ wpx Public

The WPX Theme is a starter theme for WordPress, developed by DQuinn.net.

License

Notifications You must be signed in to change notification settings

alkah3st/wpx

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

WordPress Extend

A boilerplate theme for WordPress, developed by DQuinn.net.

WordPress Extend (WPX) is a stripped down boilerplate for building brochureware with WordPress. WPX is meant to work with the plugin WPX Utility, which contains helper functions for use with the theme, and Advanced Custom Fields, for creating custom meta boxes in the Dashboard.

Features

  • Install with WPX Utility, a plugin that contains helper functions for use with the theme;
  • Ready for use with Advanced Custom Fields & Gutenberg
  • Stripped-down, un-opinionated WP templates
  • PHP namespacing for hooks and functions;
  • Includes Gulp-powered build script for the front end;
    • Simple, CSS framework-free globbed Sass;
    • Includes dynamic icon fonts via Fontello;
    • Includes inline retina images and retina background images

Installation

  1. Download and install the latest version of WordPress from WordPress.org.

  2. Download and activate the WPX Utility plugin.

  3. Download and activate the WPX Theme in your /wp-content/themes/ folder. Then cd into /assets/ and run:

  4. In /wp-content/themes/wpx/assets/gulpfile.js put in your local environment path in the external-css task.

Run the following command in terminal:

npm install

This will create a /node_modules/ folder in the /assets/ folder of the theme.

After following the instructions under Icon Fonts, run the following Gulp task:

gulp fontello

To generate your retina sprites and custom icon font, you can run:

gulp sprites.

You can then run gulp (which will trigger gulp watch) or gulp build if you are prepping the codebase for production.

Theme Organization

Default WordPress Templates

Keep default WordPress templates, such as page.php and index.php in the root of the theme. The header and footer of the site are in header.php and footer.php, in the theme root. The rest of the theme is organized as follows:

  • acf-json, for Advanced Custom Field's JSON templates;
  • assets, for front-end files;
  • includes, for hook logic;
  • templates, for WordPress page templates, block templates, and api templates

Hook Organization

Hook logic is organized in /includes/, under the hook file that makes the most sense:

  • blocks.php is for including ACF Gutenberg blocks
  • dashboard.php is hooks that alter the Dashboard
  • enqueue.php is for any FE injection of assets
  • feed.php is for modifications to the RSS feed output
  • plugins.php is for any hooks that interact with plugins
  • rewrites.php is for any URL rewriting hooks
  • schema.php is for schema markup
  • templates.php is for hooks that alter the way WP templates/UI functions

Each of these is namespaced, for example, to call a function defined in \WPX\Enqueue\, you would write:

\WPX\Enqueue\my_function();

All of the hook files are loaded via the default functions.php file in the theme root, as well as any theme initialization logic.

Classes & Components

WPX autoloads the folder structure under /classes/ as classes. It ships with /Theme/ and /UI/ as groups to create more classes related to individual modules. For example, you might create a Slideshow.php class under /UI/ for a component (that is not a block) but needs its own UI and functios.

Here is an example of a class:

<?php
/**
 * Example Module
 *
 * @package WordPress
 * @subpackage WPX_Theme
 * @since 0.1.0
 * @version 1.0
 */
namespace WPX\Classes\UI;

class ExampleModule {

	public $args;

	function __construct($params=false) {

		// if we wanted to set some parameters on init

		$defaults = array (
			'default_setting' => false,
		);

		$this->args = wp_parse_args( $params, $defaults );

		/**
		 * Example Calling a Method Dynamically
		 */
		
		// $variableMethod = 'string'.$variable;
		// $this->$variableMethod($post);

	}
	
	function exampleHeader() {
		echo "hey!";
	}

}

You would invoke this class like so:

$exampleModule = \WPX\Classes\UI\ExampleModule();
$exampleModule->exampleHeader();

The objective is to isolate component output and their related from WP templates or the organization of other hooks in /includes/ to keep things clean.

Available Global Paths

For convenience. you can get several root URIs/paths rendered in any template like so:

assets root URI:

// assets_url();

<img src="<?php echo assets_url(); ?>/images/my-image.jpg" alt="My image">

domain root URI:

// WPX_DOMAIN

<a href="<?php echo WPX_DOMAIN; ?>">Home</a>

theme root URI

// WPX_THEME_URL 

<img src="<?php echo WPX_THEME_URL; ?>/favicon.png" alt="Favicon">

theme root path

// WPX_THEME_PATH 

C:/your/file/path/website/

Image Assets

Temp Images

The folder /assets/images/temp/ is for image assets used during development (typically sampled from source.unsplash.com. The folder should not reach production.

Favicons

I use Real Favicon Generator to generate a set of crossbrowser compatible favicons for the theme. See the header.php for example output, or visit the website. These graphics are stored in /assets/images/favicons.

Inline Images

Drop inline images into /assets/images/. Their retina counterparts should be suffixed with @2x.png.

  • Drop sprites and retina sprites in /assets/images/sprites/. Retina sprites should be suffixed @2x.png and be double the pixel size of the non-retina sprites.
  • Drop backgrounds in /assets/images/bgs/. Retina backgrounds should be suffixed @2x.png and be double the pixel size of non-retina backgrounds.

Inline Retina Images

The boilerplate loads Dense, so you can render a retina-capable inline images in the templates by adding the data-2x attribute to any image tag that also has a retina class.

The value of the data-2x attribute is the path to the retina image:

<img src="<?php echo assets_url(); ?>/images/logo-main.png" class="retina" data-2x="<?php echo assets_url(); ?>/images/[email protected]">

For more information refer to the Dense documentation

Retina Sprites

You can drop images you want combined into a single sprite in /images/sprites/. They must be PNGs.

You should drop both a retina and non-retina version in this folder.

For example, if you have an icon that's 100x100 pixels, you would drop icon.png and [email protected] into this folder, and they'll automatically be combined into a retina sprite when you run: gulp setup.

  • Keep the filename brief, using hyphens for spaces, with @2x.png suffix.

  • You must put both a retina and non-retina image for each pair. If you don't have a retina image for something, just make a copy of the non-retina, double the size, and name it appropriately for the time being.

  • gulp-spritesmith will fail if retina sprites do not exist, or they are the incorrect sizes. If you do not want to use sprites, just remove the "sprites" task from the gulp and gulp build tasks.

  • You have to run gulp task manually whenever you add new images; gulp watch won't re-create sprites to save on compile time. (However gulp build will.)

Use a sprite in your Sass like so:

.myicon {
	@include retina-sprite($icon-group); // where the files involved were named "icon.png" and "[email protected]". Notice the suffix "-group"
}

This will render the following CSS:

@media (-webkit-min-device-pixel-ratio: 2), not all, (min-resolution: 192dpi) {
	.myicon {
		background-image: url(../images/[email protected]);
		background-size: 91px 91px;
	}
}

.myicon {
	background-image: url(../images/spritesheet.png);
	background-position: 0px 0px;
	width: 91px;
	height: 91px;
}

Retina Background Images

You may have a situation where you need to render a repeating background and provide a retina version of the background in your CSS. This theme has a mixin built in for this.

Simply drop the retina and non-retina background image into /assets/images/bgs/, for example background.png and [email protected].

You can then call the background like so in the SASS:

.mybackground {
	@include bg-retina("background", "jpg", 800px, 517px);
	width: 800px;
	height: 517px;
}

This will render the following CSS:

@media only screen and (-webkit-min-device-pixel-ratio: 2), 
not all, not all, not all, not all, only screen and (min-resolution: 192dpi), 
only screen and (min-resolution: 2dppx)
.background-example {
	background-image: url(../images/bgs/[email protected]);
	background-size: 800px 517px;
}

.mybackground {
	background-image: url("../images/bgs/background.jpg");
	width: 800px;
	height: 517px;
}

Icon Fonts

If you want to render an icon font (like arrows or social icons), you can use the Fontello vector font generated by the build script.

  • Go to Fontello.com, select the icons you want to include in your project, then in the big red button labeled "Download Webfont" choose "Get Config Only" in the dropdown. (The down arrow lets you choose "Get Config Only.")

  • This will generate a config.json file. Rename this to fontello.json and replace the one in the /assets/ folder with it.

  • You can now run gulp fontello and the font will be automagically generated.

To render an icon in your templates, you can use this markup:

<i class="icon-facebook"></i>

The classname is defined in the fontello.json, and you can set the class name prior to exporting from Fontello.com using Fontello's web interface.

If you need to add new icons after exporting, just re-import the fontello.json into Fontello.com. (Click the little wrench icon in the web interface.) This will make all the icons you imported selected in the web interface, then you can select more an export the json file again.

Semantic HTML

HTML should be written semantically in HTML5. That means use <div> elements if the element has no semantic meaning, but <section> elements when the element represents a break in the document outline.

For example:

<section class="my-articles"> <--! has semantic meaning, is a "section" of the document for Recent Articles -->

	<h1>Recent Articles</h1>

	<div class="wrapper"> <--! has no meaning, is just a wrapper -->

		<article class="article">

			<h1><a href="#">Title of Article</a></h1>

		</article>

		...

	</div>

</section> 

Semantic Heading Levels

Don't forget H1s. Whenever you open a sectioning element (<section>, <article>, see W3C.org on Sectioning Elements, make sure there is at least one <h1> somewhere in it.

Screenreader Class. You can use class="screenreader" if you do not want to display the <h1> to the user. This class is defined in base.scss to hide the element in a screenreader-friendly way. All sectioning elements must have at least one <h1> inside them to be considered meaningful sections.

About Heading Levels. Each sectioning element resets the heading levels. That is, a <section> element can have <h1> - <h6> and an <article> inside of the <section> can also have <h1> - <h6>:

<section>

	<h1>Recent Posts</h1>

	<h2>Category: Potatoes</h2>

	<article>
		<h1>Why Potatoes are Great</h1>
		<h2>Dylan Thomas</h2>
	</article>

	<article>
		<h1>How to Make Salty Potatoes</h1>
		<h2>Jim Michelson</h2>
	</article>

</section>

Keep in mind that the document itself has its own heading levels distinct from any sections:

<body>

	<h1>Title of the Page Itself</h1>

	<h2>My Subtitle for the Page</h2>

	<section>
		<h1>My Special Section</h1>
		<h2>My Subtitle for the Section</h2>
	</section>

Use the Chrome extension HTML5 Outliner to see the semantic meaning in your pages.

Styles

WPX does not include a CSS Framework. Do not introduce a CSS framework (such as Bootstrap, etc) or a CSS grid-based framework to the codebase. Additional mixins are welcome, but remember to keep things simple and not add unnecessary complexity to the project. Only normalize.css is included in the vendor files, as well as a handful of useful mixins.

Some quick ground rules for CSS:

  • Write everything in lowercase and use hyphens to separate words;
  • Use 4 tabs for spaces;
  • Do not write vendor prefixes for CSS3 properties in your CSS. The build script will handle this automatically. So write border-radius: 10px and not -webkit-border-radius: 10px;
  • Do not use IDs. We target elements in JS with data attributes.
  • Don't use em font sizes (see below for the desired methodology)
  • When defining line-height, use fixed values like 1, 1.5 and not 32px unless you are vertically-centering an element

SASS Globbing

All the SASS files for the project are in /styles/sass/ folder, organized like so:

globals
	- base.scss // for global styles
	- blocks.scss // all styles to override core Gutenberg blocks
	- buttons.scss // all styles to override core Gutenberg buttons (and for custom buttons) 
	- footer.scss // styles related to the footer
	- forms.scss // styles related to form elements
	- header.scss // styles related to the header
modules // module-based styles
	- blocks
		- custom-block.scss // an example "Custom Block" for Gutenberg
		- other-custom-block.scss ...
	- components
		- my-component.scss // for non-block modules used in the theme
	- navs
		- nav-footer.scss // for the footer nav
		- nav-mobile.scss // for the mobile nav
		- nav-primary.scss // for the primary nav
templates // for styles concentrated to static templates
utility // variables, fonts, mixins
	- mixins.scss // global mixins
	- normalize.scss // this is the latest normalize 
	- sprites.scss // generated via the spritemap in gulp (DO NOT EDIT)
	- tinymce.scss // styles non-block markup from Gutenberg
	- variables.scss // global variables
vendor // sass from third parties
	- modal.scss ... 

Globals. In base.scss we includes selectors that can be used anywhere in the website, such as element overrides, global classes, and structural selectors that are not context dependent.

Gutenberg/VE Styles. The mixin tinymce.scss contains styles to handle the non-block output of the Gutenberg editor, whereas the sheet blocks.scss contains styles to handle the core Gutenberg block output. Keep in mind that most of the default styles are set in the theme.json. Wherever in the templates of the site I might output the contents of Gutenberg, I need to ensure that all outputted markup is styled consistently. In this case I will wrap the output with a .tinymce class. This ensures the limited set of markup that the Visual Editor can output styles the outputted markup in isolation. (These days most templates are wrapped in tinymce output from head to toe.)

Modules (Components vs. Blocks). You can break down other contextual groups of selectors into individual SASS files under /modules/, in whatever way makes sense for the project. Typically a discreet bundle of contextual selectors, such as a block or a primary navigation, is a good candidate for a separate .scss file in /modules/. Note that we make a distinction between a module that is a block (meaning, a Gutenberg block registered in the system) and a component, which is not a block, and organize the CSS separately.

Buttons. We make use of the button block in WP across the theme, so buttons.scss is treated separately from other blocks so that any modifications to the blocks in Gutenberg also impacts buttons elsewhere in the theme.

Vendor. The /vendor/ folder is meant for including any SASS partials or CSS provided by third party plugins, and will get compiled with everything else. The styles in /vendor/icons/ as well as the sprites.scss sheet are generated by the built script. Just rename any vendor CSS to SASS and drop in this folder.

Writing Selectors

Above all else, we want to adhere to DRY principles (Do Not Repeat Yourself), keep things simple, and make things easy to understand. It's OK to repeat selectors if it means you can better isolate a module to make it re-usable throughout the site. At the same time, don't pursue modularity at the cost of sanity: we are building brochureware, not Facebook. For these reasons, avoid BEM or (severely) Atomic CSS methodologies.

Some quick ground rules for SASS:

  • Do not nest selectors more than 3 levels deep;
  • Avoid complicated SASS abstractions and/or calculations;
  • Put mixins immediately after an opening selector, then all properties of the selector, then pseudo element selectors, then state modifiers, then nested selectors.

Types of Selector-Objects

Let's establish some specific guidelines about writing nested selectors, in the spirit of SMACSS. Since we can nest selectors with SASS, the goal is to categorize our nested selectors into "selector objects" and use them with purpose. Any selector object you write should fit into one of these categories:

Global Objects. These are selector objects that are meant to be used anywhere in the site, independent of context. Global objects may not have a lot of nested selectors, if any at all. You shouldn't have a lot of these, and you shouldn't treat them like state modifiers (see below), otherwise you'll end up with Atomic CSS (which we do not like). A .button is a good example of a global object:

.button {
	@include font-size(14px, 12px);
	background-color: color('yellow');
	display: inline-block;
	padding: 12px 0;
	border-radius: 30px;
	color: color('black');
	text-align: center;
	min-width: 270px;
	text-transform: uppercase;
	font-weight: 600;
	letter-spacing: 2px;
	border: 3px solid transparent;
	text-decoration: none !important;

	// pseudo class
	&:hover {
		...
	}

	// state modifier
	.is-outline {
		.. // an alternate look/feel 
	}

}

As you can see, this selector is an object unto itself: you could place it anywhere in the site and it doesn't rely on any properties outside of itself to be presentable.

Module Objects. Probably the most common type of selector object you will write. They're generally self-contained, and may have a great deal of nested selectors. Like global objects, modules are also generally context-independent, but they are less likely to be nested inside of other selector objects. The difference between global objects and module objects is really a matter of complexity.

.module {

	// special context
	&.in-sidebar {
		... styles that alter this module specifically by context
		... here we can break the rule of 3, because:
		... .module.in-sidebar .module-inner .module-description is still 3 deep
	}

	.module-inner {

		.module-description {
			...
		}

	}

	.module-title {
		...
	}

	// this module alters the look of the global .button object
	.button {
		... // styles that alter the button specifically for this module
	}

}

The module object's naming pattern is best expressed as:

name (.module)
	> name + context (.module-inner)
		> name + context (.module-description)

The topmost selector is represented by a unique namespace .module, and nested selectors should be prefixed with the module's unique name.

Whenever you make a new module, puts its styles into a corresponding file module.scss in the /assets/styles/sass/modules/components/ folder (or /assets/styles/sass/modules/blocks/ if the module is a Gutenberg block).

Layout Objects. Layout objects are used for just that: elements that serve a strictly structural purpose (as opposed to functional purpose) in layouts. Their types include headers, footers, wrappers, sections, asides, sidebars, and navs. Layout objects, unlike global or module objects, are very unlikely to be nested inside one another.

.header-main {

	.header-main-outer {

		.header-main-inner {
			...
		}

	}

}

// or 

.sidebar {

	// indicating an alternative context
	&.in-left-rail {
		...
	}

	&.in-right-rail {
		...
	}

	// this module is affected by special styles when it appears
	// inside a sidebar layout object
	.module {
		...
	}

}

The naming pattern for layout objects should borrow from a fixed list of predefined layout types (such as the list enumerated above), where the topmost selector and nested selectors are prefixed with the layout type:

layout type + context (.header-main)
	> layout type + context + secondary context (.header-main-outer) 
		> layout type + context + tertiary context (.header-main-inner)

The right place for most layout objects is in base.scss, though if you have a lot of them, it might make sense to organize them into a /structures/ folder in partials like footer.scss and header.scss nested within /base/ for example.

State Modifier Objects. These are selectors that are meant to be used inside of other objects, and usually represent states triggered by Javascript or context-specific modifiers to the presentation of a module/layout. If you intend to define a state modifier object globally (meaning it can be used anywhere, independent of context), it's wise to suffix all properties in the object !important. It is preferable to constraint state modifier objects to modules or layouts. Avoid defining lots of global state modifiers, as that will lead down the path to Atomic CSS (which we do not like).

.is-error {
	color: red !important;
	border: 1px solid $red !important;
} 

.is-aligned-left {
	float: left;
}

Sass Variables

You will find all the variables in /assets/styles/sass/partials/utility/variables.scss.

Mainly these are fonts, variables for the global context (the max-width of the site, see below), the site's margin rhythm (see below), global color declarations from Gutenberg, and viewport sizes.

Do not litter your partials with hex values or font declarations.

Declaring Colors

To declare a color in an element, use the WP variables established in theme.json. For example, if you set:


{
	"name": "Black",
	"slug": "black",
	"color": "#000000"
},


Then you can reference that color as var(--wp--preset--color--blue).

Viewport Font Sizes

Use the @font-size mixin to render fonts responsively. Always specify your sizes in pixels, and they will get converted to rem as they are interpolated from a min to max size (using clamp()).

	h1 {
		@include font-size($size: 16px, $min: 14px, $max: 22px, $context: 1000px);
	}

At a minimum, you can just specify the starting size and the minimum size.

	h1 {
		@include font-size(16px, 14px);
	}

This will render a default size, a minimum size, a maximum size:

	font-size: clamp(14px, 1.6vw, 16px);

Context Mixin & Responsive Layouts

I tend to standardize my "context" in a responsive design. Context in this sense means the maximum width of the content area that the designer intended. Usually this is something near 1200 pixels. The context widths are set in theme.json as:


var(--wp--style--global--content-size);
var(--wp--style--global--wide-size)

Or you can call context($max-width: var(--wp--style--global--content-size), $width: 90%); and it will output:


.my-element {
	margin: 0 auto;
	max-width: 1200px;
	width: 90%;
}

You can then size elements based on a percent of that context responsively.

For the most part, all layout elements should be styled with fluid widths, and you should utilize break points to adjust the rhythm of that scaling when the design looks awkward at any given viewport. This implies defining breakpoints per selector object, rather than globally across the site. More on this below.

Breaking Out of Context

While @include context() defines the standard page width for the flow of content, @include width-wide() optionally defines a size when a block is set to "wide" width, meaning it breaks out of the flow about 120%. This is 1600px (matching the value of --wp--style--global--wide-size vs. the standard 1200px (the value of --wp--style--global--content-size).

You can force a block or component to break out of the page context by using the width-wide() or explode-width() mixins:

.my-block {
	@include width-wide($width: 120%, $margin: -10%, $breakeven: 1000px);
}

The parameters are optional, and the $breakeven is the viewport where we should set the container back to 100%. Results in:

.my-block {
	width: 150%;
	max-width: 150%;
	margin-left: -25%;

	@include breakpoint(1000px) {
		width: 100%;
		max-width: 100%;
		margin-left: 0;
	}
}

You may want to redefine the default for $breakeven at which the block goes back to 100% width to suit the needs of the design.

For the explode-width() function:

.my-block {
	@include explode-width();
}

Results in:

.my-block {
	width: 100vw;
	margin-left: calc(-50vw + 50%);
	position: relative;
}

Setting Breakpoints

If it makes sense to introduce a breakpoint for the primary navigation module (/assets/styles/sass/modules/navs/nav-primary.scss) at 800 pixels, 570 pixels, and 400 pixels, we should do that specifically for that module, even if the main content area only needs to break at 1000 pixels and 500 pixels.

You can define breakpoints by using the breakpoint(); mixin. There are several predefined viewports (you will find the viewports in /assets/styles/sass/utility/variables.scss), and they are: $viewport_ultrawide, $viewport_desktop, $viewport_tablet, $viewport_phablet, and $viewport_mobile. These variables should be adjusted to better suit your design, and you are encouraged to define viewports as needed, directly into the function, if the prefdefined viewports are unsuitable in a particular context: @include breakpoint(548px).

	.sidebar {
		width: percentage(500px / $context);

		@include breakpoint-mobile("mobile") {
			width: 100%;
		}
	}

Results in:

	.sidebar {
		width: 41.66666666666667%;

		@media only screen and (max-width: 500px) {
			.sidebar {
				width: 100%;
			}
		}
	}

The exception here would be things like icons and squares, that aren't meant to scale at all.

The minmax() mixin

The @include minmax() is very useful for setting rhythmic margins and paddings. If you want to set a responsive top and bottom margin or padding that scales with the viewport and also has a minimum and maximum size, this is the mixin for you:

	.sidebar {
		@include minmax(margin, 8%, 8%);
	}

This will result in the margin maxing out at a fixed pixel size for when exceeding the $viewport_desktop width set in /styles/sass/utility/variables.scss (assuming this was 1200):

	.sidebar {
		margin-top: 8%;
		margin-bottom: 8%;
	}

	@media only screen and (max-width: 1200px) {
		.sidebar {
			margin-top: 80px;
			margin-bottom: 80px;
		}
	}

Below that viewport size, the percentage gets used, up until we hit the minimum viewport size, which is by default $viewport_mobile:

	@media only screen and (max-width: 800px) {
		.sidebar {
			margin-top: 80px;
			margin-bottom: 80px;
		}
	}

If you wanted to set a different "minimum" for the margin/padding, you could do this:

	.sidebar {
		@include minmax(margin, 8%, 8%);

		@include breakpoint($viewport_mobile) {
			@include minmax(margin, 2.5%, 2.5%);
		}
	}

Which would result in:

	.sidebar {
		margin-top: 8%;
		margin-bottom: 8%;
	}

	@media only screen and (max-width: 1200px) {
		.sidebar {
			margin-top: 80px;
			margin-bottom: 80px;
		}
	}

	@media only screen and (max-width: 800px) {
		.sidebar {
			margin-top: 25px;
			margin-bottom: 25px;
		}
	}

You can also tweak the min and max breakpoint from the initial parameters:

@mixin minmax($style, $percent-top: auto, $percent-bottom : auto, $max-breakpoint: $viewport_desktop, $min-breakpoint: $viewport_mobile)

Enquire (Breakpoints in JS)

There may be situations where you need to trigger JS at specific breakpoints. For this, I use Enquire, which is already installed. You should define a set of Enquire breakpoints for each module in which it applies. For example, the layout.js module has a set defined for you (at arbitrary breakpoints);

	/*
	bind layouts
	 */
	bindBreakpoints: function() {

		enquire
		.register("screen and (min-width: 801px)", {

			// desktop
			match: function() {},

			// tablet
			unmatch: function() {}

		})
		.register("screen and (max-width: 800px)", {

			// tablet
			match: function() {},

			// desktop
			unmatch: function() {}

		})
		.register("screen and (max-width: 600px)", {

			// mobile
			match: function() {},

			// tablet
			unmatch: function() {}
		});

	}

Enquire triggers as the viewport scales, as well as once on initial load.

Javascript Methodology

The vast majority of projects we are building are brochureware, and not dynamic web apps. (If the project is a dynamic web app, you will need to reconsider the build script to accommodate a framework of your choice.)

To that end we just need a consistent way to reference elements via jQuery. We reference elements using either a data attribute data-target="element" or by controller data-controller="myController". Parent elements that are controllers contain child elements that are targets. Therefore treat data-target attributes as IDs unique to the parent data-controller. Similarly, data-controllers need to be treated as IDs unique to the page.

To reference a controller:

WPX.Utils.ctrl('loader') would reference <div data-controller="loader"></div>

To reference a target in a controller:

WPX.Utils.target('loader','loaderSpinner') would reference <div data-target="loaderSpinner"></div> inside of <div data-controller="loader"></div>.

Use camelCasing for the IDs inside targets and controllers. Keep in mind these utility functions cannot work with elements rendered via ajax.

  • Each custom script is considered a "module" and all modules run simultaneously on all pages of the site.

Build vs. Debug Mode

When define( 'WP_ENVIRONMENT_TYPE', 'development' ); in the wp-config.php is set to development, we are in "non-build mode." When it is set to production, we are in "build mode." You can also use staging as a value with WP, but you'll need to modify the enqueue below to account for that.

The significance of this is that in non-build mode, all JS scripts are output in the footer.php uncompressed, following this compilation order:

  1. Libraries in the js/vendor folder. We are not using a package manager here because, again, the goal is simplicity in brochureware. My experience building many, many of these types of sites is that there is no point fiddling with the tooling for something so simple. We simply drop the source files in js/vendor once, and we don't have to worry about those source files inadvertently getting updated because the build script has evolved. Since we are expecting that all module objects execute simultaneously, the order of compilation should not matter.
  2. app.init.js (this declares the global WPX object for modules)
  3. Module scripts (your custom scripts)

This looks like the following in the enqueue.php file of /includes/:

// we enqueue assets based on the build status, with cache busting
if (wp_get_environment_type() === 'development' || wp_get_environment_type() === 'local') {
	wp_enqueue_script( 'wpx.js', assets_url().'/js/app.js', array('jquery'), filemtime( get_template_directory().'/assets/js/app.js' ), true);
	wp_enqueue_style( 'wpx.styles', assets_url().'/styles/screen.css', false, filemtime( get_template_directory().'/assets/styles/screen.css' ), 'screen');
} else {
	wp_enqueue_script( 'wpx.js', assets_url().'/js/app.min.js', array('jquery'), filemtime( get_template_directory().'/assets/js/app.min.js' ), true);
	wp_enqueue_style( 'wpx.styles', assets_url().'/styles/screen.min.css', false, filemtime( get_template_directory().'/assets/styles/screen.min.css' ), 'screen');
}

In build mode, app.min.js is attached instead of app.js, by running gulp build.

Writing Custom JS Modules

In /js/modules/ you will find each custom JS module. You can create as many as you like, just remember that they all run simultaneously and in the order they are named. They are being written in such a way that they are not meant to be interdependent on one another. It is not wise to build synchronous relationships between modules; assume they are running asynchronously, after jQuery is ready.

Here is what the general utility module looks like:

jQuery(document).ready(function($) {

	'use strict';

	WPX.Utility = {

		$myController: WPX.Utils.ctrl('myController'),
		$myTarget: WPX.Utils.target('myController', 'myTarget'),
		someVariable: false,
		$someElement: $('.my-element'),

		init: function() {

			// bind onclicks here
			this.bindEvents();
			
			// fire an arbitrary function
			this.myFunction();

		},

		/** 
		 * bind all events
		*/
		bindEvents: function() {
			
			...

		},

		myFunction: function() {
			
			WPX.Utility.$someElement.addClass('yay');
			WPX.Utility.someVariable = 10;

		}

	};

	WPX.Utility.init(); // each module needs a unique name

});

Tips:

  • You can target functions inside modules globally by calling WPX.myModule.myFunction();
  • You can hook onto specific templates in WordPress by checking the body class, for example, or test for the existence of elements on the page: if ($('#my-element').length) { ... }

Concerning JS & Gulp

When you run gulp, the build script will:

  1. jshint your modules in /modules
  2. Simultaneously compile the SASS into screen.css and JS into app.js. (The JS compilation order is always /vendor/ then app.init.js then /modules/.
  3. Watch all sass files, js files, and php files for changes and refresh them with Livereload. (PHP files and JS files on save will refresh the page; only sass will hot reload.)

When you run gulp build, the build script will:

  1. Do steps 1 - 3 in gulp above, though the sass and JS task will remove console logs, sourcemaps, and minify/uglify the compilation into screen.min.css and app.min.js respectively.
  2. Also simultaneously compile all your sprites in /images/sprites/ into spritesheet.png and [email protected], and create the relevant scss files in /styles/sass/utility/sprites/;
  3. Also simultaneously load the fontello.config json to fetch the icon fonts, as well as update the scss related to those icon fonts;
  4. Then minify all images and svgs in /images
  5. Minify all Gutenberg-related sass files.

You can also run the following tasks independently:

  • gulp fontello to retrieve new files related to Fontello. Don't forget to run gulp afterwards.
  • gulp sprites to generate new spritemaps. Don't forget to run gulp afterwards.

Gulp Build Script

The gulp build script is already written for you and ready to go. See the package.json for version requirements.

Prep Fonts

After you have updated the Fontello Icon Fonts, you then need to run the following Gulp command:

gulp fontello

Generate Sprites

If you are using Retina Sprites, whenever you add more sprites to /images/sprites/ you need to run the following Gulp command:

gulp sprites

As a result, Gulp will merge your sprites together into a retina and non-retina sprite sheet in /images/spritesheet.png and /images/[email protected] and then update the /styles/sass/utility/sprites.scss with the newly available mixins.

This task is run on build.

Compile Gutenberg Sass

When you watch with the gulp task, styles are also compiled for Gutenberg. Gulp will merge the sass files under styles\gutenberg into a single gutenberg.css that is in turn applied to the Dashboard.

You can control what styles get served to Gutenberg by editing the gutenberg.scss in the /assets/ folder. All blocks by default are included, as well as the base.scss, all utilities, buttons, and the core block.scss overrides. We also apply the theme's fonts (if you're using Gutenberg) and we apply the tinymce() mixin to the block editor here.

Watch with Livereload

When you're developing, you'll want Gulp to listen for when you save any SASS or JS files, so that the browser reloads the styles, or refreshes the page in the event that you altered JS. This will also refresh the page is PHP files are saved.

gulp watch (or just gulp)

  • If a scss file is changed, it will compile your scss into css.
  • If a JS file is changed, it will jshint your modules, then compile everything in js/vendor/ with app.init.js with everything in js/modules into app.js.
  • If a php file is changed, it will reload the page. Livereload listens for changed .php files in theme root, /templates/, and /partials/.

Make sure you have the Chrome extension Livereload installed. You need to run gulp first, then click the Livereload icon in your browser to enable live reloading.

Gutenberg

WPX is Gutenberg-ready. WPX relies on the ACF Pro.

Registering Custom Blocks

  1. In includes/blocks.php copy the Custom Block registration to register your own. Change the parameters in the acf_register_block function as you see fit. Read about them in the ACF documentation. The parameter for render_template is the file you will create in step 3.

  2. You can also reference JS to run in Gutenberg if necessary when setting up the block. See the example block for reference.

  3. Make sure the name of the block you registered matches the name in the allowed_block_types code. Your name will always be 'acf/name-of-block,' where 'name-of-block' is the name parameter entered into step 1.

  4. Under templates\blocks\ create a new file for your block and render the output of the block.

  5. You'll finally need to make a corresponding ACF field in the Dashboard using the new Block field type to register your meta for the block, and assign the field to your new block.

Your custom blocks should now appear in Gutenberg.

Block Styles in the Dashboard

WPX applies a stylesheet called gutenberg.css to the Gutenberg editor (or gutenberg.min.css if WP_ENVIRONMENT_TYPE is set to development). This is a compilation of any sass files included in /styles/gutenberg/gutenberg.scss. You will notice that there is a sample custom-block.scss in that directory, which contains the following:

/**
 * custom block
 */
.my-custom-block {
}

We load a limited subset of the styles defined in the theme into Gutenberg to avoid having to repeat styles. This is so that only the styles related to the block are introduced to the Gutenberg editor. Gutenberg styles blocks by default in the editor already, so we only need to add styles we'd like to appear in the editor to make the block look similar to its end result on the front end. Thus the goal with applying only the relevant block styles is to create a low-fidelity presentation of the block within the Gutenberg editor itself.

Theme JSON

I have set theme.json to allow maximum control over blocks (enabling all settings that were set to false by default).

The exception is the "lightbox" setting, as it has a bug in 6.6+ where the global setting doesn't do anything, and you have to enable the lightbox on every block you want to use it with.

If a setting does not appear in the theme.json, that's because it's active by default.

Many core block settings have been extended to custom blocks, and you can conditionally enable/disable these properties when registering the blocks.

Global Vars Defined by WP

The theme.json defines global vars that are used throughout the theme, such as context width, colors, and font sizes. You can find all of these by inspecting the page at the bottom for wp-preset-XXX.

  • We do not allow the end user to change font families as this is defined by the design and mediated in the custom CSS.

Plugin List

In WPX I typically start with the following plugins in every install:

  • Admin Columns
  • Admin Menu Editor
  • Advanced Custom Fields Pro
  • ACF: Advanced Taxonomy Selector
  • ACF: Nav Menu Field
  • ACF: Post Type Selector
  • Better Search Replace
  • CMS Tree Page View
  • Custom Taxonomy Order
  • FakerPress
  • Filebird Lite
  • Nested Pages
  • Ninja Forms
  • Redirection
  • Relevanssi
  • Reusable Blocks Extended
  • Safe SVG
  • Simple Page Ordering
  • User Switching
  • WP Extend Utilities*
  • YIKES Simple Taxonomy Ordering
  • Yoast Duplicate Post
  • Yoast SEO
  • This is required as it contains the utility functions for WPX.

About

The WPX Theme is a starter theme for WordPress, developed by DQuinn.net.

Resources

License

Stars

Watchers

Forks

Packages

No packages published