simple-guts

2.3.1 • Public • Published

simple-guts

The simple-guts package is a tiny library of extremely useful utility functions that can serve as the innermost core of a larger library or framework.

Usage

Server-side

In the NodeJS environment, simple-guts will provide its module export as an initialization function, which must be called to initialize the simple-guts API. Configuration parameters can be supplied either through a global environment block object, $env, or passed into the initialization function.

Using $env:

// Define global environment block
$env = {
    useGlobals: true,
    namespace: 'guts',
    core: 'guts-demo'
};
// Guts will pick up config from $env, whether useGlobals
// is true or not
require('../guts.js')();
// Confirm that guts global namespace was defined
console.log(guts.core);

Passing in the configuration:

// Pass in a literal configuration object and do not
// use global variables.
var guts = require('../guts.js')({core: 'guts-demo'});
// Confirm that guts local namespace was defined
console.log(guts.core);

Client-side

Deploy guts.js to your Web server.

  <script type="text/javascript">
    // Define global environment block, $env
    $env = {
      useGlobals: true, // Must be defined
      namespace: 'guts', // Defines the global namespace
      core: 'guts-demo' // Arbitrary core designation
    };
  </script> 
  <!-- Including guts.js directly will cause it to initialize, automatically,
    in the browser. Therefore, it relies on $env for configuration, as there
    is no other way to pass it in, whether useGlobals is true or not. -->
  <script src="js/guts.js"></script> 
  <script type="text/javascript">
    // Confirm that guts global namespace was defined
    console.log(guts.core);
  </script> 

There are three demos provided under the demo/ directory in the simple-guts project. If you add webpack to the project, or install it globally, then you can make changes to the js files directly in the demo/ folder, and the HTML files under demo/www/, to experiment.

Run npm run build from the command line to rebuild the demos. You can use webpack-dev-server or any other Web server to map a url to the demo/www/ directory to load the HTML demo files in a browser.

Incorporate into a larger library

Defining the core property is optional if using guts on its own, but if simple-guts is used as the core of a larger library, it is recommended that core be set to a string that identifies that library. For example, if you were creating a library called "MegaUtils", you might set core to "MegaUtils", or "Mega".

// Module megautils.js
var mega = require('simple-guts')({core: 'MegaUtils'});
mega.extend(mega, {
    id: (function() {
        var _id = 1000;
        return function(prefix) {
            return (prefix||'ID')+(_id++);
        };
    })()
});
module.exports = mega;
 
// Some other module:
var mega = require('megautils');
// Output "MegaUtils MEGA1000"
console.log(mega.core, mega.id('MEGA'));

A call to require('simple-guts') returns a factory method that accepts a configuration object and generates an object with the core methods on it, as described below.

The configuration object supports the following options:

  • namespace - Names the top-level namespace to which all core methods are attached. Note that if useGlobals is not true, this global namespace will not be created, but it is nevertheless returned by the factory method. Defaults to 'NS'.
  • core - An arbitrary string to identify the namespace in code. Defaults to 'NS'.
  • useGlobals - If true (truthy), this defines global variables for top-level namespace and $env environment block (if not already defined). If false or undefined (falsey), no global objects are defined. In either case, $env is also defined as a property on the resulting namespace, which is returned by the factory method.

Configuration can be absorbed from a pre-defined global $env object.

$env = {
    core: 'guts-demo'
};
var guts = require('simple-guts')();
console.log(guts.core);  // Displays 'guts-demo'

It is not necessary to provide a core or namespace name if useGlobals is unspecified, undefined or false. The value for both core and namespace will default to 'NS', but namespace only applies if useGlobals is true.

var guts = require('simple-guts')();
console.log(guts.core);  // Displays 'NS'

guts.global

Reference to the global object, whether running server-side or in a browser.

var guts = require('simple-guts')();
if (guts.global.module) {
    console.log('Might be running in NodeJS');
}
if (guts.global.window) {
    console.log('Likely running in a browser');
}

guts.core

Instance id of the namespace generated by the factory method. This is normally specified in the "core" configuration option, or is set to "NS" by default. The core property is used primarily by higher constructs that use simple-guts at its core, to identify the implementation. A higher construct may define core through the $env global, prior to invoking simple-guts, and may be used in client-side code to detect script reload in the event it gets loaded more than once. If a script using simple-guts does get loaded more than once, guts.multiple will get defined as true.

$env = {namespace: 'guts', core: 'guts-demo', useGlobals: true};
require('simple-guts')();
console.log(guts.core); // Displays 'guts-demo'
if (guts.multiple) console.log('Script loaded more than once!');

guts.$env

Reference to the environment block (configuration settings). If useGlobals is true, $env also is defined in the global space.

// If useGlobals == true was specified in the configuration,
// then $env will be defined as a global variable. Either way,
// it is always available in the namespace.
require('simple-guts')({useGlobals: true, namespace: 'guts', core: 'guts-demo'});
console.log(guts.$env.core); // Displays 'guts-demo' via global guts namespace
console.log($env.core);   // Also displays 'guts-demo'
console.log(guts.core);   // Also displays 'guts-demo', as core is applied to namespace
// Namespace is private and $env is only defined on the namespace, and
// core == 'NS', when no configuration is supplied.
var guts = require('simple-guts')();
console.log(guts.$env.core); // Displays 'guts-demo' via private guts namespace

