node package manager
Share your code. npm Orgs help your team discover, share, and reuse code. Create a free org »

@develephant/props-aware

PropsAware

Properties as dispatchers. A "living" global properties data store.

props-aware

Install

npm i @develephant/props-aware --save

Usage

const PropsAware = require('@develephant/props-aware')

Overview

PropsAware (or PA) is a global property object that emits events on property changes.

You can listen for these changes virtually anywhere in your program.

Events are only emitted when the underlying property data changes.

In code it looks like this:

//file-one.js 
 
/* Pull in PropsAware as PA */
const PA = require('@develephant/props-aware')
 
let props = PA.props()
 
PA.on('score', (val) => {
  console.log(val)
})
 
props.score = 300
 
/* outputs 300 */
 

Lets create another JS file, and add the following:

//file-two.js 
 
/* Pull in PropsAware as PA */
const PA = require('@develephant/props-aware')
 
props = PA.props()
 
props.score = 500 //will trigger an update 
 
/* file-one.js will output 500 */
 
props.score = 500 //will not trigger an update, same data 
 

Discussion

Some things to consider before, or when using PropsAware:

  • Only properties are managed; strings, numbers, booleans, arrays, and "data" objects.
  • Properties are stored as a flat tree. Only root keys trigger update events. [1]
  • When you set a PA property, its immediately available to all listeners.
  • Callbacks are set on the PropsAware (PA) object directly, not the property itself. [2]
  • To change a property without emitting an update event, use PA.set(p, v).
  • Limit yourself to a handful of base PA properties, and pass primitives for flow control.
  • There are no guarantees on delivery order or timing. it will get there though.
  • When creating object instances with PA properties, each instance will have its own listener. [3]
  • Dont set PA properties in an onAll handler. [4]

Footnotes

1) Only root keys trigger an update:

//example PA props object 
...
let props = {
  score: 200
  user: {
    name: 'User',
    color: 'green'
  }
}

In the object above, when the score property is updated with a new value, an event will be emitted.

The user key holds an object. When the key is set with an updated object, the user key will trigger:

//only root keys emit 
PA.on('user', (val) => {
  console.log(val.color) //blue 
})
 
props.user = { color: 'blue' }
 

Changing user.color directly will not trigger an event. Additionally, in the code above, the name key will be stripped away. This may be fine if thats whats intended.

An easy way to handle this, is pulling the object first:

let obj = props.user //by ref 
obj.color = 'yellow'
props.user = obj //will trigger 
 
//yellow 

Or, if you want to be super fancy...

let obj = Object.assign({}, props.user) //cloned 
obj.color = 'yellow'
props.user = obj //will trigger 

The above holds true for Arrays too.

2) Callbacks are set on the PA object, not the property:

...
//works! 
PA.on('score', (val) => {
  console.log(val)
})
 
//does NOT work 
score.on((val) => {
  console.log(val)
}
 

3) Created instances of a Class will all emit:

class MyClass {
  constructor() {
    this.props = PA.props()
    PA.on('score', (val) => {
      console.log(val)
    })
  }
}
 
const instance1 = new MyClass()
const instance2 = new MyClass()
const instance3 = new MyClass()
 
instance1.props.score = 100
 
/* instance1 outputs 100 */
/* instance2 outputs 100 */
/* instance3 outputs 100 */

4) Dont set properties in an onAll handler:

let props = PA.props()
PA.onAll((val, prop) => { //infinite loop 
  props[prop] = val //dont do it! 
  props.dontsetme = 'oops' //think of the puppies!! 
})

If you really, really need/want to set a PA property in an onAll handler you can either:

Set it silently, without triggering an update

let props = PA.props()
PA.onAll((val, prop) => {
  PA.set(prop, val) //does not emit 
})

Or, you can use this workaround/hack:

let props = PA.props()
PA.onAll((val, prop) => {
  setTimeout(() => { props[prop] = val }, 1)
  //will run until your computer starts smoking 
})

But, at the end of the day, thats an infinite loop. Its not advisable.

Keep it simple

Try to keep the amount of PropsAware properties to a minimum. Pass strings, numbers, and booleans to handle messaging and state.

Consider the following:

const PA = require('@develephant/props-aware')
let props = PA.props()
 
//this is okay... 
props.walking = true
props.running = false
 
//but this is better! 
props.pace = 'walking'
//OR 
props.pace = 'running'
 

In most basic programs, you shouldnt need more than 4-5 PA properties.

API

props() -> PropsAware_properties

Retreives the PropsAware property object. All properties on this object emit when set.

const PA = require('@develephant/props-aware')
 
let props = PA.props()

on(prop, cb)

Callback receives:

Name Purpose
val The property value
prop The name of the property

Listen for a property update.

const PA = require('@develephant/props-aware')
 
PA.on('score', (val, prop) => {
  console.log(val)
})

has(prop) -> success_bool

Checks to see if a specific root property is housed in the PropsAware property table.

const PA = require('@develephant/props-aware')
 
let has_username = PA.has('username')

del(prop) -> success_bool

Removes a properties update listeners. This destroys all update listeners for the property, except the global onAll.

let success = PA.del('score')

onAll(cb)

Callback receives:

Name Purpose
val The property value
prop The name of the property

Any part of the program can listen for all PA property changes. When using this method, you need to filter the messages with control statements.

PA.onAll((val, prop) => {
  console.log('changed', prop, val)
})

Property based flow control:

PA.onAll((val, prop) => {
  if (prop === 'goback') {
    console.log('go back')
  } else if (prop === 'goforward') {
    console.log('go forward')
  }
})
 
//...somewhere else 
 
/* PA Props reference */
let props = PA.props()
props.goback = true
//or 
props.goforward = true
 

State based flow control (using on):

PA.on('state', (val) => {
  if (val === 'goback') {
    console.log('go back')
  } else if (val === 'goforward') {
    console.log('go forward')
  }
})
 
//...somewhere else 
 
/* PA Props reference */
let props = PA.props()
props.state = 'goback'
//...or 
props.state = 'goforward'

State based flow control is generally the better choice.

With the pattern above, its entirely possible to manipulate the bulk of your program with one PA property!

onDel(cb)

Callback receives:

Name Purpose
prop The name of the property

Fired when the del method is used in the PropsAware object

PA.onDel((prop) => {
  console.log('del', prop)
})

sync() --> success_bool

Dispatch all properties, to all listeners. Should not be used often, especially with large property objects.

Note: Any onAll listeners will be called with all emitted properties.

const PA = require('@develephant/props-aware')
 
//Set a property "silently" 
PA.set('prop', 'val')
 
//Nevermind, resend everything 
PA.sync()

Examples

In Classes

//ClassA.js 
 
const PA = require('@develephant/props-aware')
 
class ClassA {
  constructor() {
    this.props = PA.props()
 
    PA.on('score', (val) => {
      console.log(val)
    })
  }
}
module.exports = ClassA
//ClassB.js 
 
const PA = require('@develephant/props-aware')
 
class ClassB {
  constructor() {
    this.props = PA.props()
    this.props.score = 100
  }
}
module.exports = ClassB

Run

//main.js 
 
const ClassA = require('./ClassA')
const ClassB = require('./ClassB')
 
const A = new ClassA()
const B = new ClassB() /* Class A will output "100" */

In Objects

//obj-one.js 
 
/* Pull in PropsAware as PA */
const PA = require('@develephant/parse-aware')
 
const ObjA = {
  props: PA.props(),
  listen() {
    PA.on('greeting', (val) => {
      console.log(greeting)
    })
  }
}
module.exports = ObjA
//obj-two.js 
 
/* Pull in PropsAware as PA */
const PA = require('@develephant/props-aware')
 
const ObjB = {
  props: PA.props()
}
module.exports = ObjB

Run

//main.js 
 
const objA = require('./obj-one')
const objB = require('./obj-two')
 
objA.listen()
 
objA.props.greeting = 'Hola' /* ObjA will output "Hola" */
 
//Props object is "global" so this works via objB: 
 
objB.props.greeting = 'Hello' /* ObjA will output "Hello" */
 

A Function

const PA = require('@develephant/props-aware')
 
modules.export = function() {
  this.props = PA.props()
  this.props.greeting = 'Howdy'
  PA.on('score', (val) => {
    console.log(val)
  })
}
 
// Assuming the Objects above... 
 
/* ObjA will output 'Howdy' */
 
// Call score through Class B above 
classB.props.score = 1000
 
/* This Function will output 1000 *\
/* ObjA will output 1000 */
 

Next Steps

  • Immutability

^_^


PropsAware ⋆ © 2017 develephant ⋆ Apache-2.0 license