Making Bootstrap work with the WordPress Block Editor

tl;dr: You need to load Bootstrap’s CSS in the 'wp_enqueue_scripts' hook with a priority lower than 10.

Where do I begin with this one? First, some foundational details: I’m old school. I’ve been a professional web developer/designer since 1996. I’ve embraced new technologies as they come along, but I tend to bristle at things that are either a) not an open standard or b) need to be compiled.

I refused to use Flash. In the end I was proved right, as open web technologies supplanted it. Flash is dead, and the open web is not. I still refuse to use CSS preprocessors for a similar reason. They’re a non-standard workaround to limitations in the current standard. They fragment the landscape instead of pushing the standard forward. And as CSS variables have gained wide browser support, preprocessors are beginning to look as pointless as Flash.

Now the thing I hate is npm — or any similar tech that requires me to run shell scripts and compile anything. I’ve long since embraced server-side scripting, using open source libraries like jQuery, and even using a pre-built CMS (or CMS-ish system) like WordPress. (Yes, for many years I rolled my own CMSes.) The bottom line for me is, if it’s code I can download simply and treat as a “black box,” fine. But if it’s something I need to get my hands dirty in, I shouldn’t need anything to work with it but a text editor.

All of that to say, I am having a bit of a time with where things are going these days with Gutenberg, a.k.a. the WordPress Block Editor. And I haven’t exactly been quiet about it here.

This week I needed to extend the Block Editor by creating a carousel/slideshow block. Yep, I’ve rolled my own with these for many years as well, but this time around I decided I wanted to work with something pre-built, so I settled on the one in Bootstrap. I don’t really need all of Bootstrap (although I suspect over time I will need more of it), but customizing it requires using npm, so I decided to go ahead and include the entire pre-compiled package in my theme.

That’s when the problems began.

Oh, the carousel works great! But I spent a bunch of time yesterday trying to figure out why the custom background color and fonts defined in my theme.json file were being ignored… especially since they’re right there in the inline CSS inside the <head> of every page.

Don’t even get me started on inline CSS, or the way so many people in the industry seem to worship PageSpeed Insights. Once again we’re skating to where the puck is, instead of where it’s going, and to stretch the analogy to the limit, we’re melting all of the ice in front of it too. This is not the way to move web development forward intelligently.

Ah-hem.

Eventually I worked out what’s happening. WordPress, historically, was designed to allow you to define dependencies, so you could load CSS and JavaScript in a logical manner. Gutenberg/Block Editor throws all of this out the window with this inline CSS. Certain “critical” inline CSS for the Block Editor gets loaded immediately regardless of the dependencies you put into wp_enqueue_style(). The result being, styles defined (indirectly, in a way more convoluted way than vanilla CSS) in my theme.json file were appearing before the Bootstrap CSS file was loaded. And since I’m using the compiled Bootstrap instead of a custom npm build, there’s a bunch of “general” CSS it’s throwing in, including color and font definitions on the <body> tag that override Gutenberg’s earlier inline CSS.

Fortunately, someone else had the same problem and figured out a solution. But since, in 2022, spammers overrun any forum thread that’s left open too long, the thread was already locked and I couldn’t express my appreciation to the poster who shared it. So I’m writing this blog post instead.

The trick is to load any third-party CSS that you might need to override using a separate function called on the 'wp_enqueue_scripts' hook, with a low priority number (less than 10, since that’s the magic default).

Here’s a generalized version of the code I’m using:

function my_enqueue_scripts_early() {

    $ver = '1.0.0'; // Your theme version; could also be a class property, constant, global variable, etc.

    wp_enqueue_style('bootstrap-style', get_theme_file_uri('vendors/bootstrap/bootstrap.min.css'), array(), $ver);
    wp_enqueue_script('bootstrap', get_theme_file_uri('vendors/bootstrap/bootstrap.bundle.min.js'), array('jquery'), $ver);

}
add_action('wp_enqueue_scripts', 'my_enqueue_scripts_early', 8);

WordPress development in the Gutenberg era: Threading the needle of Block Theme development

As described in several of my recent posts here, I have been working for the past month or so on building my first “all-in” Block Theme for WordPress.

After nearly 4 years of adamantly resisting “Gutenberg” and the new Block Editor revolution — not because I disliked the block concept, but because I disagreed philosophically with the core team’s approach (to what constitutes a block, which types of blocks are important, and which technologies are used to manage the UI of the editing screen) — I am finally accepting that if I am to continue making a living primarily as a WordPress developer, I need to give up on my Classic Editor, Advanced Custom Fields “Flexible Content” approach, and embrace that the Block Editor is now The Way.

One of numerous challenges I’ve faced in this process (on top of the learning curve of a completely unfamiliar method of constructing themes, the dearth of adequate and up-to-date documentation, and the core team’s willingness to allow very unfinished versions of functionality to roll out in public WordPress releases) is figuring out the best way to approach some of the more complex design structures I am used to dealing with via ACF’s Flexible Content fields.

My biggest hurdle is recognizing that what I think of as a “block” is not what the WordPress core team thinks of as a “block”

