├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── bower.json ├── dist ├── bullet.js └── bullet.min.js ├── example ├── index.html └── js │ ├── app │ └── app.js │ └── libs │ └── bullet.js ├── gulpfile.js ├── package.json ├── src └── bullet.js └── test ├── .jshintrc ├── _root-suite.js └── spec ├── existence-checks ├── custom-errors.js ├── methods.js └── properties.js ├── methods ├── _getMappings.js ├── addEventName.js ├── addMultipleEventNames.js ├── getTriggerAsync.js ├── off.js ├── on.js ├── once.js ├── removeEventName.js ├── replaceAllCallbacks.js ├── replaceCallback.js ├── setTriggerAsync.js └── trigger.js └── strict-methods ├── getStrictMode.js ├── off.js ├── on.js ├── once.js ├── replaceAllCallbacks.js ├── replaceCallback.js ├── setStrictMode.js └── trigger.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = false 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "env": { 4 | "browser": true, 5 | "commonjs": true, 6 | "es6": true, 7 | "node": true 8 | }, 9 | "globals": { 10 | "define": false 11 | }, 12 | "rules": { 13 | "accessor-pairs": "error", 14 | "array-bracket-newline": "error", 15 | "array-bracket-spacing": "error", 16 | "array-callback-return": "error", 17 | "array-element-newline": "error", 18 | "arrow-body-style": "error", 19 | "arrow-parens": "off", 20 | "arrow-spacing": [ 21 | "error", 22 | { 23 | "after": true, 24 | "before": true 25 | } 26 | ], 27 | "block-scoped-var": "error", 28 | "block-spacing": "error", 29 | "brace-style": "off", 30 | "callback-return": "error", 31 | "camelcase": "error", 32 | "capitalized-comments": "off", 33 | "class-methods-use-this": "error", 34 | "comma-dangle": "off", 35 | "comma-spacing": [ 36 | "error", 37 | { 38 | "after": true, 39 | "before": false 40 | } 41 | ], 42 | "comma-style": [ 43 | "error", 44 | "last" 45 | ], 46 | "complexity": "error", 47 | "computed-property-spacing": [ 48 | "error", 49 | "never" 50 | ], 51 | "consistent-return": "off", 52 | "consistent-this": "off", 53 | "curly": "off", 54 | "default-case": "error", 55 | "dot-location": [ 56 | "error", 57 | "property" 58 | ], 59 | "dot-notation": "error", 60 | "eol-last": "error", 61 | "eqeqeq": "error", 62 | "for-direction": "error", 63 | "func-call-spacing": "error", 64 | "func-name-matching": "error", 65 | "func-names": [ 66 | "error", 67 | "never" 68 | ], 69 | "func-style": [ 70 | "error", 71 | "declaration" 72 | ], 73 | "generator-star-spacing": "error", 74 | "global-require": "error", 75 | "guard-for-in": "off", 76 | "handle-callback-err": "error", 77 | "id-blacklist": "error", 78 | "id-length": ["error", { "exceptions": ["i"] }], 79 | "id-match": "error", 80 | "indent": "off", 81 | "indent-legacy": "off", 82 | "init-declarations": "off", 83 | "jsx-quotes": "error", 84 | "key-spacing": "off", 85 | "keyword-spacing": "off", 86 | "line-comment-position": "error", 87 | "linebreak-style": [ 88 | "error", 89 | "unix" 90 | ], 91 | "lines-around-comment": "error", 92 | "lines-around-directive": "error", 93 | "max-depth": "error", 94 | "max-len": "off", 95 | "max-lines": "off", 96 | "max-nested-callbacks": "error", 97 | "max-params": ["error", 4], 98 | "max-statements": "off", 99 | "max-statements-per-line": "error", 100 | "multiline-ternary": [ 101 | "error", 102 | "never" 103 | ], 104 | "new-cap": "error", 105 | "new-parens": "error", 106 | "newline-after-var": "off", 107 | "newline-before-return": "off", 108 | "newline-per-chained-call": "error", 109 | "no-alert": "error", 110 | "no-array-constructor": "error", 111 | "no-await-in-loop": "error", 112 | "no-bitwise": "error", 113 | "no-buffer-constructor": "error", 114 | "no-caller": "error", 115 | "no-catch-shadow": "error", 116 | "no-confusing-arrow": "error", 117 | "no-console": "off", 118 | "no-continue": "error", 119 | "no-div-regex": "error", 120 | "no-duplicate-imports": "error", 121 | "no-else-return": "error", 122 | "no-empty-function": "off", 123 | "no-eq-null": "error", 124 | "no-eval": "error", 125 | "no-extend-native": "error", 126 | "no-extra-bind": "error", 127 | "no-extra-label": "error", 128 | "no-extra-parens": [ 129 | "error", 130 | "all", 131 | { 132 | "nestedBinaryExpressions": false 133 | } 134 | ], 135 | "no-floating-decimal": "error", 136 | "no-implicit-globals": "error", 137 | "no-implied-eval": "error", 138 | "no-inline-comments": "error", 139 | "no-invalid-this": "off", 140 | "no-iterator": "error", 141 | "no-label-var": "error", 142 | "no-labels": "error", 143 | "no-lone-blocks": "error", 144 | "no-lonely-if": "off", 145 | "no-loop-func": "error", 146 | "no-magic-numbers": "off", 147 | "no-mixed-operators": [ 148 | "error", 149 | { 150 | "allowSamePrecedence": true 151 | } 152 | ], 153 | "no-mixed-requires": "error", 154 | "no-multi-assign": "error", 155 | "no-multi-spaces": "error", 156 | "no-multi-str": "error", 157 | "no-multiple-empty-lines": "off", 158 | "no-native-reassign": "error", 159 | "no-negated-condition": "off", 160 | "no-negated-in-lhs": "error", 161 | "no-nested-ternary": "error", 162 | "no-new": "error", 163 | "no-new-func": "error", 164 | "no-new-object": "error", 165 | "no-new-require": "error", 166 | "no-new-wrappers": "error", 167 | "no-octal-escape": "error", 168 | "no-param-reassign": "error", 169 | "no-path-concat": "error", 170 | "no-process-env": "off", 171 | "no-process-exit": "error", 172 | "no-proto": "error", 173 | "no-prototype-builtins": "error", 174 | "no-restricted-globals": "error", 175 | "no-restricted-imports": "error", 176 | "no-restricted-modules": "error", 177 | "no-restricted-properties": "error", 178 | "no-restricted-syntax": "error", 179 | "no-return-assign": "error", 180 | "no-return-await": "error", 181 | "no-script-url": "error", 182 | "no-self-compare": "error", 183 | "no-sequences": "error", 184 | "no-shadow": "off", 185 | "no-shadow-restricted-names": "error", 186 | "no-spaced-func": "error", 187 | "no-sync": "off", 188 | "no-tabs": "error", 189 | "no-template-curly-in-string": "error", 190 | "no-ternary": "off", 191 | "no-throw-literal": "error", 192 | "no-trailing-spaces": "off", 193 | "no-undef-init": "error", 194 | "no-undefined": "error", 195 | "no-underscore-dangle": "off", 196 | "no-unmodified-loop-condition": "error", 197 | "no-unneeded-ternary": "error", 198 | "no-unused-expressions": "off", 199 | "no-unused-vars": [ 200 | "error", 201 | { 202 | "args": "none", 203 | "vars": "all" 204 | } 205 | ], 206 | "no-use-before-define": "off", 207 | "no-useless-call": "error", 208 | "no-useless-computed-key": "error", 209 | "no-useless-concat": "error", 210 | "no-useless-constructor": "error", 211 | "no-useless-rename": "error", 212 | "no-useless-return": "error", 213 | "no-var": "error", 214 | "no-void": "error", 215 | "no-warning-comments": "off", 216 | "no-whitespace-before-property": "error", 217 | "no-with": "error", 218 | "nonblock-statement-body-position": "error", 219 | "object-curly-newline": "off", 220 | "object-curly-spacing": [ 221 | "error", 222 | "never" 223 | ], 224 | "object-property-newline": [ 225 | "error", 226 | { 227 | "allowMultiplePropertiesPerLine": true 228 | } 229 | ], 230 | "object-shorthand": "off", 231 | "one-var": "off", 232 | "one-var-declaration-per-line": "error", 233 | "operator-assignment": [ 234 | "error", 235 | "always" 236 | ], 237 | "operator-linebreak": "error", 238 | "padded-blocks": "off", 239 | "padding-line-between-statements": "error", 240 | "prefer-arrow-callback": "off", 241 | "prefer-const": "error", 242 | "prefer-numeric-literals": "error", 243 | "prefer-promise-reject-errors": "off", 244 | "prefer-reflect": "off", 245 | "prefer-rest-params": "error", 246 | "prefer-spread": "error", 247 | "prefer-template": "off", 248 | "quote-props": "off", 249 | "quotes": [ 250 | "error", 251 | "single" 252 | ], 253 | "radix": [ 254 | "error", 255 | "always" 256 | ], 257 | "require-await": "error", 258 | "require-jsdoc": "off", 259 | "rest-spread-spacing": "error", 260 | "semi": ["error", "always"], 261 | "semi-spacing": "error", 262 | "semi-style": [ 263 | "error", 264 | "last" 265 | ], 266 | "sort-imports": "off", 267 | "sort-keys": "off", 268 | "sort-vars": "error", 269 | "space-before-blocks": "error", 270 | "space-before-function-paren": "error", 271 | "space-in-parens": "off", 272 | "space-infix-ops": "error", 273 | "space-unary-ops": "error", 274 | "spaced-comment": "off", 275 | "strict": "off", 276 | "switch-colon-spacing": "error", 277 | "symbol-description": "error", 278 | "template-curly-spacing": [ 279 | "error", 280 | "never" 281 | ], 282 | "template-tag-spacing": "error", 283 | "unicode-bom": [ 284 | "error", 285 | "never" 286 | ], 287 | "valid-jsdoc": "error", 288 | "vars-on-top": "off", 289 | "wrap-iife": [ 290 | "error", 291 | "inside" 292 | ], 293 | "wrap-regex": "error", 294 | "yield-star-spacing": "error", 295 | "yoda": [ 296 | "error", 297 | "never" 298 | ] 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #--------------------------------------- 2 | # Packages 3 | #--------------------------------------- 4 | node_modules/ 5 | 6 | #--------------------------------------- 7 | # General 8 | #--------------------------------------- 9 | logs 10 | *.log 11 | *.sublime-* 12 | 13 | 14 | #--------------------------------------- 15 | # OS generated files 16 | #--------------------------------------- 17 | .DS_Store 18 | .DS_Store? 19 | ._* 20 | .Spotlight-V100 21 | .Trashes 22 | ehthumbs.db 23 | Thumbs.db 24 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .npmignore 2 | .jshintrc 3 | bower.json 4 | Gruntfile.js 5 | src 6 | test 7 | example 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Ivan Hayes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bullet 2 | 3 | Bullet is a lightweight and simple to use pub-sub library, with AMD/CJS support and an intuitive API. 4 | It was built to facilitate a simple and consistent system of communication across web applications and includes only the bare essentials typically needed to achieve this, along with great error-handling and thorough unit tests. 5 | 6 | ### Usage 7 | 8 | #### npm 9 | Install via npm using the following command in your command prompt: 10 | 11 | ```shell 12 | npm i -S bullet-pubsub 13 | ``` 14 | 15 | Include Bullet within your application: 16 | 17 | ```javascript 18 | var Bullet = require('bullet-pubsub'); 19 | ``` 20 | 21 | 22 | #### Bower 23 | Install via Bower using the following command in your command prompt: 24 | 25 | ```shell 26 | bower install bullet 27 | ``` 28 | 29 | Include Bullet in your application: 30 | 31 | ```html 32 | 33 | ``` 34 | 35 | 36 | #### Installation without a package manager 37 | If you are not using npm or Bower, then grab either the [minified](https://raw.githubusercontent.com/munkychop/bullet/master/dist/bullet.min.js), or [non-minified](https://raw.githubusercontent.com/munkychop/bullet/master/dist/bullet.js) source from Github and include Bullet in your application: 38 | 39 | ```html 40 | 41 | ``` 42 | 43 | 44 | ### Methods 45 | 46 | #### **.on()** 47 | 48 | ```javascript 49 | Bullet.on('someMessageName', callback); 50 | ``` 51 | 52 | Register a callback function to get called whenever the specified message is triggered. 53 | 54 | 55 | **Example usage:** 56 | 57 | ```javascript 58 | 59 | function helloCallback () { 60 | console.log('hello there :)'); 61 | } 62 | 63 | 64 | // Register the 'helloCallback' function to be called whenever the 'hello' message is triggered: 65 | 66 | Bullet.on('hello', helloCallback); 67 | 68 | 69 | // Somewhere later in the application... 70 | 71 | 72 | // Trigger the 'hello' message – Bullet will call the 'helloCallback' function: 73 | 74 | Bullet.trigger('hello'); 75 | 76 | ``` 77 | 78 | 79 | ---------- 80 | 81 | 82 | #### **.off()** 83 | 84 | ```javascript 85 | Bullet.off('someMessageName'[, callback]); 86 | ``` 87 | 88 | Remove either all callback functions or a specific callback function registered against the specified message. 89 | 90 | ```javascript 91 | Bullet.off(); 92 | ``` 93 | 94 | Remove all registered mappings by calling `off` with no parameters. 95 | 96 | 97 | **Example usage:** 98 | 99 | ```javascript 100 | 101 | function helloCallback () { 102 | console.log('hello there :)'); 103 | } 104 | 105 | function anotherCallback () { 106 | console.log('hello again :)'); 107 | } 108 | 109 | 110 | Bullet.on('hello', helloCallback); 111 | Bullet.on('hello', anotherCallback); 112 | 113 | 114 | // Somewhere later in the application... 115 | 116 | 117 | // Trigger the 'hello' message – Bullet will call both the 'helloCallback' and 'anotherCallback' functions: 118 | 119 | Bullet.trigger('hello'); 120 | 121 | 122 | // Remove all callback functions associated with the 'hello' message: 123 | 124 | Bullet.off('hello'); 125 | 126 | 127 | // Attempt to trigger the 'hello' message again – Bullet won't call any functions: 128 | 129 | Bullet.trigger('hello'); 130 | 131 | ``` 132 | 133 | 134 | **Example usage removing a specific callback:** 135 | 136 | ```javascript 137 | 138 | function helloCallback () { 139 | console.log('hello there :)'); 140 | } 141 | 142 | function anotherCallback () { 143 | console.log('hello again :)'); 144 | } 145 | 146 | 147 | Bullet.on('hello', helloCallback); 148 | Bullet.on('hello', anotherCallback); 149 | 150 | 151 | // Somewhere later in the application... 152 | 153 | 154 | // Trigger the 'hello' message – Bullet will call both the 'helloCallback' and 'anotherCallback' functions: 155 | 156 | Bullet.trigger('hello'); 157 | 158 | 159 | // Remove only the 'anotherCallback' function associated with the 'hello' message: 160 | 161 | Bullet.off('hello', anotherCallback); 162 | 163 | 164 | // Trigger the 'hello' message again – Bullet will only call the 'helloCallback' function: 165 | 166 | Bullet.trigger('hello'); 167 | 168 | ``` 169 | 170 | 171 | **Example usage removing all mappings:** 172 | 173 | ```javascript 174 | 175 | function helloCallback () { 176 | console.log('hello there :)'); 177 | } 178 | 179 | function goodbyeCallback () { 180 | console.log('goodbye :)'); 181 | } 182 | 183 | 184 | Bullet.on('hello', helloCallback); 185 | Bullet.on('goodbye', goodbyeCallback); 186 | 187 | 188 | // Somewhere later in the application... 189 | 190 | 191 | // Trigger the 'hello' message – Bullet will call the 'helloCallback' function: 192 | 193 | Bullet.trigger('hello'); 194 | 195 | 196 | // Trigger the 'goodbye' message – Bullet will call the 'goodbyeCallback' function: 197 | 198 | Bullet.trigger('goodbye'); 199 | 200 | 201 | // Remove all mappings by calling the Bullet.off method with no parameters: 202 | 203 | Bullet.off(); 204 | 205 | 206 | // Attempt to trigger the 'hello' message again – Bullet will not call the 'helloCallback' function: 207 | 208 | Bullet.trigger('hello'); 209 | 210 | 211 | // Attempt to trigger the 'goodbye' message again – Bullet will not call the 'goodbyeCallback' function: 212 | 213 | Bullet.trigger('goodbye'); 214 | 215 | ``` 216 | 217 | 218 | ---------- 219 | 220 | 221 | #### **.once()** 222 | 223 | ```javascript 224 | Bullet.once('someMessageName', callback); 225 | ``` 226 | 227 | This function behaves in the same way as the the `on` function, except that – once registered – the callback function will only be called a single time when the specified message is triggered. 228 | 229 | 230 | **Example usage:** 231 | 232 | ```javascript 233 | 234 | function helloCallback () { 235 | console.log('hello there :)'); 236 | } 237 | 238 | 239 | // Register the 'helloCallback' function to be called the first time that the 'hello' message is triggered: 240 | 241 | Bullet.once('hello', helloCallback); 242 | 243 | 244 | // Somewhere later in the application... 245 | 246 | 247 | // Trigger the 'hello' message – Bullet will call the 'helloCallback' function: 248 | 249 | Bullet.trigger('hello'); 250 | 251 | 252 | // Attempt to trigger the 'hello' message again – Bullet won't call any functions this time: 253 | 254 | Bullet.trigger('hello'); 255 | 256 | ``` 257 | 258 | 259 | ---------- 260 | 261 | 262 | #### **.trigger()** 263 | 264 | ```javascript 265 | Bullet.trigger('someMessageName'[, data]); 266 | ``` 267 | 268 | This function will call all callback functions registered against the specified message, optionally passing in custom data as a payload. 269 | 270 | 271 | **Example usage:** 272 | 273 | ```javascript 274 | 275 | function helloCallback () { 276 | console.log('hello there :)'); 277 | } 278 | 279 | 280 | // Register the 'helloCallback' function to be called whenever the 'hello' message is triggered: 281 | 282 | Bullet.on('hello', helloCallback); 283 | 284 | 285 | // Somewhere later in the application... 286 | 287 | 288 | // Trigger the 'hello' message – Bullet will call the 'helloCallback' function: 289 | 290 | Bullet.trigger('hello'); 291 | 292 | ``` 293 | 294 | 295 | **Example usage with custom data:** 296 | 297 | ```javascript 298 | 299 | function helloCallback (data) { 300 | console.log(data); 301 | } 302 | 303 | 304 | // Register the 'helloCallback' function to be called whenever the 'hello' message is triggered: 305 | 306 | Bullet.on('hello', helloCallback); 307 | 308 | 309 | // Somewhere later in the application... 310 | 311 | 312 | // Create some custom data: 313 | 314 | var customData = { 315 | someProp : 'bro', 316 | someOtherProp : 'awesome!' 317 | }; 318 | 319 | 320 | // Trigger the 'hello' message – Bullet will call the 'helloCallback' function and 321 | // pass in the custom data that you created, which will be sent to the function as a parameter: 322 | 323 | Bullet.trigger('hello', customData); 324 | 325 | ``` 326 | 327 | 328 | ---------- 329 | 330 | 331 | #### **.replaceCallback()** 332 | 333 | ```javascript 334 | Bullet.replaceCallback('someMessageName', oldCallback, newCallback[, once]); 335 | ``` 336 | 337 | Replace a single mapped callback for the specified event name with a new callback, optionally setting the 'once' parameter. 338 | 339 | 340 | **Example usage:** 341 | 342 | ```javascript 343 | 344 | function helloCallback () { 345 | console.log('hello!'); 346 | } 347 | 348 | 349 | function someOtherCallback () { 350 | console.log('konnichiwa!'); 351 | } 352 | 353 | 354 | // Explicitly add the event name. 355 | 356 | Bullet.addEventName('hello') 357 | 358 | 359 | // Create an event mapping. 360 | 361 | Bullet.on('hello', helloCallback); 362 | 363 | 364 | // Remove the 'helloCallback' function mapping from the 'hello' event and replace it with a mapping for the 'someOtherCallback' function, while setting the 'once' value for the new callback (optional). 365 | 366 | Bullet.replaceCallback(Bullet.events.hello, helloCallback, someOtherCallback, true); 367 | 368 | ``` 369 | 370 | 371 | ---------- 372 | 373 | 374 | #### **.replaceAllCallbacks()** 375 | 376 | ```javascript 377 | Bullet.replaceAllCallbacks('someMessageName', newCallback[, once]); 378 | ``` 379 | 380 | Replace all of the specified event name’s mapped callbacks with the specified callback, optionally setting the 'once' parameter. 381 | 382 | 383 | **Example usage:** 384 | 385 | ```javascript 386 | 387 | function helloCallback () { 388 | console.log('hello!'); 389 | } 390 | 391 | 392 | function someOtherCallback () { 393 | console.log('konnichiwa!'); 394 | } 395 | 396 | 397 | // Explicitly add an event name. 398 | 399 | Bullet.addEventName('hello') 400 | 401 | 402 | // Create an event mapping. 403 | 404 | Bullet.on('hello', helloCallback); 405 | 406 | 407 | // Replace all function mappings from the 'hello' event with a mapping for the 'someOtherCallback' function, while setting the 'once' value for the new callback (optional). 408 | 409 | Bullet.replaceAllCallbacks(Bullet.events.hello, someOtherCallback, true); 410 | 411 | ``` 412 | 413 | 414 | ---------- 415 | 416 | 417 | #### **.getStrictMode()** 418 | 419 | ```javascript 420 | Bullet.getStrictMode(); 421 | ``` 422 | 423 | Returns a boolean – true if strict mode is enabled and false if not. 424 | 425 | 426 | **Example usage:** 427 | 428 | ```javascript 429 | 430 | // Check whether or not strict mode is enabled: 431 | 432 | var strictMode = Bullet.getStrictMode(); // false (the default) 433 | 434 | 435 | // Turn on strict mode: 436 | 437 | Bullet.setStrictMode(true); 438 | 439 | 440 | // Check again whether or not strict mode is enabled: 441 | 442 | strictMode = Bullet.getStrictMode(); // true 443 | 444 | ``` 445 | 446 | 447 | ---------- 448 | 449 | 450 | #### **.setStrictMode()** 451 | 452 | ```javascript 453 | Bullet.setStrictMode(boolean); 454 | ``` 455 | 456 | Calling the `on`, `once`, `trigger`, `replaceCallback`, or `replaceAllCallbacks` methods – when strict mode is enabled – will cause Bullet to check if the specified message was explicitly added to the `events` object and, if not, Bullet will throw an Error. See the `addEventName` method for details on defining event names. 457 | 458 | 459 | **Example errors when calling the `on`, `once`, or `trigger` methods:** 460 | 461 | ```javascript 462 | 463 | function helloCallback () { 464 | console.log('hello there :)'); 465 | } 466 | 467 | function someOtherCallback () { 468 | console.log('konnichiwa!'); 469 | } 470 | 471 | 472 | // Turn on strict mode: 473 | 474 | Bullet.setStrictMode(true); 475 | 476 | 477 | // Attempt to register the 'helloCallback' function to be called whenever the 'hello' message is triggered – Bullet will throw an error: 478 | 479 | Bullet.on('hello', helloCallback); // throws error due to unrecognised message 480 | 481 | 482 | // Attempt to register the 'helloCallback' function to be called just once, when the 'hello' message is triggered – Bullet will throw an error: 483 | 484 | Bullet.once('hello', helloCallback); // throws error due to unrecognised message 485 | 486 | 487 | // Attempt to trigger a 'hello' message which hasn't been explicitly added as an event – Bullet will throw an error: 488 | 489 | Bullet.trigger('hello'); // throws error due to unrecognised message 490 | 491 | ``` 492 | 493 | 494 | **Example errors when calling the `replaceCallback`, or `replaceAllCallbacks` methods:** 495 | 496 | ```javascript 497 | 498 | function helloCallback () { 499 | console.log('hello there :)'); 500 | } 501 | 502 | function someOtherCallback () { 503 | console.log('konnichiwa!'); 504 | } 505 | 506 | 507 | // Map the 'helloCallback' to the 'hello' message: 508 | 509 | Bullet.on('hello', helloCallback); 510 | 511 | 512 | // Turn on strict mode: 513 | 514 | Bullet.setStrictMode(true); 515 | 516 | 517 | // Attempt to replace a function mapping for a message which hasn't been explicitly added as an event – Bullet will throw an error: 518 | 519 | Bullet.replaceCallback('hello', helloCallback, someOtherCallback); // throws error due to unrecognised message 520 | 521 | 522 | // Attempt to replace all function mappings for a message which hasn't been explicitly added as an event – Bullet will throw an error: 523 | 524 | Bullet.replaceAllCallbacks('hello', someOtherCallback); // throws error due to unrecognised message 525 | 526 | ``` 527 | 528 | 529 | ---------- 530 | 531 | 532 | #### **.addEventName()** 533 | 534 | ```javascript 535 | Bullet.addEventName('someMessage'); 536 | ``` 537 | 538 | Explicitly add a message to Bullet’s 'events' object. **_Explicitly defined message names are required when strict mode is enabled._** 539 | 540 | 541 | **Example usage:** 542 | 543 | ```javascript 544 | 545 | function helloCallback () { 546 | console.log('hello there :)'); 547 | } 548 | 549 | 550 | // Register the 'helloCallback' function to be called whenever a 'hello' message is triggered: 551 | 552 | Bullet.on('hello', helloCallback); 553 | 554 | 555 | // Attempt to trigger the 'hello' message – Bullet will call the 'helloCallback' function as expected: 556 | 557 | Bullet.trigger('hello'); 558 | 559 | 560 | // Turn on strict mode: 561 | 562 | Bullet.setStrictMode(true); 563 | 564 | 565 | // Attempt to trigger the 'hello' message again – Bullet will throw an error due to strict mode, as the message hasn't been explicitly added: 566 | 567 | Bullet.trigger('hello'); 568 | 569 | 570 | // Explicitly add the 'hello' message: 571 | 572 | Bullet.addEventName('hello'); 573 | 574 | 575 | // Attempt to trigger the 'hello' message again – Bullet will call the 'helloCallback' function as expected, now that the 'hello' message has been explicitly added: 576 | 577 | Bullet.trigger('hello'); 578 | 579 | ``` 580 | 581 | 582 | ---------- 583 | 584 | 585 | #### **.addMultipleEventNames()** 586 | 587 | ```javascript 588 | Bullet.addMultipleEventNames(['someMessage', 'someOtherMessage']); 589 | ``` 590 | 591 | Explicitly add one or more messages to Bullet’s 'events' object. **_Explicitly defined message names are required when strict mode is enabled._** 592 | 593 | This method works in the same way as the `addEventName` method, but instead accepts an array of message names. 594 | 595 | 596 | ---------- 597 | 598 | 599 | #### **.removeEventName()** 600 | 601 | ```javascript 602 | Bullet.removeEventName('someMessage'); 603 | ``` 604 | 605 | Explicitly remove a message from Bullet’s 'events' object. 606 | 607 | 608 | **Example usage:** 609 | 610 | ```javascript 611 | 612 | function helloCallback () { 613 | console.log('hello there :)'); 614 | } 615 | 616 | 617 | // Turn on strict mode: 618 | 619 | Bullet.setStrictMode(true); 620 | 621 | 622 | // Explicitly add a 'hello' message: 623 | 624 | Bullet.addEventName('hello'); 625 | 626 | 627 | // Register the 'helloCallback' function to be called whenever the 'hello' message is triggered: 628 | 629 | Bullet.on('hello', helloCallback); 630 | 631 | 632 | // Attempt to trigger the 'hello' message – Bullet will call the 'helloCallback' function as expected: 633 | 634 | Bullet.trigger('hello'); 635 | 636 | 637 | // Explicitly remove the 'hello' message: 638 | 639 | Bullet.removeEventName('hello'); 640 | 641 | 642 | // Attempt to trigger the 'hello' message again – Bullet will throw an error due to strict mode, as the message no longer exists as a part of Bullet’s 'events' object: 643 | 644 | Bullet.trigger('hello'); 645 | 646 | ``` 647 | 648 | 649 | ---------- 650 | 651 | 652 | #### **.getTriggerAsync()** 653 | 654 | ```javascript 655 | Bullet.getTriggerAsync(); 656 | ``` 657 | 658 | Returns a boolean – true if async message triggers are enabled and false if not. 659 | 660 | 661 | **Example usage:** 662 | 663 | ```javascript 664 | 665 | // Check whether or not async message triggers are enabled: 666 | 667 | var triggerAsync = Bullet.getTriggerAsync(); // true (the default) 668 | 669 | 670 | // Turn off async message triggers: 671 | 672 | Bullet.setTriggerAsync(false); 673 | 674 | 675 | // Check again whether or not async message triggers are enabled: 676 | 677 | triggerAsync = Bullet.getTriggerAsync(); // false 678 | 679 | ``` 680 | 681 | 682 | ---------- 683 | 684 | 685 | #### **.setTriggerAsync()** 686 | 687 | ```javascript 688 | Bullet.setTriggerAsync(boolean); 689 | ``` 690 | 691 | When called and passed a value of `true`, Bullet will trigger messages asynchronously (outside of the current execution call stack) and when called and passed a value of `false`, Bullet will trigger messages synchronously. 692 | 693 | 694 | **Example usage:** 695 | 696 | ```javascript 697 | 698 | // Check whether or not async message triggers are enabled: 699 | 700 | var triggerAsync = Bullet.getTriggerAsync(); // true (the default) 701 | 702 | 703 | // Turn off async message triggers: 704 | 705 | Bullet.setTriggerAsync(false); 706 | 707 | 708 | // Check again whether or not async message triggers are enabled: 709 | 710 | triggerAsync = Bullet.getTriggerAsync(); // false 711 | 712 | ``` 713 | 714 | 715 | ---------- 716 | 717 | 718 | ### Properties 719 | 720 | #### **.events** 721 | 722 | ```javascript 723 | Bullet.events 724 | ``` 725 | 726 | Used for getting a reference to message strings that have been explicitly defined within the 'events' object, usually via the `addEventName` method. 727 | *This property becomes most important when strict mode is enabled.* 728 | 729 | 730 | **Example usage:** 731 | 732 | ```javascript 733 | 734 | function helloCallback () { 735 | console.log('hi'); 736 | } 737 | 738 | 739 | // Explicitly define a message string using the 'addEventName' method. 740 | 741 | Bullet.addEventName('hello'); 742 | 743 | 744 | // Within the 'on' method, reference the message that was explicitly added to the 'events' object. 745 | // This is helpful because an error will be thrown if the message is undefined: 746 | 747 | Bullet.on(Bullet.events.hello, helloCallback); 748 | 749 | // Bracket notation can be used to access the property instead, if necessary: 750 | // Bullet.events['hello'] 751 | 752 | 753 | // Somewhere later in the application... 754 | 755 | 756 | // Trigger the message that was explicitly added to the 'events' object – Bullet will call the 'helloCallback' function. 757 | // Again, this is helpful because an error will be thrown if the message is undefined: 758 | 759 | Bullet.trigger(Bullet.events.hello); 760 | 761 | 762 | // It is also still possible to trigger messages by using a string literal – Bullet will still call the 'helloCallback' function: 763 | 764 | Bullet.trigger('hello'); 765 | 766 | 767 | // Note that – when using a string literal – an error will NOT be thrown here if the message doesn't exist within the 'events' object, unless we enable strict mode: 768 | 769 | Bullet.trigger('someOtherMessage'); // no error thrown for unrecognised message 770 | 771 | 772 | // Enable strict mode. 773 | 774 | Bullet.setStrictMode(true); 775 | 776 | 777 | // Now that strict mode is enabled, attempt to trigger the unrecognised message again – Bullet will throw an error due to the unrecognised message: 778 | 779 | Bullet.trigger('someOtherMessage'); // error thrown 780 | 781 | ``` 782 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bullet", 3 | "homepage": "https://github.com/munkychop/bullet", 4 | "authors": [ 5 | "Ivan Hayes <@munkychop>" 6 | ], 7 | "description": "Bullet is an ultra lightweight and simple to use pub-sub library.", 8 | "main": ["dist/bullet.js", "dist/bullet.min.js"], 9 | "keywords": [ 10 | "bullet", 11 | "bullet-pubsub", 12 | "javascript", 13 | "library", 14 | "pub-sub", 15 | "pubsub", 16 | "events", 17 | "communication" 18 | ], 19 | "license": "MIT", 20 | "ignore": [ 21 | "**/.*", 22 | "bower_components", 23 | "src", 24 | "test", 25 | "example", 26 | "Gruntfile.js", 27 | "node_modules", 28 | "package.json", 29 | "LICENSE" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /dist/bullet.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; 4 | 5 | (function () { 6 | 7 | function Bullet() { 8 | // ------------------------------------------------------------------------------------------ 9 | // -- Custom Errors 10 | // ------------------------------------------------------------------------------------------ 11 | function ParamCountError(methodName, expectedParamsString, paramCount) { 12 | this.message = 'Bullet:: [' + methodName + '] ' + expectedParamsString + ', but received: ' + paramCount; 13 | var error = new Error(this.message); 14 | if (typeof error.stack !== 'undefined') this.stack = error.stack; 15 | } 16 | setupCustomError(ParamCountError); 17 | 18 | function ParamTypeError(methodName, parameterName, parameter, expectedType) { 19 | this.message = 'Bullet:: [' + methodName + '] Expected parameter - ' + parameterName + ' - to be type: ' + expectedType + ', but received type: ' + (typeof parameter === 'undefined' ? 'undefined' : _typeof(parameter)); 20 | var error = new TypeError(this.message); 21 | if (typeof error.stack !== 'undefined') this.stack = error.stack; 22 | } 23 | setupCustomError(ParamTypeError); 24 | 25 | function EventNameLengthError(methodName) { 26 | this.message = 'Bullet:: [' + methodName + '] Expected event name parameter to be longer than 0 characters'; 27 | var error = new Error(this.message); 28 | if (typeof error.stack !== 'undefined') this.stack = error.stack; 29 | } 30 | setupCustomError(EventNameLengthError); 31 | 32 | function EventNamesArrayLengthError(methodName) { 33 | this.message = 'Bullet:: [' + methodName + '] Expected event names array to contain one or more event names'; 34 | var error = new Error(this.message); 35 | if (typeof error.stack !== 'undefined') this.stack = error.stack; 36 | } 37 | setupCustomError(EventNamesArrayLengthError); 38 | 39 | function UndeclaredEventError(methodName, eventName) { 40 | this.message = 'Bullet:: [' + methodName + '] Event string: "' + eventName + '" does not exist within the events dictionary\nPlease use the Bullet.addEventName method to add this string.'; 41 | 42 | var error = new Error(this.message); 43 | if (typeof error.stack !== 'undefined') this.stack = error.stack; 44 | } 45 | setupCustomError(UndeclaredEventError); 46 | 47 | function UnmappedEventError(methodName, eventName) { 48 | this.message = 'Bullet:: [' + methodName + '] Event string: "' + eventName + '" is not mapped to any callbacks\nPlease use the Bullet.on method to map this string to a callback.'; 49 | 50 | var error = new Error(this.message); 51 | if (typeof error.stack !== 'undefined') this.stack = error.stack; 52 | } 53 | setupCustomError(UnmappedEventError); 54 | 55 | function setupCustomError(CustomError) { 56 | CustomError.prototype = new Error(); 57 | CustomError.prototype.name = CustomError.name; 58 | CustomError.prototype.constructor = CustomError; 59 | } 60 | 61 | var _CALLBACK_NAMESPACE = '__bullet_pubsub__'; 62 | // ------------------------------------------------------------------------------------------ 63 | // -- Private variables 64 | // ------------------------------------------------------------------------------------------ 65 | var _self = this; 66 | var _mappings = {}; 67 | var _strictMode = false; 68 | var _triggerAsync = true; 69 | 70 | // Expose custom error type constructors (for testing), but use an underscore to imply privacy. 71 | _self._errors = { 72 | ParamCountError: ParamCountError, 73 | ParamTypeError: ParamTypeError, 74 | EventNameLengthError: EventNameLengthError, 75 | EventNamesArrayLengthError: EventNamesArrayLengthError, 76 | UndeclaredEventError: UndeclaredEventError, 77 | UnmappedEventError: UnmappedEventError 78 | }; 79 | 80 | // ------------------------------------------------------------------------------------------ 81 | // -- Public variables 82 | // ------------------------------------------------------------------------------------------ 83 | _self.events = {}; 84 | 85 | // ------------------------------------------------------------------------------------------ 86 | // -- Private methods 87 | // ------------------------------------------------------------------------------------------ 88 | function _runCallback(eventName, data) { 89 | for (var id in _mappings[eventName].callbacks) { 90 | var callbackObject = _mappings[eventName].callbacks[id]; 91 | 92 | if (typeof callbackObject.cb === 'function') callbackObject.cb(data); 93 | if (typeof callbackObject.once === 'boolean' && callbackObject.once === true) _self.off(eventName, callbackObject.cb); 94 | } 95 | } 96 | 97 | function _cloneCallbacks(callbacks) { 98 | var clonedCallbacks = {}; 99 | 100 | for (var callbackName in callbacks) { 101 | clonedCallbacks[callbackName] = { 102 | cb: callbacks[callbackName].cb, 103 | once: callbacks[callbackName].once 104 | }; 105 | } 106 | 107 | return clonedCallbacks; 108 | } 109 | 110 | function _deleteAllCallbackReferencesForEvent(eventName) { 111 | for (var id in _mappings[eventName].callbacks) { 112 | var callback = _mappings[eventName].callbacks[id].cb; 113 | 114 | callback[_CALLBACK_NAMESPACE].totalEvents--; 115 | 116 | if (callback[_CALLBACK_NAMESPACE].totalEvents === 0) { 117 | delete callback[_CALLBACK_NAMESPACE]; 118 | } else { 119 | delete callback[_CALLBACK_NAMESPACE][eventName]; 120 | } 121 | } 122 | } 123 | 124 | function _deleteAllCallbackReferences() { 125 | for (var eventName in _mappings) { 126 | _deleteAllCallbackReferencesForEvent(eventName); 127 | } 128 | } 129 | 130 | // Expose _getMappings method (for testing), but use an underscore to imply privacy. 131 | _self._getMappings = function () { 132 | // Return a dictionary object that has no effect on app state to ensure '_mappings' 133 | // stays private, even if the value returned from this method is modified. 134 | var clonedMappings = {}; 135 | 136 | for (var mapping in _mappings) { 137 | clonedMappings[mapping] = { 138 | callbacks: _cloneCallbacks(_mappings[mapping].callbacks), 139 | totalCallbacks: _mappings[mapping].totalCallbacks 140 | }; 141 | } 142 | 143 | return clonedMappings; 144 | }; 145 | 146 | // ------------------------------------------------------------------------------------------ 147 | // -- Public methods 148 | // ------------------------------------------------------------------------------------------ 149 | _self.on = function (eventName, fn, once) { 150 | if (arguments.length < 2 || arguments.length > 3) { 151 | throw new ParamCountError('on', 'Expected between 2 and 3 parameters', arguments.length); 152 | } 153 | 154 | if (typeof eventName !== 'string') { 155 | throw new ParamTypeError('on', 'event name', eventName, 'string'); 156 | } else if (eventName.length === 0) { 157 | throw new EventNameLengthError('on'); 158 | } else if (_strictMode && typeof _self.events[eventName] === 'undefined') { 159 | throw new UndeclaredEventError('on', eventName); 160 | } 161 | 162 | if (typeof fn !== 'function') { 163 | throw new ParamTypeError('on', 'callback', fn, 'function'); 164 | } 165 | 166 | if (typeof once !== 'undefined' && typeof once !== 'boolean') { 167 | throw new ParamTypeError('on', 'once', once, 'boolean'); 168 | } 169 | 170 | // Create a reference between the callback and stored event. 171 | var callbackId = null; 172 | 173 | // If the named event object already exists in the dictionary... 174 | if (typeof _mappings[eventName] !== 'undefined') { 175 | // Attempt to get the callback ID from the callback itself. 176 | if (typeof fn[_CALLBACK_NAMESPACE] === 'undefined') { 177 | fn[_CALLBACK_NAMESPACE] = { 178 | totalEvents: 0 179 | }; 180 | } 181 | 182 | // Add a new callback object to the existing event object. 183 | if (typeof fn[_CALLBACK_NAMESPACE][eventName] === 'undefined') { 184 | callbackId = _mappings[eventName].totalCallbacks; 185 | 186 | _mappings[eventName].totalCallbacks++; 187 | 188 | _mappings[eventName].callbacks[callbackId] = { 189 | cb: fn, 190 | once: typeof once === 'boolean' ? once : false 191 | }; 192 | 193 | // On the callback, create a reference to the event mapping. 194 | fn[_CALLBACK_NAMESPACE][eventName] = callbackId; 195 | fn[_CALLBACK_NAMESPACE].totalEvents++; 196 | } 197 | 198 | if (typeof once === 'boolean') { 199 | // Get the callback ID from the value of the existing event name key. 200 | callbackId = fn[_CALLBACK_NAMESPACE][eventName]; 201 | 202 | // The function already exists, so update it's 'once' value. 203 | _mappings[eventName].callbacks[callbackId].once = once; 204 | } 205 | } else { 206 | // Create a new event object in the dictionary with the specified name and callback. 207 | _mappings[eventName] = { 208 | callbacks: {} 209 | }; 210 | 211 | callbackId = 0; 212 | 213 | _mappings[eventName].callbacks[callbackId] = { cb: fn, once: !!once }; 214 | _mappings[eventName].totalCallbacks = 1; 215 | 216 | // On the callback, create a reference to the event mapping. 217 | if (typeof fn[_CALLBACK_NAMESPACE] === 'undefined') { 218 | fn[_CALLBACK_NAMESPACE] = {}; 219 | fn[_CALLBACK_NAMESPACE].totalEvents = 1; 220 | } else { 221 | fn[_CALLBACK_NAMESPACE].totalEvents++; 222 | } 223 | 224 | fn[_CALLBACK_NAMESPACE][eventName] = callbackId; 225 | } 226 | }; 227 | 228 | _self.once = function (eventName, fn) { 229 | if (arguments.length !== 2) { 230 | throw new ParamCountError('once', 'Expected 2 parameters', arguments.length); 231 | } else if (typeof eventName !== 'string') { 232 | throw new ParamTypeError('once', 'event name', eventName, 'string'); 233 | } else if (eventName.length === 0) { 234 | throw new EventNameLengthError('once'); 235 | } else if (_strictMode && typeof _self.events[eventName] === 'undefined') { 236 | throw new UndeclaredEventError('once', eventName); 237 | } 238 | 239 | if (typeof fn !== 'function') { 240 | throw new ParamTypeError('once', 'callback', fn, 'function'); 241 | } 242 | 243 | _self.on(eventName, fn, true); 244 | }; 245 | 246 | _self.off = function (eventName, fn) { 247 | if (arguments.length === 0) { 248 | // delete all references to Bullet that exist on mapped callbacks. 249 | _deleteAllCallbackReferences(); 250 | 251 | // Remove all mappings from the dictionary. 252 | _mappings = {}; 253 | 254 | return; 255 | } else if (typeof eventName !== 'string') { 256 | throw new ParamTypeError('off', 'event name', eventName, 'string'); 257 | } else if (eventName.length === 0) { 258 | throw new EventNameLengthError('off'); 259 | } else if (_strictMode && typeof _self.events[eventName] === 'undefined') { 260 | throw new UndeclaredEventError('off', eventName); 261 | } 262 | 263 | if (typeof _mappings[eventName] === 'undefined') { 264 | // There is no mapping to remove, so return silently. 265 | return; 266 | } 267 | 268 | // Remove just the function, if passed as a parameter and in the dictionary. 269 | if (typeof fn === 'function') { 270 | // if (typeof fn[_CALLBACK_NAMESPACE] === 'undefined' || typeof fn[_CALLBACK_NAMESPACE][eventName] === 'undefined') { 271 | // // TODO: Throw error here if in strict mode. 272 | // } 273 | 274 | // Retrieve a reference to the stored event from the callback. 275 | var id = fn[_CALLBACK_NAMESPACE][eventName]; 276 | var fnToRemove = _mappings[eventName].callbacks[id]; 277 | 278 | if (typeof fnToRemove !== 'undefined') { 279 | // delete the callback object from the dictionary. 280 | delete _mappings[eventName].callbacks[id]; 281 | 282 | // delete the event reference on the callback function. 283 | delete fn[_CALLBACK_NAMESPACE][eventName]; 284 | 285 | _mappings[eventName].totalCallbacks--; 286 | fn[_CALLBACK_NAMESPACE].totalEvents--; 287 | 288 | if (_mappings[eventName].totalCallbacks === 0) { 289 | // There are no more functions in the dictionary that are 290 | // registered to this event, so delete the named event object. 291 | delete _mappings[eventName]; 292 | } 293 | 294 | if (fn[_CALLBACK_NAMESPACE].totalEvents === 0) { 295 | // There are no more events registered on this callback, 296 | // so delete the Bullet namespace. 297 | delete fn[_CALLBACK_NAMESPACE]; 298 | } 299 | } 300 | } else if (typeof fn !== 'undefined') { 301 | throw new ParamTypeError('off', 'callback', fn, 'function'); 302 | } else { 303 | // No callback was passed to the 'off' method... 304 | 305 | // For each callback in _mappings[eventName], delete the reference to 306 | // the specified event name on the callback itself. 307 | _deleteAllCallbackReferencesForEvent(eventName); 308 | 309 | // Delete all functions in the dictionary that are registered to this 310 | // event by deleting the named event object. 311 | delete _mappings[eventName]; 312 | } 313 | }; 314 | 315 | // Replace a single mapped callback for the specified event name with a new callback. 316 | _self.replaceCallback = function (eventName, oldFn, newFn, once) { 317 | if (typeof eventName !== 'string') { 318 | throw new ParamTypeError('replaceCallback', 'event name', eventName, 'string'); 319 | } else if (eventName.length === 0) { 320 | throw new EventNameLengthError('replaceCallback'); 321 | } else if (typeof _mappings[eventName] === 'undefined') { 322 | throw new UnmappedEventError('replaceCallback', eventName); 323 | } else if (_strictMode && typeof _self.events[eventName] === 'undefined') { 324 | throw new UndeclaredEventError('replaceCallback', eventName); 325 | } 326 | 327 | if (typeof oldFn !== 'function') { 328 | throw new ParamTypeError('replaceCallback', 'callback', oldFn, 'function'); 329 | } 330 | 331 | if (typeof newFn !== 'function') { 332 | throw new ParamTypeError('replaceCallback', 'callback', newFn, 'function'); 333 | } 334 | 335 | if (typeof once !== 'undefined' && typeof once !== 'boolean') { 336 | throw new ParamTypeError('replaceCallback', 'once', once, 'boolean'); 337 | } 338 | 339 | _self.off(eventName, oldFn); 340 | _self.on(eventName, newFn, once); 341 | }; 342 | 343 | // Replace all of the specified event name’s mapped callbacks with the specified callback. 344 | _self.replaceAllCallbacks = function (eventName, newFn, once) { 345 | if (typeof eventName !== 'string') { 346 | throw new ParamTypeError('replace', 'event name', eventName, 'string'); 347 | } else if (eventName.length === 0) { 348 | throw new EventNameLengthError('replace'); 349 | } else if (typeof _mappings[eventName] === 'undefined') { 350 | throw new UnmappedEventError('replace', eventName); 351 | } else if (_strictMode && typeof _self.events[eventName] === 'undefined') { 352 | throw new UndeclaredEventError('replace', eventName); 353 | } 354 | 355 | if (typeof newFn !== 'function') { 356 | throw new ParamTypeError('replace', 'callback', newFn, 'function'); 357 | } 358 | 359 | if (typeof once !== 'undefined' && typeof once !== 'boolean') { 360 | throw new ParamTypeError('replace', 'once', once, 'boolean'); 361 | } 362 | 363 | _self.off(eventName); 364 | _self.on(eventName, newFn, once); 365 | }; 366 | 367 | _self.trigger = function (eventName, data) { 368 | if (typeof eventName !== 'string') { 369 | throw new ParamTypeError('trigger', 'event name', eventName, 'string'); 370 | } else if (eventName.length === 0) { 371 | throw new EventNameLengthError('trigger'); 372 | } else if (_strictMode && typeof _self.events[eventName] === 'undefined') { 373 | throw new UndeclaredEventError('trigger', eventName); 374 | } 375 | 376 | if (typeof _mappings[eventName] === 'undefined') { 377 | if (_strictMode) throw new UnmappedEventError('trigger', eventName); 378 | 379 | // Return silently if not in strict mode. 380 | return; 381 | } 382 | 383 | // Check whether or not this is a browser environment. 384 | if (_triggerAsync && typeof window !== 'undefined') { 385 | window.setTimeout(function () { 386 | _runCallback(eventName, data); 387 | }, 0); 388 | } else { 389 | _runCallback(eventName, data); 390 | } 391 | }; 392 | 393 | _self.addEventName = function (eventName) { 394 | if (typeof eventName !== 'string') { 395 | throw new ParamTypeError('addEventName', 'event name', eventName, 'string'); 396 | } else if (eventName.length === 0) { 397 | throw new EventNameLengthError('addEventName'); 398 | } 399 | 400 | _self.events[eventName] = eventName; 401 | }; 402 | 403 | _self.addMultipleEventNames = function (eventNames) { 404 | if (!(eventNames instanceof Array)) { 405 | throw new ParamTypeError('addMultipleEventNames', 'event names', eventNames, 'array'); 406 | } else if (eventNames.length === 0) { 407 | throw new EventNamesArrayLengthError('addMultipleEventNames'); 408 | } 409 | 410 | var i = 0; 411 | var length = eventNames.length; 412 | 413 | for (i; i < length; i++) { 414 | var currentEventName = eventNames[i]; 415 | 416 | _self.addEventName(currentEventName); 417 | } 418 | }; 419 | 420 | _self.removeEventName = function (eventName) { 421 | if (typeof eventName !== 'string') { 422 | throw new ParamTypeError('removeEventName', 'event name', eventName, 'string'); 423 | } else if (eventName.length === 0) { 424 | throw new EventNameLengthError('removeEventName'); 425 | } 426 | 427 | if (_self.events[eventName]) delete _self.events[eventName]; 428 | }; 429 | 430 | _self.getStrictMode = function () { 431 | // Return a boolean that doesn't directly point to the internal '_strictMode' property. 432 | return _strictMode === true; 433 | }; 434 | 435 | _self.setStrictMode = function (useStrictMode) { 436 | if (typeof useStrictMode !== 'boolean') { 437 | throw new ParamTypeError('setStrictMode', 'strict mode', useStrictMode, 'boolean'); 438 | } 439 | 440 | _strictMode = useStrictMode; 441 | }; 442 | 443 | _self.getTriggerAsync = function () { 444 | // Return a boolean that doesn't directly point to the internal '_triggerAsync' property. 445 | return _triggerAsync === true; 446 | }; 447 | 448 | _self.setTriggerAsync = function (useAsync) { 449 | if (typeof useAsync !== 'boolean') { 450 | throw new ParamTypeError('setTriggerAsync', 'trigger async', useAsync, 'boolean'); 451 | } 452 | 453 | _triggerAsync = useAsync; 454 | }; 455 | 456 | // TODO : Create a 'replaceAllEventNames' method with an array of strings passed as a param. 457 | // - include type checks for string while looping over the array. 458 | 459 | // TODO : Create a 'removeAllEventNames' method. No params necessary. 460 | // – Internally this could simply call 'replaceAllEventNames' and pass an empty array as a param. 461 | 462 | // TODO : Create an 'onAny' method with an array of strings passed as the first param and a single callback as the second. 463 | // - include type checks for string while looping over the array. 464 | 465 | // TODO : Create an 'onMultiple' method with an array of flat objects passed as a param. 466 | // - example of required param structure: 467 | // [{eventName: 'someEvent', callback: someCallback, once: false}, {eventName: 'anotherEvent', callback: anotherCallback, once: true}] 468 | } 469 | 470 | // ------------------------------------------------------------------------------------------ 471 | // -- Module definition 472 | // ------------------------------------------------------------------------------------------ 473 | // Check for AMD/Module support, otherwise define Bullet as a global variable. 474 | 475 | if (typeof define !== 'undefined' && define.amd) { 476 | // AMD. Register as an anonymous module. 477 | define(function () { 478 | return new Bullet(); 479 | }); 480 | } else if (typeof module !== 'undefined' && module.exports) { 481 | module.exports = new Bullet(); 482 | } else { 483 | window.Bullet = new Bullet(); 484 | } 485 | })(); -------------------------------------------------------------------------------- /dist/bullet.min.js: -------------------------------------------------------------------------------- 1 | "use strict";var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};!function(){function e(){function e(e,t,n){this.message="Bullet:: ["+e+"] "+t+", but received: "+n;var o=new Error(this.message);void 0!==o.stack&&(this.stack=o.stack)}function t(e,t,n,o){this.message="Bullet:: ["+e+"] Expected parameter - "+t+" - to be type: "+o+", but received type: "+(void 0===n?"undefined":_typeof(n));var a=new TypeError(this.message);void 0!==a.stack&&(this.stack=a.stack)}function n(e){this.message="Bullet:: ["+e+"] Expected event name parameter to be longer than 0 characters";var t=new Error(this.message);void 0!==t.stack&&(this.stack=t.stack)}function o(e){this.message="Bullet:: ["+e+"] Expected event names array to contain one or more event names";var t=new Error(this.message);void 0!==t.stack&&(this.stack=t.stack)}function a(e,t){this.message="Bullet:: ["+e+'] Event string: "'+t+'" does not exist within the events dictionary\nPlease use the Bullet.addEventName method to add this string.';var n=new Error(this.message);void 0!==n.stack&&(this.stack=n.stack)}function r(e,t){this.message="Bullet:: ["+e+'] Event string: "'+t+'" is not mapped to any callbacks\nPlease use the Bullet.on method to map this string to a callback.';var n=new Error(this.message);void 0!==n.stack&&(this.stack=n.stack)}function i(e){e.prototype=new Error,e.prototype.name=e.name,e.prototype.constructor=e}function c(e,t){for(var n in d[e].callbacks){var o=d[e].callbacks[n];"function"==typeof o.cb&&o.cb(t),"boolean"==typeof o.once&&!0===o.once&&w.off(e,o.cb)}}function l(e){var t={};for(var n in e)t[n]={cb:e[n].cb,once:e[n].once};return t}function s(e){for(var t in d[e].callbacks){var n=d[e].callbacks[t].cb;n[v].totalEvents--,0===n[v].totalEvents?delete n[v]:delete n[v][e]}}function f(){for(var e in d)s(e)}i(e),i(t),i(n),i(o),i(a),i(r);var v="__bullet_pubsub__",w=this,d={},h=!1,u=!0;w._errors={ParamCountError:e,ParamTypeError:t,EventNameLengthError:n,EventNamesArrayLengthError:o,UndeclaredEventError:a,UnmappedEventError:r},w.events={},w._getMappings=function(){var e={};for(var t in d)e[t]={callbacks:l(d[t].callbacks),totalCallbacks:d[t].totalCallbacks};return e},w.on=function(o,r,i){if(arguments.length<2||arguments.length>3)throw new e("on","Expected between 2 and 3 parameters",arguments.length);if("string"!=typeof o)throw new t("on","event name",o,"string");if(0===o.length)throw new n("on");if(h&&void 0===w.events[o])throw new a("on",o);if("function"!=typeof r)throw new t("on","callback",r,"function");if(void 0!==i&&"boolean"!=typeof i)throw new t("on","once",i,"boolean");var c=null;void 0!==d[o]?(void 0===r[v]&&(r[v]={totalEvents:0}),void 0===r[v][o]&&(c=d[o].totalCallbacks,d[o].totalCallbacks++,d[o].callbacks[c]={cb:r,once:"boolean"==typeof i&&i},r[v][o]=c,r[v].totalEvents++),"boolean"==typeof i&&(c=r[v][o],d[o].callbacks[c].once=i)):(d[o]={callbacks:{}},c=0,d[o].callbacks[c]={cb:r,once:!!i},d[o].totalCallbacks=1,void 0===r[v]?(r[v]={},r[v].totalEvents=1):r[v].totalEvents++,r[v][o]=c)},w.once=function(o,r){if(2!==arguments.length)throw new e("once","Expected 2 parameters",arguments.length);if("string"!=typeof o)throw new t("once","event name",o,"string");if(0===o.length)throw new n("once");if(h&&void 0===w.events[o])throw new a("once",o);if("function"!=typeof r)throw new t("once","callback",r,"function");w.on(o,r,!0)},w.off=function(e,o){if(0===arguments.length)return f(),void(d={});if("string"!=typeof e)throw new t("off","event name",e,"string");if(0===e.length)throw new n("off");if(h&&void 0===w.events[e])throw new a("off",e);if(void 0!==d[e])if("function"==typeof o){var r=o[v][e];void 0!==d[e].callbacks[r]&&(delete d[e].callbacks[r],delete o[v][e],d[e].totalCallbacks--,o[v].totalEvents--,0===d[e].totalCallbacks&&delete d[e],0===o[v].totalEvents&&delete o[v])}else{if(void 0!==o)throw new t("off","callback",o,"function");s(e),delete d[e]}},w.replaceCallback=function(e,o,i,c){if("string"!=typeof e)throw new t("replaceCallback","event name",e,"string");if(0===e.length)throw new n("replaceCallback");if(void 0===d[e])throw new r("replaceCallback",e);if(h&&void 0===w.events[e])throw new a("replaceCallback",e);if("function"!=typeof o)throw new t("replaceCallback","callback",o,"function");if("function"!=typeof i)throw new t("replaceCallback","callback",i,"function");if(void 0!==c&&"boolean"!=typeof c)throw new t("replaceCallback","once",c,"boolean");w.off(e,o),w.on(e,i,c)},w.replaceAllCallbacks=function(e,o,i){if("string"!=typeof e)throw new t("replace","event name",e,"string");if(0===e.length)throw new n("replace");if(void 0===d[e])throw new r("replace",e);if(h&&void 0===w.events[e])throw new a("replace",e);if("function"!=typeof o)throw new t("replace","callback",o,"function");if(void 0!==i&&"boolean"!=typeof i)throw new t("replace","once",i,"boolean");w.off(e),w.on(e,o,i)},w.trigger=function(e,o){if("string"!=typeof e)throw new t("trigger","event name",e,"string");if(0===e.length)throw new n("trigger");if(h&&void 0===w.events[e])throw new a("trigger",e);if(void 0!==d[e])u&&"undefined"!=typeof window?window.setTimeout(function(){c(e,o)},0):c(e,o);else if(h)throw new r("trigger",e)},w.addEventName=function(e){if("string"!=typeof e)throw new t("addEventName","event name",e,"string");if(0===e.length)throw new n("addEventName");w.events[e]=e},w.addMultipleEventNames=function(e){if(!(e instanceof Array))throw new t("addMultipleEventNames","event names",e,"array");if(0===e.length)throw new o("addMultipleEventNames");var n=0,a=e.length;for(n;n 2 | 3 | 4 | 5 | Bullet 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

Foo events triggered: 0

14 |

Bar events triggered: 0

15 |

Baz events triggered: 0

16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /example/js/app/app.js: -------------------------------------------------------------------------------- 1 | // TODO : Visually display the current state of Bullet.events 2 | // TODO : Provide buttons to add/remove events 3 | // TODO : Provide buttons to map/unmap events to callbacks?? 4 | 5 | (function () { 6 | 7 | 'use strict'; 8 | 9 | var Bullet = window.Bullet; 10 | 11 | // Bullet.setStrictMode(true); 12 | 13 | Bullet.addEventName('foo'); 14 | Bullet.addEventName('bar'); 15 | Bullet.addEventName('baz'); 16 | 17 | var buttonFoo = document.getElementById('button-foo'); 18 | var buttonBar = document.getElementById('button-bar'); 19 | var buttonBaz = document.getElementById('button-baz'); 20 | var textFoo = document.getElementById('text-foo'); 21 | var textBar = document.getElementById('text-bar'); 22 | var textBaz = document.getElementById('text-baz'); 23 | 24 | var numFooEvents = 0; 25 | var numBarEvents = 0; 26 | var numBazEvents = 0; 27 | 28 | function fooEventHandler () 29 | { 30 | numFooEvents++; 31 | textFoo.innerHTML = numFooEvents; 32 | } 33 | 34 | function barEventHandler () 35 | { 36 | numBarEvents++; 37 | textBar.innerHTML = numBarEvents; 38 | } 39 | 40 | function bazEventHandler () 41 | { 42 | numBazEvents++; 43 | textBaz.innerHTML = numBazEvents; 44 | } 45 | 46 | function triggerFoo () 47 | { 48 | Bullet.trigger(Bullet.events.foo); 49 | } 50 | 51 | function triggerBar () 52 | { 53 | Bullet.trigger(Bullet.events.bar); 54 | } 55 | 56 | function triggerBaz () 57 | { 58 | Bullet.trigger(Bullet.events.baz); 59 | } 60 | 61 | Bullet.on(Bullet.events.foo, fooEventHandler); 62 | Bullet.on(Bullet.events.bar, barEventHandler); 63 | Bullet.once(Bullet.events.baz, bazEventHandler); 64 | 65 | buttonFoo.onclick = triggerFoo; 66 | buttonBar.onclick = triggerBar; 67 | buttonBaz.onclick = triggerBaz; 68 | })(); -------------------------------------------------------------------------------- /example/js/libs/bullet.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; 4 | 5 | (function () { 6 | 7 | function Bullet() { 8 | // ------------------------------------------------------------------------------------------ 9 | // -- Custom Errors 10 | // ------------------------------------------------------------------------------------------ 11 | function ParamCountError(methodName, expectedParamsString, paramCount) { 12 | this.message = 'Bullet:: [' + methodName + '] ' + expectedParamsString + ', but received: ' + paramCount; 13 | var error = new Error(this.message); 14 | if (typeof error.stack !== 'undefined') this.stack = error.stack; 15 | } 16 | setupCustomError(ParamCountError); 17 | 18 | function ParamTypeError(methodName, parameterName, parameter, expectedType) { 19 | this.message = 'Bullet:: [' + methodName + '] Expected parameter - ' + parameterName + ' - to be type: ' + expectedType + ', but received type: ' + (typeof parameter === 'undefined' ? 'undefined' : _typeof(parameter)); 20 | var error = new TypeError(this.message); 21 | if (typeof error.stack !== 'undefined') this.stack = error.stack; 22 | } 23 | setupCustomError(ParamTypeError); 24 | 25 | function EventNameLengthError(methodName) { 26 | this.message = 'Bullet:: [' + methodName + '] Expected event name parameter to be longer than 0 characters'; 27 | var error = new Error(this.message); 28 | if (typeof error.stack !== 'undefined') this.stack = error.stack; 29 | } 30 | setupCustomError(EventNameLengthError); 31 | 32 | function EventNamesArrayLengthError(methodName) { 33 | this.message = 'Bullet:: [' + methodName + '] Expected event names array to contain one or more event names'; 34 | var error = new Error(this.message); 35 | if (typeof error.stack !== 'undefined') this.stack = error.stack; 36 | } 37 | setupCustomError(EventNamesArrayLengthError); 38 | 39 | function UndeclaredEventError(methodName, eventName) { 40 | this.message = 'Bullet:: [' + methodName + '] Event string: "' + eventName + '" does not exist within the events dictionary\nPlease use the Bullet.addEventName method to add this string.'; 41 | 42 | var error = new Error(this.message); 43 | if (typeof error.stack !== 'undefined') this.stack = error.stack; 44 | } 45 | setupCustomError(UndeclaredEventError); 46 | 47 | function UnmappedEventError(methodName, eventName) { 48 | this.message = 'Bullet:: [' + methodName + '] Event string: "' + eventName + '" is not mapped to any callbacks\nPlease use the Bullet.on method to map this string to a callback.'; 49 | 50 | var error = new Error(this.message); 51 | if (typeof error.stack !== 'undefined') this.stack = error.stack; 52 | } 53 | setupCustomError(UnmappedEventError); 54 | 55 | function setupCustomError(CustomError) { 56 | CustomError.prototype = new Error(); 57 | CustomError.prototype.name = CustomError.name; 58 | CustomError.prototype.constructor = CustomError; 59 | } 60 | 61 | var _CALLBACK_NAMESPACE = '__bullet_pubsub__'; 62 | // ------------------------------------------------------------------------------------------ 63 | // -- Private variables 64 | // ------------------------------------------------------------------------------------------ 65 | var _self = this; 66 | var _mappings = {}; 67 | var _strictMode = false; 68 | var _triggerAsync = true; 69 | 70 | // Expose custom error type constructors (for testing), but use an underscore to imply privacy. 71 | _self._errors = { 72 | ParamCountError: ParamCountError, 73 | ParamTypeError: ParamTypeError, 74 | EventNameLengthError: EventNameLengthError, 75 | EventNamesArrayLengthError: EventNamesArrayLengthError, 76 | UndeclaredEventError: UndeclaredEventError, 77 | UnmappedEventError: UnmappedEventError 78 | }; 79 | 80 | // ------------------------------------------------------------------------------------------ 81 | // -- Public variables 82 | // ------------------------------------------------------------------------------------------ 83 | _self.events = {}; 84 | 85 | // ------------------------------------------------------------------------------------------ 86 | // -- Private methods 87 | // ------------------------------------------------------------------------------------------ 88 | function _runCallback(eventName, data) { 89 | for (var id in _mappings[eventName].callbacks) { 90 | var callbackObject = _mappings[eventName].callbacks[id]; 91 | 92 | if (typeof callbackObject.cb === 'function') callbackObject.cb(data); 93 | if (typeof callbackObject.once === 'boolean' && callbackObject.once === true) _self.off(eventName, callbackObject.cb); 94 | } 95 | } 96 | 97 | function _cloneCallbacks(callbacks) { 98 | var clonedCallbacks = {}; 99 | 100 | for (var callbackName in callbacks) { 101 | clonedCallbacks[callbackName] = { 102 | cb: callbacks[callbackName].cb, 103 | once: callbacks[callbackName].once 104 | }; 105 | } 106 | 107 | return clonedCallbacks; 108 | } 109 | 110 | function _deleteAllCallbackReferencesForEvent(eventName) { 111 | for (var id in _mappings[eventName].callbacks) { 112 | var callback = _mappings[eventName].callbacks[id].cb; 113 | 114 | callback[_CALLBACK_NAMESPACE].totalEvents--; 115 | 116 | if (callback[_CALLBACK_NAMESPACE].totalEvents === 0) { 117 | delete callback[_CALLBACK_NAMESPACE]; 118 | } else { 119 | delete callback[_CALLBACK_NAMESPACE][eventName]; 120 | } 121 | } 122 | } 123 | 124 | function _deleteAllCallbackReferences() { 125 | for (var eventName in _mappings) { 126 | _deleteAllCallbackReferencesForEvent(eventName); 127 | } 128 | } 129 | 130 | // Expose _getMappings method (for testing), but use an underscore to imply privacy. 131 | _self._getMappings = function () { 132 | // Return a dictionary object that has no effect on app state to ensure '_mappings' 133 | // stays private, even if the value returned from this method is modified. 134 | var clonedMappings = {}; 135 | 136 | for (var mapping in _mappings) { 137 | clonedMappings[mapping] = { 138 | callbacks: _cloneCallbacks(_mappings[mapping].callbacks), 139 | totalCallbacks: _mappings[mapping].totalCallbacks 140 | }; 141 | } 142 | 143 | return clonedMappings; 144 | }; 145 | 146 | // ------------------------------------------------------------------------------------------ 147 | // -- Public methods 148 | // ------------------------------------------------------------------------------------------ 149 | _self.on = function (eventName, fn, once) { 150 | if (arguments.length < 2 || arguments.length > 3) { 151 | throw new ParamCountError('on', 'Expected between 2 and 3 parameters', arguments.length); 152 | } 153 | 154 | if (typeof eventName !== 'string') { 155 | throw new ParamTypeError('on', 'event name', eventName, 'string'); 156 | } else if (eventName.length === 0) { 157 | throw new EventNameLengthError('on'); 158 | } else if (_strictMode && typeof _self.events[eventName] === 'undefined') { 159 | throw new UndeclaredEventError('on', eventName); 160 | } 161 | 162 | if (typeof fn !== 'function') { 163 | throw new ParamTypeError('on', 'callback', fn, 'function'); 164 | } 165 | 166 | if (typeof once !== 'undefined' && typeof once !== 'boolean') { 167 | throw new ParamTypeError('on', 'once', once, 'boolean'); 168 | } 169 | 170 | // Create a reference between the callback and stored event. 171 | var callbackId = null; 172 | 173 | // If the named event object already exists in the dictionary... 174 | if (typeof _mappings[eventName] !== 'undefined') { 175 | // Attempt to get the callback ID from the callback itself. 176 | if (typeof fn[_CALLBACK_NAMESPACE] === 'undefined') { 177 | fn[_CALLBACK_NAMESPACE] = { 178 | totalEvents: 0 179 | }; 180 | } 181 | 182 | // Add a new callback object to the existing event object. 183 | if (typeof fn[_CALLBACK_NAMESPACE][eventName] === 'undefined') { 184 | callbackId = _mappings[eventName].totalCallbacks; 185 | 186 | _mappings[eventName].totalCallbacks++; 187 | 188 | _mappings[eventName].callbacks[callbackId] = { 189 | cb: fn, 190 | once: typeof once === 'boolean' ? once : false 191 | }; 192 | 193 | // On the callback, create a reference to the event mapping. 194 | fn[_CALLBACK_NAMESPACE][eventName] = callbackId; 195 | fn[_CALLBACK_NAMESPACE].totalEvents++; 196 | } 197 | 198 | if (typeof once === 'boolean') { 199 | // Get the callback ID from the value of the existing event name key. 200 | callbackId = fn[_CALLBACK_NAMESPACE][eventName]; 201 | 202 | // The function already exists, so update it's 'once' value. 203 | _mappings[eventName].callbacks[callbackId].once = once; 204 | } 205 | } else { 206 | // Create a new event object in the dictionary with the specified name and callback. 207 | _mappings[eventName] = { 208 | callbacks: {} 209 | }; 210 | 211 | callbackId = 0; 212 | 213 | _mappings[eventName].callbacks[callbackId] = { cb: fn, once: !!once }; 214 | _mappings[eventName].totalCallbacks = 1; 215 | 216 | // On the callback, create a reference to the event mapping. 217 | if (typeof fn[_CALLBACK_NAMESPACE] === 'undefined') { 218 | fn[_CALLBACK_NAMESPACE] = {}; 219 | fn[_CALLBACK_NAMESPACE].totalEvents = 1; 220 | } else { 221 | fn[_CALLBACK_NAMESPACE].totalEvents++; 222 | } 223 | 224 | fn[_CALLBACK_NAMESPACE][eventName] = callbackId; 225 | } 226 | }; 227 | 228 | _self.once = function (eventName, fn) { 229 | if (arguments.length !== 2) { 230 | throw new ParamCountError('once', 'Expected 2 parameters', arguments.length); 231 | } else if (typeof eventName !== 'string') { 232 | throw new ParamTypeError('once', 'event name', eventName, 'string'); 233 | } else if (eventName.length === 0) { 234 | throw new EventNameLengthError('once'); 235 | } else if (_strictMode && typeof _self.events[eventName] === 'undefined') { 236 | throw new UndeclaredEventError('once', eventName); 237 | } 238 | 239 | if (typeof fn !== 'function') { 240 | throw new ParamTypeError('once', 'callback', fn, 'function'); 241 | } 242 | 243 | _self.on(eventName, fn, true); 244 | }; 245 | 246 | _self.off = function (eventName, fn) { 247 | if (arguments.length === 0) { 248 | // delete all references to Bullet that exist on mapped callbacks. 249 | _deleteAllCallbackReferences(); 250 | 251 | // Remove all mappings from the dictionary. 252 | _mappings = {}; 253 | 254 | return; 255 | } else if (typeof eventName !== 'string') { 256 | throw new ParamTypeError('off', 'event name', eventName, 'string'); 257 | } else if (eventName.length === 0) { 258 | throw new EventNameLengthError('off'); 259 | } else if (_strictMode && typeof _self.events[eventName] === 'undefined') { 260 | throw new UndeclaredEventError('off', eventName); 261 | } 262 | 263 | if (typeof _mappings[eventName] === 'undefined') { 264 | // There is no mapping to remove, so return silently. 265 | return; 266 | } 267 | 268 | // Remove just the function, if passed as a parameter and in the dictionary. 269 | if (typeof fn === 'function') { 270 | // if (typeof fn[_CALLBACK_NAMESPACE] === 'undefined' || typeof fn[_CALLBACK_NAMESPACE][eventName] === 'undefined') { 271 | // // TODO: Throw error here if in strict mode. 272 | // } 273 | 274 | // Retrieve a reference to the stored event from the callback. 275 | var id = fn[_CALLBACK_NAMESPACE][eventName]; 276 | var fnToRemove = _mappings[eventName].callbacks[id]; 277 | 278 | if (typeof fnToRemove !== 'undefined') { 279 | // delete the callback object from the dictionary. 280 | delete _mappings[eventName].callbacks[id]; 281 | 282 | // delete the event reference on the callback function. 283 | delete fn[_CALLBACK_NAMESPACE][eventName]; 284 | 285 | _mappings[eventName].totalCallbacks--; 286 | fn[_CALLBACK_NAMESPACE].totalEvents--; 287 | 288 | if (_mappings[eventName].totalCallbacks === 0) { 289 | // There are no more functions in the dictionary that are 290 | // registered to this event, so delete the named event object. 291 | delete _mappings[eventName]; 292 | } 293 | 294 | if (fn[_CALLBACK_NAMESPACE].totalEvents === 0) { 295 | // There are no more events registered on this callback, 296 | // so delete the Bullet namespace. 297 | delete fn[_CALLBACK_NAMESPACE]; 298 | } 299 | } 300 | } else if (typeof fn !== 'undefined') { 301 | throw new ParamTypeError('off', 'callback', fn, 'function'); 302 | } else { 303 | // No callback was passed to the 'off' method... 304 | 305 | // For each callback in _mappings[eventName], delete the reference to 306 | // the specified event name on the callback itself. 307 | _deleteAllCallbackReferencesForEvent(eventName); 308 | 309 | // Delete all functions in the dictionary that are registered to this 310 | // event by deleting the named event object. 311 | delete _mappings[eventName]; 312 | } 313 | }; 314 | 315 | // Replace a single mapped callback for the specified event name with a new callback. 316 | _self.replaceCallback = function (eventName, oldFn, newFn, once) { 317 | if (typeof eventName !== 'string') { 318 | throw new ParamTypeError('replaceCallback', 'event name', eventName, 'string'); 319 | } else if (eventName.length === 0) { 320 | throw new EventNameLengthError('replaceCallback'); 321 | } else if (typeof _mappings[eventName] === 'undefined') { 322 | throw new UnmappedEventError('replaceCallback', eventName); 323 | } else if (_strictMode && typeof _self.events[eventName] === 'undefined') { 324 | throw new UndeclaredEventError('replaceCallback', eventName); 325 | } 326 | 327 | if (typeof oldFn !== 'function') { 328 | throw new ParamTypeError('replaceCallback', 'callback', oldFn, 'function'); 329 | } 330 | 331 | if (typeof newFn !== 'function') { 332 | throw new ParamTypeError('replaceCallback', 'callback', newFn, 'function'); 333 | } 334 | 335 | if (typeof once !== 'undefined' && typeof once !== 'boolean') { 336 | throw new ParamTypeError('replaceCallback', 'once', once, 'boolean'); 337 | } 338 | 339 | _self.off(eventName, oldFn); 340 | _self.on(eventName, newFn, once); 341 | }; 342 | 343 | // Replace all of the specified event name’s mapped callbacks with the specified callback. 344 | _self.replaceAllCallbacks = function (eventName, newFn, once) { 345 | if (typeof eventName !== 'string') { 346 | throw new ParamTypeError('replace', 'event name', eventName, 'string'); 347 | } else if (eventName.length === 0) { 348 | throw new EventNameLengthError('replace'); 349 | } else if (typeof _mappings[eventName] === 'undefined') { 350 | throw new UnmappedEventError('replace', eventName); 351 | } else if (_strictMode && typeof _self.events[eventName] === 'undefined') { 352 | throw new UndeclaredEventError('replace', eventName); 353 | } 354 | 355 | if (typeof newFn !== 'function') { 356 | throw new ParamTypeError('replace', 'callback', newFn, 'function'); 357 | } 358 | 359 | if (typeof once !== 'undefined' && typeof once !== 'boolean') { 360 | throw new ParamTypeError('replace', 'once', once, 'boolean'); 361 | } 362 | 363 | _self.off(eventName); 364 | _self.on(eventName, newFn, once); 365 | }; 366 | 367 | _self.trigger = function (eventName, data) { 368 | if (typeof eventName !== 'string') { 369 | throw new ParamTypeError('trigger', 'event name', eventName, 'string'); 370 | } else if (eventName.length === 0) { 371 | throw new EventNameLengthError('trigger'); 372 | } else if (_strictMode && typeof _self.events[eventName] === 'undefined') { 373 | throw new UndeclaredEventError('trigger', eventName); 374 | } 375 | 376 | if (typeof _mappings[eventName] === 'undefined') { 377 | if (_strictMode) throw new UnmappedEventError('trigger', eventName); 378 | 379 | // Return silently if not in strict mode. 380 | return; 381 | } 382 | 383 | // Check whether or not this is a browser environment. 384 | if (_triggerAsync && typeof window !== 'undefined') { 385 | window.setTimeout(function () { 386 | _runCallback(eventName, data); 387 | }, 0); 388 | } else { 389 | _runCallback(eventName, data); 390 | } 391 | }; 392 | 393 | _self.addEventName = function (eventName) { 394 | if (typeof eventName !== 'string') { 395 | throw new ParamTypeError('addEventName', 'event name', eventName, 'string'); 396 | } else if (eventName.length === 0) { 397 | throw new EventNameLengthError('addEventName'); 398 | } 399 | 400 | _self.events[eventName] = eventName; 401 | }; 402 | 403 | _self.addMultipleEventNames = function (eventNames) { 404 | if (!(eventNames instanceof Array)) { 405 | throw new ParamTypeError('addMultipleEventNames', 'event names', eventNames, 'array'); 406 | } else if (eventNames.length === 0) { 407 | throw new EventNamesArrayLengthError('addMultipleEventNames'); 408 | } 409 | 410 | var i = 0; 411 | var length = eventNames.length; 412 | 413 | for (i; i < length; i++) { 414 | var currentEventName = eventNames[i]; 415 | 416 | _self.addEventName(currentEventName); 417 | } 418 | }; 419 | 420 | _self.removeEventName = function (eventName) { 421 | if (typeof eventName !== 'string') { 422 | throw new ParamTypeError('removeEventName', 'event name', eventName, 'string'); 423 | } else if (eventName.length === 0) { 424 | throw new EventNameLengthError('removeEventName'); 425 | } 426 | 427 | if (_self.events[eventName]) delete _self.events[eventName]; 428 | }; 429 | 430 | _self.getStrictMode = function () { 431 | // Return a boolean that doesn't directly point to the internal '_strictMode' property. 432 | return _strictMode === true; 433 | }; 434 | 435 | _self.setStrictMode = function (useStrictMode) { 436 | if (typeof useStrictMode !== 'boolean') { 437 | throw new ParamTypeError('setStrictMode', 'strict mode', useStrictMode, 'boolean'); 438 | } 439 | 440 | _strictMode = useStrictMode; 441 | }; 442 | 443 | _self.getTriggerAsync = function () { 444 | // Return a boolean that doesn't directly point to the internal '_triggerAsync' property. 445 | return _triggerAsync === true; 446 | }; 447 | 448 | _self.setTriggerAsync = function (useAsync) { 449 | if (typeof useAsync !== 'boolean') { 450 | throw new ParamTypeError('setTriggerAsync', 'trigger async', useAsync, 'boolean'); 451 | } 452 | 453 | _triggerAsync = useAsync; 454 | }; 455 | 456 | // TODO : Create a 'replaceAllEventNames' method with an array of strings passed as a param. 457 | // - include type checks for string while looping over the array. 458 | 459 | // TODO : Create a 'removeAllEventNames' method. No params necessary. 460 | // – Internally this could simply call 'replaceAllEventNames' and pass an empty array as a param. 461 | 462 | // TODO : Create an 'onAny' method with an array of strings passed as the first param and a single callback as the second. 463 | // - include type checks for string while looping over the array. 464 | 465 | // TODO : Create an 'onMultiple' method with an array of flat objects passed as a param. 466 | // - example of required param structure: 467 | // [{eventName: 'someEvent', callback: someCallback, once: false}, {eventName: 'anotherEvent', callback: anotherCallback, once: true}] 468 | } 469 | 470 | // ------------------------------------------------------------------------------------------ 471 | // -- Module definition 472 | // ------------------------------------------------------------------------------------------ 473 | // Check for AMD/Module support, otherwise define Bullet as a global variable. 474 | 475 | if (typeof define !== 'undefined' && define.amd) { 476 | // AMD. Register as an anonymous module. 477 | define(function () { 478 | return new Bullet(); 479 | }); 480 | } else if (typeof module !== 'undefined' && module.exports) { 481 | module.exports = new Bullet(); 482 | } else { 483 | window.Bullet = new Bullet(); 484 | } 485 | })(); -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const rename = require('gulp-rename'); 3 | const babel = require('gulp-babel'); 4 | const uglify = require('gulp-uglify'); 5 | const pump = require('pump'); 6 | 7 | gulp.task('compile', (cb) => { 8 | pump([ 9 | gulp.src('src/bullet.js'), 10 | babel({presets: ['es2015']}), 11 | gulp.dest('dist'), 12 | gulp.dest('example/js/libs'), 13 | rename('bullet.min.js'), 14 | uglify({ 15 | compress : { 16 | drop_console: true 17 | }, 18 | mangle : true, 19 | }), 20 | gulp.dest('dist'), 21 | ], cb); 22 | }); 23 | 24 | gulp.task('default', ['watch']); 25 | 26 | gulp.task('watch', ['compile'], () => { 27 | gulp.watch('src/bullet.js', ['compile']); 28 | }); 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bullet-pubsub", 3 | "title": "Bullet", 4 | "description": "A lightweight and simple to use pub-sub library.", 5 | "version": "2.3.0", 6 | "homepage": "https://github.com/munkychop/bullet", 7 | "author": { 8 | "name": "Ivan Hayes", 9 | "url": "http://www.ivanhayes.com" 10 | }, 11 | "main": "dist/bullet.js", 12 | "scripts": { 13 | "test": "mocha test/**/*.js", 14 | "lint": "eslint src/bullet.js", 15 | "watch": "gulp", 16 | "start": "npm run test && npm run watch", 17 | "dist": "npm run lint && npm run test && gulp compile" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git://github.com/munkychop/bullet.git" 22 | }, 23 | "keywords": [ 24 | "bullet", 25 | "io", 26 | "emit", 27 | "events", 28 | "javascript", 29 | "library", 30 | "pub-sub", 31 | "pubsub", 32 | "communication" 33 | ], 34 | "private": false, 35 | "license": "MIT", 36 | "devDependencies": { 37 | "babel-preset-es2015": "^6.24.1", 38 | "chai": "^3.0.0", 39 | "eslint": "^4.8.0", 40 | "gulp": "^3.9.1", 41 | "gulp-babel": "^7.0.0", 42 | "gulp-rename": "^1.2.2", 43 | "gulp-uglify": "^3.0.0", 44 | "mocha": "^3.5.3", 45 | "pump": "^1.0.2", 46 | "sinon": "^1.16.1", 47 | "sinon-chai": "^2.8.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/bullet.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | function Bullet () { 4 | // ------------------------------------------------------------------------------------------ 5 | // -- Custom Errors 6 | // ------------------------------------------------------------------------------------------ 7 | function ParamCountError (methodName, expectedParamsString, paramCount) { 8 | this.message = 'Bullet:: [' + methodName + '] ' + expectedParamsString + ', but received: ' + paramCount; 9 | const error = new Error(this.message); 10 | if (typeof error.stack !== 'undefined') this.stack = error.stack; 11 | } 12 | setupCustomError(ParamCountError); 13 | 14 | function ParamTypeError (methodName, parameterName, parameter, expectedType) { 15 | this.message = 'Bullet:: [' + methodName + '] Expected parameter - ' + parameterName + ' - to be type: ' + expectedType + ', but received type: ' + typeof parameter; 16 | const error = new TypeError(this.message); 17 | if (typeof error.stack !== 'undefined') this.stack = error.stack; 18 | } 19 | setupCustomError(ParamTypeError); 20 | 21 | function EventNameLengthError (methodName) { 22 | this.message = 'Bullet:: [' + methodName + '] Expected event name parameter to be longer than 0 characters'; 23 | const error = new Error(this.message); 24 | if (typeof error.stack !== 'undefined') this.stack = error.stack; 25 | } 26 | setupCustomError(EventNameLengthError); 27 | 28 | function EventNamesArrayLengthError (methodName) { 29 | this.message = 'Bullet:: [' + methodName + '] Expected event names array to contain one or more event names'; 30 | const error = new Error(this.message); 31 | if (typeof error.stack !== 'undefined') this.stack = error.stack; 32 | } 33 | setupCustomError(EventNamesArrayLengthError); 34 | 35 | function UndeclaredEventError (methodName, eventName) { 36 | this.message = 'Bullet:: [' + methodName + '] Event string: "' + eventName + '" does not exist within the events dictionary\nPlease use the Bullet.addEventName method to add this string.'; 37 | 38 | const error = new Error(this.message); 39 | if (typeof error.stack !== 'undefined') this.stack = error.stack; 40 | } 41 | setupCustomError(UndeclaredEventError); 42 | 43 | function UnmappedEventError (methodName, eventName) { 44 | this.message = 'Bullet:: [' + methodName + '] Event string: "' + eventName + '" is not mapped to any callbacks\nPlease use the Bullet.on method to map this string to a callback.'; 45 | 46 | const error = new Error(this.message); 47 | if (typeof error.stack !== 'undefined') this.stack = error.stack; 48 | } 49 | setupCustomError(UnmappedEventError); 50 | 51 | function setupCustomError (CustomError) { 52 | CustomError.prototype = new Error(); 53 | CustomError.prototype.name = CustomError.name; 54 | CustomError.prototype.constructor = CustomError; 55 | } 56 | 57 | const _CALLBACK_NAMESPACE = '__bullet_pubsub__'; 58 | // ------------------------------------------------------------------------------------------ 59 | // -- Private variables 60 | // ------------------------------------------------------------------------------------------ 61 | const _self = this; 62 | let _mappings = {}; 63 | let _strictMode = false; 64 | let _triggerAsync = true; 65 | 66 | // Expose custom error type constructors (for testing), but use an underscore to imply privacy. 67 | _self._errors = { 68 | ParamCountError : ParamCountError, 69 | ParamTypeError : ParamTypeError, 70 | EventNameLengthError : EventNameLengthError, 71 | EventNamesArrayLengthError: EventNamesArrayLengthError, 72 | UndeclaredEventError : UndeclaredEventError, 73 | UnmappedEventError : UnmappedEventError, 74 | }; 75 | 76 | 77 | // ------------------------------------------------------------------------------------------ 78 | // -- Public variables 79 | // ------------------------------------------------------------------------------------------ 80 | _self.events = {}; 81 | 82 | 83 | // ------------------------------------------------------------------------------------------ 84 | // -- Private methods 85 | // ------------------------------------------------------------------------------------------ 86 | function _runCallback (eventName, data) { 87 | for (const id in _mappings[eventName].callbacks) { 88 | const callbackObject = _mappings[eventName].callbacks[id]; 89 | 90 | if (typeof callbackObject.cb === 'function') callbackObject.cb(data); 91 | if (typeof callbackObject.once === 'boolean' && callbackObject.once === true) _self.off(eventName, callbackObject.cb); 92 | } 93 | } 94 | 95 | function _cloneCallbacks (callbacks) { 96 | const clonedCallbacks = {}; 97 | 98 | for (const callbackName in callbacks) { 99 | clonedCallbacks[callbackName] = { 100 | cb : callbacks[callbackName].cb, 101 | once : callbacks[callbackName].once 102 | }; 103 | } 104 | 105 | return clonedCallbacks; 106 | } 107 | 108 | function _deleteAllCallbackReferencesForEvent (eventName) { 109 | for (const id in _mappings[eventName].callbacks) { 110 | const callback = _mappings[eventName].callbacks[id].cb; 111 | 112 | callback[_CALLBACK_NAMESPACE].totalEvents--; 113 | 114 | if (callback[_CALLBACK_NAMESPACE].totalEvents === 0) { 115 | delete callback[_CALLBACK_NAMESPACE]; 116 | } 117 | else { 118 | delete callback[_CALLBACK_NAMESPACE][eventName]; 119 | } 120 | } 121 | } 122 | 123 | function _deleteAllCallbackReferences () { 124 | for (const eventName in _mappings) { 125 | _deleteAllCallbackReferencesForEvent(eventName); 126 | } 127 | } 128 | 129 | // Expose _getMappings method (for testing), but use an underscore to imply privacy. 130 | _self._getMappings = function () { 131 | // Return a dictionary object that has no effect on app state to ensure '_mappings' 132 | // stays private, even if the value returned from this method is modified. 133 | const clonedMappings = {}; 134 | 135 | for (const mapping in _mappings) { 136 | clonedMappings[mapping] = { 137 | callbacks : _cloneCallbacks(_mappings[mapping].callbacks), 138 | totalCallbacks : _mappings[mapping].totalCallbacks 139 | }; 140 | } 141 | 142 | return clonedMappings; 143 | }; 144 | 145 | 146 | // ------------------------------------------------------------------------------------------ 147 | // -- Public methods 148 | // ------------------------------------------------------------------------------------------ 149 | _self.on = function (eventName, fn, once) { 150 | if (arguments.length < 2 || arguments.length > 3) { 151 | throw new ParamCountError('on', 'Expected between 2 and 3 parameters', arguments.length); 152 | } 153 | 154 | if (typeof eventName !== 'string') { 155 | throw new ParamTypeError('on', 'event name', eventName, 'string'); 156 | } 157 | else if (eventName.length === 0) { 158 | throw new EventNameLengthError('on'); 159 | } 160 | else if (_strictMode && typeof _self.events[eventName] === 'undefined') { 161 | throw new UndeclaredEventError('on', eventName); 162 | } 163 | 164 | if (typeof fn !== 'function') { 165 | throw new ParamTypeError('on', 'callback', fn, 'function'); 166 | } 167 | 168 | if (typeof once !== 'undefined' && typeof once !== 'boolean') { 169 | throw new ParamTypeError('on', 'once', once, 'boolean'); 170 | } 171 | 172 | // Create a reference between the callback and stored event. 173 | let callbackId = null; 174 | 175 | // If the named event object already exists in the dictionary... 176 | if (typeof _mappings[eventName] !== 'undefined') { 177 | // Attempt to get the callback ID from the callback itself. 178 | if (typeof fn[_CALLBACK_NAMESPACE] === 'undefined') { 179 | fn[_CALLBACK_NAMESPACE] = { 180 | totalEvents: 0 181 | }; 182 | } 183 | 184 | // Add a new callback object to the existing event object. 185 | if (typeof fn[_CALLBACK_NAMESPACE][eventName] === 'undefined') { 186 | callbackId = _mappings[eventName].totalCallbacks; 187 | 188 | _mappings[eventName].totalCallbacks++; 189 | 190 | _mappings[eventName].callbacks[callbackId] = { 191 | cb : fn, 192 | once : typeof once === 'boolean' ? once : false 193 | }; 194 | 195 | // On the callback, create a reference to the event mapping. 196 | fn[_CALLBACK_NAMESPACE][eventName] = callbackId; 197 | fn[_CALLBACK_NAMESPACE].totalEvents++; 198 | } 199 | 200 | if (typeof once === 'boolean') { 201 | // Get the callback ID from the value of the existing event name key. 202 | callbackId = fn[_CALLBACK_NAMESPACE][eventName]; 203 | 204 | // The function already exists, so update it's 'once' value. 205 | _mappings[eventName].callbacks[callbackId].once = once; 206 | } 207 | } 208 | else { 209 | // Create a new event object in the dictionary with the specified name and callback. 210 | _mappings[eventName] = { 211 | callbacks : {} 212 | }; 213 | 214 | callbackId = 0; 215 | 216 | _mappings[eventName].callbacks[callbackId] = {cb : fn, once : !!once}; 217 | _mappings[eventName].totalCallbacks = 1; 218 | 219 | // On the callback, create a reference to the event mapping. 220 | if (typeof fn[_CALLBACK_NAMESPACE] === 'undefined') { 221 | fn[_CALLBACK_NAMESPACE] = {}; 222 | fn[_CALLBACK_NAMESPACE].totalEvents = 1; 223 | } 224 | else { 225 | fn[_CALLBACK_NAMESPACE].totalEvents++; 226 | } 227 | 228 | fn[_CALLBACK_NAMESPACE][eventName] = callbackId; 229 | } 230 | }; 231 | 232 | _self.once = function (eventName, fn) { 233 | if (arguments.length !== 2) { 234 | throw new ParamCountError('once', 'Expected 2 parameters', arguments.length); 235 | } 236 | else if (typeof eventName !== 'string') { 237 | throw new ParamTypeError('once', 'event name', eventName, 'string'); 238 | } 239 | else if (eventName.length === 0) { 240 | throw new EventNameLengthError('once'); 241 | } 242 | else if (_strictMode && typeof _self.events[eventName] === 'undefined') { 243 | throw new UndeclaredEventError('once', eventName); 244 | } 245 | 246 | if (typeof fn !== 'function') { 247 | throw new ParamTypeError('once', 'callback', fn, 'function'); 248 | } 249 | 250 | _self.on(eventName, fn, true); 251 | }; 252 | 253 | _self.off = function (eventName, fn) { 254 | if (arguments.length === 0) { 255 | // delete all references to Bullet that exist on mapped callbacks. 256 | _deleteAllCallbackReferences(); 257 | 258 | // Remove all mappings from the dictionary. 259 | _mappings = {}; 260 | 261 | return; 262 | } 263 | else if (typeof eventName !== 'string') { 264 | throw new ParamTypeError('off', 'event name', eventName, 'string'); 265 | } 266 | else if (eventName.length === 0) { 267 | throw new EventNameLengthError('off'); 268 | } 269 | else if (_strictMode && typeof _self.events[eventName] === 'undefined') { 270 | throw new UndeclaredEventError('off', eventName); 271 | } 272 | 273 | if (typeof _mappings[eventName] === 'undefined') { 274 | // There is no mapping to remove, so return silently. 275 | return; 276 | } 277 | 278 | // Remove just the function, if passed as a parameter and in the dictionary. 279 | if (typeof fn === 'function') { 280 | // if (typeof fn[_CALLBACK_NAMESPACE] === 'undefined' || typeof fn[_CALLBACK_NAMESPACE][eventName] === 'undefined') { 281 | // // TODO: Throw error here if in strict mode. 282 | // } 283 | 284 | // Retrieve a reference to the stored event from the callback. 285 | const id = fn[_CALLBACK_NAMESPACE][eventName]; 286 | const fnToRemove = _mappings[eventName].callbacks[id]; 287 | 288 | if (typeof fnToRemove !== 'undefined') { 289 | // delete the callback object from the dictionary. 290 | delete _mappings[eventName].callbacks[id]; 291 | 292 | // delete the event reference on the callback function. 293 | delete fn[_CALLBACK_NAMESPACE][eventName]; 294 | 295 | _mappings[eventName].totalCallbacks--; 296 | fn[_CALLBACK_NAMESPACE].totalEvents--; 297 | 298 | if (_mappings[eventName].totalCallbacks === 0) { 299 | // There are no more functions in the dictionary that are 300 | // registered to this event, so delete the named event object. 301 | delete _mappings[eventName]; 302 | } 303 | 304 | if (fn[_CALLBACK_NAMESPACE].totalEvents === 0) { 305 | // There are no more events registered on this callback, 306 | // so delete the Bullet namespace. 307 | delete fn[_CALLBACK_NAMESPACE]; 308 | } 309 | } 310 | } 311 | else if (typeof fn !== 'undefined') { 312 | throw new ParamTypeError('off', 'callback', fn, 'function'); 313 | } 314 | else { 315 | // No callback was passed to the 'off' method... 316 | 317 | // For each callback in _mappings[eventName], delete the reference to 318 | // the specified event name on the callback itself. 319 | _deleteAllCallbackReferencesForEvent(eventName); 320 | 321 | // Delete all functions in the dictionary that are registered to this 322 | // event by deleting the named event object. 323 | delete _mappings[eventName]; 324 | } 325 | }; 326 | 327 | // Replace a single mapped callback for the specified event name with a new callback. 328 | _self.replaceCallback = function (eventName, oldFn, newFn, once) { 329 | if (typeof eventName !== 'string') { 330 | throw new ParamTypeError('replaceCallback', 'event name', eventName, 'string'); 331 | } 332 | else if (eventName.length === 0) { 333 | throw new EventNameLengthError('replaceCallback'); 334 | } 335 | else if (typeof _mappings[eventName] === 'undefined') { 336 | throw new UnmappedEventError('replaceCallback', eventName); 337 | } 338 | else if (_strictMode && typeof _self.events[eventName] === 'undefined') { 339 | throw new UndeclaredEventError('replaceCallback', eventName); 340 | } 341 | 342 | if (typeof oldFn !== 'function') { 343 | throw new ParamTypeError('replaceCallback', 'callback', oldFn, 'function'); 344 | } 345 | 346 | if (typeof newFn !== 'function') { 347 | throw new ParamTypeError('replaceCallback', 'callback', newFn, 'function'); 348 | } 349 | 350 | if (typeof once !== 'undefined' && typeof once !== 'boolean') { 351 | throw new ParamTypeError('replaceCallback', 'once', once, 'boolean'); 352 | } 353 | 354 | _self.off(eventName, oldFn); 355 | _self.on(eventName, newFn, once); 356 | }; 357 | 358 | // Replace all of the specified event name’s mapped callbacks with the specified callback. 359 | _self.replaceAllCallbacks = function (eventName, newFn, once) { 360 | if (typeof eventName !== 'string') { 361 | throw new ParamTypeError('replace', 'event name', eventName, 'string'); 362 | } 363 | else if (eventName.length === 0) { 364 | throw new EventNameLengthError('replace'); 365 | } 366 | else if (typeof _mappings[eventName] === 'undefined') { 367 | throw new UnmappedEventError('replace', eventName); 368 | } 369 | else if (_strictMode && typeof _self.events[eventName] === 'undefined') { 370 | throw new UndeclaredEventError('replace', eventName); 371 | } 372 | 373 | if (typeof newFn !== 'function') { 374 | throw new ParamTypeError('replace', 'callback', newFn, 'function'); 375 | } 376 | 377 | if (typeof once !== 'undefined' && typeof once !== 'boolean') { 378 | throw new ParamTypeError('replace', 'once', once, 'boolean'); 379 | } 380 | 381 | _self.off(eventName); 382 | _self.on(eventName, newFn, once); 383 | }; 384 | 385 | _self.trigger = function (eventName, data) { 386 | if (typeof eventName !== 'string') { 387 | throw new ParamTypeError('trigger', 'event name', eventName, 'string'); 388 | } 389 | else if (eventName.length === 0) { 390 | throw new EventNameLengthError('trigger'); 391 | } 392 | else if (_strictMode && typeof _self.events[eventName] === 'undefined') { 393 | throw new UndeclaredEventError('trigger', eventName); 394 | } 395 | 396 | if (typeof _mappings[eventName] === 'undefined') { 397 | if (_strictMode) throw new UnmappedEventError('trigger', eventName); 398 | 399 | // Return silently if not in strict mode. 400 | return; 401 | } 402 | 403 | // Check whether or not this is a browser environment. 404 | if (_triggerAsync && typeof window !== 'undefined') { 405 | window.setTimeout(function () { 406 | _runCallback(eventName, data); 407 | }, 0); 408 | } 409 | else { 410 | _runCallback(eventName, data); 411 | } 412 | }; 413 | 414 | _self.addEventName = function (eventName) { 415 | if (typeof eventName !== 'string') { 416 | throw new ParamTypeError('addEventName', 'event name', eventName, 'string'); 417 | } 418 | else if (eventName.length === 0) { 419 | throw new EventNameLengthError('addEventName'); 420 | } 421 | 422 | _self.events[eventName] = eventName; 423 | }; 424 | 425 | _self.addMultipleEventNames = function (eventNames) { 426 | if (!(eventNames instanceof Array)) { 427 | throw new ParamTypeError('addMultipleEventNames', 'event names', eventNames, 'array'); 428 | } 429 | else if (eventNames.length === 0) { 430 | throw new EventNamesArrayLengthError('addMultipleEventNames'); 431 | } 432 | 433 | let i = 0; 434 | const length = eventNames.length; 435 | 436 | for (i; i < length; i++) { 437 | const currentEventName = eventNames[i]; 438 | 439 | _self.addEventName(currentEventName); 440 | } 441 | }; 442 | 443 | _self.removeEventName = function (eventName) { 444 | if (typeof eventName !== 'string') { 445 | throw new ParamTypeError('removeEventName', 'event name', eventName, 'string'); 446 | } 447 | else if (eventName.length === 0) { 448 | throw new EventNameLengthError('removeEventName'); 449 | } 450 | 451 | if (_self.events[eventName]) delete _self.events[eventName]; 452 | }; 453 | 454 | _self.getStrictMode = function () { 455 | // Return a boolean that doesn't directly point to the internal '_strictMode' property. 456 | return _strictMode === true; 457 | }; 458 | 459 | _self.setStrictMode = function (useStrictMode) { 460 | if (typeof useStrictMode !== 'boolean') { 461 | throw new ParamTypeError('setStrictMode', 'strict mode', useStrictMode, 'boolean'); 462 | } 463 | 464 | _strictMode = useStrictMode; 465 | }; 466 | 467 | _self.getTriggerAsync = function () { 468 | // Return a boolean that doesn't directly point to the internal '_triggerAsync' property. 469 | return _triggerAsync === true; 470 | }; 471 | 472 | _self.setTriggerAsync = function (useAsync) { 473 | if (typeof useAsync !== 'boolean') { 474 | throw new ParamTypeError('setTriggerAsync', 'trigger async', useAsync, 'boolean'); 475 | } 476 | 477 | _triggerAsync = useAsync; 478 | }; 479 | 480 | // TODO : Create a 'replaceAllEventNames' method with an array of strings passed as a param. 481 | // - include type checks for string while looping over the array. 482 | 483 | // TODO : Create a 'removeAllEventNames' method. No params necessary. 484 | // – Internally this could simply call 'replaceAllEventNames' and pass an empty array as a param. 485 | 486 | // TODO : Create an 'onAny' method with an array of strings passed as the first param and a single callback as the second. 487 | // - include type checks for string while looping over the array. 488 | 489 | // TODO : Create an 'onMultiple' method with an array of flat objects passed as a param. 490 | // - example of required param structure: 491 | // [{eventName: 'someEvent', callback: someCallback, once: false}, {eventName: 'anotherEvent', callback: anotherCallback, once: true}] 492 | } 493 | 494 | 495 | // ------------------------------------------------------------------------------------------ 496 | // -- Module definition 497 | // ------------------------------------------------------------------------------------------ 498 | // Check for AMD/Module support, otherwise define Bullet as a global variable. 499 | 500 | if (typeof define !== 'undefined' && define.amd) { 501 | // AMD. Register as an anonymous module. 502 | define(function () { 503 | return new Bullet(); 504 | }); 505 | } 506 | else if (typeof module !== 'undefined' && module.exports) { 507 | module.exports = new Bullet(); 508 | } 509 | else { 510 | window.Bullet = new Bullet(); 511 | } 512 | 513 | })(); 514 | -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.jshintrc", 3 | 4 | "browser" : false, 5 | "mocha" : true, 6 | 7 | // Custom Globals 8 | "globals" : { 9 | 10 | "expect" : true, 11 | "chai" : true, 12 | "sinon" : true, 13 | "BULLET_NAMESPACE" : true 14 | } 15 | } -------------------------------------------------------------------------------- /test/_root-suite.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | global.chai = require('chai'); 4 | global.sinon = require('sinon'); 5 | global.expect = chai.expect; 6 | 7 | global.BULLET_NAMESPACE = '__bullet_pubsub__'; 8 | 9 | var sinonChai = require('sinon-chai'); 10 | chai.use(sinonChai); 11 | 12 | var bulletSingleton = require('../src/bullet'); 13 | var BulletClass = bulletSingleton.constructor; 14 | 15 | 16 | beforeEach(function () { 17 | 18 | this.bullet = new BulletClass(); 19 | this.testEventName = 'hello there'; 20 | this.testCallback = function testCallback () {}; 21 | }); -------------------------------------------------------------------------------- /test/spec/existence-checks/custom-errors.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Custom Error Existence', function () { 4 | 5 | it('should have custom error type "ParamCountError"', function () { 6 | expect(this.bullet._errors.ParamCountError).to.be.a('function'); 7 | }); 8 | 9 | it('should have custom error type "ParamTypeError"', function () { 10 | expect(this.bullet._errors.ParamTypeError).to.be.a('function'); 11 | }); 12 | 13 | it('should have custom error type "EventNameLengthError"', function () { 14 | expect(this.bullet._errors.EventNameLengthError).to.be.a('function'); 15 | }); 16 | 17 | it('should have custom error type "EventNamesArrayLengthError"', function () { 18 | expect(this.bullet._errors.EventNamesArrayLengthError).to.be.a('function'); 19 | }); 20 | 21 | it('should have custom error type "UndeclaredEventError"', function () { 22 | expect(this.bullet._errors.UndeclaredEventError).to.be.a('function'); 23 | }); 24 | 25 | it('should have custom error type "UnmappedEventError"', function () { 26 | expect(this.bullet._errors.UnmappedEventError).to.be.a('function'); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/spec/existence-checks/methods.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Method Existence', function () { 4 | 5 | it('should have a private method named "_getMappings"', function () { 6 | expect(this.bullet._getMappings).to.be.a('function'); 7 | }); 8 | 9 | it('should have a public method named "on"', function () { 10 | expect(this.bullet.on).to.be.a('function'); 11 | }); 12 | 13 | it('should have a public method named "off"', function () { 14 | expect(this.bullet.off).to.be.a('function'); 15 | }); 16 | 17 | it('should have a public method named "replaceCallback"', function () { 18 | expect(this.bullet.replaceCallback).to.be.a('function'); 19 | }); 20 | 21 | it('should have a public method named "replaceAllCallbacks"', function () { 22 | expect(this.bullet.replaceAllCallbacks).to.be.a('function'); 23 | }); 24 | 25 | it('should have a public method named "trigger"', function () { 26 | expect(this.bullet.trigger).to.be.a('function'); 27 | }); 28 | 29 | it('should have a public method named "once"', function () { 30 | expect(this.bullet.once).to.be.a('function'); 31 | }); 32 | 33 | it('should have a public method named "addEventName"', function () { 34 | expect(this.bullet.addEventName).to.be.a('function'); 35 | }); 36 | 37 | it('should have a public method named "addMultipleEventNames"', function () { 38 | expect(this.bullet.addMultipleEventNames).to.be.a('function'); 39 | }); 40 | 41 | it('should have a public method named "removeEventName"', function () { 42 | expect(this.bullet.removeEventName).to.be.a('function'); 43 | }); 44 | 45 | it('should have a public method named "getStrictMode"', function () { 46 | expect(this.bullet.getStrictMode).to.be.a('function'); 47 | }); 48 | 49 | it('should have a public method named "setStrictMode"', function () { 50 | expect(this.bullet.setStrictMode).to.be.a('function'); 51 | }); 52 | 53 | it('should have a public method named "getTriggerAsync"', function () { 54 | expect(this.bullet.getTriggerAsync).to.be.a('function'); 55 | }); 56 | 57 | it('should have a public method named "setTriggerAsync"', function () { 58 | expect(this.bullet.setTriggerAsync).to.be.a('function'); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /test/spec/existence-checks/properties.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Property Existence', function () { 4 | 5 | it('should have a public property named "events"', function () { 6 | expect(this.bullet.events).to.be.an('object'); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /test/spec/methods/_getMappings.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('_getMappings()', function () { 4 | 5 | it('should return the public state of the internal "_mappings" object', function () { 6 | 7 | var mappings = this.bullet._getMappings(); 8 | expect(mappings).to.deep.equal({}); 9 | 10 | // Add a property to the returned mappings object. 11 | mappings.foo = 'bar'; 12 | 13 | // Get the updated events map. 14 | mappings = this.bullet._getMappings(); 15 | 16 | // Bullet's internal mappings should not have been modified. 17 | expect(mappings).to.deep.equal({}); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/spec/methods/addEventName.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('addEventName()', function () { 4 | 5 | it('should add an event to the public "events" object', function () { 6 | 7 | var events = this.bullet.events; 8 | 9 | // The map should start empty 10 | expect(events).to.deep.equal({}); 11 | 12 | // Add an event. 13 | this.bullet.addEventName('foo'); 14 | 15 | // Get the updated events map. 16 | events = this.bullet.events; 17 | 18 | // Bullet's internal _events should not have been modified. 19 | expect(events).to.deep.equal({foo : 'foo'}); 20 | 21 | // Add another event. 22 | this.bullet.addEventName('bar'); 23 | 24 | // Get the updated events map. 25 | events = this.bullet.events; 26 | 27 | // Bullet's internal _events should not have been modified. 28 | expect(events).to.deep.equal({foo : 'foo', bar : 'bar'}); 29 | }); 30 | 31 | it('should throw an ParamTypeError if the passed parameter is not a string', function () { 32 | 33 | var self = this; 34 | 35 | function callAddEventName () { 36 | self.bullet.addEventName({hello : 'hi'}); 37 | } 38 | 39 | expect(callAddEventName).to.throw(this.bullet._errors.ParamTypeError); 40 | }); 41 | 42 | it('should throw an EventNameLengthError if the passed string parameter length is 0', function () { 43 | 44 | var self = this; 45 | 46 | function callAddEventName () { 47 | self.bullet.addEventName(''); 48 | } 49 | 50 | expect(callAddEventName).to.throw(this.bullet._errors.EventNameLengthError); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /test/spec/methods/addMultipleEventNames.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('addMultipleEventNames()', function () { 4 | 5 | it('should add multiple events to the public "events" object', function () { 6 | 7 | var events = this.bullet.events; 8 | 9 | // The map should start empty 10 | expect(events).to.deep.equal({}); 11 | 12 | // Add multiple events. 13 | this.bullet.addMultipleEventNames(['foo', 'bar', 'baz']); 14 | 15 | // Get the updated events map. 16 | events = this.bullet.events; 17 | 18 | // Bullet's public "events" object should now include the new events. 19 | expect(events).to.deep.equal({foo : 'foo', bar : 'bar', baz : 'baz'}); 20 | }); 21 | 22 | it('should throw an ParamTypeError if the passed parameter is not an array', function () { 23 | 24 | var self = this; 25 | 26 | function callAddMultipleEventNames () { 27 | self.bullet.addMultipleEventNames('hi'); 28 | } 29 | 30 | expect(callAddMultipleEventNames).to.throw(this.bullet._errors.ParamTypeError); 31 | }); 32 | 33 | it('should throw an EventNamesArrayLengthError if the passed array parameter length is 0', function () { 34 | 35 | var self = this; 36 | 37 | function callAddMultipleEventNames () { 38 | self.bullet.addMultipleEventNames([]); 39 | } 40 | 41 | expect(callAddMultipleEventNames).to.throw(this.bullet._errors.EventNamesArrayLengthError); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/spec/methods/getTriggerAsync.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('getTriggerAsync()', function () { 4 | 5 | it('should return the boolean value of the private "_triggerAsync" property', function () { 6 | 7 | expect(this.bullet.getTriggerAsync()).to.be.a('boolean'); 8 | }); 9 | 10 | it('should be true by default', function () { 11 | 12 | expect(this.bullet.getTriggerAsync()).to.equal(true); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /test/spec/methods/off.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('off()', function () { 4 | 5 | before(function () { 6 | 7 | this.someOtherEventName = 'toto'; 8 | this.someOtherCallback = function someOtherCallback () {}; 9 | this.testCallbackId = 0; 10 | this.someOtherCallbackId = 1; 11 | }); 12 | 13 | it('should remove a mapping only for the specified event name and function', function () { 14 | 15 | // Create multiple event mappings so that we can test the removal of a single mapping. 16 | this.bullet.on(this.testEventName, this.testCallback); 17 | this.bullet.on(this.testEventName, this.someOtherCallback); 18 | 19 | // Get the events map. 20 | var mappings = this.bullet._getMappings(); 21 | 22 | expect(mappings[this.testEventName].callbacks[this.testCallbackId]).to.be.an('object'); 23 | expect(mappings[this.testEventName].callbacks[this.someOtherCallbackId]).to.be.an('object'); 24 | 25 | expect(this.testCallback[BULLET_NAMESPACE]).to.be.an('object'); 26 | expect(this.someOtherCallback[BULLET_NAMESPACE]).to.be.an('object'); 27 | 28 | 29 | // Remove the mapping to this.testCallback 30 | this.bullet.off(this.testEventName, this.testCallback); 31 | 32 | // Get the updated events map. 33 | mappings = this.bullet._getMappings(); 34 | 35 | expect(mappings[this.testEventName].callbacks[this.testCallbackId]).to.be.an('undefined'); 36 | expect(mappings[this.testEventName].callbacks[this.someOtherCallbackId]).to.be.an('object'); 37 | 38 | expect(this.testCallback[BULLET_NAMESPACE]).to.be.an('undefined'); 39 | expect(this.someOtherCallback[BULLET_NAMESPACE]).to.be.an('object'); 40 | 41 | // Remove the mapping to this.someOtherCallback 42 | this.bullet.off(this.testEventName, this.someOtherCallback); 43 | 44 | // Get the updated events map. 45 | mappings = this.bullet._getMappings(); 46 | 47 | // The map should be empty, now that all event mappings have been removed. 48 | expect(mappings).to.deep.equal({}); 49 | 50 | expect(this.someOtherCallback[BULLET_NAMESPACE]).to.be.an('undefined'); 51 | }); 52 | 53 | it('should remove all mappings for the specified event name', function () { 54 | 55 | // Create multiple event mappings so that we can test the removal of all mappings 56 | // for a specific event name. 57 | this.bullet.on(this.testEventName, this.testCallback); 58 | this.bullet.on(this.testEventName, this.someOtherCallback); 59 | this.bullet.on(this.someOtherEventName, this.testCallback); 60 | this.bullet.on(this.someOtherEventName, this.someOtherCallback); 61 | 62 | // Get the events map. 63 | var mappings = this.bullet._getMappings(); 64 | 65 | expect(mappings[this.testEventName].callbacks[this.testCallbackId]).to.be.an('object'); 66 | expect(mappings[this.testEventName].callbacks[this.someOtherCallbackId]).to.be.an('object'); 67 | expect(mappings[this.someOtherEventName].callbacks[this.testCallbackId]).to.be.an('object'); 68 | expect(mappings[this.someOtherEventName].callbacks[this.someOtherCallbackId]).to.be.an('object'); 69 | 70 | expect(this.testCallback[BULLET_NAMESPACE][this.testEventName]).to.equal(this.testCallbackId); 71 | expect(this.testCallback[BULLET_NAMESPACE][this.someOtherEventName]).to.equal(this.testCallbackId); 72 | 73 | expect(this.someOtherCallback[BULLET_NAMESPACE][this.testEventName]).to.equal(this.someOtherCallbackId); 74 | expect(this.someOtherCallback[BULLET_NAMESPACE][this.someOtherEventName]).to.equal(this.someOtherCallbackId); 75 | 76 | // Remove all mappings for this.testEventName 77 | this.bullet.off(this.testEventName); 78 | 79 | // Get the updated events map. 80 | mappings = this.bullet._getMappings(); 81 | 82 | expect(mappings[this.testEventName]).to.be.an('undefined'); 83 | expect(mappings[this.someOtherEventName].callbacks[this.testCallbackId]).to.be.an('object'); 84 | expect(mappings[this.someOtherEventName].callbacks[this.someOtherCallbackId]).to.be.an('object'); 85 | 86 | // All references to the specified event name should have been removed from testCallback and someOtherCallback. 87 | expect(this.testCallback[BULLET_NAMESPACE][this.testEventName]).to.be.an('undefined'); 88 | expect(this.someOtherCallback[BULLET_NAMESPACE][this.testEventName]).to.be.an('undefined'); 89 | 90 | // The other event – someOtherEventName – on these callbacks shouldn't be affected. 91 | expect(this.testCallback[BULLET_NAMESPACE][this.someOtherEventName]).to.equal(this.testCallbackId); 92 | expect(this.someOtherCallback[BULLET_NAMESPACE][this.someOtherEventName]).to.equal(this.someOtherCallbackId); 93 | 94 | // Remove all mappings for this.someOtherEventName 95 | this.bullet.off(this.someOtherEventName); 96 | 97 | // Get the updated events map. 98 | mappings = this.bullet._getMappings(); 99 | 100 | expect(mappings[this.someOtherEventName]).to.be.an('undefined'); 101 | 102 | // All references to bullet should have been removed from someOtherCallback. 103 | expect(this.someOtherCallback[BULLET_NAMESPACE]).to.be.an('undefined'); 104 | }); 105 | 106 | it('should remove all mappings when no params are passed', function () { 107 | 108 | // Create multiple event mappings so that we can test the removal of all mappings 109 | this.bullet.on(this.testEventName, this.testCallback); 110 | this.bullet.on(this.testEventName, this.someOtherCallback); 111 | this.bullet.on(this.someOtherEventName, this.testCallback); 112 | this.bullet.on(this.someOtherEventName, this.someOtherCallback); 113 | 114 | // Get the events map. 115 | var mappings = this.bullet._getMappings(); 116 | 117 | expect(mappings[this.testEventName].callbacks[this.testCallbackId]).to.be.an('object'); 118 | expect(mappings[this.testEventName].callbacks[this.someOtherCallbackId]).to.be.an('object'); 119 | expect(mappings[this.someOtherEventName].callbacks[this.testCallbackId]).to.be.an('object'); 120 | expect(mappings[this.someOtherEventName].callbacks[this.someOtherCallbackId]).to.be.an('object'); 121 | 122 | expect(this.testCallback[BULLET_NAMESPACE][this.testEventName]).to.equal(this.testCallbackId); 123 | expect(this.testCallback[BULLET_NAMESPACE][this.someOtherEventName]).to.equal(this.testCallbackId); 124 | 125 | expect(this.someOtherCallback[BULLET_NAMESPACE][this.testEventName]).to.equal(this.someOtherCallbackId); 126 | expect(this.someOtherCallback[BULLET_NAMESPACE][this.someOtherEventName]).to.equal(this.someOtherCallbackId); 127 | 128 | // Remove all mappings. 129 | this.bullet.off(); 130 | 131 | // Get the updated events map. 132 | mappings = this.bullet._getMappings(); 133 | 134 | expect(mappings).to.deep.equal({}); 135 | 136 | // All references should have been removed from testCallback and someOtherCallback. 137 | expect(this.testCallback[BULLET_NAMESPACE]).to.be.an('undefined'); 138 | expect(this.someOtherCallback[BULLET_NAMESPACE]).to.be.an('undefined'); 139 | }); 140 | 141 | it('should throw an ParamTypeError if the event name param is not a string', function () { 142 | 143 | var self = this; 144 | 145 | function callOff () { 146 | 147 | // Attempt to unmap an event with a non-string event name parameter. 148 | self.bullet.off({}, self.testCallback); 149 | } 150 | 151 | expect(callOff).to.throw(this.bullet._errors.ParamTypeError); 152 | }); 153 | 154 | it('should throw an EventNameLengthError if the event name param is an empty string', function () { 155 | 156 | var self = this; 157 | 158 | function callOff () { 159 | 160 | // Attempt to unmap an event with an empty string as the event name parameter. 161 | self.bullet.off('', self.testCallback); 162 | } 163 | 164 | expect(callOff).to.throw(this.bullet._errors.EventNameLengthError); 165 | }); 166 | 167 | it('should throw a ParamTypeError if the callback parameter is not a function', function () { 168 | 169 | var self = this; 170 | 171 | // Map an event via the 'on' method so that we can attempt to remove it below. 172 | this.bullet.on(this.testEventName, this.testCallback); 173 | 174 | function callOff () { 175 | 176 | // Attempt to unmap an event with a non-function as the callback parameter. 177 | self.bullet.off(self.testEventName, {}); 178 | } 179 | 180 | expect(callOff).to.throw(this.bullet._errors.ParamTypeError); 181 | }); 182 | }); 183 | -------------------------------------------------------------------------------- /test/spec/methods/on.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('on()', function () { 4 | 5 | it('should create a mapping for the specified event name', function () { 6 | 7 | var mappings = this.bullet._getMappings(); 8 | var testCallbackId = 0; 9 | 10 | expect(mappings[this.testEventName]).to.be.an('undefined'); 11 | expect(this.testCallback[BULLET_NAMESPACE]).to.be.an('undefined'); 12 | 13 | // Add an event. 14 | this.bullet.on(this.testEventName, this.testCallback); 15 | 16 | // Get the updated events map. 17 | mappings = this.bullet._getMappings(); 18 | 19 | // check that Bullet's internal map has a reference to the callback. 20 | expect(mappings[this.testEventName]).to.be.an('object'); 21 | expect(mappings[this.testEventName].callbacks).to.be.an('object'); 22 | expect(mappings[this.testEventName].callbacks[testCallbackId]).to.be.an('object'); 23 | 24 | // check that a reference to the mapped event has been added to the callback. 25 | expect(this.testCallback[BULLET_NAMESPACE]).to.be.an('object'); 26 | expect(this.testCallback[BULLET_NAMESPACE].totalEvents).to.equal(1); 27 | expect(this.testCallback[BULLET_NAMESPACE][this.testEventName]).to.be.a('number'); 28 | }); 29 | 30 | it('should create mappings for different event names using the same callback', function () { 31 | 32 | // TODO : define 'someOtherEventName' and 'someOtherCallback' in a 'before' block. 33 | // (also update the others functions within the 'on()' block to use the 'before' version, i.e. this.someOtherEventName instead of just someOtherEventName) 34 | var someOtherEventName = 'hey'; 35 | var someOtherCallback = function someOtherCallback () {}; 36 | var mappings = this.bullet._getMappings(); 37 | 38 | expect(mappings[this.testEventName]).to.be.an('undefined'); 39 | expect(mappings[someOtherEventName]).to.be.an('undefined'); 40 | expect(this.testCallback[BULLET_NAMESPACE]).to.be.an('undefined'); 41 | expect(someOtherCallback[BULLET_NAMESPACE]).to.be.an('undefined'); 42 | 43 | // Add some events. 44 | this.bullet.on(this.testEventName, this.testCallback); 45 | this.bullet.on(someOtherEventName, this.testCallback); 46 | 47 | this.bullet.on(this.testEventName, someOtherCallback); 48 | this.bullet.on(someOtherEventName, someOtherCallback); 49 | 50 | // Get the updated events map. 51 | mappings = this.bullet._getMappings(); 52 | 53 | expect(mappings[this.testEventName]).to.be.an('object'); 54 | expect(mappings[this.testEventName].callbacks).to.be.an('object'); 55 | 56 | expect(mappings[someOtherEventName]).to.be.an('object'); 57 | expect(mappings[someOtherEventName].callbacks).to.be.an('object'); 58 | 59 | var testCallbackId = 0; 60 | var someOtherCallbackId = 1; 61 | 62 | expect(mappings[this.testEventName].callbacks[testCallbackId]).to.be.an('object'); 63 | expect(mappings[this.testEventName].callbacks[someOtherCallbackId]).to.be.an('object'); 64 | 65 | expect(mappings[someOtherEventName].callbacks[testCallbackId]).to.be.an('object'); 66 | expect(mappings[someOtherEventName].callbacks[someOtherCallbackId]).to.be.an('object'); 67 | 68 | // check that references to the mapped events have been added to the callback. 69 | expect(this.testCallback[BULLET_NAMESPACE]).to.be.an('object'); 70 | expect(this.testCallback[BULLET_NAMESPACE].totalEvents).to.equal(2); 71 | expect(this.testCallback[BULLET_NAMESPACE][this.testEventName]).to.equal(testCallbackId); 72 | expect(this.testCallback[BULLET_NAMESPACE][someOtherEventName]).to.equal(testCallbackId); 73 | 74 | expect(someOtherCallback[BULLET_NAMESPACE]).to.be.an('object'); 75 | expect(someOtherCallback[BULLET_NAMESPACE].totalEvents).to.equal(2); 76 | expect(someOtherCallback[BULLET_NAMESPACE][this.testEventName]).to.equal(someOtherCallbackId); 77 | expect(someOtherCallback[BULLET_NAMESPACE][someOtherEventName]).to.equal(someOtherCallbackId); 78 | }); 79 | 80 | it('should map multiple callbacks to a single event name', function () { 81 | 82 | var someOtherCallback = function someOtherCallback () {}; 83 | var mappings = this.bullet._getMappings(); 84 | 85 | expect(mappings[this.testEventName]).to.be.an('undefined'); 86 | expect(this.testCallback[BULLET_NAMESPACE]).to.be.an('undefined'); 87 | 88 | // Add an event. 89 | this.bullet.on(this.testEventName, this.testCallback); 90 | this.bullet.on(this.testEventName, someOtherCallback); 91 | 92 | // Get the updated events map. 93 | mappings = this.bullet._getMappings(); 94 | 95 | expect(mappings[this.testEventName]).to.be.an('object'); 96 | expect(mappings[this.testEventName].callbacks).to.be.an('object'); 97 | expect(mappings[this.testEventName].callbacks[0]).to.be.an('object'); 98 | expect(mappings[this.testEventName].callbacks[1]).to.be.an('object'); 99 | 100 | var testCallbackId = 0; 101 | var someOtherCallbackId = 1; 102 | 103 | expect(mappings[this.testEventName].callbacks[testCallbackId]).to.be.an('object'); 104 | 105 | 106 | // check that a reference to the mapped event has been added to the first callback. 107 | expect(this.testCallback[BULLET_NAMESPACE]).to.be.an('object'); 108 | expect(this.testCallback[BULLET_NAMESPACE].totalEvents).to.equal(1); 109 | expect(this.testCallback[BULLET_NAMESPACE][this.testEventName]).to.equal(testCallbackId); 110 | 111 | // check that a reference to the mapped event has been added to the second callback. 112 | expect(someOtherCallback[BULLET_NAMESPACE]).to.be.an('object'); 113 | expect(someOtherCallback[BULLET_NAMESPACE].totalEvents).to.equal(1); 114 | expect(someOtherCallback[BULLET_NAMESPACE][this.testEventName]).to.equal(someOtherCallbackId); 115 | }); 116 | 117 | it('should throw a ParamTypeError if the event name param is not a string', function () { 118 | 119 | var self = this; 120 | 121 | // The map should start empty 122 | expect(this.bullet._getMappings()).to.deep.equal({}); 123 | 124 | function callOn () { 125 | 126 | // Attempt to map an event with a non-string event name parameter. 127 | self.bullet.on({}, self.testCallback); 128 | } 129 | 130 | expect(callOn).to.throw(this.bullet._errors.ParamTypeError); 131 | 132 | // The map should still be empty 133 | expect(this.bullet._getMappings()).to.deep.equal({}); 134 | }); 135 | 136 | it('should throw an EventNameLengthError if the event name param is an empty string', function () { 137 | 138 | var self = this; 139 | 140 | // The map should start empty 141 | expect(this.bullet._getMappings()).to.deep.equal({}); 142 | 143 | function callOn () { 144 | 145 | // Attempt to map an event with an empty string as the event name parameter. 146 | self.bullet.on('', self.testCallback); 147 | } 148 | 149 | expect(callOn).to.throw(this.bullet._errors.EventNameLengthError); 150 | 151 | // The map should still be empty 152 | expect(this.bullet._getMappings()).to.deep.equal({}); 153 | }); 154 | 155 | it('should throw a ParamTypeError if the callback parameter is not a function', function () { 156 | 157 | var self = this; 158 | 159 | // The map should start empty 160 | expect(this.bullet._getMappings()).to.deep.equal({}); 161 | 162 | function callOn () { 163 | 164 | // Attempt to map an event with a non-function as the callback parameter. 165 | self.bullet.on(self.testEventName, {}); 166 | } 167 | 168 | expect(callOn).to.throw(this.bullet._errors.ParamTypeError); 169 | 170 | // The map should still be empty 171 | expect(this.bullet._getMappings()).to.deep.equal({}); 172 | }); 173 | 174 | it('should throw a ParamCountError if only one parameter is passed', function () { 175 | 176 | var self = this; 177 | 178 | // The map should start empty 179 | expect(this.bullet._getMappings()).to.deep.equal({}); 180 | 181 | function callOn () { 182 | 183 | // Attempt to map an event with only one parameter. 184 | self.bullet.on(self.testEventName); 185 | } 186 | 187 | expect(callOn).to.throw(this.bullet._errors.ParamCountError); 188 | 189 | // The map should still be empty 190 | expect(this.bullet._getMappings()).to.deep.equal({}); 191 | }); 192 | 193 | it('should throw a ParamCountError if no parameters are passed', function () { 194 | 195 | var self = this; 196 | 197 | // The map should start empty 198 | expect(this.bullet._getMappings()).to.deep.equal({}); 199 | 200 | function callOn () { 201 | 202 | // Attempt to map an event with no params. 203 | self.bullet.on(); 204 | } 205 | 206 | expect(callOn).to.throw(this.bullet._errors.ParamCountError); 207 | 208 | // The map should still be empty 209 | expect(this.bullet._getMappings()).to.deep.equal({}); 210 | }); 211 | 212 | it('should throw a ParamCountError if more than three parameters are passed', function () { 213 | 214 | var self = this; 215 | 216 | // The map should start empty 217 | expect(this.bullet._getMappings()).to.deep.equal({}); 218 | 219 | function callOn () { 220 | 221 | // Attempt to map an event with more than three params. 222 | self.bullet.on(self.testEventName, self.testCallback, true, 123); 223 | } 224 | 225 | expect(callOn).to.throw(this.bullet._errors.ParamCountError); 226 | 227 | // The map should still be empty 228 | expect(this.bullet._getMappings()).to.deep.equal({}); 229 | }); 230 | }); 231 | -------------------------------------------------------------------------------- /test/spec/methods/once.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('once()', function () { 4 | 5 | it('should create a one-time mapping for the specified event name', function () { 6 | 7 | var mappings = this.bullet._getMappings(); 8 | 9 | expect(mappings[this.testEventName]).to.be.an('undefined'); 10 | 11 | // Add a one-time event. 12 | this.bullet.once(this.testEventName, this.testCallback); 13 | 14 | // Get the updated events map. 15 | mappings = this.bullet._getMappings(); 16 | 17 | expect(mappings[this.testEventName]).to.be.an('object'); 18 | expect(mappings[this.testEventName].callbacks).to.be.an('object'); 19 | 20 | // Trigger the event. 21 | this.bullet.trigger(this.testEventName); 22 | 23 | // Get the updated events map. 24 | mappings = this.bullet._getMappings(); 25 | 26 | // The event mapping should have been deleted, as it was triggered once. 27 | expect(mappings[this.testEventName]).to.be.an('undefined'); 28 | }); 29 | 30 | it('should throw a ParamCountError if no parameters are passed', function () { 31 | 32 | var self = this; 33 | 34 | // The map should start empty 35 | expect(this.bullet._getMappings()).to.deep.equal({}); 36 | 37 | function callOnce () { 38 | 39 | // Attempt to map an event with no params. 40 | self.bullet.once(); 41 | } 42 | 43 | expect(callOnce).to.throw(this.bullet._errors.ParamCountError); 44 | 45 | // The map should still be empty 46 | expect(this.bullet._getMappings()).to.deep.equal({}); 47 | }); 48 | 49 | it('should throw an ParamCountError if only one parameter is passed', function () { 50 | 51 | var self = this; 52 | 53 | // The map should start empty 54 | expect(this.bullet._getMappings()).to.deep.equal({}); 55 | 56 | function callOnce () { 57 | 58 | // Attempt to map an event with only one parameter. 59 | self.bullet.once(self.testEventName); 60 | } 61 | 62 | expect(callOnce).to.throw(this.bullet._errors.ParamCountError); 63 | 64 | // The map should still be empty 65 | expect(this.bullet._getMappings()).to.deep.equal({}); 66 | }); 67 | 68 | it('should throw an ParamCountError if more than three parameters are passed', function () { 69 | 70 | var self = this; 71 | 72 | // The map should start empty 73 | expect(this.bullet._getMappings()).to.deep.equal({}); 74 | 75 | function callOnce () { 76 | 77 | // Attempt to map an event with more than three params. 78 | self.bullet.once(self.testEventName, self.testCallback, true, 123); 79 | } 80 | 81 | expect(callOnce).to.throw(this.bullet._errors.ParamCountError); 82 | 83 | // The map should still be empty 84 | expect(this.bullet._getMappings()).to.deep.equal({}); 85 | }); 86 | 87 | it('should throw an ParamTypeError if the event name param is not a string', function () { 88 | 89 | var self = this; 90 | 91 | // The map should start empty 92 | expect(this.bullet._getMappings()).to.deep.equal({}); 93 | 94 | function callOnce () { 95 | 96 | // Attempt to map an event with a non-string event name parameter. 97 | self.bullet.once({}, self.testCallback); 98 | } 99 | 100 | expect(callOnce).to.throw(this.bullet._errors.ParamTypeError); 101 | 102 | // The map should still be empty 103 | expect(this.bullet._getMappings()).to.deep.equal({}); 104 | }); 105 | 106 | it('should throw an EventNameLengthError if the event name param is an empty string', function () { 107 | 108 | var self = this; 109 | 110 | // The map should start empty 111 | expect(this.bullet._getMappings()).to.deep.equal({}); 112 | 113 | function callOnce () { 114 | 115 | // Attempt to map an event with an empty string as the event name parameter. 116 | self.bullet.once('', self.testCallback); 117 | } 118 | 119 | expect(callOnce).to.throw(this.bullet._errors.EventNameLengthError); 120 | 121 | // The map should still be empty 122 | expect(this.bullet._getMappings()).to.deep.equal({}); 123 | }); 124 | 125 | it('should throw a ParamTypeError if the callback parameter is not a function', function () { 126 | 127 | var self = this; 128 | 129 | // The map should start empty 130 | expect(this.bullet._getMappings()).to.deep.equal({}); 131 | 132 | function callOnce () { 133 | 134 | // Attempt to map an event with a non-function as the callback parameter. 135 | self.bullet.once(self.testEventName, {}); 136 | } 137 | 138 | expect(callOnce).to.throw(this.bullet._errors.ParamTypeError); 139 | 140 | // The map should still be empty 141 | expect(this.bullet._getMappings()).to.deep.equal({}); 142 | }); 143 | }); 144 | -------------------------------------------------------------------------------- /test/spec/methods/removeEventName.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('removeEventName()', function () { 4 | 5 | it('should remove an event from the internal "_strictEvents" object', function () { 6 | 7 | var events = this.bullet.events; 8 | 9 | // Add multiple events so that we can test the removal of a single event. 10 | this.bullet.addEventName('foo'); 11 | this.bullet.addEventName('bar'); 12 | this.bullet.addEventName('baz'); 13 | 14 | // Get the updated events map. 15 | events = this.bullet.events; 16 | 17 | expect(events).to.deep.equal({foo : 'foo', bar : 'bar', baz : 'baz'}); 18 | 19 | // Remove one of the events. 20 | this.bullet.removeEventName('bar'); 21 | 22 | // Get the updated events map. 23 | events = this.bullet.events; 24 | 25 | expect(events).to.deep.equal({foo : 'foo', baz : 'baz'}); 26 | 27 | // Remove the remaining events. 28 | this.bullet.removeEventName('foo'); 29 | this.bullet.removeEventName('baz'); 30 | 31 | // Get the updated events map. 32 | events = this.bullet.events; 33 | 34 | // The map should start empty 35 | expect(events).to.deep.equal({}); 36 | }); 37 | 38 | it('should throw an ParamTypeError if the passed parameter is not a string', function () { 39 | 40 | var self = this; 41 | 42 | function callremoveEventName () { 43 | self.bullet.removeEventName({hello : 'hi'}); 44 | } 45 | 46 | expect(callremoveEventName).to.throw(this.bullet._errors.ParamTypeError); 47 | }); 48 | 49 | it('should throw an EventNameLengthError if the passed string parameter length is 0', function () { 50 | 51 | var self = this; 52 | 53 | function callremoveEventName () { 54 | self.bullet.removeEventName(''); 55 | } 56 | 57 | expect(callremoveEventName).to.throw(this.bullet._errors.EventNameLengthError); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /test/spec/methods/replaceAllCallbacks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('replaceAllCallbacks()', function () { 4 | 5 | before(function () { 6 | 7 | this.someOtherCallback = function someOtherCallback () {}; 8 | }); 9 | 10 | it('should throw a ParamTypeError if the event name param is not a string', function () { 11 | 12 | var self = this; 13 | 14 | // Create an event mapping. 15 | this.bullet.on(this.testEventName, this.testCallback); 16 | 17 | function callReplaceAllCallbacks () { 18 | 19 | // Attempt to replace all functions mapped to an event name by using a non-string event name parameter. 20 | self.bullet.replaceAllCallbacks({}, self.someOtherCallback); 21 | } 22 | 23 | expect(callReplaceAllCallbacks).to.throw(this.bullet._errors.ParamTypeError); 24 | }); 25 | 26 | it('should throw an EventNameLengthError if the event name param is an empty string', function () { 27 | 28 | var self = this; 29 | 30 | // Create an event mapping. 31 | this.bullet.on(this.testEventName, this.testCallback); 32 | 33 | function callReplaceAllCallbacks () { 34 | 35 | // Attempt to replace all functions mapped to an event name by using an empty string for the event name parameter. 36 | self.bullet.replaceAllCallbacks('', self.someOtherCallback); 37 | } 38 | 39 | expect(callReplaceAllCallbacks).to.throw(this.bullet._errors.EventNameLengthError); 40 | }); 41 | 42 | it('should throw a ParamTypeError if the new callback param is not a function', function () { 43 | 44 | var self = this; 45 | 46 | // Create an event mapping. 47 | this.bullet.on(this.testEventName, this.testCallback); 48 | 49 | function callReplaceAllCallbacks () { 50 | 51 | // Attempt to replace all functions mapped to an event name by using a non-function for the new callback parameter. 52 | self.bullet.replaceAllCallbacks(self.testEventName, {}); 53 | } 54 | 55 | expect(callReplaceAllCallbacks).to.throw(this.bullet._errors.ParamTypeError); 56 | }); 57 | 58 | it('should throw a ParamTypeError if the ‘once’ param is defined but not a boolean', function () { 59 | 60 | var self = this; 61 | 62 | // Create an event mapping. 63 | this.bullet.on(this.testEventName, this.testCallback); 64 | 65 | function callReplaceAllCallbacks () { 66 | 67 | // Attempt to replace all functions mapped to an event name by using a non-boolean for the 'once' parameter. 68 | self.bullet.replaceAllCallbacks(self.testEventName, self.someOtherCallback, {}); 69 | } 70 | 71 | expect(callReplaceAllCallbacks).to.throw(this.bullet._errors.ParamTypeError); 72 | }); 73 | 74 | it('should throw an UnmappedEventError if the specified event name does not exist within the mappings object', function () { 75 | 76 | var self = this; 77 | 78 | function callReplaceAllCallbacks () { 79 | 80 | // Attempt to replace a function that is not mapped to any event name. 81 | self.bullet.replaceAllCallbacks('someRandomEventName', self.someOtherCallback); 82 | } 83 | 84 | expect(callReplaceAllCallbacks).to.throw(this.bullet._errors.UnmappedEventError); 85 | }); 86 | 87 | it('should replace all functions mapped to the specified event with a single function', function () { 88 | 89 | var thirdCallback = function thirdCallback () {}; 90 | var newCallback = function newCallback () {}; 91 | 92 | // Create multiple event mappings. 93 | this.bullet.on(this.testEventName, this.testCallback); 94 | this.bullet.on(this.testEventName, this.someOtherCallback); 95 | this.bullet.on(this.testEventName, thirdCallback); 96 | 97 | // Get the events map. 98 | var mappings = this.bullet._getMappings(); 99 | 100 | expect(mappings[this.testEventName].totalCallbacks).to.equal(3); 101 | expect(mappings[this.testEventName].callbacks[0].cb).to.equal(this.testCallback); 102 | expect(mappings[this.testEventName].callbacks[1].cb).to.equal(this.someOtherCallback); 103 | expect(mappings[this.testEventName].callbacks[2].cb).to.equal(thirdCallback); 104 | 105 | // Replace all mapped callbacks with the 'newCallback'. 106 | this.bullet.replaceAllCallbacks(this.testEventName, newCallback); 107 | 108 | // Get the updated events map. 109 | mappings = this.bullet._getMappings(); 110 | 111 | expect(mappings[this.testEventName].totalCallbacks).to.equal(1); 112 | expect(mappings[this.testEventName].callbacks[0].cb).to.equal(newCallback); 113 | expect(mappings[this.testEventName].callbacks[1]).to.be.an('undefined'); 114 | expect(mappings[this.testEventName].callbacks[2]).to.be.an('undefined'); 115 | }); 116 | 117 | it('should respect the ‘once’ parameter when replacing all mapped functions', function () { 118 | 119 | // Create an event mapping. 120 | this.bullet.on(this.testEventName, this.testCallback); 121 | 122 | // Update the function mapped to the testEventName and set the 'once' param for the new function. 123 | this.bullet.replaceAllCallbacks(this.testEventName, this.someOtherCallback, true); 124 | 125 | // Get the updated events map. 126 | var mappings = this.bullet._getMappings(); 127 | 128 | expect(mappings[this.testEventName].callbacks[0].cb).to.equal(this.someOtherCallback); 129 | expect(mappings[this.testEventName].callbacks[0].once).to.equal(true); 130 | }); 131 | }); 132 | -------------------------------------------------------------------------------- /test/spec/methods/replaceCallback.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('replaceCallback()', function () { 4 | 5 | before(function () { 6 | 7 | this.someOtherCallback = function someOtherCallback () {}; 8 | }); 9 | 10 | it('should throw a ParamTypeError if the event name param is not a string', function () { 11 | 12 | var self = this; 13 | 14 | // Create an event mapping. 15 | this.bullet.on(this.testEventName, this.testCallback); 16 | 17 | function callReplaceCallback () { 18 | 19 | // Attempt to update a function mapped to an event name by using a non-string event name parameter. 20 | self.bullet.replaceCallback({}, self.testCallback, self.someOtherCallback); 21 | } 22 | 23 | expect(callReplaceCallback).to.throw(this.bullet._errors.ParamTypeError); 24 | }); 25 | 26 | it('should throw an EventNameLengthError if the event name param is an empty string', function () { 27 | 28 | var self = this; 29 | 30 | // Create an event mapping. 31 | this.bullet.on(this.testEventName, this.testCallback); 32 | 33 | function callReplaceCallback () { 34 | 35 | // Attempt to update a function mapped to an event name by using an empty string for the event name parameter. 36 | self.bullet.replaceCallback('', self.testCallback, self.someOtherCallback); 37 | } 38 | 39 | expect(callReplaceCallback).to.throw(this.bullet._errors.EventNameLengthError); 40 | }); 41 | 42 | it('should throw a ParamTypeError if the old callback param is not a function', function () { 43 | 44 | var self = this; 45 | 46 | // Create an event mapping. 47 | this.bullet.on(this.testEventName, this.testCallback); 48 | 49 | function callReplaceCallback () { 50 | 51 | // Attempt to update a function mapped to an event name by using a non-function for the old callback parameter. 52 | self.bullet.replaceCallback(self.testEventName, {}, self.someOtherCallback); 53 | } 54 | 55 | expect(callReplaceCallback).to.throw(this.bullet._errors.ParamTypeError); 56 | }); 57 | 58 | it('should throw a ParamTypeError if the new callback param is not a function', function () { 59 | 60 | var self = this; 61 | 62 | // Create an event mapping. 63 | this.bullet.on(this.testEventName, this.testCallback); 64 | 65 | function callReplaceCallback () { 66 | 67 | // Attempt to update a function mapped to an event name by using a non-function for the new callback parameter. 68 | self.bullet.replaceCallback(self.testEventName, self.testCallback, {}); 69 | } 70 | 71 | expect(callReplaceCallback).to.throw(this.bullet._errors.ParamTypeError); 72 | }); 73 | 74 | it('should throw a ParamTypeError if the ‘once’ param is defined but not a boolean', function () { 75 | 76 | var self = this; 77 | 78 | // Create an event mapping. 79 | this.bullet.on(this.testEventName, this.testCallback); 80 | 81 | function callReplaceCallback () { 82 | 83 | // Attempt to update a function mapped to an event name by using a non-boolean for the 'once' parameter. 84 | self.bullet.replaceCallback(self.testEventName, self.testCallback, self.someOtherCallback, {}); 85 | } 86 | 87 | expect(callReplaceCallback).to.throw(this.bullet._errors.ParamTypeError); 88 | }); 89 | 90 | it('should throw an UnmappedEventError if the specified event name does not exist within the mappings object', function () { 91 | 92 | var self = this; 93 | 94 | function callReplaceCallback () { 95 | 96 | // Attempt to update a function that is not mapped to any event name. 97 | self.bullet.replaceCallback('someRandomEventName', self.testCallback, self.someOtherCallback); 98 | } 99 | 100 | expect(callReplaceCallback).to.throw(this.bullet._errors.UnmappedEventError); 101 | }); 102 | 103 | it('should update a single mapped function for the specified event name', function () { 104 | 105 | // Create an event mapping. 106 | this.bullet.on(this.testEventName, this.testCallback); 107 | 108 | // Get the events map. 109 | var mappings = this.bullet._getMappings(); 110 | 111 | expect(mappings[this.testEventName].totalCallbacks).to.equal(1); 112 | expect(mappings[this.testEventName].callbacks[0].cb).to.equal(this.testCallback); 113 | 114 | // Replace the 'this.testCallback' mapping with a mapping for 'this.someOtherCallback' 115 | this.bullet.replaceCallback(this.testEventName, this.testCallback, this.someOtherCallback); 116 | 117 | // Get the updated events map. 118 | mappings = this.bullet._getMappings(); 119 | 120 | expect(mappings[this.testEventName].totalCallbacks).to.equal(1); 121 | expect(mappings[this.testEventName].callbacks[0].cb).to.equal(this.someOtherCallback); 122 | }); 123 | 124 | it('should respect the ‘once’ parameter when replacing mapped functions', function () { 125 | 126 | var self = this; 127 | 128 | // Create an event mapping. 129 | this.bullet.on(this.testEventName, this.testCallback); 130 | 131 | // update the function mapped to the testEventName and set the 'once' param for the new function. 132 | self.bullet.replaceCallback(self.testEventName, self.testCallback, self.someOtherCallback, true); 133 | 134 | // Get the updated events map. 135 | var mappings = this.bullet._getMappings(); 136 | 137 | expect(mappings[this.testEventName].callbacks[0].once).to.equal(true); 138 | }); 139 | }); 140 | -------------------------------------------------------------------------------- /test/spec/methods/setTriggerAsync.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('setTriggerAsync()', function () { 4 | 5 | it('should set the private "_triggerAsync" property to a boolean value', function () { 6 | 7 | // It should be true by default. 8 | expect(this.bullet.getTriggerAsync()).to.equal(true); 9 | 10 | // Turn off async triggers. 11 | this.bullet.setTriggerAsync(false); 12 | 13 | expect(this.bullet.getTriggerAsync()).to.equal(false); 14 | 15 | // Turn on async triggers. 16 | this.bullet.setTriggerAsync(true); 17 | 18 | expect(this.bullet.getTriggerAsync()).to.equal(true); 19 | }); 20 | 21 | it('should throw a ParamTypeError if a non-boolean value is passed as the parameter', function () { 22 | 23 | var self = this; 24 | 25 | // It should be true by default. 26 | expect(this.bullet.getTriggerAsync()).to.equal(true); 27 | 28 | function callSetTriggerAsync () { 29 | 30 | // Call setTriggerAsync and pass in a non-boolean value. 31 | self.bullet.setTriggerAsync({}); 32 | } 33 | 34 | expect(callSetTriggerAsync).to.throw(this.bullet._errors.ParamTypeError); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/spec/methods/trigger.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('trigger()', function () { 4 | 5 | afterEach(function () { 6 | 7 | sinon.restore(this.testCallback); 8 | }); 9 | 10 | it('should trigger a mapped callback', function () { 11 | 12 | sinon.spy(this, 'testCallback'); 13 | 14 | // Create an event mapping so that we can test that it gets triggered below. 15 | this.bullet.on(this.testEventName, this.testCallback); 16 | 17 | // Trigger the event. 18 | this.bullet.trigger(this.testEventName); 19 | 20 | expect(this.testCallback.calledOnce).to.equal(true); 21 | }); 22 | 23 | it('should trigger a mapped callback with data', function () { 24 | 25 | var testData = {hello : 'sunshine'}; 26 | 27 | sinon.spy(this, 'testCallback'); 28 | 29 | // Create an event mapping so that we can test that it gets triggered below. 30 | this.bullet.on(this.testEventName, this.testCallback); 31 | 32 | // Trigger the event with data. 33 | this.bullet.trigger(this.testEventName, testData); 34 | 35 | expect(this.testCallback).to.have.been.calledWith(testData); 36 | }); 37 | 38 | it('should throw an ParamTypeError if the event name param is not a string', function () { 39 | 40 | var self = this; 41 | 42 | function callTrigger () { 43 | 44 | // Attempt to trigger an event with a non-string event name parameter. 45 | self.bullet.trigger({}); 46 | } 47 | 48 | expect(callTrigger).to.throw(this.bullet._errors.ParamTypeError); 49 | }); 50 | 51 | it('should throw an EventNameLengthError if the event name param is an empty string', function () { 52 | 53 | var self = this; 54 | 55 | function callTrigger () { 56 | 57 | // Attempt to trigger an event with an empty string as the event name parameter. 58 | self.bullet.trigger(''); 59 | } 60 | 61 | expect(callTrigger).to.throw(this.bullet._errors.EventNameLengthError); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /test/spec/strict-methods/getStrictMode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Strict Mode:: getStrictMode()', function () { 4 | 5 | it('should return the boolean value of the private "_strictMode" property', function () { 6 | 7 | expect(this.bullet.getStrictMode()).to.be.a('boolean'); 8 | }); 9 | 10 | it('should be false by default', function () { 11 | 12 | expect(this.bullet.getStrictMode()).to.equal(false); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /test/spec/strict-methods/off.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Strict Mode:: off()', function () { 4 | 5 | it('should throw an UndeclaredEventError if the event name param is not in the "events" object', function () { 6 | 7 | var self = this; 8 | 9 | // Turn on strict mode. 10 | this.bullet.setStrictMode(true); 11 | 12 | function callOff () { 13 | 14 | // Attempt to unmap an event that hasn't been added to the 'events' object. 15 | self.bullet.off(self.testEventName, self.testCallback); 16 | } 17 | 18 | expect(callOff).to.throw(this.bullet._errors.UndeclaredEventError); 19 | }); 20 | 21 | it('should not throw an UndeclaredEventError if the event name param is in the "events" object', function () { 22 | 23 | var self = this; 24 | 25 | // Turn on strict mode. 26 | this.bullet.setStrictMode(true); 27 | 28 | // Add the test event to the 'events' object via the 'addEventName' method. 29 | this.bullet.addEventName(this.testEventName); 30 | 31 | // Map the test event to a callback via the 'on' method. 32 | this.bullet.on(this.testEventName, this.testCallback); 33 | 34 | function callOff () { 35 | 36 | // Unmap the event that was added to the 'events' object. 37 | self.bullet.off(self.testEventName, self.testCallback); 38 | } 39 | 40 | expect(callOff).to.not.throw(this.bullet._errors.UndeclaredEventError); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/spec/strict-methods/on.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Strict Mode:: on()', function () { 4 | 5 | it('should throw an UndeclaredEventError if the event name param is not in the "events" object', function () { 6 | 7 | var self = this; 8 | 9 | // Turn on strict mode. 10 | this.bullet.setStrictMode(true); 11 | 12 | // The map should start empty 13 | expect(this.bullet._getMappings()).to.deep.equal({}); 14 | 15 | function callOn () { 16 | 17 | // Attempt to map an event that hasn't been added to the 'events' object. 18 | self.bullet.on(self.testEventName, self.testCallback); 19 | } 20 | 21 | expect(callOn).to.throw(this.bullet._errors.UndeclaredEventError); 22 | 23 | // The map should still be empty 24 | expect(this.bullet._getMappings()).to.deep.equal({}); 25 | }); 26 | 27 | it('should not throw an UndeclaredEventError if the event name param is in the "events" object', function () { 28 | 29 | var self = this; 30 | 31 | // Turn on strict mode. 32 | this.bullet.setStrictMode(true); 33 | 34 | // Add the test event to the 'events' object via the 'addEventName' method. 35 | this.bullet.addEventName(this.testEventName); 36 | 37 | // Get the mappings. 38 | var mappings = this.bullet._getMappings(); 39 | 40 | // The map should start empty. 41 | expect(mappings).to.deep.equal({}); 42 | 43 | function callOn () { 44 | 45 | // Map an event that was added to the 'events' object. 46 | self.bullet.on(self.testEventName, self.testCallback); 47 | } 48 | 49 | expect(callOn).to.not.throw(this.bullet._errors.UndeclaredEventError); 50 | 51 | // Get the updated events map. 52 | mappings = this.bullet._getMappings(); 53 | 54 | expect(mappings[this.testEventName]).to.be.an('object'); 55 | expect(mappings[this.testEventName].callbacks[0].cb).to.equal(this.testCallback); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/spec/strict-methods/once.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Strict Mode:: once()', function () { 4 | 5 | it('should throw an UndeclaredEventError if the event name param is not in the "events" object', function () { 6 | 7 | var self = this; 8 | 9 | // Turn on strict mode. 10 | this.bullet.setStrictMode(true); 11 | 12 | // The map should start empty 13 | expect(this.bullet._getMappings()).to.deep.equal({}); 14 | 15 | function callOnce () { 16 | 17 | // Attempt to map an event that hasn't been added to the 'events' object. 18 | self.bullet.once(self.testEventName, self.testCallback); 19 | } 20 | 21 | expect(callOnce).to.throw(this.bullet._errors.UndeclaredEventError); 22 | 23 | // The map should still be empty 24 | expect(this.bullet._getMappings()).to.deep.equal({}); 25 | }); 26 | 27 | it('should not throw an UndeclaredEventError if the event name param is in the "events" object', function () { 28 | 29 | var self = this; 30 | 31 | // Turn on strict mode. 32 | this.bullet.setStrictMode(true); 33 | 34 | // Add the test event to the 'events' object via the 'addEventName' method. 35 | this.bullet.addEventName(this.testEventName); 36 | 37 | // Get the mappings. 38 | var mappings = this.bullet._getMappings(); 39 | 40 | // The map should start empty. 41 | expect(mappings).to.deep.equal({}); 42 | 43 | function callOnce () { 44 | 45 | // Map an event that was added to the 'events' object. 46 | self.bullet.once(self.testEventName, self.testCallback); 47 | } 48 | 49 | expect(callOnce).to.not.throw(this.bullet._errors.UndeclaredEventError); 50 | 51 | // Get the updated events map. 52 | mappings = this.bullet._getMappings(); 53 | 54 | expect(mappings[this.testEventName]).to.be.an('object'); 55 | expect(mappings[this.testEventName].callbacks[0].cb).to.equal(this.testCallback); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/spec/strict-methods/replaceAllCallbacks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Strict Mode:: replaceAllCallbacks()', function () { 4 | 5 | it('should throw an UndeclaredEventError if the event name param is not in the "events" object', function () { 6 | 7 | var self = this; 8 | 9 | // Create an event mapping. 10 | this.bullet.on(this.testEventName, this.testCallback); 11 | 12 | // Turn on strict mode after the event was already mapped (to avoid errors from the 'on' method). 13 | this.bullet.setStrictMode(true); 14 | 15 | function callReplaceAllCallbacks () { 16 | 17 | // Attempt to replace all functions for an event that hasn't been added to the 'events' object. 18 | self.bullet.replaceAllCallbacks(self.testEventName, self.someOtherCallback); 19 | } 20 | 21 | expect(callReplaceAllCallbacks).to.throw(this.bullet._errors.UndeclaredEventError); 22 | }); 23 | 24 | it('should not throw an UndeclaredEventError if the event name param is in the "events" object', function () { 25 | 26 | var self = this; 27 | 28 | // Turn on strict mode. 29 | this.bullet.setStrictMode(true); 30 | 31 | // Add the test event to the 'events' object via the 'addEventName' method. 32 | this.bullet.addEventName(this.testEventName); 33 | 34 | // Create an event mapping. 35 | this.bullet.on(this.testEventName, this.testCallback); 36 | 37 | function callReplaceAllCallbacks () { 38 | 39 | // Replace all functions for an event that was added to the 'events' object. 40 | self.bullet.replaceAllCallbacks(self.testEventName, self.someOtherCallback); 41 | } 42 | 43 | expect(callReplaceAllCallbacks).to.not.throw(this.bullet._errors.UndeclaredEventError); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /test/spec/strict-methods/replaceCallback.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Strict Mode:: replaceCallback()', function () { 4 | 5 | it('should throw an UndeclaredEventError if the event name param is not in the "events" object', function () { 6 | 7 | var self = this; 8 | 9 | // Create an event mapping. 10 | this.bullet.on(this.testEventName, this.testCallback); 11 | 12 | // Turn on strict mode after the event was already mapped (to avoid errors from the 'on' method). 13 | this.bullet.setStrictMode(true); 14 | 15 | function callReplace () { 16 | 17 | // Attempt to replace a function for an event that hasn't been added to the 'events' object. 18 | self.bullet.replaceCallback(self.testEventName, self.testCallback, self.someOtherCallback); 19 | } 20 | 21 | expect(callReplace).to.throw(this.bullet._errors.UndeclaredEventError); 22 | }); 23 | 24 | it('should not throw an UndeclaredEventError if the event name param is in the "events" object', function () { 25 | 26 | var self = this; 27 | 28 | // Turn on strict mode. 29 | this.bullet.setStrictMode(true); 30 | 31 | // Add the test event to the 'events' object via the 'addEventName' method. 32 | this.bullet.addEventName(this.testEventName); 33 | 34 | // Create an event mapping. 35 | this.bullet.on(this.testEventName, this.testCallback); 36 | 37 | function callReplace () { 38 | 39 | // Replace a function for an event that was added to the 'events' object. 40 | self.bullet.replaceCallback(self.testEventName, self.testCallback, self.someOtherCallback); 41 | } 42 | 43 | expect(callReplace).to.not.throw(this.bullet._errors.UndeclaredEventError); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /test/spec/strict-methods/setStrictMode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Strict Mode:: setStrictMode()', function () { 4 | 5 | it('should set the private "_strictMode" property to a boolean value', function () { 6 | 7 | // It should be false by default. 8 | expect(this.bullet.getStrictMode()).to.equal(false); 9 | 10 | // Turn on strict mode. 11 | this.bullet.setStrictMode(true); 12 | 13 | expect(this.bullet.getStrictMode()).to.equal(true); 14 | 15 | // Turn off strict mode. 16 | this.bullet.setStrictMode(false); 17 | 18 | expect(this.bullet.getStrictMode()).to.equal(false); 19 | }); 20 | 21 | it('should throw a ParamTypeError if a non-boolean value is passed as the parameter', function () { 22 | 23 | var self = this; 24 | 25 | // It should be false by default. 26 | expect(this.bullet.getStrictMode()).to.equal(false); 27 | 28 | function callSetStrictMode () { 29 | 30 | // Call setStrictMode and pass in a non-boolean value. 31 | self.bullet.setStrictMode({}); 32 | } 33 | 34 | expect(callSetStrictMode).to.throw(this.bullet._errors.ParamTypeError); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/spec/strict-methods/trigger.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Strict Mode:: trigger()', function () { 4 | 5 | it('should throw an UndeclaredEventError if the event name param is not in the "events" object', function () { 6 | 7 | var self = this; 8 | 9 | // Turn on strict mode. 10 | this.bullet.setStrictMode(true); 11 | 12 | function callTrigger () { 13 | 14 | // Attempt to trigger an event that hasn't been added to the 'events' object. 15 | self.bullet.trigger(self.testEventName); 16 | } 17 | 18 | expect(callTrigger).to.throw(this.bullet._errors.UndeclaredEventError); 19 | }); 20 | 21 | it('should throw an UnmappedEventError if the event name param is not mapped to any callbacks', function () { 22 | 23 | var self = this; 24 | 25 | // Turn on strict mode. 26 | this.bullet.setStrictMode(true); 27 | 28 | // Add the test event to the 'events' object via the 'addEventName' method. 29 | this.bullet.addEventName(this.testEventName); 30 | 31 | function callTrigger () { 32 | 33 | // Attempt to trigger an event that hasn't been mapped any callbacks via the 'on' method. 34 | self.bullet.trigger(self.testEventName); 35 | } 36 | 37 | expect(callTrigger).to.throw(this.bullet._errors.UnmappedEventError); 38 | }); 39 | 40 | it('should not throw an UndeclaredEventError if the event name param is in the "events" object', function () { 41 | 42 | var self = this; 43 | 44 | // Turn on strict mode. 45 | this.bullet.setStrictMode(true); 46 | 47 | // Add the test event to the 'events' object via the 'addEventName' method. 48 | this.bullet.addEventName(this.testEventName); 49 | 50 | function callTrigger () { 51 | 52 | // Trigger the event that was added to the 'events' object, but wasn't mapped to any callback. 53 | self.bullet.trigger(self.testEventName); 54 | } 55 | 56 | expect(callTrigger).not.to.throw(this.bullet._errors.UndeclaredEventError); 57 | }); 58 | 59 | it('should not throw an UnmappedEventError if the event name param is mapped to a callback', function () { 60 | 61 | var self = this; 62 | 63 | // Turn on strict mode. 64 | this.bullet.setStrictMode(true); 65 | 66 | // Add the test event to the 'events' object via the 'addEventName' method. 67 | this.bullet.addEventName(this.testEventName); 68 | 69 | // Map the event that was added to the 'events' object. 70 | self.bullet.on(self.testEventName, self.testCallback); 71 | 72 | function callTrigger () { 73 | 74 | // Trigger the event that was added to the 'events' object and mapped to a callback. 75 | self.bullet.trigger(self.testEventName); 76 | } 77 | 78 | expect(callTrigger).to.not.throw(this.bullet._errors.UnmappedEventError); 79 | }); 80 | }); 81 | --------------------------------------------------------------------------------