1

In an ApostropheCMS application, we have a piece type "book". The front-end users are able, from the CMS, to update fields declared in the piece's index.js.

We need to dynamically add or update a field once the user saves, namely a citations field. We use Citation.js to generate MLA, Chicago, etc. citations according to what the editor has entered in the CMS.

We don't want to make this field visible in the CMS, because it needs to always be overwritten by the Citation.js generated results. (If there is a way to add a field and hide it from the CMS, that would be a great solution!).

Our current idea is to add the field (if it's missing) or update it (if it exists) on save:

(mostly) pseudo code
self.on('apostrophe-docs:afterSave', 'updateBook', async (req) => {
  const { piece } = req;

  // fetch citations
  const citations = { ... };

  // create update piece
  const updated = _.cloneDeep(piece);
  updated.citations = citations;

  // check if citations field already present
  if (!('citations' in piece)) {
    // add citations field

    // should method be different if field doesnt exist yet?
    self.update(req, updated);

  } else {
    // check when citations were last updated to ensure enough time diff to update

    // update citations field if all is well
    self.update(req, updated);
  }
});

As expected, this currently creates an infinite loop because 'apostrophe-docs:afterSave' is called after calling self.update.

  • Is there a way to pass a param to prevent that callback?
    • Otherwise, we were thinking about checking when the last update() occurred, any better suggestion?
  • Does update() not add fields that are on the piece passed to the method? Does it only care about fields defined in the index.js?

Any suggestion on how to achieve that is welcome.

jansensan
  • 633
  • 1
  • 8
  • 23

1 Answers1

0

beforeSave is more likely what you should use. You won't need to call .update() if you simply add the information onto the piece before it's actually saved to the db.

To your question about visibility, you don't need to add document properties to the piece schema to be able to save them. Fields need to be in the piece schema to be edited or viewed in the UI (even if set to readOnly: true).

So in the construct step you could add something like:

self.on('books:beforeSave', 'populateCitation');

self.populateCitation = function (req, piece, options) {
  // Being extra safe here.
  if (piece.type !== 'book') {
    return;
  }
  // Let's pretend `bookInfo` 
  if (piece.bookInfo) {
    // `getCitation` would be a method using Citation.js
    const citationInfo = getCitation(piece.bookInfo);
    piece.citation = citationInfo;
  }
};

You can then read the citation property on the document in code and (I'm pretty sure) in the template as well, if it is present (make sure to check for it in the template before printing).

alexbea
  • 1,311
  • 14
  • 25
  • 1
    Excellent, it did exactly what was needed! Now, since we have more than 50 items to update, is there a way to force commit? No change needs to be made in the data of the pieces, so commits say "no changes made" and dont work... Or should that be another SO question? – jansensan Nov 16 '20 at 19:54
  • Probably another SO question or one for Discord: https://chat.apostrophecms.com/. I don't fully grok the user flow, but my quick thought would be to check out the `commit` and `batchCommit` routes: https://github.com/apostrophecms/apostrophe-workflow/blob/main/lib/routes.js#L8-L27 – alexbea Nov 16 '20 at 20:44