Here I will admit this is a shortcoming of my own approach. I have been “opinionated” in my development approach (well, about everything, really), and created large-scale and complex “blocks” that, in Gutenberg/Block Editor terms, would really be “groups” or “block patterns,” not blocks. Gutenberg blocks are more granular.

Gutenberg blocks are also static, in that they generally do not interact with database content, or if they do, it is in very limited “bloggy” ways that don’t align with my use of WordPress as a general-purpose CMS.

So I’ve found myself falling back on ACF. I like its server-side approach. I’m more accustomed to dealing with PHP and MySQL. I use JavaScript (mainly jQuery) a fair bit for front-end interactive elements, but I don’t build complex functionality in JavaScript and I avoid AJAX if I can help it.

You can see I’m destined for a strained relationship with the Block Editor.

A concrete example: “Tiles”

One of the most idiosyncratic elements of my old ACF approach is the block I call “Tiles.” It’s a way of creating a set of small blurbs to link to other pages/posts. There are numerous options for appearance: number of tiles per row, relationship between the image and text in a tile, etc. And there are also numerous options for the content source: a specific page or post (with the title, featured image and excerpt automatically pulled in), a dynamic feed from the blog or a specific category (likewise with the info pulled in automatically, except this one auto-updates when there’s a new post), or a completely custom-built tile, where you manually select the image and enter the text and link.

Here are screenshots of the ACF-based editing tools for my Tiles block.

I tried recreating this entire setup as a new ACF-based Block field group. It worked, to be sure, but the complex ACF editing layout really did not feel right in the new Block Editor interface, either in the sidebar (eek) or in the main content area. It felt like a cop-out.

Then I considered creating a block pattern. I knew this would lack some of the benefits of my ACF-based Tiles block, but one in particular: the option to dynamically pull in the details of another page/post, rather than manually entering the text. But as a starting point, I decided to recreate the “custom” tile type.

That, also, worked. But it was finicky and didn’t apply well in a lot of different places. So I realized that instead of creating a Tiles block pattern, I needed to create a Tile block pattern. Just one tile. Instead of a monolithic block that was really a group, users would insert each individual tile into whatever larger structure they want (e.g. columns).

The end result was a block pattern that looked like this (screenshot instead of live code because, well, you really don’t want to use this):

I was really proud of myself for using the new “lock” feature to force the elements to stay in a particular order, but to allow the user to remove elements they don’t need, such as the image, lead-in text, or CTA button.

Still… I didn’t like it. And it didn’t allow for dynamic sources.

Along the way I also finally came to grasp another fundamental limitation of the Block Editor and Block Patterns. Since all of the parameters of the blocks are stored as HTML comments right in the post content, you (the theme developer) can’t update the design of a block pattern once it’s inserted into a page/post. The Block Editor isn’t dynamically inserting content into a template when the page is rendered. It’s making a one-off copy of the template and storing it right along with the content at the point when the content is inserted into the page/post.

This seems, to me, to be a fundamental flaw in the entire Gutenberg/Block Editor approach. It’s bad enough that if I were building it myself, I’d have stopped right there and taken a completely different direction. Maybe there’s a long-term plan to address this limitation, but for now it appears to be here to stay. Which led me back to ACF.

Threading the needle

And that was when I had my insight into the true nature of the problem. It was the fact that what I think of as a “block” is larger-scale than what the Block Editor treats as a block.

So I went back to my Tiles ACF field group, stripped out the repeater functionality and created a Tile ACF field group. Now you’re building one tile at a time, it has all of the previous benefits of ACF’s dynamic integration of content with template functionality, it seems to correctly fit the definition of a “block” in the WordPress sense, and you can still have a flexible presentation of multiple tiles in a group… just using Block Editor “groups” to achieve that.

I still have more to learn and questions to answer (followed by more questions to ask and then answer), but I feel like this was a major step forward in finding a way to merge the benefits of ACF with the… inevitability, I guess, of the Block Editor.

Another obscure WordPress problem: setting document.domain for cross-site scripting iframes breaks Gravity Forms AJAX submissions

Whew… that title was almost as long as the variable name I’m about to throw out in a code example.

I spent well over an hour beating my head against the wall on this problem today before narrowing it down to a Gravity Forms issue. The scenario: I have a site that is loading iframes from a different subdomain. As is common in this situation, I wanted to be able to adjust the height of the iframe with JavaScript, to match the height of the page within the iframe and prevent internal scrollbars.

The solution to that problem is readily available on teh interwebz, with the addition of a bit of extra JavaScript to allow cross-site scripting: both the containing page and the contained page need to specify the same document.domain so browsers will let them talk to each other.

Not long after we put this in place, my client informed me that none of their AJAX-based Gravity Forms were working. The spinner would just spin indefinitely, even if (usually) the form actually did submit properly. It didn’t take me long to narrow the problem down to a JavaScript error pertaining to cross-site scripting. I found that AJAX and document.domain don’t mix. Or at least that seemed to be the issue.

