component:Electrum simplifies framework-agnostic declaration of React components.
npm install eletcrum


Electrum simplifies framework-agnostic declaration of React components.
It is used internally by Epsitec SA to
bridge the gap with its _Xcraft toolchain_ and with its _Lydia framework_.
_Electrum_ is an alloy of gold and
silver used to produce ancient Lydian
coinage.
> The first metal coins ever made, were of Electrum and date back to the end
> of the 7th century, beginning of the 6th century BC.
In order to reduce dependency hell, electrum exports its own dependencies
directly:
* React, ReactDOM and ReactDOMServer from react, react-dom and
react-dom/server
* radium from radium.
* captureMouseEvents from electrum-events.
* FieldStates from electrum-field.
* Store and State from electrum-state.
* Styles, Theme and ColorManipulator from electrum-theme.
* Trace from electrum-trace.
The implementation of electrum is being modified radically.
Please wait until version has stabilized.
Let's say we want to display an article which contains the content
and information about the author. The article data might be represented
like this:
``json`
{ "article":
{ "content":
{ "title": "About typography"
, "text": "Lorem ipsum..."
, "date": "2015-12-02" }
, "author":
{ "name": "John"
, "mail": "john@doe.org" } } }
This can be loaded into a store instance. The "article" node will be
passed as _state_ to the component:
`javascript`
// In this example, the article is the root component
const state = store.select ('article');
return
The can be implemented as a _stateless function component_:
`javascript`
import E from 'electrum';
function Article (props) {
return (
);
}
Components will very often need to read values from the store. To make life
easier for the developer, electrum provides a read() method, which takesprops
the of the component and an optional key of the value to read:
` {E.read (props, 'details')}javascript`
import E from 'electrum';
function Content (props) {
return (
{E.read (props)}
);
}
We decided to use radium as the way to go to inject styles into components.E
By using the instance provided by import Electrum from 'electrum'),radium
components are automatically configured to use when _wrapped_ like
this:
`javascript`
import Electrum from 'electrum';
import _Button from './Button.component.js';
import _Button$styles from './Button.styles.js';
export const Button = Electrum.wrap ('Button', _Button, {styles: _Button$styles});
See electrum-theme for an explanation of how style functions should be
defined. Style functions can have following signatures:
* () => ... → a parameterless style function.(theme) => ...
* → a style function, based on the theme.(theme, props) => ...
* → a style function, based on the theme and onstyleProps
the component properties. The component should implement a _getter_ named
which returns a hash with the meaningful properties.
Multiple styles definitions can be exported as a _hash_ of style functions.
A component linked with a style definition consisting of a single style
function will expose following method and property:
* styles → a _styles object_ which can be set on DOM element stylewith(s1, s2, ...)
properties; the styles object is compatible with Radium. It exposes a
function which can be used to obtain an updatedmergeStyles(s1, s2, ...)
_styles object_ into which additional styles have been merged.
* → a _hash_ containing the merged styles.
A component linked with a style definition consisting of a multiple style
functions will expose following methods:
* getStyles(key) → a _styles object_ for the specified style definiton,style
which can be set on DOM element properties; the styles object iswith(s1, s2, ...)
compatible with Radium. It exposes a function whichmergeStyles(key, s1, s2, ...)
can be used to obtain an updated _styles object_ into which additional
styles have been merged.
* → a _hash_ containing the merged
styles for the specified style definition.
Note that the signature of the methods are different from a component
with a single style function.
Electrum.wrap() returns a new component class, which will be treated as a
pure component by React:
* shouldComponentUpdate(nextProps, nextState) → pure component.
The test is based on a _shallow comparison_ of the properties and of the state
(if any).
It injects some additional functionality:
* link(id) → shorthand for Electrum.link(this.props, id).link(id, overrides)
* → shorthand for Electrum.link(this.props, id, overrides).read()
* → shorthand for this.read('value').read(key)
* → returns named property if it exists on this.props, otherwiseElectrum.read(this.props, key)
calls and reads the value from the state.theme
* → shorthand for this.props.theme.styles
* → resolves the _styles_ based on rules implemented by Styles.
The component is also extended by Radium
which will flatten styles arrays injected in child components, and handle the:hover
state required to handle browser states such as .
One more trick Electrum.wrap() does is that it ensures that event handleronChange
methods (e.g. or handleFoo) get properly bound to the componentReact
instance. Therefore, event handlers can be passed to in a natural
way:
`javascript`
render () {
return Click me;
}
whereas normally, you would have to write this:
`javascript`
render () {
return Click me;
}
or do the binding manually in the constructor:
`javascript`
constructor () {
super ();
this.onClick = this.onClick.bind (this);
}
render () {
return Click me;
}
Electrum's autobinding looks for methods starting with on or handleonClick
and using _camel case_ (such as ); other methods won't bethis
automatically bound to .
See also the explanation on autobinding on the React blog.
Electrum can use a bus to dispatch messages/commands and notify changes.
The bus interface looks like this:
`javascript`
{
dispatch (props, message, payload) {}
notify (props, source, value, ...states) {}
}
A bus can be attached with Electrum.useBus(bus).
The default Electrum instance is configured to use electrum-events,
which injects various event handlers into the wrapped components:
* onChange → fires notification of type _change_onKeyDown
* , onKeyUp, onKeyPress → fire notifications ofonFocus
type _key-down_, _key-up_, _key-press_
* → fires notification of type _focus_onSelect
* → fires notification of type _select_
> Note: if the component provides its own event handlers, they will be
> called by the injected methods.
Events will automatically be sent to the bus, if one has been configured
(see Electrum.use). The EventHandlers class in electrum-events is
in charge of the event forwarding. It will provide the _value_ and the
_states_ associated with the underlying component, usually by reading
the DOM:
* source ← {type, event} where _type_ is the event namevalue
* ← event.target.valuestates
* ← {begin:0, end:10} for text fields
When the defaults are not meaningful (e.g. for a checkbox, where thegetValue()
_value_ does not exist per se), the component can provide the value
(method ) or the states (method getStates()):
`javascript`
class MyCheckbox extends React.Component {
render () {
return ... / />;
}
getValue (target) {
// The value will be 'on' or 'off', depending on the checked state
// of the target DOM node:
return target.checked ? 'on' : 'off';
}
}
The easiest way to get all components of a module wrapped is to use the
electrum-require-components module.
See electrum-require-components.
``
npm install electrum-require-components --save-dev
Edit package.json to add a script that can be invoked with npm run regenall.js
in order to regenerate the source file which includes, wraps and exports
all components.
``
"scripts": {
...
"regen": "electrum-require-components --wrap ./src components .component.js all.js"
}
To export all components found in your module, use:
`javascript`
export * from './all.js';
Electrum includes basic tracing functionality, which might come in handy when
live debugging wrapped components.
Whenever React calls a wrapped component's shouldComponentUpdate(), Electrum
will call the corresponding logging function:
`javascript`
import E from 'electrum';
E.configureLog ('shouldComponentUpdate', (component, nextProps, nextState, result) => { / ... / });
The arguments are:
* component → component instance.nextProps
* → next properties, as provided to shouldComponentUpdate.nextState
* → next state, as provided to shouldComponentUpdate.result
* → result of the call to shouldComponentUpdate, where true
means that the component should be rendered.
Components may need to represent their internal state as a collection of simple
state objects:
`javascript`
const fieldSelection = { from: 12, to: 17 }; // 'from,to'
const listSelection = { first: 5, active: 8 }; // 'active,first'
These state objects have _fingerprints_ which are based on their sorted
property names ('from,to', 'active,first'). It does not include theid
optional property.
The FieldStates class maintains an internal array of state objects.electrum-field
It is implemented in and made available by electrum
as a convenience.
* FieldStates.fingerprint (state) → the fingerprint of a state object.find (fingerprint)
* → the first state object which matches theundefined
specified fingerprint, or if none can be found.get ()
* → an immutable array of immutable state objects.
The instances are immutable. All methods which modify the internal array
of state objects will return a new instance (or the unchanged instance if
the update was a no-op). The original instance is never modified.
* add (state) → a new instance where the internal array of states hasadd (state1, state2, ...)
been updated by adding or replacing a state; matching is done based on the
state's fingerprint.
* → same as add() called multiple times.remove (fingerprint)
* → a new instance where the internal array of
states has been updated by removing the first state matching the specified
fingerprint.
Electrum prescribes how actions represent their specific state and
provides the Action class to inspect it:
* Action.isEnabled (state) → true if the state is enabled.Action.isDisabled (state)
* → true` if the state is disabled.