Conditional field - Creating a 'Flexible Content Field' (like 'Advanced Custom Fields' wordpress plugin)

THE QUESTION

I would like to create a Flexible Content Field** by using Grav’s ‘list fieldtype’ in combination with a ‘select fieldtype’ and the ‘conditional fieldtype’.

** Define groups of sub fields (layouts) and add, edit, and re-order them to create highly customized content!

THE STORY

In an article template the user can use a repeater (aka list fieldtype) to select the type of field(s) he/she would like to add. The selection (aka select fieldtype) the user makes determines (aka conditional fieldtype) wich new fields are being added.

THE BENEFIT

These added fields could be used to render pre-designed components… from adding another ‘tinymce editor’ to adding a slideshow, a fancy testimonial, a responsive youtube video and so on.

So the user who is writing his article could add pre-designed components into the content mix.

THE PROBLEM

I think the example code i wrote for this feature is in the right direction, but:

  • Setting the condition to the select field value doesn’t seem to be working (as this is inside a list - it’s allsow an array).
  • Next there is showing the needed field which the user selected. I guess this will require added JS to the admin plugin and some hooks.

I really would like to get this feature to work as i could use it in all Grav projects.
But for the moment i’m stuck and could use some help, or direction.

THE CODE

This would go inside a template.yaml file (in this example: article.yaml file).

header.field.selection:
  type: list
  style: vertical
  label: Repeater/list with conditional field selection
  fields:
    .select: # this select sets the boolean for the conditional fields
      type: select
      size: long
      classes: fancy
      label: Select the type of field you would like to add
      options:
        tinymce: Textarea
        slideshow: Slideshow
        testimonial: Testimonial
        video: Video
    .tinymce:
      type: conditional # if Textarea was selected render the tinymce fields
      condition: "header.field.selection.select is same as('tinymce') ? 'true' : 'false'" # header.field.selection is an array
      fields: 
        .fieldName:
          type: text
          label: Tinymce
    .slideshow:
      type: conditional # if slideshow was selected render the slideshow fields
      condition: "header.field.selection.select is same as('slideshow') ? 'true' : 'false'"
      fields: 
        .fieldName2:
          type: text
          label: Slideshow
    .testimonial:
      type: conditional # if testimonial was selected render the testimonial fields
      condition: "header.field.selection.select is same as('testimonial') ? 'true' : 'false'"
      fields: 
        .fieldName3:
          type: text
          label: Testimonial
    .video:
      type: conditional # if video was selected render the video fields
      condition: "header.field.selection.select is same as('video') ? 'true' : 'false'"
      fields: 
        .fieldName4:
          type: text
          label: Video

WORKING 'FLEXIBLE CONTENT FIELD’

WHAT YOU NEED TO DO TO IMPLEMENT THIS:

  1. Extend your theme to add/load some css & js into the admin
  2. Add the css & js file in the theme folder
  3. Add the <templateName>.yaml file to display the Flexible Content Field in the admin
  4. Loop through the ‘Flexible Content Field’ inside your <templateName>.html.twig file to display the data.

MORE DETAILED EXPLANATION:

#1. Extend your theme
Inside your theme folder you have a <themename>.php file. Add the code:
(In this example my theme name is ‘Base’, change this name to your theme name.)

<?php
namespace Grav\Theme;

use Grav\Common\Theme;

class Base extends Theme
{
    // Add assets to the Admin
    public function onAssetsInitialized() {
        if ($this->isAdmin()) {

            // add JS
            $this->grav['assets']->addJs('theme://js/admin.js');
            // add CSS
            $this->grav['assets']->addCss('theme://css/admin.css');

        }
    }
}

#2. Add css & js files.
Your theme has a css folder and a js folder.

  • In the css folder create a file named ‘admin.css’.
  • In the js folder create a file named ‘admin.js’

Add this CSS:

/* Hide form-fieldset by default */
ul.field-selection > li > .form-fieldset {
    display: none;
}

/* Show form-fieldset that is selected */
ul.field-selection > li > .form-fieldset.field-selection--show {
    display: block;
}

Add this JS:

var flexibleContentField = (function (w, d, $, undefined) {

    'use strict';

    var s = {
            selectors: {
                theRepeater: '.field-selection',
                theSelect: '.field-selection__select'
            }
        },
        els = {},
        init = function () {

            // define elements
            els.theRepeater = $(s.selectors.theRepeater);

            // no elements
            if (!els.theRepeater.length) { return; }

            // theloop
            els.theRepeater.each(function() {
                // the bind
                $(this).on('change', checkTarget);

                // adjust existing selections
                var theRepeaterItems = $(this).children('li');
                theRepeaterItems.each(function(index, element) {
                    var theRepeaterItem = $(element),
                        theSelect = theRepeaterItem.find(s.selectors.theSelect);

                    showHide(theRepeaterItem, theSelect);
                });
            });
            
        },
        checkTarget = function(event) {

            // The vars
            var theRepeaterItem = $(event.target).closest('li'),
                theSelect = event.target;

            // Target is Select?
            theSelect.nodeName.toLowerCase() === 'select' ? showHide(theRepeaterItem, theSelect) : '';
        },
        showHide = function (theRepeaterItem, theSelect) {

            // the vars
            var theRepeaterItem = $(theRepeaterItem),
                theSelect = $(theSelect),
                theSelectParent = theSelect.closest('.form-field'),

                theValue = theSelect.val(),
                theRefererPrefix = 'field-selection__',
                theReferer = theRefererPrefix + theValue,

                theSelectedFields = theRepeaterItem.find('input[type="hidden"][value="'+theReferer+'"]'),
                theFieldToShow = theSelectedFields.closest('.form-fieldset');

            // Exeption - default selected
            if (theValue === 'default') { return; }

            // show the selected field
            theFieldToShow.addClass('field-selection--show');

            // hide the select
            theSelectParent.css('display','none');
        }

    return {
        init: init
    };

}(window, window.document, window.jQuery));

