#cp.prop
This is a utility library for helping keep track of single-value property states. Each property provides access to a single value. Must be readable, but may be read-only. It works by creating a table which has a get
and (optionally) a set
function which are called when changing the state.
#Features:
#1. Callable
A prop
can be called like a function once created. Eg:
local value = true local propValue = prop.new(function() return value end, function(newValue) value = newValue end) propValue() == true -- `value` is still true propValue(false) == false -- now `value` is false
#2. Togglable
A prop
comes with toggling built in - as long as the it has a set
function. Continuing from the last example:
propValue:toggle() -- `value` went from `false` to `true`.
Note: Toggling a non-boolean value will flip it to nil
and a subsequent toggle will make it true
. See the toggle method for more details.
#3. Watchable
Interested parties can 'watch' the prop
value to be notified of changes. Again, continuing on:
propValue:watch(function(newValue) print "New Value: "...newValue) end) -- prints "New Value: true" immediately propValue(false) -- prints "New Value: false"
This will also work on AND and [OR][#or] properties. Any changes from component properties will trigger a notification.
#4. Observable
Similarly, you can 'observe' a prop as a cp.rx.Observer
by calling the observe
method:
propValue:toObservable():subscribe(function(value) print(tostring(value) end))
These will never emit an onError
or onComplete
message, just onNext
with either nil
or the current value as it changes.
#5. Combinable
We can combine or modify properties with AND/OR and NOT operations. The resulting values will be a live combination of the underlying prop
values. They can also be watched, and will be notified when the underlying prop
values change. For example:
local watered = prop.TRUE() -- a simple `prop` which stores the current value internally, defaults to `true` local fed = prop.FALSE() -- same as above, defautls to `false` local rested = prop.FALSE() -- as above. local satisfied = watered:AND(fed) -- will be true if both `watered` and `fed` are true. local happy = satisfied:AND(rested) -- will be true if both `satisfied` and `happy`. local sleepy = fed:AND(prop.NOT(rested)) -- will be sleepy if `fed`, but not `rested`. -- These statements all evaluate to `true` satisfied() == false happy() == false sleepy() == false -- Get fed fed(true) == true satisfied() == true happy() == false sleepy() == true -- Get rest rested:toggle() == true satisfied() == true happy() == true sleepy() == false -- These will produce an error, because you can't modify an AND or OR: happy(true) happy:toggle()
You can also use non-boolean properties. Any non-nil
value is considered to be true
.
#6. Immutable
If appropriate, a prop
may be immutable. Any prop
with no set
function defined is immutable. Examples are the prop.AND
and prop.OR
instances, since modifying combinations of values doesn't really make sense.
Additionally, an immutable wrapper can be made from any prop
value via either prop.IMMUTABLE(...)
or calling the myValue:IMMUTABLE()
method.
Note that the underlying prop
value(s) are still potentially modifiable, and any watchers on the immutable wrapper will be notified of changes. You just can't make any changes directly to the immutable property instance.
For example:
local isImmutable = propValue:IMMUTABLE() isImmutable:toggle() -- results in an `error` being thrown isImmutable:watch(function(newValue) print "isImmutable changed to "..newValue end) propValue:toggle() -- prints "isImmutable changed to false"
#7. Bindable
A property can be bound to an 'owning' table. This table will be passed into the get
and set
functions for the property if present. This is mostly useful if your property depends on internal instance values of a table. For example, you might want to make a property work as a method instead of a function:
local owner = { _value = true } owner.value = prop(function() return owner._value end) owner:isMethod() -- error!
To use a prop
as a method, you need to attach
it to the owning table, like so:
local owner = { _value = true } owner.isMethod = prop(function(self) return self._value end, function(value, self) self._value = value end):bind(owner) owner:isMethod() -- success! owner.isMethod() -- also works - will still pass in the bound owner. owner.isMethod:owner() == owner -- is true~
You can also use the prop.bind function to bind multple properties at once:
local owner = { _value = true } prop.bind(o) { isMethod = prop(function(self) return self._value end) } owner:isMethod() -- success!
The prop.extend function will also bind any cp.prop
values it finds:
local owner = prop.extend({ _value = true, isMethod = prop(function(self) return self._value end), }) owner:isMethod() -- success!
The bound owner
is passed in as the last parameter of the get
and set
functions.
#8. Extendable
A common use case is using metatables to provide shared fields and methods across multiple instances. A typical example might be:
local person = {} function person:name(newValue) if newValue then self._name = newValue end return self._name end function person.new(name) local o = { _name = name } return setmetatable(o, { __index = person }) end local johnDoe = person.new("John Doe") johnDoe:name() == "John Doe"
If we want to make the name
a property, we might try creating a bound property like this:
person.name = prop(function(self) return self._name end, function(value, self) self._name = value end):bind(person)
Unfortunately, this doesn't work as expected:
johnDoe:name() -- Throws an error because `person` is the owner, not `johnDoe`. johnDoe.name() == nil -- Works, but will return `nil` because "John Doe" is applied to the new table, not `person`
The fix is to use prop.extend
when creating the new person. Rewrite person.new
like so:
person.new(name) local o = { _name = name } return prop.extend(o, person) end
Now, this will work as expected:
johnDoe:name() == "John Doe" johnDoe.name() == "John Doe"
The prop.extend
function will set the source
table as a metatable of the target
, as well as binding any bound props that are in the source
to target
.
#Tables
Because tables are copied by reference rather than by value, changes made inside a table will not necessarily trigger an update when setting a value with an updated table value. By default, tables are simply passed in and out without modification. You can nominate for a property to make copies of tables (not userdata) when getting or setting, which effectively isolates the value being stored from outside modification. This can be done with the deepTable and shallowTable methods. Below is an example of them in action:
local value = { a = 1, b = { c = 1 } } local valueProp = prop.THIS(value) local deepProp = prop.THIS(value):deepTable() local shallowProp = prop.THIS(value):shallowTable() -- print a message when the prop value is updated valueProp:watch(function(v) print("value: a = " .. v.a ..", b.c = ".. v.b.c ) end) deepProp:watch(function(v) print("deep: a = " .. v.a ..", b.c = ".. v.b.c ) end) shallowProp:watch(function(v) print("shallow: a = " .. v.a ..", b.c = ".. v.b.c ) end) -- change the original table: value.a = 2 value.b.c = 2 valueProp().a == 2 -- modified valueProp().b.c == 2 -- modified shallowProp().a == 1 -- top level is copied shallowProp().b.c == 2 -- child tables are referenced deepProp().a == 1 -- top level is copied deepProp().b.c == 1 -- child tables are copied as well -- get the 'value' property value = valueProp() -- returns the original value table value.a = 3 -- updates the original value table `a` value value.b.c = 3 -- updates the original `b` table's `c` value valueProp(value) -- nothing is printed, since it's still the same table valueProp().a == 3 -- still referencing the original table valueProp().b.c == 3 -- the child is still referenced too shallowProp().a == 1 -- still unmodified after the initial copy shallowProp().b.c == 3 -- still updated, since `b` was copied by reference deepProp().a == 1 -- still unmodified after initial copy deepProp().b.c == 1 -- still unmodified after initial copy -- get the 'deep copy' property value = deepProp() -- returns a new table, with all child tables also copied. value.a = 4 -- updates the new table's `a` value value.b.c = 4 -- updates the new `b` table's `c` value deepProp(value) -- prints "deep: a = 4, b.c = 4" valueProp().a == 3 -- still referencing the original table valueProp().b.c == 3 -- the child is still referenced too shallowProp().a == 1 -- still unmodified after the initial copy shallowProp().b.c == 3 -- still referencing the original `b` table. deepProp().a == 4 -- updated to the new value deepProp().b.c == 4 -- updated to the new value -- get the 'shallow' property value = shallowProp() -- returns a new table with top-level keys copied. value.a = 5 -- updates the new table's `a` value value.b.c = 5 -- updates the original `b` table's `c` value. shallowProp(value) -- prints "shallow: a = 5, b.c = 5" valueProp().a == 3 -- still referencing the original table valueProp().b.c == 5 -- still referencing the original `b` table shallowProp().a == 5 -- updated to the new value shallowProp().b.c == 5 -- referencing the original `b` table, which was updated deepProp().a == 4 -- unmodified after the last update deepProp().b.c == 4 -- unmodified after the last update
So, a little bit tricky. The general rule of thumb is:
- If working with immutable objects, use the default
value
value copy, which preserves the original. - If working with an array of immutible objects, use the
shallow
table copy. - In most other cases, use a
deep
table copy.
#API Overview
Constants - Useful values which cannot be changed
Functions - API calls offered directly by the extension
Constructors - API calls which return an object, typically one that offers API methods
Fields - Variables which can only be accessed from an object returned by a constructor
Methods - API calls which can only be made on an object returned by a constructor
- ABOVE
- AND
- ATLEAST
- ATMOST
- BELOW
- bind
- cached
- clear
- clone
- deepTable
- EQ
- get
- hasWatchers
- id
- IMMUTABLE
- IS
- ISNOT
- label
- mirror
- monitor
- mutable
- mutate
- NEQ
- NOT
- OR
- owner
- preWatch
- set
- shallowTable
- toggle
- toObservable
- unwatch
- update
- value
- watch
- wrap
#API Documentation
#Constants
Signature | cp.prop.NIL -> cp.prop |
Type | Constant |
Description | Returns a cp.prop which will always be nil . |
Parameters |
|
Returns |
|
Notes | None |
Source | src/extensions/cp/prop/init.lua line 1402 |
#Functions
Signature | cp.prop.bind(owner[, relaxed]) -> function |
Type | Function |
Description | This provides a utility function for binding multiple properties to a single owner in a simple way. |
Parameters |
|
Returns |
|
Notes |
|
Examples | None |
Source | src/extensions/cp/prop/init.lua line 1714 |
Signature | cp.prop.extend(target, source) -> table |
Type | Function |
Description | Makes the target extend the source . It will copy all bound properties on the source table into the target, rebinding it to the target table. Other keys are inherited via the metatable. |
Parameters |
|
Returns |
|
Notes | None |
Examples | None |
Source | src/extensions/cp/prop/init.lua line 1692 |
Signature | cp.prop.is(value) -> boolean |
Type | Function |
Description | Checks if the value is an instance of a cp.prop . |
Parameters |
|
Returns |
|
Notes | None |
Examples | None |
Source | src/extensions/cp/prop/init.lua line 345 |
#Constructors
Signature | cp.prop.AND(...) -> cp.prop |
Type | Constructor |
Description | Returns a new cp.prop which will be true if all cp.prop instances passed into the function return a truthy value. |
Parameters |
|
Returns |
|
Notes |
|
Examples | None |
Source | src/extensions/cp/prop/init.lua line 1530 |
Signature | cp.prop.FALSE() -> cp.prop |
Type | Constructor |
Description | Returns a new cp.prop which will cache internally, initially set to false . |
Parameters |
|
Returns |
|
Notes | None |
Examples | None |
Source | src/extensions/cp/prop/init.lua line 1440 |
Signature | cp.prop.FROM(value) --> cp.prop |
Type | Constructor |
Description | Creates a new prop value, with the provided value . |
Parameters |
|
Returns |
|
Notes |
|
Examples | None |
Source | src/extensions/cp/prop/init.lua line 1338 |
Signature | cp.prop.IMMUTABLE(propValue) -- cp.prop |
Type | Constructor |
Description | Returns a new cp.prop instance which will not allow the wrapped value to be modified. |
Parameters |
|
Returns |
|
Notes |
|
Examples | None |
Source | src/extensions/cp/prop/init.lua line 1384 |
Signature | cp.prop.new(getFn, setFn, cloneFn) --> cp.prop |
Type | Constructor |
Description | Creates a new prop value, with the provided get and set functions. |
Parameters |
|
Returns |
|
Notes |
|
Examples | None |
Source | src/extensions/cp/prop/init.lua line 1305 |
Signature | cp.prop.NOT(propValue) -> cp.prop |
Type | Constructor |
Description | Returns a new cp.prop which negates the provided propValue . |
Parameters |
|
Returns |
|
Notes |
|
Examples | None |
Source | src/extensions/cp/prop/init.lua line 1466 |
Signature | cp.prop.OR(...) -> cp.prop |
Type | Constructor |
Description | Returns a new cp.prop which will return the first 'truthy' value provided by one of the provided properties. Otherwise, returns the last 'falsy' value. |
Parameters |
|
Returns |
|
Notes |
|
Examples | None |
Source | src/extensions/cp/prop/init.lua line 1585 |
Signature | cp.prop.THIS([initialValue]) -> cp.prop |
Type | Constructor |
Description | Returns a new cp.prop instance which will cache a value internally. It will default to the value of the initialValue , if provided. |
Parameters |
|
Returns |
|
Notes | None |
Examples | None |
Source | src/extensions/cp/prop/init.lua line 1362 |
Signature | cp.prop.TRUE() -> cp.prop |
Type | Constructor |
Description | Returns a new cp.prop which will cache internally, initially set to true . |
Parameters |
|
Returns |
|
Notes | None |
Examples | None |
Source | src/extensions/cp/prop/init.lua line 1427 |
#Fields
Signature | cp.prop.mainWindow <cp.prop: cp.ui.Window; read-only; live> |
Type | Field |
Description | The main Window, or nil if none is available. |
Notes | None |
Source | src/extensions/cp//app.lua line 472 |
#Methods
Signature | cp.prop:ABOVE() -> cp.prop <boolean; read-only> |
Type | Method |
Description | Returns a new property comparing this property to something . |
Parameters |
|
Returns |
|
Notes | None |
Examples | None |
Source | src/extensions/cp/prop/init.lua line 1155 |
Signature | cp.prop:AND(...) -> cp.prop |
Type | Method |
Description | Returns a new cp.prop which will be true if this and all other cp.prop instances passed into the function return true . |
Parameters |
|
Returns |
|
Notes |
|
Examples | None |
Source | src/extensions/cp/prop/init.lua line 1571 |
Signature | cp.prop:ATLEAST() -> cp.prop <boolean; read-only> |
Type | Method |
Description | Returns a new property comparing this property to something . |
Parameters |
|
Returns |
|
Notes | None |
Examples | None |
Source | src/extensions/cp/prop/init.lua line 1201 |
Signature | cp.prop:ATMOST() -> cp.prop <boolean; read-only> |
Type | Method |
Description | Returns a new property comparing this property to something . |
Parameters |
|
Returns |
|
Notes | None |
Examples | None |
Source | src/extensions/cp/prop/init.lua line 1178 |
Signature | cp.prop:BELOW() -> cp.prop <boolean; read-only> |
Type | Method |
Description | Returns a new property comparing this property to something . |
Parameters |
|
Returns |
|
Notes | None |
Examples | None |
Source | src/extensions/cp/prop/init.lua line 1132 |
Signature | cp.prop:bind(owner, [key]) -> cp.prop |
Type | Method |
Description | Binds the property to the specified owner. Once bound, it cannot be changed. |
Parameters |
|
Returns |
|
Notes |
|
Examples | None |
Source | src/extensions/cp/prop/init.lua line 551 |
Signature | cp.prop:cached() -> prop |
Type | Method |
Description | This can be called once to enable caching of the result inside the prop . |
Parameters |
|
Returns |
|
Notes |
|
Examples | None |
Source | src/extensions/cp/prop/init.lua line 446 |
Signature | cp.prop:clear() -> nil |
Type | Method |
Description | Clears the property. Watchers will be notified if the value has changed. |
Parameters |
|
Returns |
|
Notes | None |
Examples | None |
Source | src/extensions/cp/prop/init.lua line 538 |
Signature | cp.prop:clone() -> cp.prop |
Type | Method |
Description | Returns a new copy of the property. |
Parameters |
|
Returns |
|
Notes | None |
Examples | None |
Source | src/extensions/cp/prop/init.lua line 904 |
Signature | cp.prop:deepTable([skipMetatable]) -> prop |
Type | Method |
Description | This can be called once to enable deep copying of table values. By default, table s are simply passed in and out. If a sub-key of a table changes, no change will be registered when setting. |
Parameters |
|
Returns |
|
Notes |
|
Examples | None |
Source | src/extensions/cp/prop/init.lua line 393 |
Signature | cp.prop:EQ(something) -> cp.prop <boolean; read-only> |
Type | Method |
Description | Synonym for IS. |
Parameters |
|
Returns |
|
Notes | None |
Examples | None |
Source | src/extensions/cp/prop/init.lua line 1087 |
Signature | cp.prop:get() -> value |
Type | Method |
Description | Returns the current value of the property. |
Parameters |
|
Returns |
|
Notes | None |
Examples | None |
Source | src/extensions/cp/prop/init.lua line 491 |
Signature | cp.prop:hasWatchers() -> boolean |
Type | Method |
Description | Returns true if the property has any watchers. |
Parameters |
|
Returns |
|
Notes | None |
Examples | None |
Source | src/extensions/cp/prop/init.lua line 707 |
Signature | cp.prop:id() -> number |
Type | Method |
Description | Returns the current ID. |
Parameters |
|
Returns |
|
Notes | None |
Examples | None |
Source | src/extensions/cp/prop/init.lua line 362 |
Signature | cp.prop:IMMUTABLE() -> cp.prop |
Type | Method |
Description | Returns a new cp.prop instance wrapping this property which will not allow it to be modified. |
Parameters |
|
Returns |
|
Notes |
|
Examples | None |
Source | src/extensions/cp/prop/init.lua line 1413 |
Signature | cp.prop:IS(something) -> cp.prop <boolean; read-only> |
Type | Method |
Description | Returns a new property returning true if the value is equal to something . |
Parameters |
|
Returns |
|
Notes | None |
Examples | None |
Source | src/extensions/cp/prop/init.lua line 1064 |
Signature | cp.prop:ISNOT(something) -> cp.prop <boolean; read-only> |
Type | Method |
Description | Returns a new property returning true when this property is not equal to something . |
Parameters |
|
Returns |
|
Notes | None |
Examples | None |
Source | src/extensions/cp/prop/init.lua line 1098 |
Signature | cp.prop:label([newLabel]) -> string | cp.prop |
Type | Method |
Description | Gets and sets the property label. This is human-readable text describing the cp.prop . It is used when converting the prop to a string, for example. |
Parameters |
|
Returns |
|
Notes | None |
Examples | None |
Source | src/extensions/cp/prop/init.lua line 375 |
Signature | cp.prop:mirror(otherProp) -> self |
Type | Method |
Description | Configures this prop and the other prop to mirror each other's values. When one changes the other will change with it. Only one prop needs to mirror. |
Parameters |
|
Returns |
|
Notes | None |
Examples | None |
Source | src/extensions/cp/prop/init.lua line 1046 |
Signature | cp.prop:monitor(...) -> cp.prop |
Type | Method |
Description | Adds an uncloned watch to the otherProp which will trigger an update check in this property. |
Parameters |
|
Returns |
|
Notes | None |
Examples | None |
Source | src/extensions/cp/prop/init.lua line 833 |
Signature | cp.prop:mutable() -> boolean |
Type | Method |
Description | Checks if the cp.prop can be modified. |
Parameters |
|
Returns |
|
Notes | None |
Examples | None |
Source | src/extensions/cp/prop/init.lua line 607 |
Signature | cp.prop:mutate(getFn, [setFn]) -> cp.prop <anything; read-only> |
Type | Method |
Description | Returns a new property that is a mutation of the current one. |
Parameters |
|
Returns |
|
Notes |
|
Examples | None |
Source | src/extensions/cp/prop/init.lua line 929 |
Signature | cp.prop:NEQ(something) -> cp.prop <boolean; read-only> |
Type | Method |
Description | A synonym for ISNOT |
Parameters |
|
Returns |
|
Notes | None |
Examples | None |
Source | src/extensions/cp/prop/init.lua line 1121 |
Signature | cp.prop:NOT() -> cp.prop |
Type | Method |
Description | Returns a new cp.prop which negates the current value. |
Parameters |
|
Returns |
|
Notes |
|
Examples | None |
Source | src/extensions/cp/prop/init.lua line 1494 |
Signature | cp.prop:OR(...) -> cp.prop |
Type | Method |
Description | Returns a new cp.prop which will be true if this or any cp.prop instance passed into the function returns true . |
Parameters |
|
Returns |
|
Notes |
|
Examples | None |
Source | src/extensions/cp/prop/init.lua line 1626 |
Signature | cp.prop:owner() -> table |
Type | Method |
Description | If this is a 'method', return the table instance the method is attached to. |
Parameters |
|
Returns |
|
Notes | None |
Examples | None |
Source | src/extensions/cp/prop/init.lua line 594 |
Signature | cp.prop:preWatch(preWatchFn) -> cp.prop |
Type | Method |
Description | Adds a function which will be called once if any watchers are added to this prop. |
Parameters |
|
Returns |
|
Notes |
|
Examples | None |
Source | src/extensions/cp/prop/init.lua line 721 |
Signature | cp.prop:set(newValue) -> value |
Type | Method |
Description | Sets the property to the specified value. Watchers will be notified if the value has changed. |
Parameters |
|
Returns |
|
Notes | None |
Examples | None |
Source | src/extensions/cp/prop/init.lua line 509 |
Signature | cp.prop:shallowTable(skipMetatable) -> prop |
Type | Method |
Description | This can be called once to enable shallow cloning of table values. By default, table s are simply passed in and out. If a sub-key of a table changes, no change will be registered when setting. |
Parameters |
|
Returns |
|
Notes |
|
Examples | None |
Source | src/extensions/cp/prop/init.lua line 419 |
Signature | cp.prop:toggle() -> boolean | nil |
Type | Method |
Description | Toggles the current value. |
Parameters |
|
Returns |
|
Notes |
|
Examples | None |
Source | src/extensions/cp/prop/init.lua line 643 |
Signature | cp.prop:toObservable() -> cp.rx.Observable |
Type | Method |
Description | Returns the cp.rx.Observable for the property. This will emit onNext() events with the current value whenever the cp.prop is updated. Any new subscriptions will receive the most recent value immediately. |
Parameters |
|
Returns |
|
Notes |
|
Examples | None |
Source | src/extensions/cp/prop/init.lua line 779 |
Signature | cp.prop:unwatch(watchFn) -> boolean |
Type | Method |
Description | Removes the specified watch method as a watcher, if present. |
Parameters |
|
Returns |
|
Notes |
|
Examples | None |
Source | src/extensions/cp/prop/init.lua line 758 |
Signature | cp.prop:update() -> value |
Type | Method |
Description | Forces an update of the property and notifies any watchers if it has changed. |
Parameters |
|
Returns |
|
Notes | None |
Examples | None |
Source | src/extensions/cp/prop/init.lua line 864 |
Signature | cp.prop:value([newValue]) -> value |
Type | Method |
Description | Returns the current value of the cp.prop instance. If a newValue is provided, and the instance is mutable, the value will be updated and the new value is returned. If it is not mutable, an error will be thrown. |
Parameters |
|
Returns |
|
Notes |
|
Examples | None |
Source | src/extensions/cp/prop/init.lua line 465 |
Signature | cp.prop:watch(watchFn, notifyNow, uncloned) -> cp.prop, function |
Type | Method |
Description | Adds the watch function to the value. When the value changes, watchers are notified by calling the function. |
Parameters |
|
Returns |
|
Notes |
|
Examples | None |
Source | src/extensions/cp/prop/init.lua line 667 |
Signature | cp.prop:wrap([owner[, key]]) -> cp.prop <anything> |
Type | Method |
Description | Returns a new property that wraps this one. It will be able to get and set the same as this, and changes to this property will trigger updates in the wrapper. |
Parameters |
|
Returns |
|
Notes | None |
Examples | None |
Source | src/extensions/cp/prop/init.lua line 1011 |