Skip to content

Client Modules

Jonathan Eiten edited this page Dec 12, 2017 · 7 revisions

Synopsis

  • Hypergrid Client Modules are CommonJS modules wrapped with a few lines of code to insert them into the window.fin.Hypergrid.modules namespace.
  • Inside a client module, the module, module.exports, exports, and require() all work as usual.
  • The Hypergrid CDN hosts build files for Hypergrid core and other modules on github.com/fin-hypergrid.

Background

Skip this section if you are already familiar with modules, build files, and loading them from the CDN.

What are modules?

Although .html files can accommodate JavaScript code inside <script>...</script> elements, this approach quickly gets out of hand for all but the simplest applications. Code can be moved to a separate .js file which the .html file can load using an empty <script src="source.js"></script> with a reference to the file in its src attribute. When the .js file "exports" a single object, we call it a module.

Primary recommendation: Bundle your modules into a single .js file

For serious development with Hypergrid, we recommend using a module bundler, to bundle an application module (i.e., a .js file) + the fin-hypergrid module + any other npm modules into a single .js file using Browserify or webpack. There are many advantages to this approach for serious development.

Alternative: Load all your modules separately

That said, we recognize that:

  1. There are dev teams out there who cannot for various reasons use npm.
  2. There are dev teams out there who simply prefer to work with build files from a CDN via a series of <script> tags.
  3. For very simple projects such as quick & easy prototyping/proof-of-concept, using the CDN build file is just simpler.

The Hypergrid CDN

For applications that load modules separately, the Hypergrid CDN hosts build files for this purpose. The CDN index has sample <script> tags you can can copy & paste into your application .html file.

Loading the Hypergrid build file

The Hypergrid build file is a module that defines the Hypergrid constructor/namespace, including the Hypergrid Module Loader.

Note: The Hypergrid build file is not itself a Hypergrid Client Module. Rather, it includes the code that defines client modules and how they load other modules; it is required before attempting to load client modules.

A sample <script> tag to load Hypergrid, as shown in the CDN index, looks like this:

<script src='https://fin-hypergrid.github.io/core/3.0.0/build/fin-hypergrid.min.js'></script>

Note: A fully qualified version number is always required to load any build file from the CDN.

Once an application page loads the fin-hypergrid.js (or fin-hypergrid.min.js) module, its scripts have access to the Hypergrid constructor/namespace object via the global property window.fin.Hypergrid.

Other modules

Previously, other modules (such as plug-ins, data source modules, library modules, mix-ins, etc.) were not available on the CDN; the application developer was on his or her own when it came to working with modules. The CDN now hosts all the modules whose repos are housed under the fin-hypergrid GitHub organization; see the CDN index for the full list.

The Hypergrid Module Loader

Hypergrid 3.0.0's module loader, which has no dependencies on import or any external module loader, is implicitly invoked inside Hypergrid Client Modules with a local require() function. Other types of modules, including straight JavaScript or bundled modules, can invoke fin.Hypergrid.require explicitly to get references to Client Modules that were loaded earlier into the browser.

Hypergrid Client Modules

Hypergrid offers a simple module ecosystem called Client Modules, loaded into the fin.Hypergrid namespace by the Hypergrid Module Loader.

A Hypergrid Client Module is a CommonJS module with a Hypergrid Client Module Wrapper.

All modules hosted under github.com/fin-hypergrid are CommonJS modules and many of these are also published to the Hypergrid CDN with a Client Module wrapper. This makes migrating between approaches (module loader vs. module bundler) painless because the same source files (without the wrapper) can be used for either.

All the modules listed in the CDN Index — with the exception of the Hypergrid build itself (fin-hypergrid/core) — are wrapped as Hypergrid Client Modules.

Key features

Hypergrid Client Modules look and behave like CommonJS modules and include the following features:

  • Each module is in a closure (so all their local vars are private)
  • module.exports and exports are fully supported (CommonJS stuff)
  • require() is supported as follows (CommonJS subset):
    • require('fin-hypergrid') — Access to Hypergrid itself
    • require('fin-hypergrid/src/...') — Access to a selected set of internal Hypergrid modules
    • require('other-module') — Access to other Client Modules
    • Note that the local file construct (require('./local-file')) is not supported!
  • No global object "pollution" (beyond window.fin.Hypergrid.modules)

Wrapping a module

Modules need to be "wrapped" before they are served using the following header and footer.

Hypergrid Client Module header:

(function(require, module, exports) {

Hypergrid Client Module footer:

})(fin.Hypergrid.require, fin.Hypergrid.modules, fin.Hypergrid.modules.exports = {});
fin.Hypergrid.modules['module-name'] = fin.Hypergrid.modules.exports;

Manual wrapping

Wrap your modules manually by including the code snippits above at the top and bottom of your source file. (This is generally not recommended however as the file will no longer be a CommonJS module.)

Programmatic wrapping

A better approach is to keep the source file as is, and wrap a copy of it. The copy typically has the same name but is put in a ./build/ subfolder.

Here is a simple Node app to wrap your module files:

wrap.js:

var fs = require('fs');
var process = require('process');
var Wrapper = require('fin-hypergrid-client-module-wrapper');
var buildDir = 'build/';

