Jump to content

PubSub pattern in JavaScript


guinness
 Share

Recommended Posts

For those who are unfamiliar with the PubSub pattern, then perhaps look at this playlist I posted here today before proceeding. I admit it was a new one for me, but then there are so many patterns that it's hard to keep track of 'em.

I basically took the PubSub sample from the video and made a few tweaks here and there. Before you could only pass an event name and single argument, which lets face it is not brilliant if you want to "publish" with multiple arguments. Passing a single object literal is a horrible workaround.

With this version it uses the 'arguments' (https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/arguments) array-like object to obtain the arguments passed and then splices the first element, as this is the event name which is not required to be passed to the callback. I have just noticed there is a huge red warning on that page about Google's V8 engine, but for now I will leave as is.

Enjoy! (Test by running on JSBin.com if you want)

//
// PubSub pattern in JavaScript
//      By LearnCodeAcademy, URL: https://gist.github.com/learncodeacademy/777349747d8382bfb722
//
// Additional info:
//      Tuts+, URL: https://www.youtube.com/watch?v=U4cigUpzW2U&list=PL15G0RGjxzGdBqDPF4DDrlOt9YmjampzO&index=8
//      Peter Higgins, URL: https://github.com/phiggins42/bloody-jquery-plugins/blob/master/pubsub.js
//      PubSubJS, handle/token concept, URL: https://github.com/mroderick/PubSubJS/blob/master/src/pubsub.js
//      PubSub, subscription array concept, URL: https://github.com/drublic/PubSub/
//      PubSub Wikipedia, URL: https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern
//
// Performance tests:
//      forEach-vs-for-loop: http://jsperf.com/array-foreach-vs-for-loop/5
//      for-loop-vs-indexOf: http://jsperf.com/js-for-loop-vs-array-indexof/8
//      shift-vs-splice: http://jsperf.com/shift-vs-splice
//

/**
 * PubSub module
 *
 * Modified: 2015/08/28
 * @author softwarespot
 */
var PubSub = (function (Array, Object) {
    // Constants

    // Version number of the module
    var VERSION = '0.1.0';

    // Array constant enumeration
    var HANDLE_ID = 0,
        HANDLE_SUBSCRIPTION = 1,
        HANDLE_CALLBACK = 2,
        HANDLE_MAX = 3;

    // Return strings of toString() found on the Object prototype
    var ObjectStrings = {
        FUNCTION: '[object Function]',
        STRING: '[object String]'
    };

    // Fields

    // Hold event names with an array of callbacks for each one
    var _subscribers = {};

    // Unique identifier. Leet speak for PubSub_Module
    var _handleId = '|>|_|85|_|8_|\\/|0|)|_|13';

    // Generic handle for an error. This is an array so the reference can be used as a
    // way of verifying that it's an error
    var _handleError = [_handleId];

    // Store the Object toString method
    var _objectToString = Object.prototype.toString;

    // Methods

    // Check if a value is a function. Based on the idea by lodash
    function isFunction(value) {

        return isObject(value) && _objectToString.call(value) === ObjectStrings.FUNCTION;

    }

    // Check if an opaque 'PubSub' handle is valid
    function isHandle(handle) {

        return Array.isArray(handle) && handle.length === HANDLE_MAX && handle[HANDLE_ID] === _handleId;

    }

    // Check if a value is an object. Based on the idea by lodash
    function isObject(value) {

        // Store the typeof
        var type = typeof value;

        // !!value is basically checking if value is not 'truthy' e.g. null or zero and then inverts that boolean value
        // So, !'Some test' is false and then inverting false is true. There if value contains 'something', continue
        return !!value && (type === 'object' || type === 'function');

    }

    // Check if a value is a string datatype with a length greater than zero when whitespace is stripped. Based partially on the idea by lodash
    function isString(value) {

        return (typeof value === 'string' || _objectToString.call(value) === ObjectStrings.STRING) && value.trim().length > 0;

    }

    // Public API
    return {
        // Subscribe to a subscription with a callback function. It's best practice not to make this an anonymous function
        // as then you can't properly unsubscribe, since the callback function reference is required
        // Returns an opaque handle for use with unsubscribe(), though it's optional to use
        subscribe: function (subscriptions, callbacks) { // on()
            // Store whether the first param is a string
            var isStringTypes = isString(subscriptions) && isFunction(callbacks);

            // If a string and a function datatype, then create an array for each
            if (isStringTypes) {
                callbacks = [callbacks];
                subscriptions = [subscriptions];
            }

            // If either of the arguments are not an array or the lengths simply mismatch
            if (!Array.isArray(subscriptions) || !Array.isArray(callbacks) || subscriptions.length !== callbacks.length) {
                return _handleError;
            }

            // Variables used with inside the loop(s)
            var subscription = null;
            var functions = null;
            var callback = null;

            // Return array of handles i.e. [handle id, subscription, callback]
            var handles = [];

            // Iterate through all the subscriptions
            for (var i = 0, subscriptionsLength = subscriptions.length; i < subscriptionsLength; i++) {
                // Store the subscription
                subscription = subscriptions[i];

                // If an array for the event name doesn't exist, then generate a new empty array
                // This cannot be done on the function datatype for obvious reasons
                _subscribers[subscription] = _subscribers[subscription] || [];

                // Retrieve the callbacks for the subscription
                functions = _subscribers[subscription];

                // Store the callback
                callback = callbacks[i];

                // The callback should be a function datatype
                if (!isFunction(callback)) {
                    continue;
                }

                // Check if the callback hasn't already been registered for the event name
                if (functions.indexOf(callback) === -1) {
                    // Push the callback function to the event name array
                    functions.push(callback);

                    // An opaque 'PubSub' handle
                    handles.push([_handleId, subscription, callback]);
                }
            }

            // An error occurred as no opaque 'PubSub' handles were pushed to the handles array
            if (handles.length === 0) {
                return isStringTypes ? _handleError : [_handleError];
            }

            // If a string was passed as the first parameter, then return a single handle instead of an array of handles
            return isStringTypes ? handles[0] : handles;
        },

        // Unsubscribe from a subscription. A string and callback function reference are expected OR
        // the handle returned from subscribe()
        // Returns true or false
        unsubscribe: function (subscriptions, callbacks) { // off()
            // If the reference is equal to the handle error array, then an error occurred with subscribing
            if (subscriptions === _handleError) {
                return false;
            }

            // Set the following variable(s), if it's an opaque 'PubSub' handle returned from subscribe()
            if (isHandle(subscriptions)) {
                // Do not swap, otherwise it will cause an error with overwriting subscriptions
                // The value of callbacks will be ignored
                callbacks = [subscriptions[HANDLE_CALLBACK]];
                subscriptions = [subscriptions[HANDLE_SUBSCRIPTION]];
                // If a string and a function datatype, then create an array for each
            } else if (isString(subscriptions) && isFunction(callbacks)) {
                callbacks = [callbacks];
                subscriptions = [subscriptions];
            }

            // If either of the arguments are not an array or the lengths simply mismatch
            if (!Array.isArray(subscriptions) || !Array.isArray(callbacks) || subscriptions.length !== callbacks.length) {
                return false;
            }

            // Variables used with the loop(s)
            var functions = null;
            var index = 0;

            // Iterate through all the subscriptions
            for (var i = 0, subscriptionsLength = subscriptions.length; i < subscriptionsLength; i++) {
                // Retrieve the callbacks for the subscription
                functions = _subscribers[subscriptions[i]];

                // The subscription hasn't been created or there are simply no callbacks assigned
                if (!functions) {
                    continue;
                }

                // If a callback function reference exists for the subscription,
                // then remove from the array using the index value
                index = functions.indexOf(callbacks[i]);
                if (index !== -1) {
                    // Only remove one value
                    functions.splice(index, 1);
                }
            }

            return true;
        },

        // Publish a subscription to all subscribers with an unlimited number of arguments. The subscription is the last argument i.e. arguments[arguments.length]
        // Returns number of subscriptions published
        publish: function (subscriptions) { // emit()
            // Set the following variable(s), if it's an opaque 'PubSub' handle returned from subscribe()
            if (isHandle(subscriptions)) {
                // Convert to an array datatype
                subscriptions = [subscriptions[HANDLE_SUBSCRIPTION]];
                // If a string has been passed, then convert to an array datatype
            } else if (isString(subscriptions)) {
                subscriptions = [subscriptions];
            }

            // Store the number of subscriptions published
            var published = 0;

            // If not an array, then the subscription was either not a valid array, handle or string
            if (!Array.isArray(subscriptions)) {
                return published;
            }

            var i = 0;
            var length = 0;

            // Construct new arguments to pass to the callback function

            // Convert the array-like object to an array
            // URL: https://shifteleven.com/articles/2007/06/28/array-like-objects-in-javascript/
            // var callbackParams = Array.prototype.slice.call(arguments);

            // Remove the first element from the arguments array-like object, as this contains
            // the subscription name that is not required
            // callbackParams.shift(); // Quicker than .splice()

            // According to MDN, this is a safer approach to constructing a new array. It starts from 1 to
            // skip the first parameter which is the subscription name
            // URL: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/arguments
            var callbackParams = [];
            for (i = 1, length = arguments.length; i < length; i++) {
                callbackParams.push(arguments[i]);
            }

            // Push the subscription to the end of the arguments array as a comma separated string,
            // just in case it's required
            callbackParams.push(subscriptions.join(','));

            // Variables used with the loop(s)
            var functionsLength = 0;
            var functions = null;
            var j = 0;

            // Iterate through all the subscriptions
            for (i = 0, length = subscriptions.length; i < length; i++) {
                // Retrieve the callbacks for the subscription
                functions = _subscribers[subscriptions[i]];

                // The subscription hasn't been created or there are simply no callbacks assigned
                if (!functions) {
                    continue;
                }

                // For each callback function, call the function with the callback arguments
                // by using apply() and passing the (new) array of arguments
                for (j = 0, functionsLength = functions.length; j < functionsLength; j++) {
                    functions[j].apply(this, callbackParams);
                    // Increase the number of publish subscriptions
                    published++;
                }
            }

            return published;
        },

        // Clear the internal subscribers store
        clear: function () {
            _subscribers = {};
        },

        // Get the version number of the module
        getVersion: function () {
            return VERSION;
        }
    };
})(Array, Object);

