Overriding JavaScript
The most straightforward approach to customizing JavaScript in YSCP is to copy the .twig template into a custom theme and change the code as needed. This works, but if the code in the original file changes in a newer YSCP release, the change will be masked by the template override in the child theme.
Merging changes in JavaScript into overridden templates can be cumbersome and make upgrades more expensive. There is a better way to customize JavaScript, that allows changes in the default theme to automatically apply to child themes. The approach is based on the JavaScript language feature allowing functions, methods, and object properties to be redeclared. Instead of overriding all JavaScript in a template file, it is possible to only override the functions or properties that need to be changed.
To do so, a new template is added to the page that is rendered after the original template containing the JavaScript code to be customized.
Step by step
Follow this recipe when overriding:
- Determine the template containing the original JS
- Create the new template and confirm it is rendered after the original
- Override only the function or method that needs changes while keeping as much as possible of the code in the original file active
Example 1: yui.formatPrice
One relatively common customization target is the yui.formatPrice method.
This method is declared in the template templates/frontend/blocks/head-resources.twig of the yui/craft-plugin plugin.
It contains many important core functions. Sometimes it is updated in new releases to include bug fixes or new features, so overriding the whole file makes upgrades more expensive.
In this example, we want to customize the method to return FREE! if the price is zero.
Please note that in a real project, it would not be enough to customize only the JavaScript yui.formatPrice function.
All prices are initially rendered by PHP using the \yui\craft\twigextensions\HelperTwigExtension->formatPrice function.
Customizing the PHP method is outside the scope of this article.
Inject the new template for the override
We can add our new override template anywhere on the page after the original template.
One possibility would be adding it as a new child of the {% block head %} block content:
{% block head %}
parent()
{% include 'page/js/format-price-customization' %}
{% endblock %}
Override the code
Then we create the new template and add a script tag.
We have added comments to the code below to explain noteworthy aspects of the customization.
<script>
// To keep the global scope tidy, we wrap everything in an IIFE (Immediately Invoked Function Expression).
// Otherwise, all variables and constants we declare would be globally visible.
(() => {
// Keep a reference to the original method
const origFormatPrice = yui.formatPrice;
// Redeclare the method we are customizing.
// By using the `function` keyword instead of an arrow function we
// have access to the magic `arguments` variable to capture new
// arguments possibly added in the future.
yui.formatPrice = function (value, showSign, options = {}) {
// If the value is larger than zero, call the original method
if (value > 0) {
// Call the original method using apply() to ensure all
// arguments are passed along.
return origFormatPrice.apply(null, arguments)
}
// Return custom value if applicable
return '{{ "FREE!"|t("yui") }}';
}
})()
</script>
Notes on the example
Calling the original function
It is not always possible to customize a function and still delegate to the original function.
In some cases, the function needs to be completely replaced.
For yui.formatPrice this would be the case if the numeric formatting itself should be changed.
Testing the customization
To test the customization, we can call yui.formatPrice(1) and yui.formatPrice(0) on the browser console.