Skip to content

Loading of Resources

Andre Kless edited this page Jul 15, 2021 · 108 revisions

content is up-to-date for ccmjs v26.4.1

Table Of Contents

Introduction

The ccmjs core script provides a service for asynchronous loading of resources. It could be used with the method ccm.load. With ccm.load you can load resources like HTML, CSS, Images, JavaScript, Modules, JSON and XML data on-demand and cross-domain. On a single ccm.load call several resources can be loaded at once. It can be flexibly controlled which resources are loaded in serial and which in parallel. The results are passed as a Promise. So you can also use ccm.load with async await. Everything that can be loaded via ccm.load can also be specified as resource dependency like [ 'ccm.load', ... ] in configurations for ccmjs-based component instances and is therefore flexibly exchangeable.

Syntax

ccm.load( resource1, resource2, ..., resourceX )

Parameter Values

Parameter Description
resource1, resource2, ..., resourceX The resources to be loaded.

Technical Details

Return Value

  • Promise that provides the result(s) of the ccm.load call

JavaScript Version:

  • ECMAScript 6

Detailed Explanation

In the following, all aspects of how to use ccm.load are described in detail.

Simplest Case: Loading by URL

In the simplest case, only an URL is passed as a parameter for a resource to be loaded:

ccm.load( 'style.css' );

The URL of the resource can be a relative or an absolute path. The resource does not have to be within the same domain and can be loaded cross-domain.

Loading by Resource Data Object

Instead of an URL, an object can be passed, which then contains other information besides the URL, via which the loading of the resource is even more flexible controllable. For example, when loading a resource, it can be specified in which context it is loaded. The CSS contained in a CSS file can now also be loaded into a specific Shadow DOM:

const shadow = document.createElement( 'div' );
shadow.attachShadow( { mode: 'open' } );

ccm.load( { url: 'style.css', context: shadow } );

See here, here and here for more examples of loading CSS.

Here is a list of settings that can be made for loading a resource via such an object:

Property Description
url Required. URL of the resource.
context Context in which the resource should be loaded (default is <head>).
method HTTP method to use: 'PUT', 'GET', 'POST', 'DELETE', 'fetch' or 'JSONP' (default is 'POST').
params HTTP parameters to send (in the case of a data exchange).
headers HTTP headers to be set in the case of a HTTP request.
attr HTML attributes to be set for the HTML tag that loads the resource.
init init object. Only relevant when using the Fetch API (method: 'fetch').
import Name of the exported property to be imported. Only relevant when loading an ES6 module. If not specified, all exported things of the module are imported.
type Resource is loaded as 'css', 'data', 'html', 'image', 'js', 'module' or 'xml'. If not specified, the type is automatically recognized by the file extension. If the file extension is unknown, 'data' is used by default.

Result of a ccm.load Call

ccm.load returns a Promise that provides the result(s) of the ccm.load call.

The following example loads the content "Hello, <b>World</b>!" of a HTML file:

Promise

ccm.load( 'hello.html' ).then( result => {} );

async await

const result = await ccm.load( 'hello.html' );

The variable result then contains the HTML as ccmjs HTML Data:

{
  "inner": [
    "Hello, ",
    {
      "inner": "World",
      "tag": "b"
    },
    "!"
  ]
}

With ccm.helper.html( result ) the HTML can be converted to DOM Elements.

Error Handling

If loading of at least one resource fails, the resulting Promise will be rejected.

Promise

ccm.load( 'not_exits.html' ).catch( error => {} );

async await

try {
  await ccm.load( 'not_exists.html' );
}
catch ( error ) {}

The variable error then contains an object with informations about the error. The following table shows what information this object contains:

Property Description
call Action Data of the original method call
data Object of the XMLHttpRequest
error Error Object of the failed XMLHttpRequest
resource Passed Resource Data Object

In case of a XMLHttpRequest the HTTP status code and the response text can be checked via data and with error you can get the responded error message:

try {
  await ccm.load( 'not_available.html' );
}
catch ( error ) {
  if ( error.data.status === 404 )     // not found?
    alert( error.error.message );      // => show error message of XMPHttpRequest
  else if ( error.data.responseText )  // error with response text?
    alert( error.data.responseText );  // => show response text
  else
    alert( 'Something went wrong' );
}

