Internationalisation
Bundled Plugins API
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:
1
local value = true
2
local propValue = prop.new(function() return value end, function(newValue) value = newValue end)
3
propValue() == true -- `value` is still true
4
propValue(false) == false -- now `value` is false
Copied!

2. Togglable

A prop comes with toggling built in - as long as the it has a set function. Continuing from the last example:
1
propValue:toggle() -- `value` went from `false` to `true`.
Copied!
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:
1
propValue:watch(function(newValue) print "New Value: "...newValue) end) -- prints "New Value: true" immediately
2
propValue(false) -- prints "New Value: false"
Copied!
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:
1
propValue:toObservable():subscribe(function(value) print(tostring(value) end))
Copied!
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:
1
local watered = prop.TRUE() -- a simple `prop` which stores the current value internally, defaults to `true`
2
local fed = prop.FALSE() -- same as above, defautls to `false`
3
local rested = prop.FALSE() -- as above.
4
local satisfied = watered:AND(fed) -- will be true if both `watered` and `fed` are true.
5
local happy = satisfied:AND(rested) -- will be true if both `satisfied` and `happy`.
6
local sleepy = fed:AND(prop.NOT(rested)) -- will be sleepy if `fed`, but not `rested`.
7
8
-- These statements all evaluate to `true`
9
satisfied() == false
10
happy() == false
11
sleepy() == false
12
13
-- Get fed
14
fed(true) == true
15
satisfied() == true
16
happy() == false
17
sleepy() == true
18
19
-- Get rest
20
rested:toggle() == true
21
satisfied() == true
22
happy() == true
23
sleepy() == false
24
25
-- These will produce an error, because you can't modify an AND or OR:
26
happy(true)
27
happy:toggle()
Copied!
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:
1
local isImmutable = propValue:IMMUTABLE()
2
isImmutable:toggle() -- results in an `error` being thrown
3
isImmutable:watch(function(newValue) print "isImmutable changed to "..newValue end)
4
propValue:toggle() -- prints "isImmutable changed to false"
Copied!

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:
1
local owner = {
2
_value = true
3
}
4
owner.value = prop(function() return owner._value end)
5
owner:isMethod() -- error!
Copied!
To use a prop as a method, you need to attach it to the owning table, like so:
1
local owner = { _value = true }
2
owner.isMethod = prop(function(self) return self._value end, function(value, self) self._value = value end):bind(owner)
3
owner:isMethod() -- success!
4
owner.isMethod() -- also works - will still pass in the bound owner.
5
owner.isMethod:owner() == owner -- is true~
Copied!
You can also use the prop.bind function to bind multple properties at once:
1
local owner = { _value = true }
2
prop.bind(o) {
3
isMethod = prop(function(self) return self._value end)
4
}
5
owner:isMethod() -- success!
Copied!
The prop.extend function will also bind any cp.prop values it finds:
1
local owner = prop.extend({
2
_value = true,
3
isMethod = prop(function(self) return self._value end),
4
})
5
owner:isMethod() -- success!
Copied!
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:
1
local person = {}
2
function person:name(newValue)
3
if newValue then
4
self._name = newValue
5
end
6
return self._name
7
end
8
9
function person.new(name)
10
local o = { _name = name }
11
return setmetatable(o, { __index = person })
12
end
13
14
local johnDoe = person.new("John Doe")
15
johnDoe:name() == "John Doe"
Copied!
If we want to make the name a property, we might try creating a bound property like this:
1
person.name = prop(function(self) return self._name end, function(value, self) self._name = value end):bind(person)
Copied!
Unfortunately, this doesn't work as expected:
1
johnDoe:name() -- Throws an error because `person` is the owner, not `johnDoe`.
2
johnDoe.name() == nil -- Works, but will return `nil` because "John Doe" is applied to the new table, not `person`
Copied!
The fix is to use prop.extend when creating the new person. Rewrite person.new like so:
1
person.new(name)
2
local o = { _name = name }
3
return prop.extend(o, person)
4
end
Copied!
Now, this will work as expected:
1
johnDoe:name() == "John Doe"
2
johnDoe.name() == "John Doe"
Copied!
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:
1
local value = { a = 1, b = { c = 1 } }
2
local valueProp = prop.THIS(value)
3
local deepProp = prop.THIS(value):deepTable()
4
local shallowProp = prop.THIS(value):shallowTable()
5
6
-- print a message when the prop value is updated
7
valueProp:watch(function(v) print("value: a = " .. v.a ..", b.c = ".. v.b.c ) end)
8
deepProp:watch(function(v) print("deep: a = " .. v.a ..", b.c = ".. v.b.c ) end)
9
shallowProp:watch(function(v) print("shallow: a = " .. v.a ..", b.c = ".. v.b.c ) end)
10
11
-- change the original table:
12
value.a = 2
13
value.b.c = 2
14
15
valueProp().a == 2 -- modified
16
valueProp().b.c == 2 -- modified
17
shallowProp().a == 1 -- top level is copied
18
shallowProp().b.c == 2 -- child tables are referenced
19
deepProp().a == 1 -- top level is copied
20
deepProp().b.c == 1 -- child tables are copied as well
21
22
-- get the 'value' property
23
value = valueProp() -- returns the original value table
24
25
value.a = 3 -- updates the original value table `a` value
26
value.b.c = 3 -- updates the original `b` table's `c` value
27
28
valueProp(value) -- nothing is printed, since it's still the same table
29
30
valueProp().a == 3 -- still referencing the original table
31
valueProp().b.c == 3 -- the child is still referenced too
32
shallowProp().a == 1 -- still unmodified after the initial copy
33
shallowProp().b.c == 3 -- still updated, since `b` was copied by reference
34
deepProp().a == 1 -- still unmodified after initial copy
35
deepProp().b.c == 1 -- still unmodified after initial copy
36
37
-- get the 'deep copy' property
38
value = deepProp() -- returns a new table, with all child tables also copied.
39
40
value.a = 4 -- updates the new table's `a` value
41
value.b.c = 4 -- updates the new `b` table's `c` value
42
43
deepProp(value) -- prints "deep: a = 4, b.c = 4"
44
45
valueProp().a == 3 -- still referencing the original table
46
valueProp().b.c == 3 -- the child is still referenced too
47
shallowProp().a == 1 -- still unmodified after the initial copy
48
shallowProp().b.c == 3 -- still referencing the original `b` table.
49
deepProp().a == 4 -- updated to the new value
50
deepProp().b.c == 4 -- updated to the new value
51
52
-- get the 'shallow' property
53
value = shallowProp() -- returns a new table with top-level keys copied.
54
55
value.a = 5 -- updates the new table's `a` value
56
value.b.c = 5 -- updates the original `b` table's `c` value.
57
58
shallowProp(value) -- prints "shallow: a = 5, b.c = 5"
59
60
valueProp().a == 3 -- still referencing the original table
61
valueProp().b.c == 5 -- still referencing the original `b` table
62
shallowProp().a == 5 -- updated to the new value
63
shallowProp().b.c == 5 -- referencing the original `b` table, which was updated
64
deepProp().a == 4 -- unmodified after the last update
65
deepProp().b.c == 4 -- unmodified after the last update
Copied!
So, a little bit tricky. The general rule of thumb is:
  1. 1.
    If working with immutable objects, use the default value value copy, which preserves the original.
  2. 2.
    If working with an array of immutible objects, use the shallow table copy.
  3. 3.
    In most other cases, use a deep table copy.

