One of the core tenents of HCJ is that it should be easy to define new and custom components and layouts. Again, HCJ is fundamentally just an interface: any functions that implement this interface can interoperate together. There are some things that virtually all components will do exactly the same way, so we provide a helper method, hcj.component.component, for defining new components.
component :: (String? , BuildComponent) - gt; Component
Where BuildComponent is defined as:
type BuildComponent = (Node, Context, MeasureWidth, MeasureHeight) - gt; {minWidth :: Stream Number?, minHeight :: Stream (Number - gt; Number)?, remove :: Function?}
The component function takes two arguments: an optional tag name, followed by a build method. If the tag name is not specified, it defaults to div. The build method initializes the component, and provides its minimum dimensions. It is passed four arguments: first el, the created root node of the component, and context, the context as it was passed into the component. The remaining two arguments are a MeasureWidth function and a MeasureHeight function, as explained below. It returns a "pre-instance", which the component function upgrades to a full instance.
The build method can provide the minimum dimensions one of two ways. It can either call the MeasureWidth and MeasureHeight functions that it is passed, or (perhaps more commonly) return an object with minWidth and minHeight properties. If it calls MeasureWidth, its width is measured by cloning the root element, appending it to an invisible sandbox, and reading off various computed DOM styles, and this number is pushed into its minWidth stream. Similarly, if it calls MeasureHeight, a function is pushed into its minHeight stream that clones the element, appends it to an invisible sandbox, sets its width, measures its height at that width, and returns that height.
Example:
// nbsp;SomeCaptcha nbsp;component
nbsp;
var nbsp;c nbsp;= nbsp;hcj.component;
var nbsp;stream nbsp;= nbsp;hcj.stream;
nbsp;
var nbsp;captcha nbsp;= nbsp;c.component(function nbsp;(el, nbsp;context, nbsp;mw, nbsp;mh) nbsp;{
nbsp; nbsp;mw();
nbsp; nbsp;mh();
nbsp; nbsp;var nbsp;someCaptcha nbsp;= nbsp;SomeCaptcha.render(el);
nbsp; nbsp;someCaptcha.promise.then(function nbsp;() nbsp;{
nbsp; nbsp; nbsp; nbsp;mw();
nbsp; nbsp; nbsp; nbsp;mh();
nbsp; nbsp;});
nbsp; nbsp;return nbsp;{
nbsp; nbsp; nbsp; nbsp;remove: nbsp;function nbsp;() nbsp;{
nbsp; nbsp; nbsp; nbsp; nbsp; nbsp;someCaptcha.remove();
nbsp; nbsp; nbsp; nbsp;},
nbsp; nbsp;};
});
The width and height are measured when the captcha component is first rendered, yielding 0 for each. The 3rd party captcha is rendered into the component's root element. Once the captcha is fully rendered, the width and height are measured again. Whenever the instance is removed, the captcha is removed first.