The usefulness and purpose of $env is not evident when using simple-guts alone. Its utility becomes more evident in higher constructs that use simple-guts at the core.

guts.emptyFn

This is an empty function that does nothing when invoked. Useful for assigning as a placeholder to properties or parameters that expect a function, as a means of avoiding having to test if it is defined before calling it.

var guts = require('simple-guts')();
...
var doSomething = function(callback) {
    // Do something
    callback();
};
doSomething(guts.emptyFn);
var ftn = function(callback) {
    callback();
};
 
var hello = function() {
    alert('Hello world!');
};
 
ftn(hello);    // This will display a dialog box with "Hello world!" in it
hello = Kjs.emptyFn;    // Dispose of the callback function, replacing it with Kjs,emptyFn
ftn(hello);    // This will do nothing
hello = null;    // If the callback is simply nullified, then...
ftn(hello);        // this would throw an exception, as the ftn function attempts to invoke the null variable

guts.extend

Object extender (apply properties)

var obj = guts.extend([deep,] src, objn...);

Param Type Default Description
deep boolean false Optional Boolean value to specify whether to do a deep (true) or shallow (false) copy. Deep extension goes only one level deep.
src object undefined Object to extend. Can be null or undefined to create a new empty object to be extended.
objn... object undefined Any number of objects can be passed in. They will be processed from left to right, copying all the properties from each object to the src object.

Returns the src object, if defined, or a new object (if src is undefined) created by combining all the properties of the objects passed into the function.

guts.extend is used primarily to extend an existing object, to override an existing property, or to add new properties.

var guts = require('simple-guts')();
var
    _id = 1000,
    id = function() {
        return ++_id;
    },
    update = function(item) {
        guts.extend(item, {id: 'sample'+id()});
        // Do whatever update does here
    },
    entity = {
        name: 'Vacuum cleaner',
        comment: 'This thing really sucks!',
        category: 'appliances'
    }
;
 
update(entity);
console.log(entity.id);    // Displays id property that was applied in the call to update()

By default, guts.extend performs a shallow copy, copying only direct properties (not inherited/prototype properties) from the source object(s) to the target object. A deep copy will extend not only the objects passed in, but will also extend object properties if the source and target objects both have an object property of the same name. A deep copy will only go one level deep in order to avoid infinite recursion in case of self-referencing properties. Any property that refers to the global window object will not be extended.

var guts = require('simple-guts')();
var
    config = {
        top: 10,
        left: 100,
        item: {
            columns: 2
        }
    },
    opt = {
        width: 250,
        height: 100,
        item: {
            columns: 3,
            display: true
        }
    }
;
 
guts.extend(config, opt);
 
/* Resulting config object is:
    {
        top: 10,
        left: 100,
        width: 250,
        height: 100,
        item: {
            columns: 3,
            display: true
        }
    }
*/

guts.descend

Object descender (composite properties)

var obj = guts.descend([deep,] objn...);

Param Type Default Description
deep boolean false Optional Boolean value to specify whether to do a deep (true) or shallow (false) copy.
objn... object undefined Any number of objects can be passed in. The will be processed from left to right, copying all the properties from each object to the src object.

Returns a new object created by combining all the properties of the objects passed into the function. Returns a new object created by combining all the properties of the objects passed into the function.

This works exactly like calling guts.extend with null as the first object parameter - in fact, it is a wrapper around guts.extend to do just that.

guts.descend is used extensively in functions that accept an object parameter that may specify default values for any properties that are not specified in the object being passed in. For example:

var guts = require('simple-guts')();
(function() {
    var
        _defaultOptions = {
            height: 100,
            width: 100,
            zIndex: 1000,
            color: '#cccccc'
        },
        _newBox = function(obj) {
            var cfg = guts.descend(_defaultOptions, obj);
            // Create a box here
        }
    ;
 
    _newBox({height: 50, width: 200});    // Creates a box 200x50 at zIndex 1000 and color #cccccc
    _newBox({zIndex: 1001});              // Creates a box 100x100 at zIndex 1001 and color #cccccc
    _newBox({color: 'blue'});             // Creates a blue box, 100x100 at zIndex 1000
})();

By default, guts.descend performs a shallow copy, copying only direct properties (not inherited/prototype properties) from the source object(s) to the target object. A deep copy will extend not only the objects passed in, but will also extend object properties if the source and target objects both have an object property of the same name. A deep copy will only go one level deep in order to avoid infinite recursion in case of self-referencing properties. Any property that refers to the global window object will not be extended.

var guts = require('simple-guts')();
var
    config = {
        top: 10,
        left: 100,
        item: {
            columns: 2
        }
    },
    opt = {
        width: 250,
        height: 100,
        item: {
            columns: 3,
            display: true
        }
    }
;
 
 
var obj = guts.descend(config, opt);
 
/* Resulting obj object is:
    {
        top: 10,
        left: 100,
        width: 250,
        height: 100,
        item: {
            columns: 3,
            display: true
        }
    }
 
    config object remains unchanged
*/

Using guts.descend leaves all original objects intact, since it creates a new object.

Deep copies will be far less performant than shallow copies.

Sometimes, guts.descend is used simply to create a copy of an existing object so that its properties can be extended or modified without affecting the original object.