But that’s where I hit a wall. No one else seemed to be describing the exact problem I was having. Most solutions involved adding a Access-Control-Allow-Origin header, but that didn’t do anything for me.

Eventually I realized that was because the problem wasn’t with the AJAX, per se. It was the fact that Gravity Forms adds its own hidden iframe where it works some secret mojo on AJAX submissions. And that iframe needed to have document.domain added to it, just like my site and the other subdomain I was loading in iframes did.

So the question then was, is there a Gravity Forms hook to modify its iframe output? Fortunately, the answer is yes.

The gform_ajax_iframe_content filter pretty much does what it says on the tin. Add a filter to insert the necessary JavaScript, and you’re good. The only thing I don’t get about this is the name given to its lone input parameter. I mean, really? (Actually… I do think I understand it, but I don’t understand it.)

Anyway… here’s what you need to make this work. Just replace example.com with the correct domain name. And if you’re running on a version of PHP before 5.3, you won’t be able to use an anonymous function. But you’re not running an old version of PHP, are you?

add_filter('gform_ajax_iframe_content', function($doctype_html_html_head_meta_charset_utf_8_head_body_class_gf_ajax_postback_form_string_body_html) {
  echo "<script>document.domain = 'example.com';</script>\n";
  return $doctype_html_html_head_meta_charset_utf_8_head_body_class_gf_ajax_postback_form_string_body_html;
});

Web standards: a Win-Win-Win situation

Today is the fourth annual “Blue Beanie Day,” a tradition established by the father of web standards, Jeffrey Zeldman.

What are web standards? Simply put, they’re awesome. But seriously… the goal of web standards is to establish a set of best practices for web designers and developers, and a set of open, shared languages and tools for building websites and displaying them in a consistent manner.

At the heart of the modern web standards movement are a set of three core languages: HTML5, for organizing and structuring content; CSS3, for designing the presentation of that content; and JavaScript, for providing rich interaction with that content.

HTML5, CSS3, and JavaScript are all open standards. The specifications are published for anyone to see; they’re open and evolving, for anyone to contribute to; and they’re freely available for anyone to build an application for rendering content delivered via these languages (in common parlance, a web browser, but we’re starting to see “web” content appearing in all sorts of applications for computers and mobile devices these days).

But why are these three web standards so great? Because they create a win-win-win situation:

A win for web designers/developers. By establishing a common set of tools that are open and free to anyone, web designers and developers can get started with no barriers to entry. Plus, by standardizing these tools, the same skills can be applied anywhere a website is being built. And as web browser makers adopt these standards, the last 15 years’ worth of browser-to-browser inconsistency will fade. Our job is made easier, we can get more done in less time, and, with powerful frameworks like jQuery built on top of these standards, this power to do more with less will grow exponentially.

A win for site owners. If you’re paying to build a website, you want to know you’re spending your money wisely. You want your investment to last, and you want to make sure everyone who wants to access your site, can. Web standards are the key to an accessible, reliable, “future-proof” website. Some Internet technologies may come and go; jumping on the latest trend may make your site seem “with it” today, but tomorrow it will be painfully dated… if it even works at all. But these three core web technologies will always be at the heart of the web. Plus, a site built with web standards will automatically be structured well for search engine listings, without the need for expensive and questionable SEO tactics.

A win for Internet users. Web content that is built and delivered with a diligent adherence to web standards will work reliably with any device, any software, that is used to access the Internet. Plus, no well-formed, standards-compliant HTML page ever crashed a web browser.

Web standards: Win-Win-Win.

TinyMCE and the relative URLs SNAFU

My CMS, cms34, uses a few third-party tools for certain complex features. One of the most useful is TinyMCE, a JavaScript/DOM-based drop-in WYSIWYG text editor. If that doesn’t mean anything to you, you probably want to stop here, but in case you’re a glutton for punishment, it is, in short, a way to produce HTML-formatted text with a word processor-like interface. Think Microsoft Word in a web browser, with the results being formatted for display on a web page. Slick.

Anyway, there’s been one nagging problem: When users paste in URLs for links, TinyMCE converts any on-site links into “relative URLs” — it strips out the domain name. This is not necessarily bad; in fact, for the most part I would want it to do that. But for some reason the nature of my CakePHP-based CMS seems to confound TinyMCE’s ability to properly determine the relative URL. And what’s worse, the CMS includes an enewsletter editor which has to have absolute URLs, but TinyMCE was converting them to relative URLs even if the user pasted in an absolute URL.

A little research led me to a handy explanation in the TinyMCE Wiki. Basically, if you want your URLs to always be absolute, make sure your TinyMCE configuration includes the following:

relative_urls : false,
remove_script_host : false,
document_base_url : “http://www.site.com/path1/”

Of course, you’ll want to change the value of document_base_url to be the actual base URL of your website. As it happens, my CMS has a global JavaScript file that creates a variable called baseUrl that I can use anywhere in JavaScript to substitute for the full base URL of the website. So, in my case, I set the value for document_base_url equal to baseUrl.

And, voilà, it seems to work!