Jump to content
Jonathan Lahijani

There's no such thing as a 'perfect' page builder

Recommended Posts

I've probably thought up a dozen different approaches to creating an ideal page builder in ProcessWire but each of them have their pros and cons.  It's very difficult to strike a perfect balance for something that suits both developers and editors.  There are so many (!) other variables to consider.  This isn't just a ProcessWire dilemma, but just the nature of page building itself.  There are trade-offs and complexity must live somewhere.

In an ideal world, you'd use RepeaterMatrix in a classic way, but if you run that thought experiment, here's what happens:

  1. I'll make matrix-types for all the sections in a particular page.  I'll write the templates for those matrix-types, so all an editor has to do is fill in the fields and everything just falls into place.  I'll drive off into the sunset and be happy.
  2. Well, turns out there are some changes that need to be done.  MatrixType X needs another field.  I'll add that field and update the template.
  3. Well now we need another matrix-type, but it's basically identical to MatrixType X except the columns are flipped.  Does this warrant a new matrix-type or a toggle option of some sort?
  4. ...
  5. You start another new project and it needs a page builder like the first site.  You realize there's going to be some heavy re-use of matrix-types from the first site, so you now think... hmm maybe I should somehow modularize this approach where there is a shared matrix-type library. (I've done this)
  6. ...
  7. Many of the matrix-types between site 1 and site 2 are similar, but with slight differences... some have different css class names, some have different field names.  Now this shared matrix-type library has a bunch of matrix-types but you feel unsatisfied because you think there is a better way to refactor this.
  8. The ideas in your head about how to refactor end up going against the grain of the classic page builder way / repeater matrix approach and now you're frustrated.

I've gone through this thought process a bunch of times.  I've researched basically every single page builder out there.  It's like trying to solve a puzzle without a definitive end result.

One thing that happened is reactive frameworks gave rise to the current generation of page builders (think WP Block Editor / Gutenberg, SquareSpace, and few dozen others).  This left the classic page builder approach behind (RepeaterMatrix, ACF Flexible Content).  But I like the classic approach because it gives more programmatic control and it's the way ProcessWire works... and editors can't really mess things up since it's a more controlled experience.  I feel like there hasn't been experimentation and development for the classic approach since the world has "moved on" to the newer generation approach.

I think RepeaterMatrix is good, but maybe it can benefit from some experimentation of some sort.

  • Like 12

Share this post


Link to post
Share on other sites

Hi @Jonathan Lahijani

Thank you for your research into this topic. I agree with you that RepeaterMatrix is a good and probably the best solution for page-building, and should be developed even further in the future.

Share this post


Link to post
Share on other sites

This is a braindump of experiences and considerations that a matrix-based page builder should balance.  I'll give more thoughts on this later:

heading hierarchy
source order
div nesting
section patterns

general fields
general fields "2"..."n"
  ex: image + image_2
  ex: list_items + list_items_2 (for example, a section w/ description list + tabs)
fields mapping to components
  ex: title      => heading
  ex: image      => image
  ex: menu_items => subnav items, nav items
  ex: list_items => description list items, tab items, accordion items, slider items, slideshow items, grid items
what to do if field is empty?
  hide surrounding div structure?

list_items field selectively showing/hiding appropriate fields depending on component type
  ex: accordion items => title + body only
  ex: slideshow items => image only
  (note: currently a bug with this using matrix template overrides; utilizing hack workaround instead atm)


item templates for use in repeatable components
when to use hannacode vs. component

section switching (in an easy/quick way)
section options (mystique)
css framework components
3rd party components
component options
css framework flexibility (use setting() to store classes)

naming approach
  ex: Grid vs. List of People

user friend matrix-type registration
visual selector w/ placeholder content
instant preview (ideally prodrafts)

direct vs. dynamic content
saved / reusable sections
multilingual

weird things like
  - wrapping links (messes up predictable div nesting?)
  - image backgrounds using inline css
  - single line vs multi-line text
    ex: sometimes a heading should be multi-line instead of having 2 separate fields



ability to programmatically include a section template using $files->include


css grid vs. flex grid
  - flex gap?
    https://coryrylan.com/blog/css-gap-space-with-flexbox

A section is literally an HTML <section> (or it can be a div if specified).  Imagine a section that has 2 components in the following source order: image + text.

A section can be styled independently and map to UIkit section classes: https://getuikit.com/docs/section

You could call this section "Image + Text" in repeatermatrix for example and give it the UIkit markup to put the image on the left and the text on the right using the appropriate css classes, and wrap it in a container.

This is very easy to understand for content editors.  Very important.

Now you what if you want flexibility such as having a small container instead of a regular sized container?

The "wrong" way to do this would be to make a new matrix-type called "Image + Text (Small Container)".  Instead you think of maybe having additional dropdowns to manage such things in the matrix block, but if you think that through and build many sections, it will be overly complex and feel wrong.

The "correct" way to do this would be to have various section patterns that modify the div structure and component options of that matrix-type.  So for example, if you wanted a small container + animations + rounded corners on the image, you could have a section pattern (would have to be created by the developer) that the user selects that applies all that collectively.  Maybe each section has 2-5 section patterns which seems reasonable.

With this section pattern approach, you could theoretically have the same section display the image on its own row and the text below.  But what if you want each component to be in its own different sized container?  If you think this through this requires a different div nesting structure to keep things super flexible:

<div class="<?=setting('div-1-class')?>" <?=setting('div-1-attr')?>><!-- container -->
  <div class="<?=setting('div-1-div-1-class')?>" <?=setting('div-1-div-1-attr')?>><!-- container inner -->
    <div class="<?=setting('div-1-div-1-div-1-class')?>" <?=setting('div-1-div-1-div-1-attr')?>><!-- grid -->
      <div class="<?=setting('div-1-div-1-div-1-div-1-class')?>" <?=setting('div-1-div-1-div-1-div-1-attr')?>><!-- col -->
        <div class="<?=setting('div-1-div-1-div-1-div-1-div-1-class')?>" <?=setting('div-1-div-1-div-1-div-1-div-1-attr')?>><!-- component wrapper -->
          <?=files()->include('./fields/blocks/image.php', [
            'page'=>$page,
            'block_options'=>setting('image-1-block-options')
          ])?>
        </div>
      </div>
      <div class="<?=setting('div-1-div-1-div-1-div-2-class')?>" <?=setting('div-1-div-1-div-1-div-2-attr')?>>
        <div class="<?=setting('div-1-div-1-div-1-div-2-div-1-class')?>" <?=setting('div-1-div-1-div-1-div-2-div-1-attr')?>>
          <?=files()->include('./fields/blocks/text.php', [
            'page'=>$page,
            'block_options'=>setting('text-1-block-options')
          ])?>
        </div>
      </div>
    </div>
  </div>
</div>

vs.

<div class="<?=setting('div-1-class')?>" <?=setting('div-1-attr')?>><!-- container 1 -->
  <div class="<?=setting('div-1-div-1-class')?>" <?=setting('div-1-div-1-attr')?>><!-- container inner 1 -->
    <div class="<?=setting('div-1-div-1-div-1-class')?>" <?=setting('div-1-div-1-div-1-attr')?>><!-- grid 1 -->
      <div class="<?=setting('div-1-div-1-div-1-div-1-class')?>" <?=setting('div-1-div-1-div-1-div-1-attr')?>><!-- col 1 -->
        <div class="<?=setting('div-1-div-1-div-1-div-1-div-1-class')?>" <?=setting('div-1-div-1-div-1-div-1-div-1-attr')?>><!-- component wrapper 1 -->
          <?=files()->include('./fields/blocks/image.php', [
            'page'=>$page,
            'block_options'=>setting('image-1-block-options')
          ])?>
        </div>
      </div>
    </div>
  </div>
</div>
<div class="<?=setting('div-2-class')?>" <?=setting('div-2-attr')?>><!-- container 2 -->
  <div class="<?=setting('div-2-div-1-class')?>" <?=setting('div-2-div-1-attr')?>><!-- container inner 2 -->
    <div class="<?=setting('div-2-div-1-div-1-class')?>" <?=setting('div-2-div-1-div-1-attr')?>><!-- grid 2 -->
      <div class="<?=setting('div-2-div-1-div-1-div-1-class')?>" <?=setting('div-2-div-1-div-1-div-1-attr')?>><!-- col 2 -->
        <div class="<?=setting('div-2-div-1-div-1-div-1-div-1-class')?>" <?=setting('div-2-div-1-div-1-div-1-div-1-attr')?>><!-- component wrapper 2 -->
          <?=files()->include('./fields/blocks/text.php', [
            'page'=>$page,
            'block_options'=>setting('text-1-block-options')
          ])?>
        </div>
      </div>
    </div>
  </div>
</div>

You can represent those 2-component div structures like this:

(1 2)
(1) (2)

If you have 3 components:

(1 2 3)
(1 2) (3)
(1) (2 3)
(1) (2) (3)

4 components:

(1 2 3 4)
(1 2 3) (4)
(1 2) (3 4)
(1) (2 3 4)
(1) (2) (3 4)
(1) (2 3) (4)
(1 2) (3) (4)
(1) (2) (3) (4)

5 components:

(1 2 3 4 5)
(1) (2 3 4 5)
(1 2) (3 4 5)
(1 2 3) (4 5)
(1 2 3 4) (5)
(1 2 3) (4) (5)
(1 2) (3) (4) (5)
(1 2) (3 4) (5)
(1) (2 3 4) (5)
(1) (2 3) (4 5)
(1) (2) (3 4 5)
(1) (2 3) (4) (5)
(1) (2) (3) (4 5)
(1 2) (3) (4 5)
(1) (2) (3) (4) (5)

(it can actually get more complex than that, but no need to go there)

A CSS Grid approach probably removes the complexity required for div nesting (not 100% sure since I haven't used CSS-grid in a long time).  UIkit would ideally have a built in solution in version 4.

Maybe I'm overthinking this div nesting part.

Perhaps the section pattern (which comes in via a json settings file and stores its values in setting()) directly specifies which div nesting layout should be used.  Hmm.

  • Like 3

Share this post


Link to post
Share on other sites

Interesting thoughts indeed 🙂

I have not yet actually used CSS Grid in a real life project yet, because some mobile browsers have just recently started to support them (https://caniuse.com/?search=grid – Date Relative tab) but probably sometime next year it will be"almost safe" to use it even without a polyfill.

I think CSS Grid is for the "main layout" of a page and not for the layout of individual components. Sure, CSS Grid could be used for individual components as well (why not?) but it looks like it would be an overkill for that purpose. However, flexbox is still great for individual components. The grid systems in CSS frameworks are pretty useful, but in the light of CSS Grid, in the future I will probably only use them for individual components, and CSS Grids for the layout of the page.

Being able to freely and "responsively" move blocks around the page based on viewport size is the real power of CSS Grid, I think:

This "rearranging-freely power " is why ditching the classic layout grid systems for page layouts is the future 🙂  Not to mention that the various framework grid systems require various levels of wrappers to support their specific grids, while the CSS Grid is standardized, of course.

  • Like 2

Share this post


Link to post
Share on other sites

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...