// Demo

// A simple demo of how to use the following PubSub module
// DO NOT USE ANONYMOUS FUNCTIONS AS YOU WILL BE UNABLE TO UNSUBSCRIBE LATER ON

function fireFirst() {
    console.log('fireFirst: %o', arguments);
}

function fireSecond(arg1, arg2) {
    console.log('fireSecond: %o', arguments);
    // console.log('Arguments: %s, %s', arg1, arg2);
}

console.log('!!! START PubSub DEMO');

// Subscribe to an event and bind a callback for each by passing a function reference
var handle = PubSub.subscribe('search/twitter', fireFirst);

// Display the handle returned by the subscription
console.log('Handle: %o', handle);

PubSub.subscribe('search/twitter', fireSecond);

// Publish the event
PubSub.publish('search/twitter', 'Example arg1', 'Example arg2');

// Look in the browser's console

// Destroy the event only for the second function by passing the event name and the function reference i.e. fireSecond (no parentheses)
PubSub.unsubscribe('search/twitter', fireSecond);

// Publish the event
// Notice how there is only one console.log() displayed, which is for the fireFirst() function
console.log('This should NOT show for the fireSecond function');
PubSub.publish('search/twitter', 'Example arg1', 'Example arg2');

// Destroy the event for the fist function by passing the handle
PubSub.unsubscribe(handle);

// Publish the event
// Notice no event fired
console.log('This should NOT show for any events, as all were unsubscribed previously');
PubSub.publish('search/twitter', 'Example arg1', 'Example arg2');

// Test subscribing to multiple subscriptions and unsubscribing
console.log('Testing multiple subscriptions:');
PubSub.subscribe(['search/github', 'search/js'], [fireFirst, fireSecond]);
console.log('Events should display below:');
PubSub.publish(['search/github', 'search/js']);
PubSub.unsubscribe(['search/github', 'search/js'], [fireFirst, fireSecond]);
console.log('Events should NOT display below:');
PubSub.publish(['search/github', 'search/js']);

console.log('!!! END PubSub DEMO');

 

Edited by guinness
Minor updates

UDF List:

 
_AdapterConnections()_AlwaysRun()_AppMon()_AppMonEx()_ArrayFilter/_ArrayReduce_BinaryBin()_CheckMsgBox()_CmdLineRaw()_ContextMenu()_ConvertLHWebColor()/_ConvertSHWebColor()_DesktopDimensions()_DisplayPassword()_DotNet_Load()/_DotNet_Unload()_Fibonacci()_FileCompare()_FileCompareContents()_FileNameByHandle()_FilePrefix/SRE()_FindInFile()_GetBackgroundColor()/_SetBackgroundColor()_GetConrolID()_GetCtrlClass()_GetDirectoryFormat()_GetDriveMediaType()_GetFilename()/_GetFilenameExt()_GetHardwareID()_GetIP()_GetIP_Country()_GetOSLanguage()_GetSavedSource()_GetStringSize()_GetSystemPaths()_GetURLImage()_GIFImage()_GoogleWeather()_GUICtrlCreateGroup()_GUICtrlListBox_CreateArray()_GUICtrlListView_CreateArray()_GUICtrlListView_SaveCSV()_GUICtrlListView_SaveHTML()_GUICtrlListView_SaveTxt()_GUICtrlListView_SaveXML()_GUICtrlMenu_Recent()_GUICtrlMenu_SetItemImage()_GUICtrlTreeView_CreateArray()_GUIDisable()_GUIImageList_SetIconFromHandle()_GUIRegisterMsg()_GUISetIcon()_Icon_Clear()/_Icon_Set()_IdleTime()_InetGet()_InetGetGUI()_InetGetProgress()_IPDetails()_IsFileOlder()_IsGUID()_IsHex()_IsPalindrome()_IsRegKey()_IsStringRegExp()_IsSystemDrive()_IsUPX()_IsValidType()_IsWebColor()_Language()_Log()_MicrosoftInternetConnectivity()_MSDNDataType()_PathFull/GetRelative/Split()_PathSplitEx()_PrintFromArray()_ProgressSetMarquee()_ReDim()_RockPaperScissors()/_RockPaperScissorsLizardSpock()_ScrollingCredits_SelfDelete()_SelfRename()_SelfUpdate()_SendTo()_ShellAll()_ShellFile()_ShellFolder()_SingletonHWID()_SingletonPID()_Startup()_StringCompact()_StringIsValid()_StringRegExpMetaCharacters()_StringReplaceWholeWord()_StringStripChars()_Temperature()_TrialPeriod()_UKToUSDate()/_USToUKDate()_WinAPI_Create_CTL_CODE()_WinAPI_CreateGUID()_WMIDateStringToDate()/_DateToWMIDateString()Au3 script parsingAutoIt SearchAutoIt3 PortableAutoIt3WrapperToPragmaAutoItWinGetTitle()/AutoItWinSetTitle()CodingDirToHTML5FileInstallrFileReadLastChars()GeoIP databaseGUI - Only Close ButtonGUI ExamplesGUICtrlDeleteImage()GUICtrlGetBkColor()GUICtrlGetStyle()GUIEventsGUIGetBkColor()Int_Parse() & Int_TryParse()IsISBN()LockFile()Mapping CtrlIDsOOP in AutoItParseHeadersToSciTE()PasswordValidPasteBinPosts Per DayPreExpandProtect GlobalsQueue()Resource UpdateResourcesExSciTE JumpSettings INISHELLHOOKShunting-YardSignature CreatorStack()Stopwatch()StringAddLF()/StringStripLF()StringEOLToCRLF()VSCROLLWM_COPYDATAMore Examples...

Updated: 22/04/2018

Link to comment
Share on other sites

Wow, time to refactor based on these results >> http://jsperf.com/js-for-loop-vs-array-indexof/8

UDF List:

 
_AdapterConnections()_AlwaysRun()_AppMon()_AppMonEx()_ArrayFilter/_ArrayReduce_BinaryBin()_CheckMsgBox()_CmdLineRaw()_ContextMenu()_ConvertLHWebColor()/_ConvertSHWebColor()_DesktopDimensions()_DisplayPassword()_DotNet_Load()/_DotNet_Unload()_Fibonacci()_FileCompare()_FileCompareContents()_FileNameByHandle()_FilePrefix/SRE()_FindInFile()_GetBackgroundColor()/_SetBackgroundColor()_GetConrolID()_GetCtrlClass()_GetDirectoryFormat()_GetDriveMediaType()_GetFilename()/_GetFilenameExt()_GetHardwareID()_GetIP()_GetIP_Country()_GetOSLanguage()_GetSavedSource()_GetStringSize()_GetSystemPaths()_GetURLImage()_GIFImage()_GoogleWeather()_GUICtrlCreateGroup()_GUICtrlListBox_CreateArray()_GUICtrlListView_CreateArray()_GUICtrlListView_SaveCSV()_GUICtrlListView_SaveHTML()_GUICtrlListView_SaveTxt()_GUICtrlListView_SaveXML()_GUICtrlMenu_Recent()_GUICtrlMenu_SetItemImage()_GUICtrlTreeView_CreateArray()_GUIDisable()_GUIImageList_SetIconFromHandle()_GUIRegisterMsg()_GUISetIcon()_Icon_Clear()/_Icon_Set()_IdleTime()_InetGet()_InetGetGUI()_InetGetProgress()_IPDetails()_IsFileOlder()_IsGUID()_IsHex()_IsPalindrome()_IsRegKey()_IsStringRegExp()_IsSystemDrive()_IsUPX()_IsValidType()_IsWebColor()_Language()_Log()_MicrosoftInternetConnectivity()_MSDNDataType()_PathFull/GetRelative/Split()_PathSplitEx()_PrintFromArray()_ProgressSetMarquee()_ReDim()_RockPaperScissors()/_RockPaperScissorsLizardSpock()_ScrollingCredits_SelfDelete()_SelfRename()_SelfUpdate()_SendTo()_ShellAll()_ShellFile()_ShellFolder()_SingletonHWID()_SingletonPID()_Startup()_StringCompact()_StringIsValid()_StringRegExpMetaCharacters()_StringReplaceWholeWord()_StringStripChars()_Temperature()_TrialPeriod()_UKToUSDate()/_USToUKDate()_WinAPI_Create_CTL_CODE()_WinAPI_CreateGUID()_WMIDateStringToDate()/_DateToWMIDateString()Au3 script parsingAutoIt SearchAutoIt3 PortableAutoIt3WrapperToPragmaAutoItWinGetTitle()/AutoItWinSetTitle()CodingDirToHTML5FileInstallrFileReadLastChars()GeoIP databaseGUI - Only Close ButtonGUI ExamplesGUICtrlDeleteImage()GUICtrlGetBkColor()GUICtrlGetStyle()GUIEventsGUIGetBkColor()Int_Parse() & Int_TryParse()IsISBN()LockFile()Mapping CtrlIDsOOP in AutoItParseHeadersToSciTE()PasswordValidPasteBinPosts Per DayPreExpandProtect GlobalsQueue()Resource UpdateResourcesExSciTE JumpSettings INISHELLHOOKShunting-YardSignature CreatorStack()Stopwatch()StringAddLF()/StringStripLF()StringEOLToCRLF()VSCROLLWM_COPYDATAMore Examples...

Updated: 22/04/2018

Link to comment
Share on other sites

Fixed performance of the code. See first post for update.

UDF List:

 
_AdapterConnections()_AlwaysRun()_AppMon()_AppMonEx()_ArrayFilter/_ArrayReduce_BinaryBin()_CheckMsgBox()_CmdLineRaw()_ContextMenu()_ConvertLHWebColor()/_ConvertSHWebColor()_DesktopDimensions()_DisplayPassword()_DotNet_Load()/_DotNet_Unload()_Fibonacci()_FileCompare()_FileCompareContents()_FileNameByHandle()_FilePrefix/SRE()_FindInFile()_GetBackgroundColor()/_SetBackgroundColor()_GetConrolID()_GetCtrlClass()_GetDirectoryFormat()_GetDriveMediaType()_GetFilename()/_GetFilenameExt()_GetHardwareID()_GetIP()_GetIP_Country()_GetOSLanguage()_GetSavedSource()_GetStringSize()_GetSystemPaths()_GetURLImage()_GIFImage()_GoogleWeather()_GUICtrlCreateGroup()_GUICtrlListBox_CreateArray()_GUICtrlListView_CreateArray()_GUICtrlListView_SaveCSV()_GUICtrlListView_SaveHTML()_GUICtrlListView_SaveTxt()_GUICtrlListView_SaveXML()_GUICtrlMenu_Recent()_GUICtrlMenu_SetItemImage()_GUICtrlTreeView_CreateArray()_GUIDisable()_GUIImageList_SetIconFromHandle()_GUIRegisterMsg()_GUISetIcon()_Icon_Clear()/_Icon_Set()_IdleTime()_InetGet()_InetGetGUI()_InetGetProgress()_IPDetails()_IsFileOlder()_IsGUID()_IsHex()_IsPalindrome()_IsRegKey()_IsStringRegExp()_IsSystemDrive()_IsUPX()_IsValidType()_IsWebColor()_Language()_Log()_MicrosoftInternetConnectivity()_MSDNDataType()_PathFull/GetRelative/Split()_PathSplitEx()_PrintFromArray()_ProgressSetMarquee()_ReDim()_RockPaperScissors()/_RockPaperScissorsLizardSpock()_ScrollingCredits_SelfDelete()_SelfRename()_SelfUpdate()_SendTo()_ShellAll()_ShellFile()_ShellFolder()_SingletonHWID()_SingletonPID()_Startup()_StringCompact()_StringIsValid()_StringRegExpMetaCharacters()_StringReplaceWholeWord()_StringStripChars()_Temperature()_TrialPeriod()_UKToUSDate()/_USToUKDate()_WinAPI_Create_CTL_CODE()_WinAPI_CreateGUID()_WMIDateStringToDate()/_DateToWMIDateString()Au3 script parsingAutoIt SearchAutoIt3 PortableAutoIt3WrapperToPragmaAutoItWinGetTitle()/AutoItWinSetTitle()CodingDirToHTML5FileInstallrFileReadLastChars()GeoIP databaseGUI - Only Close ButtonGUI ExamplesGUICtrlDeleteImage()GUICtrlGetBkColor()GUICtrlGetStyle()GUIEventsGUIGetBkColor()Int_Parse() & Int_TryParse()IsISBN()LockFile()Mapping CtrlIDsOOP in AutoItParseHeadersToSciTE()PasswordValidPasteBinPosts Per DayPreExpandProtect GlobalsQueue()Resource UpdateResourcesExSciTE JumpSettings INISHELLHOOKShunting-YardSignature CreatorStack()Stopwatch()StringAddLF()/StringStripLF()StringEOLToCRLF()VSCROLLWM_COPYDATAMore Examples...

Updated: 22/04/2018

Link to comment
Share on other sites

jQuery version I modified, though not as robust as the one above/below.

/*
 * jQuery Tiny Pub/Sub - v0.1 - 2015/08/07
 * http://benalman.com/
 *
 * Original Copyright (c) 2010 'Cowboy' Ben Alman
 * Dual licensed under the MIT and GPL licenses.
 * http://benalman.com/about/license/
 *
 * Made awesome by Rick Waldron, with additional ideas by Jeffrey Way and softwarespot
 * URL: https://gist.github.com/rwaldron/705311
 */
(function($) {

    // API for PubSub e.g. $.subscribe('example/test'); or $.publish('example/test', function (event, results) {});
    var pubSub = {
            'subscribe': 'on',
            'unsubscribe': 'off',
            'publish': 'trigger'

        },
        store = $({});

    $.each(pubSub, function(callback, api) {
        // Apply the likes of $.subscribe() or $.publish() to jQuery
        $[callback] = function() {
            // When called then invoke the jQuery versions e.g. .on() or .trigger()
            store[api].apply(store, arguments);
        };
    });

})(jQuery);

// Demo

// A simple demo of how to use the following PubSub module
// DO NOT USE ANONYMOUS FUNCTIONS AS YOU WILL BE UNABLE TO UNSUBSCRIBE

function fireFirst() {
    console.log('fireFirst: %o', arguments);
}

function fireSecond(_, arg1) { // _ = event, therefore this can be ignored
    console.log('fireSecond: %o', arguments);
    console.log('Arguments: %s', arg1);
}

// Subscribe to an event and bind a callback for each by passing a function reference
$.subscribe('search/twitter', fireFirst);
$.subscribe('search/twitter', fireSecond);

// Publish the event
$.publish('search/twitter', ['Example arg1', 'Example arg2']); // Must be an array of arguments

// Look in the browser's console

// Destroy the event only for the second function by passing the event name and the function reference i.e. fireSecond (no parentheses)
$.unsubscribe('search/twitter', fireSecond);

// Publish the event
// Notice how there is only one console.log() displayed, which is for the fireFirst() function
$.publish('search/twitter', ['Example arg1', 'Example arg2']); // Must be an array of arguments

 

Edited by guinness

UDF List:

 
_AdapterConnections()_AlwaysRun()_AppMon()_AppMonEx()_ArrayFilter/_ArrayReduce_BinaryBin()_CheckMsgBox()_CmdLineRaw()_ContextMenu()_ConvertLHWebColor()/_ConvertSHWebColor()_DesktopDimensions()_DisplayPassword()_DotNet_Load()/_DotNet_Unload()_Fibonacci()_FileCompare()_FileCompareContents()_FileNameByHandle()_FilePrefix/SRE()_FindInFile()_GetBackgroundColor()/_SetBackgroundColor()_GetConrolID()_GetCtrlClass()_GetDirectoryFormat()_GetDriveMediaType()_GetFilename()/_GetFilenameExt()_GetHardwareID()_GetIP()_GetIP_Country()_GetOSLanguage()_GetSavedSource()_GetStringSize()_GetSystemPaths()_GetURLImage()_GIFImage()_GoogleWeather()_GUICtrlCreateGroup()_GUICtrlListBox_CreateArray()_GUICtrlListView_CreateArray()_GUICtrlListView_SaveCSV()_GUICtrlListView_SaveHTML()_GUICtrlListView_SaveTxt()_GUICtrlListView_SaveXML()_GUICtrlMenu_Recent()_GUICtrlMenu_SetItemImage()_GUICtrlTreeView_CreateArray()_GUIDisable()_GUIImageList_SetIconFromHandle()_GUIRegisterMsg()_GUISetIcon()_Icon_Clear()/_Icon_Set()_IdleTime()_InetGet()_InetGetGUI()_InetGetProgress()_IPDetails()_IsFileOlder()_IsGUID()_IsHex()_IsPalindrome()_IsRegKey()_IsStringRegExp()_IsSystemDrive()_IsUPX()_IsValidType()_IsWebColor()_Language()_Log()_MicrosoftInternetConnectivity()_MSDNDataType()_PathFull/GetRelative/Split()_PathSplitEx()_PrintFromArray()_ProgressSetMarquee()_ReDim()_RockPaperScissors()/_RockPaperScissorsLizardSpock()_ScrollingCredits_SelfDelete()_SelfRename()_SelfUpdate()_SendTo()_ShellAll()_ShellFile()_ShellFolder()_SingletonHWID()_SingletonPID()_Startup()_StringCompact()_StringIsValid()_StringRegExpMetaCharacters()_StringReplaceWholeWord()_StringStripChars()_Temperature()_TrialPeriod()_UKToUSDate()/_USToUKDate()_WinAPI_Create_CTL_CODE()_WinAPI_CreateGUID()_WMIDateStringToDate()/_DateToWMIDateString()Au3 script parsingAutoIt SearchAutoIt3 PortableAutoIt3WrapperToPragmaAutoItWinGetTitle()/AutoItWinSetTitle()CodingDirToHTML5FileInstallrFileReadLastChars()GeoIP databaseGUI - Only Close ButtonGUI ExamplesGUICtrlDeleteImage()GUICtrlGetBkColor()GUICtrlGetStyle()GUIEventsGUIGetBkColor()Int_Parse() & Int_TryParse()IsISBN()LockFile()Mapping CtrlIDsOOP in AutoItParseHeadersToSciTE()PasswordValidPasteBinPosts Per DayPreExpandProtect GlobalsQueue()Resource UpdateResourcesExSciTE JumpSettings INISHELLHOOKShunting-YardSignature CreatorStack()Stopwatch()StringAddLF()/StringStripLF()StringEOLToCRLF()VSCROLLWM_COPYDATAMore Examples...

Updated: 22/04/2018

Link to comment
Share on other sites

First post updated. Now using a IIFE, as a way to encapsulate the private variable. I was happy with the previous object literal module pattern, but I think I am going to publish this on github for reals! So kind of want to to be up to scratch.

UDF List:

 
_AdapterConnections()_AlwaysRun()_AppMon()_AppMonEx()_ArrayFilter/_ArrayReduce_BinaryBin()_CheckMsgBox()_CmdLineRaw()_ContextMenu()_ConvertLHWebColor()/_ConvertSHWebColor()_DesktopDimensions()_DisplayPassword()_DotNet_Load()/_DotNet_Unload()_Fibonacci()_FileCompare()_FileCompareContents()_FileNameByHandle()_FilePrefix/SRE()_FindInFile()_GetBackgroundColor()/_SetBackgroundColor()_GetConrolID()_GetCtrlClass()_GetDirectoryFormat()_GetDriveMediaType()_GetFilename()/_GetFilenameExt()_GetHardwareID()_GetIP()_GetIP_Country()_GetOSLanguage()_GetSavedSource()_GetStringSize()_GetSystemPaths()_GetURLImage()_GIFImage()_GoogleWeather()_GUICtrlCreateGroup()_GUICtrlListBox_CreateArray()_GUICtrlListView_CreateArray()_GUICtrlListView_SaveCSV()_GUICtrlListView_SaveHTML()_GUICtrlListView_SaveTxt()_GUICtrlListView_SaveXML()_GUICtrlMenu_Recent()_GUICtrlMenu_SetItemImage()_GUICtrlTreeView_CreateArray()_GUIDisable()_GUIImageList_SetIconFromHandle()_GUIRegisterMsg()_GUISetIcon()_Icon_Clear()/_Icon_Set()_IdleTime()_InetGet()_InetGetGUI()_InetGetProgress()_IPDetails()_IsFileOlder()_IsGUID()_IsHex()_IsPalindrome()_IsRegKey()_IsStringRegExp()_IsSystemDrive()_IsUPX()_IsValidType()_IsWebColor()_Language()_Log()_MicrosoftInternetConnectivity()_MSDNDataType()_PathFull/GetRelative/Split()_PathSplitEx()_PrintFromArray()_ProgressSetMarquee()_ReDim()_RockPaperScissors()/_RockPaperScissorsLizardSpock()_ScrollingCredits_SelfDelete()_SelfRename()_SelfUpdate()_SendTo()_ShellAll()_ShellFile()_ShellFolder()_SingletonHWID()_SingletonPID()_Startup()_StringCompact()_StringIsValid()_StringRegExpMetaCharacters()_StringReplaceWholeWord()_StringStripChars()_Temperature()_TrialPeriod()_UKToUSDate()/_USToUKDate()_WinAPI_Create_CTL_CODE()_WinAPI_CreateGUID()_WMIDateStringToDate()/_DateToWMIDateString()Au3 script parsingAutoIt SearchAutoIt3 PortableAutoIt3WrapperToPragmaAutoItWinGetTitle()/AutoItWinSetTitle()CodingDirToHTML5FileInstallrFileReadLastChars()GeoIP databaseGUI - Only Close ButtonGUI ExamplesGUICtrlDeleteImage()GUICtrlGetBkColor()GUICtrlGetStyle()GUIEventsGUIGetBkColor()Int_Parse() & Int_TryParse()IsISBN()LockFile()Mapping CtrlIDsOOP in AutoItParseHeadersToSciTE()PasswordValidPasteBinPosts Per DayPreExpandProtect GlobalsQueue()Resource UpdateResourcesExSciTE JumpSettings INISHELLHOOKShunting-YardSignature CreatorStack()Stopwatch()StringAddLF()/StringStripLF()StringEOLToCRLF()VSCROLLWM_COPYDATAMore Examples...

Updated: 22/04/2018

Link to comment
Share on other sites

Kind of finalised now. Just added an opaque handle that is returned from subscribe() which can optionally be passed to publish or unsubscribe, though the conventional string and callback function approach are still available. It's basically a lot more flexible. An idea I got from >> https://github.com/mroderick/PubSubJS/blob/master/src/pubsub.js