Watch the error variable in the Developer Console to see what other useful data is included.

Loading with Timeout

For loading resources, a timeout can be set:

ccm.load.timeout = 10000;  // timeout after 10s

In this example, resources that last longer than 10 seconds would fail. By default, there is no time limit. If a result is received after the timeout has expired, a message like this appears in the Developer Console:

[ccm] loading of https://my.domain/bigdata.php succeeded after timeout (10000ms)

Loading of Multiple Resources at Once

On a single ccm.load call several resources can be loaded at once:

const results = await ccm.load( 'hello.html', 'style.css', 'image.png' );

When multiple resources are loaded, the Promise provides an array instead of a single value as the result. The array contains the results in the order in which the parameters were passed.

In this example the variable results contains:

[ {"inner":["Hello, ",{"inner":"World","tag":"b"},"!"]}, "style.css", "image.png" ].

If loading a resource does not supply anything specific, the default result is the URL of the resource. This applies, for example, when loading CSS and images.

If loading one of the resources fails, the result is still an array. For failed resources, the array will contain the error object instead of the result:

try {
  await ccm.load( 'not_exists.html', 'style.css', 'image.png' );
}
catch ( error ) {
  console.log( error );  // [ {error object}, "style.css", "image.png" ]
}

Parallel and Serial Loading of Resources

It can be flexibly controlled which resources are loaded serially and which ones in parallel. By default, resources are loaded in parallel. When resources are to be loaded one after another, they simply need to be passed as an array:

ccm.load( [ 'hello.html', 'style.css' ] );

In the example, the two resources are now loaded serially.

The serial and parallel loading can be flexibly controlled as deep as desired. With each deeper array level you switch between serial and parallel loading:

ccm.load(
  'hello.html',     // Array Level 0: Parallel
  [
    'style.css',    // Array Level 1: Serial
    'image.png',
    [
      'data.json',  // Array Level 2: Parallel
      'script.js'
    ],
    'logo.gif'
  ],
  'picture.jpg'
);

The example loads the resources in the following timeline:

Resource Timeline
hello.html ******------------------
style.css ******------------------
image.png ------******------------
data.json ------------******------
script.js ------------******------
logo.gif ------------------******
picture.jpg ******------------------

Loading of JavaScript

Loading a JavaScript file will execute the JavaScript code contained in the file. As a result of this resource ccm.load returns the URL as usual. But the loaded JavaScript code can also set the result individually:

/* script.js */
ccm.files[ 'script.js' ] = { foo: 'bar' };
const result = await ccm.load( 'script.js' );

Loading this JavaScript file with ccm.load results in: { foo: 'bar' }.

ccm.load returns as a result of loading a JavaScript file what the contained JavaScript code puts in ccm.files[ 'filename' ], where filename is the filename of the JavaScript file. Otherwise, the result is the URL. Using this convention, a JavaScript file can provide data across domains. Publicly fetched data in the global namespace ccm.files will be immediately deleted by ccmjs.

In case of a minimized JavaScript file, ".min" in filename can be omitted:

/* script.min.js */
ccm.files['script.js']={foo:'bar'};
const result = await ccm.load( 'script.min.js' );

See here, here and here for more examples of loading JavaScript.

Loading of Data

Data can be loaded via ccm.load:

const result = await ccm.load( 'hello.php' );
/* hello.php */
<?php
echo 'Hello, World!';
?>

This example results in: "Hello, World!"

A Resource Data Object can be used to specify HTTP parameters to be transmitted:

const result = await ccm.load( {
  url: 'echo.php',
  params: {         // sets HTTP parameters
    name: 'John'
  }
} );
/* echo.php */
<?php
echo 'Hello, '.filter_input( INPUT_POST, 'name', FILTER_SANITIZE_STRING );
?>

This example results in: "Hello, John!"

The Resource Data Object can also be used to specify whether GET or POST should be used as HTTP method:

const result = await ccm.load( {
  url: 'hello.php',
  method: 'GET'      // sets HTTP method
} );

By default, POST is used.

JSONP can be used for data exchange. In this case, it is always a GET request. JSONP is only necessary if the data has to be loaded cross-domain:

