# apostrophe-module

This "module" is the base class for all other modules. This module is never actually configured and used directly. Instead all other modules extend it (or a subclass of it) and benefit from its standard features, such as asset pushing.

New methods added here should be lightweight wrappers that invoke an implementation provided in another module, such as apostrophe-assets, with sensible defaults for the current module. For instance, any module can call self.render(req, 'show', { data... }) to render the views/show.html template of that module.

TODO: wrapper for adding command-line tasks. In the meantime it is recommended that you always use your module's name and a colon as the prefix for a task name when calling self.apos.tasks.add.

# Methods

# emit(*name *, , arg1, arg2...) [events]

Emit an event from this module. Returns a promise.

The promise will not resolve until all of the event handlers have resolved, in the order they were registered. Note that it is OK for event handlers to return a simple value rather than a promise, in which case they resolve immediately.

Any extra parameters passed after name are passed to the event handlers as parameters, in the order given.

See the on method.

# on(name, methodName, fn) [events]

Register an event handler method in this module. The given method name will be invoked when the given event name is emitted with emit. As a shortcut, you may optionally pass a function as a third argument. That function becomes a method of your module called methodName. This is exactly the same as defining it the normal way.

Your method may return a promise. If it does, the next event handler method will not begin running until your promise resolves. If your promise rejects, the chain stops and the promise returned by the emit method also rejects.

"What about events of other modules?" Register them with this name syntax: module-name:methodName. This is similar to the "cross-module includes" syntax used elsewhere in Apostrophe.

"Why do I need a method name? Why not a function alone?" It should always be possible for subclasses to intentionally override or extend your method via the super pattern.

"Why can't I use a methodName that is identical to the event name?" Doing so sets you up for an accidental collision with other event handlers in subclasses. Your method name should describe what your method does in response to the event.

# callAllAndEmit(callAllName, eventName, callback, argument, ...) [events]

Invoke a callAll method and emit a new-style promise event. callAll is invoked first, and if its callback does not receive an error, emit is invoked. When the promise returned by emit resolves, the final callback is invoked. A promise interface is not provided here because this method should only be used to migrate away from legacy callAll invocations. New code should always emit a promise event and avoid callAll.

# route(method, path, responder, fn)

Add an Express route to apos.app. The path argument is appended to the "action" of this module, which is /modules/modulename/.

Calling this method again allows routes to be overridden, which you normally can't do in Express.

Syntax:

self.route('post', 'edit-monkey', function(req, res) { ... })

That is roughly equivalent to:

self.apos.app.post(self.action + '/edit-monkey', function(req, res) { ... })

You can also pass middleware in the usual way, after the path argument. Note that some standardized optional middleware is available to pass in this way, i.e. self.apos.middleware.files for file uploads.

The omittable responder argument is a string containing the name of a method of this module used to send a response when given (req, err, value). It is normally provided for you by a call to self.apiRoute or self.htmlRoute. It is not invoked at all unless the route invokes its third argument, next(), with an error (or null) and an optional value argument. next() cannot be used in a route without a responder. (It behaves as normal for Express in a middleware function.)

# apiRoute(method, path, fn)

Similar to route, except that the route function receives (req, res, next), and you may pass next either an error or (null, result) where result is a JSON-friendly object to be sent to the browser; a status: 'ok' property is automatically added.

An exception: if result is an array, it is sent without a status property. This loophole is needed because arrays cannot have extra properties in JSON and certain legacy APIs send back arrays without an enclosing object.

If you do pass an error to next, the status code is still 200 but the browser receives an object whose status property is not ok. If the error is a string, it is sent as the status, otherwise the error is logged and the status is simply error to avoid revealing information that could be used maliciously.

An exception: mongodb errors relating to duplicate keys are sent as the status unique, which is useful for suggesting to users that they may need to use a different username, etc.

If you require additional properties for the JSON object sent for an error, you can pass an object containing them as the third argument to next. This is separate from the success value to avoid accidentally disclosing information on errors.

