(written by Grok and reviewed by Max Milbers)

Audience: Template builders, module and plugin developers
Status: VirtueMart 5
File: administrator/components/com_virtuemart/helpers/vmjsapi.php
Core JS/CSS: media/com_virtuemart/js/ and media/com_virtuemart/css/

This is the VirtueMart 5 version of The VM JavaScript Handler (2014). The queue, the name, and writeJS() are the same idea. The files moved, ES modules arrived, and template overrides follow Joomla’s media folder.

1. Why VirtueMart still has its own handler

Joomla’s usual “put a script in the document head” is not enough for a shop. Product details, the mini cart, and browse fragments reload by AJAX. Scripts that rode in the AJAX HTML header are thrown away. VirtueMart must:

  • queue files and inline snippets by name so a second call does not load the same file twice,
  • let a template replace a named entry before it is written,
  • write files to the head and inline code at the end of the component, even when a module rendered the cart — hence vmJsApi::writeJS() after the module layout,
  • re-run behaviour after AJAX (that part now lives in VMInit).

VirtueMart’s answer: vmJsApi is still the only supported way to load shop JS and CSS. In VirtueMart 5 it also resolves media/com_virtuemart/, minified twins, Joomla template overlays, and ES modules.

2. Where the files live

Core static files are no longer under components/com_virtuemart/assets/. They are:

media/com_virtuemart/js/
media/com_virtuemart/css/
media/com_virtuemart/images/

Filename without .js / .css. Subfolder goes in the name: sortable/sortable-global, product/vmprices.module. Directory argument without a trailing slash. Minified files come from npm run minify (JS and CSS). Hidden config noMinified=1 or debug loads the readable source.

Full override map: Technics article Public assets in media/com_virtuemart (alias virtuemart5-media-com-virtuemart-overrides).

3. The three calls you actually use

Call What it does
vmJsApi::addvScriptModule($name, $path = '') ES module (type="module") through Joomla’s Web Asset Manager. This is the default for new work.
vmJsApi::addJScript($name, $script = false) Classic file ($script === false) or inline string. Same name twice: first wins.
vmJsApi::css($name, $path = false) Stylesheet; picks .min.css when minified output is on.
vmJsApi::addvScriptModule('be/myfeature.module');
vmJsApi::addJScript('my-inline', 'window.Virtuemart = window.Virtuemart || {};');
vmJsApi::css('glightbox');

A leading / on a classic addJScript string is still a site-root URL (old full-path style). Prefer the name-only form so setPath can find template overrides and min files.

4. setPath: how a file is found

You almost never call setPath yourself. addJScript, addvScriptModule and css do. First existing file on disk wins (is_file). Canonical template override (site template cassiopeia, file product/vmprices.module.js):

media/templates/site/cassiopeia/js/com_virtuemart/product/vmprices.module.js

Core if nothing overrides:

media/com_virtuemart/js/product/vmprices.module.js

Do not point configuration assets_general_path at your template. That old trick is gone. Do not put new files in leftover components/com_virtuemart/assets/ on updated shops.

5. ES modules and the import map

Modules must not import core with ../ or /components/com_virtuemart/assets/js/…. That breaks when a template overrides a single file. Core modules write:

import vmInitRegistry from 'virtuemart/vm-init-registry.module.js';

PHP emits an import map so virtuemart/ is media/com_virtuemart/js/ (with the Joomla subdirectory if the shop is not in the web root). Keep relative import only inside a library folder that always ships together (Sortable ESM, Alpine’s own file).

6. Dual load (libraries on window)

Sortable, AutoComplete, Mustache must exist as window.Sortable (etc.) for both modules and leftover classic code:

vmJsApi::addvScriptModule('sortable/sortable.core.esm.min');
vmJsApi::addvScriptModule('sortable/sortable-global');

The *-global.js file imports the library, assigns the global, and registers a VMInit marker. Your feature waits with VMInit.waitForGlobal('Sortable') — the property name on window, not the label Sortable Ready.

If core already attached behaviour to a hook (data-vmjs-sortable, Choices on a class, and so on), put that markup on the page and do not paste the core init loop into a plugin. Load the two modules yourself only on a page that does not already run the VirtueMart JS bootstrap.

7. Modules, plugins, writeJS

Views write the queue after display. A module that renders a cart (or similar) must still flush the queue itself:

require JModuleHelper::getLayoutPath('mod_virtuemart_cart');
echo vmJsApi::writeJS();

Inspect what is queued:

$js = vmJsApi::getJScripts();

8. Do’s and don’ts

  • Do use addvScriptModule for new JavaScript.
  • Do import core with the virtuemart/ prefix.
  • Do override on media/templates/site/yourtemplate/js/com_virtuemart/.
  • Do not put .js in the first argument of addJScript / addvScriptModule.
  • Do not add a trailing slash on the directory argument.
  • Do not hand-edit *.min.js or *.min.css.
  • Do not assume document.ready means all modules have finished — register with VMInit.

9. What stayed from 2014

Named queue, first registration wins, writeJS() for modules, inline snippets at the end of the component, CDATA wrapping where needed. jQuery-from-Google examples in the old article are history. Fancybox is GLightbox. Cookie globals are gone from the guest cart (localStorage). The 2014 page remains as the historical how-to; this page is what you follow on VirtueMart 5.


Related Technics: