Parcel is a data container. Its job is to hold your data, split it into smaller parts, and merge changes back together.
If you're using React, you probably won't be instanciating parcels directly. Please see the getting started page to see how to best use Parcels in a React app.
import Parcel from 'dataparcels';
import Parcel from 'react-dataparcels';
new Parcel({
value?: any,
handleChange?: Function
});
The value you want to put in the Parcel. This value will be changed immutably when change methods are called on the Parcel. The data type of the value
will determine the type of Parcel that will be created, and will determine which methods you can use to change the value. Please read Parcel types for more info.
The handleChange
function will be called whenever the Parcel's value has been triggered to change. It is passed newParcel
, a replacement Parcel containing the changes; and changeRequest
, a ChangeRequest that contains details about the change itself.
In handleChange
you would typically implement logic to replace your current parcel with newParcel
, but if you're using React you should read getting started to save you the trouble of implementing this yourself.
// creates a Parcel that contains a value of 123
let parcel = new Parcel({
value: 123,
handleChange: (newParcel) => {
// here you can insert logic to replace
// your existing parcel with the newParcel
})
});
Examples can be found on the data editing page.
value: any
Returns the Parcel's value.
let value = 123;
let parcel = new Parcel({value});
parcel.value; // returns 123
meta: Object
Returns an object containing the parcel's meta data. Read Parcel meta for more info.
let value = 123;
let parcel = new Parcel({value});
parcel.meta; // returns {}
// set initial meta and check again
parcel
.initialMeta({abc: 123})
.meta; // returns {abc: 123}
data: Object
Returns an object containing the Parcel's data, which includes:
value
- The Parcel's valuemeta
- The Parcel's meta objectkey
- The Parcel's keychild
- The Parcel's child information, which includes any meta
, key
and child
data related to the value
s children.let value = 123;
let parcel = new Parcel({value});
parcel.data;
// return {
// child: undefined,
// key: '^',
// meta: {},
// value: 123,
// }
key: string
Returns the Parcel's key
. Dataparcels automatically gives unique keys to all children of a parent parcel. See parcel keys for more info.
Because they are unique, the can be used as keys when rendering an array of elements with React. This is demonstrated here.
let value = {
abc: 123,
def: 456
};
let parcel = new Parcel({value});
parcel.get("abc").key; // returns "abc"
let value = [
123,
456
];
let parcel = new Parcel({value});
parcel.get(0).key; // returns "#a"
id: string
Returns the Parcel's id
. Under most circumstances, id
s are unique among all Parcels that are descendants of a single original Parcel. You won't often need to use this, but it can sometimes be useful for debugging.
let value = {
abc: 123,
def: 456
};
let parcel = new Parcel({value});
parcel.get("abc").id; // returns "^.abc"
path: string[]
Returns the Parcel's path
, an array of strings indicating how to access the current Parcel's value.
let value = {
abc: {
def: 123
}
};
let parcel = new Parcel({value});
parcel.get("abc").get("def").path; // returns ["abc", "def"]
get(key: string|number): Parcel // only on ParentParcels
get(key: string|number, notSetValue: any): Parcel // only on ParentParcels
Returns a Parcel containing the value associated with the provided key / index.
If the key / index doesn't exist, a Parcel with a value of notSetValue
will be returned.
If notSetValue
is not provided then a Parcel with a value of
undefined
will be returned.
let value = {
abc: 123,
def: 456
};
let parcel = new Parcel({value});
parcel.get('abc').value; // returns 123
parcel.get('xyz').value; // returns undefined
parcel.get('xyz', 789).value; // returns 789
When called on a Parcel with an indexed value, such as an array, this method can accept an index
or a key
.
index
(number) is used to get a value based off its position. It can also be negative, indicating an offset from the end of the sequence.key
(string) is used to get a specific value by its unique key within the Parcel.let value = ['abc', 'def', 'ghi'];
let parcel = new Parcel({value});
parcel.get(0).value; // returns 'abc'
parcel.get(-1).value; // returns 'ghi'
parcel.get('#a').value; // returns 'abc'
getIn(keyPath: Array<string|number>): Parcel // only on ParentParcels
getIn(keyPath: Array<string|number>, notSetValue: any): Parcel // only on ParentParcels
Returns a Parcel containing the value associated with the provided key path.
If the key path doesn't exist, a Parcel with a value of notSetValue
will be returned.
If notSetValue
is not provided then a Parcel with a value of
undefined
will be returned.
let value = {
a: {
b: 123
}
};
let parcel = new Parcel({value});
parcel.getIn(['a','b']).value; // returns 123
parcel.getIn(['a','z']).value; // returns undefined
parcel.getIn(['a','z'], 789).value; // returns 789
When called on a Parcel with an indexed value, such as an array, this method can accept an index
or a key
.
index
(number) is used to get a value based off its position. It can also be negative, indicating an offset from the end of the sequence.key
(string) is used to get a specific value by its unique key within the Parcel.children(mapper?: ParcelMapper): ParentType<Parcel> // only on ParentParcels
type ParcelMapper = (
item: Parcel,
property: string|number,
parent: Parcel
) => any;
Returns all of the Parcel's children as new ChildParcels, contained within the original Parcel's data structure.
An optional mapper
function can be passed, which will be called on each child.
let value = {
abc: 123,
def: 456
};
let parcel = new Parcel({value});
parcel.children();
// returns {
// abc: Parcel, // contains a value of 123
// def: Parcel // contains a value of 456
// }
toArray(mapper?: ParcelMapper): Array<Parcel> // only on ParentParcels
type ParcelMapper = (
item: Parcel,
property: string|number,
parent: Parcel
) => any;
Like children(), expect the returned data structure is cast to an array.
An optional mapper
function can be passed, which will be called on each child.
let value = {
abc: 123,
def: 456
};
let parcel = new Parcel({value});
parcel.toArray();
// returns [
// Parcel, // contains a value of 123
// Parcel // contains a value of 456
// ]
metaAsParcel(key: string): Parcel
Typically Parcel meta is accessed and set via the .meta property and the .setMeta() method. The metaAsParcel()
function is an alternative that creates a Parcel that controls a piece of meta data. It's useful for binding meta changes to inputs.
let parcel = new Parcel({value: 123});
<input {...parcel.metaAsValue('foo').spreadDOM()} />
// if 'bar' is entered in the input, parcel.meta.foo will equal 'bar'
spread(): {value: *, onChange: OnChangeFunction}
spread(notFoundValue: any): {value: *, onChange: OnChangeFunction}
type OnChangeFunction = (value: any) => void;
This is designed to bind a Parcel with an input component that expects a value
and an onChange
callback. The onChange
callback is expected to pass an updated value as its first argument.
If notFoundValue
is provided, and the Parcel's value is undefined or has been marked for deletion, the returned value will be equal to notFoundValue
.
let parcel = new Parcel({
value: 123
});
<MyInputComponent {...parcel.spread()} />
// ^ this is equivalent to
// <MyInputComponent value={parcel.value} onChange={parcel.onChange} />
spreadDOM(): {value: *, onChange: OnChangeDOMFunction}
spreadDOM(notFoundValue: any): {value: *, onChange: OnChangeDOMFunction}
type OnChangeDOMFunction = (event: HTMLEvent) => void;
This is designed to bind a Parcel with an HTML input. It returns an object with the Parcel's value
and its onChangeDOM
function.
If notFoundValue
is provided, and the Parcel's value is undefined or has been marked for deletion, the returned value will be equal to notFoundValue
.
let parcel = new Parcel({
value: 123
});
<input {...parcel.spreadDOM()} />
// ^ this is equivalent to
// <input value={parcel.value} onChange={parcel.onChangeDOM} />
spreadDOMCheckbox(): {value: *, onChange: OnChangeDOMCheckboxFunction}
spreadDOMCheckbox(notFoundValue: boolean): {value: *, onChange: OnChangeDOMCheckboxFunction}
type OnChangeDOMCheckboxFunction = (event: HTMLEvent) => void;
This is designed to bind a Parcel with an HTML checkbox.
It returns an object with checked
and onChange
, where checked
is the Parcel's value
cast to a boolean, and onChange
is the Parcel's onChangeDOMCheckbox
function.
If notFoundValue
is provided, and the Parcel's value is undefined or has been marked for deletion, the returned value will be equal to notFoundValue
cast to a boolean.
let parcel = new Parcel({
value: 123
});
<input type="checkbox" {...parcel.spreadDOMCheckbox()} />
onChange(value: any): void
This is designed for use with input components that call onChange
with a new value.
It triggers a change that replaces the current value in the Parcel with the value
provided.
If called on a ParentParcel, any child information that the Parcel had is removed, such as child keys or meta.
It is equivalent to calling set() with no key.
let parcel = new Parcel({
value: 123
});
<MyInputComponent
value={parcel.value}
onChange={parcel.onChange}
/>
onChangeDOM(event: HTMLEvent): void
This is designed for use with HTML inputs.
It triggers a change that replaces the current value in the Parcel with the value
of the event provided.
If called on a ParentParcel, any child information that the Parcel had is removed, such as child keys or meta.
let parcel = new Parcel({
value: 123
});
<input
value={parcel.value}
onChangeDOM={parcel.onChangeDOM}
/>
onChangeDOMCheckbox(event: HTMLEvent): void
This is designed for use with HTML checkboxes. It triggers a change that replaces the current value in the Parcel with the checked state of the checkbox.
let parcel = new Parcel({
value: false
});
<input
type="checkbox"
value={parcel.value}
onChangeDOMCheckbox={parcel.onChangeDOMCheckbox}
/>
isFirst(): boolean
Returns true if this parcel is the first of its siblings.
isLast(): boolean
Returns true if this parcel is the last of its siblings.
set(value: any): void
Calling set()
will trigger a change that replaces the current value in the Parcel with the value
provided. This is equivalent to calling onChange().
let parcel = new Parcel({
value: 123
});
parcel.set(456);
// this triggers a change that sets the parcel's value to 456
When called on a Parcel with an indexed value, such as an array, this method can accept an index
or a key
.
index
(number) is used to get a value based off its position. It can also be negative, indicating an offset from the end of the sequence.key
(string) is used to get a specific value by its unique key within the Parcel.delete(): void // only on ChildParcels
Calling delete()
will trigger a change that will delete the current Parcel off of its parent. This variation of the delete()
method only exists on ChildParcels.
let value = {
abc: 123,
def: 456
};
let parcel = new Parcel({value});
parcel.get('abc').delete();
// this triggers a change that sets the parcel's value to {def: 456}
When called on a Parcel with an indexed value, such as an array, this method can accept an index
or a key
.
index
(number) is used to get a value based off its position. It can also be negative, indicating an offset from the end of the sequence.key
(string) is used to get a specific value by its unique key within the Parcel.// updates value - only to be used if shape doesn't change
update((value: any) => any): Parcel
// updates shape / meta
update(asNode((node: ParcelNode) => ParcelNode)): Parcel
update(asChildNodes((nodes: any) => any): Parcel
Calling update()
will trigger a change that replaces the current value in the Parcel with the result of the value updater provided to it. The value updater is passed the current value of the Parcel, from which you can return the intended replacement value.
let parcel = new Parcel({
value: 123
});
parcel.update(value => value + 1);
// this triggers a change that sets the parcel's value to 124
This method is safe to use in most simple cases, but in some cases it should not be used:
To find out why, and what to do about it, please read about parcel updaters.
// updates value - only to be used if shape doesn't change
map((value: any) => any): Parcel
// updates shape / meta
update(asNode((node: ParcelNode) => ParcelNode)): Parcel
update(asChildNodes((nodes: any) => any): Parcel
The map()
function will trigger a change that will update every child value. It is passed each child value in order, from which you can return each intended replacement value.
let value = {
a: 1,
b: 2,
c: 3
};
let parcel = new Parcel({value});
parcel.map(value => value * 2);
// this triggers a change that sets the parcel's value to {a: 2, b: 4, c: 6}
This method is safe to use in most simple cases, but in some cases it should not be used:
To find out why, and what to do about it, please read about parcel updaters.
setMeta(partialMeta: Object): void
Triggers a change that sets meta
at the current parcel's location. Values on the partialMeta
object are merged shallowly onto any existing meta
. Read Parcel meta for more info.
let parcel = new Parcel({
value: "abc"
});
parcel.setMeta({
abc: 123
});
// ^ this triggers a change that sets the parcel's meta to {abc: 123}
parcel.setMeta({
def: 456
});
// ^ this triggers a change that sets the parcel's meta to {abc: 123, def: 456}
dispatch(dispatchable: Action|Action[]|ChangeRequest): void
The dispatch()
method is used by Parcels internally to pass a ChangeRequest upward to the next Parcel in the chain.
insertAfter(value: any): void // only on ElementParcels, will insert after self
This inserts value
after the current Parcel, within the current ParentParcel.
let value = ['a','b','c'];
let parcel = new Parcel({value});
parcel.get(1).insertAfter('!');
// this triggers a change that sets the parcel's value to ['a','b','!','c'];
When called on a Parcel with an indexed value, such as an array, this method can accept an index
or a key
.
index
(number) is used to get a value based off its position. It can also be negative, indicating an offset from the end of the sequence.key
(string) is used to get a specific value by its unique key within the Parcel.insertBefore(value: any): void // only on ElementParcels, will insert before self
This inserts value
before the current Parcel, within the current ParentParcel.
let value = ['a','b','c'];
let parcel = new Parcel({value});
parcel.get(1).insertBefore('!');
// this triggers a change that sets the parcel's value to ['a','!','b','c'];
When called on a Parcel with an indexed value, such as an array, this method can accept an index
or a key
.
index
(number) is used to get a value based off its position. It can also be negative, indicating an offset from the end of the sequence.key
(string) is used to get a specific value by its unique key within the Parcel.push(...values: Array<*>): void // only on IndexedParcels
This triggers a change that pushes all provided values to the end of the current ParentParcel.
let value = ['a','b','c'];
let parcel = new Parcel({value});
parcel.push('d','e');
// this triggers a change that sets the parcel's value to ['a','b','c','d','e'];
pop(): void // only on IndexedParcels
This triggers a change that pops the last value off of the end of the current ParentParcel.
let value = ['a','b','c'];
let parcel = new Parcel({value});
parcel.pop();
// this triggers a change that sets the parcel's value to ['a','b'];
shift(): void // only on IndexedParcels
This triggers a change that pops the first value off of the start of the current ParentParcel.
let value = ['a','b','c'];
let parcel = new Parcel({value});
parcel.shift();
// this triggers a change that sets the parcel's value to ['b','c'];
swap(keyA: string|number, keyB: string|number): void // only on IndexedParcels, will swap children
This swaps the child Parcel at keyA
with the one at the position of keyB
.
let value = ['a','b','c'];
let parcel = new Parcel({value});
parcel.swap(0, 1);
// this also triggers a change that sets the parcel's value to ['b','a','c'];
When called on a Parcel with an indexed value, such as an array, this method can accept an index
or a key
.
index
(number) is used to get a value based off its position. It can also be negative, indicating an offset from the end of the sequence.key
(string) is used to get a specific value by its unique key within the Parcel.swapNext(): void // only on ElementParcels, will swap with next sibling
This swaps the current Parcel with its next sibling Parcel, within the current ParentParcel. If called on the last child, it swaps with the first child.
let value = ['a','b','c'];
let parcel = new Parcel({value});
parcel.get(0).swapNext();
// this triggers a change that sets the parcel's value to ['b','a','c'];
When called on a Parcel with an indexed value, such as an array, this method can accept an index
or a key
.
index
(number) is used to get a value based off its position. It can also be negative, indicating an offset from the end of the sequence.key
(string) is used to get a specific value by its unique key within the Parcel.swapPrev(): void // only on ElementParcels, will swap with previous sibling
This swaps the current Parcel with its previous sibling Parcel, within the current ParentParcel. If called on the first child, it swaps with the last child.
let value = ['a','b','c'];
let parcel = new Parcel({value});
parcel.get(1).swapPrev();
// this triggers a change that sets the parcel's value to ['b','a','c'];
When called on a Parcel with an indexed value, such as an array, this method can accept an index
or a key
.
index
(number) is used to get a value based off its position. It can also be negative, indicating an offset from the end of the sequence.key
(string) is used to get a specific value by its unique key within the Parcel.unshift(...values: Array<*>): void // only on IndexedParcels
This triggers a change that unshifts all provided values to the start of the current ParentParcel.
let value = ['a','b','c'];
let parcel = new Parcel({value});
parcel.unshift('d','e');
// this triggers a change that sets the parcel's value to ['d','e','a','b','c'];
has(key: string|number): boolean // only on ParentParcels
Returns true if the Parcel has a child at the provided key
or index
, or false otherwise.
size(): number // only on ParentParcels
Returns the number of children this Parcel has.
isChild(): boolean
Returns true if the parcel is a child parcel. Read Parcel types for more info.
When a parcel is a child parcel, it allows the use of child methods.
isElement(): boolean
Returns true if the parcel is an element parcel. Read Parcel types for more info.
When a parcel is an element parcel, it allows the use of element methods.
isIndexed(): boolean
Returns true if the parcel is an indexed parcel. Read Parcel types for more info.
When a parcel is an indexed parcel, it allows the use of indexed methods.
isParent(): boolean
Returns true if the parcel is a parent parcel. Read Parcel types for more info.
When a parcel is a parent parcel, it allows the use of branch methods and parent methods.
isTopLevel(): boolean
Returns true if the parcel is a top level parcel. Read Parcel types for more info.
// updates value - only to be used if shape doesn't change
modifyDown((value: any) => any): Parcel
// updates shape / meta
update(asNode((node: ParcelNode) => ParcelNode)): Parcel
update(asChildNodes((nodes: any) => any): Parcel
modifyDown
lets you modify a Parcel's value so that subsequent Parcels in the chain receive and make changes against the modified value. See Modifying data to fit the UI for examples.
let parcel = new Parcel({
value: "abc"
});
parcel
.modifyDown(value => value.toUpperCase());
.value // "ABC" (top level Parcel is still "abc")
It does not trigger any changes of its own, it just modifies the values that pass through it from above. Changes fired from beneath .modifyDown()
are passed through unchanged.
This method is safe to use in most simple cases, but in some cases it should not be used:
To find out why, and what to do about it, please read about parcel updaters.
// updates value - only to be used if shape doesn't change
modifyUp((value: any, changeRequest: ChangeRequest) => any): Parcel
// updates shape / meta
update(asNode((node: ParcelNode, changeRequest: ChangeRequest) => ParcelNode)): Parcel
update(asChildNodes((nodes: any, changeRequest: ChangeRequest) => any): Parcel
modifyUp()
lets you modify a Parcel's new value when a change is being propagated upward.
let parcel = new Parcel({
value: "abc"
});
parcel
.modifyUp(value => value.toUpperCase());
.set("def");
// this triggers a change to set the value to "def"
// which propagates upward through .modifyUp()
// .modifyUp() turns "def" into "DEF"
// and the change request continues up to the original Parcel
// The Parcel then has a new value of "DEF"
It does not trigger any changes of its own, but awaits a change from below. Values from above are passed through unchanged. See Modifying data to fit the UI for examples.
You can also cancel a change by returning cancel from modifyUp()
's updater. This allows you to programatically prevent certain changes from being applied to the data in the top level Parcel. This example shows an input that cancels any changes that would set the value to null
:
import cancel from 'dataparcels/cancel';
// or
import cancel from 'react-dataparcels/cancel';
let parcel = new Parcel({
value: 123
})
parcel = parcel.modifyUp(value => value === null ? cancel : value);
parcel.set(456); // this would work, value becomes 123
parcel.set(null); // this would cause no change
This method is safe to use in most simple cases, but in some cases it should not be used:
To find out why, and what to do about it, please read about parcel updaters.
initialMeta(initialMeta: Object): Parcel
Parcel meta
defaults to an empty object. The initialMeta
method replaces the meta for all descendant Parcels.
Once a descendant Parcel triggers a change, the initial meta is also propagated up to the top level Parcel.
let parcel = new Parcel({
value: "abc"
});
parcel
.initialMeta({
abc: 123
})
.meta // this returns {abc: 123} initially, but this can change after subsequent calls to setMeta()
spy(sideEffect: SideEffectFunction): Parcel
type SideEffectFunction = (parcel: Parcel) => void;
When the spy
method is called on a parcel, it immediately calls the sideEffect
function, passing itself as the first parameter. The return value of sideEffect
is ignored. It returns the original parcel, so it can be chained. This is useful for debugging.
let value = {
abc: 123
};
let parcel = new Parcel({value});
parcel
.spy(parcel => console.log(parcel.data)) // 1. logs the parcel to the console ({abc: 123})
.get('abc')
.spy(parcel => console.log(parcel.data)) // 2. logs the parcel to the console (123)
.value; // 3. returns 123
spyChange(sideEffect: SideEffectFunction): Parcel
type SideEffectFunction = (changeRequest: ChangeRequest) => void;
Once the spyChange
method is called on a parcel, it will call the sideEffect
function each time a change is requested from beneath, passing the associated ChangeRequest as the first parameter. The return value of sideEffect
is ignored. It returns a clone of the original parcel, so it can be chained. This is useful for debugging.
let value = {
abc: 123
};
let parcel = new Parcel({value});
parcel
.spyChange(changeRequest => console.log(changeRequest.nextData)) // 3. logs the change request to the console (containing {abc: 456})
.get('abc')
.spyChange(changeRequest => console.log(changeRequest.nextData)) // 2. logs the change request to the console (containing 456)
.onChange(456); // 1. a change is made
pipe(...updaters: Function[]): Parcel
The pipe
method allows for a parcel to be passed through one or more parcel modifying functions, while retaining the ability to chain. It allows for easier function composition.
let valueToString = (parcel) => parcel.modifyDown(value => `${value}`);
let changeToNumber = (parcel) => parcel.modifyUp(value => Number(value));
let parcel = new Parcel({value: 123});
parcel
.pipe(
valueToString,
changeToNumber
)
.value // returns "123"
The above is equivalent to:
let parcel = new Parcel({value: 123});
parcel
.modifyDown(value => `${value}`)
.modifyUp(value => Number(value))
.value // returns "123"