UDF List:

 
_AdapterConnections()_AlwaysRun()_AppMon()_AppMonEx()_ArrayFilter/_ArrayReduce_BinaryBin()_CheckMsgBox()_CmdLineRaw()_ContextMenu()_ConvertLHWebColor()/_ConvertSHWebColor()_DesktopDimensions()_DisplayPassword()_DotNet_Load()/_DotNet_Unload()_Fibonacci()_FileCompare()_FileCompareContents()_FileNameByHandle()_FilePrefix/SRE()_FindInFile()_GetBackgroundColor()/_SetBackgroundColor()_GetConrolID()_GetCtrlClass()_GetDirectoryFormat()_GetDriveMediaType()_GetFilename()/_GetFilenameExt()_GetHardwareID()_GetIP()_GetIP_Country()_GetOSLanguage()_GetSavedSource()_GetStringSize()_GetSystemPaths()_GetURLImage()_GIFImage()_GoogleWeather()_GUICtrlCreateGroup()_GUICtrlListBox_CreateArray()_GUICtrlListView_CreateArray()_GUICtrlListView_SaveCSV()_GUICtrlListView_SaveHTML()_GUICtrlListView_SaveTxt()_GUICtrlListView_SaveXML()_GUICtrlMenu_Recent()_GUICtrlMenu_SetItemImage()_GUICtrlTreeView_CreateArray()_GUIDisable()_GUIImageList_SetIconFromHandle()_GUIRegisterMsg()_GUISetIcon()_Icon_Clear()/_Icon_Set()_IdleTime()_InetGet()_InetGetGUI()_InetGetProgress()_IPDetails()_IsFileOlder()_IsGUID()_IsHex()_IsPalindrome()_IsRegKey()_IsStringRegExp()_IsSystemDrive()_IsUPX()_IsValidType()_IsWebColor()_Language()_Log()_MicrosoftInternetConnectivity()_MSDNDataType()_PathFull/GetRelative/Split()_PathSplitEx()_PrintFromArray()_ProgressSetMarquee()_ReDim()_RockPaperScissors()/_RockPaperScissorsLizardSpock()_ScrollingCredits_SelfDelete()_SelfRename()_SelfUpdate()_SendTo()_ShellAll()_ShellFile()_ShellFolder()_SingletonHWID()_SingletonPID()_Startup()_StringCompact()_StringIsValid()_StringRegExpMetaCharacters()_StringReplaceWholeWord()_StringStripChars()_Temperature()_TrialPeriod()_UKToUSDate()/_USToUKDate()_WinAPI_Create_CTL_CODE()_WinAPI_CreateGUID()_WMIDateStringToDate()/_DateToWMIDateString()Au3 script parsingAutoIt SearchAutoIt3 PortableAutoIt3WrapperToPragmaAutoItWinGetTitle()/AutoItWinSetTitle()CodingDirToHTML5FileInstallrFileReadLastChars()GeoIP databaseGUI - Only Close ButtonGUI ExamplesGUICtrlDeleteImage()GUICtrlGetBkColor()GUICtrlGetStyle()GUIEventsGUIGetBkColor()Int_Parse() & Int_TryParse()IsISBN()LockFile()Mapping CtrlIDsOOP in AutoItParseHeadersToSciTE()PasswordValidPasteBinPosts Per DayPreExpandProtect GlobalsQueue()Resource UpdateResourcesExSciTE JumpSettings INISHELLHOOKShunting-YardSignature CreatorStack()Stopwatch()StringAddLF()/StringStripLF()StringEOLToCRLF()VSCROLLWM_COPYDATAMore Examples...

Updated: 22/04/2018

Link to comment
Share on other sites

This version has a sophisticated API compared to the first version and I have implemented the ability to register multiple subscriptions at once e.g.

// New API

// Single or Multiple registration
PubSub.subscribe(['sub1', 'sub2'], [callback1, callback2]);
PubSub.subscribe('pubsub', callback3);
PubSub.publish(['sub1', 'sub2', 'sub3']);
PubSub.unsubscribe(['sub1', 'sub2', 'sub3'], [callback1, callback2, callback3]);

// Handle/token usage
var handle = PubSub.subscribe('sub4', callback4);
PubSub.publish(handle);
PubSub.unsubscribe(handle);

See post #1 for PubSub module.

Edited by guinness

UDF List:

 
_AdapterConnections()_AlwaysRun()_AppMon()_AppMonEx()_ArrayFilter/_ArrayReduce_BinaryBin()_CheckMsgBox()_CmdLineRaw()_ContextMenu()_ConvertLHWebColor()/_ConvertSHWebColor()_DesktopDimensions()_DisplayPassword()_DotNet_Load()/_DotNet_Unload()_Fibonacci()_FileCompare()_FileCompareContents()_FileNameByHandle()_FilePrefix/SRE()_FindInFile()_GetBackgroundColor()/_SetBackgroundColor()_GetConrolID()_GetCtrlClass()_GetDirectoryFormat()_GetDriveMediaType()_GetFilename()/_GetFilenameExt()_GetHardwareID()_GetIP()_GetIP_Country()_GetOSLanguage()_GetSavedSource()_GetStringSize()_GetSystemPaths()_GetURLImage()_GIFImage()_GoogleWeather()_GUICtrlCreateGroup()_GUICtrlListBox_CreateArray()_GUICtrlListView_CreateArray()_GUICtrlListView_SaveCSV()_GUICtrlListView_SaveHTML()_GUICtrlListView_SaveTxt()_GUICtrlListView_SaveXML()_GUICtrlMenu_Recent()_GUICtrlMenu_SetItemImage()_GUICtrlTreeView_CreateArray()_GUIDisable()_GUIImageList_SetIconFromHandle()_GUIRegisterMsg()_GUISetIcon()_Icon_Clear()/_Icon_Set()_IdleTime()_InetGet()_InetGetGUI()_InetGetProgress()_IPDetails()_IsFileOlder()_IsGUID()_IsHex()_IsPalindrome()_IsRegKey()_IsStringRegExp()_IsSystemDrive()_IsUPX()_IsValidType()_IsWebColor()_Language()_Log()_MicrosoftInternetConnectivity()_MSDNDataType()_PathFull/GetRelative/Split()_PathSplitEx()_PrintFromArray()_ProgressSetMarquee()_ReDim()_RockPaperScissors()/_RockPaperScissorsLizardSpock()_ScrollingCredits_SelfDelete()_SelfRename()_SelfUpdate()_SendTo()_ShellAll()_ShellFile()_ShellFolder()_SingletonHWID()_SingletonPID()_Startup()_StringCompact()_StringIsValid()_StringRegExpMetaCharacters()_StringReplaceWholeWord()_StringStripChars()_Temperature()_TrialPeriod()_UKToUSDate()/_USToUKDate()_WinAPI_Create_CTL_CODE()_WinAPI_CreateGUID()_WMIDateStringToDate()/_DateToWMIDateString()Au3 script parsingAutoIt SearchAutoIt3 PortableAutoIt3WrapperToPragmaAutoItWinGetTitle()/AutoItWinSetTitle()CodingDirToHTML5FileInstallrFileReadLastChars()GeoIP databaseGUI - Only Close ButtonGUI ExamplesGUICtrlDeleteImage()GUICtrlGetBkColor()GUICtrlGetStyle()GUIEventsGUIGetBkColor()Int_Parse() & Int_TryParse()IsISBN()LockFile()Mapping CtrlIDsOOP in AutoItParseHeadersToSciTE()PasswordValidPasteBinPosts Per DayPreExpandProtect GlobalsQueue()Resource UpdateResourcesExSciTE JumpSettings INISHELLHOOKShunting-YardSignature CreatorStack()Stopwatch()StringAddLF()/StringStripLF()StringEOLToCRLF()VSCROLLWM_COPYDATAMore Examples...

Updated: 22/04/2018

Link to comment
Share on other sites

  • 2 weeks later...

Updated the PubSub module in post #1.

UDF List:

 
_AdapterConnections()_AlwaysRun()_AppMon()_AppMonEx()_ArrayFilter/_ArrayReduce_BinaryBin()_CheckMsgBox()_CmdLineRaw()_ContextMenu()_ConvertLHWebColor()/_ConvertSHWebColor()_DesktopDimensions()_DisplayPassword()_DotNet_Load()/_DotNet_Unload()_Fibonacci()_FileCompare()_FileCompareContents()_FileNameByHandle()_FilePrefix/SRE()_FindInFile()_GetBackgroundColor()/_SetBackgroundColor()_GetConrolID()_GetCtrlClass()_GetDirectoryFormat()_GetDriveMediaType()_GetFilename()/_GetFilenameExt()_GetHardwareID()_GetIP()_GetIP_Country()_GetOSLanguage()_GetSavedSource()_GetStringSize()_GetSystemPaths()_GetURLImage()_GIFImage()_GoogleWeather()_GUICtrlCreateGroup()_GUICtrlListBox_CreateArray()_GUICtrlListView_CreateArray()_GUICtrlListView_SaveCSV()_GUICtrlListView_SaveHTML()_GUICtrlListView_SaveTxt()_GUICtrlListView_SaveXML()_GUICtrlMenu_Recent()_GUICtrlMenu_SetItemImage()_GUICtrlTreeView_CreateArray()_GUIDisable()_GUIImageList_SetIconFromHandle()_GUIRegisterMsg()_GUISetIcon()_Icon_Clear()/_Icon_Set()_IdleTime()_InetGet()_InetGetGUI()_InetGetProgress()_IPDetails()_IsFileOlder()_IsGUID()_IsHex()_IsPalindrome()_IsRegKey()_IsStringRegExp()_IsSystemDrive()_IsUPX()_IsValidType()_IsWebColor()_Language()_Log()_MicrosoftInternetConnectivity()_MSDNDataType()_PathFull/GetRelative/Split()_PathSplitEx()_PrintFromArray()_ProgressSetMarquee()_ReDim()_RockPaperScissors()/_RockPaperScissorsLizardSpock()_ScrollingCredits_SelfDelete()_SelfRename()_SelfUpdate()_SendTo()_ShellAll()_ShellFile()_ShellFolder()_SingletonHWID()_SingletonPID()_Startup()_StringCompact()_StringIsValid()_StringRegExpMetaCharacters()_StringReplaceWholeWord()_StringStripChars()_Temperature()_TrialPeriod()_UKToUSDate()/_USToUKDate()_WinAPI_Create_CTL_CODE()_WinAPI_CreateGUID()_WMIDateStringToDate()/_DateToWMIDateString()Au3 script parsingAutoIt SearchAutoIt3 PortableAutoIt3WrapperToPragmaAutoItWinGetTitle()/AutoItWinSetTitle()CodingDirToHTML5FileInstallrFileReadLastChars()GeoIP databaseGUI - Only Close ButtonGUI ExamplesGUICtrlDeleteImage()GUICtrlGetBkColor()GUICtrlGetStyle()GUIEventsGUIGetBkColor()Int_Parse() & Int_TryParse()IsISBN()LockFile()Mapping CtrlIDsOOP in AutoItParseHeadersToSciTE()PasswordValidPasteBinPosts Per DayPreExpandProtect GlobalsQueue()Resource UpdateResourcesExSciTE JumpSettings INISHELLHOOKShunting-YardSignature CreatorStack()Stopwatch()StringAddLF()/StringStripLF()StringEOLToCRLF()VSCROLLWM_COPYDATAMore Examples...

Updated: 22/04/2018

Link to comment
Share on other sites

Updated the PubSub module in post #1.

UDF List:

 
_AdapterConnections()_AlwaysRun()_AppMon()_AppMonEx()_ArrayFilter/_ArrayReduce_BinaryBin()_CheckMsgBox()_CmdLineRaw()_ContextMenu()_ConvertLHWebColor()/_ConvertSHWebColor()_DesktopDimensions()_DisplayPassword()_DotNet_Load()/_DotNet_Unload()_Fibonacci()_FileCompare()_FileCompareContents()_FileNameByHandle()_FilePrefix/SRE()_FindInFile()_GetBackgroundColor()/_SetBackgroundColor()_GetConrolID()_GetCtrlClass()_GetDirectoryFormat()_GetDriveMediaType()_GetFilename()/_GetFilenameExt()_GetHardwareID()_GetIP()_GetIP_Country()_GetOSLanguage()_GetSavedSource()_GetStringSize()_GetSystemPaths()_GetURLImage()_GIFImage()_GoogleWeather()_GUICtrlCreateGroup()_GUICtrlListBox_CreateArray()_GUICtrlListView_CreateArray()_GUICtrlListView_SaveCSV()_GUICtrlListView_SaveHTML()_GUICtrlListView_SaveTxt()_GUICtrlListView_SaveXML()_GUICtrlMenu_Recent()_GUICtrlMenu_SetItemImage()_GUICtrlTreeView_CreateArray()_GUIDisable()_GUIImageList_SetIconFromHandle()_GUIRegisterMsg()_GUISetIcon()_Icon_Clear()/_Icon_Set()_IdleTime()_InetGet()_InetGetGUI()_InetGetProgress()_IPDetails()_IsFileOlder()_IsGUID()_IsHex()_IsPalindrome()_IsRegKey()_IsStringRegExp()_IsSystemDrive()_IsUPX()_IsValidType()_IsWebColor()_Language()_Log()_MicrosoftInternetConnectivity()_MSDNDataType()_PathFull/GetRelative/Split()_PathSplitEx()_PrintFromArray()_ProgressSetMarquee()_ReDim()_RockPaperScissors()/_RockPaperScissorsLizardSpock()_ScrollingCredits_SelfDelete()_SelfRename()_SelfUpdate()_SendTo()_ShellAll()_ShellFile()_ShellFolder()_SingletonHWID()_SingletonPID()_Startup()_StringCompact()_StringIsValid()_StringRegExpMetaCharacters()_StringReplaceWholeWord()_StringStripChars()_Temperature()_TrialPeriod()_UKToUSDate()/_USToUKDate()_WinAPI_Create_CTL_CODE()_WinAPI_CreateGUID()_WMIDateStringToDate()/_DateToWMIDateString()Au3 script parsingAutoIt SearchAutoIt3 PortableAutoIt3WrapperToPragmaAutoItWinGetTitle()/AutoItWinSetTitle()CodingDirToHTML5FileInstallrFileReadLastChars()GeoIP databaseGUI - Only Close ButtonGUI ExamplesGUICtrlDeleteImage()GUICtrlGetBkColor()GUICtrlGetStyle()GUIEventsGUIGetBkColor()Int_Parse() & Int_TryParse()IsISBN()LockFile()Mapping CtrlIDsOOP in AutoItParseHeadersToSciTE()PasswordValidPasteBinPosts Per DayPreExpandProtect GlobalsQueue()Resource UpdateResourcesExSciTE JumpSettings INISHELLHOOKShunting-YardSignature CreatorStack()Stopwatch()StringAddLF()/StringStripLF()StringEOLToCRLF()VSCROLLWM_COPYDATAMore Examples...

Updated: 22/04/2018

Link to comment
Share on other sites

Opted for using leet speak identifier instead of guid. In the next couple of days I will be working an ES2015 version, which can easily be converted to ES5 using a transpiler e.g. babel.

