question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

As a developer, I can "add" or "override" admin bar items, piece filters, batch operations, single-piece operations, etc. as elegantly as fields

See original GitHub issue

The problem

When writing a new module that extends another module, merging in new schema fields is really easy in 3.x. But merging in other kinds of extensible things is as hard as ever.

What we did for schema fields

The new 3.x module format has a really nice pattern for adding and overriding schema fields, via special section in the module format:

module.exports = {
  extend: '@apostrophecms/piece-type',
  fields: {
    add: {
      color: {
        type: 'select',
        choices: [ ... ]
      }
    },
    remove: [ 'published' ]
  }
}

If other fields were inherited from a base class, it just merges: if a field has the same name it is replaced, otherwise it is added.

remove also behaves sensibly. If a subclass removes a field it’s gone; if a sub-subclass removes a second field, both fields are gone; if a sub-sub-subclass adds one of the fields back, it’s back… feels good.

You can even use a function for fields if you need to peep at options when deciding what fields to include.

What we used to have to do for fields (and still have to do for other things)

This replaced a pattern that was hard to get right in an extensible way:

// 2.x
module.exports = {
  beforeConstruct(self, options) {
    options.addFields = [
      {
        name: 'color',
        type: 'select',
        choices: [ ... ]
      }
    ].concat(options.addFields || []);
  }
};

If you didn’t do this special “beforeConstruct concat dance,” your addFields would cancel out the one in the base class or the one from your subclass. Bad news.

Examples of things we didn’t provide a solution for

  • Modules extending piece-type can have filters. There is a default filters option with the core stuff like published, and an addFilters option to avoid accidentally removing those. But this doesn’t help you write a module that can be subclassed with even more, unless you follow the “concat pattern” above.
  • Modules extending piece-type can have batchOperations. Same concern.
  • Modules extending piece-type can have custom operations on individual pieces too. There’s no provision for this in 3.x right now. In 2.x the pattern was a one-off.
  • There’s more: addImageSizes, addCsrfExceptions, addColumns. Probably others that didn’t follow this pattern but could have.

All of these have the same problem as addFields, but no improved solution in 3.x. They also all have a name, which suggests they can be solved the same way.

Proposed solution

  • All of these things, not just fields, should have sections in the module format. Let’s call them “cascades” for now.
  • They should work just like fields, i.e. with add, remove and group subsections. Although group is not necessary for all of them, there could be others that benefit from grouping.
  • We should not hard-code a list of these sections in apostrophe core. Instead, a module should be able to register one. Note that this would usually be done once by us in a core module that other people are extending, but not necessarily:
// in modules/@apostrophecms/doc-type/index.js
module.exports = {
  cascades: [ 'fields' ]
}
// in modules/@apostrophecms/piece-type/index.js
module.exports = {
  extend: '@apostrophecms/doc-type',
  cascades: [ 'filters', 'columns', 'operations', 'batchOperations' ]
  // Set up the default batch operations, etc.
  batchOperations: {
    add: {
      trash: {
        label: 'Trash'
      }
    }
  }
}
// in modules/@apostrophecms/attachment/index.js
module.exports = {
  cascades: [ 'imageSizes' ],
  imageSizes: {
    add: {
      // standard image sizes are born here
      'one-half': { ... },
      full: { ... }
    }
  }
}

Now here’s a project level modules/@apostrophecms/attachment/index.js tweaking the image sizes:

// in modules/@apostrophecms/attachment/index.js
module.exports = {
  imageSizes: {
    add: {
      'epic': { width: 5000, height: 5000 }
    },
    remove: [ 'one-half' ]
  }
}

And a custom piece type (might be project level, might be npm, whatever) that adds a “manage” filter:

// In products/index.js
module.exports = {
  extend: '@apostrophecms/piece-type',
  filters: {
    add: {
      color: {
        label: 'Color',
        multiple: true
      }
    }
  }
}

Notice that I do NOT have to re-declare cascades in project level stuff, or even in any pieces module that is just extending cascades that were already declared in the base class. Once it’s turned on, the subclasses get it for free.

What comes out the other end?

An important question is what the module sees at the end of this process.

Right now, we turn fields back into options.addFields and that gets turned into an array as self.schema. Basically… basically so I wouldn’t have to rewrite the schemas module. No better reason.

I think that was a mistake. I think fields should get turned into self.fields, which should be an object. batchOperations would get turned into self.batchOperations, which would be an object. Etc.

This doesn’t mean modules can’t turn that object into an array at the end if they want to, it’s just not done by the module format processor. What comes out should be an object.

(Note that the order of object properties is well defined in modern JavaScript, including Node.js and IE11+ browsers. At one point it was not.)

What do you think folks?

cc @stuartromanek @breyell @localghost443 @abea @livetodeliver @bgantick @bobclewell @ngranahan @falkodev

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:1
  • Comments:5 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
abeacommented, Aug 28, 2020

This sounds right to me. To make sure I understand, cascades would be declared in these base (and a few other) core modules, so normally it would never be used in projects? Maybe someone doing something really fancy would want a new cascading option, in which case they could add that cascades option with only the new option in it?

0reactions
boutellcommented, Sep 3, 2020

This was merged to 3.0.

Read more comments on GitHub >

github_iconTop Results From Across the Web

show_admin_bar | Hook - WordPress Developer Resources
Filters whether to show the admin bar. ... This filter is part of the function is_admin_bar_showing() . Note: The Admin Bar is replaced...
Read more >
Customizing (or Removing) the WordPress Admin Toolbar
That being said, in this article I will show you how to manage the WordPress toolbar, removing it for specific roles, adding links...
Read more >
The Nutanix Bible - Classic Edition
The Nutanix Bible - A detailed narrative of the Nutanix architecture, how the software and features work and how to leverage it for...
Read more >
DKAN Documentation
This is the central site for technical/developer documentation of DKAN ... It is possible to add new fields to DKAN to conform to...
Read more >
How to Hide WordPress Admin Bar: Ultimate Guide for 2022
It offers quick access to pages like Dashboard, Themes, Widgets, Menus, Customize, New Page, Edit Post, etc. Although, by default, all WordPress ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found