var guts = require('simple-guts')();
var
    defaults = {
        height: 100,
        width: 200,
        zIndex: 1000
    }
;
 
var config = guts.descend(defaults);
...
config.height = 250;

guts.drain

Object evaporator (strips own properties)

guts.drain(obj);

Param Type Default Description
obj object undefined Accepts a single object from which all direct properties will be deleted.

Returns obj.

There are times when you may define an associative array that may have a reference to it from within a closure. If there is more than one reference to the same object, changes can be made to that object through each of the references. Often, to clear an associative array, the tendency is to just assign it a new empty object. The problem with this is, if you do this to one reference, it leaves any other reference referring to the original object, and the updated reference referring to the new object, so now there is one or more reference that has just become stale.

Consider the following:

var guts = require('simple-guts')();
// Create a small module, guts.sample
guts.sample = guts.sample||(function() {
    var
        _items = {},
        _dangle = function() {
            _items = {};          // Improperly clear the array by assigning a brand new one
        },
        _clear = function() {
            guts.drain(_items);    // Properly clear the array by draining it
        },
        _getItems = function() {
            return _items;        // Return a reference to the associative array
        },
        _set = function(name, value) {
            _items[name] = value; // Define a property on the associative array
        }
    ;
    return {
        dangle: _dangle,
        clear: _clear,
        getItems: _getItems
    };
})();
 
var items = guts.sample.getItems(); // Get a reference to the items, which is a private associative array within the closure of guts.sample module
guts.sample.set('prop1', 'Hello');  // Define 'prop1' property in items list through the private reference
items.prop2 = 'world!';             // Define prop2 property in items list through the external reference variable
alert(items.prop1);                 // Displays 'Hello' -- alert(items['prop1']); would also work
alert(items.prop2);                 // Displays 'world!'  -- alert(items['prop2']); would also work
guts.sample.dangle();               // Create a bad situation
alert(items.prop1);                 // Still displays 'Hello' through the now-stale items reference
items = guts.sample.getItems();     // Refresh the reference
alert(items.prop1);                 // Displays undefined
guts.sample.set('message', 'Hello world!');
alert(items.message);               // Displays Hello world!
guts.sample.clear();                // Properly drain the array
alert(items.message);               // Displays undefined, as items still refers to the same array, which as been emptied through the private reference

guts.object

Object inheritor (prototype extend + apply)

var obj = guts.object(base[, objn...])

Param Type Default Description
base object required Object to extend.
objn... object undefined Optional: Any number of objects can be passed in. The will be processed from left to right, copying all the properties from each object to the src object.

Returns a new object with base as its prototype.

This is a method that should exist in native JavaScript and was inspired by Douglas Crockford. This will create a new object, using the base object as its prototype. In addition, if any additional objects are passed in, their direct properties will be copied (shallow copy only) to the new object in the same manner as guts.extend().

guts.object is very useful in extending data objects, without disturbing the original object - for example, to add computed properties.

var guts = require('simple-guts')();
(function() {
    var
        // Define a data object
        data = {
            fname: 'Bruce',
            lname: 'Jenner'
        },
        // Extend the data object to add fullName computed property
        person = guts.object(data, {
            fullName: '${fname} ${lname}'
        })
    ;
 
    // Use the resolver to display the computed property
    console.log( $s('${fullName}', person) ); // Displays "Bruce Jenner"
    data.fname = 'Caitlyn';                 // Change the data in the original object
    console.log( $s('${fullName}', person) ); // Displays "Caitlyn Jenner" since data is the prototype of person
})();
  • $s is not part of simple-guts, but is part of another (unreleased) package that interpolates string expressions with data.

guts.bind

Binds an object to a function

var binding = guts.bind(context, fn[, arg..]);

Param Type Default Description
context object (required) JavaScript object to bind to a function
ftn function (required) Function to be bound to the given context
arg... (any) undefined Any number of static arguments can be included (or none at all), which will be passed into the call to ftn when it is invoked. Any arguments that are passed into the binding call will follow the static arguments.

Returns a binding, which is a function that acts as a wrapper for calling ftn. This function expects the same parameters as the ftn function that it wraps, and all parameters are passed along to ftn when invoked.

A binding is a means of associating a function with an object, essentially treating the function as though it were a method of the object to which it is bound. The bound object becomes the "this" special variable within the invocation of the function, giving the function access to any of the object's public properties or methods.

Consider the following:

var
    obj = {
        msg: 'Hello world!',
        say: function() { alert(this.msg); }
    },
    message = obj.say;
;
obj.say(); // Displays "Hello world!"
message(); // Attempts to display window.msg, which is likely undefined

An object, obj, is created, with a string property, msg, and a method, say. When obj.say() is invoked, say is intrinsically bound to obj, so this.msg refers to obj.msg, since "this" equals obj. On the other hand, a variable, message, is assigned the value of obj.say, which is a function. In this case, message is a direct reference to obj.say. Calling message(), therefore, bypasses the intrinsic binding, since say is not called within the context of obj. Instead, it gets bound to the global object, by default. If you wish to invoke message in a way that retains the original binding, one way to do it would be to use JavaScript's call or apply method (since it requires no parameters, it makes no difference which one to use):

