# apostrophe-areas

# Inherits from: apostrophe-module

# apos.areas

An area is a series of zero or more widgets, in which users can add and remove widgets and drag them to reorder them. This module implements areas, including calling the loader methods of widgets that have them whenever a doc containing areas is fetched, via an extension to apostrophe-cursors. This module also provides browser-side support for invoking the players of widgets in an area and for editing areas.

# Methods

# setWidgetManager(name, manager) [api]

Set the manager object for the given widget type name. The manager is expected to provide sanitize, output and load methods. Normally this method is called for you when you extend the apostrophe-widgets module, which is recommended.

# getWidgetManager(name) [api]

Get the manager object for the given widget type name.

# warnMissingWidgetType(name) [api]

Print warning message about a missing widget type — only once per run per type.

# renderArea(area, options) [api]

Render the given area object via area.html, passing the specified options to the template. Called for you by the apos.area and apos.singleton template helpers.

# sanitizeItems(req, items, callback) [api]

Sanitize an array of items intended to become the items property of an area. Invokes the sanitize method for each widget's manager. Widgets with no manager are discarded. Invoked for you by the routes that save areas and by the implementation of the area schema field type.

If the sanitize method of a widget manager reports a string error, this method will report a string error like "5.required", where 5 is the index of the widget in the area and required is the string error from the widget.

# renderWidget(req, type, data, options, callback) [api]

Renders markup for a widget of the given type. The actual content of the widget is passed in data. The callback is invoked with (null, html) on success. Invoked by the render-widget route, which is used to update a widget on the page after it is saved.

# saveArea(req, docId, dotPath, items, callback) [api]

Update or create an area at the specified dot path in the document with the specified id, if we have permission to do so. The change is saved in the database before the callback is invoked. The items array is NOT sanitized here; you should call sanitizeItems first. Called for you by the save-area route.

# lockSanitizeAndSaveArea(req, areaInfo, callback) [api]

Lock, sanitize and save the area described by areaInfo on behalf of req.

areaInfo must have items, docId and dotPath parameters. For bc, if req.htmlPageId is not present then advisory locking is not performed.

Note that the area is not unlocked. This method is designed for autosave operations, which continue until the page is unloaded, at which time the save-areas-and-unlock route will be accessed.

This method performs sanitization of all properties of areaInfo before trusting it, so passing req.body is a safe thing to do.

# walk(doc, iterator) [api]

Walk the areas in a doc. Your iterator function is invoked once for each area found, and receives the area object and the dot-notation path to that object. note that areas can be deeply nested in docs via array schemas.

If the iterator explicitly returns false, the area is removed from the page object, otherwise no modifications are made. This happens in memory only; the database is not modified.

# getSchemaOptions(doc, name) [api]

If the schema corresponding to the given doc's type property has an options property for the given field name, return that property. This is used to conveniently default to the options already configured for a particular area in the schema when working with apostrophe-pieces in a page template.

# richText(within, options) [api]

Returns the rich text markup of all rich text widgets within the provided doc or area, concatenated as a single string.

By default the rich text contents of the widgets are joined with a newline between. You may pass your own options.delimiter string if you wish a different delimiter or the empty string. You may also pass an HTML element name like div via options.wrapper to wrap each one in a <div>...</div> block. Of course, there may already be a div in the rich txt (but then again there may not).

Also available as a helper via apos.areas.richText(area, options) in templates.

Content will be retrieved from any widget type that supplies a getRichText method.

# plaintext(within, options) [api]

Returns the plaintext contents of all rich text widgets within the provided doc or area, concatenated as a single string.

By default the rich text contents of the various widgets are joined with a newline between. You may pass your own options.delimiter string if you wish a different delimiter or the empty string.

Whitespace is trimmed off the leading and trailing edges of the string, and consecutive newlines are condensed to one, to better match reasonable expectations re: text that began as HTML.

Pass options.limit to limit the number of characters. This method will return fewer characters in order to avoid cutting off in mid-word.

By default, three periods (...) follow a truncated string. If you prefer, set options.ellipsis to a different suffix, which may be the empty string if you wish.

Also available as a helper via apos.areas.plaintext(area, options) in templates.

Content will be retrieved from any widget type that supplies a getRichText method.

# fromPlaintext(plaintext, options) [api]

Very handy for imports of all kinds: convert plaintext to an area with one apostrophe-rich-text widget if it is not blank, otherwise an empty area. null and undefined are tolerated and converted to empty areas. Takes an option el if you wish to specify a wrapper element. Ex: fromPlaintext(text, { el: 'p' }).

# fromRichText(html) [api]

Convert HTML to an area with one 'apostrophe-rich-text' widget, otherwise an empty area. null and undefined are tolerated and converted to empty areas.

# modulesReady() [api]

When all modules are ready and all widget managers therefore should have been added, determine the list of rich text widgets for purposes of the apos.areas.richText() method which returns just the rich text of the area

# loadDeferredWidgets(req, callback) [api]

Load widgets which were deferred until as late as possible. Only comes into play if req.deferWidgetLoading was set to true for the request. Invoked after the last pageBeforeSend handler, and also at the end of the apostrophe-global middleware.

# widgetControlGroups(req, widget, options) [api]

This method is called when rendering widgets in an area, to add top-level controls to them, such as the movement arrows and the edit pencil. It can be extended to add more controls in a context-sensitive way, or configured via the addWidgetControlGroups option (see the source, TODO: document the format in detail)

# addSchemaWidgetControls(req, widget, controlGroups) [api]

