IMPLEMENTED: Make the element IDs copy to clipboard on click

At times I have to use their IDs and am left with typing them instead by seeing and the Bricks ID structure is not a convenient one to remember so why not allow copying the ID on clicking a key that is maybe next to the ‘Rename ID’ pencil icon?

I am surprised even in the rename field, the existing ID doesn’t get auto typed to edit/copy.

Issue

12 Likes

+1 for both class and id copy to clipboard :slight_smile:

4 Likes

Also +1 for that.

@timmse what do you think about it, any chance that we can get a static ID text field somewhere above the word “container” or anywhere else to copy and paste the ID?

I’ve also mentioned in facebook that there is a UX problem, when giving an Element a custom ID via Advanced->CSS->ID there’s no chance that we can find out the ID of the element without removing that ID!

TIA for your answer! :wink:

Would really like to see this as well. Being able to copy a class name is pretty essential when using BEM to name your classes :slight_smile:

1 Like

Fully second this … have also been missing a copy-id function heavily!

1 Like

I would very much like to see this added. Or at least a small copy icon. I understand that clicking is already tied to opening the global class menu.

2 Likes

For those interested in a DIY method while we wait, I threw something together today. If you find any issues, let me know.

Add this to your functions.php file:

add_action( 'wp_enqueue_scripts', function() {
	if ( bricks_is_builder_main() ) {
		wp_enqueue_script( 'stu-element-copy-id', get_stylesheet_directory_uri() . '/stu-element-copy-id.js');
	}
} );

And put this in a file called stu-element-copy-id.js inside your child theme:

function stuElementCopyId() {
	const copyIcon = '<svg version="1.1" viewBox="0 0 30 30" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="bricks-svg"><path d="M9.5,5h11c0.276,0 0.5,-0.224 0.5,-0.5v-1c0,-1.378 -1.122,-2.5 -2.5,-2.5h-1.774c-0.353,-0.609 -1.007,-1 -1.726,-1c-0.718,0 -1.373,0.39 -1.726,1h-1.774c-1.378,0 -2.5,1.122 -2.5,2.5v1c0,0.276 0.224,0.5 0.5,0.5Zm14.766,-2h-1.766c-0.276,0 -0.5,0.224 -0.5,0.5v1c0,0.828 -0.672,1.5 -1.5,1.5h-11c-0.826,0 -1.5,-0.672 -1.5,-1.5v-1c0,-0.276 -0.224,-0.5 -0.5,-0.5h-1.765c-0.956,0 -1.735,0.781 -1.735,1.739v23.523c0,0.957 0.779,1.738 1.735,1.738h18.529c0.957,0 1.736,-0.781 1.736,-1.739v-23.522c0,-0.958 -0.778,-1.739 -1.734,-1.739Z" fill="currentColor"></path></svg>';

	const span = document.createElement('span');
	span.setAttribute('class', 'bricks-svg-wrapper stu-copy');
	span.setAttribute('data-balloon', 'Copy (Element ID)');
	span.setAttribute('data-balloon-pos', 'top-right');
	span.innerHTML = copyIcon;

	const style = document.createElement('style');
	style.innerHTML = `
		#bricks-panel-element-classes .active-class .actions .stu-copy {
			visibility: hidden;
		}
		#bricks-panel-element-classes .active-class:hover .actions .stu-copy {
			visibility: visible;
		}
	`;
	document.head.appendChild(style);

	const targetNode = document.getElementById('bricks-panel'),
		  config = {childList: true, subtree: true},
		  callback = (mutationList, observer) => {
			  for (const mutation of mutationList) {

				      // If changing element being edited
				  if (mutation.target.id == 'bricks-panel-element' ||
					  // If going from settings panel or element picker to editing element
					  mutation.addedNodes.length !== 0 && mutation.addedNodes[0].id === 'bricks-panel-element') {

					  const activeClass = document.querySelector('#bricks-panel-element-classes .active-class'),
							placeholder = activeClass.querySelector('input').getAttribute('placeholder'),
							dataId = document.getElementById('bricks-builder-iframe').contentWindow.document.querySelector(placeholder).getAttribute('data-id'),
							actionsContainer = activeClass.querySelector('div.actions');

					  actionsContainer.prepend(span);

					  const copy = actionsContainer.querySelector('span.stu-copy');

					  copy.addEventListener('click', function(e) {
						  e.stopPropagation();

						  // Comment this line and uncomment the next line if you'd  prefer getting the bricks set element id always
						  navigator.clipboard.writeText(placeholder);

						  // Uncomment this line if you want the bricks set element id
						  // navigator.clipboard.writeText(dataId);
					  });

				  }
			  }
		  };

	const observer = new MutationObserver(callback);

	observer.observe(targetNode, config);
}

document.addEventListener('DOMContentLoaded', function() {
	stuElementCopyId();
});
11 Likes

Thanks, i hope it will be added built-in in bricks builder
It is very helpful for custom css

1 Like

+1 really nice UX

please implement this soon

1 Like

Beautiful snippet. Thank you.

1 Like

Hey all,

There’s been a few bugs reported lately. Mainly it doesn’t work with custom ID’s. I can’t update my original reply because too much time has passed. I’ve included a link to a GitHub project that has a fix. I’ll include future fixes there as well:

4 Likes

Much thanks, mate :slight_smile:

@cmstew Thank you for you contribution. Much appreciated <3!

Here’s an updated version that handles active class as well:

And if you use a snippet plugin like Code Snippets, WPCodebox, etc. just plug this in a snippet:

Updated 3/24/23

<?php