const result = await ccm.load( {
  url: 'https://other.domain.com/data.php',
  method: 'JSONP'  // turns on JSONP
} );
/* data.php */
<?php
$callback = filter_input( INPUT_GET, 'callback', FILTER_SANITIZE_STRING );
echo $callback.'({"foo":"bar"});';
?>

This example results in: { "foo": "bar" }

JSONP is not required if Cross-origin Resource Sharing (CORS) is already working.

Loading a Resource Regardless of its File Extension

Normally ccm.load automatically recognizes at the file extension how the resource should be loaded. If a type is specified in the Resource Data Object, the file extension of the resource is ignored and the resource is loaded as the specified type:

ccm.load( {
  url: 'style.php',
  type: 'css'        // resource is loaded as CSS file
} );
/* style.php */
<?php
header( 'Content-Type: text/css' );
?>
b { color: red; }

Although the resource does not have the file extension .css, it will be loaded like a CSS file. The <head> now contains: <link rel="stylesheet" type="text/css" href="style.php">.

If type is not specified and the file extension is unknown, a data exchange is assumed.
See here and here for another examples of loading a resource regardless of its file extension.

Other Aspects

All Resource Data Objects passed to ccm.load are cloned to prevent them from being changed externally.

In a Resource Data Object, the reference to a ccmjs Instance can also be passed for the property context. The resource is then loaded into the instance's Shadow DOM.

When a resource is loaded into a specific context, care must be taken that this context has DOM contact. The chosen context should not be an on-the-fly element or part of it. This is required so that the HTML element used to load the resource is evaluated by the browser.

More Examples

Loading the Content of an HTML File with a Relative Path

const result = await ccm.load( 'https://ccmjs.github.io/ccm/unit_tests/dummy/hello.html' );
/* hello.html */
Hello, <b>World</b>!

Result: {"inner":["Hello, ",{"inner":"World","tag":"b"},"!"]}.

Loading the Content of an JSON File with an Absolute Path

const data = await ccm.load( 'https://ccmjs.github.io/ccm/unit_tests/dummy/data.json' );
/* data.json */
{ "foo": "bar" }

Result: { "foo": "bar" }

If you want to load the static data from a JSON file cross-domain, the data should instead be provided via a JavaScript file (see here and here). Then the static data is loaded with JSONP and the HTTP request is not blocked by the Same-origin Policy (SOP) of the browser. JSONP is not required if Cross-origin Resource Sharing (CORS) is already working.

Loading of HTML as HTML String

const html_string = await ccm.load( {
  "url": "https://ccmjs.github.io/ccm/unit_tests/dummy/hello.html",
  "type": "data"
} );

Result: Hello, <b>World</b>!

With ccm.helper.html2json( html_string ) you can transform the HTML string to ccmjs HTML data.

Loading of a CSS File in the <head> Context

await ccm.load( 'https://ccmjs.github.io/ccm/unit_tests/dummy/style.css' );

The CSS rules contained in the CSS file are now active inside the global DOM.

The <head> now contains:
<link rel="stylesheet" type="text/css" href="https://ccmjs.github.io/ccm/unit_tests/dummy/style.css">

Loading of a CSS File in a Shadow DOM Context

const shadow = document.createElement( 'div' );
shadow.attachShadow( { mode: 'open' } );

ccm.load( {
  url: 'https://ccmjs.github.io/ccm/unit_tests/dummy/style.css',
  context: shadow
} );

This time the CSS rules contained in the CSS file are now active inside the Shadow DOM and the <link> element is appended to the shadow-root of shadow.

Loading of a CSS File in the Shadow DOM of a ccmjs Instance

const instance =
  await ccm.instance( 'https://ccmjs.github.io/akless-components/blank/ccm.blank.js' );

ccm.load( {
  url: 'https://ccmjs.github.io/ccm/unit_tests/dummy/style.css',
  context: instance
} );

The CSS rules contained in the CSS file are now active inside the Shadow DOM of the ccmjs Instance and the <link> element is appended to its shadow-root.

Preloading of an Image

await ccm.load( 'https://ccmjs.github.io/ccm/unit_tests/dummy/image.png' );

When the resulting Promise is resolved, the loaded image is in the browser cache.

Loading of a JavaScript Library

This example loads the JavaScript library jQuery v3.2.1.

ccm.load( 'https://code.jquery.com/jquery-3.2.1.js' );

The code contained in the loaded JavaScript file is now executed and the <head> contains:
<script src="https://code.jquery.com/jquery-3.2.1.js"></script>

Loading of a JavaScript File with Subresource Integrity

This is also an example of setting HTML attributes for the HTML tag that loads a resource.

ccm.load( {
  url: 'https://ccmjs.github.io/ccm/unit_tests/dummy/script.js',
  attr: {
    integrity: 'sha384-QoLtnRwWkKw2xXw4o/pmW2Z1Zwst5f16sRMbRfP/Ova1nnEN6t2xUwiLOZ7pbbDW',
    crossorigin: 'anonymous'
  }
} );

The <head> now contains:

<script src="https://ccmjs.github.io/ccm/unit_tests/dummy/script.js"  
    integrity="sha384-QoLtnRwWkKw2xXw4o/pmW2Z1Zwst5f16sRMbRfP/Ova1nnEN6t2xUwiLOZ7pbbDW"
    crossorigin="anonymous"></script>

Cross-domain Loading of Static JSON through a JavaScript File

const json = await ccm.load( 'https://ccmjs.github.io/ccm/unit_tests/dummy/script.js' );
/* script.js */
ccm.files[ 'script.js' ] = { foo: 'bar' };

Result: { "foo": "bar" }

Dynamic Loading of an Image

This example preloads an image that comes from a PHP interface.

ccm.load( {
  url: 'https://kaul.inf.h-brs.de/ccm/dummy/image.php',
  type: 'image'
} );
<?php
header( 'Content-Type: image/png' );
readfile( 'image.png' );
?>

If the PHP interface would additionally implement user authentication, images could be made available to specific user groups. An HTTP parameter could also be used to control which image should be delivered.

Dynamic Loading of JavaScript

This example loads JavaScript that comes from a PHP interface.

const result = await ccm.load( {
  url: 'https://kaul.inf.h-brs.de/ccm/dummy/js.php',
  type: 'js'
} );
/* js.php */
ccm.files[ 'js.php' ] = { foo: '<? echo 'bar'; ?>' };
console.log( 'Hello, <? echo 'World'; ?>!' );

The <head> now contains: <script src="https://kaul.inf.h-brs.de/ccm/dummy/js.php">.

Result in the Developer Console: Hello, World!

Result of the resolved Promise: { "foo": "bar" }

POST Request via Fetch API

const result = await ccm.load( {
  url: './api/projects',
  method: 'fetch',
  init: {
    method: 'post',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify( {
      title: 'Example Project',
      url: 'http://www.example.com'
    } )
  }
} );

Loading of a static XML file

const result = await ccm.load( 'https://ccmjs.github.io/ccm/unit_tests/dummy/note.xml' );
/* note.xml */
<note>
  <foo>bar</foo>
</note>

Result: #document "<note><foo>bar</foo></note>"

If you want to load the static data from a XML file cross-domain, the data should instead be provided via a JavaScript file (see here and here). Then the static data is loaded with JSONP and the HTTP request is not blocked by the Same-origin Policy (SOP) of the browser. JSONP is not required if Cross-origin Resource Sharing (CORS) is already working.

Loading of a Module

const result = await ccm.load( 'https://ccmjs.github.io/akless-components/modules/helper.mjs' );

When loading a Module, the result is an object that contains all things that are exported in the Module. In this example result contains helper functions.

Loading of a Function via a Module

const sleep = await ccm.load( 'https://ccmjs.github.io/akless-components/modules/helper.mjs#sleep' );
/* helper.mjs.js */
...
export function sleep( time ) {
  return new Promise( resolve => setTimeout( resolve, time ) );
}
...

The result is the helper function sleep from the loaded Module. It can now be called like that:

await sleep( 10000 );  // wait 10 seconds

So in ccmjs it's possible to specify single interchangeable external JavaScript functions as dependencies in the configuration to a ccmjs-based component instance.

Clone this wiki locally