In addition, if req.errorMessages is set, it is passed as the messages property of the JSON object. This is a helpful way to accommodate percolating detailed error messages up from a nested function call through a stack that otherwise only accommodates simple string errors.

# htmlRoute(method, path, fn)

Similar to route, except that the route function receives (req, res, next), and you may pass next either an error or (null, result) where result is an HTML string to be sent to the browser; a 200 status is automatically used.

If you do pass an error to next, and the error is a string, the string is converted to an error code according to the following rules: notfound => 404, invalid => 401, forbidden => 403, error => 500, anything else => 500. If the error is not a string, it is converted to a 500 error and logged for the developer on the server side only, to avoid revealing information that could be used maliciously.

# renderRoute(method, path, fn)

Similar to htmlRoute, except that the route function receives (req, res, next), and you may pass next either an error or (null, result) where result is an object with a template property naming a template in the current module, and optionally a data property containing an object to be passed to the template. The result of template rendering is sent directly to the client, with a 200 status code.

If you do pass an error to next, and the error is a string, the string is converted to an error code according to the following rules: notfound => 404, invalid => 401, forbidden => 403, error => 500, anything else => 500. If the error is not a string, it is converted to a 500 error and logged for the developer on the server side only, to avoid revealing information that could be used maliciously.

# apiResponder(req, err, result, extraError)

See apiRoute for details. You should not call this directly.

# htmlResponder(req, err, result)

See htmlRoute for details. You should not call this directly.

# renderResponder(req, err, result)

See renderRoute for details. You should not call this directly.

# addHelpers(*object *, or name, value)

Add nunjucks template helpers in the namespace for our module. Typically called with an object in which each property is a helper name and each value is a helper function. Can also be called with name, function to add just one helper.

# addHelperShortcut(name)

Add a nunjucks template helper to the global namespace. This should be used very sparingly, and pretty much never in npm modules. The only exceptions in apostrophe core are apos.area and apos.singleton.

The helper must be added first with addHelpers.

# pushAsset(type, name, options)

Push an asset to the browser. type may be script or stylesheet. The second argument is the name of the file, without the extension.

For stylesheets, if name is editor, then public/css/editor.css is pushed. If public/css/editor.less exists it is compiled as needed to create the CSS version.

For scripts, if name is editor, then public/js/editor.js is pushed.

For both scripts and stylesheets, if the module is subclassed, and the file exists in both the parent module and the subclass, both files are pushed, in that order.

If options.when is set to always or not specified, the asset is included in every page regardless of whether the user is logged in. If options.when is set to user, it is included only if the user is logged in.

# render(req, name, data)

Render a template. Template overrides are respected; the project level lib/modules/modulename/views folder wins if it has such a template, followed by the npm module, followed by its parent classes. If you subclass a module, your version wins if it exists.

You MUST pass req as the first argument. This allows internationalization/localization to work. If you are writing a Nunjucks helper function, use self.partial instead. This method is primarily used to implement routes that respond with HTML fragments.

All properties of data appear in Nunjucks templates as properties of the data object. Nunjucks helper functions can be accessed via the apos object.

If not otherwise specified, data.user and data.permissions are provided for convenience.

The data argument may be omitted.

# renderAndSend(req, name, data)

Similar to render, but sends the response to the client, ending the request. If an exception is thrown by self.render, the error is properly logged and a 500 error is correctly sent to the client. Convenient in routes that simply send the markup for a modal, for instance, especially if for legacy reasons they must use self.route rather tha self.renderRoute.

# partial(name, data)

For use in Nunjucks helper functions. Renders a template, in the context of the same request that started the original call to Nunjucks. Otherwise the same as render.

# renderString(req, s, data)

Render a template in a string (not from a file), looking for includes, etc. in our preferred places.

Otherwise the same as render.

# partialString(req, s, data)

For use in Nunjucks helper functions. Renders a template found in a string (not a file), in the context of the same request that started the original call to Nunjucks. Otherwise the same as partial.