add_action( 'wp_enqueue_scripts', function() {
	if ( bricks_is_builder_main() ) { ?>
        <script>
            document.addEventListener('DOMContentLoaded', () => {
                stuElementCopyElement();
            });

            function stuElementCopyElement() {
                const copyIdIcon = '<svg width="12" height="13" viewBox="0 0 12 13" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4 1.549a.5.5 0 0 1 .5.5v2h3v-2a.5.5 0 0 1 1 0v2H10a.5.5 0 0 1 0 1H8.5v3H10a.5.5 0 0 1 0 1H8.5v2a.5.5 0 1 1-1 0v-2h-3v2a.5.5 0 1 1-1 0v-2H2a.5.5 0 0 1 0-1h1.5v-3H2a.5.5 0 0 1 0-1h1.5v-2a.5.5 0 0 1 .5-.5Zm3.5 6.5v-3h-3v3h3Z" fill="currentColor"/></svg>';
                const copyClassIcon = '<svg width="12" height="13" viewBox="0 0 12 13" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M8.146 3.695a.5.5 0 0 1 .708 0l2.5 2.5a.5.5 0 0 1 0 .707l-2.5 2.5a.5.5 0 1 1-.708-.707l2.147-2.146-2.147-2.147a.5.5 0 0 1 0-.707Zm-4.292 0a.5.5 0 0 1 0 .707L1.707 6.55l2.147 2.146a.5.5 0 1 1-.708.707l-2.5-2.5a.5.5 0 0 1 0-.707l2.5-2.5a.5.5 0 0 1 .708 0ZM7.108 1.56a.5.5 0 0 1 .38.597l-2 9a.5.5 0 0 1-.976-.217l2-9a.5.5 0 0 1 .596-.38Z" fill="currentColor"/></svg>';

                // Destructuring for efficient future use
                const { querySelector } = document;
                const { contentWindow } = document.getElementById('bricks-builder-iframe');

                // Create copy id icon
                const spanId = document.createElement('span');
                spanId.setAttribute('class', 'bricks-svg-wrapper stu-copy-id');
                spanId.setAttribute('data-balloon', 'Copy (Element ID)');
                spanId.setAttribute('data-balloon-pos', 'top-right');
                spanId.innerHTML = copyIdIcon;

                // Create copy class icon
                const spanClass = document.createElement('span');
                spanClass.setAttribute('class', 'bricks-svg-wrapper stu-copy-class');
                spanClass.setAttribute('data-balloon', 'Copy (Element Class)');
                spanClass.setAttribute('data-balloon-pos', 'top-right');
                spanClass.innerHTML = copyClassIcon;
                
                // Create style tag and append to head
                document.head.appendChild(document.createElement('style')).innerHTML = `
                    #bricks-panel-element-classes .active-class .actions :is(.stu-copy-id, .stu-copy-class) {
                            visibility: hidden;
                    }
                    #bricks-panel-element-classes .active-class:hover .actions :is(.stu-copy-id, .stu-copy-class) {
                            visibility: visible;
                    }
                `;

                // MutationObserver declarations
                const targetNode = document.querySelector('#bricks-panel');
                const config = { childList: true, subtree: true };

                // Callback function to detect DOM changes within #bricks-panel
                const callback = (mutationList, observer) => {
                    for (const mutation of mutationList) {
                        if (mutation.target.id === 'bricks-panel-element' || (mutation.addedNodes.length > 0 && mutation.addedNodes[0].id === 'bricks-panel-element')) {
                            const activeClass = document.querySelector('#bricks-panel-element-classes .active-class');
                            const placeholder = activeClass.querySelector('input').getAttribute('placeholder');
                            const bricksCanvas = contentWindow.document;
                            const dataId = bricksCanvas.querySelector(placeholder) ? bricksCanvas.querySelector(placeholder).getAttribute('data-id') : null;
                            const actionsContainer = activeClass.querySelector('div.actions');
                            const elementClass = document.querySelector('#bricks-panel-element-classes .element-classes .element-class.active > .name')?.innerText ?? null;
                            
                            if (elementClass) {
                                actionsContainer.prepend(spanId, spanClass);

                                const copyClass = actionsContainer.querySelector('span.stu-copy-class');
                                copyClass.addEventListener('click', (e) => {
                                    e.stopPropagation();
                                    navigator.clipboard.writeText(elementClass);
                                });
                            } else {
                                actionsContainer.prepend(spanId);

                                const copyId = actionsContainer.querySelector('span.stu-copy-id');
                                copyId.addEventListener('click', (e) => {
                                    e.stopPropagation();
                                    navigator.clipboard.writeText(placeholder);
                                });
                            }
                        }
                    }
                };
                
                // Create the MutationObserver object and monitor changes within #bricks-panel
                const observer = new MutationObserver(callback);
                observer.observe(targetNode, config);
            }
        </script>
    <?php }
} );
3 Likes

Hey Chris,

You’re very welcome. Thanks for the contribution. I’ll take a closer look this evening. :slightly_smiling_face:

1 Like

+1 for this feature, thanks for the custom code snippets team :slight_smile:

This isn’t working for me. I tried the latest code on your git page.

First element that I copy the ID for works fine but if I try to copy a second elements ID I still have the first element ID on my clipboard.

+1 for this addition. By copying the IDs/Classes this way we don’t need to manually type it(wrongly typed sometimes).

Great idea :ok_hand:

We could provide something like this:

bricks-copy-element-id-to-clipboard

So after clicking the new “copy” icon (not yet in Bricks) the value in the clipboard is #brxe-yfiosu.
Or better just brxe-yfiosu (without the leading #)?

Agreed on populating the existing element ID after you click “rename” like this:

bricks-rename-element-id-populate

9 Likes

With # is okay - most of the time anyway I think. Maybe even less support requests why # was not copied with it. :partying_face:

1 Like

It’s okay with #. Thomas.

1 Like