message.call(obj); // Displays "Hello world!"
message.apply(obj); // Also displays "Hello world!"

Now let's expand on the original code by adding a "caller" function:

var
    caller = function(callback) {
      callback();
    },
    obj = {
        msg: 'Hello world!',
        say: function() { alert(this.msg); }
    },
    message = obj.say;
;
caller(obj.say);    // Calls obj.say in the context of the global object, effectively attempting to display window.msg (via this.msg) which is likely undefined

In this case, caller accepts a callback parameter (which it assumes to be a function) and simply invokes the callback. It has no awareness of the context in which the callback needs to be called. Any function passed in simply gets invoked, which sets its "this" variable equal to the global object. Passing in obj.say essentially does the same thing as the original message variable assignment, setting callback as a direct reference to obj.say. Thus, when callback() is invoked, obj's say method is invoked in the context of the global object, so this.msg is undefined.

guts.bind can be used to send a binding to caller, instead:

caller(guts.bind(obj, obj.say));    // Displays "Hello world!"

Here, the obj.say function is bound to the obj object, basically preserving the context. What gets passed to caller is a function that carries a closure that ensures that say will get called in the context of obj, much the same way as calling message.apply(obj) in an earlier example.

You can also define bindings and pass them around as needed:

var hello = guts.bind(obj, obj.say);
...
caller(hello);

As noted above, guts.bind is useful for binding functions to objects when passing them in as callbacks. But the function being bound need not even be a method of any object. Any function can be bound to any object. For example:

var
    earth = { location: 'Planet Earth' },
    pluto = { location: 'Dwarf Planet Pluto' },
    hello = function() {
        alert('Hello from ' + (this.location||'nowhere') + '!');
    },
 
    fromEarth = guts.bind(earth, hello),
    fromPluto = guts.bind(pluto, hello)
;
fromEarth(); // Displays "Hello from Planet Earth!"
fromPluto(); // Displays "Hello from Dwarf Planet Pluto!"

Two data objects are defined, earth and pluto, which each have a location property. A separate hello function is created, that is not bound to any object, that displays a message referring to this.location for part of the message. Then two bindings are created, fromEarth and fromPluto, which binds the hello function to each of the data objects in separate bindings. Calling each binding, directly, displays the respective message. If the code from the previous examples is still in memory, fromEarth and fromPluto, being bindings, can be passed in as callbacks to caller:

caller(fromEarth);
caller(fromPluto);

In this way, you can see that binding can be very useful for creating various functions that can act on data objects, without the functions having to be explicitly defined as methods on the object, itself. Separating data from behavior can be very powerful, which maintaining a clear separation of concerns. But this leads to architectural concepts that are outside of the scope of this document.

DOM events are another place where bindings are useful, since the only way available to attach an event handler is by passing a (callback) function to a DOM API call. Although guts.bind can be used for event bindings, it is highly recommended that you use guts.bindEvent, instead, as this will account for any cross-browser differences if your code needs to support more than one browser.

guts.bindEx

Binding with function calling context capture

guts.bindEx( context, ftn[, arg...] );

Param Type Default Description
context object (required) JavaScript object to bind to a function
ftn function (required) Function to be bound to the given context. This function must accept a context parameter as the first argument.
arg... (any) undefined Any number of static arguments can be included (or none at all), which will be passed into the call to ftn when it is invoked. Any arguments that are passed into the binding call will follow the static arguments.

Returns a binding, which is a function that acts as a wrapper for calling ftn. This function expects the same parameters as the ftn function that it wraps, and all parameters are passed along to ftn when invoked.

This works exactly like guts.bind(), except there is one additional parameter that is passed in as the first parameter to ftn when it is invoked. This parameter is the context ("this") in which the binding is invoked. Usually, the binding is called from the global scope, but this may not always be the case, and it may be desirable to know the context of the binding call - especially if the binding, itself, has been bound to another object.

Note that if a binding is bound to another object, ftn specified in the original binding will still get called in the context of the original binding - no additional binding can override this. But the target function might be interested in both bindings.

One example of this can be found in the $box API (external reference not available) that creates a wrapper around the jQuery fancybox plug-in. The fancybox plug-in is a sort of lightbox, modal overlay implementation, and the API accepts a large number of configuration options, including a number of callbacks, such as onStart, onComplete and onClosed. The convention that fancybox uses is to call these callbacks in the context of the fancybox configuration object. In other words, "this" will refer to the object containing all the configuration options for the active fancybox instance.

The $box API also wants to carry some additional configuration options, that apply specifically to the $box API itself, but still carry the fancybox config options to the target handlers. Therefore, it uses guts.bindEx so that the context provided by the fancybox plug-in gets captured in the invocation and forwarded to the target function.

    ...
    _onBoxStart = function(fb) {
        var opt = this;
        ...
    },
    _onBoxComplete = function(fb) {
        var opt = this;
        ...
    },
    _onBoxClosed = function(fb) {
        var opt = this;
        ...
    },
    ...
    fancyboxCfg = function(opt) {
        var def = (opt.fancybox.type=='iframe') ? _defFancyIFrame : _defFancy;
            return guts.descend(def, opt.fancybox, {
                onStart: guts.bindEx(opt, _onBoxStart),
                onComplete: guts.bindEx(opt, _onBoxComplete),
                onClosed: guts.bindEx(opt, _onBoxClosed)
            });
    },
    ...