API Overview

API Documentation

Constants

NIL

Signature
cp.prop.NIL -> cp.prop
Type
Constant
Description
Returns a cp.prop which will always be nil.
Parameters
  • None
Returns
  • a new cp.prop instance with a value of nil.

Functions

AND

Signature
cp.prop.AND(...) -> cp.prop
Type
Function
Description
Returns a new cp.prop which will be true if all cp.prop instances passed into the function return a truthy value.
Parameters
  • ... - The list of cp.prop instances to 'AND' together.
Returns
  • a cp.prop instance.
Notes
  • The value of this instance will resolve by lazily checking the value of the contained cp.prop instances in the order provided. The first falsy value will be returned. Otherwise the last truthy value is returned.
  • The instance is immutable.
  • Once you have created an 'AND', you cannot 'OR' as a method. Eg, this will fail: prop.TRUE():AND(prop:FALSE()):OR(prop.TRUE()). This is to avoid ambiguity as to whether the 'AND' or 'OR' takes precedence. Is it (true and false) or true or true and (false or true)?.
  • To combine 'AND' and 'OR' values, group them together when combining. Eg:
  • (true and false) or true: prop.OR( prop.TRUE():AND(prop.FALSE()), prop.TRUE() )
  • true and (false or true): prop.TRUE():AND( prop.FALSE():OR(prop.TRUE()) )

bind

Signature
cp.prop.bind(owner[, relaxed]) -> function
Type
Function
Description
This provides a utility function for binding multiple properties to a single owner in
Parameters
  • owner - The owner table to bind the properties to.
  • relaxed - If true, then non-cp.prop fields will be ignored. Otherwise they generate an error.
Returns
  • A function which should be called, passing in a table of key/value pairs which are string/cp.prop value.
Notes
  • If you are binding multiple cp.prop values that are dependent on other cp.prop values on the same owner (e.g. via mutate or a boolean join), you will have to break it up into multiple prop.bind(...) {...} calls, so that the dependent property can access the bound property.
  • If a cp.prop provided as bindings already has a bound owner, it will be wrapped instead of bound directly.

