1.0.0-alpha.2 • Published 2 years ago

drupal-rjsf v1.0.0-alpha.2

Weekly downloads
-
License
GPL-3.0-or-later
Repository
-
Last release
2 years ago

RJSF

The module integrats the ReactJSONSchemForm js library into Drupal's Form and Field API.

Install Steps

FOR THIS MODULE TO FUNCTION THE EDITOR AND EDITOR WIDGETS MUST BE LOCATED IN THE LIBRARIES DIRECTORY

The easiest way to ensure the editor and widgets are available is to use asset-packagist and require the drupal-rjsf npm package as a composer dependency, the npm package is managed and developed alongside the Drupal module. To set up your project to use asset-packagist see here. Then run composer require npm-asset/drupal-rjsf.

Examples

Form API

To use an RJSF editor in Drupal all that needs to be done is to add a rjsf_editor element to the form and provide RJSF schema configs.

Usage Example

$form['rjsf_element'] = [
  '#type' => 'rjsf_editor',
  '#server_validation' => TRUE,
  '#client_validation' => TRUE,
  '#schema' => [
    'title' => 'A registration form',
    'description' => 'A simple form example.',
    'type' => 'object',
    'required' => ['firstName', 'lastName'],
    'properties' => [
      'firstName' => [
        'type' => 'string',
        'title' => 'First name',
        'default' => 'Chuck',
      ],
      'lastName' => [
        'type' => 'string',
        'title' => 'Last name',
      ],
      'telephone' => [
        'type' => 'string',
        'title' => 'Telephone',
        'minLength' => 10,
      ],
    ],
  ],
  '#uiSchema' => [
    'firstName' => [
      'ui:autofocus' => TRUE,
      'ui:emptyValue' => '',
      'ui:autocomplete' => 'family-name',
    ],
    'lastName' => [
      'ui:emptyValue' => '',
      'ui:autocomplete' => 'given-name',
    ],
    'age' => [
      'ui:widget' => 'updown',
      'ui:title' => 'Age of person',
      'ui:description' => '(earthian year)',
    ],
    'bio' => [
      'ui:widget' => 'textarea',
    ],
    'password' => [
      'ui:widget' => 'password',
      'ui:help' => 'Hint: Make it strong!',
    ],
    'date' => [
      'ui:widget' => 'alt-datetime',
    ],
    'telephone' => [
      'ui:options' => [
        'inputType' => 'tel',
      ],
    ],
  ],
];

Accessing Drupal Data in an RJSF Form

To leverage the full power of Drupal with RJSF this module provides several submodules to cover the most common use cases of needing to get data out of Drupal. When enabled these modules provide RJSF fields/widgets that integrate into Drupal via api to provide similar(or improved) functionality to what is avialble via Drupal FieldAPI. These custom form components leverage Module Federation to that allows for integrating custom widgets into the editing experience, to learn how to create your own custom widgets see Creating your own RJSF Elements below.

Entity Browser(rjsf_entity_browser)

The integration of entity browser allows for RJSF to reference any entity inside of Drupal. With support for multiple browsers developers can define as many entity browser's as they want tailoring the editing experience to the content.

For how to use this field see the module README

Rich Text(rjsf_rich_text)

Drupal's WYSIWYG editing experience is available inside of components. Developers can set up, configure, control permissions for all rich text styles all from within the normal Drupal pages.

For how to use this field see the module README

Autocomplete(rjsf_entity_autocomplete)

A nearly identical functional replacement of Drupal's entity autocomplete field.

For how to use this field see the module README

Links

@TODO write a link widget to reference Drupal content

Creating your own RJSF Elements

Additional editor elements can be added to the editor by creating your own RJSF element and then adding it to the editor via Module Federation. To see the pattern in use checkout the submodules under the /modules folder and for a more detailed example and instructions on implementing your own see rjsf_example_widget module and README.

To add a new element you'll roughly need to

  • Determine if you are creating a RJSF widget or field. See the RJSF docs here for more details on the differences
  • Create a new Drupal module(or use an existing one)
  • Decide on a namespace for the new element
  • Determine if the element will need any new definitions, formats, or filters
  • Determine if you're pulling data from Drupal and if so what APIs will be used to retrieve data?
  • Implement the RJSF React element with support for the various properties available to fields/widgets(required, disabled, default values, etc)
  • Document usage examples, new filters, new formats, new defintions in the module's README
  • Never trust the data submitted until it's been validated on the backend by the validator service

IMPORTANT if the elements' data can't be validated purely using JSON Schema validation you need to implement a filter so the data can be validated on the backend. This is almost always required when dealing with references to Drupal entities or pulling data from an API.

Preprocessing Data

Inevitably when working with data you want to massage it before passing it on to the next part of the system. This is why the module includes a way to define and use render preprocess plugins with the data. By default this service is not used in the module and is provided only as a helper/utility to integrate into other systems and patterns. Using the service is as simple as

$processed = \Drupal::getContainer()->get('rjsf.render.preprocessor')->preprocess(
  $values,
  $componentDef['renderPreprocess'] ?? [],
  $componentDef['component']['schema'],
  $componentDef['component']['uiSchema'] ?? []
);

Adding preprocess plugins to your schema

To add preprocessors your schema add a new top level property renderPreprocess next to schema and uiSchema. The structure for renderPreprocess follows the structure for uiSchema meaning the property structure from schema should be replicated and at each level $plugins can be added. $plugins is expected to be an object or array of objects. When multiple plugins are added for one field the plugins are chained with the first plugin passing the processed value to the second plugin and so on until all plugins have run.

Each preprocessor object needs to contain a $plugin value mapping to the plugin id for the preprocessor and optionally a $vars key representing an object of variables available for a specific preprocessor.

Examples of how to implement preprocessors can be found in src/Plugin/Rjsf/RenderPreprocess

Single preprocess plugin

schema: { ... },
uiSchema: { ... },
renderPreprocess: {
  examplefield: {
    $plugins: {
      $plugin: 'preproccess_plugin_id'
    }
  }
}

Multiple preprocess plugins

schema: { ... },
uiSchema: { ... },
renderPreprocess: {
  examplefield: {
    $plugins: {[
      {
        $plugin: 'preproccess_plugin_id',
        $vars: {
          var_1: 'some value',
          var_2: 'some value',
        }
      },
      {
        $plugin: 'preproccess_plugin_id',
        $vars: {
          var_1: 'some value',
          var_2: 'some value',
        }
      }
    ]}
  }
}

Defined by this Module

Widgets

The RJSF module itself doesn't define any new widgets as it is focused only on integrating react-jsonschema-form into Drupal and not on extending react-jsonschema-form's capabilities. Any necessary widgets created will be as a submodule or a separate module entirely.

Fields

The RJSF module itself doesn't define any new fields as it is focused only on integrating react-jsonschema-form into Drupal and not on extending react-jsonschema-form's capabilities. Any necessary widgets created will be as a submodule or a separate module entirely.

Formats

Format's used by the RJSF submodules that are likely to be a standard representation of Drupal data are defined at the top level in RJSF itself so that other modules can make use of the same structure without needing to enable the RJSF's submodule.

entity_reference

@TODO document

Filters

Filter's used by the RJSF submodules that are likely to be a standard representation of Drupal data are defined at the top level in RJSF itself so that other modules can make use of the same structure without needing to enable the RJSF's submodule.

entity_type

@TODO document

rich_text

@TODO document

Definitions

Definition's used by the RJSF submodules that are likely to be a standard representation of Drupal data are defined at the top level in RJSF itself so that other modules can make use of the same structure without needing to enable the RJSF's submodule.

entity_reference

@TODO document

rich_text

@TODO document

Preprocessors

entity_load

This preprocessor is compatible with the entity_reference definition defined by RJSF so will accept an object or array of objects containing the entity type and entity uuid. The processed result will be the Drupal entity or an array of Drupal entities along with the aggregate cache data for all entities loaded.

Example

Minimum Data

{
  examplefield: {
    "type": 'node',
    'uuid': '123qwer-123qwe-123ersdf'
  }
}

Schema

renderPreprocess: {
  examplefield: {
    $plugins: {
      $plugin: 'entity_load'
    }
  }
}

Vars

This preprocessor doesn't support any variables.

Processed

[
'original' => // the original value,
'processed' => // the Drupal entity or array of Drupal entities
'cacheable_metadata' => // the aggregate cache data for all Drupal entities loaded
]

entity_render

This preprocessor is compatible with the entity_reference definition defined by RJSF so will accept an object or array of objects containing the entity type and entity uuid. The processed result will be html representing the fully rendered Drupal entity or an array html representingg the fully rendered Drupal entities along with the aggregate cache data for all entities loaded.

Example

Minimum Data

{
  examplefield: {
    "type": 'node',
    'uuid': '123qwer-123qwe-123ersdf'
  }
}

Schema

renderPreprocess: {
  examplefield: {
    $plugins: {
      $plugin: 'entity_load'
      $vars: {
        view_mode: 'full'
      }
    }
  }
}

Vars

This preprocessor allows for a view_mode variable to define which view mode to render the entity with. If not included the view mode defaults to 'full'.

Processed

[
'original' => // the original value,
'processed' => // a string of html or an array of html strings
'cacheable_metadata' => // the aggregate cache data for all Drupal entities rendered
]

rich_text

USING THIS PREPROCESSOR IS REQUIRED TO SECURELY RENDER RICH TEXT FIELDS

This preprocessor is compatible with the rich_text definition defined by RJSF so will accept an object containing the value and format. The processed result will html representing the source text after being processed by Drupal format filters along with the aggregate cache data for any entities rendered by the format/filters.

Example

Minimum Data

{
  examplefield: {
    "value": '<div>some rich text strings</div>',
    'format': 'example_format'
  }
}

Schema

renderPreprocess: {
  examplefield: {
    $plugins: {
      $plugin: 'rich_text'
    }
  }
}

Vars

This preprocessor doesn't support any variables.

Processed

[
'original' => // the original value,
'processed' => // a string of html
'cacheable_metadata' => // any cache data from the rich text rendering process
]

Development

The easiest path to work on the RJSF javascript is to symlink the {path to library directory}/drupal-rjsf/dist directory directly to the {path to modules directory}/contrib/rjsf/dist directory. Once that is done move into {path to modules directory}/contrib/rjsf from there yarn build:all which will build the editor and all fields/widgets. The submodules also provide their own yarn build and yarn build:dev commands which can be used to rebuild just their js without having to rebuild everything.

  1. mkdir {path to library directory}/drupal-rjsf
  2. cd {path to library directory}/drupal-rjsf
  3. ln -s ../../modules/contrib/rjsf/dist/ dist
  4. cd ../../modules/contrib/rjsf
  5. yarn build:all