JavaScript
The Framework uses RequireJS to load in only the JavaScript needed for the page in question. When building new elements or modules which are to be enhanced using JavaScript, the functionality should therefore be included using RequireJS.
-
Set the
data-app
attribute on theinit.js
script tag within the page to the URL that the JavaScript files are located (do not include any trailing slashes). This URL should be the full path to the folder location, but should remain protocol agnostic:<script id="js-init" src="./v.2023.01/shared/assets/themes-managed/[ release version ]/shared/js/init.js" data-app="="//:[ Site domain ]/..." integrity="[ init integrity digest ]" ></script>
-
Load the JavaScript for the module by setting the
data-module
attribute on the element to which the JavaScript will be applied toapp/[module]
, where[module]
is the name given to the JavaScript file for your module without the.js
file extension:<div class="..." data-module="app/[JavaScript block name]">...</div>
As an example, if you are using v5.0.0 of the Framework, with your JavaScript files stored at www.aviva.co.uk/project/library/js
, and your module file is called project-module.js
, your js-init
setup would be:
<script id="js-init" src="./v.2023.01/shared/assets/themes-managed/v5.0.0/shared/js/init.js" data-app="//www.aviva.co.uk/project/library/js" crossorigin="anonymous" integrity="[ init integrity digest ]"></script>
With the call to the module being:
<div class="..." data-module="app/project-module">...</div>
Considerations when writing blocks
Multi-language support
If content being added via the JavaScript which contains text, consider making that configurable so that it can be changed if required to better suit the usage or language.
A very example of this could be a button added to a page by JS, whose text content may need to be presented in a number of different languages if the component is used globally.
Accessibility
Always consider best practices with regard to accessibility, and, wherever possible, make use of ARIA attributes to help users with assistive technologies to better navigate the page/application. See the Accessibility Manual for more information.
Writing JavaScript blocks
Use RequireJS to define the block, loading in any JavaScript files that the block is dependent on. Check the list of core JavaScript files at the bottom of this page so that files are not loaded multiple times from different locations. For example:
define( [ 'utility' ], function run( utility ) { // Set up define call for RequireJS
'use strict'; // Always use strict mode var mTestComponentFn = {
// Define any default settings
defaults : {
firstDefault : 1,
secondDefault : 2,
}, // Create the init function to run on load
init : function( elm ) {
// Your module code goes in here, for example:
if ( !elm.classList.contains( 'js-m-test-component' ) ) {
elm.classList.add( 'js-m-test-component' ); elm.mTestComponent = {}; // Setup mTestComponent object mTestComponentFn.create( elm );
}
return elm.mTestComponent;
}, create : function( elm) { // Additional functions can be also created
// Creation code
}
}; return { // Finally, return your block, initialising the instance of its use
initInstance: function( elm ) {
return mTestComponentFn.init( elm );
}
};
});
Important note: The init function for the block will be called for every instance of the element/module that occurs within the page on load, so precautions should be taken to ensure that this does not get applied multiple times to the same element/module. This could be achieved by simply checking for a particular class being present on the element/module, for example .js-module
, which is only added by the JavaScript.
Extending the RequireJS config
If you need to extend the RequireJS config, use the following code within a separate JS file:
var FRAMEWORK = window.FRAMEWORK || {};
FRAMEWORK.extend = FRAMEWORK.extend || {};FRAMEWORK.extend.requireConfig = {
paths : {
//... custom paths
},
moduleAttributes : {
'app/yourModule' : {
crossorigin : 'anonymous',
//... Any other attribute e.g. integrity
}
}
};
Then include the URL to your require config extension file as a data attribute on the init.js script tag.
<script async id="js-init" src="./v.2023.01/shared/assets/themes-managed/[ release version ]/shared/js/init.js" data-require="[ require JS config file url ]" data-require-integrity="[ SRI for require JS config file ]" crossorigin="anonymous" ></script>
Your require settings will be loaded asynchronously by the framework setup JavaScript. If an integrity hash is provided for the require config extension file it will be applied on loading the file.
Important note: There are several caveats to using this process:
- Do not overwrite the
baseUrl
orskipDataMain
config properties; these are required by Framework to work correctly. - If you are adding a config property that uses an object type, (eg.
paths
), then when the Framework config and the extension config are merged, the Framework's values will take priority. - If you are adding a config property that uses an array type (eg.
deps
), then the Framework's config value will be concatenated with the extension config. - If you are adding a
callback
property, this will be wrapped within the Framework's own callback function; the Framework function will run first, and then call any additional callbacks. - To add your own attributes to the script tag; include a
moduleAttributes
property that is an object type, and include an object for each module within that object where the module name is the key and, within, set the attribute name as the key and the attibute value as the value.
CORS on RequireJS modules
The crossorigin
attribute on img, video or script tags enables you to configure the CORS request for an element's fetched data.
It is possbile to include custom attributes to modules by extending the RequireJS config. This can be used to include a crossorigin
attribute to handle the CORS request.
var FRAMEWORK = window.FRAMEWORK || {};
FRAMEWORK.extend = FRAMEWORK.extend || {};FRAMEWORK.extend.requireConfig = {
moduleAttributes : {
'app/yourModule' : {
crossorigin : 'anonymous',
...
//... Any other attribute e.g. integrity
},
'app/' : { // This would set the attributes to all modules where the module name starts with 'app/'
crossorigin : 'anonymous',
...
},
...
}
};
SRI on RequireJS modules
Subresource integrity is a feature of the browser that ensures the resources that are fetched are delivered without unexpected change to them, to prevent, for example files changed by a hacker on the CDN being able to execute a harmful script. This works by the browser generating a hash digest from the fetched resource against one provided in the element's integrity attribute. Framework files included using RequireJS have this feature included.
Since v4.12 it is possbile to include custom attributes to modules by extending the RequireJs config. This can be used to include a integrity attribute to handle the CORS request. The element also requires the crossorigin attribute to be present.
To generate a digest you can use a shell command below or paste the file link into SRI Hash Generator.
openssl dgst -sha384 -binary FILENAME.js | openssl base64 -A
Example of how to extend the Require config to include a integrity attribute to a module:
var FRAMEWORK = window.FRAMEWORK || {};
FRAMEWORK.extend = FRAMEWORK.extend || {};FRAMEWORK.extend.requireConfig = {
moduleAttributes : {
'app/yourModule' : {
integrity : 'sha384-digestGoesHere'
crossorigin : 'anonymous',
...
//... Any other attribute, eg. integrity
},
'app/' : { // This would set the attributes to all modules where the module name starts with 'app/'
crossorigin : 'anonymous',
...
}
}
};
For more information go to the Mozilla Developer Docs.
Extending animation timings and component configs
If you need to extend the animation timings or extend/update any default component settings, use the following code within a separate JS file:
var FRAMEWORK = window.FRAMEWORK || {};
FRAMEWORK.extend = FRAMEWORK.extend || {};FRAMEWORK.extend.timings = {
//... custom timings
};FRAMEWORK.extend.componentSettings = {
//... custom component settings
};
Then include the URL to your custom settings file as a data attribute on the init.js script tag.
<script async id="js-init" src="./v.2023.01/shared/assets/themes-managed/[ release version ]/shared/js/init.js" data-settings="[ component settings file url ]" crossorigin="anonymous"></script>
Your custom settings will be loaded asynchronously by the framework setup JavaScript.
Important note:
- All timing values must be in milliseconds eg. 2500 for 2.5s.
- Take care when updating default component settings so that errors are not caused by incorrect values.
Working programmatically with RequireJS
Sometimes you may want more flexibility than using Framework blocks; in this situation, working directly with RequireJS is the recommended approach.
To do this, wait for the RequireJS config to be loaded asynchronously and fire its callback, which will emit the frameworkready
event on document
. This is set up as follows:
document.addEventListener( 'frameworkready', function onRequireReady() {
'use strict'; FRAMEWORK.require( [ 'app/custom-module' ], function run( customModule ) {
// When Require JS has loaded and run, do something...
});
});
Module script loading
JavaScript modules are loaded automatically using a combination of mutation and intersection observers. This removes the need to manually call 'moduleScan' as in v4.X.X and below in order to setup newly added components and also increases page performance by defering the load of JavaScript modules until they are close to coming into view.
If a module should not be lazy loaded the the additional attribute data-module-load="true"
to be added alongside their data-module attribute. This will ensure the module is loaded straight away. Only prevent lazy loading of modules when absolutely necessary.
On the rare occasion that the lazy loading of hidden modules causes issues when modules are later are revealed by their parent module, it is possible to force the loading of targeted modules prior to them being revealed, ie. when the parent module runs its initial setup. To do this, simply fire a loadModule
event on the element that is required to be set up early. Components which have not loaded their JavaScript modules can be found by searching for the class has-module-observer
.
elm.querySelectorAll( '.has-module-observer' ).forEach( function loop( element ) {
utility.trigger( element, 'loadModule' );
});
Note: The loadModule
is only available for use from v5.3.0 of Framework.
Loading JavaScript that not compatible with RequireJS
If, when migrating to the Framework, there are a number of blocks of JavaScript which are required for page functionality, but which it is not viable to rewrite into the Framework module format, we suggest trying this solution.
- Set the
data-app
attribute on theinit.js
script tag within the page as detailed above. -
Add in each required script under the
init.js
script tag in the following format:<script id="js-init" src="./v.2023.01/shared/assets/themes-managed/[ release version ]/shared/js/init.js" data-app="./website/scripts" crossorigin="anonymous" integrity="[ init integrity digest ]"></script> <span data-module="app/jquery.validate.min" ></span>
-
If one of the files has a dependency on another, the file is modified so that the original code is wrapped by an AMD loader function. For example, to ensure that module aBlock is loaded before the JavaScript is run:
( function( factory ) { if ( typeof define === 'function' && define.amd ) { // AMD - register as an anonymous module depending on aBlock. define( 'app/faq-manager', [ 'aBlock' ], factory ); } else { // No AMD - register plugin with global aBlock object. factory( aBlock ); } }( function( aBlock ) { // The original JavaScript goes here }));
Triggering a refresh
Sometimes you may want to force content to refresh, for example if elements need to be redrawn after being hidden and then shown again.
The simplest method is to use frameworks utility function that is available:
utility.trigger( window, 'refresh' );
Interacting with Framework components
From v5.0.0, we have made interacting with Framework components easier and more reliable. All components now return an object which may contain functions which can be called to, for example, update or get settings.
The following is an example of how the settings would be updated on a date component.
FRAMEWORK.require( [ 'blocks/a-date' ], function run( aDateModule ) {
'use strict'; var myDateEl = document.querySelector( '#dateInput' ), // Get my component
newSettings = {
minDate : '2016-01-01',
maxDate : '2018-01-01',
}; // Some data to use /*
* This will run the initInstance function and return the aDate object which is appended
* to the myDateEl; if the component has already been initialised, it will not run as the
* init function, but will still return the appropriate aDate object
*/
var aDateInstance = aDateModule.initInstance( myDateEl ); // Run setSetting as required
aDateInstance.setSettings( newSettings );
});
Using jQuery validate and unobtrusive
We recommend avoiding the use of jQuery unless absolutely necessary and is no longer loaded be default, however jQuery validate and unobtrusive are available for use with your blocks of JavaScript. Load them by adding them to your define
block as follows:
define( [ 'jquery', 'jquery.validate' ], function run( $ ) {
...
});
define( [ 'jquery', 'jquery.validate.unobtrusive' ], function run( $ ) {
...
});
jQuery validate and unobtrusive are configured to display errors correctly with the default markup for form components, but for this to work, there are certain elements which MUST be in the markup, so the markup MUST be correct as per the Framework documentation.
Updating unobtrusive settings
If you need to update the unobtrusive defaults, then do so as follows:
FRAMEWORK.require( [ 'jquery', 'jquery.validate.unobtrusive' ], function run( $, unobtrusive ) {
var $form = $('form'); unobtrusive.options.errorClass = 'my-custom-error'; unobtrusive
// Remove validation
.destroy( $form )
// Reparse the document to apply new unobtrusive defaults
.parse( document );
});
Important note: this is typically a blunt approach, so a better method whenever possible would be to update the validator settings instead:
FRAMEWORK.require( [ 'jquery', 'jquery.validate.unobtrusive' ], function run( $, unobtrusive ) {
var $form = $( 'form' ),
validator = $form.data( 'validator' ); if ( validator ) {
validator.settings.errorClass = 'my-custom-error';
}
});
Validator error placement
There may be occasions where error placement does not work as expected for a certain form field; to work around this, you may need to modify jquery.validate's settings, for example:
FRAMEWORK.require( [ 'jquery', 'jquery.unobtrusive' ], function run( $, unobtrusive ) {
var $form = $( 'form' ),
validator = $form.data( 'validator' ); if ( validator ) {
validator.settings.errorPlacement = function errorPlacement( error, $element ) {
// Position the error message in a custom location for 'my-element'
if ( $element.attr( 'id' ) === 'my-element' ) {
$( '#my-element-error-container' ).append( error );
// Otherwise use the default errorPlacement handler
} else {
$.validator.defaults.errorPlacement.apply( this, arguments );
}
};
}
});
Using multiple Require configs
It is possible to use Require with multiple configurations. Framework has been setup to use its own context so that if third party libraries use Require themselves it should be possible to have both Framework and the third library running side by side.
An example of how to setup a separate context once RequireJS has been loaded by Frameworks init.js:
document.addEventListener( 'requireready', function onRequireReady() {
'use strict'; var NEWCONTEXT = window.NEWCONTEXT || {}; NEWCONTEXT.requireConfig = {
context : 'newcontext',
...
}; NEWCONTEXT.require = require.config( NEWCONTEXT.requireConfig ); // Setup new instance of Require with custom config NEWCONTEXT.require( [ ... ], function run( ... ) {
// Module code here
});
});
See RequireJS multiversion support for further details.
Using multiple versions of jQuery
It is possible to run multiple versions of jQuery alongside each other. Framework makes jQuery 3.4.1 available and when loaded by any module this will apply to the global $ and jQuery variables. Any additional versions of jQuery must be loaded using noConflict
.
See RequireJS mapping modules to use noConflict for further details on how to load a version of jQuery using noConflict and reference it within your modules.
Core JavaScript files
Files available to use with RequireJS
block | Description | Functions / Variables |
---|---|---|
jquery | jQuery v3.6.0 (v3.5.1 prior to release v5.5.0) | Note: jQuery is no longer loaded by default |
jquery.validate | jQuery validate v1.19.3 (v1.19.2 prior to release v5.5.0) - https://jqueryvalidation.org | |
jquery.validate.unobtrusive | jQuery validate v3.2.12 (v3.2.11 prior to release v5.5.0) |
Includes default error placement to match the Frameworks markup. When loading unobtrusive do not load jQuery validate as well. |
jquery.validate.unobtrusive-ajax | jQuery validate v3.2.6 |
Includes default error placement to match the Frameworks markup. When loading unobtrusive-ajax do not load jQuery validate as well. |
utility | Core utility functions | See migrating away from jQuery for reference |
dayjs | DayJS - v1.10.4 (v1.8.23 prior to release v5.5.0) | Core dayjs.js file; used as a lightweight replacement for Moment, but may require additional modules to be able to access some of Moment's functionalities. For details of use, visit https://github.com/iamkun/dayjs |
dayjs/plugin/... | DayJS plugins - v1.10.4 (v1.8.23 prior to release v5.5.0) |
For details of other use and other plugins, visit https://github.com/iamkun/dayjs/blob/dev/docs/en/Plugin.md |
dayjs/locale/... | DayJS additional locales - v1.10.4 (v1.8.23 prior to release v5.5.0) |
For details on how to use locale files, visit https://github.com/iamkun/dayjs/blob/dev/docs/en/I18n.md. Additional locales can be downloaded from https://unpkg.com/browse/[email protected]/locale/ |
vendor/pikaday | Pikaday - v1.8.2 (v1.8.0 prior to release v5.5.0) - https://github.com/Pikaday/Pikaday | |
vendor/numeral | Numeral.js - v2.0.6 - http://adamwdraper.github.com/Numeral-js/ | |
highcharts | Highcharts - v8.2.2 (v8.2.0 prior to release v5.5.0) - https://www.highcharts.com | CSS available from /assets/themes-managed/[ release version ]/shared/css/vendor/highcharts7/... |
highcharts/modules/... | Highcharts modules - for v8.2.2 (v8.2.0 prior to release v5.5.0) - https://www.highcharts.com |
|
highcharts/indicators/... | Highcharts indicators - for v8.2.2 (v8.2.0 prior to release v5.5.0) - https://www.highcharts.com |
|
highcharts/lib/... | Highcharts lib - for v8.2.2 (v8.2.0 prior to release v5.5.0) - https://www.highcharts.com |
|
highcharts/themes/... | Highcharts themes - for v8.2.2 (v8.2.0 prior to release v5.5.0) - https://www.highcharts.com |
|
highcharts/highstock | Highstock - v8.2.2 (v8.2.0 prior to release v5.5.0) - https://www.highcharts.com | |
highcharts/highcharts-3d | Highcharts 3D - v8.2.2 (v8.2.0 prior to release v5.5.0) - https://www.highcharts.com | CSS available from /assets/themes-managed/[ release version ]/shared/css/vendor/highcharts8/... |
highcharts/highcharts-more | Highcharts More - v8.2.2 (v8.2.0 prior to release v5.5.0) - https://www.highcharts.com | CSS available from /assets/themes-managed/[ release version ]/shared/css/vendor/highcharts8/... |
vendor/highcharts9/highcharts | Highcharts - v9.0.1 (from release v5.5.0) - https://www.highcharts.com | CSS available from /assets/themes-managed/[ release version ]/shared/css/vendor/highcharts9/... |
vendor/highcharts9/modules/... | Highcharts modules - for v9.0.1 (from release v5.5.0) - https://www.highcharts.com |
|
vendor/highcharts9/indicators/... | Highcharts indicators - for v9.0.1 (from release v5.5.0) - https://www.highcharts.com |
|
vendor/highcharts9/lib/... | Highcharts lib - for v9.0.1 (from release v5.5.0) - https://www.highcharts.com |
|
vendor/highcharts9/themes/... | Highcharts themes - for v9.0.1 (from release v5.5.0) - https://www.highcharts.com |
|
vendor/highcharts9/highstock | Highstock - v9.0.1 (from release v5.5.0) - https://www.highcharts.com | |
vendor/highcharts9/highcharts-3d | Highcharts 3D - v9.0.1 (from release v5.5.0) - https://www.highcharts.com | CSS available from /assets/themes-managed/[ release version ]/shared/css/vendor/highcharts8/... |
vendor/highcharts9/highcharts-more | Highcharts More - v9.0.1 (from release v5.5.0) - https://www.highcharts.com | CSS available from /assets/themes-managed/[ release version ]/shared/css/vendor/highcharts8/... |
vendor/highcharts7/highcharts | Highcharts - v7.2.2 (v7.0.2 prior to release v5.5.0) - https://www.highcharts.com | CSS available from /assets/themes-managed/[ release version ]/shared/css/vendor/highcharts7/... |
vendor/highcharts7/modules/... | Highcharts modules - for v7.2.2 (v7.0.2 prior to release v5.5.0) - https://www.highcharts.com |
|
vendor/highcharts7/indicators/... | Highcharts indicators - for v7.2.2 (v7.0.2 prior to release v5.5.0) - https://www.highcharts.com |
|
vendor/highcharts7/lib/... | Highcharts lib - for v7.2.2 (v7.0.2 prior to release v5.5.0) - https://www.highcharts.com |
|
vendor/highcharts7/themes/... | Highcharts themes - for v7.2.2 (v7.0.2 prior to release v5.5.0) - https://www.highcharts.com |
|
vendor/highcharts7/highstock | Highstock - v7.2.2 (v7.0.2 prior to release v5.5.0) - https://www.highcharts.com | |
vendor/highcharts7/highcharts-3d | Highcharts 3D - v7.2.2 (v7.0.2 prior to release v5.5.0) - https://www.highcharts.com | CSS available from /assets/themes-managed/[ release version ]/shared/css/vendor/highcharts7/... |
vendor/highcharts7/highcharts-more | Highcharts More - v7.2.2 (v7.0.2 prior to release v5.5.0) - https://www.highcharts.com | CSS available from /assets/themes-managed/[ release version ]/shared/css/vendor/highcharts7/... |