if (!fs.existsSync(buildDir)) fs.mkdirSync(buildDir);

process.argv.slice(2).forEach(function(filenameWithOptionalVersion) {
    var matches = filenameWithOptionalVersion.match(/^((.*)\.js)(@(.*))?$/),
        filename = matches[1], modname = matches[2], version = matches[4] || '';
    var wrapper = new Wrapper({ name: modname, version: version });
    fs.writeFileSync(buildDir + filename, wrapper.wrap(fs.readFileSync(filename)));
});

The above is invoked as follows:

node wrap A.js@1.5.0 B.js@2.1.0
# or:
node wrap *.js # but no version information

Caveat: As you can see, we have moved away from bare naked development with Notepad.exe, and at this point we would again recommend using npm and a module bundler instead if at all possible!

Wrapping with gulp

If you are into gulp, fin-hypergrid-client-module-maker defines a build task that uses fin-hypergrid-client-module-wrapper (which supplies the above strings) to wrap your source file using the name defined in package.json. It also defines lint, test, and doc tasks for your convenience.

Examples

A. Loading a plug-in

Let's say we have an app module my-app that depends on some Client Modules from the fin-hypergrid CDN, plus a couple of local modules which we'll call A and B. B is a Hypergrid plug-in which depends on A.

Note: By convention, a module's filename should reflect the module's name, although this is not required.

fin-hypergrid, the Hypergrid build file, must always be loaded first as it contains the Module Loader which all other modules depend on. Then load other modules in dependency order.

Note: Predefined modules do not need to be loaded as they are "baked into" (predefined in) Hypergrid.

index.html (excerpt):

<script src='https://fin-hypergrid.github.io/core/3.0.0/build/fin-hypergrid.min.js'></script>
<script src='A.js'></script>
<script src='B.js'></script>
<script src='my-app.js'></script>
<script>
window.onload = function() {
   var app = Hypergrid.require('my-app');
   var grid = app.grid;
}
</script>

B.js (excerpt):

var A = require('A');

my-app.js (excerpt):

var Hypergrid = require('fin-hypergrid');
function App() {
    this.grid = new Hypergrid({ plugins: [require('B')] });
}
var app = new App();
// add app stuff here like event handlers, etc
app.grid.addEventListener(...);
...
module.exports = app;

B. Loading a data source

As of version Hypergrid 3 you can "bring your own" data source using the new DataSource option.

Note: The datasaur-local data source is still included in 3.0.0 an is still the default when the DataSource option is not defined. However, datasaur-local will be removed as of Hypergrid 4.0.0, after which time there will be no default data source; you will need to supply one. So it is a good idea to start defining it now in your application source.

index.html (excerpt):

<script src='https://fin-hypergrid.github.io/core/3.0.0/build/fin-hypergrid.min.js'></script>
<script src='https://fin-hypergrid.github.io/datasaur-base/3.0.0/build/datasaur-base.js'></script>
<script src='https://fin-hypergrid.github.io/datasaur-local/3.0.0/build/datasaur-local.js'></script>
<script src='my-app.js'></script>
<script>
window.onload = function() {
   var app = Hypergrid.require('my-app');
   var grid = app.grid;
}
</script>

my-app.js (excerpt):

var Hypergrid = require('fin-hypergrid');
function App() {
    this.grid = new Hypergrid({ DataSource: [require('datasaur-local')] });
}
var app = new App();
...
module.exports = app;

Note: require('datasaur-local') will work in Hypergrid 3.0.0 even without the <script> tag that loads it because as mentioned above, datasaur-local is still included. However, as of Hypergrid 4.0.0, the <script> tag will be required.

Legacy patterns

Although the legacy expression fin.Hypergrid would still work, don't use it! Use require('fin-hypergrid') instead. This will allow you to move to Browserify without having to make any changes later to my-app.js to accommodate the move.

Modules hosted under github.com/fin-hypergrid actually have package names with fin-hypergrid- prepended. This creates an informal “scope” for the npmjs.org registry.

Predefined modules

The set of predefined modules includes two subsets, Internal Modules and External Modules.

Internal modules

These are modules that are objects that Hypergrid defines; they are not available outside of the Hypergrid build except through Hypergrid Module Loader using the following syntax:

var module = require('fin-hypergrid/src/subfolder/module');

The legacy syntax Hypergrid.subfolder.module is deprecated. Though still functional, a console warning will be issued; the functionality will be removed entirely in version 4. Please convert to the new syntax now.

Permissible subfolder names include (see each index file for available module names):

Note: Access internal modules is provided as a stop-gap measure for missing functionality. When such functionality is eventually provided, the exposed objects will be deprecated following a warning period.

Note: Access to internal modules is limited to the subfolders listed above. By contrast, the Browserify require() interface has no such limitations. As the former is a subset of the later, migration from the former to the latter is guaranteed, but not necessarily the vice versa.

External modules

Several external modules included in the fin.Hypergrid build are accessible with the following syntax:

var module = require('module');

Replace module with any of the following:

Note: In the interests of maintaining a flat dependency graph, these modules should not be loaded separately.

Note: The two datasaur packages will be removed as of Hypergrid 4.0.0 but the require() syntax will still work providing you load the packages explicitly (with <script> tags).