docs/includes/elements/container.md
Elementor's new replacement for Sections & Columns.
Container uses Flex (in the future also Grid) and gives you the option to drag & drop Anything. Anywhere. Even nested!
Creating a new core element in a legacy code-base is never easy. Therefore, we had to deal with many weird problems.
The main one is the DnD (Drag & Drop) aspect. DnD-ing into a flex container is a real challenge by itself, and when you add the fact that you need to support the old existing DnD mechanism, it becomes a huge headache.
Some things that we needed to take into consideration were:
flex-direction is row?flex container?top / bottom / vertical?flex-basis / flex-shrink / flex-grow?flex item if there are some calculations like grow/shrink?dragover event on the existing Container?The answer to all those question is one - Yes. It caused any problem you can imagine. And even more. We had to implement a lot of workarounds. From simple UI tweaks, To hooking into jQuery UI Draggable events and overriding some of its behaviors.
Another aspect is the new elements hierarchy. We needed to decide which elements we want to allow inside Containers. The final decision (which actually was fairly easy) is to allow only Widgets & Containers inside a Container, since Sections & Columns are useless there. Plus, we decided to also allow Container inside Column, in order to let users with existing websites to use this new futuristic feature.
padding and calc() and it might have some issues and limitations.views/container.js file.sortable.js behavior applied to the Containers, only because there is a bug when sorting
Containers in the Navigator due to the removal of jQuery UI Sortable.
Should be removed when the Navigator will be migrated to React.horizontalThreshold property, not sure why it works. Everything lower didn't work. 🤷♂️element:dragged, which sometimes might interfere with element:selected).Droppable instance in order to avoid dropping issues with
nested Containers._container.scss file are leftovers from the POC phase that should be editor-only, and
should be moved to another file.In the container.php file, add a new control as any other widget:
// container.php
$this->add_control(
'overflow',
[
'label' => esc_html__( 'Overflow', 'elementor' ),
'type' => Controls_Manager::SELECT,
'default' => '',
'options' => [
'' => esc_html__( 'Default', 'elementor' ),
'hidden' => esc_html__( 'Hidden', 'elementor' ),
'auto' => esc_html__( 'Auto', 'elementor' ),
],
'selectors' => [
'{{WRAPPER}}' => '--overflow: {{VALUE}}',
],
]
);
Notice that we've set a CSS variable to the {{WRAPPER}} - We're gonna use that in the SCSS file:
// _container.scss
// First, reset the CSS var in the top of the file, to its initial value:
--overflow: visible;
// ...
// other vars declarations
// ...
// Then, consume it:
overflow: var( --overflow );
This variables reset method is required in order to avoid style leakage as stated above.
{{WRAPPER}} as the selector, and consume the variable under the proper selector in the SCSS file
instead of putting a long selector in the PHP file.--var-name: revert; will be your best bet, but don't overuse it).In order to extend the Container element, you'll need to extend it in PHP & JS:
_get_default_child_type()).elementor/elements/elements_registered hook.elementor/document/config filter.views/base.js::getChildView().elementsHierarchy object under /js/editor/utils/helpers.js.views/*.js::isDroppingAllowed()).Create a new class under the /includes/elements directory that extends Elementor\Includes\Elements\Container.
Relevant (or interesting) methods to override might be:
_get_default_child_type()get_type()get_name()get_title()get_icon()register_controls()In order to inform the Editor about this new element, register it under the Elements_Manager::init_elements() method (/includes/managers/elements.php):
private function init_elements() {
// ...
// Other code
// ...
$this->register_element_type( new Awesome_Element() );
// ...
// Other code
// ...
}
Create a new Marionette class under the /assets/dev/js/editor/elements/views directory that extends ContainerView (container.js in the same directory).
Then, just add/remove methods and features. Relevant (or interesting) methods to override might be:
classname()getCurrentUiStates()getDroppableOptions()isDroppingAllowed()onDragStart()onDragEnd()In order to inform the Editor about this new element, add a new case under the getChildView() method in /views/base.js,
that imports your newly created element:
switch ( elType ) {
// ...
// Other cases
// ...
case 'my-awesome-container':
ChildView = require( 'elementor-elements/views/my-awesome-container' );
break;
}
Currently, the Container element has only a single related hook called "set-direction-mode"
(/js/editor/document/hooks/ui/settings/set-direction-mode.js).
This hook handles changes in the "Direction Mode" UI state that's attached to the Container. Theoretically, this hook should support any element that has "Direction Mode" support in its Marionette view, but currently it's only implemented in Container.
Each time a setting gets changed (either from the Panel or using an external document/elements/settings command), this hook gets fired
and handles the UI state change.
Note: When the user is viewing the "Advanced" tab in the Panel, the UI state change is fired on the parent because the Direction Mode is mainly responsible for rotating flex icons, and the controls in the advanced tab should be rotated relatively to the parent (e.g. "align-self" is a child control, but it's relative to the parent).