Component
Class defined in ui/Component.js#100

A light-weight component implementation inspired by React and Ember. In contrast to the large frameworks it does much less things automagically in favour of synchronous rendering and a minimalistic life-cycle. It also provides up-tree communication and dependency injection.

Why synchronous rendering?

Synchronous rendering, while it may seem less performant, is necessary because substance must render the model, after it has changed before the next change is triggered by the user.

Asynchronous rendering as it exists in React means that the UI will eventually catch up to changes in the model. This is not acceptable in substance because substance plays with contenteditable and thus, cursor positions, etc are maintained in the browser's DOM. If we went the async way, the cursor in the DOM would be briefly inconsistent with the cursor in the model. In this brief window, changes triggered by the user would be impossible to apply.

Concepts:

  • props are provided by a parent component. An initial set of properties is provided via constructor. After that, the parent component can call setProps or extendProps to update these properties which triggers rerendering if the properties change.

  • state is a set of flags and values which are used to control how the component gets rendered given the current props. Using setState the component can change its internal state, which leads to a rerendering if the state changes. Prefer using extendState rather than setState.

    Normally, a component maintains its own state. It isn't recommended that a parent pass in or update state. If you find the need for this, you should be looking at props.

    State would be useful in situations where the component itself controls some aspect of rendering. Eg. whether a dropdown is open or not could be a state within the dropdown component itself since no other component needs to know it.

  • A child component with a ref id will be reused on rerender. All others will be wiped and rerender from scratch. If you want to preserve a grand-child (or lower), then make sure that all anchestors have a ref id. After rendering the child will be accessible via this.refs[ref].

  • A component can send actions via send which are bubbled up through all parent components until one handles it. A component declares that it can handle an action by calling the handleActions method on itself in the constructor or the didUpdate lifecycle hook.

Lifecycle hooks

The RenderingEngine triggers a set of hooks for you to define behavior in various stages of the rendering cycle. The names are pretty self explanatory. If in doubt, please check out the method documentation below.

  1. Component#didMount
  2. Component#didUpdate
  3. Component#dispose
  4. Component#willReceiveProps
  5. Component#willUpdateState
Example

Define a component:

class HelloMessage extends Component {
  render() {
    return $$('div').append(
      'Hello ',
      this.props.name
    )
  }
}

And mount it to a DOM Element:

HelloMessage.mount({name: 'John'}, document.body)
new Component()
Constructor defined in ui/Component.js#108

Construcutor is only used internally.

Provides the context which is delivered to every child component. Override if you want to provide your own child context. Child context is available to all components rendered from within this component's render method as this.context.

Returns
Object

the child context

Example
class A extends Component {
...
  getChildContext() {
    // Optional, but useful to merge super's context
    return Object.assign({}, super.getChildContext(), {foo: 'bar'})
  }

  render($$) {
    return $$(B)
  }
}

class B extends Component {
  render($$) {
    // this.context.foo is available here
  }
}

Component

Override this within your component to provide the initial state for the component. This method is internally called by the RenderingEngine and the state defined here is made available to the Component#render method as this.state.

Returns
Object

the initial state

Provides the parent of this component.

Returns
Component

the parent component or null if this component does not have a parent.

Get the top-most Component. This the component mounted using ui/Component.mount

Returns
Component

The root component

Short hand for using labelProvider API

Example
render($$) {
  let el = $$('div').addClass('sc-my-component')
  el.append(this.getLabel('first-name'))
  return el
}

Get a component class for the component name provided. Use this within the render method to render children nodes.

Parameters
componentNameString

The component's registration name

maybeBoolean

if true then does not throw when no Component is found

Returns
Class

The ComponentClass

Example
render($$) {
  let el = $$('div').addClass('sc-my-component')
  let caption = this.props.node.getCaption() // some method that returns a node
  let CaptionClass = this.getComponent(caption.type)
  el.append($$(CaptionClass, {node: caption}))
  return el
}

