Search code examples
apostrophe-cms

Adding custom parameters to a widget


Lets say we created a simple widget which may have a string or a richtext field. When I insert the widget into the html, I would like to specify which field to be visible/active. In other words I'd like to add some custom options to the widget, which will change its behavior. The widget looks like this:

{{
    apos.singleton(data.page, 'footerTitle', 'footerTitle', {
        controls: {
            movable: false,
            position: 'top-right'
        }
    })
}}

And I'd like to do something like this:

{{
    apos.singleton(data.page, 'footerTitle', 'footerTitle', {
        controls: {
            movable: false,
            position: 'top-right'
        },
        settings: {
            type: 'string',
            anotherParameter: 1,
            thirdParameter: false
        }
    })
}}

Later (inside the widget's index.js) these parameters to be handled accordingly:

...
beforeConstruct: function( self, options )
{
    if (type === 'string')
    {
        // Do some additional setup, magic, whatever...
    }
    switch (anotherParamter)
    {
        // Add some insane code here :)
    }
},
construct: function( self, options )
    {
        const superLoad = self.load;
        self.load = ( req, widgets, callback ) => superLoad( req, widgets, ( err ) =>
        {
            if( err )
            {
                return callback( err );
            }

            for( const widget of widgets )
            {
                // Some additional work here based on the initial settings.
            }

            return callback( null );
        } );
    }
...

Is there any possibility to add custom parameters, options to a widget (in version 2.98.1 or later in 3.0.0)?


Solution

  • This is not possible in the way you've shown here in Apostrophe 2.x because widget options are passed from the template, and the template can only do synchronous work. The widget module's load method has already been run at this point. So it is too late to influence how things are fetched.

    However, there is a way to get the effect you want. What we do in this situation is create subclasses of the widget, and use the appropriate subclass in each place. Alternatively, we sometimes use a select element in the widget's schema.

    In both cases, you can then modify the behavior of load to suit the situation.

    For the subclassing approach, one can write a second module that extends the first:

    // in lib/modules/subclass-widgets
    module.exports = {
      name: 'subclass',
      extend: 'original-widgets',
      construct: function(self, options) {
        var superLoad = self.load;
        self.load = function(req, widgets, callback) {
          // ... we can call superLoad if we want or completely replace it
        };
      }
    }
    

    In the template you then give the option of original, subclass or both.

    For the select field alternative, you can use just a single widget type:

    // in lib/modules/cool-widgets/index.js
    module.exports = {
      name: 'cool',
      addFields: [
        {
          name: 'subtype',
          label: 'Subtype',
          type: 'select',
          choices: [
            {
              label: 'One',
              value: 'one'
            },
            {
              label: 'Two',
              value: 'two'
            },
          ]
        }
      ]
      construct: function(self, options) {
        self.load = function(req, widgets, callback) {
          for (const widget of widgets) {
            if (widget.subtype === 'two') { ... }
          }
        };
      }
    

    The best choice depends on whether you are generally going to give the user the choice every time and the code doesn't diverge all that much (use the select element) or you want to limit what they can do in each template (use subclassing instead, and only put the subclass that matches the use case in each template).

    As for version 3.0, there will be ways to achieve what you originally asked in 3.0. For one thing, the output method is an async method in 3.x. That means you can override it and do asynchronous work with the options. For another, 3.x supports async components in templates:

    https://github.com/apostrophecms/apostrophe/issues/1668

    (Closed because it is implemented in the 3.0 branch)

    However, the release of 3.0 is still some time off and it is not yet time to start projects on it. Version 2.x has enterprise support through 2023 and as the maintainers we are starting new enterprise projects on 2.x today, so I would not recommend waiting.

    3.0 development slowed down a bit this summer as we span Apostrophe Technologies out into its own company and took on additional enterprise support work based on 2.x, but in the long run this is what is allowing us to staff up and pursue 3.0 much more effectively in the coming months.