# renderer(name, data)

Returns a function that can be used to invoke self.render at a later time. The returned function must be called with req. You may pass data now and also when invoking the function; data passed now serves as defaults for the object passed later.

# partialer(name, data)

Returns a function that can be used to invoke self.partial at a later time. You may pass data now and also when invoking the function; data passed now serves as defaults for the object passed later.

# renderPage(req, template, data)

TIP: you probably want self.sendPage, which loads data.home for you and also sends the response to the browser.

This method generates a complete HTML page for transmission to the browser. Returns HTML markup ready to send (but self.sendPage is more convenient).

If template is a function it is passed a data object, otherwise it is rendered as a nunjucks template relative to this module via self.render.

data is provided to the template, with additional default properties as described below.

Depending on whether the request is an AJAX request, outerLayout is set to:

apostrophe-templates:outerLayout.html

Or:

apostrophe-templates:refreshLayout.html

This allows the template to handle either a content area refresh or a full page render just by doing this:

{% extend outerLayout %}

Note the lack of quotes.

Under the following conditions, refreshLayout.html is used in place of outerLayout.html:

req.xhr is true (always set on AJAX requests by jQuery) req.query.xhr is set to simulate an AJAX request req.decorate is false req.query.apos_refresh is true

These default properties are provided on the data object in nunjucks:

data.user (req.user) data.query (req.query) data.permissions (req.user._permissions) data.calls (javascript markup to insert all global and request-specific calls pushed by server-side code)

# sendPage(req, template, data)

This method generates and sends a complete HTML page to the browser.

If template is a function it is passed a data object, otherwise it is rendered as a nunjucks template relative to this module via self.render.

data is provided to the template, with additional default properties as described below.

outerLayout is set to:

apostrophe-templates:outerLayout.html

Or:

apostrophe-templates:refreshLayout.html

This allows the template to handle either a content area refresh or a full page render just by doing this:

{% extend outerLayout %}

Note the lack of quotes.

Under the following conditions, refreshLayout.html is used in place of outerLayout.html:

req.xhr is true (always set on AJAX requests by jQuery) req.query.xhr is set to simulate an AJAX request req.decorate is false req.query.apos_refresh is true

These default properties are provided on the data object in nunjucks:

data.user (req.user) data.query (req.query) data.permissions (req.user._permissions) data.calls (javascript markup to insert all global and request-specific calls pushed by server-side code) data.home (basic information about the home page, usually with ._children)

First, pageBeforeSend is invoked on every module that has such a method. It receives req and an optional callback, and can modify req.data.

# pushCreateSingleton(req, when)

Push a browser call to instantiate an object with the same moog type name as this module on the browser side, passing the given options to the constructor. The singleton is given the same alias on the apos object as it has on the server, if any, otherwise it is still available via apos.modules[full-module-name].

This method calls getCreateSingletonOptions(req) to determine what options object to pass to the browser-side singleton.

If the req option is omitted or null, pushBrowserCall is used; this is adequate if you don't need different options depending on the current user. getCreateSingletonOptions() will also get an undefined argument in this case.

If req is given, req.browserCall is used, and the singleton is created only once per page lifetime in the browser, even if an apos.change event would otherwise cause it to be created again.

If req is given and when is not, the singleton is always created; it is assumed that you are def the request to decide if it is needed before calling.

If when is given, the singleton is created only for the specified scene (always or user).

If neither is given, the singleton is created only for the user scene (a user is logged in, or req.scene has been set to user).

This method is not called automatically. Invoke it if your module actually needs a singleton on the browser side. Defining the moog type for that singleton is up to you (hint: look at various user.js files).

# getCreateSingletonOptions(req)

pushCreateSingleton calls this method to find out what options should be passed to the singleton it creates on the browser side. These must be in the form of a JSON-friendly object. By default, the action property is passed as the sole option, which is sometimes sufficient.

# defineRelatedType(tool, options)