This abbreviated example shows the basic pattern. A call to fancyboxConfig accepts an options object (opt) and creates or extends an opt.fancybox object property that includes three properties, onStart, onComplete and onClosed, which are defined as bindings, using guts.bindEx.

guts.bindEx(opt, _onBoxStart), for example, creates a binding between the _onBoxStart function and the opt options object. This is the primary binding, which means the context ("this") of the _onBoxStart function will refer to the opt object, as is evident from the first line inside the function:

        var opt = this;

These three bindings get applied to the fancybox API. As mentioned, fancybox will invoke these callbacks in the context of the fancybox config object. guts.bindEx is used to avoid losing this context when it applies the opt context to the call. It captures the config object when fancybox invokes the callback, and sends it as the first parameter to the bound function. Hence, the fb parameter in the function definition:

    _onBoxStart = function(fb) {
        ...
    },

In this way, the callback functions have access to both the $box opt object, and the fb fancybox config object.

Most bindings do not get any more complicated than that. However, like guts.bind() and guts.bindEvent(), additional static arguments can be applied to the binding when it is defined. One arbitrary example might look something like this:

var
    config = {
        level: 'info'
    },
    // Log a simple message
    ping = function(level, msg) {
        console[level](msg);
    },
    // Callback handler
    callback = function(cfg, keepAlive) {
        var opt = this;
        keepAlive(cfg.level, opt.msg);
    },
    // Create a primary binding
    getAction = function(opt) {
        return guts.bindEx(opt, callback, ping);
    },
    // Create a secondary binding
    action = guts.bind(config, getAction({msg: 'Hello world!'}))
;
 
action();

This may appear to be a convoluted example, but hopefully it communicates the necessary concepts when studied. config is a simple data object, defining one property, level. ping is a function that accepts two parameters, level and msg, to simply write a message to the console log. callback is the intended callback function to do the busy work. It accepts cfg as the first parameter, which is expected to be the context object ("this") passed in from the initial binding invocation. The context ("this") of the actual function is expected to be an object that was directly bound to the function in the primary binding. getAction creates the primary binding, binding opt to callback. But it also passes in one additional parameter, ping. When invoked, it receives the context from the binding call in cfg, and the context is set to the primary binding (opt), and it takes cfg.level and opt.msg and passes them along to keepAlive. The keepAlive parameter receives the additional parameter that was specified in the binding, which is ping. So, keepAlive is ping, and ping receives the level and message to use in creating the log entry. Finally, action is created as a binding, using guts.bind() to bind the config object, to the primary binding. This means the primary binding will get called in the context of config. The primary binding, being from guts.bindEx(), captures config and passes it to the primary binding as the first parameter, therefore received as the first parameter by the callback function. The primary binding is passed in an object with a msg property, thus becoming the context of the call to callback.

Now that everything is primed, action() is invoked to kick it all off, which is called as a function, thus it is invoked in the global context. But since it is a binding...well, you can take it from here.

If that wasn't enough, what if we wanted to pass in an additional static parameter to the secondary binding? Let's rewrite this a bit:

var
    config = {
        level: 'info'
    },
    // Log a simple message
    ping = function(level, msg) {
        console[level](msg);
    },
    // Callback handler
    callback = function(cfg, keepAlive, more) {
        var opt = this;
        keepAlive(cfg.level, opt.msg + ' ' + more);
    },
    // Create a primary binding
    getAction = function(opt) {
        return guts.bindEx(opt, callback, ping);
    },
    // Create a secondary binding
    action = guts.bind(config, getAction({msg: 'Hello world!'}), 'Wake up!')
;
 
action();

If all goes well, this should record an entry in the log which reads, "Hello world! Wake up!"

How does that work?

Let's start with the primary binding. It hasn't changed. It binds opt to callback and carries one additional static parameter. The secondary binding binds config to the primary binding and also carries one additional static parameter. When callback is called, it gets cfg from the guts.bindEx binding, plus the additional parameter from the primary binding, followed by the additional parameter from the secondary binding. In other words, the bound function receives all the static parameters from the primary binding, first, followed by any and all parameters provided in the invocation call.

guts.bindEvent

Event binding

guts.bindEvent( context, ftn[, arg...] );

Param Type Default Description
context object (required) JavaScript object to bind to a function
ftn function (required) Function to be bound to the given context. This function must accept an event parameter as its first argument.
arg... (any) undefined Any number of static arguments can be included (or none at all), which will be passed into the call to ftn when it is invoked. Any arguments that are passed into the binding call will follow the static arguments.

Returns a binding, which is a function that acts as a wrapper for calling ftn. This function expects to be called as a DOM event handler, so will receive an event object as its first argument, followed by any other parameters the ftn function accepts.

If you are not familiar with bindings, please review guts.bind() before continuing.

guts.bindEvent is recommended for use when you want to attach an event handler to the DOM. In the early days of DOM scripting, before more sophisticated MVC JavaScript architecture emerged, it was a challenge to identify a single item from a list to correlate it with the data object that was used to render it, when an event handler was invoked on the item in the UI. Often, one approach to assist with this was to attach a data object to the DOM. This is not recommended, for many reasons, not the least of which is bridging the separation of concerns between data and UI. A better approach is to simply use a binding with the event handler, itself, so that the handler function is called in the context of the data object to act on.