UDF List:

 
_AdapterConnections()_AlwaysRun()_AppMon()_AppMonEx()_ArrayFilter/_ArrayReduce_BinaryBin()_CheckMsgBox()_CmdLineRaw()_ContextMenu()_ConvertLHWebColor()/_ConvertSHWebColor()_DesktopDimensions()_DisplayPassword()_DotNet_Load()/_DotNet_Unload()_Fibonacci()_FileCompare()_FileCompareContents()_FileNameByHandle()_FilePrefix/SRE()_FindInFile()_GetBackgroundColor()/_SetBackgroundColor()_GetConrolID()_GetCtrlClass()_GetDirectoryFormat()_GetDriveMediaType()_GetFilename()/_GetFilenameExt()_GetHardwareID()_GetIP()_GetIP_Country()_GetOSLanguage()_GetSavedSource()_GetStringSize()_GetSystemPaths()_GetURLImage()_GIFImage()_GoogleWeather()_GUICtrlCreateGroup()_GUICtrlListBox_CreateArray()_GUICtrlListView_CreateArray()_GUICtrlListView_SaveCSV()_GUICtrlListView_SaveHTML()_GUICtrlListView_SaveTxt()_GUICtrlListView_SaveXML()_GUICtrlMenu_Recent()_GUICtrlMenu_SetItemImage()_GUICtrlTreeView_CreateArray()_GUIDisable()_GUIImageList_SetIconFromHandle()_GUIRegisterMsg()_GUISetIcon()_Icon_Clear()/_Icon_Set()_IdleTime()_InetGet()_InetGetGUI()_InetGetProgress()_IPDetails()_IsFileOlder()_IsGUID()_IsHex()_IsPalindrome()_IsRegKey()_IsStringRegExp()_IsSystemDrive()_IsUPX()_IsValidType()_IsWebColor()_Language()_Log()_MicrosoftInternetConnectivity()_MSDNDataType()_PathFull/GetRelative/Split()_PathSplitEx()_PrintFromArray()_ProgressSetMarquee()_ReDim()_RockPaperScissors()/_RockPaperScissorsLizardSpock()_ScrollingCredits_SelfDelete()_SelfRename()_SelfUpdate()_SendTo()_ShellAll()_ShellFile()_ShellFolder()_SingletonHWID()_SingletonPID()_Startup()_StringCompact()_StringIsValid()_StringRegExpMetaCharacters()_StringReplaceWholeWord()_StringStripChars()_Temperature()_TrialPeriod()_UKToUSDate()/_USToUKDate()_WinAPI_Create_CTL_CODE()_WinAPI_CreateGUID()_WMIDateStringToDate()/_DateToWMIDateString()Au3 script parsingAutoIt SearchAutoIt3 PortableAutoIt3WrapperToPragmaAutoItWinGetTitle()/AutoItWinSetTitle()CodingDirToHTML5FileInstallrFileReadLastChars()GeoIP databaseGUI - Only Close ButtonGUI ExamplesGUICtrlDeleteImage()GUICtrlGetBkColor()GUICtrlGetStyle()GUIEventsGUIGetBkColor()Int_Parse() & Int_TryParse()IsISBN()LockFile()Mapping CtrlIDsOOP in AutoItParseHeadersToSciTE()PasswordValidPasteBinPosts Per DayPreExpandProtect GlobalsQueue()Resource UpdateResourcesExSciTE JumpSettings INISHELLHOOKShunting-YardSignature CreatorStack()Stopwatch()StringAddLF()/StringStripLF()StringEOLToCRLF()VSCROLLWM_COPYDATAMore Examples...

Updated: 22/04/2018

Link to comment
Share on other sites

Here is an ES2015 version

//
// PubSub pattern in JavaScript
//      By LearnCodeAcademy, URL: https://gist.github.com/learncodeacademy/777349747d8382bfb722
//
// Additional info:
//      Tuts+, URL: https://www.youtube.com/watch?v=U4cigUpzW2U&list=PL15G0RGjxzGdBqDPF4DDrlOt9YmjampzO&index=8
//      Peter Higgins, URL: https://github.com/phiggins42/bloody-jquery-plugins/blob/master/pubsub.js
//      PubSubJS, handle/token concept, URL: https://github.com/mroderick/PubSubJS/blob/master/src/pubsub.js
//      PubSub, subscription array concept, URL: https://github.com/drublic/PubSub/
//      PubSub Wikipedia, URL: https://en.wikipedia.org/wiki/Publish–subscribe_pattern
//
// Performance tests:
//      forEach-vs-for-loop: http://jsperf.com/array-foreach-vs-for-loop/5
//      for-loop-vs-indexOf: http://jsperf.com/js-for-loop-vs-array-indexof/8
//      shift-vs-splice: http://jsperf.com/shift-vs-splice
//

/**
 * PubSub module
 *
 * Modified: 2015/08/27
 * Note: The following is written in ES2015/6, therefore it will not work on all browsers just yet
 * @author softwarespot
 */
let PubSub = (function (Array, Object) {
    // Constants

    // Version number of the module
    const VERSION = 0.1;

    // Array constant enumeration
    const HANDLE_ID = 0, HANDLE_SUBSCRIPTION = 1, HANDLE_CALLBACK = 2, HANDLE_MAX = 3;

    // Return strings of toString() found on the Object prototype
    const ObjectStrings = {
        FUNCTION: '[object Function]',
        STRING: '[object String]'
    };

    // Fields

    // Hold event names with an array of callbacks for each one
    let _subscribers = {};

    // Unique identifier. Leet speak for PubSub_Module
    let _handleId = '|>|_|85|_|8_|\\/|0|)|_|13';

    // Generic handle for an error. This is an array so the reference can be used as a
    // way of verifying that it's an error
    let _handleError = [_handleId];

    // Store the Object toString method
    let _objectToString = Object.prototype.toString;

    // Methods

    // Check if a value is a function. Based on the idea by lodash
    let isFunction = function (value) {

        return isObject(value) && _objectToString.call(value) === ObjectStrings.FUNCTION;

    };

    // Check if an opaque 'PubSub' handle is valid
    let isHandle = function (handle) {

        return Array.isArray(handle) && handle.length === HANDLE_MAX && handle[HANDLE_ID] === _handleId;

    };

    // Check if a value is an object. Based on the idea by lodash
    let isObject = function (value) {

        // Store the typeof
        let type = typeof value;

        // !!value is basically checking if value is not 'truthy' e.g. null or zero and then inverts that boolean value
        // So, !'Some test' is false and then inverting false is true. There if value contains 'something', continue
        return !!value && (type === 'object' || type === 'function');

    };

    // Check if a value is a string datatype with a length greater than zero when whitespace is stripped. Based partially on the idea by lodash
    let isString = function (value) {

        return (typeof value  === 'string' || _objectToString.call(value) === ObjectStrings.STRING) && value.trim().length > 0;

    };

    // Public API
    return {
        // Subscribe to a subscription with a callback function. It's best practice not to make this an anonymous function
        // as then you can't properly unsubscribe, since the callback function reference is required
        // Returns an opaque handle for use with unsubscribe(), though it's optional to use
        subscribe: function (subscriptions, callbacks) { // on()
            // Store whether the first param is a string
            let isStringTypes = isString(subscriptions) && isFunction(callbacks);

            // If a string and a function datatype, then create an array for each
            if (isStringTypes) {
                callbacks = [callbacks];
                subscriptions = [subscriptions];
            }

            // If either of the arguments are not an array or the lengths simply mismatch
            if (!Array.isArray(subscriptions) || !Array.isArray(callbacks) || subscriptions.length !== callbacks.length) {
                return _handleError;
            }

            // Return array of handles i.e. [handle id, subscription, callback]
            let handles = [];

            // Iterate through all the subscriptions
            for(let i = 0, subscriptionsLength = subscriptions.length; i < subscriptionsLength; i++) {
                // Store the subscription
                let subscription = subscriptions[i];

                // If an array for the event name doesn't exist, then generate a new empty array
                // This cannot be done on the function datatype for obvious reasons
                _subscribers[subscription] = _subscribers[subscription] || [];

                // Retrieve the callbacks for the subscription
                let functions = _subscribers[subscription];

                // Store the callback
                let callback = callbacks[i];

                // The callback should be a function datatype
                if (!isFunction(callback)) {
                    continue;
                }

                // Check if the callback hasn't already been registered for the event name
                if (!functions.includes(callback)) {
                    // Push the callback function to the event name array
                    functions.push(callback);

                    // An opaque 'PubSub' handle
                    handles.push([_handleId, subscription, callback]);
                }
            }

            // An error occurred as no opaque 'PubSub' handles were pushed to the handles array
            if (handles.length === 0) {
                return isStringTypes ? _handleError : [_handleError];
            }

            // If a string was passed as the first parameter, then return a single handle instead of an array of handles
            return isStringTypes ? handles[0] : handles;
        },

        // Unsubscribe from a subscription. A string and callback function reference are expected OR
        // the handle returned from subscribe()
        // Returns true or false
        unsubscribe: function (subscriptions, callbacks) { // off()
            // If the reference is equal to the handle error array, then an error occurred with subscribing
            if (subscriptions === _handleError) {
                return false;
            }

            // Set the following variable(s), if it's an opaque 'PubSub' handle returned from subscribe()
            if (isHandle(subscriptions)) {
                // Do not swap, otherwise it will cause an error with overwriting subscriptions
                // The value of callbacks will be ignored
                callbacks = [subscriptions[HANDLE_CALLBACK]];
                subscriptions = [subscriptions[HANDLE_SUBSCRIPTION]];
            // If a string and a function datatype, then create an array for each
            } else if (isString(subscriptions) && isFunction(callbacks)) {
                callbacks = [callbacks];
                subscriptions = [subscriptions];
            }

            // If either of the arguments are not an array or the lengths simply mismatch
            if (!Array.isArray(subscriptions) || !Array.isArray(callbacks) || subscriptions.length !== callbacks.length) {
                return false;
            }

            // Iterate through all the subscriptions
            for(let i = 0, subscriptionsLength = subscriptions.length; i < subscriptionsLength; i++) {
                // Retrieve the callbacks for the subscription
                let functions = _subscribers[subscriptions[i]];

                // The subscription hasn't been created or there are simply no callbacks assigned
                if (!functions) {
                    continue;
                }

                // If a callback function reference exists for the subscription,
                // then remove from the array using the index value
                let index = functions.indexOf(callbacks[i]);
                if (index !== -1) {
                    // Only remove one value
                    functions.splice(index, 1);
                }
            }

            return true;
        },

        // Publish a subscription to all subscribers with an unlimited number of arguments. The subscription is the last argument i.e. arguments[arguments.length]
        // Returns number of subscriptions published
        publish: function (subscriptions, ...args) { // emit()
            // Set the following variable(s), if it's an opaque 'PubSub' handle returned from subscribe()
            if (isHandle(subscriptions)) {
                // Convert to an array datatype
                subscriptions = [subscriptions[HANDLE_SUBSCRIPTION]];
            // If a string has been passed, then convert to an array datatype
            } else if (isString(subscriptions)) {
                subscriptions = [subscriptions];
            }

            // Store the number of subscriptions published
            let published = 0;

            // If not an array, then the subscription was either not a valid array, handle or string
            if (!Array.isArray(subscriptions)) {
                return published;
            }

            // Push the subscription to the end of the arguments array as a comma separated string,
            // just in case it's required
            args.push(subscriptions.join(','));

            // Iterate through all the subscriptions
            for(let i = 0, length = subscriptions.length; i < length; i++) {
                // Retrieve the callbacks for the subscription
                let functions = _subscribers[subscriptions[i]];

                // The subscription hasn't been created or there are simply no callbacks assigned
                if (!functions) {
                    continue;
                }

                // For each callback function, call the function with the callback arguments
                // by using apply() and passing the (new) array of arguments
                for (let j = 0, functionsLength = functions.length; j < functionsLength; j++) {
                    functions[j].apply(this, args);
                    // Increase the number of publish subscriptions
                    published++;
                }
            }

            return published;
        },

        // Clear the internal subscribers store
        clear: function () {
            _subscribers = {};
        },

        // Get the version number of the module
        getVersion: function () {
            return VERSION;
        }
    };
})(Array, Object);