Define a new moog type related to this module, autoloading its definition from the appropriate file. This is very helpful when you want to define another type of object, other than the module itself. Apostrophe uses this method to define database cursor types related to modules. The name of the related type will be the name of the module, followed by a hyphen, followed by the value of tool. The definition of the type will be automatically loaded from the lib/tool.js file of the module (substitute the actual tool parameter for tool, i.e. cursor.js). This is done recursively for all modules that this module extends, whether or not they actually have a lib/tool.js file. If the file is missing, an empty definition is synthesized that extends the next "parent class" in the chain. If any of the types are already defined, execution stops at that point. For instance, if apostrophe-images has already called this as a subclass of apostrophe-pieces, then apostrophe-files will just define its own cursor, extending apostrophe-pieces-cursor, and stop. This prevents duplicate definitions when many types extend apostrophe-pieces. If options.stop is set to the name of an ancestor module, the chain stops after defining the related type for that module. For instance, the module apostrophe-files extends apostrophe-pieces, which extends apostrophe-doc-type-manager. So when that module calls:

self.defineRelatedType('cursor', {
  stop: 'apostrophe-doc-type-manager'
});

We get: apostrophe-files-cursor extends... apostrophe-pieces-cursor which extends... apostrophe-doc-type-manager-cursor which extends... apostrophe-cursor (because cursor.js for doc-type-manager says so)

# createRelatedType(tool, options, callback)

Create an object of a related type defined by this module. See defineRelatedType. A convenient wrapper for calling apos.create.

For instance, if this module is apostrophe-images, then self.createRelatedType('cursor', { options... }) will create an instance of apostrophe-images-cursor.

As usual with moog, the callback is required only if at least one construct, beforeConstruct or afterConstruct function takes a callback.

# getOption(req, dotPathOrArray, def)

A convenience method to fetch properties of self.options.

req is required to provide extensibility; modules such as apostrophe-workflow and apostrophe-option-overrides can use it to change the response based on the current page and other factors tied to the request.

The second argument may be a dotPath, as in:

(req, 'flavors.grape.sweetness')

Or an array, as in:

(req, [ 'flavors', 'grape', 'sweetness' ])

The optional def argument is returned if the property, or any of its ancestors, does not exist. If no third argument is given in this situation, undefined is returned.

Also available as a Nunjucks helper; often convenient to invoke as module.getOption. When calling the helper, the req argument is implied, just pass the path(s).

# logError(req, err)

Log the given error, with informative context based on the given request.

# email(req, templateName, data, options, callback)

Send email. Renders an HTML email message using the template specified in templateName, which receives data as its data object (literally called data in your templates, just like with page templates).

The nodemailer option of the apostrophe-email module must be configured before this method can be used. That option's value is passed to Nodemailer's createTransport method. See the Nodemailer documentation (opens new window).

A plaintext version is automatically generated for email clients that require or prefer it, including plaintext versions of links. So you do not need a separate plaintext template.

nodemailer is used to deliver the email. The options object is passed on to nodemailer, except that options.html and options.plaintext are automatically provided via the template.

In particular, your options object should contain from, to and subject. You can also configure a default from address, either globally by setting the from option of the apostrophe-email module, or locally for this particular module by setting the from property of the email option to this module.

If you need to localize options.subject, you can call req.__ns('apostrophe', subject).

The callback receives (err, info), per the Nodemailer documentation. With most transports, lack of an err indicates the message was handed off but has not necessarily arrived yet and could still bounce back at some point.

If you do not provide a callback, a promise is returned.

# addTask(name, usage, fn)

Add an Apostrophe command line task to your module. The command line syntax will be:

node app name-of-module:name

Where name is the name argument given here (use hyphens). The usage message is printed if the user asks for help with the task.

fn is invoked with (apos, argv, callback). You may return a promise, in which case you must not invoke callback.

To carry out actions requiring req in your code, call self.apos.tasks.getReq to get a req with unlimited admin permissions.