# Crop Sections (collapsible panels)
Links: [Home](../index.md)
A row of side-by-side panels ("sections") that fills its parent: fixed-width or fluid sections share the available width, each has a header and a scrolling body of articles, and any section collapses into a 48 px vertical tab with a CSS 3D rotation. Sections can be attached, detached and looked up by name; the layout is recomputed every 500 ms. Written by SGApps.IO.
## Loading the Module
```js
Application.require("elements/crop-sections").then(function (CropSections) {
var panels = CropSections();
document.getElementById('mount').appendChild(panels.node()); // fills #mount (100 % x 100 %) -- give #mount a height
var nav = panels.addSection({ name: 'Navigation', fixedWidth: 180 });
nav.header().innerHTML = 'Navigation';
nav.articles()[0].innerHTML = '
';
var main = panels.addSection({ name: 'Details' }); // fluid: shares the remaining width
main.header().textContent = 'Details';
});
```
**Dependencies:** `uri-load`, `request` (the two templates are fetched with `request().url(...).response('text')`), `extensions/prototype` (the `_()` chain in the section). The module links `elements/crop-sections/style.css` and, through the sub-module `elements/crop-sections/section.js`, `section/style.css`. The header article carries the theme class `rr-bg-regular` (a background colour in the RegalRoyal theme); define it yourself when the theme is not loaded.
## Markup
```html
panels.node() -- position: relative, 100 % x 100 %
panels.sectionsContainer() -- white-space: nowrap, overflow: hidden
section.node() -- inline-block, margin 10px 5px
sized by section.render(): width - 10, height - 20
section.content()
section.header() -- 28 px line, your title
section.body() -- overflow-y: auto, max-height = height - 60
section.articles()[0] -- your content (add more s)
...
```
## API
### `CropSections()`
Returns the **platform**, an `ApplicationPrototype`. A `setInterval` of 500 ms calls `render()` for the lifetime of the page (it is never cleared), so size changes of the parent and collapses are picked up automatically.
| Method | Returns | Description |
|---|---|---|
| `node()` | `HTMLElement` | The root `` |
| `sectionsContainer()` | `HTMLElement` | The `.crop-sections--container` the sections live in |
| `addSection([config], [detached])` | section | Builds a section from `config` (`{ name, fixedWidth, collapsed }`, all optional), attaches it unless `detached` is `true`, renders. Returns the section |
| `sections()` | section[] | A copy of the list of attached sections, in attach order |
| `section(name)` | section or `null` | The first attached section whose `config().name` matches |
| `render()` | -- | The layout pass: every attached section gets `height(nodeHeight - 25)`; the free width is `containerWidth - 25` minus the real width of every collapsed section and the `fixedWidth` of every fixed one, and is split equally among the remaining (fluid) sections with `width()` |
All methods carry the framework's `before` / `on` / `after` hooks. The platform also listens to a `section-collapsed` event on itself (re-render), but nothing in the module emits it -- `render()` runs on the interval instead.
### The section object
Built by `elements/crop-sections/section.js` and extended by `addSection()`:
| Method | Returns | Description |
|---|---|---|
| `node()` | `HTMLElement` | `.crop-sections--item-container` |
| `content()` | `HTMLElement` | `.crop-sections--item-content` |
| `header()` | `HTMLElement` | The `
` inside the `` -- put the title (and a collapse toggle) here |
| `body()` | `HTMLElement` | The inner `` (scrolls vertically) |
| `articles()` | `HTMLElement[]` | The `` children of the body (one by default; append more to `body()`) |
| `width([px])` | number | Setter (`parseInt`; `0` / `NaN` keeps the old value) + re-render; the platform sets it on every layout pass |
| `height([px])` | number | Same, set by the platform |
| `fixedWidth([px])` | number | `0` (default) = fluid; a positive number = fixed width in px, taken out of the free width |
| `collapsed([bool])` | boolean | Collapses / expands the section (`collapsed` class on `.crop-sections--item`: 48 px wide, header rotated 90 degrees, body hidden). The platform redistributes the width on its next pass (within 500 ms) |
| `render()` | -- | Applies width / height / collapsed to the DOM (margins 5 px horizontal, 10 px vertical; body `max-height = height - 60`) |
| `config()` | object | The configuration object passed to `addSection()` (live) |
| `name([name])` | string or `null` | Getter / setter of `config().name` |
| `platform()` | platform | The owning `CropSections()` instance |
| `attached()` | boolean | In the list **and** in the DOM |
| `attach()` | -- | Adds the section to the list and to the container (no-op when attached) |
| `detach()` | -- | Removes it from the list and from the DOM; the object stays usable, `attach()` brings it back |
`width`, `height`, `fixedWidth` and `collapsed` re-render the section through their `after*` hooks when called with an argument.
Two helper classes are honoured by the stylesheet inside the header: `.uncollapsed-hidden` (shown only while collapsed) and `.collapsed-hidden` (hidden while collapsed) -- use them for a toggle that changes its glyph.
## Use Cases
### A three-pane layout with a collapse toggle per section
```js
Application.require("elements/crop-sections").then(function (CropSections) {
var panels = CropSections();
document.getElementById('mount').appendChild(panels.node());
var addSection = function (name, fixedWidth) {
var section = panels.addSection({ name: name, fixedWidth: fixedWidth || 0 });
section.header().innerHTML = '' + name + ' ↔';
section.header().querySelector('.toggle').onclick = function () {
section.collapsed(!section.collapsed());
};
return section;
};
addSection('Navigation', 180);
addSection('Editor');
addSection('Preview');
});
```
### Keeping a detached section around
```js
var inspector = panels.addSection({ name: 'Inspector', fixedWidth: 260 }, true); // built, not shown
inspector.header().textContent = 'Inspector';
document.getElementById('toggle-inspector').onclick = function () {
if (inspector.attached()) inspector.detach();
else inspector.attach();
};
```
### Several articles in one section
```js
var section = panels.section('Details');
['Summary', 'History', 'Comments'].forEach(function (title) {
var article = document.createElement('article');
article.innerHTML = '' + title + '
';
section.body().appendChild(article); // articles() now returns four entries
});
```
## Notes
- The root node is `100 % x 100 %` of its parent and the height of every section is derived from it: mount the platform in an element with an explicit height.
- The layout is recomputed on a 500 ms interval, never on demand -- after `collapsed()`, `fixedWidth()`, `attach()` / `detach()` or a window resize, the new widths appear within half a second; call `panels.render()` yourself for an immediate update.
- The free width leaves 25 px for the container's own padding / borders; with only fixed and collapsed sections nothing is resized.
- The collapse animation is a 0.5 s CSS transition (`transform`, `width`); the collapsed tab shows the header text rotated and a dashed outline.
- Live demo: [playground](playground.html#module=crop-sections ':ignore :target=_blank').