Consider the following data:

var
    muppets = [
        {name: 'Kermit the Frog', url: 'wiki.muppet.com/Kermit'},
        {name: 'Miss Piggy', url: 'wiki.muppet.com/Piggy'},
        {name: 'Animal', url: 'wiki.muppet.com/Animal'}
    ]
;

Assume it was used to generate the following markup:

<ul id="muppet_list">
    <li id="muppet:0"><span>Kermit the Frog</span></li>
    <li id="muppet:1"><span>Miss Piggy</span></li>
    <li id="muppet:2"><span>Animal</span></li>
</ul>

Let's say we want to do navigate to a wiki page when clicking on any of the names in the list. There are at least two ways to attach click event handlers to this list. The less efficient way is to attach click handlers to each of the items. A more efficient way is to apply a single click handler to the

    tag, itself (known as a delegate). Let's look at the less efficient way for simplicity.

var
    // Define a helper function to navigate to a specific muppet page
    navigateToMuppet = function(muppet) {
        location.href = muppet.url;
    },
    // Define muppet click handler
    onMuppetClick = function(event) {
        // Redirect to the respective URL - Binding will carry the data for the specific muppet in "this"
        navigateToMuppet(this);
    }
;
 
for (var i=0; i<muppets.length; i++) {
    // Attach click handler bound to specific muppet data
    $('#muppet:'+i).on('click', guts.bindEvent(muppets[i], onMuppetClick));
}

Note that a call to guts.bindEvent creates a new function. Therefore, there is a new function created for each item in the list. As mentioned, this is inefficient, but the benefit to this approach, is that the click event handler will get called with the data object that is associated with whatever item is clicked. This makes it a simple matter of pulling the muppet data from "this" and passing it along to the helper function.

In the example above, onMuppetClick is invoked in the context of an entry from the muppets array, referred to by "this". onMuppetClick merely passes "this" to navigateToMuppet, which expects a data object for a particular muppet - which is exactly what it gets. The location is then updated to the url specified in the data.

This example navigates away from the page, but it certainly does not have to. The take-away from this example is to see how data can be bound to the event handler, without having to take some extra messy step to attach data to the DOM.

Another problem with applying the event handler(s) in this manner is when it comes to tear-down (such as to clean up on page unload), there has been no reference to the bound handlers that can be passed into jQuery's off method in order to remove the handlers for a clean unload.

Therefore, the above could be rewritten as follows:

var
    muppets = [
        {name: 'Kermit the Frog', url: 'wiki.muppet.com/Kermit'},
        {name: 'Miss Piggy', url: 'wiki.muppet.com/Piggy'},
        {name: 'Animal', url: 'wiki.muppet.com/Animal'}
    ],
 
    // Define a helper function to navigate to a specific muppet page
    navigateToMuppet = function(muppet) {
        location.href = muppet.url;
    },
 
    // Define muppet click handler
    onMuppetClick = function(event) {
        // Redirect to the respective URL - Binding will carry the data for the specific muppet in "this"
        navigateToMuppet(this);
    },
 
    // Helper method to assign click handlers to muppet list
    activate = function() {
        for (var i=0; i<muppets.length; i++) {
            $('#muppet:'+i).on('click', muppets[i].onClick);
        }
    },
 
    // Helper method to remove click handlers from muppet list
    deactivate = function() {
        for (var i=0; i<muppets.length; i++) {
            $('#muppet:'+i).off('click', muppets[i].onClick);
        }
    }
;
 
$init(function _$init_muppets($) {
    // Define click handler bindings in the data
    for (var i=0; i<muppets.length; i++) {
        muppets[i].onClick = guts.bindEvent(muppets[i], onMuppetClick);
    }
});
 
$ready(function _$ready_muppets($) {
    activate();  // Assign click handlers
});
 
$unload(function _$unload_muppets($) {
    deactivate();  // Remove click handlers
});

The above makes reference to $init, $ready and $unload, which are part of a framework not described here, but serves well to illustrate a sequence of events, whereby $init occurs first, followed later by $ready. $unload responds when the page unloads due to navigation or page reload.

guts.bindF

Function interceptor binding

guts.bindF( ftn, callback[, arg...] );

Param Type Default Description
ftn function (required) Original (target) function to intercept
callback function (required) Intercept function to be called when binding is invoked.

callback(ftn[, arg...][, param...]) ftn = The target function gets passed into the callback for reference arg... = Any static arguments specified in the binding param... = Any parameters passed into the binding invocation Return false to prevent the target function from getting called Return an object { args: [...], context: {...} } to specify additional parameters to add to target function call, and/or a different context in which to call it.| |arg...|(any)|undefined|Any number of static arguments can be included (or none at all), which will be passed into callback when the binding is invoked. None of the arguments are passed into the target function.|

Returns a special binding that wraps a specified target function. This binding provides the opportunity to intercept the invocation of a target function by invoking a callback function, first.

guts.bindF() is most useful when given an event handler function that is to be attached to a DOM element, but your code wants or needs to respond to that same event, first (great for use in wrappers).