// Demo

// A simple demo of how to use the following PubSub module
// DO NOT USE ANONYMOUS FUNCTIONS AS YOU WILL BE UNABLE TO UNSUBSCRIBE LATER ON

function fireFirst() {
    console.log('fireFirst: %o', arguments);
}

function fireSecond(arg1, arg2) {
    console.log('fireSecond: %o', arguments);
    // console.log('Arguments: %s, %s', arg1, arg2);
}

console.log('!!! START PubSub DEMO');

// Subscribe to an event and bind a callback for each by passing a function reference
let handle = PubSub.subscribe('search/twitter', fireFirst);

// Display the handle returned by the subscription
console.log('Handle: %o', handle);

PubSub.subscribe('search/twitter', fireSecond);

// Publish the event
PubSub.publish('search/twitter', 'Example arg1', 'Example arg2');

// Look in the browser's console

// Destroy the event only for the second function by passing the event name and the function reference i.e. fireSecond (no parentheses)
PubSub.unsubscribe('search/twitter', fireSecond);

// Publish the event
// Notice how there is only one console.log() displayed, which is for the fireFirst() function
console.log('This should NOT show for the fireSecond function');
PubSub.publish('search/twitter', 'Example arg1', 'Example arg2');

// Destroy the event for the fist function by passing the handle
PubSub.unsubscribe(handle);

// Publish the event
// Notice no event fired
console.log('This should NOT show for any events, as all were unsubscribed previously');
PubSub.publish('search/twitter', 'Example arg1', 'Example arg2');

// Test subscribing to multiple subscriptions and unsubscribing
console.log('Testing multiple subscriptions:');
PubSub.subscribe(['search/github', 'search/js'], [fireFirst, fireSecond]);
console.log('Events should display below:');
PubSub.publish(['search/github', 'search/js']);
PubSub.unsubscribe(['search/github', 'search/js'], [fireFirst, fireSecond]);
console.log('Events should NOT display below:');
PubSub.publish(['search/github', 'search/js']);

console.log('!!! END PubSub DEMO');

I am loving let (block scope variables) and rest params =)

Edited by guinness
Using JS syntax highlighting

UDF List:

 
_AdapterConnections()_AlwaysRun()_AppMon()_AppMonEx()_ArrayFilter/_ArrayReduce_BinaryBin()_CheckMsgBox()_CmdLineRaw()_ContextMenu()_ConvertLHWebColor()/_ConvertSHWebColor()_DesktopDimensions()_DisplayPassword()_DotNet_Load()/_DotNet_Unload()_Fibonacci()_FileCompare()_FileCompareContents()_FileNameByHandle()_FilePrefix/SRE()_FindInFile()_GetBackgroundColor()/_SetBackgroundColor()_GetConrolID()_GetCtrlClass()_GetDirectoryFormat()_GetDriveMediaType()_GetFilename()/_GetFilenameExt()_GetHardwareID()_GetIP()_GetIP_Country()_GetOSLanguage()_GetSavedSource()_GetStringSize()_GetSystemPaths()_GetURLImage()_GIFImage()_GoogleWeather()_GUICtrlCreateGroup()_GUICtrlListBox_CreateArray()_GUICtrlListView_CreateArray()_GUICtrlListView_SaveCSV()_GUICtrlListView_SaveHTML()_GUICtrlListView_SaveTxt()_GUICtrlListView_SaveXML()_GUICtrlMenu_Recent()_GUICtrlMenu_SetItemImage()_GUICtrlTreeView_CreateArray()_GUIDisable()_GUIImageList_SetIconFromHandle()_GUIRegisterMsg()_GUISetIcon()_Icon_Clear()/_Icon_Set()_IdleTime()_InetGet()_InetGetGUI()_InetGetProgress()_IPDetails()_IsFileOlder()_IsGUID()_IsHex()_IsPalindrome()_IsRegKey()_IsStringRegExp()_IsSystemDrive()_IsUPX()_IsValidType()_IsWebColor()_Language()_Log()_MicrosoftInternetConnectivity()_MSDNDataType()_PathFull/GetRelative/Split()_PathSplitEx()_PrintFromArray()_ProgressSetMarquee()_ReDim()_RockPaperScissors()/_RockPaperScissorsLizardSpock()_ScrollingCredits_SelfDelete()_SelfRename()_SelfUpdate()_SendTo()_ShellAll()_ShellFile()_ShellFolder()_SingletonHWID()_SingletonPID()_Startup()_StringCompact()_StringIsValid()_StringRegExpMetaCharacters()_StringReplaceWholeWord()_StringStripChars()_Temperature()_TrialPeriod()_UKToUSDate()/_USToUKDate()_WinAPI_Create_CTL_CODE()_WinAPI_CreateGUID()_WMIDateStringToDate()/_DateToWMIDateString()Au3 script parsingAutoIt SearchAutoIt3 PortableAutoIt3WrapperToPragmaAutoItWinGetTitle()/AutoItWinSetTitle()CodingDirToHTML5FileInstallrFileReadLastChars()GeoIP databaseGUI - Only Close ButtonGUI ExamplesGUICtrlDeleteImage()GUICtrlGetBkColor()GUICtrlGetStyle()GUIEventsGUIGetBkColor()Int_Parse() & Int_TryParse()IsISBN()LockFile()Mapping CtrlIDsOOP in AutoItParseHeadersToSciTE()PasswordValidPasteBinPosts Per DayPreExpandProtect GlobalsQueue()Resource UpdateResourcesExSciTE JumpSettings INISHELLHOOKShunting-YardSignature CreatorStack()Stopwatch()StringAddLF()/StringStripLF()StringEOLToCRLF()VSCROLLWM_COPYDATAMore Examples...

Updated: 22/04/2018

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...