Render the component.

ATTENTION: this does not create a DOM presentation but a virtual representation which is compiled into a DOM element later.

Every Component should override this method.

Parameters
$$Function

method to create components

Returns
VirtualNode

VirtualNode created using {@param $$}

Mount a component to the DOM.

Example
var app = Texture.mount({
  configurator: configurator,
  documentId: 'elife-15278'
}, document.body)

Determines if Component should be rendered again using ui/Component#rerender after changing props. For comparisons, you can use this.props and newProps.

The default implementation simply returns true.

Parameters
newPropsObject

The props are being applied to this component.

Returns

a boolean indicating whether rerender() should be run.

Rerenders the component.

Call this to manually trigger a rerender.

Triggers didMount handlers recursively.

Gets called when using component.mount(el) on an element being in the DOM already. Typically this is done for a root component.

If this is not possible because you want to do things differently, make sure you call 'component.triggerDidMount()' on root components.

Parameters
isMounted

an optional param for optimization, it's used mainly internally

Example
var frag = document.createDocumentFragment()
var comp = MyComponent.mount(frag)
...
$('body').append(frag)
comp.triggerDidMount()

Called when the element is inserted into the DOM. Typically, you can use this to set up subscriptions to changes in the document or in a node of your interest.

Remember to unsubscribe from all changes in the ui/Component#dispose method otherwise listeners you have attached may be called without a context.

Example
class Foo extends Component {
  didMount() {
    this.props.node.on('label:changed', this.rerender, this)
  }

  dispose() {
    // unless this is done, rerender could be called for a dead, disposed
    // component instance.
    this.props.node.off(this)
  }
}

Make sure that you call component.mount(el) using an element which is already in the DOM.

var component = new MyComponent()
component.mount($('body')[0])

Hook which is called after state or props have been updated and the implied rerender is completed.

Returns
boolean

indicating if this component has been mounted

Triggers dispose handlers recursively.

A hook which is called when the component is unmounted, i.e. removed from DOM, hence disposed. See ui/Component#didMount for example usage.

Remember to unsubscribe all change listeners here.

Send an action request to the parent component, bubbling up the component hierarchy until an action handler is found.

Parameters
action

the name of the action

...

arbitrary number of arguments

Returns
Boolean

true if the action was handled, false otherwise

Define action handlers. Call this during construction/initialization of a component.

Parameters
actionHandlerObject

An object where the keys define the handled actions and the values define the handler to be invoked.

These handlers are automatically removed once the Component is disposed, so there is no need to unsubscribe these handlers in the {@link ui/Component#dispose} hook.

Example
class MyComponent extends Component {
  constructor(...args) {
    super(...args)
    this.handleActions({
     'openPrompt': this.openPrompt,
     'closePrompt': this.closePrompt
    })
  }
}

Define an action handler. Call this during construction/initialization of a component.

Parameters
actionString

name

aFuncton

function of this component.

Get the current component state

Returns
Object

the current state

Sets the state of this component, potentially leading to a rerender. It is better practice to use ui/Component#extendState. That way, the code which updates state only updates part relevant to it.

Eg. If you have a Component that has a dropdown open state flag and another enabled/disabled state flag for a node in the dropdown, you want to isolate the pieces of your code making the two changes. The part of your code opening and closing the dropdown should not also automatically change or remove the enabled flag.

Note: Usually this is used by the component itself.

Parameters
newStateobject

an object with a partial update.

This is similar to setState() but extends the existing state instead of replacing it.

Parameters
newStateobject

an object with a partial update.

Called before state is changed.

Get the current properties

Returns
Object

the current state

Sets the properties of this component, potentially leading to a rerender.

Parameters
anobject

object with properties

Extends the properties of the component, without necessarily leading to a rerender.

Parameters
anobject

object with properties

Hook which is called before properties are updated. Use this to dispose objects which will be replaced when properties change.

For example you can use this to derive state from props.

Parameters
newPropsobject