Adds any schema fields of the widget marked with widgetControls: true as dropdowns amongst the widget's in-context inline controls. Currently only supported for "select" and "checkboxes" fields. Here a "checkboxes" field is visually represented in a more compact way using a multiple-select dropdown.

# isEmpty(doc, name) [api]

Returns true if the named area in the given doc is empty.

Alternate syntax: { area: doc.areaname, ... more options }

An area is empty if it has no widgets in it, or when all of the widgets in it return true when their isEmpty() methods are interrogated. For instance, if an area only contains a rich text widget and that widget. A widget with no isEmpty() method is never empty.

# pageServe(req) [protectedApi]

When a page is served to a logged-in user, make sure the session contains a blessing for every join configured in schemas for widgets

# docBeforeUpdate(req, doc, options) [protectedApi]

# restoreDisallowedFields(req, item, oldItem) [protectedApi]

Restore the original values of any fields present in the schema for a widget but disallowed for this particular editor due to permissions.

The original values are copied from oldItem. To save you an "if" statement, if oldItem is null (commonplace if the widget is new), nothing happens.

If the field type has a copy method it is used. Otherwise, custom logic handles join fields, and the rest are copied by simple assignment to the named field.

# editVirtualArea(req, items, options, callback) [routes]

Implementation detail of the edit-virtual-area and edit-virtual-areas routes.

# pageBeforeSend(req) [browser]

# getCreateSingletonOptions(req) [browser]

# Nunjucks template helpers

# singleton(doc, name, type, _options)

apos.singleton renders a single widget of a fixed type, standing alone in the page. The _options object is passed to the widget.

A singleton is just a special case of an area, so you can change your mind later and call apos.area with the same name.

The name property distinguishes this singleton from other areas in the same doc.

If _options.addLabel is set, that text appears on the button to initially populate the singleton. If _options.editLabel is set, that text appears on the button edit an existing widget in the singleton.

If _options.controls.movable is false, the widget may not be dragged out of the singleton.

If _options.controls.removable is false, the widget may not be removed entirely.

If _options.controls.position is set to top-left, top-right, bottom-left or bottom-right, the widget controls (edit, drag and remove) are positioned accordingly.

If _options is not specified, Apostrophe falls back to the options configured for the given field name in the schema for this type of doc. For ordinary pages there usually won't be any, but this is very convenient when working with apostrophe-pieces.

Alternate syntax: { area: doc.areaname, type: type, ... more options }

# area(doc, name, _options)

apos.area renders an area: a column of widgets of one or more types.

If present The _options object must contain a widgets property, an object which must at least contain a property by the name of each allowed widget. The corresponding value should be an object, and is passed on as options to widgets of that type appearing in this area.

If blockLevelControls: true is present, the controls for this area are given extra vertical space and rendered in a distinct color. These are affordances to ensure the user can clearly distinguish the controls of an area used for layout, i.e. an area that contains "two column" and "three column" widgets containing areas of their own.

The name property distinguishes this area from other areas in the same doc.

The limit option may be used to limit the number of widgets allowed.

For every widget in _options.widgets, you can pass the same options as in apos.singleton. See the documentation above for addLabel, controls.movable, controls.removable and controls.position. Note that addLabel normally does not actually begin with Add in areas, as opposed to in singletons.

If _options is not specified, Apostrophe falls back to the options configured for the given field name in the schema for this type of doc. For ordinary pages there usually won't be any, but this is very convenient when working with apostrophe-pieces.

Alternate syntax: { area: doc.areaname, ... more options }

# widget(widget, options)

apos.areas.widget renders one widget. Invoked by both apos.area and apos.singleton. Not often called directly, but see area.html if you are interested in doing so.

# richText(within, options)

Returns the rich text markup of all apostrophe-rich-text widgets within the provided doc or area, concatenated as a single string. In future this method may improve to return the content of other widgets that consider themselves primarily providers of rich text, such as subclasses of apostrophe-rich-text, which will not be regarded as a bc break. However it will never return images, videos, etc.

By default the rich text contents of the widgets are joined with a newline between. You may pass your own options.delimiter string if you wish a different delimiter or the empty string. You may also pass an HTML element name like div via options.wrapper to wrap each one in a <div>...</div> block. Of course, there may already be a div in the rich txt (but then again there may not).

Content will be retrieved from any widget type that supplies a getRichText method.

# plaintext(within, options)

Returns the plaintext contents of all rich text widgets within the provided doc or area, concatenated as a single string.

By default the rich text contents of the various widgets are joined with a newline between. You may pass your own options.delimiter string if you wish a different delimiter or the empty string.

Pass options.limit to limit the number of characters. This method will return fewer characters in order to avoid cutting off in mid-word.

By default, three periods (...) follow a truncated string. If you prefer, set options.ellipsis to a different suffix, which may be the empty string if you wish.

Content will be retrieved from any widget type that supplies a getRichText method.

# widgetControlGroups(widget, options)

Output the widget controls. The addWidgetControlGroups option can be used to append additional control groups. See the widgetControlGroups method for the format. That method can also be extended via the super pattern to make the decision dynamically.

# isEmpty(doc, name)

Returns true if the named area in the given doc is empty.

Alternate syntax: { area: doc.areaname, ... more options }

An area is empty if it has no widgets in it, or when all of the widgets in it return true when their isEmpty() methods are interrogated. For instance, if an area only contains a rich text widget and that widget contains no markup or text, this function will return true. A widget with no isEmpty() method is never empty.