Skip to content

Instantly share code, notes, and snippets.

@brunosimon
Created November 1, 2021 11:22
Show Gist options
  • Star 61 You must be signed in to star a gist
  • Fork 14 You must be signed in to fork a gist
  • Save brunosimon/120acda915e6629e3a4d497935b16bdf to your computer and use it in GitHub Desktop.
Save brunosimon/120acda915e6629e3a4d497935b16bdf to your computer and use it in GitHub Desktop.
export default class EventEmitter
{
constructor()
{
this.callbacks = {}
this.callbacks.base = {}
}
on(_names, callback)
{
// Errors
if(typeof _names === 'undefined' || _names === '')
{
console.warn('wrong names')
return false
}
if(typeof callback === 'undefined')
{
console.warn('wrong callback')
return false
}
// Resolve names
const names = this.resolveNames(_names)
// Each name
names.forEach((_name) =>
{
// Resolve name
const name = this.resolveName(_name)
// Create namespace if not exist
if(!(this.callbacks[ name.namespace ] instanceof Object))
this.callbacks[ name.namespace ] = {}
// Create callback if not exist
if(!(this.callbacks[ name.namespace ][ name.value ] instanceof Array))
this.callbacks[ name.namespace ][ name.value ] = []
// Add callback
this.callbacks[ name.namespace ][ name.value ].push(callback)
})
return this
}
off(_names)
{
// Errors
if(typeof _names === 'undefined' || _names === '')
{
console.warn('wrong name')
return false
}
// Resolve names
const names = this.resolveNames(_names)
// Each name
names.forEach((_name) =>
{
// Resolve name
const name = this.resolveName(_name)
// Remove namespace
if(name.namespace !== 'base' && name.value === '')
{
delete this.callbacks[ name.namespace ]
}
// Remove specific callback in namespace
else
{
// Default
if(name.namespace === 'base')
{
// Try to remove from each namespace
for(const namespace in this.callbacks)
{
if(this.callbacks[ namespace ] instanceof Object && this.callbacks[ namespace ][ name.value ] instanceof Array)
{
delete this.callbacks[ namespace ][ name.value ]
// Remove namespace if empty
if(Object.keys(this.callbacks[ namespace ]).length === 0)
delete this.callbacks[ namespace ]
}
}
}
// Specified namespace
else if(this.callbacks[ name.namespace ] instanceof Object && this.callbacks[ name.namespace ][ name.value ] instanceof Array)
{
delete this.callbacks[ name.namespace ][ name.value ]
// Remove namespace if empty
if(Object.keys(this.callbacks[ name.namespace ]).length === 0)
delete this.callbacks[ name.namespace ]
}
}
})
return this
}
trigger(_name, _args)
{
// Errors
if(typeof _name === 'undefined' || _name === '')
{
console.warn('wrong name')
return false
}
let finalResult = null
let result = null
// Default args
const args = !(_args instanceof Array) ? [] : _args
// Resolve names (should on have one event)
let name = this.resolveNames(_name)
// Resolve name
name = this.resolveName(name[ 0 ])
// Default namespace
if(name.namespace === 'base')
{
// Try to find callback in each namespace
for(const namespace in this.callbacks)
{
if(this.callbacks[ namespace ] instanceof Object && this.callbacks[ namespace ][ name.value ] instanceof Array)
{
this.callbacks[ namespace ][ name.value ].forEach(function(callback)
{
result = callback.apply(this, args)
if(typeof finalResult === 'undefined')
{
finalResult = result
}
})
}
}
}
// Specified namespace
else if(this.callbacks[ name.namespace ] instanceof Object)
{
if(name.value === '')
{
console.warn('wrong name')
return this
}
this.callbacks[ name.namespace ][ name.value ].forEach(function(callback)
{
result = callback.apply(this, args)
if(typeof finalResult === 'undefined')
finalResult = result
})
}
return finalResult
}
resolveNames(_names)
{
let names = _names
names = names.replace(/[^a-zA-Z0-9 ,/.]/g, '')
names = names.replace(/[,/]+/g, ' ')
names = names.split(' ')
return names
}
resolveName(name)
{
const newName = {}
const parts = name.split('.')
newName.original = name
newName.value = parts[ 0 ]
newName.namespace = 'base' // Base namespace
// Specified namespace
if(parts.length > 1 && parts[ 1 ] !== '')
{
newName.namespace = parts[ 1 ]
}
return newName
}
}
@chuckntaylor
Copy link

For anyone following Bruno's course and using Typescript, here is my adaptation.

interface Name {
  original: string
  value: string
  namespace: string
}

export class EventEmitter {
  callbacks: any = {}

  constructor() {
    this.callbacks = {}
    this.callbacks.base = {}
  }

  on(_names: string, callback: Function) {
    // Errors
    if (typeof _names === 'undefined' || _names === '') {
      console.warn('wrong names')
      return false
    }

    if (typeof callback === 'undefined') {
      console.warn('wrong callback')
      return false
    }

    // Resolve names
    const names = this.resolveNames(_names)

    // Each name
    names.forEach((_name) => {
      // Resolve name
      const name = this.resolveName(_name)

      // Create namespace if not exist
      if (!(this.callbacks[name.namespace] instanceof Object)) this.callbacks[name.namespace] = {}

      // Create callback if not exist
      if (!(this.callbacks[name.namespace][name.value] instanceof Array))
        this.callbacks[name.namespace][name.value] = []

      // Add callback
      this.callbacks[name.namespace][name.value].push(callback)
    })

    return this
  }

  off(_names: string) {
    // Errors
    if (typeof _names === 'undefined' || _names === '') {
      console.warn('wrong name')
      return false
    }

    // Resolve names
    const names = this.resolveNames(_names)

    // Each name
    names.forEach((_name) => {
      // Resolve name
      const name = this.resolveName(_name)

      // Remove namespace
      if (name.namespace !== 'base' && name.value === '') {
        delete this.callbacks[name.namespace]
      }

      // Remove specific callback in namespace
      else {
        // Default
        if (name.namespace === 'base') {
          // Try to remove from each namespace
          for (const namespace in this.callbacks) {
            if (
              this.callbacks[namespace] instanceof Object &&
              this.callbacks[namespace][name.value] instanceof Array
            ) {
              delete this.callbacks[namespace][name.value]

              // Remove namespace if empty
              if (Object.keys(this.callbacks[namespace]).length === 0) delete this.callbacks[namespace]
            }
          }
        }

        // Specified namespace
        else if (
          this.callbacks[name.namespace] instanceof Object &&
          this.callbacks[name.namespace][name.value] instanceof Array
        ) {
          delete this.callbacks[name.namespace][name.value]

          // Remove namespace if empty
          if (Object.keys(this.callbacks[name.namespace]).length === 0) delete this.callbacks[name.namespace]
        }
      }
    })

    return this
  }

  trigger(_name: string, _args?: any[]) {
    // Errors
    if (typeof _name === 'undefined' || _name === '') {
      console.warn('wrong name')
      return false
    }

    let finalResult: any = null
    let result = null

    // Default args
    const args = !(_args instanceof Array) ? [] : _args

    // Resolve names (should on have one event)
    let nameArray = this.resolveNames(_name)

    // Resolve name
    let name = this.resolveName(nameArray[0])

    // Default namespace
    if (name.namespace === 'base') {
      // Try to find callback in each namespace
      for (const namespace in this.callbacks) {
        if (
          this.callbacks[namespace] instanceof Object &&
          this.callbacks[namespace][name.value] instanceof Array
        ) {
          this.callbacks[namespace][name.value].forEach((callback: Function) => {
            result = callback.apply(this, args)

            if (typeof finalResult === 'undefined') {
              finalResult = result
            }
          })
        }
      }
    }

    // Specified namespace
    else if (this.callbacks[name.namespace] instanceof Object) {
      if (name.value === '') {
        console.warn('wrong name')
        return this
      }

      this.callbacks[name.namespace][name.value].forEach((callback: Function) => {
        result = callback.apply(this, args)

        if (typeof finalResult === 'undefined') finalResult = result
      })
    }

    return finalResult
  }

  resolveNames(_names: string) {
    let names: string | string[] = _names
    names = names.replace(/[^a-zA-Z0-9 ,/.]/g, '')
    names = names.replace(/[,/]+/g, ' ')
    names = names.split(' ')

    return names
  }

  resolveName(name: string) {
    const newName: Partial<Name> = {}
    const parts = name.split('.')

    newName.original = name
    newName.value = parts[0]
    newName.namespace = 'base' // Base namespace

    // Specified namespace
    if (parts.length > 1 && parts[1] !== '') {
      newName.namespace = parts[1]
    }

    return newName as Name
  }
}

@Beygs
Copy link

Beygs commented Sep 6, 2022

Thanks @chuckntaylor you're a legend

@rcarubbi
Copy link

rcarubbi commented Apr 9, 2023

why not use the native EventTarget?

like this:

export default class Sizes extends EventTarget {
	constructor() {
		super();
		this.setSizes();

		window.addEventListener('resize', () => {
			this.setSizes();
			this.dispatchEvent(new Event('resize'));
		});
	}

	setSizes() {
		this.width = window.innerWidth;
		this.height = window.innerHeight;
		this.pixelRation = Math.min(window.devicePixelRatio, 2);
	}
}
import Sizes from './Utils/Sizes.js';

export default class Experience {
	constructor(canvas) {
		// global access
		globalThis.experience = this;

		this.canvas = canvas;

		// Setup
		this.sizes = new Sizes();

		this.sizes.addEventListener('resize', () => {});
	}
}

@Neosoulink
Copy link

@rcarubbi, I think the big issue with EventTarget is the fact that you cannot pass arguments to the dispatchEvent method

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment