Customizing components
The Shopware 6 Administration allows you to override and extend components to change its content and its behavior.
Prerequisites
All you need for this guide is a running Shopware 6 instance, the files and preferably a registered module. Of course, you will have to understand JavaScript, Vue and have a basic familiarity with TwigJS block system, and the templating engine used in the Administration. It is just used for the block extending and overriding. Every other feature of TwigJS is not used in the Administration. However, that is a prerequisite for Shopware as a whole and will not be taught as part of this documentation.
General
To add new functionality or change the behavior of an existing component, you can either override or extend the component.
The difference between the two methods is that with Component.extend()
a new component is created. With Component.override()
, on the other hand, the previous behavior of the component is simply overwritten.
Override a component
The following example shows how you can override the template of the sw-text-field
component.
// import the new twig-template file
import template from './sw-text-field-new.html.twig';
// override the existing component `sw-text-field` by passing the new configuration
Shopware.Component.override('sw-text-field', {
template
});
Extending a component
To create your custom text-field sw-custom-field
based on the existing sw-text-field
you can implement it like the following:
// import the custom twig-template file
import template from './sw-custom-field.html.twig';
// extend the existing component `sw-text-field` by passing
// a new component name and the new configuration
Shopware.Component.extend('sw-custom-field', 'sw-text-field', {
template
});
Now you can render your new component sw-custom-field
in any template like this:
<sw-custom-field></sw-custom-field>
Customize a component template
To extend a given template you can use the Twig block
feature.
Imagine, the component you want to extend/override has the following template:
{% block card %}
<div class="sw-card">
{% block card_header %}
<div class="sw-card--header">
{{ header }}
</div>
{% endblock %}
{% block card_content %}
<div class="sw-card--content">
{{ content }}
</div>
{% endblock %}
</div>
{% endblock %}
Maybe you want to replace the markup of the header section and add an extra block to the content. With the Twig block
feature you can implement a solution like this:
{# override/replace an existing block #}
{% block card_header %}
<h1 class="custom-header">
{{ header }}
</h1>
{% endblock %}
{% block card_content %}
{# render the original block #}
{% parent %}
<div class="card-custom-content">
...
</div>
{% endblock %}
Summarized with the block
feature you will be able to replace blocks inside a template. Additionally, you can render the original markup of a block by using {% parent %}
Extending methods and computed properties
Sometimes you need to change the logic of a method or a computed property while you are extending/overriding a component. In the following example we extend the sw-text-field
component and change the onInput()
method, which gets called after the value of the input field changes.
// extend the existing component `sw-text-field` by passing
// a new component name and the new configuration
Shopware.Component.extend('sw-custom-field', 'sw-text-field', {
// override the logic of the onInput() method
methods: {
onInput() {
// add your custom logic in here
// ...
}
}
});
In the previous example, the inherited logic of onInput()
will be replaced completely. But sometimes, you will only be able to add additional logic to the method. You can achieve this by using this.$super()
call.
// extend the existing component `sw-text-field` by passing
// a new component name and the new configuration
Shopware.Component.extend('sw-custom-field', 'sw-text-field', {
// extend the logic of the onInput() method
methods: {
onInput() {
// call the original implementation of `onInput()`
const superCallResult = this.$super('onInput');
// add your custom logic in here
// ...
}
}
});
This technique also works for computed
properties, for example:
// extend the existing component `sw-text-field` by passing
// a new component name and the new configuration
Shopware.Component.extend('sw-custom-field', 'sw-text-field', {
// extend the logic of the computed property `stringRepresentation`
computed: {
stringRepresentation() {
// call the original implementation of `onInput()`
const superCallResult = this.$super('stringRepresentation');
// add your custom logic in here
// ...
}
}
});
Real world example for block overriding
Finding the block to override
In this guide we want to change the heading of the Shopware 6 dashboard to be Welcome to a customized Administration
instead of Welcome to Shopware 6
. To do this, we first need to find an appropriate twig block to override. We don't want to replace too much but also to not override too little of the Administration. In this case, we only want to override the headline and not links or anything else on the page. Looking at the twig markup for the dashboard here, suggests that we only need to override the twig block with the name sw_dashboard_index_content_intro_content_headline
to achieve our goal.
Preparing the override
Now that we know where to place our override, we need to decide what to override it with. In this very simple example it suffices to create a twig file, declare a block with the name we previously found and to insert our new header into the block.
<!-- <plugin root>/src/Resources/app/administration/src/sw-dashboard-index-override/sw-dashboard-index.html.twig -->
{% block sw_dashboard_index_content_intro_content_headline %}
<h1>
Welcome to a customized component
</h1>
{% endblock %}
This overrides the entire twig block with our new markup. However, if we want to retain the original content of the twig block and just add our markup to the existing one, we can do that by including a {% parent %}
somewhere in the twig block. Learn more about the capabilities of twig.js here.
As you might have noticed the heading we just replaced had a { $tc() }
string interpolation which is used to make it multilingual. Learn more about internationalization in the Shopware 6 Administration and about adding your own snippets to the Administration here.
Applying the override
Registering the override of the Vue component is done by using the override method of our ComponentFactory. This could be done in any .js
file, which then has to be later imported, but we'll place it in <plugin root>/src/Resources/app/administration/src/sw-dashboard-index-override/index.js
.
import template from './sw-dashboard-index.html.twig';
Shopware.Component.override('sw-dashboard-index', {
template
});
The first parameter matches the component to override, the second parameter has to be an object containing the actually overridden properties for example, the new twig template extension for this component.
Loading the JavaScript File
The main entry point to customize the Administration via a plugin is the main.js
file. It has to be placed into the <plugin root>/src/Resources/app/administration/src
directory in order to be automatically found by Shopware 6.
The only thing now left to just add an import for our previously created ./sw-dashboard-index-override/index.js
in the main.js
:
import './sw-dashboard-index-override/';
Experimental: Composition API extension system
Shopware 6 is introducing a new way to extend components using the Composition API. This system is currently in an experimental state and is needed for the future migration of components from the Options API to the Composition API.
Current status and future plans
- The existing Options API extension system remains fully supported and functional.
- The new Composition API extension system is introduced as an experimental feature.
- In future versions, components will gradually migrate from Options API to Composition API.
- Plugin developers are encouraged to familiarize themselves with the new system, but should continue using the current Component factory extension system for components written with the Options API.
- For components written with the Composition API, the new extension system should be used.
- In the long term, the Composition API extension system will become the standard way to override components. The Options API extension system will be deprecated and eventually removed when all components are migrated to the Composition API.
How it works
The new extension system introduces two main functions:
Shopware.Component.createExtendableSetup
: Used within components to make them extendable. This will mainly be used by the core team to make components extendable.Shopware.Component.overrideComponentSetup
: Used by plugins to override components.
Using overrideComponentSetup
The overrideComponentSetup
function is a key part of the new Composition API extension system. It allows plugin developers to override or extend the behavior of existing components without directly altering their source code.
Basic usage
Shopware.Component.overrideComponentSetup()('componentName', (previousState, props, context) => {
// Your extension logic here
return {
// Return the new or modified properties and methods
};
});
Parameters
componentName
: A string identifying the component you want to override.- Callback function: This function receives three arguments:
previousState
: The current state of the component, including all its reactive properties and methods.props
: The props passed to the component.context
: The setup context, similar to what you would receive in a standard Vue 3 setup function.
Return value
The callback function should return an object containing any new or modified properties or methods you want to add or change in the component.
Example: Replacing a Single Property
Shopware.Component.overrideComponentSetup()('sw-product-list', (previousState) => {
const newPageSize = ref(50);
return {
pageSize: newPageSize // Override the default page size with the new ref
};
});
Example: Adding a New Method
Shopware.Component.overrideComponentSetup()('sw-order-list', (previousState) => {
return {
newCustomMethod() {
console.log('This is a new method added to sw-order-list');
}
};
});
Example: Modifying existing data
Shopware.Component.overrideComponentSetup()('sw-customer-list', (previousState) => {
// Add a new column to the list
previousState.columns.push({ property: 'customField', label: 'Custom Field' });
return {};
});
Example: Overwriting a method
Shopware.Component.overrideComponentSetup()('sw-customer-list', (previousState) => {
// Overwrite the existing method
const newIncrement = () => {
// Able to access the previous method
previousState.increment();
// Add custom logic
console.log('Incremented by 1');
};
return {
increment: newIncrement,
};
});
Example: Accessing props and context
Shopware.Component.overrideComponentSetup()('sw-customer-list', (previousState, props, context) => {
// Access the props
console.log(props);
// Access the context
console.log(context);
return {};
});
Important notes
- Type Safety: The system aims to provide type safety. Make sure your IDE is set up to recognize the types from Shopware's type definitions.
- Reactive Properties: When modifying reactive properties, ensure you maintain their reactivity. Use Vue's reactive utilities when necessary.
- Multiple Overrides: Multiple plugins can override the same component. Overrides are applied in the order they are registered.
- Performance: Be mindful of performance implications when adding complex logic to frequently used components.
- Compatibility: This method is part of the experimental Composition API system. Ensure your plugin clearly states its dependency on this feature.
- Testing: Thoroughly test your overrides, especially when modifying core component behavior.
Example real world usage
Here is an example of how to create an extendable component and how to override it:
import { defineComponent, reactive } from 'vue';
// Original component
const originalComponent = defineComponent({
template: `
<div>
<h1>{{ message }}</h1>
<div>
<mt-button @click="increment">Increment</mt-button>
<p>
{{ countMessage }}
</p>
<p>
Notifications are currently: {{ showNotification ? 'enabled' : 'disabled' }}
</p>
</div>
</div>
`,
props: {
showNotification: {
type: Boolean,
default: false,
},
},
setup: (props, context) => Shopware.Component.createExtendableSetup({
props,
context,
name: 'originalComponent',
}, () => {
const count = ref(0);
const message = 'Hello from Shopware!';
const countMessage = computed(() => `The current count is: ${count.value}`);
const increment = () => {
count.value++;
};
const privateExample = ref('This is a private property');
return {
public: {
count,
message,
countMessage,
increment,
},
private: {
privateExample,
}
};
}),
});
// Overriding the component with a plugin
Shopware.Component.overrideComponentSetup()('originalComponent', (previousState, props) => {
const newMessage = 'Hello from the plugin!';
const newCountMessage = computed(() => `The new, amazing count is: ${previousState.count.value}`);
const newIncrement = () => {
previousState.increment();
if (props.showNotification) {
Shopware.ServiceContainer.get('notification').dispatch({
title: 'Incremented!',
message: `The count has been incremented by the user to ${previousState.count.value}!`,
variant: 'success',
});
}
};
return {
message: newMessage,
countMessage: newCountMessage,
increment: newIncrement,
};
});
In this example, createExtendableSetup
is used to make the originalComponent
extendable. The overrideComponentSetup
function is then used to modify the properties of the component. In this case, the message is changed, a new computed property is added, and the increment method is modified to show a notification if the showNotification
prop is set to true
.
Key differences from Options API extension system
- Uses Composition API syntax and reactive primitives of Vue 3 instead of Vue 2 options API.
- Extensions are applied using function composition rather than option merging.
- Provides more granular control over what parts of a component can be overridden.
- Only overrides are possible. Extending a component is not supported anymore. This can be done natively with the Composition API.
Using TypeScript
To take full advantage of the Composition API extension system, it is recommended to use TypeScript. This will provide better type safety and autocompletion in your IDE and prevent common errors.
For adding type safety to props you need to import the type of the component you want to override and use it in the overrideComponentSetup
function as a generic type: <typeof _InternalTestComponent>
. The types for the previousState
are automatically inferred from the component you are overriding by using the correct component name.
import _InternalTestComponent from 'src/the/path/to/the/exported/component';
Shopware.Component.overrideComponentSetup<typeof _InternalTestComponent>()('_internal_test_compponent', (previousState, props) => {
const newBaseValue = ref(5);
const newMultipliedValue = computed(() => {
// Props are now correctly typed
return newBaseValue.value * props.multiplier!;
});
// Previous state is now correctly typed
previousState.baseValue.value = 2;
return {
baseValue: newBaseValue,
multipliedValue: newMultipliedValue,
};
});
Accessing private properties
In some cases, you may need to access private properties of a component. This is not recommended, as it can lead to unexpected behavior and breakages when the component is updated. However, if you need to access private properties for debugging or testing purposes, you can do so using the _private
property of the previousState
object where all private properties are stored.
Note: overriding and accessing private properties has no TS support and is not recommended for production use.
Shopware.Component.overrideComponentSetup()('sw-customer-list', (previousState, props, context) => {
// Access the private properties
console.log(previousState._private.thePrivateProperty);
});