# 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.