Using Fred: What to do when duplicating elements but Bootstrap requires an ID in the markup

Although I’m using Bootstrap as an example, this could apply to any library/framework or markup where an id instead of just a class is required.

Take the Bootstrap collapse component as an example:

It works by applying an id to the div that should be collapsed/expanded and then having a button or link targeting that id to trigger it. This is all well and good if you have static markup where you can make sure all the ids on the page are unique (we all know that there should not be duplicate ids in an HTML document).

Now consider a Fred dropzone where you want to drop in multiple elements that can all be collapsed and expanded. We need to find a way to create a unique id for each element that’s dropped into the dropzone.
The most simple way would be to create a setting for the element such as:

{
   "settings": [
        {
            "name": "id_name",
            "label": "ID Name",
            "type": "text",
            "value": ""
        }
    ]
}

And then using the Bootstrap example markup, you could add Twig placeholders at the appropriate places:

<p>
  <button class="btn btn-primary" type="button" data-toggle="collapse" data-target="#{{ id_name }}" aria-expanded="false" aria-controls="{{ id_name }}">
    Button with data-target
  </button>
</p>
<div class="collapse" id="{{ id_name }}">
  <div class="card card-body">
    Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident.
  </div>
</div>

This works, BUT it requires the end user to first drop the element in the dropzone, then open the element settings sidebar and enter an arbitrary unique value as the id name.
Unfortunately this is far from user-friendly.


The alternative I’ve used is writing some JavaScript that is triggered to run either on page load or when an element is dropped into the dropzone using the event.detail.fredEl event object. There are some things to look out for though which I’ll detail below.

Firstly, remove the id, the data-target and the aria-controls within the element markup like this:

<p>
  <button class="btn btn-primary" type="button" data-toggle="collapse" data-target="#" aria-expanded="false" aria-controls="">
    Button with data-target
  </button>
</p>
<div class="collapse" id="">
  <div class="card card-body" data-fred-name="main-text" data-fred-rte="true">
    Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident.
  </div>
</div>

You may be tempted to put a generic value in the collapse div id but it’s important you don’t (it will prevent TinyMCE from working if you want a rich text editor - I’ll explain more at the end). The opening tag of the div in the example should be <div class="collapse" id="">

As you can see I’ve also added data-fred-name="main-text" and data-fred-rte="true" to the example to add editing capability.

Next, add a JavaScript function to your page that is set to run once the page has loaded.
What it will do is examine the list of elements within the dropzone. (The dropzone element is considered the parent).
It will then loop through the elements and add an incremented value to each id tag.

Here’s an example but of course you may need to edit it to match your markup:

function indexElementsInDropzone(myDropzoneId) {
    var nodes = myDropzoneId.children;
    Object.keys(nodes).forEach(function (i) {
        var droppedElement = nodes[i];
        var button = droppedElement.querySelector('.btn');
        var collapse = droppedElement.querySelector('.collapse');
        button.dataset.target = '#collapseDiv'+i;
        button.setAttribute('aria-controls','collapseDiv'+i);
        collapse.setAttribute('id','collapseDiv'+i);
    });
}

So this should be set to trigger whenever the page has loaded or when a new element has dropped and it will loop through and set the ids and data-targets.

Note for TinyMCE RTE

If the id in the markup is NOT empty and you’re trying to use the TinyMCE rich text editor, the editor will only load for the first element in the dropzone and none of the others. Make sure it’s empty and the rich text editor will load for all of the elements.

Hope some people find this helpful.
I’d be interested to see any other methods people used to overcome this kind of obstacle!
Cheers!

4 Likes

To make it slightly less hairy, In the latest commit I tried generating unique ID for an element, that can be used in a twig as {{ id }}. It may need some more testing, but my initial check seemed to be fine. Hope it helps.

4 Likes

That’s brilliant! Thanks @theboxer
I haven’t tested it yet but will try to soon. Cheers!

1 Like