Take the following example. A simple injectScript function is created to dynamically load an external JavaScript file. The caller can pass in the script URL (src), and a function to be called when the script is loaded (onLoad). But injectScript wants to respond to the onLoad event as well, to clear a timeout timer, before letting the caller's handler get called. To do so, it simply redefines onLoad to be a bindF binding, and assigns that as the event handler, instead.

var injectScript = function(src, onLoad) {
    var
        // Get script insertion point in the document
        first = document.getElementsByTagName('script')[0],
 
        // Set a 30-second timeout
        t = guts._(function() {
            $log.error('Timeout while loading ' + src);
        }, 30000),
 
        // Create script tag
        script = document.createElement('script')
        ;
 
    // Intercept onLoad to clear loading timeout using bindF
    onLoad = guts.bindF(onLoad, function(ftn) {
        clearTimeout(t);            // Cancel timeout timer
        script.onLoad = undefined;  // Just to leave no dangling closures
    });
 
    // Assign our bound function
    script.onLoad = onLoad;
    // Tell it what to load
    script.src = src;
    // Insert script tag
    first.parentNode.insertBefore(script, first);
};

One convenient piece to this is that the original function may be undefined. If so, it will not create any problems, and you need not write any conditional code in your code to test whether you need to wrap the function or not. The binding will still carry your callback, plus an undefined reference to the other (missing) function, so it will call your callback, then simply make no attempt to call the empty reference.

The callback can also be used to conditionally prevent the original function from getting called. Simply return false from the callback function to prevent the original function from getting called. Note that it must be an explicitly false Boolean value and not a falsey value (empty string, 0, undefined and null will not qualify).

$module('clickwatch', function(log, env, module) {
    var
 
        // Enable/disable
        _enable = function(enable) {
            env.enabled = !!enable;
        },
 
        // Primary function - define intercept and attach click handler
        _api = function(sel, onClick) {
            onClick = guts.bindF(onClick, function(ftn) {
                return env.enabled;
            });
            $(sel).on('click', onClick);
        }
        ;
 
    // Enable by default
    _enable(true);
 
    // Return the clickwatch module API
    return guts.extend(_api, {
        enable: _enable
    });
});
 
guts.clickwatch('#mybutton1', function(event) { alert('Hello world!'); });
guts.clickwatch('#mybutton2', function(event) { alert('There is only change.'); });
 
... /* Clicking the buttons will show the alerts */
 
guts.clickwatch.enable(false);  // Disable all watched clicks
 
... /* Click event is now disabled so buttons will not function */

Note that the first parameter to the callback is always ftn. This ftn argument receives the value of the original function.

            onClick = guts.bindF(onClick, function(ftn) {
                // Here, ftn is the original onClick
                return env.enabled;
            });

This means it is possible for your handler to call this function, directly.

            onClick = guts.bindF(onClick, function(ftn, event) {
                ftn.call(this, event);
                return false; // To avoid ftn getting called again
            });

Note in this example, above, an event object is received. This would be the case if the function is used as a DOM event handler, as the DOM would provide an event object in the call. But the first parameter to the callback is always the original function, followed by any parameters provided when the binding is invoked, in this case, event. You could conceivably call ftn in a different context or with different parameters, depending on the requirements. The possibilities are endless.

If you need to provide additional parameters through the binding, you can specify them in the binding:

            onClick = guts.bindF(onClick, function(ftn, msg, event) {
                alert(msg);
                ftn.call(this, event);
                return false; // To avoid ftn getting called again
            }, 'Hello world!');

Note that specifying additional parameters on the binding, pushes the invocation parameters further down the parameter list. In this case, the callback function expects ftn, which it always receives, and msg, which receives the string 'Hello world!' provided as the additional parameter to the binding. Then, any parameters passed in the binding invocation are included. ATTENTION: When specifying additional arguments, it is imperative that values are provided for all of them (even if undefined), otherwise parameters from the binding invocation may come into the wrong argument.

            // Incorrect:
            onClick = guts.bindF(onClick, function(ftn, msg, event) {
                alert(msg);  // This will attempt to alert the event object, since it was passed into msg!
                ftn.call(this, event);  // event is undefined, or could be another parameter from the invocation!
                return false;
            });
            ...
            // Correct:
            onClick = guts.bindF(onClick, function(ftn, msg, event) {
                if (msg) alert(msg);  // This will alert msg if defined
                ftn.call(this, event);  // Works as expected
                return false;
            }, undefined);  // Passing in undefined msg param

Special Case: As noted, returning false from the callback will prevent the bound function from getting called. Another option is to return an object from the callback in order to augment how the target function gets called. Ordinarily, it would get called as if the bindF wrapper was not present. But the callback function has the power to dynamically alter the way in which the target function gets called. One thing it cannot do is remove parameters, or change the order or the values of the originally expected parameters (with one exception: the callback receives the same parameters that the target expects, and if one or more of those parameters is an object, the callback can modify properties on that object). But it can do the following:

Parameter Type Description
args Array If specified, this provides additional arguments to be appended to the argument list when the target function is invoked
context Object If specified, this will become the context of the target function. The context in which the binding was called is lost in this case. However, the callback is invoked in the binding context, so it could be passed as an additional parameter to the target function --

return {args: [this], context: otherObject };