// theCall - on window loaded
(function (w, d, undefined) {

    "use strict";

    var raf = requestAnimationFrame || mozRequestAnimationFrame || webkitRequestAnimationFrame || msRequestAnimationFrame,
        init = function () { w.flexibleContentField.init(); };

    // when all is loaded
    raf ? raf(function () { w.setTimeout(init, 0); }) : w.addEventListener('load', init);

}(window, window.document));

#3. Define the fields

Define the fields for your ‘Flexible Content Field’ inside a <templateName>.yaml file.
As example, you could need this Flexible Content Field inside an article.html.twig template, so the yaml file for this template would be article.yaml.

Inside your <templateName>.yaml file add this code to display the Flexible Content Field.
As example this Flexible Content Field code is used to let a user choose between a tinyMCE, a slideshow, a testimonial and a video.

Where is states # Define your grouped field here..., thats where you define the fields you need for your pre-designed components.

    # - Flexible Content Field -
    #
    # The Flexible Content Field is used to let a user
    # define groups of sub fields (layouts) that he/she can add, edit,
    # and re-order to create highly customized content.

    fieldSeletion:
      type: fieldset
      title: Field selection
      text: Flexible Content Fields
      icon: puzzle-piece
      collapsed: false
      collapsible: false
      fields:

        # Flexible Content Field - Repeater
        #
        # requirements:
        # - classes: field-selection
        header.fieldSelection:
          type: list
          style: vertical
          label: Repeater/list with conditional field selection
          classes: field-selection
          fields:

            # Flexible Content Field - Selector
            # 
            # requirements:
            # - type: select
            # - classes: field-selection__select
            # - the 'keys' of the options are the 'field option' names.
            # - 'default' option is required
            .select:
              type: select
              size: long
              classes: field-selection__select
              label: Select the type of field you would like to add
              default: 'default'
              options:
                default: Maak een keuze
                tinymce: Text editor
                slideshow: Slideshow
                testimonial: Testimonial
                video: Video

            # Field Option
            #
            # requirements:
            # - type: fieldset
            .tinymce:
              type: fieldset
              title: Text editor
              collapsed: true
              collapsible: true
              fields:

                # Field Option Referer
                #
                # requirements:
                # - type: hidden
                # - default: field-selection__<Field Option>
                referer:
                  type: hidden
                  default: field-selection__tinymce

                # Fields
                #
                # Define your grouped field here...
                .tinymce.title:
                  type: text
                  label: title

            # Field Option
            .slideshow:
              type: fieldset
              title: Slideshow
              collapsed: true
              collapsible: true
              fields:

                # Field Option Referer
                referer:
                  type: hidden
                  default: field-selection__slideshow

                # Fields
                .slideshow.title:
                  type: text
                  label: title

            # Field Option
            .testimonial:
              type: fieldset
              title: Testimonial
              collapsed: true
              collapsible: true
              fields:

                # Field Option Referer
                referer:
                  type: hidden
                  default: field-selection__testimonial

                # Fields
                .testimonial.title:
                  type: text
                  label: title

            # Field Option
            .video:
              type: fieldset
              title: Video
              collapsed: true
              collapsible: true
              fields:

                # Field Option Referer
                referer:
                  type: hidden
                  default: field-selection__video

                # Fields
                .video.title:
                  type: text
                  label: title

This is what the ‘Flexible Content Field’ looks like in the admin:


When the user add an item:

When the user selected a component:

The user can add/remove/replace his selected components:

#4. The Markdown file

This is an example output in the markdown file.
You can loop throught the fieldSelection and use the select to determine which type of component you have to render, and in which order.

In this example the user whats to use:

  • a slideshow firlst
  • then a video
  • and finally a text editor
---
fieldSelection:
    -
        select: slideshow
        tinymce:
            title: ''
        slideshow:
            title: 'Example user input'
        testimonial:
            title: ''
        video:
            title: ''
    -
        select: video
        tinymce:
            title: ''
        slideshow:
            title: ''
        testimonial:
            title: ''
        video:
            title: 'Example user input'
    -
        select: tinymce
        tinymce:
            title: 'Example user input'
        slideshow:
            title: ''
        testimonial:
            title: ''
        video:
            title: ''
---

IF YOU MANAGE TO IMPROVE ON THIS CODE, AND/OR MAKE A PLUGIN OUT OF IT. PLEASE SHARE :slight_smile:

4 Likes

nice work! Would be awesome to have a version of this in the core Form plugin? Is this something you would be interested doing as a Pull Request on Form repo? https://github.com/getgrav/grav-plugin-form

2 Likes

@rhuk. Sure :slight_smile:

Who can i contact if i have questions?

Hi @Genenenenaam, one place I’d suggest is the Grav Slack room https://getgrav.org/slack

BTW, this flexible content field looks amazing!

Yes, this is an amazing idea!! It really adds a ton of flexibility and power to Grav, while making it even easier for end users (I’ve used ACF Flexible Content to build some huge WP sites).

Should there be an issue for this added to the Grav Form Plugin so there is a more formal record of this information above and a record of the desire to add this functionality to the plugin?

Hey, this looks good! Does someone know if it has been added to the forms plugin in some way?