extend

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
  • target - The target to extend
  • source - The source to extend from
Returns
  • The target, now extending the source.

FALSE

Signature
cp.prop.FALSE() -> cp.prop
Type
Function
Description
Returns a new cp.prop which will cache internally, initially set to false.
Parameters
  • None
Returns
  • a cp.prop instance defaulting to false.

IMMUTABLE

Signature
cp.prop.IMMUTABLE(propValue) -- cp.prop
Type
Function
Description
Returns a new cp.prop instance which will not allow the wrapped value to be modified.
Parameters
  • propValue - The cp.prop value to wrap.
Returns
  • a new cp.prop instance which cannot be modified.

is

Signature
cp.prop.is(value) -> boolean
Type
Function
Description
Checks if the value is an instance of a cp.prop.
Parameters
  • value - The value to check.
Returns
  • true if the value is an instance of cp.prop.

NOT

Signature
cp.prop.NOT(propValue) -> cp.prop
Type
Function
Description
Returns a new cp.prop which negates the provided propValue. Values are negated as follows:
Parameters
  • propValue - Another cp.prop instance.
Returns
  • a cp.prop instance negating the propValue.
Notes
  • If the propValue is mutable, you can set the NOT property value and the underlying value will be set to the negated value. Be aware that the same negation rules apply when setting as when getting.

OR

Signature
cp.prop.OR(...) -> cp.prop
Type
Function
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
  • ... - The list of cp.prop instances to 'OR' together.
Returns
  • a cp.prop instance.
Notes
  • The value of this instance will resolve by lazily checking the value of the contained cp.prop instances in the order provided. If any return true, no further instances will be checked.
  • The instance is immutable, since there is no realy way to flip the component values of an 'OR' in a way that makes sense.
  • Once you have created an 'OR', you cannot 'AND' as a method. Eg, this will fail: prop.TRUE():OR(prop:FALSE()):AND(prop.TRUE()). This is to avoid ambiguity as to whether the 'OR' or 'AND' takes precedence. Is it (true or false) and true or true or (false and true)?.
  • To combine 'AND' and 'OR' values, group them together when combining. Eg:
  • (true or false) and true: prop.AND( prop.TRUE():OR(prop.FALSE()), prop.TRUE() )
  • true or (false and true): prop.TRUE():OR( prop.FALSE():AND(prop.TRUE()) )

THIS

Signature
cp.prop.THIS([initialValue]) -> cp.prop
Type
Function
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
  • initialValue - The initial value to set it to (optional).
Returns
  • a new cp.prop instance.

TRUE

Signature
cp.prop.TRUE() -> cp.prop
Type
Function
Description
Returns a new cp.prop which will cache internally, initially set to true.
Parameters
  • None
Returns
  • a cp.prop instance defaulting to true.

Constructors

new

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
  • getFn - The function that will get called to retrieve the current value.
  • setFn - (optional) The function that will get called to set the new value.
  • cloneFn - (optional) The function that will get called when cloning the property.
Returns
  • The new cp.prop instance.
Notes
  • getFn signature: function([owner]) -> anything
  • owner - If this is attached as a method, the owner table is passed in.
  • setFn signature: function(newValue[, owner])
  • newValue - The new value to store.
  • owner - If this is attached as a method, the owner table is passed in.
  • cloneFn signature: function(prop) -> new cp.prop
  • This can also be executed by calling the module directly. E.g. require('cp.prop')(myGetFunction)

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.

Methods

ABOVE

Signature
cp.prop:ABOVE() -> cp.prop <boolean; read-only>
Type
Method
Description
Returns a new property comparing this property to something.
Parameters
  • something - A value, a function or a cp.prop to compare to.
Returns
  • New, read-only cp.prop which will be true if this property is greater than something.

AND

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
  • ... - The list of cp.prop instances to 'AND' together.
Returns
  • a cp.prop instance.
Notes

ATLEAST

Signature
cp.prop:ATLEAST() -> cp.prop <boolean; read-only>
Type
Method
Description
Returns a new property comparing this property to something.
Parameters
  • something - A value, a function or a cp.prop to compare to.
Returns
  • New, read-only cp.prop which will be true if this property is less than or equal to something.

ATMOST

Signature
cp.prop:ATMOST() -> cp.prop <boolean; read-only>
Type
Method
Description
Returns a new property comparing this property to something.
Parameters
  • something - A value, a function or a cp.prop to compare to.
Returns
  • New, read-only cp.prop which will be true if this property is less than or equal to something.

BELOW

Signature
cp.prop:BELOW() -> cp.prop <boolean; read-only>
Type
Method
Description
Returns a new property comparing this property to something.
Parameters
  • something - A value, a function or a cp.prop to compare to.
Returns
  • New, read-only cp.prop which will be true if this property is less than something.

bind

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