An example use case is shown below in a function designed to retrieve a JSON response:

    ...
    _loadJson = function _$loadjson(urls, opt) {
        opt = ((typeof opt == 'function') ? {onLoad: opt} : opt)||{};
        opt = guts.descend(opt, {dataType: 'json'});
        opt.onLoad = guts.bindF(opt.onLoad, function(onLoad, success, response) {
            var responses = [].concat(response), n = responses.length, datas = [], resp;
            while (responses.length) {
                resp = responses.shift();
                datas.push((success) ? resp.responseText : null);
            }
            return {args: [(n==1) ? datas[0] : datas]};
        });
        _loadFile(urls, opt); // Carry out the load request (not shown)
    },
    ...

opt.onLoad creates a bindF binding. The callback receives the success and response arguments expected for onLoad from the _loadFile call, and returns an additional argument, which is response.responseText (which is the JSON object following a successful request), or null if the request failed to return JSON data. The target function, therefore, should expect function(success, response, data).

_loadJson('https://api.ipify.org?format=json', function(success, response, data) {
    alert('Your IP address is: ' + data.ip);
});

_loadJjson supports sending a list of URLs as the first parameter, in which case response and data would each be an array for the corresponding responses (as a result of calling _loadFile, which is not described here). With that in mind, the _loadJson example would more clearly demonstrate this special usage of bindF if it was written to support only one URL and unconditionally pass along the responseText as the third parameter, whether the request was successful or not:

    ...
    _loadJson = function _$loadjson(urls, opt) {
        opt = ((typeof opt == 'function') ? {onLoad: opt} : opt)||{};
        opt = guts.descend(opt, {dataType: 'json'});
        opt.onLoad = guts.bindF(opt.onLoad, function(onLoad, success, response) {
            return {args: [response.responseText]};
        });
        _loadFile(urls, opt);
    },
    ...

guts._

Interval/timeout timer (deferred function executor)

guts._([iterations,] ftn[, timeout])

guts.([interval,] ftn[, timeout])

Param Type Default Description
iterations number 1 Optional count to specify whether to define an interval timer for n iterations, or a single timeout. A zero value (0) indicates unlimited intervals (repeat forever).
ftn function (required) Function to invoke upon timeout. For interval timers, if ftn returns true, the interval timer will be cleared.
timeout number 1 Timeout value (in milliseconds). For interval timers, this is the interval period.

Returns a timer id.

guts._() is used to defer the execution of a function, or to call a function at regular intervals (like a polling routine), so that the function gets called in an asynchronous manner. This is actually very useful in many situations, especially when invoking code that performs any manner of DOM manipulation (rendering/refreshing UI, accessing form fields, etc.) from within an event handler. It is sometimes not a good idea to execute such code from within an event handler, as the browser reflow can be in an indeterminate state, and you may not know what operation or other handlers are pending once the event handler exits.

A browser event will block the thread until the event has been completely handled, so deferring your code execution until after the thread is complete ensures the UI is in a more stable and predictable state. The default timeout is 1ms, although a timeout may likely never be so short. Timer events cannot be processed by the browser while the thread is blocked to process an event, so by setting the timeout to 1ms, this really is just a way to get your code to execute as soon as the thread is free to process timer events. It is especially a good idea to use guts._() for code that fires an event on other DOM elements, so that you do not get events firing from within an event handler.

For example, code to fire a click event on the search button in the UI may look something like the following. This code is wrapped in guts._() in case it is called from within another event handler:

        // Fire the search button on the base page
        _fireSearch = function(cfg) {
            if (cfg.btnSearch && cfg.btnSearch.length) {
                guts._(function() {
                    cfg.btnSearch.click();
                });
            }
        },

And, in fact, _fireSearch is called from a key down event when the user presses the Return key:

    _onKeydown = function _$onkeydown(e) {
        var
           cfg = _, // Gets config from bound context (not shown)
            rtn = true
        ;
        if (!_.enabled) return rtn;
        switch (e.keyCode) {
               ...
          case 13:
            if (cfg.idxItem >= 0) {
            _selectItem(cfg, cfg.idxItem);
            } else {
            // Fire search straight from text entered
            cfg.isItemSelected = false;
            _fireSearch(cfg);
            }
            e.preventDefault();
               e.stopPropagation();
            rtn = false;
            ...
          default:
            break;
        }
        return rtn;
    },

This allows the keydown event to complete before firing a click event on the search button.

It is unlikely that you will ever need the return value from guts._(), but you can use it to clear a pending timeout. It may be useful in terminating an interval timer, for example.

$module('mymodule', function(log, env, module) {
    var
        polling = function() {
            // Do some polling activity here
        },
 
        interval,
 
        onStopPolling = function(event) {
            clearInterval(interval);
        }
    ;
    $ready(function _$ready_mymodule($) {
        // Initiate polling loop
        interval = guts._(0, polling, 250);
 
        // Activate button to stop polling
        $('#stopInterval').on('click', onStopPolling);
    });
    $unload(function _$unload_mymodule($) {
        clearInterval(interval);
    });
    return {};
});

Note, as mentioned previously, $module, $ready and $unload are constructs from another framework, and are used here for clear illustration. $module defines a module, $ready callback fires when modules are initialized, and $unload callback fires when the page unloads.

Package Sidebar

Install

npm i simple-guts

Weekly Downloads

68

Version

2.3.1

License

MIT

Last publish

Collaborators

  • kwooda333